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({ 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"), password: z.string().min(6, "Password must be at least 6 characters").describe("User password"),
}); });
export type LoginInput = z.infer<typeof LoginSchema>; export type LoginInput = z.infer<typeof LoginSchema>;

View File

@@ -17,7 +17,7 @@ export async function authRoutes(app: FastifyInstance) {
const passwordSchema = await createPasswordSchema(); const passwordSchema = await createPasswordSchema();
const loginSchema = z.object({ 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, password: passwordSchema,
}); });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,7 +5,7 @@ type TFunction = ReturnType<typeof useTranslations>;
export const createLoginSchema = (t: TFunction) => export const createLoginSchema = (t: TFunction) =>
z.object({ 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")), password: z.string().min(1, t("validation.passwordRequired")),
}); });

View File

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