mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-15 19:10:47 +02:00
[V2] refactor(ui): replace native inputs with Mantine components (#4898)
# Description of Changes #### Before <img width="779" height="768" alt="image" src="https://github.com/user-attachments/assets/a6d63bac-e27d-4bd8-b44e-fa72333f4c59" /> #### After <img width="779" height="768" alt="image" src="https://github.com/user-attachments/assets/f39d8d92-802a-40ff-a7db-2b6a187b6847" /> <!-- Please provide a summary of the changes, including: - What was changed - Why the change was made - Any challenges encountered Closes #(issue_number) --> --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Signed-off-by: Balázs Szücs <[email protected]> Co-authored-by: Reece Browne <[email protected]>
This commit is contained in:
co-authored by
Reece Browne
parent
1810a12cf3
commit
a3e1cc8393
@@ -185,8 +185,8 @@
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 1rem 1rem; /* 16px 16px */
|
||||
justify-content: flex-start;
|
||||
padding: 0.75rem 1rem; /* 12px 16px */
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.75rem; /* 12px */
|
||||
background-color: var(--auth-card-bg-light-only);
|
||||
@@ -195,6 +195,12 @@
|
||||
color: var(--auth-text-primary-light-only);
|
||||
cursor: pointer;
|
||||
gap: 0.75rem; /* 12px */
|
||||
font-family: inherit;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.oauth-button-vertical:hover:not(:disabled) {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.oauth-button-vertical:disabled {
|
||||
@@ -202,6 +208,11 @@
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.oauth-button-vertical:focus-visible {
|
||||
outline: 2px solid var(--auth-border-focus-light-only);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.oauth-icon-small {
|
||||
width: 1.75rem; /* 28px */
|
||||
height: 1.75rem; /* 28px */
|
||||
@@ -217,6 +228,8 @@
|
||||
.oauth-icon-tiny {
|
||||
width: 1.25rem; /* 20px */
|
||||
height: 1.25rem; /* 20px */
|
||||
display: block;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Login Header Styles */
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import '@app/routes/authShared/auth.css';
|
||||
import { TextInput, PasswordInput, Button } from '@mantine/core';
|
||||
|
||||
interface EmailPasswordFormProps {
|
||||
email: string
|
||||
@@ -38,49 +39,46 @@ export default function EmailPasswordForm({
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="auth-fields">
|
||||
<div className="auth-field">
|
||||
<label htmlFor="email" className="auth-label">{t('login.username', 'Username')}</label>
|
||||
<input
|
||||
<TextInput
|
||||
id="email"
|
||||
label={t('login.username', 'Username')}
|
||||
type="text"
|
||||
name="username"
|
||||
autoComplete="username"
|
||||
placeholder={t('login.enterUsername', 'Enter username')}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className={`auth-input ${fieldErrors.email ? 'auth-input-error' : ''}`}
|
||||
error={fieldErrors.email}
|
||||
classNames={{ label: 'auth-label' }}
|
||||
/>
|
||||
{fieldErrors.email && (
|
||||
<div className="auth-field-error">{fieldErrors.email}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{showPasswordField && (
|
||||
<div className="auth-field">
|
||||
<label htmlFor="password" className="auth-label">{t('login.password')}</label>
|
||||
<input
|
||||
<PasswordInput
|
||||
id="password"
|
||||
type="password"
|
||||
label={t('login.password')}
|
||||
name="current-password"
|
||||
autoComplete="current-password"
|
||||
placeholder={t('login.enterPassword')}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className={`auth-input ${fieldErrors.password ? 'auth-input-error' : ''}`}
|
||||
error={fieldErrors.password}
|
||||
classNames={{ label: 'auth-label' }}
|
||||
/>
|
||||
{fieldErrors.password && (
|
||||
<div className="auth-field-error">{fieldErrors.password}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<button
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSubmitting || !email || (showPasswordField && !password)}
|
||||
className="auth-button"
|
||||
fullWidth
|
||||
loading={isSubmitting}
|
||||
>
|
||||
{submitButtonText}
|
||||
</button>
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Button } from '@mantine/core';
|
||||
|
||||
interface NavigationLinkProps {
|
||||
onClick: () => void
|
||||
text: string
|
||||
@@ -7,13 +9,14 @@ interface NavigationLinkProps {
|
||||
export default function NavigationLink({ onClick, text, isDisabled = false }: NavigationLinkProps) {
|
||||
return (
|
||||
<div className="navigation-link-container">
|
||||
<button
|
||||
<Button
|
||||
onClick={onClick}
|
||||
disabled={isDisabled}
|
||||
className="navigation-link-button"
|
||||
variant="subtle"
|
||||
>
|
||||
{text}
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { BASE_PATH } from '@app/constants/app';
|
||||
import { type OAuthProvider } from '@app/auth/oauthTypes';
|
||||
import { Button } from '@mantine/core';
|
||||
|
||||
// Debug flag to show all providers for UI testing
|
||||
// Set to true to see all SSO options regardless of backend configuration
|
||||
@@ -69,14 +70,15 @@ export default function OAuthButtons({ onProviderClick, isSubmitting, layout = '
|
||||
<div className="oauth-container-icons">
|
||||
{providers.map((p) => (
|
||||
<div key={p.id} title={`${t('login.signInWith', 'Sign in with')} ${p.label}`}>
|
||||
<button
|
||||
<Button
|
||||
onClick={() => onProviderClick(p.id)}
|
||||
disabled={isSubmitting}
|
||||
className="oauth-button-icon"
|
||||
aria-label={`${t('login.signInWith', 'Sign in with')} ${p.label}`}
|
||||
variant="default"
|
||||
>
|
||||
<img src={`${BASE_PATH}/Login/${p.file}`} alt={p.label} className="oauth-icon-small"/>
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -88,14 +90,15 @@ export default function OAuthButtons({ onProviderClick, isSubmitting, layout = '
|
||||
<div className="oauth-container-grid">
|
||||
{providers.map((p) => (
|
||||
<div key={p.id} title={`${t('login.signInWith', 'Sign in with')} ${p.label}`}>
|
||||
<button
|
||||
<Button
|
||||
onClick={() => onProviderClick(p.id)}
|
||||
disabled={isSubmitting}
|
||||
className="oauth-button-grid"
|
||||
aria-label={`${t('login.signInWith', 'Sign in with')} ${p.label}`}
|
||||
variant="default"
|
||||
>
|
||||
<img src={`${BASE_PATH}/Login/${p.file}`} alt={p.label} className="oauth-icon-medium"/>
|
||||
</button>
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -105,16 +108,18 @@ export default function OAuthButtons({ onProviderClick, isSubmitting, layout = '
|
||||
return (
|
||||
<div className="oauth-container-vertical">
|
||||
{providers.map((p) => (
|
||||
<button
|
||||
key={p.id}
|
||||
onClick={() => onProviderClick(p.id)}
|
||||
disabled={isSubmitting}
|
||||
className="oauth-button-vertical"
|
||||
title={p.label}
|
||||
>
|
||||
<img src={`${BASE_PATH}/Login/${p.file}`} alt={p.label} className="oauth-icon-tiny" />
|
||||
{p.label}
|
||||
</button>
|
||||
<div key={p.id} title={`${t('login.signInWith', 'Sign in with')} ${p.label}`}>
|
||||
<Button
|
||||
onClick={() => onProviderClick(p.id)}
|
||||
disabled={isSubmitting}
|
||||
className="oauth-button-vertical"
|
||||
aria-label={`${t('login.signInWith', 'Sign in with')} ${p.label}`}
|
||||
variant="default"
|
||||
>
|
||||
<img src={`${BASE_PATH}/Login/${p.file}`} alt={p.label} className="oauth-icon-tiny" />
|
||||
<span>{p.label}</span>
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
import '@app/routes/authShared/auth.css';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Checkbox } from '@mantine/core';
|
||||
import { Checkbox, TextInput, PasswordInput, Button } from '@mantine/core';
|
||||
import { SignupFieldErrors } from '@app/routes/signup/SignupFormValidation';
|
||||
|
||||
interface SignupFormProps {
|
||||
@@ -53,57 +53,49 @@ export default function SignupForm({
|
||||
<div className="auth-fields">
|
||||
{showName && (
|
||||
<div className="auth-field">
|
||||
<label htmlFor="name" className="auth-label">{t('signup.name')}</label>
|
||||
<input
|
||||
<TextInput
|
||||
id="name"
|
||||
type="text"
|
||||
label={t('signup.name')}
|
||||
name="name"
|
||||
autoComplete="name"
|
||||
placeholder={t('signup.enterName')}
|
||||
value={name}
|
||||
onChange={(e) => setName?.(e.target.value)}
|
||||
className={`auth-input ${fieldErrors.name ? 'auth-input-error' : ''}`}
|
||||
error={fieldErrors.name}
|
||||
classNames={{ label: 'auth-label' }}
|
||||
/>
|
||||
{fieldErrors.name && (
|
||||
<div className="auth-field-error">{fieldErrors.name}</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="auth-field">
|
||||
<label htmlFor="email" className="auth-label">{t('signup.email')}</label>
|
||||
<input
|
||||
<TextInput
|
||||
id="email"
|
||||
label={t('signup.email')}
|
||||
type="email"
|
||||
name="email"
|
||||
autoComplete="email"
|
||||
placeholder={t('signup.enterEmail')}
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && !isSubmitting && onSubmit()}
|
||||
className={`auth-input ${fieldErrors.email ? 'auth-input-error' : ''}`}
|
||||
onKeyDown={(e) => e.key === 'Enter' && !isSubmitting && onSubmit()}
|
||||
error={fieldErrors.email}
|
||||
classNames={{ label: 'auth-label' }}
|
||||
/>
|
||||
{fieldErrors.email && (
|
||||
<div className="auth-field-error">{fieldErrors.email}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="auth-field">
|
||||
<label htmlFor="password" className="auth-label">{t('signup.password')}</label>
|
||||
<input
|
||||
<PasswordInput
|
||||
id="password"
|
||||
type="password"
|
||||
label={t('signup.password')}
|
||||
name="new-password"
|
||||
autoComplete="new-password"
|
||||
placeholder={t('signup.enterPassword')}
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && !isSubmitting && onSubmit()}
|
||||
className={`auth-input ${fieldErrors.password ? 'auth-input-error' : ''}`}
|
||||
onKeyDown={(e) => e.key === 'Enter' && !isSubmitting && onSubmit()}
|
||||
error={fieldErrors.password}
|
||||
classNames={{ label: 'auth-label' }}
|
||||
/>
|
||||
{fieldErrors.password && (
|
||||
<div className="auth-field-error">{fieldErrors.password}</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div
|
||||
@@ -112,21 +104,18 @@ export default function SignupForm({
|
||||
style={{ maxHeight: showConfirm ? 96 : 0, opacity: showConfirm ? 1 : 0 }}
|
||||
>
|
||||
<div className="auth-field">
|
||||
<label htmlFor="confirmPassword" className="auth-label">{t('signup.confirmPassword')}</label>
|
||||
<input
|
||||
<PasswordInput
|
||||
id="confirmPassword"
|
||||
type="password"
|
||||
label={t('signup.confirmPassword')}
|
||||
name="new-password"
|
||||
autoComplete="new-password"
|
||||
placeholder={t('signup.confirmPasswordPlaceholder')}
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
onKeyPress={(e) => e.key === 'Enter' && !isSubmitting && onSubmit()}
|
||||
className={`auth-input ${fieldErrors.confirmPassword ? 'auth-input-error' : ''}`}
|
||||
onKeyDown={(e) => e.key === 'Enter' && !isSubmitting && onSubmit()}
|
||||
error={fieldErrors.confirmPassword}
|
||||
classNames={{ label: 'auth-label' }}
|
||||
/>
|
||||
{fieldErrors.confirmPassword && (
|
||||
<div className="auth-field-error">{fieldErrors.confirmPassword}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -152,13 +141,15 @@ export default function SignupForm({
|
||||
)}
|
||||
|
||||
{/* Sign Up Button */}
|
||||
<button
|
||||
<Button
|
||||
onClick={onSubmit}
|
||||
disabled={isSubmitting || !email || !password || !confirmPassword || (showTerms && !agree)}
|
||||
className="auth-button"
|
||||
fullWidth
|
||||
loading={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? t('signup.creatingAccount') : t('signup.signUp')}
|
||||
</button>
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user