Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/constants/routerPath.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,7 @@ export const ROUTE_PATH = {
MAIN: '/',
CLUBDETAIL: 'clubs/:clubId',
LOGIN: 'login',
CALLBACK: 'login/redirect',
SIGNUP: 'signup',
},
};
10 changes: 10 additions & 0 deletions src/pages/Routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import { DashboardPage } from '@/pages/admin/Dashboard/Page';
import { ClubDetailPage } from '@/pages/user/ClubDetail/Page';
import { MainPage } from '@/pages/user/Main/Page.tsx';
import { ApplicationDetailPage } from './admin/ApplicationDetail/Page';
import { KakaoCallback } from './admin/Login/KakaoCallback';
import { LoginPage } from './admin/Login/Page';
import { AdminSignupPage } from './admin/Signup/Page';
import { ClubApplicationPage } from './user/Apply/Page';

const { USER, ADMIN, COMMON } = ROUTE_PATH;
Expand All @@ -29,6 +31,14 @@ export const router = createBrowserRouter([
path: USER.APPLICATION,
element: <ClubApplicationPage />,
},
{
path: COMMON.CALLBACK,
element: <KakaoCallback />,
},
{
path: COMMON.SIGNUP,
element: <AdminSignupPage />,
},
{
path: '/admin',
children: [
Expand Down
43 changes: 43 additions & 0 deletions src/pages/admin/Login/KakaoCallback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import axios from 'axios';
import { useEffect } from 'react';
import { useNavigate } from 'react-router-dom';
import { LoadingSpinner } from '@/shared/components/LoadingSpinner';

export const KakaoCallback = () => {
const navigate = useNavigate();

useEffect(() => {
const code = new URL(window.location.href).searchParams.get('code');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

window.location.href를 직접 파싱하는 대신, react-router-dom에서 제공하는 useSearchParams 훅을 사용하면 더 선언적이고 안정적으로 쿼리 파라미터를 가져올 수 있습니다.

Suggested change
const code = new URL(window.location.href).searchParams.get('code');
const [searchParams] = useSearchParams();
const code = searchParams.get('code');

if (!code) return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

카카오 로그인 시 사용자가 동의를 거부하는 등 에러가 발생하면 error 쿼리 파라미터가 URL에 포함될 수 있습니다. 이 경우에 대한 에러 처리가 누락되었습니다. 사용자에게 에러 상황을 안내하고 로그인 페이지로 돌려보내는 등의 처리가 필요합니다.


console.log(code);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove debug console.log statements.

Console.log statements should be removed before merging to production.

Apply this diff to remove debug logs:

-    console.log(code);
     const fetchToken = async () => {
       try {
         const res = await axios.post('http://localhost:8080/api/auth/kakao/login', {
           authorizationCode: code,
         });
-        console.log('응답res ', res);

         // CASE 1) 기존 회원
         ...
       } catch (error) {
-        console.log('error:', error);
+        console.error('Kakao login failed:', error);
       }

Consider using a proper logger or at least console.error for error cases.

Also applies to: 19-19, 34-34

🤖 Prompt for AI Agents
In src/pages/admin/Login/KakaoCallback.tsx around lines 13, 19 and 34, remove
the debug console.log statements (e.g., console.log(code)) before merging to
production; replace any necessary runtime diagnostics with a proper logger or,
for errors only, use console.error, and ensure no stray console.log calls remain
in those lines of the file.

const fetchToken = async () => {
try {
const res = axios.post(`${import.meta.env.VITE_API_BASE_URL}/auth/kakao/login`, {
authorizationCode: code,
});
console.log('응답res ', res);

// CASE 1) 기존 회원

// 1-1. accessToken, refreshToken 발급
// localStorage.setItem('accessToken', res.data.accessToken);
// localStorage.setItem('refreshToken ', res.data.refreshToken)- (수정전)
// refreshToken은 httpOnly 관리(수정후)
// ------------------------------------------------------------
// 2-2 main 페이지 이동
// navigate('/'); // 로그인 후 홈으로 이동

// CASE 2) 기존 회원
// 2-1. 임시 토큰
// 2-2. navigate('/signup')
} catch (error) {
console.log('error:', error);
Comment on lines 13 to 35
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

개발 중에 사용된 console.log 구문들이 남아있습니다. 프로덕션 코드에 포함되지 않도록 제거해주세요.

}
Comment on lines +34 to +36
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

API 요청 실패 시 catch 블록에서 에러를 콘솔에만 출력하고 있습니다. 사용자 경험을 위해 에러 토스트 메시지를 띄우거나, 에러 페이지로 이동시키는 등 명시적인 에러 처리를 추가하는 것이 좋습니다.

};

fetchToken();
}, [navigate]);

return <LoadingSpinner />;
};
2 changes: 0 additions & 2 deletions src/pages/admin/Login/Page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import styled from '@emotion/styled';
import { LoginButton } from './component/LoginButton';
import { LoginInput } from './component/LoginInput';
import { Logo } from './component/Logo';

export const LoginPage = () => {
return (
<Container>
<Logo />
<LoginInput />
<LoginButton />
</Container>
);
Expand Down
16 changes: 12 additions & 4 deletions src/pages/admin/Login/component/LoginButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import styled from '@emotion/styled';
import { Button } from '@/shared/components/Button';

const REST_API_KEY = import.meta.env.VITE_KAKAO_REST_API_KEY;
const REDIRECT_URI = import.meta.env.VITE_KAKAO_REDIRECT_URI;

export const LoginButton = () => {
const handleKakaoLogin = () => {
const kakaoAuthUrl = `https://kauth.kakao.com/oauth/authorize?client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}&response_type=code`;
window.location.href = kakaoAuthUrl;
};
Comment on lines +8 to +11
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

❓ Verification inconclusive

Verify environment variables are defined.

Ensure that VITE_KAKAO_REST_API_KEY and VITE_KAKAO_REDIRECT_URI are properly configured in the environment files. Missing values will result in an incomplete OAuth URL.

Run the following script to check if environment variable examples or documentation exist:


🏁 Script executed:

#!/bin/bash
# Check for .env files and documentation mentioning Kakao environment variables
fd -t f '\.env' --exec cat {} \; | rg -i 'kakao|VITE_KAKAO'
fd -t f 'README' --exec cat {} \; | rg -i 'kakao|environment|\.env'

Length of output: 0


Add and document Kakao OAuth environment variables

  • Add VITE_KAKAO_REST_API_KEY and VITE_KAKAO_REDIRECT_URI to .env.example (or equivalent).
  • Document their usage in the README and ensure they’re set in all deployment environments.
🤖 Prompt for AI Agents
In src/pages/admin/Login/component/LoginButton/index.tsx lines 8-11, you need to
switch the hardcoded REST_API_KEY and REDIRECT_URI to use Vite env vars and add
+ document those variables: add VITE_KAKAO_REST_API_KEY and
VITE_KAKAO_REDIRECT_URI to .env.example with short descriptions and example
values, update the README (Auth / Kakao section) to show how to obtain and set
those vars for local, staging and production, and verify your deployment
configurations (CI/CD, hosting env settings) include the two VITE_KAKAO_* vars;
ensure the component reads them via import.meta.env.VITE_KAKAO_REST_API_KEY and
import.meta.env.VITE_KAKAO_REDIRECT_URI (and include a brief runtime
fallback/error log if missing).

Comment on lines +4 to +11
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate environment variables before use.

The REST_API_KEY and REDIRECT_URI could be undefined if environment variables are not set. This would result in a malformed OAuth URL at runtime.

Add validation and error handling:

 const REST_API_KEY = import.meta.env.VITE_KAKAO_REST_API_KEY;
 const REDIRECT_URI = import.meta.env.VITE_KAKAO_REDIRECT_URI;

 export const LoginButton = () => {
   const handleKakaoLogin = () => {
+    if (!REST_API_KEY || !REDIRECT_URI) {
+      console.error('Kakao OAuth configuration missing');
+      return;
+    }
     const kakaoAuthUrl = `https://kauth.kakao.com/oauth/authorize?client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}&response_type=code`;
     window.location.href = kakaoAuthUrl;
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const REST_API_KEY = import.meta.env.VITE_KAKAO_REST_API_KEY;
const REDIRECT_URI = import.meta.env.VITE_KAKAO_REDIRECT_URI;
export const LoginButton = () => {
const handleKakaoLogin = () => {
const kakaoAuthUrl = `https://kauth.kakao.com/oauth/authorize?client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}&response_type=code`;
window.location.href = kakaoAuthUrl;
};
const REST_API_KEY = import.meta.env.VITE_KAKAO_REST_API_KEY;
const REDIRECT_URI = import.meta.env.VITE_KAKAO_REDIRECT_URI;
export const LoginButton = () => {
const handleKakaoLogin = () => {
if (!REST_API_KEY || !REDIRECT_URI) {
console.error('Kakao OAuth configuration missing');
return;
}
const kakaoAuthUrl = `https://kauth.kakao.com/oauth/authorize?client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}&response_type=code`;
window.location.href = kakaoAuthUrl;
};
// …rest of the component
};
🤖 Prompt for AI Agents
In src/pages/admin/Login/component/LoginButton/index.tsx around lines 4 to 11,
the code builds the Kakao OAuth URL using REST_API_KEY and REDIRECT_URI without
checking they exist; validate both import.meta.env values before using them, and
handle missing values by preventing navigation and surfacing an error (e.g.,
disable the button and show a console/error message or throw a clear error).
Specifically, if either REST_API_KEY or REDIRECT_URI is falsy, do not construct
or assign window.location.href; instead set an error state or log a descriptive
message indicating which env var is missing so the UI can avoid creating a
malformed OAuth URL and inform the developer/operator.


return (
<Container>
<Button width='100%' type='submit'>
{'로그인'}
<Button to='/signup' width='100%' type='button'>
{'회원가입'}
</Button>
<KakaoButtonWrapper>
<Button width='100%' type='submit'>
<Button width='100%' type='button' onClick={handleKakaoLogin}>
<ButtonContent>
<Icon src='/assets/kakao-icon.png' width={24} height={24} />
<span>카카오 로그인</span>
{'카카오 로그인'}
</ButtonContent>
</Button>
</KakaoButtonWrapper>
Expand Down
29 changes: 29 additions & 0 deletions src/pages/admin/Signup/Page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import styled from '@emotion/styled';
import { HeaderTitle } from './components/HeaderTitle';
import { SignupForm } from './components/SignupForm';

export const AdminSignupPage = () => {
return (
<Layout>
<HeaderTitle />
<SignupForm />
</Layout>
);
};

export const Layout = styled.main(({ theme }) => ({
minHeight: '100vh',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '1.5rem',
maxWidth: '1200px',
width: '100%',
margin: '0 auto 4rem auto',
padding: '0 1.5rem',
boxSizing: 'border-box',

[`@media (max-width: ${theme.breakpoints.mobile})`]: {
padding: '1rem',
},
}));
15 changes: 15 additions & 0 deletions src/pages/admin/Signup/api/signup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { SignupFormInputs } from '../type/signup';

export const postSignupForm = async (formData: SignupFormInputs): Promise<SignupFormInputs> => {
const url = `${import.meta.env.VITE_API_BASE_URL}/auth/register`;
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(formData),
});

if (!response.ok) throw new Error('회원 가입 양식을 제출하지 못했습니다.');
return await response.json();
};
Comment on lines +3 to +15
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

프로젝트 내에서 axiosfetch가 혼용되고 있습니다. 코드의 일관성을 유지하고, 인터셉터나 공통 에러 처리 등 axios의 이점을 활용하기 위해 axios로 통일하는 것을 권장합니다. 또한, postSignupForm 함수의 반환값이 SignupForm 컴포넌트에서 사용되지 않으므로, 반환 타입을 Promise<void>로 변경하고 response.json()을 호출하지 않는 것이 더 안전합니다. 성공적인 POST 요청 후 응답 body가 비어있을 경우 response.json()은 에러를 발생시킬 수 있습니다.

22 changes: 22 additions & 0 deletions src/pages/admin/Signup/components/HeaderTitle/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import styled from '@emotion/styled';

export const HeaderTitle = () => {
return (
<TextWrapper>
<Title>관리자 등록</Title>
</TextWrapper>
);
};
const TextWrapper = styled.div({
display: 'flex',
flexDirection: 'column',
gap: '1rem',
padding: '2rem 0 1rem 0',
});

const Title = styled.h1(({ theme }) => ({
fontSize: '2rem',
fontWeight: theme.font.weight.medium,
lineHeight: 1.3,
color: theme.colors.gray900,
}));
52 changes: 52 additions & 0 deletions src/pages/admin/Signup/components/SignupForm/index.styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import styled from '@emotion/styled';

export const UserInfoWrapper = styled.div(({ theme }) => ({
boxSizing: 'border-box',
width: '48rem',
display: 'flex',
flexDirection: 'column',
gap: '30px',
borderBottom: `1px solid ${theme.colors.gray200}`,
paddingBottom: '3rem',

'@media (max-width: 48rem)': {
width: '100%',
},
}));

export const FormField = styled.div({
display: 'flex',
flexDirection: 'column',
gap: '10px',
});

export const FormRow = styled.div({
display: 'flex',
flexDirection: 'row',
gap: '2.5rem',
width: '100%',
'& > *': {
flex: '1 1 0',
minWidth: 0,
},
});

export const Label = styled.label(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: '4px',
fontWeight: theme.font.weight.medium,
}));

export const FormContainer = styled.main({
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
gap: '3rem',
});

export const ErrorMessage = styled.span(({ theme }) => ({
color: theme.colors.warning,
fontSize: theme.font.size.xs,
padding: 0,
}));
128 changes: 128 additions & 0 deletions src/pages/admin/Signup/components/SignupForm/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { useForm, FormProvider } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import { toast } from 'sonner';
import { postSignupForm } from '@/pages/admin/Signup/api/signup';
import * as S from '@/pages/admin/Signup/components/SignupForm/index.styled';
import { Button } from '@/shared/components/Button';
import { OutlineInputField } from '@/shared/components/Form/InputField/OutlineInputField';
import { theme } from '@/styles/theme';
import type { SignupFormInputs } from '@/pages/admin/Signup/type/signup';

export const SignupForm = () => {
const navigate = useNavigate();
const methods = useForm<SignupFormInputs>({
mode: 'onTouched',
defaultValues: {
name: '',
email: '',
studentId: '',
department: '',
phoneNumber: '',
},
});

const { errors, isSubmitting } = methods.formState;

const onSubmit = async (signupFormValue: SignupFormInputs) => {
try {
await postSignupForm(signupFormValue);
toast.success('회원가입 완료!', {
style: { backgroundColor: theme.colors.primary, color: 'white' },
duration: 1000,
});
setTimeout(() => {
navigate(`/login`);
}, 1000);
} catch (e) {
console.error(e);
toast.error('회원가입 실패!', {
duration: 1000,
style: {
backgroundColor: 'white',
color: theme.colors.error,
},
});
}
};
Comment on lines 26 to 46
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

onSubmit 함수의 비동기 로직에 심각한 오류가 있습니다.

  1. async 함수 내에서 await 대신 .then()을 사용하여 postSignupForm을 호출하고 있어, API 호출이 완료되기를 기다리지 않고 바로 다음 코드가 실행됩니다.
  2. API 호출 직후 성공 토스트(회원가입 완료!)와 페이지 이동(navigate('/login')) 로직이 중복되어 실행됩니다. 이로 인해 API 호출 결과와 상관없이 항상 성공 메시지가 표시되고 페이지가 이동하는 버그가 발생합니다.

await를 사용하여 비동기 흐름을 명확히 하고, 성공/실패에 따른 처리를 try...catch 블록 안에서 일관되게 관리해야 합니다.

  const onSubmit = async (signupFormValue: SignupFormInputs) => {
    try {
      await postSignupForm(signupFormValue);
      toast.success('회원가입 완료!', {
        style: { backgroundColor: theme.colors.primary, color: 'white' },
        duration: 1000,
      });

      setTimeout(() => {
        navigate('/login');
      }, 1000);
    } catch (error) {
      toast.error('회원가입에 실패했습니다.', {
        duration: 1000,
        style: {
          backgroundColor: 'white',
          color: theme.colors.error,
        },
      });
    }
  };

Comment on lines 26 to 46
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix unused error variable and navigation timing issue.

Two concerns:

  1. Pipeline failure: The error parameter on Line 36 is unused. Either prefix it with an underscore (_error) to mark as intentionally unused, or log it for debugging.

  2. Navigation timing hazard: The setTimeout on Lines 33-35 can trigger navigation after the component unmounts, potentially causing React warnings or failed navigations if the user closes the tab or navigates elsewhere during the 1-second delay.

Apply this diff to address both issues:

-    } catch (error) {
+    } catch (_error) {
       toast.error('회원가입 실패!', {
         duration: 1000,
         style: {
           backgroundColor: 'white',
           color: theme.colors.error,
         },
       });
     }

For the navigation timing issue, consider one of these patterns:

Option 1: Use toast's onAutoClose callback (cleaner)

     try {
       await postSignupForm(signupFormValue);
       toast.success('회원가입 완료!', {
         style: { backgroundColor: theme.colors.primary, color: 'white' },
         duration: 1000,
+        onAutoClose: () => navigate('/login'),
       });
-      setTimeout(() => {
-        navigate(`/login`);
-      }, 1000);

Option 2: Store timeout ID and cleanup in useEffect

useEffect(() => {
  return () => {
    // cleanup any pending timeouts when component unmounts
  };
}, []);

// In onSubmit:
const timeoutId = setTimeout(() => navigate('/login'), 1000);
// Store timeoutId for cleanup if needed
🧰 Tools
🪛 GitHub Actions: Dongarium FE CI/CD

[error] 36-36: ESLint: 'error' is defined but never used. (no-unused-vars). Command: npm run lint

🪛 GitHub Check: lint

[failure] 36-36:
'error' is defined but never used


return (
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
<S.FormContainer>
<S.UserInfoWrapper>
<S.FormField>
<S.Label>이름</S.Label>
<OutlineInputField
placeholder='이름을 입력하세요.'
{...methods.register('name', { required: '이름을 입력하세요.' })}
invalid={!!errors.name}
message={errors.name?.message}
/>
</S.FormField>

<S.FormField>
<S.Label>이메일</S.Label>
<OutlineInputField
placeholder='이메일을 입력하세요.'
{...methods.register('email', {
required: '이메일을 입력하세요.',
pattern: {
value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: '올바른 이메일 형식이 아닙니다.',
},
})}
invalid={!!errors.email}
message={errors.email?.message}
/>
</S.FormField>

<S.FormRow>
<S.FormField>
<S.Label>학번</S.Label>
<OutlineInputField
placeholder='학번을 입력하세요.'
{...methods.register('studentId', {
required: '학번을 입력하세요.',
pattern: {
value: /^[0-9]{6}$/,
message: '학번은 숫자 6자리여야 합니다.',
},
})}
invalid={!!errors.studentId}
message={errors.studentId?.message}
/>
</S.FormField>

<S.FormField>
<S.Label>학과</S.Label>
<OutlineInputField
placeholder='학과를 입력하세요.'
{...methods.register('department', { required: '학과를 입력하세요.' })}
invalid={!!errors.department}
message={errors.department?.message}
/>
</S.FormField>
</S.FormRow>

<S.FormField>
<S.Label>전화번호</S.Label>
<OutlineInputField
placeholder='010-0000-0000'
{...methods.register('phoneNumber', {
required: '전화번호를 입력하세요.',
pattern: {
value: /^\d{2,3}-\d{3,4}-\d{4}$/,
message: '올바른 전화번호 형식이 아닙니다.',
Comment on lines +110 to +115
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Phone number pattern mismatch with placeholder.

The regex pattern allows 2-3 digit area codes (\d{2,3}), but the placeholder shows 010-0000-0000 (3 digits). If only 3-digit area codes like 010 are valid, update the pattern to /^010-\d{3,4}-\d{4}$/. Otherwise, update the placeholder to reflect the flexibility, e.g., 010-1234-5678 또는 02-1234-5678.

Apply this diff if only 3-digit area codes are intended:

                   pattern: {
-                    value: /^\d{2,3}-\d{3,4}-\d{4}$/,
+                    value: /^010-\d{3,4}-\d{4}$/,
                     message: '올바른 전화번호 형식이 아닙니다.',
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
placeholder='010-0000-0000'
{...methods.register('phoneNumber', {
required: '전화번호를 입력하세요.',
pattern: {
value: /^\d{2,3}-\d{3,4}-\d{4}$/,
message: '올바른 전화번호 형식이 아닙니다.',
placeholder='010-0000-0000'
{...methods.register('phoneNumber', {
required: '전화번호를 입력하세요.',
pattern: {
value: /^010-\d{3,4}-\d{4}$/,
message: '올바른 전화번호 형식이 아닙니다.',
🤖 Prompt for AI Agents
In src/pages/admin/Signup/components/SignupForm/index.tsx around lines 109-114,
the phone placeholder "010-0000-0000" conflicts with the current regex allowing
2-3 digit area codes; fix by either (A) if only 3-digit area codes like 010 are
valid, change the validation pattern to require "010" specifically (replace
current pattern with one matching /^010-\d{3,4}-\d{4}$/), or (B) if both 2- and
3-digit area codes are allowed, update the placeholder to reflect that
flexibility (e.g., "010-1234-5678 또는 02-1234-5678"); apply the corresponding
change to the register call and/or placeholder text so pattern and placeholder
match.

},
})}
invalid={!!errors.phoneNumber}
message={errors.phoneNumber?.message}
/>
</S.FormField>
</S.UserInfoWrapper>
<Button type='submit'>{isSubmitting ? '제출중...' : '회원가입'}</Button>
</S.FormContainer>
</form>
</FormProvider>
);
};
7 changes: 7 additions & 0 deletions src/pages/admin/Signup/type/signup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export type SignupFormInputs = {
name: string;
email: string;
studentId: string;
department: string;
phoneNumber: string;
};