Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
14 changes: 14 additions & 0 deletions src/api/profile/profile.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import api from '@/api/api';
import type { UserProfile } from '@/types/user';
import type { ApiError } from '@/types/api-response';

export const getUserProfile = async (): Promise<UserProfile> => {
try {
const response = await api.get<UserProfile>('/profile');
Copy link
Member

Choose a reason for hiding this comment

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

ApiResponse 타입을 활용하면 좋을 것 같습니당

return response.data;
} catch (err) {
const apiError = err as ApiError;
console.error('프로필 로딩 에러:', apiError);
throw apiError;
}
};
9 changes: 9 additions & 0 deletions src/api/user/userEdit.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import api from '@/api/api';
import type { UpdateUserProfileRequest, UserProfileResponse } from '@/types/userEdit';

export const updateUserProfile = async (
data: UpdateUserProfileRequest
): Promise<UserProfileResponse> => {
const res = await api.put('/api/v1/profile', data);
Copy link
Member

Choose a reason for hiding this comment

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

이 부분도 .. /api/v1 지워주셔야 할 것 같습니당
그리고 프로필 수정 api 메소드는 put이 아니라 patch입니다 !!!!!! .. ㅜㅜ
image

return res.data.data;
Copy link
Collaborator

Choose a reason for hiding this comment

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

git pull origin develop 하시고 이 부분 '/profile'로 바꿔주셔야 할 것 같습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

넵!

};
22 changes: 22 additions & 0 deletions src/hooks/queries/useUserProfileQuery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useQuery } from '@tanstack/react-query';
import api from '@/api/api';

export interface UserProfile {
nickname: string;
imageUrl: string | null;
genres: string[];
auditoriums: string[];
}

const fetchUserProfile = async (): Promise<UserProfile> => {
const res = await api.get('/user/profile');
return res.data.data;
};

export const useUserProfileQuery = () => {
return useQuery<UserProfile>({
queryKey: ['userProfile'],
queryFn: fetchUserProfile,
staleTime: 1000 * 60 * 5, // 5분 캐시
});
};
13 changes: 9 additions & 4 deletions src/pages/my/CinemaChoice.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate, useLocation } from 'react-router-dom';
import { Button, Header, ToggleTab } from '@/components';
import type { CinemaFormat, CinemaType } from '@/types/onboarding';

Expand All @@ -15,6 +15,7 @@ const MAX_SELECTABLE_THEATERS = 2;

export default function CinemaChoice() {
const navigate = useNavigate();
const location = useLocation();
const [selectedTheaters, setSelectedTheaters] = useState<CinemaType[]>([]);
const [activeFormat, setActiveFormat] = useState<CinemaFormat>('Dolby');

Expand All @@ -32,8 +33,13 @@ export default function CinemaChoice() {
};

const handleConfirmSelection = () => {
console.log('선택된 영화관:', selectedTheaters);
navigate(-1);
navigate('/my/profile-edit', {
state: {
auditoriums: selectedTheaters,
nickname: location.state?.nickname,
genres: location.state?.genres,
},
});
};

const handleFormatSelect = (format: string) => {
Expand Down Expand Up @@ -67,7 +73,6 @@ export default function CinemaChoice() {
onSelect={(selectedValue) => handleFormatSelect(selectedValue)}
/>

{/* 영화관 목록 */}
<div className="mt-4 flex flex-col gap-y-3">
{currentTheaters.map((theater) => (
<Button
Expand Down
89 changes: 64 additions & 25 deletions src/pages/my/ProfileEdit.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,67 @@
import { useNavigate } from 'react-router-dom';
import { useNavigate, useLocation } from 'react-router-dom';
import { useEffect, useState } from 'react';
import { Button, Header } from '@/components';
import { MyProfileEditIcon, PencilIcon } from '@/assets';
import { updateUserProfile } from '@/api/user/userEdit.api';
import { useToastStore } from '@/store';
import { useUserProfileQuery } from '@/hooks/queries/useUserProfileQuery';


export default function ProfileEdit() {
const navigate = useNavigate();
const location = useLocation();
const { show: showToast } = useToastStore();
const { data: user, isLoading } = useUserProfileQuery();
const [nickname, setNickname] = useState('');
const [genres, setGenres] = useState<string[]>([]);
const [auditoriums, setAuditoriums] = useState<string[]>([]);

useEffect(() => {
if (!user) return;

const state = location.state;

setNickname(state?.nickname ?? user.nickname);

setGenres(
Array.isArray(state?.genres) && state.genres.length > 0
? state.genres
: user.genres
);

setAuditoriums(
Array.isArray(state?.auditoriums) && state.auditoriums.length > 0
? state.auditoriums
: user.auditoriums
);
}, [user, location.state]);
const handleNavigateToGenreSelect = () => {
navigate('/my/select-genre');
navigate('/my/select-genre', { state: { nickname, auditoriums } });
};

const handleNavigateToCinemaChoice = () => {
navigate('/my/cinema-choice');
navigate('/my/cinema-choice', { state: { nickname, genres } });
};

const handleSave = async () => {
try {
await updateUserProfile({
nickname,
genres,
auditoriums,
});
showToast('프로필이 저장되었습니다.', 3000);
navigate('/my');
} catch (error) {
showToast('저장에 실패했습니다.', 3000);
Copy link
Member

Choose a reason for hiding this comment

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

image

피그마랑 토스트 메세지가 다른 것 같습니다 수정부탁드릴게요

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

넵 맞춰서 다른부분들도 변경하도록하겠습니다!

console.error(error);
}
};

return (
<div className="font-suit flex h-screen flex-col text-white">
{/* Header */}
<Header leftSection="BACK" rightSection="KEBAB" className="bg-gray-900" />
{/* Main Content */}
<main className="flex-grow overflow-y-auto px-4 pt-[68px]">
{/* 프로필 정보 섹션 */}
<main className="flex-grow overflow-y-auto px-4 pt-[49px]">
<section className="mt-4 flex flex-col items-center rounded-xl bg-gray-800/30 px-4 py-6">
<button className="mb-6" aria-label="프로필 사진 변경">
<MyProfileEditIcon className="h-20 w-20" />
Expand All @@ -30,50 +73,46 @@ export default function ProfileEdit() {
<input
id="nickname"
type="text"
defaultValue="김씨잇"
value={nickname}
onChange={(e) => setNickname(e.target.value)}
className="mt-2 h-[46px] w-full rounded-md border border-gray-800 bg-gray-900 px-4 py-3 text-white placeholder-gray-500 focus:outline-none"
/>
</div>
</section>

<div className="mx-auto mt-10 flex h-[168px] w-[330px] flex-col justify-around">
<button
onClick={handleNavigateToGenreSelect}
className="flex w-full items-start justify-between text-left"
>
<div className="mx-auto mt-2 flex h-[168px] w-[330px] flex-col justify-around gap-y-2">
<button onClick={handleNavigateToGenreSelect} className="flex w-full justify-between text-left">
<div>
<h2 className="text-title-3 text-white">선호 장르</h2>
<p className="text-caption-2 mt-2 text-red-300">호러, SF, 로맨스</p>
<p className="text-caption-2 mt-2 text-red-300">{genres.join(', ')}</p>
</div>
<PencilIcon className="h-6 w-6 flex-shrink-0" />
<PencilIcon className="h-6 w-6" />
</button>

{/* 즐겨찾는 영화관 섹션 */}
<button
onClick={handleNavigateToCinemaChoice}
className="flex w-full items-start justify-between text-left"
>
<button onClick={handleNavigateToCinemaChoice} className="flex w-full justify-between text-left">
<div>
<h2 className="text-title-3 text-white">즐겨찾는 영화관</h2>
<div className="text-caption-2 mt-2 space-y-2 text-red-300">
<p>남양주현대아울렛 스페이스원</p>
<p>용산아이파크몰 (용아맥)</p>
{auditoriums.map((a: string) => (
<p key={a}>{a}</p>
))}
</div>
</div>
<PencilIcon className="h-6 w-6 flex-shrink-0" />
<PencilIcon className="h-6 w-6" />
</button>
</div>
</main>

{/* Footer */}
<footer className="flex-shrink-0 bg-gray-900 px-4 py-3">
<footer className="bg-gray-900 px-4 py-3">
<Button
variant="primary"
color="red"
size="lg"
rounded="lg"
className="w-full"
fontType="title-3"
onClick={handleSave}
disabled={isLoading}
>
저장하기
</Button>
Expand Down
17 changes: 11 additions & 6 deletions src/pages/my/SelectGenre.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useNavigate, useLocation } from 'react-router-dom';
import { Header, Button } from '@/components';
import type { GenreType } from '@/types/movieGenre';

Expand All @@ -8,7 +8,8 @@ const MAX_SELECTABLE_GENRES = 3;

export default function SelectGenre() {
const navigate = useNavigate();
const [selectedGenres, setSelectedGenres] = useState<GenreType[]>(['SF', '호러', '로맨스']);
const location = useLocation();
const [selectedGenres, setSelectedGenres] = useState<GenreType[]>([]);

const handleGenreClick = (genre: GenreType) => {
setSelectedGenres((prevSelected) => {
Expand All @@ -24,17 +25,21 @@ export default function SelectGenre() {
};

const handleConfirmSelection = () => {
console.log('선택된 장르:', selectedGenres);
navigate(-1);
navigate('/my/profile-edit', {
state: {
genres: selectedGenres,
nickname: location.state?.nickname,
auditoriums: location.state?.auditoriums,
},
});
};

return (
<div className="flex h-screen flex-col p-4">
<div className="flex h-screen flex-col p-4 pt-[48px]">
<Header leftSection="BACK" rightSection="KEBAB" className="bg-gray-900" />

<main className="flex-grow pt-4">
<h2 className="text-title-2 mb-2 text-white">좋아하는 장르를 선택해주세요</h2>

<p className="text-caption-2 mb-6 text-red-300">
최대 {MAX_SELECTABLE_GENRES}개까지 선택할 수 있어요.
</p>
Expand Down
9 changes: 8 additions & 1 deletion src/types/user.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
export interface User {
userId: number;
nickname: string;
profileImageUrl: string;
profileImageUrl: string | null;
}

export interface UserProfile extends User {
level: number;
progress: number;
preferredGenres: string[];
favoriteTheaters: string[];
}
13 changes: 13 additions & 0 deletions src/types/userEdit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export interface UpdateUserProfileRequest {
nickname: string;
imageUrl?: string | null;
genres?: string[];
auditoriums?: string[];
}

export interface UserProfileResponse {
nickname: string;
imageUrl: string | null;
genres: string[];
auditoriums: string[];
}