Skip to content

Conversation

@Dobbymin
Copy link
Owner

📝 요약 (Summary)

사용자 프로필 설정 페이지를 개선하고, 닉네임 변경 기능을 구현했습니다. 더 나은 사용자 경험을 위해 스켈레톤 로딩 상태를 추가하고, 세션 관리 최적화를 통해 페이지 새로고침 없이 실시간으로 변경사항이 반영되도록 개선했습니다.

✅ 주요 변경 사항 (Key Changes)

  • 설정 페이지 UI/UX 개선 (프로필 이미지 크기 확대, 레이아웃 재구성)
  • 닉네임 변경 기능 전체 구현 (API 라우트, 클라이언트 API, 커스텀 훅, UI 컴포넌트)
  • 스켈레톤 로딩 상태 추가로 데이터 로딩 중 사용자 경험 개선
  • Supabase 세션 관리 최적화 (페이지 새로고침 없이 UI 갱신)
  • 디자인 시스템에 맞춘 스타일 통일 및 반응형 레이아웃 적용

💻 상세 구현 내용 (Implementation Details)

1. 닉네임 변경 API 구현

Backend API Route

주요 기능

  • HTTP Method: PATCH
  • 인증 검증: supabase.auth.getUser()로 로그인 상태 확인
  • 입력 검증:
    • 닉네임 필수 입력
    • 문자열 타입 체크
    • 길이 제한 (2-20자)
  • Supabase Auth의 user_metadata 업데이트
  • 표준화된 APIResponse<T> 형식으로 응답
// 사용자 메타데이터 업데이트
const { data, error } = await supabase.auth.updateUser({
  data: {
    nickname,
  },
});

return NextResponse.json(
  {
    status: "success",
    data: { nickname: data.user.user_metadata.nickname }
  },
  { status: 200 }
);

Client API Wrapper

export const changeNicknameAPI = async ({
  nickname
}: ChangeNicknameParams): Promise<ChangeNicknameResponse> => {
  const response = await fetch("/api/user/nickname", {
    method: "PATCH",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ nickname }),
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.error || "닉네임 변경에 실패했습니다.");
  }

  return response.json();
};

useEffect 동기화

// userNickname이 변경되면 inputValue 동기화
useEffect(() => {
  setInputValue(userNickname);
}, [userNickname]);

2. 세션 관리 최적화

문제 상황

닉네임 변경 후 Supabase에는 반영되지만 UI가 업데이트되지 않는 문제 발생

원인 분석

  • Supabase 클라이언트의 세션 캐시가 갱신되지 않음
  • window.location.reload()는 사용자 경험이 좋지 않음

해결 방법

AuthProvider 활용

// AuthProvider는 onAuthStateChange를 구독하여 세션 변경 감지
supabase.auth.onAuthStateChange((event, session) => {
  setSession(session); // Zustand 스토어 업데이트
  if (session?.access_token) {
    setAccessToken(session.access_token);
  }
});

refreshSession() 패턴

// 1. API로 user_metadata 업데이트
await supabase.auth.updateUser({ data: { nickname } });

// 2. 세션 강제 갱신
await supabase.auth.refreshSession();

// 3. AuthProvider가 변경 감지 → Zustand 업데이트 → UI 자동 갱신

결과

  • 페이지 새로고침 없이 즉시 UI 반영
  • 부드러운 사용자 경험
  • 다른 컴포넌트에서 세션 데이터 사용 시에도 자동 업데이트

🚀 트러블 슈팅 (Trouble Shooting)

1. 수정 버튼 클릭 시 Input 창이 비어있는 문제

문제

  • useUserInfo에서 userNicknameundefined 반환 가능하게 변경
  • useChangeNickname의 초기 inputValueundefined로 설정됨
  • Edit 모드로 전환해도 Input 필드가 비어있음

해결

// useUserInfo.ts
const userNickname = session?.user?.user_metadata?.nickname || ""; // 빈 문자열로 폴백

// useChangeNickname.ts
useEffect(() => {
  setInputValue(userNickname); // userNickname 변경 시 동기화
}, [userNickname]);

2. 닉네임 변경 후 UI가 업데이트되지 않는 문제

문제

  • API 호출 성공, Supabase DB에 반영됨
  • 하지만 UI에는 이전 닉네임이 계속 표시됨

refreshSession() 사용

await supabase.auth.refreshSession();

-> 페이지 새로고침 없이 부드럽게 업데이트

분석

  • Supabase 클라이언트는 세션을 메모리/localStorage에 캐시
  • updateUser()만으로는 로컬 세션 캐시가 갱신되지 않음
  • refreshSession()은 서버에서 최신 세션 데이터를 가져옴
  • AuthProvider의 onAuthStateChange가 이를 감지하여 Zustand 스토어 업데이트
  • useUserInfo가 Zustand를 구독하므로 자동 리렌더링

⚠️ 알려진 이슈 및 참고 사항 (Known Issues & Notes)

구현되지 않은 기능

  1. 프로필 이미지 업로드

    • UI는 구현되어 있으나 백엔드 API가 없음
    • 향후 Supabase Storage와 연동 필요
    • 구현 예정: POST /api/user/avatar 엔드포인트
  2. 닉네임 취소 버튼

    • 현재 Edit 모드에서 취소 기능 없음
    • "저장" 버튼만 있어 실수로 편집 시 되돌리기 어려움
    • 개선 방안: "취소" 버튼 추가로 원래 값으로 복구

기술적 개선 사항

  1. 닉네임 중복 검사

    • 현재는 클라이언트 측 기본 검증만 수행
    • 서버에서 닉네임 중복 여부 확인 로직 추가 권장
  2. 낙관적 업데이트 (Optimistic Update)

    • 현재는 API 응답 후 UI 갱신
    • TanStack Query의 낙관적 업데이트 패턴 적용 시 더 빠른 피드백 가능
  3. 에러 경계 (Error Boundary)

    • 예상치 못한 에러 발생 시 전체 페이지 크래시 방지
    • 설정 페이지에 Error Boundary 추가 고려

성능 고려사항

  • refreshSession() 호출은 네트워크 요청을 수반하므로 필요한 경우에만 사용
  • 현재는 user_metadata 변경 시에만 호출되므로 적절함

디자인 시스템

  • 모든 컴포넌트는 프로젝트의 디자인 토큰 사용 (bg-surface-dark, text-text-primary 등)
  • 반응형 디자인 적용: 데스크톱 우선, 모바일 최적화

📸 스크린샷 (Screenshots)

스크린샷 2025-11-22 오후 5 20 12

#️⃣ 관련 이슈 (Related Issues)

@Dobbymin Dobbymin self-assigned this Nov 22, 2025
@Dobbymin Dobbymin added the ✨ Feature 새로운 기능 추가 및 구현하는 경우 label Nov 22, 2025
@Dobbymin Dobbymin linked an issue Nov 22, 2025 that may be closed by this pull request
@vercel
Copy link

vercel bot commented Nov 22, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
dobbit Ready Ready Preview Comment Nov 22, 2025 8:20am

@Dobbymin Dobbymin requested a review from Copilot November 22, 2025 08:21
@gemini-code-assist
Copy link

Summary of Changes

Hello @Dobbymin, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 Pull Request는 사용자 프로필 설정 페이지를 전면적으로 개선하여, 사용자가 자신의 닉네임을 손쉽게 변경할 수 있도록 하는 데 중점을 둡니다. UI/UX 개선과 함께, 데이터 변경 사항이 페이지 새로고침 없이 즉시 반영되도록 Supabase 세션 관리 로직을 최적화하여 부드러운 사용자 경험을 제공합니다. 또한, 데이터 로딩 시 스켈레톤 UI를 도입하여 대기 시간을 시각적으로 완화합니다.

Highlights

  • 사용자 프로필 설정 페이지 구현: 사용자 프로필 설정 페이지의 UI/UX를 개선하고, 프로필 이미지 크기 확대 및 레이아웃 재구성을 통해 사용자 경험을 향상시켰습니다.
  • 닉네임 변경 기능: 닉네임 변경을 위한 백엔드 API 라우트, 클라이언트 API, 커스텀 훅, UI 컴포넌트 등 전체 기능을 구현했습니다. 닉네임은 2자 이상 20자 이하로 제한됩니다.
  • 실시간 UI 업데이트 및 세션 관리 최적화: Supabase 세션 관리 최적화를 통해 닉네임 변경 후 페이지 새로고침 없이 UI가 실시간으로 업데이트되도록 개선했습니다. supabase.auth.refreshSession()을 활용하여 세션 캐시 갱신 문제를 해결했습니다.
  • 스켈레톤 로딩 상태 추가: 데이터 로딩 중 사용자 경험을 개선하기 위해 스켈레톤 로딩 상태를 추가하여 시각적인 피드백을 제공합니다.
  • 디자인 시스템 및 반응형 레이아웃 적용: 모든 컴포넌트에 디자인 시스템의 토큰을 적용하고, 데스크톱 우선 및 모바일 최적화 반응형 레이아웃을 구현하여 일관된 사용자 경험을 제공합니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

이 PR은 사용자 정보 수정 페이지를 구현하고 닉네임 변경 기능을 추가하는 중요한 개선 사항을 담고 있습니다. 전반적으로 코드가 잘 구조화되어 있고, 스켈레톤 UI와 세션 새로고침 같은 UX 개선 노력이 돋보입니다. 몇 가지 추가적인 개선을 위해 유효성 검사 강화, 데이터 포맷팅 함수의 버그 수정, 그리고 닉네임 중복 검사와 같은 중요한 기능에 대한 제안을 포함한 리뷰 의견을 남겼습니다. 이러한 부분들을 반영하면 코드의 안정성과 사용자 경험이 더욱 향상될 것입니다. 수고하셨습니다!

Comment on lines +30 to +35
// 사용자 메타데이터 업데이트
const { data, error } = await supabase.auth.updateUser({
data: {
nickname,
},
});

Choose a reason for hiding this comment

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

high

PR 설명에 언급된 대로 닉네임 중복 검사 로직을 추가하는 것이 중요합니다. 중복 닉네임을 허용하면 사용자 혼란을 야기하고 다른 사용자를 사칭하는 데 사용될 수 있습니다. Supabase DB에서 사용자를 조회하여 user_metadata의 닉네임이 고유한지 확인하는 로직을 updateUser 호출 전에 추가하는 것을 권장합니다. 이는 데이터 무결성과 보안을 위해 높은 우선순위로 처리해야 할 사항입니다.

Comment on lines +1 to +13
export const formattedDate = (dateString: string): string => {
const date = dateString ? new Date(dateString) : null;

const year = date ? date.getFullYear() : "";
const month = date ? String(date.getMonth() + 1).padStart(2, "0") : "";
const day = date ? String(date.getDate()).padStart(2, "0") : "";

const Hour = date ? String(date.getHours()).padStart(2, "0") : "";
const Minutes = date ? String(date.getMinutes()).padStart(2, "0") : "";
const Seconds = date ? String(date.getSeconds()).padStart(2, "0") : "";

return `${year}년 ${month}월 ${day}일 ${Hour}:${Minutes}:${Seconds}`;
};

Choose a reason for hiding this comment

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

high

formattedDate 함수에 몇 가지 개선 사항을 제안합니다.

  1. 버그 수정: dateString이 비어있을 때 "년 월 일 : :" 와 같은 잘못된 형식의 문자열이 반환됩니다. 유효하지 않은 입력에 대해서는 "-" 같은 대체 텍스트를 반환하도록 수정해야 합니다.
  2. 유효성 검사 강화: new Date(dateString)Invalid Date를 반환하는 경우에 대한 처리를 추가하여 안정성을 높일 수 있습니다.
  3. 네이밍 컨벤션: 변수명 Hour, Minutes, Seconds를 JavaScript/TypeScript 표준인 camelCase (hour, minutes, seconds)로 변경하여 코드 스타일의 일관성을 맞추는 것이 좋습니다.
Suggested change
export const formattedDate = (dateString: string): string => {
const date = dateString ? new Date(dateString) : null;
const year = date ? date.getFullYear() : "";
const month = date ? String(date.getMonth() + 1).padStart(2, "0") : "";
const day = date ? String(date.getDate()).padStart(2, "0") : "";
const Hour = date ? String(date.getHours()).padStart(2, "0") : "";
const Minutes = date ? String(date.getMinutes()).padStart(2, "0") : "";
const Seconds = date ? String(date.getSeconds()).padStart(2, "0") : "";
return `${year}${month}${day}${Hour}:${Minutes}:${Seconds}`;
};
export const formattedDate = (dateString: string): string => {
if (!dateString) {
return "-";
}
const date = new Date(dateString);
if (isNaN(date.getTime())) {
return "-";
}
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const hour = String(date.getHours()).padStart(2, "0");
const minutes = String(date.getMinutes()).padStart(2, "0");
const seconds = String(date.getSeconds()).padStart(2, "0");
return `${year}${month}${day}${hour}:${minutes}:${seconds}`;
};

Comment on lines +16 to +28
const { nickname } = body;

// 닉네임 검증
if (!nickname || typeof nickname !== "string") {
return NextResponse.json({ status: "error", data: null, error: "닉네임을 입력해주세요." }, { status: 400 });
}

if (nickname.length < 2 || nickname.length > 20) {
return NextResponse.json(
{ status: "error", data: null, error: "닉네임은 2자 이상 20자 이하로 입력해주세요." },
{ status: 400 },
);
}

Choose a reason for hiding this comment

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

medium

닉네임에 대한 유효성 검사를 강화하는 것을 제안합니다. 현재 코드는 앞뒤 공백이 포함된 닉네임을 허용할 수 있습니다. trim()을 사용하여 입력값의 양 끝에 있는 공백을 제거한 후 유효성을 검사하는 것이 좋습니다. 이렇게 하면 " test "와 같은 닉네임이 데이터베이스에 저장되는 것을 방지할 수 있습니다.

Suggested change
const { nickname } = body;
// 닉네임 검증
if (!nickname || typeof nickname !== "string") {
return NextResponse.json({ status: "error", data: null, error: "닉네임을 입력해주세요." }, { status: 400 });
}
if (nickname.length < 2 || nickname.length > 20) {
return NextResponse.json(
{ status: "error", data: null, error: "닉네임은 2자 이상 20자 이하로 입력해주세요." },
{ status: 400 },
);
}
const { nickname: rawNickname } = body;
const nickname = typeof rawNickname === "string" ? rawNickname.trim() : rawNickname;
// 닉네임 검증
if (!nickname || typeof nickname !== "string") {
return NextResponse.json({ status: "error", data: null, error: "닉네임을 입력해주세요." }, { status: 400 });
}
if (nickname.length < 2 || nickname.length > 20) {
return NextResponse.json(
{ status: "error", data: null, error: "닉네임은 2자 이상 20자 이하로 입력해주세요." },
{ status: 400 },
);
}

};

const { mutate: changeNickname, isPending } = useMutation({
mutationFn: () => changeNicknameAPI({ nickname: inputValue }),

Choose a reason for hiding this comment

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

medium

API로 닉네임을 보내기 전에 inputValue의 양쪽 공백을 제거하는 것이 좋습니다. 이렇게 하면 불필요한 공백이 데이터베이스에 저장되는 것을 방지하고, onClickEditButtoninputValue.trim() === userNickname 비교 로직과 일관성을 유지할 수 있습니다.

Suggested change
mutationFn: () => changeNicknameAPI({ nickname: inputValue }),
mutationFn: () => changeNicknameAPI({ nickname: inputValue.trim() }),

Comment on lines +43 to +53
const onClickEditButton = () => {
if (fieldState === "edit") {
if (inputValue.trim() === userNickname) {
setFieldState("view");
return;
}
changeNickname();
} else {
setFieldState("edit");
}
};

Choose a reason for hiding this comment

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

medium

사용자 경험을 개선하기 위해 닉네임 변경 API를 호출하기 전에 클라이언트 측에서 유효성 검사를 추가하는 것이 좋습니다. 현재는 서버에서만 유효성 검사를 수행하므로, 유효하지 않은 닉네임(예: 1글자)을 입력하고 저장 버튼을 누르면 API 호출 후에야 에러 메시지가 표시됩니다. onClickEditButton 함수에 닉네임 길이를 확인하는 로직을 추가하면 사용자에게 즉각적인 피드백을 줄 수 있습니다.

  const onClickEditButton = () => {
    if (fieldState === "edit") {
      const trimmedNickname = inputValue.trim();
      if (trimmedNickname === userNickname) {
        setFieldState("view");
        return;
      }

      if (trimmedNickname.length < 2 || trimmedNickname.length > 20) {
        toast.error("닉네임은 2자 이상 20자 이하로 입력해주세요.");
        return;
      }

      changeNickname();
    } else {
      setFieldState("edit");
    }
  };

@Dobbymin Dobbymin merged commit fdbc329 into main Nov 22, 2025
8 checks passed
@Dobbymin Dobbymin deleted the feat#10-setting branch November 22, 2025 08:25
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a comprehensive user profile settings page with nickname editing functionality. It introduces a new settings UI with profile image display, user information fields (email, nickname, last login), and nickname change capability. The implementation includes API routes, client API wrappers, custom hooks, skeleton loading states, and session management optimization to ensure real-time UI updates without page refreshes.

  • Renamed route path from /settings (plural) to /setting (singular) with corresponding constant updates
  • Implemented complete nickname change feature with validation, API endpoint, and optimistic UI updates
  • Added skeleton loading components to improve perceived performance during data fetching
  • Enhanced Toaster configuration with rich colors and centered positioning

Reviewed changes

Copilot reviewed 25 out of 27 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
src/app/api/user/nickname/route.ts New API endpoint for PATCH requests to update user nickname with validation (2-20 characters)
src/app/setting/page.tsx Complete settings page implementation with ProfileSection and UserInfoSection components
src/features/setting/ New feature module containing UI components, hooks, APIs, and utilities for settings functionality
src/shared/components/ui/skeleton.tsx New reusable Skeleton component for loading states
src/shared/constants/router-path.ts Updated route constant from SETTINGS to SETTING (plural to singular)
src/shared/hooks/useSelectChange.ts Updated to use renamed SETTING route constant
src/shared/components/features/header/Header.tsx Updated navigation link to use SETTING path and label
src/shared/provider/AppProvider.tsx Enhanced Toaster with richColors and top-center positioning
.gitignore Added GitHub Copilot instructions file to ignore list

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.


const onClickEditButton = () => {
if (fieldState === "edit") {
if (inputValue.trim() === userNickname) {
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

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

Inconsistent trimming logic. The comparison trims inputValue but compares it to untrimmed userNickname. If the original nickname has leading/trailing spaces, this comparison could behave unexpectedly. Consider trimming both values or ensuring the comparison matches the actual validation logic used in the API.

Suggested change
if (inputValue.trim() === userNickname) {
if (inputValue.trim() === userNickname.trim()) {

Copilot uses AI. Check for mistakes.

return (
<div className='flex items-center gap-4'>
<Label className='text-text-primary w-20'>마지막 접속일</Label>
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

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

[nitpick] Inconsistent label widths across form fields. The email and nickname fields use w-16 while the last login field uses w-20. Consider using consistent widths across all labels (e.g., w-20 for all) to maintain visual alignment.

Suggested change
<Label className='text-text-primary w-20'>마지막 접속일</Label>
<Label className='text-text-primary w-16'>마지막 접속일</Label>

Copilot uses AI. Check for mistakes.
<Button
size='icon'
variant='secondary'
className='hover:bg-surface-light absolute right-0 bottom-0 size-10 rounded-full shadow-lg'
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

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

Missing accessibility attributes for profile picture upload button. The camera button should include an aria-label to describe its purpose for screen readers (e.g., aria-label="프로필 사진 업로드" or aria-label="Upload profile picture").

Suggested change
className='hover:bg-surface-light absolute right-0 bottom-0 size-10 rounded-full shadow-lg'
className='hover:bg-surface-light absolute right-0 bottom-0 size-10 rounded-full shadow-lg'
aria-label="프로필 사진 업로드"

Copilot uses AI. Check for mistakes.
Comment on lines +21 to +22
const error = await response.json();
throw new Error(error.error || "닉네임 변경에 실패했습니다.");
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

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

Missing error handling for JSON parsing. If the error response is not valid JSON, await response.json() on line 21 will throw an exception. Consider wrapping this in a try-catch block or handling cases where the response body is not JSON (e.g., network errors, server errors without JSON body).

Suggested change
const error = await response.json();
throw new Error(error.error || "닉네임 변경에 실패했습니다.");
let errorMessage = "닉네임 변경에 실패했습니다.";
try {
const error = await response.json();
if (error && error.error) {
errorMessage = error.error;
}
} catch (e) {
// response body is not valid JSON, keep default errorMessage
}
throw new Error(errorMessage);

Copilot uses AI. Check for mistakes.
/>
) : (
<div className='flex h-10 min-w-70 items-center rounded-md bg-background px-3 py-2'>
{!isLoaded ? <Skeleton className='h-5 w-32' /> : <p className='text-text-muted text-sm'>{userNickname}</p>}
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

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

[nitpick] Hardcoded skeleton width may not match actual content width. The skeleton uses w-32 but the actual email/nickname length can vary significantly. Consider making the skeleton width match the expected content width or use a more flexible width (e.g., w-full or a percentage-based width) for better visual fidelity during loading.

Suggested change
{!isLoaded ? <Skeleton className='h-5 w-32' /> : <p className='text-text-muted text-sm'>{userNickname}</p>}
{!isLoaded ? <Skeleton className='h-5 w-full' /> : <p className='text-text-muted text-sm'>{userNickname}</p>}

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +59

return {
fieldState,
inputValue,
onChangeNickname,
onClickEditButton,
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

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

[nitpick] Missing cancel functionality when in edit mode. When a user clicks the edit button and enters edit mode but wants to discard changes, there's no way to cancel and revert to the original value without saving. Consider adding a separate "취소" (Cancel) button or allowing the edit button to cancel when the value hasn't changed (the current check at line 45 only works if the value matches the original).

Suggested change
return {
fieldState,
inputValue,
onChangeNickname,
onClickEditButton,
const onCancelEdit = () => {
setInputValue(userNickname);
setFieldState("view");
};
return {
fieldState,
inputValue,
onChangeNickname,
onClickEditButton,
onCancelEdit,

Copilot uses AI. Check for mistakes.
Comment on lines +43 to +53
const onClickEditButton = () => {
if (fieldState === "edit") {
if (inputValue.trim() === userNickname) {
setFieldState("view");
return;
}
changeNickname();
} else {
setFieldState("edit");
}
};
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

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

[nitpick] Missing client-side validation before API call. The nickname validation (length check, trim, etc.) is only done on the server side. Consider adding client-side validation in the onClickEditButton function to provide immediate feedback to users before making the API request. For example, check if inputValue.trim().length < 2 or > 20 and show an error toast.

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +25
<Label className='text-md text-text-primary w-16'>닉네임</Label>
<div className='flex items-center gap-4'>
{fieldState === "edit" ? (
<Input
className='min-w-70'
value={inputValue}
onChange={onChangeNickname}
disabled={isPending}
placeholder='닉네임을 입력하세요'
/>
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

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

Missing proper label association for the nickname input field. The Label component on line 16 is not associated with the Input component on line 19 using the htmlFor attribute. Add an id prop to the Input and a corresponding htmlFor prop to the Label to properly associate them for accessibility (e.g., <Label htmlFor="nickname" ...> and <Input id="nickname" ...>).

Suggested change
<Label className='text-md text-text-primary w-16'>닉네임</Label>
<div className='flex items-center gap-4'>
{fieldState === "edit" ? (
<Input
className='min-w-70'
value={inputValue}
onChange={onChangeNickname}
disabled={isPending}
placeholder='닉네임을 입력하세요'
/>
<Label htmlFor="nickname" className='text-md text-text-primary w-16'>닉네임</Label>
<div className='flex items-center gap-4'>
{fieldState === "edit" ? (
<Input
id="nickname"
className='min-w-70'
value={inputValue}
onChange={onChangeNickname}
disabled={isPending}
placeholder='닉네임을 입력하세요'

Copilot uses AI. Check for mistakes.
Comment on lines +4 to +11
const year = date ? date.getFullYear() : "";
const month = date ? String(date.getMonth() + 1).padStart(2, "0") : "";
const day = date ? String(date.getDate()).padStart(2, "0") : "";

const Hour = date ? String(date.getHours()).padStart(2, "0") : "";
const Minutes = date ? String(date.getMinutes()).padStart(2, "0") : "";
const Seconds = date ? String(date.getSeconds()).padStart(2, "0") : "";

Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

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

[nitpick] Potential issue with formatted date output when dateString is empty. When an empty string is passed, the function returns "년 월 일 ::" which may not be user-friendly. Consider returning a more meaningful message like "정보 없음" or "N/A" when the date is invalid or empty.

Suggested change
const year = date ? date.getFullYear() : "";
const month = date ? String(date.getMonth() + 1).padStart(2, "0") : "";
const day = date ? String(date.getDate()).padStart(2, "0") : "";
const Hour = date ? String(date.getHours()).padStart(2, "0") : "";
const Minutes = date ? String(date.getMinutes()).padStart(2, "0") : "";
const Seconds = date ? String(date.getSeconds()).padStart(2, "0") : "";
// Check for empty, null, or invalid date
if (!dateString || !date || isNaN(date.getTime())) {
return "정보 없음";
}
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
const Hour = String(date.getHours()).padStart(2, "0");
const Minutes = String(date.getMinutes()).padStart(2, "0");
const Seconds = String(date.getSeconds()).padStart(2, "0");

Copilot uses AI. Check for mistakes.
Comment on lines +18 to +28
// 닉네임 검증
if (!nickname || typeof nickname !== "string") {
return NextResponse.json({ status: "error", data: null, error: "닉네임을 입력해주세요." }, { status: 400 });
}

if (nickname.length < 2 || nickname.length > 20) {
return NextResponse.json(
{ status: "error", data: null, error: "닉네임은 2자 이상 20자 이하로 입력해주세요." },
{ status: 400 },
);
}
Copy link

Copilot AI Nov 22, 2025

Choose a reason for hiding this comment

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

Consider adding input sanitization for the nickname. While length validation is present, consider trimming whitespace before validation and storage to prevent nicknames with only spaces or leading/trailing whitespace. Apply .trim() to the nickname before the validation checks on line 19.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 새로운 기능 추가 및 구현하는 경우

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Setting 페이지 구현

2 participants