Enter키 동작 구현
This commit is contained in:
geonhee-min
2025-12-03 10:13:37 +09:00
parent 1a0cc9376f
commit 54c84dbc87
6 changed files with 52 additions and 16 deletions

View File

@@ -1,11 +1,20 @@
import { Validator } from '@/util/Validator';
import * as z from 'zod'; import * as z from 'zod';
export const LoginSchema = z.object({ export const LoginSchema = z.object({
id: z id: z
.string() .string()
.refine((val) => {
if (val.includes('@')) {
return Validator.isEmail(val);;
}
return true;
}, {
message: "이메일 형식이 올바르지 않습니다."
})
, password: z , password: z
.string() .string()
.min(8, "비밀번호는 8-12 자리여야 합니다.") .min(8, "비밀번호는 8-12 자리여야 합니다.")
.max(12, "비밀번호는 8-12 자리여야 합니다.") .max(12, "비밀번호는 8-12 자리여야 합니다.")
.regex(/^[a-z](?=.*[0-9])(?=.*[!@#$]).*$/, "비밀번호는 영소문자로 시작하여 숫자, 특수문자(!@#$)를 한 개 이상 포함하여야 합니다.") .regex(/^(?=.*[0-9])(?=.*[!@#$%^])[a-zA-Z0-9!@#$%^]+$/, "비밀번호는 영소문자로 시작하여 숫자, 특수문자(!@#$)를 한 개 이상 포함하여야 합니다.")
}); });

View File

@@ -6,12 +6,12 @@ export const ResetPasswordSchema = z.object({
, code: z , code: z
.string() .string()
.length(8) .length(8)
.regex(/^[a-z](?=.*[0-9])(?=.*[!@#$%^]).*$/, "영소문자로 시작하고 숫자와 특수문자(!@#$%^)를 포함해야 합니다.") .regex(/^(?=.*[0-9])(?=.*[!@#$%^])[a-zA-Z0-9!@#$%^]+$/, "영소문자로 시작하고 숫자와 특수문자(!@#$%^)를 포함해야 합니다.")
, password: z , password: z
.string() .string()
.min(8, "비밀번호는 8-12 자리여야 합니다.") .min(8, "비밀번호는 8-12 자리여야 합니다.")
.max(12, "비밀번호는 8-12 자리여야 합니다.") .max(12, "비밀번호는 8-12 자리여야 합니다.")
.regex(/^[a-z](?=.*[0-9])(?=.*[!@#$%^]).*$/, "영소문자로 시작하고 숫자와 특수문자(!@#$%^)를 포함해야 합니다.") .regex(/^(?=.*[0-9])(?=.*[!@#$%^])[a-zA-Z0-9!@#$%^]+$/, "영소문자로 시작하고 숫자와 특수문자(!@#$%^)를 포함해야 합니다.")
, passwordConfirm: z , passwordConfirm: z
.string() .string()
}) })

View File

@@ -4,6 +4,11 @@ export const SignUpSchema = z.object({
accountId: z accountId: z
.string() .string()
.min(5, "아이디는 5 자리 이상이어야 합니다.") .min(5, "아이디는 5 자리 이상이어야 합니다.")
.refine((val) => {
return /^[a-zA-z-_.]*$/.test(val);
}, {
message: "영문, 숫자, '- _ .' 를 제외한 문자를 사용할 수 없습니다."
})
, email: z , email: z
.string() .string()
.min(5, "이메일을 입력해주십시오.") .min(5, "이메일을 입력해주십시오.")
@@ -11,7 +16,7 @@ export const SignUpSchema = z.object({
.string() .string()
.min(8, "비밀번호는 8-12 자리여야 합니다.") .min(8, "비밀번호는 8-12 자리여야 합니다.")
.max(12, "비밀번호는 8-12 자리여야 합니다.") .max(12, "비밀번호는 8-12 자리여야 합니다.")
.regex(/^[a-z](?=.*[0-9])(?=.*[!@#$%^]).*$/, "영소문자로 시작하고 숫자와 특수문자(!@#$%^)를 포함해야 합니다.") .regex(/^(?=.*[0-9])(?=.*[!@#$%^])[a-zA-Z0-9!@#$%^]+$/, "영소문자로 시작하고 숫자와 특수문자(!@#$%^)를 포함해야 합니다.")
, name: z , name: z
.string() .string()
.min(1, "이름을 입력해주시십시오.") .min(1, "이름을 입력해주시십시오.")

View File

@@ -4,7 +4,7 @@ import { Field, FieldError, FieldLabel } from '@/components/ui/field';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { PageRouting } from '@/const/PageRouting'; import { PageRouting } from '@/const/PageRouting';
@@ -98,6 +98,13 @@ export default function LoginPage() {
) )
} }
const handleEnterKeyDown = async (e: React.KeyboardEvent<HTMLInputElement>) => {
if (!(e.key === 'Enter')) return;
const result = await loginForm.trigger();
if (!result) return;
await reqLogin();
}
return ( return (
<div className="w-full h-full flex flex-col justify-center items-center"> <div className="w-full h-full flex flex-col justify-center items-center">
<Card className="w-md pl-2 pr-2"> <Card className="w-md pl-2 pr-2">
@@ -117,6 +124,8 @@ export default function LoginPage() {
type="text" type="text"
id="form-login-id" id="form-login-id"
aria-invalid={fieldState.invalid} aria-invalid={fieldState.invalid}
tabIndex={1}
onKeyDown={handleEnterKeyDown}
/> />
<FieldError errors={[fieldState.error]} /> <FieldError errors={[fieldState.error]} />
</Field> </Field>
@@ -134,6 +143,7 @@ export default function LoginPage() {
className="p-0 bg-transparent hover:bg-transparent h-fit w-fit text-xs text-gray-400 hover:text-gray-500 cursor-pointer" className="p-0 bg-transparent hover:bg-transparent h-fit w-fit text-xs text-gray-400 hover:text-gray-500 cursor-pointer"
onClick={moveToResetPasswordPage} onClick={moveToResetPasswordPage}
type="button" type="button"
tabIndex={3}
> >
? ?
</Button> </Button>
@@ -143,6 +153,8 @@ export default function LoginPage() {
type="password" type="password"
id="form-login-password" id="form-login-password"
aria-invalid={fieldState.invalid} aria-invalid={fieldState.invalid}
tabIndex={2}
onKeyDown={handleEnterKeyDown}
/> />
<FieldError errors={[fieldState.error]} /> <FieldError errors={[fieldState.error]} />
</Field> </Field>

View File

@@ -4,7 +4,7 @@ import { Field, FieldError, FieldLabel } from '@/components/ui/field';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { useState, useCallback } from 'react'; import React, { useState, useCallback } from 'react';
import { Controller, useForm } from 'react-hook-form'; import { Controller, useForm } from 'react-hook-form';
import { useNavigate } from 'react-router-dom'; import { useNavigate } from 'react-router-dom';
import { PageRouting } from '@/const/PageRouting'; import { PageRouting } from '@/const/PageRouting';
@@ -122,7 +122,7 @@ export default function ResetPasswordPage() {
} }
} }
const handleThirdStepButton = async () => { const handleClickThirdStepButton = async () => {
if (isLoading) return; if (isLoading) return;
const passwordValid = await resetPasswordForm.trigger('password'); const passwordValid = await resetPasswordForm.trigger('password');
if (!passwordValid) return; if (!passwordValid) return;
@@ -159,6 +159,19 @@ export default function ResetPasswordPage() {
} }
const handleEnterKeyDown = async (e: React.KeyboardEvent<HTMLInputElement>) => {
if (e.key === 'Enter') {
e.preventDefault();
if (currentStep === 1) {
await handleClickFirstStepButton();
return;
}
if (currentStep === 3) {
await handleClickThirdStepButton();
}
}
}
return ( return (
<Stepper <Stepper
value={currentStep} value={currentStep}
@@ -211,13 +224,7 @@ export default function ResetPasswordPage() {
type="email" type="email"
id="reset-password-email" id="reset-password-email"
aria-invalid={fieldState.invalid} aria-invalid={fieldState.invalid}
onKeyDown={(e) => { onKeyDown={handleEnterKeyDown}
if (e.key === 'Enter') {
e.preventDefault();
if (email.length === 0) return;
handleClickFirstStepButton();
}
}}
/> />
<FieldError className="font-[12px]" errors={[fieldState.error]} /> <FieldError className="font-[12px]" errors={[fieldState.error]} />
</> </>
@@ -275,6 +282,7 @@ export default function ResetPasswordPage() {
type={ showPassword ? "text" : "password" } type={ showPassword ? "text" : "password" }
id="reset-password-password" id="reset-password-password"
aria-invalid={fieldState.invalid} aria-invalid={fieldState.invalid}
onKeyDown={handleEnterKeyDown}
className="pr-10" className="pr-10"
/> />
<button <button
@@ -306,6 +314,7 @@ export default function ResetPasswordPage() {
id="reset-password-password-confirm" id="reset-password-password-confirm"
className="pr-10" className="pr-10"
aria-invalid={fieldState.invalid} aria-invalid={fieldState.invalid}
onKeyDown={handleEnterKeyDown}
/> />
<button <button
type="button" type="button"
@@ -380,7 +389,7 @@ export default function ResetPasswordPage() {
(password.trim().length < 1) (password.trim().length < 1)
&& (passwordConfirm.trim().length < 1) && (passwordConfirm.trim().length < 1)
} }
onClick={handleThirdStepButton} onClick={handleClickThirdStepButton}
> >
</Button> </Button>

View File

@@ -7,7 +7,8 @@ export class Validator {
static validatePasswordFormat = (password: string): boolean => { static validatePasswordFormat = (password: string): boolean => {
if (password.length < 8) return false; if (password.length < 8) return false;
if (password.includes(' ')) return false;
const alphabets = 'abcdefghijklmnopqrstuvwxyz'; const alphabets = 'abcdefghijklmnopqrstuvwxyz';
const numbers = '0123456789'; const numbers = '0123456789';
const specials = '!@#$%^'; const specials = '!@#$%^';