refactor: update authentication logic to support email or username

- Modified the login schema to accept either an email or username for user authentication.
- Updated the AuthService to find users by email or username.
- Adjusted localization files to include new labels and placeholders for email or username input across multiple languages.
- Refactored the login form component to reflect the changes in the schema and improve user experience.
This commit is contained in:
Daniel Luiz Alves
2025-06-20 13:45:37 -03:00
parent 561e8faf33
commit a865aabed0
23 changed files with 115 additions and 65 deletions

View File

@@ -9,7 +9,7 @@ export const createPasswordSchema = async () => {
};
export const LoginSchema = z.object({
email: z.string().email("Invalid email").describe("User email"),
emailOrUsername: z.string().min(1, "Email or username is required").describe("User email or username"),
password: z.string().min(6, "Password must be at least 6 characters").describe("User password"),
});
export type LoginInput = z.infer<typeof LoginSchema>;

View File

@@ -17,7 +17,7 @@ export async function authRoutes(app: FastifyInstance) {
const passwordSchema = await createPasswordSchema();
const loginSchema = z.object({
email: z.string().email("Invalid email").describe("User email"),
emailOrUsername: z.string().min(1, "Email or username is required").describe("User email or username"),
password: passwordSchema,
});

View File

@@ -13,7 +13,7 @@ export class AuthService {
private emailService = new EmailService();
async login(data: LoginInput) {
const user = await this.userRepository.findUserByEmail(data.email);
const user = await this.userRepository.findUserByEmailOrUsername(data.emailOrUsername);
if (!user) {
throw new Error("Invalid credentials");
}

View File

@@ -7,6 +7,7 @@ export interface IUserRepository {
findUserByEmail(email: string): Promise<User | null>;
findUserById(id: string): Promise<User | null>;
findUserByUsername(username: string): Promise<User | null>;
findUserByEmailOrUsername(emailOrUsername: string): Promise<User | null>;
listUsers(): Promise<User[]>;
updateUser(data: UpdateUserInput & { password?: string }): Promise<User>;
deleteUser(id: string): Promise<User>;
@@ -41,6 +42,14 @@ export class PrismaUserRepository implements IUserRepository {
return prisma.user.findUnique({ where: { username } });
}
async findUserByEmailOrUsername(emailOrUsername: string): Promise<User | null> {
return prisma.user.findFirst({
where: {
OR: [{ email: emailOrUsername }, { username: emailOrUsername }],
},
});
}
async listUsers(): Promise<User[]> {
return prisma.user.findMany();
}

View File

@@ -202,6 +202,8 @@
"login": {
"welcome": "مرحبا بك",
"signInToContinue": "قم بتسجيل الدخول للمتابعة",
"emailOrUsernameLabel": "البريد الإلكتروني أو اسم المستخدم",
"emailOrUsernamePlaceholder": "أدخل بريدك الإلكتروني أو اسم المستخدم",
"emailLabel": "البريد الإلكتروني",
"emailPlaceholder": "أدخل بريدك الإلكتروني",
"passwordLabel": "كلمة المرور",
@@ -852,6 +854,7 @@
"passwordLength": "يجب أن تحتوي كلمة المرور على 8 أحرف على الأقل",
"passwordsMatch": "كلمتا المرور غير متطابقتين",
"emailRequired": "البريد الإلكتروني مطلوب",
"emailOrUsernameRequired": "البريد الإلكتروني أو اسم المستخدم مطلوب",
"passwordRequired": "كلمة المرور مطلوبة",
"nameRequired": "الاسم مطلوب",
"required": "هذا الحقل مطلوب"

View File

@@ -202,6 +202,8 @@
"login": {
"welcome": "Willkommen zu",
"signInToContinue": "Melden Sie sich an, um fortzufahren",
"emailOrUsernameLabel": "E-Mail-Adresse oder Benutzername",
"emailOrUsernamePlaceholder": "Geben Sie Ihre E-Mail-Adresse oder Benutzernamen ein",
"emailLabel": "E-Mail-Adresse",
"emailPlaceholder": "Geben Sie Ihre E-Mail-Adresse ein",
"passwordLabel": "Passwort",
@@ -852,6 +854,7 @@
"passwordLength": "Das Passwort muss mindestens 8 Zeichen lang sein",
"passwordsMatch": "Die Passwörter stimmen nicht überein",
"emailRequired": "E-Mail ist erforderlich",
"emailOrUsernameRequired": "E-Mail oder Benutzername ist erforderlich",
"passwordRequired": "Passwort ist erforderlich",
"nameRequired": "Name ist erforderlich",
"required": "Dieses Feld ist erforderlich"

View File

@@ -202,6 +202,8 @@
"login": {
"welcome": "Welcome to",
"signInToContinue": "Sign in to continue",
"emailOrUsernameLabel": "Email or Username",
"emailOrUsernamePlaceholder": "Enter your email or username",
"emailLabel": "Email Address",
"emailPlaceholder": "Enter your email",
"passwordLabel": "Password",
@@ -909,6 +911,7 @@
"passwordLength": "Password must be at least 8 characters long",
"passwordsMatch": "Passwords must match",
"emailRequired": "Email is required",
"emailOrUsernameRequired": "Email or username is required",
"passwordRequired": "Password is required",
"passwordMinLength": "Password must be at least 6 characters",
"nameRequired": "Name is required",

View File

@@ -202,6 +202,8 @@
"login": {
"welcome": "Bienvenido a",
"signInToContinue": "Inicia sesión para continuar",
"emailOrUsernameLabel": "Correo electrónico o nombre de usuario",
"emailOrUsernamePlaceholder": "Introduce tu correo electrónico o nombre de usuario",
"emailLabel": "Dirección de correo electrónico",
"emailPlaceholder": "Introduce tu correo electrónico",
"passwordLabel": "Contraseña",
@@ -852,6 +854,7 @@
"passwordLength": "La contraseña debe tener al menos 8 caracteres",
"passwordsMatch": "Las contraseñas no coinciden",
"emailRequired": "Se requiere el correo electrónico",
"emailOrUsernameRequired": "Se requiere el correo electrónico o nombre de usuario",
"passwordRequired": "Se requiere la contraseña",
"nameRequired": "El nombre es obligatorio",
"required": "Este campo es obligatorio"

View File

@@ -202,6 +202,8 @@
"login": {
"welcome": "Bienvenue à",
"signInToContinue": "Connectez-vous pour continuer",
"emailOrUsernameLabel": "Email ou Nom d'utilisateur",
"emailOrUsernamePlaceholder": "Entrez votre email ou nom d'utilisateur",
"emailLabel": "Adresse e-mail",
"emailPlaceholder": "Entrez votre e-mail",
"passwordLabel": "Mot de passe",
@@ -852,6 +854,7 @@
"passwordLength": "Le mot de passe doit contenir au moins 8 caractères",
"passwordsMatch": "Les mots de passe ne correspondent pas",
"emailRequired": "L'email est requis",
"emailOrUsernameRequired": "L'email ou le nom d'utilisateur est requis",
"passwordRequired": "Le mot de passe est requis",
"nameRequired": "Nome é obrigatório",
"required": "Este campo é obrigatório"

View File

@@ -202,6 +202,8 @@
"login": {
"welcome": "स्वागत है में",
"signInToContinue": "जारी रखने के लिए साइन इन करें",
"emailOrUsernameLabel": "ईमेल या उपयोगकर्ता नाम",
"emailOrUsernamePlaceholder": "अपना ईमेल या उपयोगकर्ता नाम दर्ज करें",
"emailLabel": "ईमेल पता",
"emailPlaceholder": "अपना ईमेल दर्ज करें",
"passwordLabel": "पासवर्ड",
@@ -852,6 +854,7 @@
"passwordLength": "पासवर्ड कम से कम 8 अक्षर का होना चाहिए",
"passwordsMatch": "पासवर्ड मेल नहीं खाते",
"emailRequired": "ईमेल आवश्यक है",
"emailOrUsernameRequired": "ईमेल या उपयोगकर्ता नाम आवश्यक है",
"passwordRequired": "पासवर्ड आवश्यक है",
"nameRequired": "नाम आवश्यक है",
"required": "यह फ़ील्ड आवश्यक है"

View File

@@ -202,6 +202,8 @@
"login": {
"welcome": "Benvenuto in",
"signInToContinue": "Accedi per continuare",
"emailOrUsernameLabel": "Email o Nome utente",
"emailOrUsernamePlaceholder": "Inserisci la tua email o nome utente",
"emailLabel": "Indirizzo Email",
"emailPlaceholder": "Inserisci la tua email",
"passwordLabel": "Parola d'accesso",
@@ -851,6 +853,7 @@
"passwordLength": "La parola d'accesso deve essere di almeno 8 caratteri",
"passwordsMatch": "Le parole d'accesso devono corrispondere",
"emailRequired": "L'indirizzo email è obbligatorio",
"emailOrUsernameRequired": "L'indirizzo email o il nome utente è obbligatorio",
"passwordRequired": "La parola d'accesso è obbligatoria",
"passwordMinLength": "La password deve contenere almeno 6 caratteri",
"nameRequired": "Il nome è obbligatorio",

View File

@@ -202,6 +202,8 @@
"login": {
"welcome": "ようこそへ",
"signInToContinue": "続行するにはサインインしてください",
"emailOrUsernameLabel": "メールアドレスまたはユーザー名",
"emailOrUsernamePlaceholder": "メールアドレスまたはユーザー名を入力してください",
"emailLabel": "メールアドレス",
"emailPlaceholder": "メールアドレスを入力してください",
"passwordLabel": "パスワード",
@@ -852,6 +854,7 @@
"passwordLength": "パスワードは最低8文字必要です",
"passwordsMatch": "パスワードが一致しません",
"emailRequired": "メールアドレスは必須です",
"emailOrUsernameRequired": "メールアドレスまたはユーザー名は必須です",
"passwordRequired": "パスワードは必須です",
"nameRequired": "名前は必須です",
"required": "このフィールドは必須です"

View File

@@ -202,6 +202,8 @@
"login": {
"welcome": "에 오신 것을 환영합니다",
"signInToContinue": "계속하려면 로그인하세요",
"emailOrUsernameLabel": "이메일 또는 사용자 이름",
"emailOrUsernamePlaceholder": "이메일 또는 사용자 이름을 입력하세요",
"emailLabel": "이메일 주소",
"emailPlaceholder": "이메일을 입력하세요",
"passwordLabel": "비밀번호",
@@ -852,6 +854,7 @@
"passwordLength": "비밀번호는 최소 8자 이상이어야 합니다",
"passwordsMatch": "비밀번호가 일치하지 않습니다",
"emailRequired": "이메일은 필수입니다",
"emailOrUsernameRequired": "이메일 또는 사용자 이름은 필수입니다",
"passwordRequired": "비밀번호는 필수입니다",
"nameRequired": "이름은 필수입니다",
"required": "이 필드는 필수입니다"

View File

@@ -202,6 +202,8 @@
"login": {
"welcome": "Welkom bij",
"signInToContinue": "Log in om door te gaan",
"emailOrUsernameLabel": "E-mail of Gebruikersnaam",
"emailOrUsernamePlaceholder": "Voer je e-mail of gebruikersnaam in",
"emailLabel": "E-mailadres",
"emailPlaceholder": "Voer je e-mail in",
"passwordLabel": "Wachtwoord",
@@ -851,6 +853,7 @@
"passwordLength": "Wachtwoord moet minimaal 8 tekens zijn",
"passwordsMatch": "Wachtwoorden moeten overeenkomen",
"emailRequired": "E-mail is verplicht",
"emailOrUsernameRequired": "E-mail of gebruikersnaam is verplicht",
"passwordRequired": "Wachtwoord is verplicht",
"passwordMinLength": "Wachtwoord moet minimaal 6 tekens bevatten",
"nameRequired": "Naam is verplicht",

View File

@@ -202,6 +202,8 @@
"login": {
"welcome": "Witaj w",
"signInToContinue": "Zaloguj się, aby kontynuować",
"emailOrUsernameLabel": "E-mail lub nazwa użytkownika",
"emailOrUsernamePlaceholder": "Wprowadź swój e-mail lub nazwę użytkownika",
"emailLabel": "Adres e-mail",
"emailPlaceholder": "Wprowadź swój adres e-mail",
"passwordLabel": "Hasło",
@@ -909,6 +911,7 @@
"passwordLength": "Hasło musi mieć co najmniej 8 znaków",
"passwordsMatch": "Hasła muszą być zgodne",
"emailRequired": "E-mail jest wymagany",
"emailOrUsernameRequired": "E-mail lub nazwa użytkownika jest wymagana",
"passwordRequired": "Hasło jest wymagane",
"passwordMinLength": "Hasło musi mieć co najmniej 6 znaków",
"nameRequired": "Nazwa jest wymagana",

View File

@@ -18,17 +18,17 @@
"click": "Clique para"
},
"createShare": {
"title": "Criar Compartilhamento",
"nameLabel": "Nome do Compartilhamento",
"title": "Criar compartilhamento",
"nameLabel": "Nome do compartilhamento",
"descriptionLabel": "Descrição",
"descriptionPlaceholder": "Digite uma descrição (opcional)",
"expirationLabel": "Data de Expiração",
"expirationLabel": "Data de expiração",
"expirationPlaceholder": "DD/MM/AAAA HH:MM",
"maxViewsLabel": "Máximo de Visualizações",
"maxViewsLabel": "Máximo de visualizações",
"maxViewsPlaceholder": "Deixe vazio para ilimitado",
"passwordProtection": "Protegido por Senha",
"passwordLabel": "Senha",
"create": "Criar Compartilhamento",
"create": "Criar compartilhamento",
"success": "Compartilhamento criado com sucesso",
"error": "Falha ao criar compartilhamento"
},
@@ -44,7 +44,7 @@
},
"emptyState": {
"noFiles": "Nenhum arquivo enviado ainda",
"uploadFile": "Enviar Arquivo"
"uploadFile": "Enviar arquivo"
},
"errors": {
"invalidCredentials": "E-mail ou senha inválidos",
@@ -53,13 +53,13 @@
"unexpectedError": "Ocorreu um erro inesperado. Por favor, tente novamente"
},
"fileActions": {
"editFile": "Editar Arquivo",
"editFile": "Editar arquivo",
"nameLabel": "Nome",
"namePlaceholder": "Digite o novo nome",
"extension": "Extensão",
"descriptionLabel": "Descrição",
"descriptionPlaceholder": "Digite a descrição do arquivo",
"deleteFile": "Excluir Arquivo",
"deleteFile": "Excluir arquivo",
"deleteConfirmation": "Tem certeza que deseja excluir ?",
"deleteWarning": "Esta ação não pode ser desfeita."
},
@@ -154,9 +154,9 @@
"bulkActions": {
"selected": "{count, plural, =1 {1 arquivo selecionado} other {# arquivos selecionados}}",
"actions": "Ações",
"download": "Baixar Selecionados",
"share": "Compartilhar Selecionados",
"delete": "Excluir Selecionados"
"download": "Baixar selecionados",
"share": "Compartilhar selecionados",
"delete": "Excluir selecionados"
}
},
"footer": {
@@ -175,23 +175,23 @@
"pageTitle": "Esqueceu a Senha"
},
"generateShareLink": {
"generateTitle": "Gerar Link de Compartilhamento",
"updateTitle": "Atualizar Link de Compartilhamento",
"generateTitle": "Gerar link de compartilhamento",
"updateTitle": "Atualizar link de compartilhamento",
"generateDescription": "Gere um link para compartilhar seus arquivos",
"updateDescription": "Atualize o alias deste link de compartilhamento",
"aliasPlaceholder": "Digite o alias",
"linkReady": "Seu link de compartilhamento está pronto:",
"generateButton": "Gerar Link",
"updateButton": "Atualizar Link",
"copyButton": "Copiar Link",
"generateButton": "Gerar link",
"updateButton": "Atualizar link",
"copyButton": "Copiar link",
"success": "Link gerado com sucesso",
"error": "Erro ao gerar link",
"copied": "Link copiado para a área de transferência"
},
"shareFile": {
"title": "Compartilhar Arquivo",
"linkTitle": "Gerar Link",
"nameLabel": "Nome do Compartilhamento",
"title": "Compartilhar arquivo",
"linkTitle": "Gerar link",
"nameLabel": "Nome do compartilhamento",
"namePlaceholder": "Digite o nome do compartilhamento",
"descriptionLabel": "Descrição",
"descriptionPlaceholder": "Digite uma descrição (opcional)",
@@ -199,16 +199,16 @@
"expirationPlaceholder": "DD/MM/AAAA HH:MM",
"maxViewsLabel": "Máximo de Visualizações",
"maxViewsPlaceholder": "Deixe vazio para ilimitado",
"passwordProtection": "Protegido por Senha",
"passwordProtection": "Protegido por senha",
"passwordLabel": "Senha",
"passwordPlaceholder": "Digite a senha",
"linkDescription": "Gere um link personalizado para compartilhar o arquivo",
"aliasLabel": "Alias do Link",
"aliasLabel": "Alias do link",
"aliasPlaceholder": "Digite um alias personalizado",
"linkReady": "Seu link de compartilhamento está pronto:",
"createShare": "Criar Compartilhamento",
"generateLink": "Gerar Link",
"copyLink": "Copiar Link"
"createShare": "Criar compartilhamento",
"generateLink": "Gerar link",
"copyLink": "Copiar link"
},
"home": {
"description": "A alternativa open-source ao WeTransfer. Compartilhe arquivos com segurança, sem rastreamento ou limitações.",
@@ -223,7 +223,9 @@
},
"login": {
"welcome": "Bem-vindo ao",
"signInToContinue": "Entre para continuar",
"signInToContinue": "Faça login para continuar",
"emailOrUsernameLabel": "E-mail ou Nome de Usuário",
"emailOrUsernamePlaceholder": "Digite seu e-mail ou nome de usuário",
"emailLabel": "Endereço de E-mail",
"emailPlaceholder": "Digite seu e-mail",
"passwordLabel": "Senha",
@@ -231,18 +233,18 @@
"signIn": "Entrar",
"signingIn": "Entrando...",
"forgotPassword": "Esqueceu a senha?",
"pageTitle": "Entrar",
"pageTitle": "Login",
"or": "ou",
"continueWithSSO": "Continuar com SSO",
"processing": "Processando autenticação..."
},
"logo": {
"labels": {
"appLogo": "Logo do Aplicativo"
"appLogo": "Logo do aplicativo"
},
"buttons": {
"upload": "Enviar Logo",
"remove": "Remover Logo"
"upload": "Enviar logo",
"remove": "Remover logo"
},
"messages": {
"uploadSuccess": "Logo enviado com sucesso",
@@ -254,11 +256,11 @@
}
},
"navbar": {
"logoAlt": "Logo do Aplicativo",
"logoAlt": "Logo do aplicativo",
"profileMenu": "Menu do Perfil",
"profile": "Perfil",
"settings": "Configurações",
"usersManagement": "Gerenciar Usuários",
"usersManagement": "Gerenciar usuários",
"logout": "Sair"
},
"navigation": {
@@ -865,18 +867,15 @@
}
},
"validation": {
"invalidEmail": "Endereço de email inválido",
"passwordMinLength": "A senha deve ter pelo menos 6 caracteres",
"firstNameRequired": "Nome é obrigatório",
"lastNameRequired": "Sobrenome é obrigatório",
"usernameLength": "Nome de usuário deve ter pelo menos 3 caracteres",
"usernameSpaces": "Nome de usuário não pode conter espaços",
"invalidEmail": "Por favor, insira um endereço de e-mail válido",
"passwordLength": "A senha deve ter pelo menos 8 caracteres",
"passwordsMatch": "As senhas não coincidem",
"passwordsMatch": "As senhas devem coincidir",
"emailRequired": "Email é obrigatório",
"emailOrUsernameRequired": "E-mail ou nome de usuário é obrigatório",
"passwordRequired": "Senha é obrigatória",
"required": "Este campo é obrigatório",
"nameRequired": "Nome é obrigatório"
"passwordMinLength": "A senha deve ter pelo menos 6 caracteres",
"nameRequired": "Nome é obrigatório",
"required": "Este campo é obrigatório"
},
"bulkDownload": {
"title": "Download em Lote",
@@ -945,8 +944,8 @@
"noExpiration": "Este compartilhamento nunca expirará e permanecerá acessível indefinidamente.",
"title": "Sobre expiração:"
},
"enableExpiration": "Habilitar Expiração",
"title": "Configurações de Expiração do Compartilhamento",
"enableExpiration": "Habilitar expiração",
"title": "Configurações de expiração do compartilhamento",
"subtitle": "Configurar quando este compartilhamento expirará",
"validation": {
"dateMustBeFuture": "A data de expiração deve estar no futuro",
@@ -957,7 +956,7 @@
"updateFailed": "Falha ao atualizar configurações de expiração"
},
"expires": "Expira:",
"expirationDate": "Data de Expiração"
"expirationDate": "Data de expiração"
},
"auth": {
"errors": {
@@ -969,10 +968,10 @@
}
},
"reverseShares": {
"pageTitle": "Receber Arquivos",
"pageTitle": "Receber arquivos",
"search": {
"title": "Gerenciar Links de Recebimento",
"createButton": "Criar Link",
"title": "Gerenciar links de recebimento",
"createButton": "Criar link",
"placeholder": "Buscar links de recebimento...",
"results": "Encontrados {filtered} de {total} links de recebimento"
},
@@ -982,13 +981,13 @@
"status": "status",
"access": "acesso",
"description": "Descrição",
"pageLayout": "Layout da Página",
"pageLayout": "Layout da página",
"security": "Segurança & Status",
"limits": "Limites",
"maxFiles": "Máximo de Arquivos",
"maxFileSize": "Tamanho Máximo",
"allowedTypes": "Tipos Permitidos",
"filesReceived": "Arquivos Recebidos",
"maxFiles": "Máximo de arquivos",
"maxFileSize": "Tamanho máximo",
"allowedTypes": "Tipos permitidos",
"filesReceived": "Arquivos recebidos",
"fileLimit": "Limite de Arquivos",
"noLimit": "Sem limite",
"noLinkCreated": "Nenhum link criado",

View File

@@ -202,6 +202,8 @@
"login": {
"welcome": "Добро пожаловать в",
"signInToContinue": "Войдите, чтобы продолжить",
"emailOrUsernameLabel": "Электронная почта или имя пользователя",
"emailOrUsernamePlaceholder": "Введите электронную почту или имя пользователя",
"emailLabel": "Адрес электронной почты",
"emailPlaceholder": "Введите вашу электронную почту",
"passwordLabel": "Пароль",
@@ -852,6 +854,7 @@
"passwordLength": "Пароль должен содержать не менее 8 символов",
"passwordsMatch": "Пароли не совпадают",
"emailRequired": "Требуется электронная почта",
"emailOrUsernameRequired": "Электронная почта или имя пользователя обязательно",
"passwordRequired": "Требуется пароль",
"nameRequired": "Требуется имя",
"required": "Это поле обязательно"

View File

@@ -202,6 +202,8 @@
"login": {
"welcome": "Hoş geldiniz'e",
"signInToContinue": "Devam etmek için oturum açın",
"emailOrUsernameLabel": "E-posta veya Kullanıcı Adı",
"emailOrUsernamePlaceholder": "E-posta veya kullanıcı adınızı girin",
"emailLabel": "E-posta Adresi",
"emailPlaceholder": "E-posta adresinizi girin",
"passwordLabel": "Şifre",
@@ -852,6 +854,7 @@
"passwordLength": "Şifre en az 8 karakter olmalıdır",
"passwordsMatch": "Şifreler eşleşmiyor",
"emailRequired": "E-posta gerekli",
"emailOrUsernameRequired": "E-posta veya kullanıcı adı gereklidir",
"passwordRequired": "Şifre gerekli",
"nameRequired": "İsim gereklidir",
"required": "Bu alan zorunludur"

View File

@@ -202,6 +202,8 @@
"login": {
"welcome": "欢迎您",
"signInToContinue": "请登录以继续",
"emailOrUsernameLabel": "电子邮件或用户名",
"emailOrUsernamePlaceholder": "请输入您的电子邮件或用户名",
"emailLabel": "电子邮件地址",
"emailPlaceholder": "请输入您的电子邮件",
"passwordLabel": "密码",
@@ -852,6 +854,7 @@
"passwordLength": "密码至少需要8个字符",
"passwordsMatch": "密码不匹配",
"emailRequired": "电子邮件为必填项",
"emailOrUsernameRequired": "电子邮件或用户名是必填项",
"passwordRequired": "密码为必填项",
"nameRequired": "名称为必填项",
"required": "此字段为必填项"

View File

@@ -23,7 +23,7 @@ export function LoginForm({ error, isVisible, onToggleVisibility, onSubmit }: Lo
const form = useForm<LoginFormValues>({
resolver: zodResolver(loginSchema),
defaultValues: {
email: "",
emailOrUsername: "",
password: "",
},
});
@@ -37,18 +37,18 @@ export function LoginForm({ error, isVisible, onToggleVisibility, onSubmit }: Lo
</p>
);
const renderEmailField = () => (
const renderEmailOrUsernameField = () => (
<FormField
control={form.control}
name="email"
name="emailOrUsername"
render={({ field }) => (
<FormItem>
<FormLabel>{t("login.emailLabel")}</FormLabel>
<FormLabel>{t("login.emailOrUsernameLabel")}</FormLabel>
<FormControl className="-mb-1">
<Input
{...field}
type="email"
placeholder={t("login.emailPlaceholder")}
type="text"
placeholder={t("login.emailOrUsernamePlaceholder")}
disabled={isSubmitting}
className="bg-transparent backdrop-blur-md"
/>
@@ -89,7 +89,7 @@ export function LoginForm({ error, isVisible, onToggleVisibility, onSubmit }: Lo
{renderErrorMessage()}
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-4">
{renderEmailField()}
{renderEmailOrUsernameField()}
{renderPasswordField()}
<Button className="w-full mt-4 cursor-pointer" variant="default" size="lg" type="submit">
{isSubmitting ? t("login.signingIn") : t("login.signIn")}

View File

@@ -12,7 +12,7 @@ import { getCurrentUser, login } from "@/http/endpoints";
import { LoginFormValues } from "../schemas/schema";
export const loginSchema = z.object({
email: z.string(),
emailOrUsername: z.string(),
password: z.string(),
});

View File

@@ -5,7 +5,7 @@ type TFunction = ReturnType<typeof useTranslations>;
export const createLoginSchema = (t: TFunction) =>
z.object({
email: z.string().min(1, t("validation.emailRequired")).email(t("validation.invalidEmail")),
emailOrUsername: z.string().min(1, t("validation.emailOrUsernameRequired")),
password: z.string().min(1, t("validation.passwordRequired")),
});

View File

@@ -7,8 +7,8 @@
*/
export type LoginBody = {
/** User email */
email: string;
/** User email or username */
emailOrUsername: string;
/**
* User password
* @minLength 8