Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

πŸ”€ λ°•λžŒνšŒ 폼 μ‹ μ²­ ν•˜κΈ° μ œμž‘ #87

Merged
merged 4 commits into from
Feb 2, 2025

Conversation

Ethen1264
Copy link
Contributor

@Ethen1264 Ethen1264 commented Feb 1, 2025

πŸ’‘ λ°°κ²½ 및 κ°œμš”

λ°•λžŒνšŒ 폼 μ‹ μ²­ ν•˜κΈ° μ œμž‘

πŸ“ƒ μž‘μ—…λ‚΄μš©

  • 폼 μ‹ μ²­ν•˜κΈ° μ»΄ν¬λ„ŒνŠΈ μ œμž‘
  • 사전 μ—°μˆ˜μž μ‹ μ²­ 둜직
  • 사전 μ°Έκ°€μž μ‹ μ²­ 둜직

🎸 기타

  • μΆ”ν›„ ν˜„μž₯ μ‹ μ²­μž κΈ°λŠ₯이 ν•„μš”ν•΄ λ³΄μž„

Summary by CodeRabbit

  • New Features & Enhancements
    • 동적 폼 λ Œλ”λ§ κΈ°λŠ₯이 μΆ”κ°€λ˜μ–΄, λ‹€μ–‘ν•œ μž…λ ₯ μ˜΅μ…˜(μ²΄ν¬λ°•μŠ€, λ“œλ‘­λ‹€μš΄, 닀쀑선택, ν…μŠ€νŠΈ μ˜μ—­)κ³Ό μœ μ—°ν•œ UI ꡬ성이 κ°€λŠ₯ν•΄μ‘ŒμŠ΅λ‹ˆλ‹€.
    • μ‹ μ²­ 처리 μ‹œ 성곡 및 였λ₯˜ μ•Œλ¦Ό ν”Όλ“œλ°±μ΄ κ°œμ„ λ˜μ–΄ μ‚¬μš©μž κ²½ν—˜μ΄ ν–₯μƒλ˜μ—ˆμŠ΅λ‹ˆλ‹€.
  • Bug Fixes
    • API 응닡에 μ˜¬λ°”λ₯Έ HTTP μƒνƒœ μ½”λ“œκ°€ ν¬ν•¨λ˜μ–΄, 였λ₯˜ μ²˜λ¦¬μ™€ λΌμš°νŒ… 일관성이 κ°œμ„ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
    • μ‹λ³„μž νƒ€μž… μ—…λ°μ΄νŠΈλ‘œ μ „λ°˜μ μΈ μ‹œμŠ€ν…œ μ•ˆμ •μ„±μ΄ κ°•ν™”λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

@Ethen1264 Ethen1264 added ✨ Feature μ‹ κ·œ κΈ°λŠ₯ 🎨 Type: Publishing λ””μžμΈ κ΅¬ν˜„ πŸ“¬ Type: API μ„œλ²„ API 톡신 labels Feb 1, 2025
@Ethen1264 Ethen1264 self-assigned this Feb 1, 2025
Copy link

coderabbitai bot commented Feb 1, 2025

Walkthrough

이 PR은 μ—¬λŸ¬ μ»΄ν¬λ„ŒνŠΈ, API, νƒ€μž… 및 후크에 걸쳐 λ‹€μ–‘ν•œ 변경사항을 ν¬ν•¨ν•©λ‹ˆλ‹€. API λΌμš°νŠΈμ—μ„œλŠ” POST 응닡에 HTTP μƒνƒœ μ½”λ“œλ₯Ό ν¬ν•¨ν•˜λ„λ‘ μˆ˜μ •λ˜κ³ , 폼 API에 GET λ©”μ†Œλ“œκ°€ μΆ”κ°€λ˜μ—ˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ νŽ˜μ΄μ§€ 및 Application μ»΄ν¬λ„ŒνŠΈμ˜ νŒŒλΌλ―Έν„° νƒ€μž…μ΄ numberμ—μ„œ string으둜 λ³€κ²½λ˜μ—ˆμœΌλ©°, μƒˆλ‘œμš΄ UI μ»΄ν¬λ„ŒνŠΈ(μ²΄ν¬λ°•μŠ€, λ“œλ‘­λ‹€μš΄, 닀쀑 μ˜΅μ…˜, μ˜΅μ…˜ μ»¨ν…Œμ΄λ„ˆ, λ¬Έμž₯ μ˜΅μ…˜)κ°€ μΆ”κ°€λ˜κ³  κΈ°μ‘΄ μ»΄ν¬λ„ŒνŠΈ λͺ‡ κ°œκ°€ μ‚­μ œλ˜μ—ˆμŠ΅λ‹ˆλ‹€. 곡유 νƒ€μž… 및 react-query 기반의 폼 쑰회/제좜 후크도 μƒˆλ‘œ λ„μž…λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

Changes

파일 λ³€κ²½ λ‚΄μš©
src/app/api/application/[expo_id]/route.ts, src/app/api/application/field/[expo_id]/route.ts, src/app/api/application/field/standard/[expo_id]/route.ts, src/app/api/application/pre-standard/[expo_id]/route.ts POST 응닡 처리 μ‹œ HTTP μƒνƒœ μ½”λ“œ ν¬ν•¨ν•˜λ„λ‘ λ³€κ²½ (NextResponse.json μˆ˜μ •)
src/app/api/form/[expo_id]/route.ts GET λ©”μ†Œλ“œ μΆ”κ°€ 및 POST 응닡 처리 κ°„μ†Œν™”
src/app/(pages)/application/[id]/[type]/page.tsx, src/views/application/ui/application/index.tsx id/params νƒ€μž… λ³€κ²½ (number β†’ string)
src/entities/application/index.tsx, src/entities/application/ui/Input/index.tsx, src/entities/application/ui/RadioGroup/index.tsx λΆˆν•„μš”ν•œ Input 및 RadioGroup μ»΄ν¬λ„ŒνŠΈ 제거/μ‚­μ œ
src/entities/application/ui/CheckBoxOption/index.tsx, src/entities/application/ui/DropDownOption/index.tsx, src/entities/application/ui/MultipleOption/index.tsx, src/entities/application/ui/OptionContainer/index.tsx, src/entities/application/ui/SentenceOption/index.tsx μƒˆλ‘œμš΄ UI μ»΄ν¬λ„ŒνŠΈ μΆ”κ°€ (μ²΄ν¬λ°•μŠ€, λ“œλ‘­λ‹€μš΄, 닀쀑 μ˜΅μ…˜, μ˜΅μ…˜ μ»¨ν…Œμ΄λ„ˆ, λ¬Έμž₯ μ˜΅μ…˜)
src/shared/types/application/type.ts 폼 κ΄€λ ¨ μƒˆλ‘œμš΄ μΈν„°νŽ˜μ΄μŠ€ 및 νƒ€μž… μΆ”κ°€ (DynamicFormItem, ApplicationForm λ“±)
src/widgets/application/api/getForm.ts, src/widgets/application/api/postApplication.ts μƒˆ API ν•¨μˆ˜ μΆ”κ°€: 폼 쑰회 및 제좜
src/widgets/application/model/useGetForm.ts, src/widgets/application/model/usePostApplication.ts react-query 기반 폼 쑰회 및 제좜 μ»€μŠ€ν…€ 후크 μΆ”κ°€
src/widgets/application/api/formApi.ts, src/widgets/application/model/applicationFormHandler.ts, src/widgets/application/ui/form/ApplicationForm/standard/index.tsx, src/widgets/application/ui/form/ApplicationForm/trainee/index.tsx, src/entities/application/ui/TrainingRadioGroup/index.tsx λΆˆν•„μš” μ½”λ“œ 및 μ»΄ν¬λ„ŒνŠΈ μ‚­μ œ (폼 API, 제좜 ν•Έλ“€λŸ¬, UI 폼 μ»΄ν¬λ„ŒνŠΈ)

Sequence Diagram(s)

sequenceDiagram
    participant User as μ‚¬μš©μž
    participant AL as ApplicationLayout
    participant PH as usePostApplication
    participant API as postApplication API
    participant Server as μ„œλ²„
    participant Toast as ν† μŠ€νŠΈ μ•Œλ¦Ό

    User->>AL: 폼 제좜
    AL->>PH: onSubmit 호좜
    PH->>API: postApplication 호좜 (params, type, data)
    API->>Server: HTTP POST μš”μ²­
    Server-->>API: 응닡 (data, status)
    API-->>PH: 응닡 λ°˜ν™˜
    PH-->>AL: 변이 성곡
    AL->>Toast: 성곡 λ©”μ‹œμ§€ ν‘œμ‹œ 및 λ¦¬λ‹€μ΄λ ‰νŠΈ
Loading
sequenceDiagram
    participant Hook as useGetForm
    participant GF as getForm
    participant API as API μ—”λ“œν¬μΈνŠΈ

    Hook->>GF: 폼 id 및 type μš”μ²­
    GF->>API: HTTP GET μš”μ²­ (/api/form/{id}?type={type})
    API-->>GF: 응닡 λ°˜ν™˜ (data)
    GF-->>Hook: 데이터 λ°˜ν™˜
Loading

Possibly related PRs

Suggested labels

🐞 Bug

Suggested reviewers

  • happytaeyoon

Poem

μ•ˆλ…•, λ‚˜λŠ” κ·€μ—¬μš΄ 토끼야,
μ½”λ“œ 숲 속을 λ›°λ©° 변경을 λ…Έλž˜ν•˜μ§€,
νƒ€μž…μ€ κΉ”λ”ν•˜κ²Œ, 응닡도 투λͺ…ν•˜κ²Œ,
μƒˆλ‘œμš΄ κΈ°λŠ₯듀이 μΆ€μΆ”λ“― 배치되고,
λ‚΄ 발자ꡭ처럼 μ½”λ“œμ— 즐거움이 μŠ€λ©°λ“œλ„€ 🐰✨
ν•¨κ»˜ λ‹¬λ¦¬μž, μ½”λ“œμ˜ λ“€νŒμœΌλ‘œ!

✨ Finishing Touches
  • πŸ“ Generate Docstrings (Beta)

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❀️ Share
πŸͺ§ Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

πŸ”­ Outside diff range comments (2)
src/app/api/application/field/[expo_id]/route.ts (1)

Line range hint 1-35: μ½”λ“œ 쀑볡 제거 ν•„μš”

이 파일의 μ½”λ“œκ°€ λ‹€λ₯Έ API 라우트 νŒŒμΌλ“€κ³Ό 거의 λ™μΌν•©λ‹ˆλ‹€. 곡톡 λ‘œμ§μ„ λ³„λ„μ˜ λ―Έλ“€μ›¨μ–΄λ‚˜ μœ ν‹Έλ¦¬ν‹° ν•¨μˆ˜λ‘œ μΆ”μΆœν•˜λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같은 뢀뢄듀을 곡톡화할 수 μžˆμŠ΅λ‹ˆλ‹€:

  1. 인증 둜직
  2. μ—λŸ¬ 처리
  3. 응닡 ν¬λ§·νŒ…

μ˜ˆμ‹œ λ¦¬νŒ©ν† λ§:

// src/shared/middleware/withAuth.ts
export const withAuth = (handler: NextApiHandler) => async (req: NextApiRequest, res: NextApiResponse) => {
  const cookieStore = cookies();
  const accessToken = cookieStore.get('accessToken')?.value;
  const config = accessToken
    ? {
        headers: {
          Authorization: `Bearer ${accessToken}`,
        },
      }
    : {};
  
  try {
    return await handler(req, res, config);
  } catch (error) {
    const axiosError = error as AxiosError<{ message: string }>;
    const status = axiosError.response?.status;
    const message = axiosError.response?.data?.message;
    return NextResponse.json({ error: message }, { status });
  }
};
src/app/api/application/pre-standard/[expo_id]/route.ts (1)

Line range hint 11-19: λ³΄μ•ˆ κ°œμ„  μ œμ•ˆ

ν˜„μž¬ 인증 토큰이 μ—†λŠ” κ²½μš°μ—λ„ μš”μ²­μ΄ μ²˜λ¦¬λ©λ‹ˆλ‹€. λ°•λžŒνšŒ μ‹ μ²­κ³Ό 같은 μ€‘μš”ν•œ μž‘μ—…μ€ 인증된 μ‚¬μš©μžλ§Œ μˆ˜ν–‰ν•  수 μžˆλ„λ‘ μ œν•œν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

   const config = accessToken
     ? {
         headers: {
           Authorization: `Bearer ${accessToken}`,
         },
       }
-    : {};
+    : null;
+  
+  if (!config) {
+    return NextResponse.json(
+      { error: '인증이 ν•„μš”ν•©λ‹ˆλ‹€' },
+      { status: 401 }
+    );
+  }
🧹 Nitpick comments (11)
src/widgets/application/ui/ApplicationLayout/index.tsx (4)

20-20: useForm μ‚¬μš© μ‹œ 검증 둜직 ꢌμž₯
useForm<ApplicationFormValues>()λ₯Ό μ‚¬μš© μ€‘μ΄μ§€λ§Œ, resolver(예: yupResolver) 등을 μΆ”κ°€ν•˜μ—¬ 폼 데이터에 λŒ€ν•œ μŠ€ν‚€λ§ˆ 검증을 μ μš©ν•˜λ©΄ μœ νš¨μ„± 검사가 더 κ²¬κ³ ν•΄μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€.


22-22: μ„œλ²„ 응닡 μ—λŸ¬ 처리 보강 μ œμ•ˆ
useGetForm으둜 formListλ₯Ό κ°€μ Έμ˜¬ λ•Œ, isLoading 외에도 였λ₯˜ λ°œμƒ μ‹œμ— λŒ€μ‘ν•˜κΈ° μœ„ν•œ isError 등을 μ‚¬μš©ν•˜λ©΄ μ‚¬μš©μž μ•ˆλ‚΄μ— 도움이 λ©λ‹ˆλ‹€.


24-24: μ—λŸ¬ 외에 성곡 λ©”μ‹œμ§€ κ²€ν† 
usePostApplication μ‚¬μš© μ‹œ, μ‹€νŒ¨ μ‹œμ—λŠ” μ—λŸ¬ 처리λ₯Ό ν•˜λ”λΌλ„, 성곡 μ‹œμ— μ•ˆλ‚΄(성곡 ν† μŠ€νŠΈ λ“±)κ°€ ν•„μš”ν•  수 μžˆμŠ΅λ‹ˆλ‹€. κ³ λ €ν•΄λ³΄μ‹œλ©΄ μ’‹κ² μŠ΅λ‹ˆλ‹€.


26-28: showError ν•¨μˆ˜μ˜ 처리 λ²”μœ„ ν™•λŒ€ μ œμ•ˆ
ν˜„μž¬ toast.error만 ν˜ΈμΆœν•˜λŠ”λ°, μ—λŸ¬ μœ ν˜•(μ„œλ²„ λ‹€μš΄, κΆŒν•œ 문제 λ“±)에 따라 λ‹€λ₯Έ μ•‘μ…˜μ΄λ‚˜ 둜그λ₯Ό λΆ„κΈ°ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. 상황별 λŒ€μ‘ λ‘œμ§μ„ ν™•μž₯ν•˜λ©΄ μ’‹κ² μŠ΅λ‹ˆλ‹€.

src/shared/types/application/type.ts (1)

1-5: 폼 νƒ€μž…μ— λŒ€ν•œ μΆ”κ°€ μ œμ•½ 쑰건이 ν•„μš”ν•©λ‹ˆλ‹€.

DynamicFormItem μΈν„°νŽ˜μ΄μŠ€μ˜ jsonData ν•„λ“œκ°€ λ‹¨μˆœ string νƒ€μž…μœΌλ‘œ μ •μ˜λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. JSON λ°μ΄ν„°μ˜ ꡬ쑰λ₯Ό 더 λͺ…ν™•ν•˜κ²Œ μ •μ˜ν•˜λ©΄ νƒ€μž… μ•ˆμ „μ„±μ„ 높일 수 μžˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같은 κ°œμ„ μ„ μ œμ•ˆλ“œλ¦½λ‹ˆλ‹€:

 export interface DynamicFormItem {
   title: string;
   formType: 'SENTENCE' | 'CHECKBOX' | 'MULTIPLE' | 'DROPDOWN';
-  jsonData: string;
+  jsonData: {
+    options?: string[];
+    placeholder?: string;
+    required?: boolean;
+  };
 }
src/widgets/application/model/usePostApplication.ts (1)

7-21: μ—λŸ¬ 처리λ₯Ό κ°œμ„ ν•˜μ„Έμš”

μ—λŸ¬ μ²˜λ¦¬κ°€ λ„ˆλ¬΄ λ‹¨μˆœν™”λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. μ‚¬μš©μžμ—κ²Œ 더 ꡬ체적인 μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό μ œκ³΅ν•˜λ©΄ 쒋을 것 κ°™μŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 κ°œμ„ ν•΄λ³΄μ„Έμš”:

  return useMutation({
    mutationFn: (data: FormattedApplicationData) =>
      postApplication(params, type, data),
    onSuccess: () => {
      toast.success('λ°•λžŒνšŒ 신청이 μ™„λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€.');
      router.push('/');
    },
-   onError: () => {
-     toast.error('λ°•λžŒνšŒ 신청에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.');
-   },
+   onError: (error: any) => {
+     const errorMessage = error?.response?.data?.message || 'λ°•λžŒνšŒ 신청에 μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€.';
+     toast.error(errorMessage);
+   },
  });
src/app/api/form/[expo_id]/route.ts (1)

35-54: GET λ©”μ†Œλ“œ κ΅¬ν˜„μ— λŒ€ν•œ ν”Όλ“œλ°±

GET λ©”μ†Œλ“œ κ΅¬ν˜„μ΄ μ „λ°˜μ μœΌλ‘œ 잘 λ˜μ–΄μžˆμœΌλ‚˜, λͺ‡ 가지 κ°œμ„ μ΄ ν•„μš”ν•©λ‹ˆλ‹€:

  1. νƒ€μž… νŒŒλΌλ―Έν„°κ°€ μ—†λŠ” κ²½μš°μ— λŒ€ν•œ μ²˜λ¦¬κ°€ ν•„μš”ν•©λ‹ˆλ‹€.
  2. μ—λŸ¬ 응닡에 κΈ°λ³Έ λ©”μ‹œμ§€λ₯Ό μΆ”κ°€ν•˜λ©΄ 쒋을 것 κ°™μŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•˜λŠ” 것을 μ œμ•ˆν•©λ‹ˆλ‹€:

 export async function GET(
   request: NextRequest,
   { params }: { params: { expo_id: string } },
 ) {
   const { expo_id } = params;
   const { searchParams } = new URL(request.url);
   const type = searchParams.get('type');

   try {
     const response = await apiClient.get(`/form/${expo_id}`, {
       params: { type },
     });
     return NextResponse.json(response.data);
   } catch (error) {
     const axiosError = error as AxiosError<{ message: string }>;
     const status = axiosError.response?.status || 500;
-    const message = axiosError.response?.data?.message;
+    const message = axiosError.response?.data?.message || '폼을 λΆˆλŸ¬μ˜€λŠ”λ° μ‹€νŒ¨ν–ˆμŠ΅λ‹ˆλ‹€';
     return NextResponse.json({ error: message }, { status });
   }
 }
src/entities/application/ui/SentenceOption/index.tsx (2)

5-11: Props μΈν„°νŽ˜μ΄μŠ€μ— λŒ€ν•œ ν”Όλ“œλ°±

Props μΈν„°νŽ˜μ΄μŠ€κ°€ 잘 μ •μ˜λ˜μ–΄ μžˆμœΌλ‚˜, λͺ‡ 가지 κ°œμ„ μ‚¬ν•­μ΄ μžˆμŠ΅λ‹ˆλ‹€:

  1. maxLength와 row에 λŒ€ν•œ κΈ°λ³Έκ°’ 섀정이 ν•„μš”ν•©λ‹ˆλ‹€.
  2. Props μΈν„°νŽ˜μ΄μŠ€μ— μ„€λͺ…적인 JSDoc 주석을 μΆ”κ°€ν•˜λ©΄ 쒋을 것 κ°™μŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•˜λŠ” 것을 μ œμ•ˆν•©λ‹ˆλ‹€:

+/**
+ * SentenceOption μ»΄ν¬λ„ŒνŠΈμ˜ Props μΈν„°νŽ˜μ΄μŠ€
+ * @property maxLength - ν…μŠ€νŠΈ μ˜μ—­μ˜ μ΅œλŒ€ 길이
+ * @property row - ν…μŠ€νŠΈ μ˜μ—­μ˜ κΈ°λ³Έ ν–‰ 수
+ * @property required - ν•„μˆ˜ μž…λ ₯ μ—¬λΆ€
+ * @property register - react-hook-form의 register ν•¨μˆ˜
+ * @property name - 폼 ν•„λ“œ 이름
+ */
 interface Props {
   maxLength: number;
   row: number;
   required: boolean;
   register: UseFormRegister<ApplicationFormValues>;
   name: string;
 }

24-34: handleChange ν•¨μˆ˜ κ°œμ„  μ œμ•ˆ

handleChange ν•¨μˆ˜μ˜ κ΅¬ν˜„μ΄ 잘 λ˜μ–΄ μžˆμœΌλ‚˜, μ„±λŠ₯ μ΅œμ ν™”λ₯Ό μœ„ν•œ κ°œμ„ μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•˜λŠ” 것을 μ œμ•ˆν•©λ‹ˆλ‹€:

-  const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
+  const handleChange = React.useCallback((e: React.ChangeEvent<HTMLTextAreaElement>) => {
     if (textareaRef.current) {
       textareaRef.current.style.height = 'auto';
       textareaRef.current.style.height = `${textareaRef.current.scrollHeight}px`;
     }
     setHasInput(e.target.value.length > 0);

     if (onChange) {
       onChange(e);
     }
-  };
+  }, [onChange]);
src/entities/application/ui/OptionContainer/index.tsx (2)

26-54: switch λ¬Έ λ¦¬νŒ©ν† λ§ μ œμ•ˆ

switch 문을 μ‚¬μš©ν•œ κ΅¬ν˜„μ΄ λ³΅μž‘ν•˜κ³  ν™•μž₯성이 μ œν•œμ μž…λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 객체 맀핑을 μ‚¬μš©ν•˜μ—¬ λ¦¬νŒ©ν† λ§ν•˜λŠ” 것을 μ œμ•ˆν•©λ‹ˆλ‹€:

-  let inputComponent;
-  switch (formType) {
-    case 'SENTENCE':
-      inputComponent = (
-        <SentenceOption
-          register={register}
-          name={title}
-          maxLength={1000}
-          row={1}
-          required={false}
-        />
-      );
-      break;
-    case 'CHECKBOX':
-      inputComponent = (
-        <CheckBoxOption options={options} register={register} name={title} />
-      );
-      break;
-    case 'MULTIPLE':
-      inputComponent = (
-        <MultipleOption options={options} register={register} name={title} />
-      );
-      break;
-    case 'DROPDOWN':
-      inputComponent = (
-        <DropDownOption options={options} register={register} name={title} />
-      );
-      break;
-  }
+  const componentMap = {
+    SENTENCE: () => (
+      <SentenceOption
+        register={register}
+        name={title}
+        maxLength={1000}
+        row={1}
+        required={false}
+      />
+    ),
+    CHECKBOX: () => (
+      <CheckBoxOption options={options} register={register} name={title} />
+    ),
+    MULTIPLE: () => (
+      <MultipleOption options={options} register={register} name={title} />
+    ),
+    DROPDOWN: () => (
+      <DropDownOption options={options} register={register} name={title} />
+    ),
+  };
+
+  const inputComponent = componentMap[formType as keyof typeof componentMap]?.();

56-61: μ»¨ν…Œμ΄λ„ˆ μŠ€νƒ€μΌλ§ κ°œμ„  μ œμ•ˆ

border-1 ν΄λž˜μŠ€κ°€ Tailwind CSSμ—μ„œ μ§€μ›λ˜μ§€ μ•Šμ„ 수 μžˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•˜λŠ” 것을 μ œμ•ˆν•©λ‹ˆλ‹€:

-    <div className="flex flex-col gap-[20px] rounded-sm border-1 border-solid border-gray-200 p-[18px]">
+    <div className="flex flex-col gap-5 rounded-sm border border-solid border-gray-200 p-[18px]">
       <p className="text-h4 text-black">{title}</p>
       <div className="space-y-2">{inputComponent}</div>
     </div>
πŸ“œ Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

πŸ“₯ Commits

Reviewing files that changed from the base of the PR and between 7783c37 and 56d62ce.

πŸ“’ Files selected for processing (27)
  • src/app/(pages)/application/[id]/[type]/page.tsx (1 hunks)
  • src/app/api/application/[expo_id]/route.ts (1 hunks)
  • src/app/api/application/field/[expo_id]/route.ts (1 hunks)
  • src/app/api/application/field/standard/[expo_id]/route.ts (1 hunks)
  • src/app/api/application/pre-standard/[expo_id]/route.ts (1 hunks)
  • src/app/api/form/[expo_id]/route.ts (3 hunks)
  • src/entities/application/index.tsx (0 hunks)
  • src/entities/application/ui/CheckBoxOption/index.tsx (1 hunks)
  • src/entities/application/ui/DropDownOption/index.tsx (1 hunks)
  • src/entities/application/ui/Input/index.tsx (0 hunks)
  • src/entities/application/ui/MultipleOption/index.tsx (1 hunks)
  • src/entities/application/ui/OptionContainer/index.tsx (1 hunks)
  • src/entities/application/ui/RadioGroup/index.tsx (0 hunks)
  • src/entities/application/ui/SentenceOption/index.tsx (1 hunks)
  • src/entities/application/ui/TrainingRadioGroup/index.tsx (0 hunks)
  • src/shared/types/application/type.ts (1 hunks)
  • src/views/application/ui/application/index.tsx (1 hunks)
  • src/widgets/application/api/formApi.ts (0 hunks)
  • src/widgets/application/api/getForm.ts (1 hunks)
  • src/widgets/application/api/postApplication.ts (1 hunks)
  • src/widgets/application/model/applicationFormHandler.ts (0 hunks)
  • src/widgets/application/model/useGetForm.ts (1 hunks)
  • src/widgets/application/model/usePostApplication.ts (1 hunks)
  • src/widgets/application/types/type.ts (0 hunks)
  • src/widgets/application/ui/ApplicationLayout/index.tsx (1 hunks)
  • src/widgets/application/ui/form/ApplicationForm/standard/index.tsx (0 hunks)
  • src/widgets/application/ui/form/ApplicationForm/trainee/index.tsx (0 hunks)
πŸ’€ Files with no reviewable changes (9)
  • src/entities/application/index.tsx
  • src/widgets/application/types/type.ts
  • src/entities/application/ui/Input/index.tsx
  • src/entities/application/ui/RadioGroup/index.tsx
  • src/widgets/application/ui/form/ApplicationForm/trainee/index.tsx
  • src/widgets/application/api/formApi.ts
  • src/widgets/application/ui/form/ApplicationForm/standard/index.tsx
  • src/widgets/application/model/applicationFormHandler.ts
  • src/entities/application/ui/TrainingRadioGroup/index.tsx
πŸ”‡ Additional comments (11)
src/widgets/application/ui/ApplicationLayout/index.tsx (3)

3-11: Import λ¬Έ 확인 μ™„λ£Œ
react-hook-form, react-toastify λ“± ν•„μš”ν•œ λͺ¨λ“ˆλ“€μ΄ μ •μƒμ μœΌλ‘œ importλ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. ν˜„μž¬λ‘œμ„œλŠ” μ˜μ‘΄μ„± μΆ©λŒλ„ μ—†κ³  λ‹¨μˆœ 승인 κ°€λŠ₯ν•©λ‹ˆλ‹€.


17-17: νŒŒλΌλ―Έν„° νƒ€μž… λ³€κ²½ 확인
paramsλ₯Ό numberμ—μ„œ string으둜 λ³€κ²½ν•œ 것은 동적 λΌμš°νŒ… 처리 λ°©μ‹κ³Όμ˜ 일관성 μΈ‘λ©΄μ—μ„œ μ μ ˆν•΄ λ³΄μž…λ‹ˆλ‹€. λ‹€λ§Œ, λ‹€λ₯Έ κ³³μ—μ„œ paramsλ₯Ό 숫자둜 κΈ°λŒ€ν•˜κ³  μžˆμ§€ μ•Šμ€μ§€ 확인이 ν•„μš”ν•©λ‹ˆλ‹€.


65-108: TRAINEE 쑰건뢀 λ Œλ”λ§ 둜직 μž¬ν™•μΈ
type === 'TRAINEE' 쑰건에 따라 'μ—°μˆ˜μ› 아이디' κ΄€λ ¨ ν•„λ“œλ₯Ό λ³΄μ—¬μ£ΌλŠ” 둜직이 적절히 λ™μž‘ν•΄ λ³΄μž…λ‹ˆλ‹€. λ‹€λ§Œ, λ‹€λ₯Έ νŽ˜μ΄μ§€λ‚˜ λ‘œμ§μ—μ„œ type이 λ³€ν•  λ•Œ λ¬Έμ œκ°€ 생기지 μ•ŠλŠ”μ§€ μ—¬λΆ€λ₯Ό ν•œλ²ˆ 더 검증해 λ³΄μ‹œκΈΈ ꢌμž₯λ“œλ¦½λ‹ˆλ‹€.

src/app/(pages)/application/[id]/[type]/page.tsx (1)

4-4: νŒŒλΌλ―Έν„° νƒ€μž… λ³€κ²½ 주의
params.idκ°€ string으둜 λ³€κ²½λμœΌλ―€λ‘œ, 이 값을 μ„œλ²„λ‚˜ λ‹€λ₯Έ μ»΄ν¬λ„ŒνŠΈμ—μ„œ number둜만 μΈμ‹ν•˜λŠ” 경우 νƒ€μž… λΆˆμΌμΉ˜κ°€ λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€. κΈ°μ‘΄ 둜직과 좩돌이 μ—†λŠ”μ§€ ν™•μΈν•΄λ³΄λŠ” 것이 μ’‹κ² μŠ΅λ‹ˆλ‹€.

src/widgets/application/model/useGetForm.ts (1)

6-11: ν›Œλ₯­ν•œ κ΅¬ν˜„μž…λ‹ˆλ‹€!

useQueryλ₯Ό μ‚¬μš©ν•œ 데이터 페칭 κ΅¬ν˜„μ΄ κΉ”λ”ν•˜λ©°, 쿼리 ν‚€κ°€ μ μ ˆν•˜κ²Œ κ΅¬μ„±λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. νƒ€μž… μ•ˆμ „μ„±λ„ 잘 보μž₯λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

src/views/application/ui/application/index.tsx (1)

5-5: νƒ€μž… 변경이 적절히 μ΄λ£¨μ–΄μ‘ŒμŠ΅λ‹ˆλ‹€!

params의 νƒ€μž…μ„ numberμ—μ„œ string으둜 λ³€κ²½ν•œ 것이 μ μ ˆν•©λ‹ˆλ‹€. μ»΄ν¬λ„ŒνŠΈμ˜ ꡬ쑰도 κΉ”λ”ν•˜κ³  λͺ¨λ°”일 λ°˜μ‘ν˜•λ„ 잘 κ΅¬ν˜„λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

src/shared/types/application/type.ts (1)

17-23: κ°œμΈμ •λ³΄ ν•„λ“œμ— λŒ€ν•œ μœ νš¨μ„± 검증이 ν•„μš”ν•©λ‹ˆλ‹€.

FormattedApplicationData νƒ€μž…μ˜ phoneNumber와 같은 κ°œμΈμ •λ³΄ ν•„λ“œμ— λŒ€ν•œ ν˜•μ‹ μ œμ•½μ΄ μ—†μŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같은 μ •κ·œμ‹ νŒ¨ν„΄μ„ μ‚¬μš©ν•˜μ—¬ μ „ν™”λ²ˆν˜Έ ν˜•μ‹μ„ κ²€μ¦ν•˜λŠ” 것을 μΆ”μ²œλ“œλ¦½λ‹ˆλ‹€:

export type PhoneNumber = string & { 
  __brand: 'PhoneNumber';
};

export function validatePhoneNumber(phone: string): PhoneNumber {
  const pattern = /^01[0-9]-?[0-9]{4}-?[0-9]{4}$/;
  if (!pattern.test(phone)) {
    throw new Error('μœ νš¨ν•˜μ§€ μ•Šμ€ μ „ν™”λ²ˆν˜Έ ν˜•μ‹μž…λ‹ˆλ‹€');
  }
  return phone as PhoneNumber;
}

export type FormattedApplicationData = {
  trainingId?: string;
  name: string;
  phoneNumber: PhoneNumber;
  personalInformationStatus: boolean;
  informationJson: string;
};
src/app/api/application/[expo_id]/route.ts (2)

27-27: 응닡에 μƒνƒœ μ½”λ“œ 포함 - 쒋은 κ°œμ„ μ‚¬ν•­

HTTP μƒνƒœ μ½”λ“œλ₯Ό 응닡에 ν¬ν•¨μ‹œν‚€λŠ” 것은 ν΄λΌμ΄μ–ΈνŠΈ μΈ‘μ—μ„œ 응닡을 더 μ •ν™•ν•˜κ²Œ μ²˜λ¦¬ν•  수 있게 ν•΄μ£ΌλŠ” 쒋은 κ°œμ„ μ‚¬ν•­μž…λ‹ˆλ‹€.


Line range hint 6-8: νƒ€μž… 뢈일치 문제 ν•΄κ²° ν•„μš”

νŽ˜μ΄μ§€ μ»΄ν¬λ„ŒνŠΈμ—μ„œλŠ” expo_idκ°€ λ¬Έμžμ—΄λ‘œ μ²˜λ¦¬λ˜λŠ” 반면, API λΌμš°νŠΈμ—μ„œλŠ” 숫자둜 처리되고 μžˆμŠ΅λ‹ˆλ‹€. νƒ€μž… 일관성을 μœ„ν•΄ μˆ˜μ •μ΄ ν•„μš”ν•©λ‹ˆλ‹€.

-  { params }: { params: { expo_id: number } },
+  { params }: { params: { expo_id: string } },
src/app/api/application/field/standard/[expo_id]/route.ts (1)

Line range hint 1-35: ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„± ν•„μš”

API μ—”λ“œν¬μΈνŠΈμ— λŒ€ν•œ ν…ŒμŠ€νŠΈ μ½”λ“œκ°€ μ—†μŠ΅λ‹ˆλ‹€. λ‹€μŒκ³Ό 같은 ν…ŒμŠ€νŠΈ μΌ€μ΄μŠ€λ“€μ΄ ν•„μš”ν•©λ‹ˆλ‹€:

  1. 인증된 μš”μ²­ 처리
  2. μΈμ¦λ˜μ§€ μ•Šμ€ μš”μ²­ 처리
  3. 잘λͺ»λœ μž…λ ₯κ°’ 처리
  4. μ„œλ²„ μ—λŸ¬ 처리

ν…ŒμŠ€νŠΈ μ½”λ“œ μ˜ˆμ‹œλ₯Ό μƒμ„±ν•΄λ“œλ¦΄κΉŒμš”?

src/app/api/form/[expo_id]/route.ts (1)

23-24: 응닡 처리 방식이 κ°œμ„ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

NextResponse.json을 μ‚¬μš©ν•˜μ—¬ 응닡과 μƒνƒœ μ½”λ“œλ₯Ό ν•¨κ»˜ λ°˜ν™˜ν•˜λ„λ‘ μˆ˜μ •λœ 것이 μ’‹μŠ΅λ‹ˆλ‹€.

Comment on lines +30 to +63
const onSubmit = (data: ApplicationFormValues) => {
const formattedData: {
trainingId?: string;
name: string;
phoneNumber: string;
personalInformationStatus: boolean;
informationJson: string;
} = {
name: String(data['이름을 μž…λ ₯ν•˜μ„Έμš”'] || ''),
phoneNumber: String(data['νœ΄λŒ€ν° 번호λ₯Ό μž…λ ₯ν•˜μ„Έμš”'] || ''),
personalInformationStatus: true,
informationJson: JSON.stringify(
formList?.dynamicForm?.reduce<Record<string, string>>((acc, form) => {
const value = data[form.title as keyof ApplicationFormValues];
if (form.formType === 'CHECKBOX') {
acc[form.title] = Array.isArray(value)
? value.join(', ')
: String(value || '');
} else {
acc[form.title] = String(value || '');
}
return acc;
}, {}),
),
};

if (type === 'TRAINEE') {
formattedData.trainingId = String(
data['μ—°μˆ˜μ› 아이디λ₯Ό μž…λ ₯ν•˜μ„Έμš”'] || '',
);
}

PostApplication(formattedData);
};
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

ν•˜λ“œμ½”λ”©λœ ν•œκΈ€ ν•„λ“œλͺ… 관리 λ°©μ•ˆ
onSubmit λ‘œμ§μ—μ„œ '이름을 μž…λ ₯ν•˜μ„Έμš”', 'νœ΄λŒ€ν° 번호λ₯Ό μž…λ ₯ν•˜μ„Έμš”' λ“± ν•œκΈ€ λ¬Έμžμ—΄μ΄ 직접 μ‚¬μš©λ˜κ³  μžˆμŠ΅λ‹ˆλ‹€. ν•„λ“œλͺ…이 λ³€κ²½λ˜λ©΄ 전체 μ½”λ“œ μˆ˜μ •μ΄ ν•„μš”ν•˜λ―€λ‘œ, μƒμˆ˜λ‚˜ enum으둜 κ΄€λ¦¬ν•˜λŠ” 방식이 μœ μ§€λ³΄μˆ˜μ— μœ λ¦¬ν•©λ‹ˆλ‹€.

μ˜ˆμ‹œ μ½”λ“œλ₯Ό μ°Έκ³ ν•΄ μ£Όμ„Έμš”:

 const onSubmit = (data: ApplicationFormValues) => {
+  const FIELD_NAME_NAME = '이름을 μž…λ ₯ν•˜μ„Έμš”';
+  const FIELD_NAME_PHONE = 'νœ΄λŒ€ν° 번호λ₯Ό μž…λ ₯ν•˜μ„Έμš”';

   const formattedData = {
-    name: String(data['이름을 μž…λ ₯ν•˜μ„Έμš”'] || ''),
-    phoneNumber: String(data['νœ΄λŒ€ν° 번호λ₯Ό μž…λ ₯ν•˜μ„Έμš”'] || ''),
+    name: String(data[FIELD_NAME_NAME] || ''),
+    phoneNumber: String(data[FIELD_NAME_PHONE] || ''),
     ...
   };
   ...
 };
πŸ“ 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 onSubmit = (data: ApplicationFormValues) => {
const formattedData: {
trainingId?: string;
name: string;
phoneNumber: string;
personalInformationStatus: boolean;
informationJson: string;
} = {
name: String(data['이름을 μž…λ ₯ν•˜μ„Έμš”'] || ''),
phoneNumber: String(data['νœ΄λŒ€ν° 번호λ₯Ό μž…λ ₯ν•˜μ„Έμš”'] || ''),
personalInformationStatus: true,
informationJson: JSON.stringify(
formList?.dynamicForm?.reduce<Record<string, string>>((acc, form) => {
const value = data[form.title as keyof ApplicationFormValues];
if (form.formType === 'CHECKBOX') {
acc[form.title] = Array.isArray(value)
? value.join(', ')
: String(value || '');
} else {
acc[form.title] = String(value || '');
}
return acc;
}, {}),
),
};
if (type === 'TRAINEE') {
formattedData.trainingId = String(
data['μ—°μˆ˜μ› 아이디λ₯Ό μž…λ ₯ν•˜μ„Έμš”'] || '',
);
}
PostApplication(formattedData);
};
const onSubmit = (data: ApplicationFormValues) => {
const FIELD_NAME_NAME = '이름을 μž…λ ₯ν•˜μ„Έμš”';
const FIELD_NAME_PHONE = 'νœ΄λŒ€ν° 번호λ₯Ό μž…λ ₯ν•˜μ„Έμš”';
const formattedData: {
trainingId?: string;
name: string;
phoneNumber: string;
personalInformationStatus: boolean;
informationJson: string;
} = {
name: String(data[FIELD_NAME_NAME] || ''),
phoneNumber: String(data[FIELD_NAME_PHONE] || ''),
personalInformationStatus: true,
informationJson: JSON.stringify(
formList?.dynamicForm?.reduce<Record<string, string>>((acc, form) => {
const value = data[form.title as keyof ApplicationFormValues];
if (form.formType === 'CHECKBOX') {
acc[form.title] = Array.isArray(value)
? value.join(', ')
: String(value || '');
} else {
acc[form.title] = String(value || '');
}
return acc;
}, {}),
),
};
if (type === 'TRAINEE') {
formattedData.trainingId = String(
data['μ—°μˆ˜μ› 아이디λ₯Ό μž…λ ₯ν•˜μ„Έμš”'] || '',
);
}
PostApplication(formattedData);
};

Comment on lines +1 to +6
import axios from 'axios';

export const getForm = async (id: string, type: string) => {
const response = await axios.get(`/api/form/${id}?type=${type}`);
return response.data;
};
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

μ—λŸ¬ 처리λ₯Ό μœ„ν•œ try-catch ꡬ문 κ³ λ €
비동기 톡신 μ‹œ axios μš”μ²­μ΄ μ‹€νŒ¨ν•˜λ©΄ μ—λŸ¬λ₯Ό λ°˜ν™˜ν•  텐데, μ΄λ•Œ λ‹¨μˆœνžˆ ν•¨μˆ˜ λ°–μœΌλ‘œ μ—λŸ¬κ°€ μ „νŒŒλ  κ°€λŠ₯성이 μžˆμŠ΅λ‹ˆλ‹€. ν•„μš”ν•˜λ‹€λ©΄ 내뢀에 try-catch ꡬ문을 μΆ”κ°€ν•˜κ±°λ‚˜, axios 인터셉터λ₯Ό μ‚¬μš©ν•˜μ—¬ 곡톡 μ—λŸ¬ 처리λ₯Ό μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ˜ˆμ‹œ:

 export const getForm = async (id: string, type: string) => {
-  const response = await axios.get(`/api/form/${id}?type=${type}`);
-  return response.data;
+  try {
+    const response = await axios.get(`/api/form/${id}?type=${type}`);
+    return response.data;
+  } catch (error) {
+    // μ—λŸ¬ 처리λ₯Ό μœ„ν•œ 둜직
+    throw error;
+  }
 };
πŸ“ 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
import axios from 'axios';
export const getForm = async (id: string, type: string) => {
const response = await axios.get(`/api/form/${id}?type=${type}`);
return response.data;
};
import axios from 'axios';
export const getForm = async (id: string, type: string) => {
try {
const response = await axios.get(`/api/form/${id}?type=${type}`);
return response.data;
} catch (error) {
// μ—λŸ¬ 처리λ₯Ό μœ„ν•œ 둜직
throw error;
}
};

Comment on lines +4 to +17
export const postApplication = async (
params: string,
type: string,
data: FormattedApplicationData,
) => {
const urlMap: Record<string, string> = {
STANDARD: `/api/application/pre-standard/${params}`,
DEFAULT: `/api/application/${params}`,
};

const url = urlMap[type] || urlMap.DEFAULT;
const response = await axios.post(url, data);
return response.data;
};
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

μ—λŸ¬ μ²˜λ¦¬μ™€ URL ꡬ성을 κ°œμ„ ν•΄ μ£Όμ„Έμš”.

λ‹€μŒ 사항듀을 κ³ λ €ν•΄ μ£Όμ‹œκΈ° λ°”λžλ‹ˆλ‹€:

  1. API 호좜 μ‹€νŒ¨ μ‹œμ˜ μ—λŸ¬ μ²˜λ¦¬κ°€ λˆ„λ½λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.
  2. URL 맀핑이 ν•˜λ“œμ½”λ”©λ˜μ–΄ μžˆμ–΄ μœ μ§€λ³΄μˆ˜κ°€ μ–΄λ €μšΈ 수 μžˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 κ°œμ„ ν•˜λŠ” 것을 μ œμ•ˆλ“œλ¦½λ‹ˆλ‹€:

 export const postApplication = async (
   params: string,
   type: string,
   data: FormattedApplicationData,
 ) => {
   const urlMap: Record<string, string> = {
     STANDARD: `/api/application/pre-standard/${params}`,
     DEFAULT: `/api/application/${params}`,
   };

   const url = urlMap[type] || urlMap.DEFAULT;
-  const response = await axios.post(url, data);
-  return response.data;
+  try {
+    const response = await axios.post(url, data);
+    return response.data;
+  } catch (error) {
+    if (axios.isAxiosError(error)) {
+      throw new Error(`μ‹ μ²­μ„œ 제좜 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: ${error.message}`);
+    }
+    throw error;
+  }
 };

λ˜ν•œ URL ꡬ성을 μœ„ν•œ μƒμˆ˜λ₯Ό 별도 파일둜 λΆ„λ¦¬ν•˜λŠ” 것을 κ³ λ €ν•΄ λ³΄μ„Έμš”.

πŸ“ 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
export const postApplication = async (
params: string,
type: string,
data: FormattedApplicationData,
) => {
const urlMap: Record<string, string> = {
STANDARD: `/api/application/pre-standard/${params}`,
DEFAULT: `/api/application/${params}`,
};
const url = urlMap[type] || urlMap.DEFAULT;
const response = await axios.post(url, data);
return response.data;
};
export const postApplication = async (
params: string,
type: string,
data: FormattedApplicationData,
) => {
const urlMap: Record<string, string> = {
STANDARD: `/api/application/pre-standard/${params}`,
DEFAULT: `/api/application/${params}`,
};
const url = urlMap[type] || urlMap.DEFAULT;
try {
const response = await axios.post(url, data);
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(`μ‹ μ²­μ„œ 제좜 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: ${error.message}`);
}
throw error;
}
};

Comment on lines +16 to +27
const DropDownOption = ({ options, register, name }: Props) => {
return (
<select className="rounded border px-2 py-1" {...register(name)}>
<option value="">μ„ νƒν•˜μ„Έμš”</option>
{options.map((option) => (
<option key={option.value} value={option.label}>
{option.label}
</option>
))}
</select>
);
};
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

μ ‘κ·Όμ„±κ³Ό μœ νš¨μ„± 검사λ₯Ό κ°œμ„ ν•˜μ„Έμš”

ν˜„μž¬ κ΅¬ν˜„μ—λŠ” λ‹€μŒκ³Ό 같은 κ°œμ„ μ΄ ν•„μš”ν•©λ‹ˆλ‹€:

  1. ν•„μˆ˜ 선택 μ—¬λΆ€ 검증이 μ—†μŠ΅λ‹ˆλ‹€
  2. μ ‘κ·Όμ„± 속성이 λˆ„λ½λ˜μ—ˆμŠ΅λ‹ˆλ‹€

λ‹€μŒκ³Ό 같이 κ°œμ„ ν•΄λ³΄μ„Έμš”:

const DropDownOption = ({ options, register, name }: Props) => {
  return (
    <select 
-     className="rounded border px-2 py-1" 
+     className="rounded border px-2 py-1"
+     aria-label={name}
+     required
      {...register(name, { required: "이 ν•­λͺ©μ€ ν•„μˆ˜μž…λ‹ˆλ‹€" })}
    >
      <option value="">μ„ νƒν•˜μ„Έμš”</option>
      {options.map((option) => (
        <option key={option.value} value={option.label}>
          {option.label}
        </option>
      ))}
    </select>
  );
};
πŸ“ 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 DropDownOption = ({ options, register, name }: Props) => {
return (
<select className="rounded border px-2 py-1" {...register(name)}>
<option value="">μ„ νƒν•˜μ„Έμš”</option>
{options.map((option) => (
<option key={option.value} value={option.label}>
{option.label}
</option>
))}
</select>
);
};
const DropDownOption = ({ options, register, name }: Props) => {
return (
<select
className="rounded border px-2 py-1"
aria-label={name}
required
{...register(name, { required: "이 ν•­λͺ©μ€ ν•„μˆ˜μž…λ‹ˆλ‹€" })}
>
<option value="">μ„ νƒν•˜μ„Έμš”</option>
{options.map((option) => (
<option key={option.value} value={option.label}>
{option.label}
</option>
))}
</select>
);
};

Comment on lines +15 to +31
const CheckBoxOption = ({ options, register, name }: Props) => {
return (
<div>
{options.map((option) => (
<div key={option.value} className="mb-2 flex items-center">
<input
type="checkbox"
value={option.label}
className="h-4 w-4 accent-blue-500"
{...register(name)}
/>
<label className="ml-2 text-sm">{option.label}</label>
</div>
))}
</div>
);
};
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

μ²΄ν¬λ°•μŠ€ 그룹의 접근성을 κ°œμ„ ν•˜μ„Έμš”

μ²΄ν¬λ°•μŠ€ 그룹에 λŒ€ν•œ μ μ ˆν•œ HTML ꡬ쑰와 ARIA 속성이 λˆ„λ½λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 κ°œμ„ ν•΄λ³΄μ„Έμš”:

const CheckBoxOption = ({ options, register, name }: Props) => {
  return (
-   <div>
+   <fieldset>
+     <legend className="sr-only">{name}</legend>
+     <div role="group" aria-labelledby={`${name}-group`}>
      {options.map((option) => (
        <div key={option.value} className="mb-2 flex items-center">
          <input
            type="checkbox"
            value={option.label}
            className="h-4 w-4 accent-blue-500"
+           id={`${name}-${option.value}`}
            {...register(name)}
          />
-         <label className="ml-2 text-sm">{option.label}</label>
+         <label htmlFor={`${name}-${option.value}`} className="ml-2 text-sm">
+           {option.label}
+         </label>
        </div>
      ))}
+     </div>
+   </fieldset>
-   </div>
  );
};
πŸ“ 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 CheckBoxOption = ({ options, register, name }: Props) => {
return (
<div>
{options.map((option) => (
<div key={option.value} className="mb-2 flex items-center">
<input
type="checkbox"
value={option.label}
className="h-4 w-4 accent-blue-500"
{...register(name)}
/>
<label className="ml-2 text-sm">{option.label}</label>
</div>
))}
</div>
);
};
const CheckBoxOption = ({ options, register, name }: Props) => {
return (
<fieldset>
<legend className="sr-only">{name}</legend>
<div role="group" aria-labelledby={`${name}-group`}>
{options.map((option) => (
<div key={option.value} className="mb-2 flex items-center">
<input
type="checkbox"
value={option.label}
className="h-4 w-4 accent-blue-500"
{...register(name)}
id={`${name}-${option.value}`}
/>
<label htmlFor={`${name}-${option.value}`} className="ml-2 text-sm">
{option.label}
</label>
</div>
))}
</div>
</fieldset>
);
};

Comment on lines +15 to +31
const MultipleOption = ({ options, register, name }: Props) => {
return (
<div>
{options.map((option) => (
<div key={option.value} className="mb-2 flex items-center">
<input
type="radio"
value={option.label}
className="h-4 w-4 accent-blue-500"
{...register(name)}
/>
<label className="ml-2 text-sm">{option.label}</label>
</div>
))}
</div>
);
};
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

λΌλ””μ˜€ λ²„νŠΌ 그룹의 접근성을 κ°œμ„ ν•˜μ„Έμš”

λΌλ””μ˜€ λ²„νŠΌ 그룹에 λŒ€ν•œ μ μ ˆν•œ HTML ꡬ쑰와 ARIA 속성이 λˆ„λ½λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 κ°œμ„ ν•΄λ³΄μ„Έμš”:

const MultipleOption = ({ options, register, name }: Props) => {
  return (
-   <div>
+   <fieldset>
+     <legend className="sr-only">{name}</legend>
+     <div role="radiogroup" aria-labelledby={`${name}-group`}>
      {options.map((option) => (
        <div key={option.value} className="mb-2 flex items-center">
          <input
            type="radio"
            value={option.label}
            className="h-4 w-4 accent-blue-500"
+           id={`${name}-${option.value}`}
            {...register(name)}
          />
-         <label className="ml-2 text-sm">{option.label}</label>
+         <label htmlFor={`${name}-${option.value}`} className="ml-2 text-sm">
+           {option.label}
+         </label>
        </div>
      ))}
+     </div>
+   </fieldset>
-   </div>
  );
};

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +39 to +49
<textarea
ref={(el) => {
textareaRef.current = el;
ref(el);
}}
className="w-full resize-none overflow-hidden bg-transparent text-body4 text-black placeholder-transparent"
rows={row}
maxLength={maxLength}
onChange={handleChange}
{...rest}
/>
Copy link

Choose a reason for hiding this comment

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

πŸ› οΈ Refactor suggestion

ν…μŠ€νŠΈ μ˜μ—­ μ ‘κ·Όμ„± κ°œμ„  ν•„μš”

ν…μŠ€νŠΈ μ˜μ—­μ˜ 접근성을 κ°œμ„ ν•˜κΈ° μœ„ν•œ 속성듀이 λˆ„λ½λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•˜λŠ” 것을 μ œμ•ˆν•©λ‹ˆλ‹€:

 <textarea
   ref={(el) => {
     textareaRef.current = el;
     ref(el);
   }}
   className="w-full resize-none overflow-hidden bg-transparent text-body4 text-black placeholder-transparent"
   rows={row}
   maxLength={maxLength}
   onChange={handleChange}
+  aria-label={name}
+  aria-required={required}
   {...rest}
 />
πŸ“ 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
<textarea
ref={(el) => {
textareaRef.current = el;
ref(el);
}}
className="w-full resize-none overflow-hidden bg-transparent text-body4 text-black placeholder-transparent"
rows={row}
maxLength={maxLength}
onChange={handleChange}
{...rest}
/>
<textarea
ref={(el) => {
textareaRef.current = el;
ref(el);
}}
className="w-full resize-none overflow-hidden bg-transparent text-body4 text-black placeholder-transparent"
rows={row}
maxLength={maxLength}
onChange={handleChange}
aria-label={name}
aria-required={required}
{...rest}
/>

Comment on lines +19 to +24
const options = jsonData
? Object.entries(JSON.parse(jsonData)).map(([key, value]) => ({
value: key,
label: value as string,
}))
: [];
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

JSON νŒŒμ‹± 둜직 μ•ˆμ „μ„± κ°œμ„  ν•„μš”

JSON νŒŒμ‹± κ³Όμ •μ—μ„œ λ°œμƒν•  수 μžˆλŠ” 였λ₯˜μ— λŒ€ν•œ μ²˜λ¦¬κ°€ λˆ„λ½λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.

λ‹€μŒκ³Ό 같이 μˆ˜μ •ν•˜λŠ” 것을 μ œμ•ˆν•©λ‹ˆλ‹€:

-  const options = jsonData
-    ? Object.entries(JSON.parse(jsonData)).map(([key, value]) => ({
-        value: key,
-        label: value as string,
-      }))
-    : [];
+  const options = useMemo(() => {
+    if (!jsonData) return [];
+    try {
+      const parsed = JSON.parse(jsonData);
+      return Object.entries(parsed).map(([key, value]) => ({
+        value: key,
+        label: typeof value === 'string' ? value : String(value),
+      }));
+    } catch (error) {
+      console.error('JSON νŒŒμ‹± 였λ₯˜:', error);
+      return [];
+    }
+  }, [jsonData]);
πŸ“ 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 options = jsonData
? Object.entries(JSON.parse(jsonData)).map(([key, value]) => ({
value: key,
label: value as string,
}))
: [];
const options = useMemo(() => {
if (!jsonData) return [];
try {
const parsed = JSON.parse(jsonData);
return Object.entries(parsed).map(([key, value]) => ({
value: key,
label: typeof value === 'string' ? value : String(value),
}));
} catch (error) {
console.error('JSON νŒŒμ‹± 였λ₯˜:', error);
return [];
}
}, [jsonData]);

@Ethen1264 Ethen1264 merged commit b3a4c4d into develop Feb 2, 2025
2 checks passed
@Ethen1264 Ethen1264 deleted the feature/application branch February 3, 2025 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
✨ Feature μ‹ κ·œ κΈ°λŠ₯ πŸ“¬ Type: API μ„œλ²„ API 톡신 🎨 Type: Publishing λ””μžμΈ κ΅¬ν˜„
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants