add password toggle icon for all password inputs & use share folder checkbox for remember password in login page.
This commit is contained in:
7
backend/prisma/migrations/20260710150528/migration.sql
Normal file
7
backend/prisma/migrations/20260710150528/migration.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "lab_case_tasks" ALTER COLUMN "updatedAt" DROP DEFAULT,
|
||||
ALTER COLUMN "prosthesisTypeCode" DROP DEFAULT,
|
||||
ALTER COLUMN "workflowStepCode" DROP DEFAULT;
|
||||
|
||||
-- RenameIndex
|
||||
ALTER INDEX "lab_case_tasks_labCaseId_treatmentDetailId_prosthesisTypeCode_s" RENAME TO "lab_case_tasks_labCaseId_treatmentDetailId_prosthesisTypeCo_key";
|
||||
@@ -77,6 +77,8 @@
|
||||
"confirmPassword": "Confirm password",
|
||||
"fullName": "Full name",
|
||||
"rememberMe": "Remember me",
|
||||
"showPassword": "Show password",
|
||||
"hidePassword": "Hide password",
|
||||
"forgotPassword": "Forgot your password?",
|
||||
"invalidCredentials": "Invalid email or password",
|
||||
"loginFailed": "Login failed",
|
||||
|
||||
@@ -77,6 +77,8 @@
|
||||
"confirmPassword": "تأیید رمز عبور",
|
||||
"fullName": "نام و نام خانوادگی",
|
||||
"rememberMe": "مرا به خاطر بسپار",
|
||||
"showPassword": "نمایش رمز عبور",
|
||||
"hidePassword": "پنهان کردن رمز عبور",
|
||||
"forgotPassword": "رمز عبور خود را فراموش کردهاید؟",
|
||||
"invalidCredentials": "ایمیل یا رمز عبور نامعتبر است",
|
||||
"loginFailed": "ورود ناموفق بود",
|
||||
|
||||
@@ -77,6 +77,8 @@
|
||||
"confirmPassword": "Bevestig wachtwoord",
|
||||
"fullName": "Volledige naam",
|
||||
"rememberMe": "Onthoud mij",
|
||||
"showPassword": "Wachtwoord tonen",
|
||||
"hidePassword": "Wachtwoord verbergen",
|
||||
"forgotPassword": "Wachtwoord vergeten?",
|
||||
"invalidCredentials": "Ongeldig e-mailadres of wachtwoord",
|
||||
"loginFailed": "Inloggen mislukt",
|
||||
|
||||
@@ -82,6 +82,8 @@ const en = {
|
||||
confirmPassword: 'Confirm password',
|
||||
fullName: 'Full name',
|
||||
rememberMe: 'Remember me',
|
||||
showPassword: 'Show password',
|
||||
hidePassword: 'Hide password',
|
||||
forgotPassword: 'Forgot your password?',
|
||||
invalidCredentials: 'Invalid email or password',
|
||||
loginFailed: 'Login failed',
|
||||
|
||||
@@ -103,6 +103,11 @@ export default function AccountSettingsPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const passwordToggleLabels = {
|
||||
show: tAuth('showPassword'),
|
||||
hide: tAuth('hidePassword'),
|
||||
};
|
||||
|
||||
if (!isAuthReady || !user) {
|
||||
return (
|
||||
<p className="text-text-secondary text-sm">{tCommon('loadingEllipsis')}</p>
|
||||
@@ -139,6 +144,7 @@ export default function AccountSettingsPage() {
|
||||
placeholder={tAuth('passwordPlaceholder')}
|
||||
error={errors.currentPassword?.message}
|
||||
icon={<Lock className="h-5 w-5 icon-flat" />}
|
||||
passwordToggleLabels={passwordToggleLabels}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -149,6 +155,7 @@ export default function AccountSettingsPage() {
|
||||
placeholder={tAuth('passwordPlaceholder')}
|
||||
error={errors.newPassword?.message}
|
||||
icon={<Lock className="h-5 w-5 icon-flat" />}
|
||||
passwordToggleLabels={passwordToggleLabels}
|
||||
/>
|
||||
|
||||
<Input
|
||||
@@ -158,6 +165,7 @@ export default function AccountSettingsPage() {
|
||||
placeholder={tAuth('passwordPlaceholder')}
|
||||
error={errors.confirmPassword?.message}
|
||||
icon={<Lock className="h-5 w-5 icon-flat" />}
|
||||
passwordToggleLabels={passwordToggleLabels}
|
||||
/>
|
||||
|
||||
{error && (
|
||||
|
||||
@@ -93,6 +93,11 @@ function AcceptInviteContent() {
|
||||
}
|
||||
}
|
||||
|
||||
const passwordToggleLabels = {
|
||||
show: t('showPassword'),
|
||||
hide: t('hidePassword'),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen app-web-bg flex items-center justify-center p-4">
|
||||
<div className="w-full max-w-md surface-card p-6 space-y-5">
|
||||
@@ -134,12 +139,14 @@ function AcceptInviteContent() {
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
passwordToggleLabels={passwordToggleLabels}
|
||||
/>
|
||||
<Input
|
||||
label={t('labelConfirmPassword')}
|
||||
type="password"
|
||||
value={confirmPassword}
|
||||
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||
passwordToggleLabels={passwordToggleLabels}
|
||||
/>
|
||||
<Button type="button" fullWidth isLoading={submitting} onClick={() => void onAccept()}>
|
||||
{t('activateAccount')}
|
||||
|
||||
@@ -154,6 +154,11 @@ function AcceptOrganizationInviteContent() {
|
||||
}
|
||||
};
|
||||
|
||||
const passwordToggleLabels = {
|
||||
show: t('showPassword'),
|
||||
hide: t('hidePassword'),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen app-web-bg flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="sm:mx-auto sm:w-full sm:max-w-md">
|
||||
@@ -226,6 +231,7 @@ function AcceptOrganizationInviteContent() {
|
||||
placeholder={t('passwordPlaceholder')}
|
||||
error={errors.password?.message}
|
||||
icon={<Lock className="h-5 w-5 icon-flat" />}
|
||||
passwordToggleLabels={passwordToggleLabels}
|
||||
/>
|
||||
<Input
|
||||
label={t('confirmPassword')}
|
||||
@@ -234,6 +240,7 @@ function AcceptOrganizationInviteContent() {
|
||||
placeholder={t('passwordPlaceholder')}
|
||||
error={errors.confirmPassword?.message}
|
||||
icon={<Lock className="h-5 w-5 icon-flat" />}
|
||||
passwordToggleLabels={passwordToggleLabels}
|
||||
/>
|
||||
<Button type="button" variant="primary" onClick={() => void handleNext()} fullWidth>
|
||||
{tCommon('continue')}
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Mail, Lock } from 'lucide-react';
|
||||
import { useAuth } from '@/lib/hooks/useAuth';
|
||||
import { getRememberedEmail } from '@/lib/auth/rememberMe';
|
||||
import { Button } from '@/components/ui/shared/Button';
|
||||
import { Checkbox } from '@/components/ui/shared/Checkbox';
|
||||
import { Input } from '@/components/ui/shared/Input';
|
||||
import { TopBarControls } from '@/components/ui/shared/TopBarControls';
|
||||
|
||||
@@ -48,6 +49,8 @@ export default function LoginPage() {
|
||||
const {
|
||||
register,
|
||||
handleSubmit,
|
||||
watch,
|
||||
setValue,
|
||||
formState: { errors },
|
||||
} = useForm<LoginForm>({
|
||||
resolver: zodResolver(loginSchema),
|
||||
@@ -57,6 +60,8 @@ export default function LoginPage() {
|
||||
},
|
||||
});
|
||||
|
||||
const rememberMe = watch('rememberMe');
|
||||
|
||||
const onSubmit = async (data: LoginForm) => {
|
||||
try {
|
||||
setError(null);
|
||||
@@ -114,20 +119,18 @@ export default function LoginPage() {
|
||||
placeholder={t('passwordPlaceholder')}
|
||||
error={errors.password?.message}
|
||||
icon={<Lock className="h-5 w-5 icon-flat" />}
|
||||
passwordToggleLabels={{
|
||||
show: t('showPassword'),
|
||||
hide: t('hidePassword'),
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center">
|
||||
<input
|
||||
id="remember-me"
|
||||
type="checkbox"
|
||||
className="h-4 w-4 rounded border-border bg-background-secondary text-primary focus:ring-primary/40"
|
||||
{...register('rememberMe')}
|
||||
<Checkbox
|
||||
checked={rememberMe}
|
||||
onChange={(checked) => setValue('rememberMe', checked)}
|
||||
label={t('rememberMe')}
|
||||
/>
|
||||
<label htmlFor="remember-me" className="ml-2 block text-sm text-text-secondary">
|
||||
{t('rememberMe')}
|
||||
</label>
|
||||
</div>
|
||||
<div className="text-sm">
|
||||
<Link href="/forgot-password" className="font-medium text-primary hover:opacity-90">
|
||||
{t('forgotPassword')}
|
||||
|
||||
@@ -115,6 +115,11 @@ export default function RegisterPage() {
|
||||
}
|
||||
};
|
||||
|
||||
const passwordToggleLabels = {
|
||||
show: t('showPassword'),
|
||||
hide: t('hidePassword'),
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative min-h-screen app-web-bg flex flex-col justify-center py-12 sm:px-6 lg:px-8">
|
||||
<div className="absolute top-4 right-4">
|
||||
@@ -189,6 +194,7 @@ export default function RegisterPage() {
|
||||
placeholder={t('passwordPlaceholder')}
|
||||
error={errors.password?.message}
|
||||
icon={<Lock className="h-5 w-5 icon-flat" />}
|
||||
passwordToggleLabels={passwordToggleLabels}
|
||||
/>
|
||||
<Input
|
||||
label={t('confirmPassword')}
|
||||
@@ -197,6 +203,7 @@ export default function RegisterPage() {
|
||||
placeholder={t('passwordPlaceholder')}
|
||||
error={errors.confirmPassword?.message}
|
||||
icon={<Lock className="h-5 w-5 icon-flat" />}
|
||||
passwordToggleLabels={passwordToggleLabels}
|
||||
/>
|
||||
<Button type="button" variant="primary" onClick={handleNext} fullWidth>
|
||||
{tCommon('continue')}
|
||||
|
||||
@@ -1,18 +1,63 @@
|
||||
'use client';
|
||||
|
||||
import React, { forwardRef, useId } from 'react';
|
||||
import React, { forwardRef, useId, useState } from 'react';
|
||||
import { Eye, EyeOff } from 'lucide-react';
|
||||
|
||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
label?: string;
|
||||
error?: string;
|
||||
icon?: React.ReactNode;
|
||||
endIcon?: React.ReactNode;
|
||||
passwordToggleLabels?: {
|
||||
show: string;
|
||||
hide: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
({ label, error, icon, className = '', id, ...props }, ref) => {
|
||||
(
|
||||
{
|
||||
label,
|
||||
error,
|
||||
icon,
|
||||
endIcon,
|
||||
passwordToggleLabels,
|
||||
className = '',
|
||||
id,
|
||||
type,
|
||||
...props
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const genId = useId();
|
||||
const inputId = id ?? genId;
|
||||
|
||||
const resolvedEndIcon =
|
||||
endIcon ??
|
||||
(passwordToggleLabels ? (
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setShowPassword((visible) => !visible)}
|
||||
className="rounded p-0.5 text-text-muted transition-colors hover:text-text-secondary"
|
||||
aria-label={
|
||||
showPassword ? passwordToggleLabels.hide : passwordToggleLabels.show
|
||||
}
|
||||
>
|
||||
{showPassword ? (
|
||||
<EyeOff className="h-5 w-5 icon-flat" />
|
||||
) : (
|
||||
<Eye className="h-5 w-5 icon-flat" />
|
||||
)}
|
||||
</button>
|
||||
) : undefined);
|
||||
|
||||
const resolvedType = passwordToggleLabels
|
||||
? showPassword
|
||||
? 'text'
|
||||
: 'password'
|
||||
: type;
|
||||
|
||||
return (
|
||||
<div className="w-full">
|
||||
{label && (
|
||||
@@ -31,15 +76,22 @@ export const Input = forwardRef<HTMLInputElement, InputProps>(
|
||||
</div>
|
||||
)}
|
||||
|
||||
{resolvedEndIcon && (
|
||||
<div className="absolute inset-y-0 right-0 pr-3 flex items-center text-text-muted">
|
||||
{resolvedEndIcon}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<input
|
||||
ref={ref}
|
||||
id={inputId}
|
||||
type={resolvedType}
|
||||
className={`
|
||||
w-full rounded-[var(--radius-md)] border
|
||||
${error ? 'border-red-500' : 'border-border'}
|
||||
bg-background-secondary/90 text-text-primary
|
||||
|
||||
${icon ? 'pl-10' : 'pl-4'} pr-4 py-2
|
||||
${icon ? 'pl-10' : 'pl-4'} ${resolvedEndIcon ? 'pr-10' : 'pr-4'} py-2
|
||||
|
||||
placeholder:text-text-muted
|
||||
|
||||
|
||||
Reference in New Issue
Block a user