Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
46 changes: 27 additions & 19 deletions src/app/(user-page)/mypage/MyPageClient.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
'use client';

import {
QUERY_KEYS,

Check warning on line 4 in src/app/(user-page)/mypage/MyPageClient.tsx

View workflow job for this annotation

GitHub Actions / check

'QUERY_KEYS' is defined but never used. Allowed unused vars must match /^_/u
prefetchProfileData,
} from '@/hooks/queries/useMyPageQueries';
import { useQueryClient } from '@tanstack/react-query';
import { useRouter, useSearchParams } from 'next/navigation';
import { useCallback, useEffect, useRef, useState } from 'react';
import { getProfile } from 'service/api/mypageProfile';

Check warning on line 10 in src/app/(user-page)/mypage/MyPageClient.tsx

View workflow job for this annotation

GitHub Actions / check

'getProfile' is defined but never used. Allowed unused vars must match /^_/u

import { TAB_TYPES } from '../../../constants/mypage/mypageConstant';
import {
ACTIVE_SECTION,
TAB_ACTIVE,
TAB_BUTTON,
TAB_CONTAINER,
TAB_INACTIVE,
TAB_INDICATOR,
} from '../../../constants/mypage/mypageCss';
// 컴포넌트 임포트
import BasicEdit from './_features/BasicEdit';
import BasicInfo from './_features/BasicInfo';
Expand Down Expand Up @@ -129,10 +122,6 @@
return () => window.removeEventListener('resize', handleResize);
}, [updateIndicator]);

// 탭 버튼 클래스 생성 함수
const getTabButtonClass = (tab: string) =>
`${TAB_BUTTON} ${activeTab === tab ? TAB_ACTIVE : TAB_INACTIVE}`;

// 5. 조건부 렌더링 최적화
const renderContent = () => {
const isEditMode = editModeSection === activeTab;
Expand Down Expand Up @@ -170,12 +159,16 @@
return (
<div className="profile-manager flex flex-col">
{/* 탭 네비게이션 */}
<div className={TAB_CONTAINER}>
<div className="tabs-container relative flex w-full md:mb-6">
<button
ref={(el) => {
tabRefs.current[TAB_TYPES.BASIC] = el;
}}
className={getTabButtonClass(TAB_TYPES.BASIC)}
className={`w-1/4 px-5 py-3 text-center font-medium transition-colors ${
activeTab === TAB_TYPES.BASIC
? 'text-main'
: 'text-Cgray500 hover:text-Cgray400'
}`}
onClick={() => handleTabChange(TAB_TYPES.BASIC)}
>
기본정보
Expand All @@ -184,7 +177,11 @@
ref={(el) => {
tabRefs.current[TAB_TYPES.CONTACT] = el;
}}
className={getTabButtonClass(TAB_TYPES.CONTACT)}
className={`w-1/4 px-5 py-3 text-center font-medium transition-colors ${
activeTab === TAB_TYPES.CONTACT
? 'text-main'
: 'text-Cgray500 hover:text-Cgray400'
}`}
onClick={() => handleTabChange(TAB_TYPES.CONTACT)}
>
연락수단
Expand All @@ -193,7 +190,11 @@
ref={(el) => {
tabRefs.current[TAB_TYPES.TECH] = el;
}}
className={getTabButtonClass(TAB_TYPES.TECH)}
className={`w-1/4 px-5 py-3 text-center font-medium transition-colors ${
activeTab === TAB_TYPES.TECH
? 'text-main'
: 'text-Cgray500 hover:text-Cgray400'
}`}
onClick={() => handleTabChange(TAB_TYPES.TECH)}
>
기술스택
Expand All @@ -202,18 +203,25 @@
ref={(el) => {
tabRefs.current[TAB_TYPES.PASSWORD] = el;
}}
className={getTabButtonClass(TAB_TYPES.PASSWORD)}
className={`w-1/4 px-5 py-3 text-center font-medium transition-colors ${
activeTab === TAB_TYPES.PASSWORD
? 'text-main'
: 'text-Cgray500 hover:text-Cgray400'
}`}
onClick={() => handleTabChange(TAB_TYPES.PASSWORD)}
>
비밀번호변경
</button>

{/* 애니메이션 underbar */}
<div className={TAB_INDICATOR} style={indicatorStyle} />
<div
className="absolute bottom-0 h-1 bg-main transition-all duration-200 ease-in-out"
style={indicatorStyle}
/>
</div>

{/* 활성화된 섹션만 렌더링 */}
<div className={ACTIVE_SECTION}>{renderContent()}</div>
<div className="active-section mt-4">{renderContent()}</div>
</div>
);
};
Expand Down
111 changes: 64 additions & 47 deletions src/app/(user-page)/mypage/_features/BasicEdit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,6 @@ import {
MAX_INTRO_LENGTH,
POSITION_OPTIONS,
} from '../../../../constants/mypage/mypageConstant';
import {
BUTTON_ACTIONS,
BUTTON_OUTLINE,
BUTTON_PRIMARY,
ERROR_TEXT,
FIELD_CONTAINER,
FIELD_SECTION,
FORM_CONTAINER,
INPUT_FIELD_EDIT,
LABEL_EDIT,
SECTION_CONTAINER,
TEXTAREA_FIELD_EDIT,
TOGGLE_BUTTON_ACTIVE,
TOGGLE_BUTTON_BASE,
TOGGLE_BUTTON_INACTIVE,
} from '../../../../constants/mypage/mypageCss';
import { IFormData } from '../../../../types/mypageTypes';

interface BasicEditProps {
Expand Down Expand Up @@ -156,10 +140,6 @@ const BasicEdit = ({ onEditComplete }: BasicEditProps) => {
[setValue],
);

// 토글 버튼 스타일 결정 함수
const getToggleButtonClass = (isActive: boolean) =>
`${TOGGLE_BUTTON_BASE} ${isActive ? TOGGLE_BUTTON_ACTIVE : TOGGLE_BUTTON_INACTIVE}`;

// 취소 핸들러
const handleCancel = () => {
onEditComplete();
Expand All @@ -171,28 +151,59 @@ const BasicEdit = ({ onEditComplete }: BasicEditProps) => {
}

return (
<form onSubmit={handleSubmit(onSubmit)} className={FORM_CONTAINER}>
<div className={SECTION_CONTAINER}>
<form
onSubmit={handleSubmit(onSubmit)}
className="w-full rounded-[16px] border border-Cgray300 p-[32px]"
>
<div className="flex flex-col gap-[16px] md:gap-[32px]">
{/* 이름 입력 필드 */}
<div className={FIELD_CONTAINER}>
<label htmlFor="name-input" className={LABEL_EDIT}>
<div className="flex flex-col gap-[8px]">
<label htmlFor="name-input" className="typo-head3 text-main">
사용자 이름
</label>
<input
id="name-input"
type="text"
{...register('name', { required: true })}
className={INPUT_FIELD_EDIT}
{...register('name', {
required: true,
validate: (value) => {
// 한글만 있는지 확인
const isKoreanOnly = /^[가-힣]+$/.test(value);

// 영어만 있는지 확인
const isEnglishOnly = /^[a-zA-Z\s]+$/.test(value);

if (isKoreanOnly) {
return (
value.length <= 5 ||
'한글 이름은 최대 5글자까지 입력 가능합니다'
);
} else if (isEnglishOnly) {
return (
value.length <= 8 ||
'영어 이름은 최대 8글자까지 입력 가능합니다'
);
} else {
// 혼합 또는 다른 언어인 경우
return '한글 또는 영어로만 이름을 입력해주세요';
}
},
})}
className="typo-button1 h-[40px] rounded-[16px] border-b border-Cgray300 bg-Cgray200 px-4 py-2 text-Cgray700 focus:outline-none md:h-[50px]"
/>
{errors.name && (
<span className={ERROR_TEXT}>이름을 입력해주세요</span>
<span className="text-sm text-warning">
{errors.name.type === 'required'
? '이름을 입력해주세요'
: errors.name.message}
</span>
)}
</div>

{/* 자기소개 텍스트 영역 */}
<div className={FIELD_CONTAINER}>
<div className="flex flex-col gap-2">
<div className="flex items-center justify-between">
<label htmlFor="intro-input" className={LABEL_EDIT}>
<label htmlFor="intro-input" className="typo-head3 text-main">
자기소개
</label>
</div>
Expand All @@ -205,30 +216,32 @@ const BasicEdit = ({ onEditComplete }: BasicEditProps) => {
},
})}
rows={3}
className={TEXTAREA_FIELD_EDIT}
className="h-[140px] resize-none rounded-[16px] border-b border-Cgray300 bg-Cgray200 px-4 py-2 text-Cgray700 focus:outline-none"
/>
{errors.intro && (
<span className={ERROR_TEXT}>{errors.intro.message}</span>
<span className="text-sm text-warning">{errors.intro.message}</span>
)}
{introLength > MAX_INTRO_LENGTH && !errors.intro && (
<span className={ERROR_TEXT}>
<span className="text-sm text-warning">
최대 {MAX_INTRO_LENGTH}자까지 작성 가능합니다
</span>
)}
</div>

{/* 포지션 버튼 */}
<div className={FIELD_SECTION}>
<div className={LABEL_EDIT}>포지션</div>
<div className="flex flex-col gap-[16px] border-b border-Cgray300 pb-[16px] md:pb-[32px]">
<div className="typo-head3 text-main">포지션</div>
<div className="flex w-full flex-wrap gap-2">
<div className="rounded-4 typo-head3 flex w-full gap-[12px]">
{POSITION_OPTIONS.map((option) => (
<button
key={option.value}
type="button"
className={getToggleButtonClass(
currentPosition === option.value,
)}
className={`flex flex-1 flex-col items-center justify-center gap-1 rounded-md py-1 transition-colors duration-300 md:py-2 ${
currentPosition === option.value
? 'bg-default text-main'
: 'bg-disable text-Cgray500'
}`}
onClick={() => setValue('position', option.value)}
aria-label={option.value}
>
Expand All @@ -248,14 +261,18 @@ const BasicEdit = ({ onEditComplete }: BasicEditProps) => {
</div>

{/* 성별 토글 버튼 */}
<div className={FIELD_SECTION}>
<div className={LABEL_EDIT}>성별</div>
<div className="flex flex-col gap-[16px] border-b border-Cgray300 pb-[16px] md:pb-[32px]">
<div className="typo-head3 text-main">성별</div>
<div className="typo-head3 flex w-full gap-[16px] rounded-md">
{GENDER_OPTIONS.map((option) => (
<button
key={option.value}
type="button"
className={getToggleButtonClass(currentGender === option.value)}
className={`flex flex-1 flex-col items-center justify-center gap-1 rounded-md py-1 transition-colors duration-300 md:py-2 ${
currentGender === option.value
? 'bg-default text-main'
: 'bg-disable text-Cgray500'
}`}
onClick={() => setValue('gender', option.value)}
aria-label={option.value}
>
Expand All @@ -274,8 +291,8 @@ const BasicEdit = ({ onEditComplete }: BasicEditProps) => {
</div>

{/* 연령대 드롭다운 */}
<div className={FIELD_SECTION}>
<div className={LABEL_EDIT}>연령대</div>
<div className="flex flex-col gap-[16px] border-b border-Cgray300 pb-[16px] md:pb-[32px]">
<div className="typo-head3 text-main">연령대</div>
<Controller
name="age"
control={control}
Expand All @@ -294,8 +311,8 @@ const BasicEdit = ({ onEditComplete }: BasicEditProps) => {
</div>

{/* 지역 드롭다운 */}
<div className={FIELD_SECTION}>
<div className={LABEL_EDIT}>지역</div>
<div className="flex flex-col gap-[16px] border-b border-Cgray300 pb-[16px] md:pb-[32px]">
<div className="typo-head3 text-main">지역</div>
<Controller
name="location"
control={control}
Expand All @@ -314,18 +331,18 @@ const BasicEdit = ({ onEditComplete }: BasicEditProps) => {
/>
</div>

<div className={BUTTON_ACTIONS}>
<div className="flex justify-between">
<Button
type="button"
variant="outline"
className={BUTTON_OUTLINE}
className="h-[40px] w-[140px] md:h-[46px]"
onClick={handleCancel}
>
취소
</Button>
<Button
type="submit"
className={BUTTON_PRIMARY}
className="h-[40px] w-[140px] select-none md:h-[46px]"
disabled={
isSubmitting || isUpdating || introLength > MAX_INTRO_LENGTH
}
Expand Down
Loading
Loading