-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 호스트 정보 수정 API 연동 #113
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
Changes from 7 commits
23d2e58
200d0e0
9dc47dd
358163f
574e485
3c40fff
5c99113
76c57ee
5b80e2a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import { axiosClient } from '../../../shared/types/api/http-client'; | ||
| import { HostChannelInfoRequest, HostChannelInfoResponse } from '../model/hostChannelInfo'; | ||
|
|
||
| const hostChannelInfo = async (dto: HostChannelInfoRequest) => { | ||
| const response = await axiosClient.get<HostChannelInfoResponse>(`/host-channels/${dto.hostChannelId}/info`); | ||
| return response.data; | ||
| }; | ||
| export default hostChannelInfo; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { useQuery } from '@tanstack/react-query'; | ||
| import hostChannelInfo from '../api/hostChannelInfo'; | ||
|
|
||
| const useHostChannelInfo = (hostChannelId: number) => { | ||
| const { data } = useQuery({ | ||
| queryKey: ['hostInfo', hostChannelId], | ||
| queryFn: () => hostChannelInfo({ hostChannelId }), | ||
| enabled: !!hostChannelId, | ||
| }); | ||
| return { data }; | ||
| }; | ||
| export default useHostChannelInfo; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| export interface HostChannelInfoRequest { | ||
| hostChannelId: number; | ||
| } | ||
|
|
||
| export interface HostChannelInfoResponse { | ||
| result: { | ||
| id: number; | ||
| profileImageUrl: string; | ||
| hostChannelName: string; | ||
| channelDescription: string; | ||
| email: string; | ||
| hostChannelMembers: { id: number; memberName: string }[]; | ||
| }; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import { axiosClient } from '../../../shared/types/api/http-client'; | ||
| import { UpdateHostChannelInfoRequest } from '../model/host'; | ||
|
|
||
| const updateHostInfo = async (hostChannelId: number, dto: UpdateHostChannelInfoRequest) => { | ||
| const response = await axiosClient.put(`/host-channels/${hostChannelId}`, dto); | ||
| return response.data; | ||
| }; | ||
| export default updateHostInfo; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { useMutation } from '@tanstack/react-query'; | ||
| import updateHostInfo from '../api/host'; | ||
| import { UpdateHostChannelInfoRequest } from '../model/host'; | ||
| import { useParams } from 'react-router-dom'; | ||
|
|
||
| export const useUpdateHostChannelInfo = () => { | ||
| const { id } = useParams(); | ||
| const hostChannelId = Number(id); | ||
|
|
||
| const mutation = useMutation({ | ||
| mutationFn: (dto: UpdateHostChannelInfoRequest) => updateHostInfo(hostChannelId, dto), | ||
| }); | ||
| return mutation; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| export interface UpdateHostChannelInfoRequest { | ||
| profileImageUrl: string; | ||
| hostChannelName: string; | ||
| hostEmail: string; | ||
| channelDescription: string; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,15 @@ | ||
| import { useNavigate, useParams } from 'react-router-dom'; | ||
| import HostDetailLayout from '../../../../shared/ui/backgrounds/HostDetailLayout'; | ||
| import { trendingEvents } from '../../../../shared/types/eventCardType'; | ||
| import EventCard from '../../../../shared/ui/EventCard'; | ||
| import useHostDetail from '../../../../entities/host/hook/useHostDetailHook'; | ||
|
|
||
| const HostDetailPage = () => { | ||
| const navigate = useNavigate(); | ||
| // URL에서 hostId를 가져오기 | ||
| const { id } = useParams<{ id: string }>(); | ||
|
|
||
| // hostId에 해당하는 이벤트들만 필터링 | ||
| const filteredEvents = trendingEvents.filter(event => event.id === Number(id)); | ||
| const hostChannelId = Number(id); | ||
| const { data } = useHostDetail(hostChannelId); | ||
|
|
||
|
Comment on lines
+11
to
13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion URL 파라미터 검증 로직 부재 |
||
| return ( | ||
| <HostDetailLayout | ||
|
|
@@ -20,16 +20,16 @@ const HostDetailPage = () => { | |
| } | ||
| > | ||
| <div className="grid grid-cols-2 gap-4 mx-5 mt-3 md:grid-cols-2 lg:grid-cols-2 z-50"> | ||
| {filteredEvents.map((event, index) => ( | ||
| {data?.result.events.map(event => ( | ||
| <EventCard | ||
| id={event.id} | ||
| key={index} | ||
| img={event.img} | ||
| eventTitle={event.eventTitle} | ||
| dDay={event.dDay} | ||
| host={event.host} | ||
| eventDate={event.eventDate} | ||
| location={event.location} | ||
| key={event.id} | ||
| img={event.bannerImageUrl} | ||
| eventTitle={event.title} | ||
| dDay={event.remainDays} | ||
| host={event.hostChannelName} | ||
| eventDate={event.startDate} | ||
| location={event.onlineType} | ||
| hashtags={event.hashtags} | ||
Yejiin21 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| /> | ||
| ))} | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,33 +1,34 @@ | ||||||||||||||||||||||||||||||||||
| import HostDetailLayout from '../../../../shared/ui/backgrounds/HostDetailLayout'; | ||||||||||||||||||||||||||||||||||
| import ProfileCircle from '../../../../../design-system/ui/Profile'; | ||||||||||||||||||||||||||||||||||
| import { useParams } from 'react-router-dom'; | ||||||||||||||||||||||||||||||||||
| import { hostInfo } from '../../../../shared/types/hostInfoType'; | ||||||||||||||||||||||||||||||||||
| import useHostChannelInfo from '../../../../entities/host/hook/useHostChannelInfoHook'; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const HostInfoPage = () => { | ||||||||||||||||||||||||||||||||||
| const { id } = useParams<{ id: string }>(); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const user = hostInfo.filter(user => user.id === Number(id)); | ||||||||||||||||||||||||||||||||||
| const hostChannelId = Number(id); | ||||||||||||||||||||||||||||||||||
| const { data } = useHostChannelInfo(hostChannelId); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
Comment on lines
+9
to
11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion URL 파라미터 검증 및 로딩/에러 상태를 명시적으로 처리해주세요 -const hostChannelId = Number(id);
-const { data } = useHostChannelInfo(hostChannelId);
+const hostChannelId = Number(id);
+
+// 잘못된 URL 파라미터 방어
+if (!id || Number.isNaN(hostChannelId)) {
+ return <div>잘못된 URL 입니다.</div>;
+}
+
+// React-Query useHostChannelInfo 훅이 `enabled` 옵션을 지원한다고 가정
+const { data, isLoading, isError } = useHostChannelInfo(hostChannelId, {
+ enabled: !!hostChannelId,
+});
+
+if (isLoading) return <div>로딩 중...</div>;
+if (isError) return <div>데이터를 불러오지 못했습니다.</div>;📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||
| <HostDetailLayout> | ||||||||||||||||||||||||||||||||||
| <div className="relative overflow-y-auto"> | ||||||||||||||||||||||||||||||||||
| <div className="flex flex-col px-8 md:px-12 gap-6"> | ||||||||||||||||||||||||||||||||||
| <div className="flex flex-col gap-4 py-8"> | ||||||||||||||||||||||||||||||||||
| <p className="text-xl text-black font-semibold">대표 이메일</p> | ||||||||||||||||||||||||||||||||||
| <p>example@example.com</p> | ||||||||||||||||||||||||||||||||||
| <p>{data?.result.email}</p> | ||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
| <div className="flex flex-col gap-4 lg:gap-6"> | ||||||||||||||||||||||||||||||||||
| <p className="text-xl text-black font-semibold">멤버 목록</p> | ||||||||||||||||||||||||||||||||||
| <div className="flex flex-wrap gap-x-5 gap-y-4 lg:gap-x-10 lg:gap-y-6 text-sm md:text-16 lg:text-base"> | ||||||||||||||||||||||||||||||||||
| {user.map(user => ( | ||||||||||||||||||||||||||||||||||
| {data?.result.hostChannelMembers.map(user => ( | ||||||||||||||||||||||||||||||||||
| <ProfileCircle | ||||||||||||||||||||||||||||||||||
| key={user.id} | ||||||||||||||||||||||||||||||||||
| id={user.id} | ||||||||||||||||||||||||||||||||||
| profile="userProfile" | ||||||||||||||||||||||||||||||||||
| name={user.nickname} | ||||||||||||||||||||||||||||||||||
| name={user.memberName.slice(1)} | ||||||||||||||||||||||||||||||||||
| className="w-12 h-12 md:w-13 md:h-13 lg:w-14 lg:h-14 text-sm md:text-16 lg:text-base" | ||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||
| {user.name} | ||||||||||||||||||||||||||||||||||
| {user.memberName} | ||||||||||||||||||||||||||||||||||
| </ProfileCircle> | ||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||
Yejiin21 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -2,7 +2,7 @@ import { useNavigate, useParams } from 'react-router-dom'; | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ProfileCircle from '../../../../design-system/ui/Profile'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import Header from '../../../../design-system/ui/Header'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { ButtonHTMLAttributes, ReactElement } from 'react'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { hostInfo, hostInfoData } from '../../types/hostInfoType'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import useHostChannelInfo from '../../../entities/host/hook/useHostChannelInfoHook'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| interface HostDetailLayoutProps { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rightContent?: ReactElement<ButtonHTMLAttributes<HTMLButtonElement>>; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -12,7 +12,8 @@ const HostDetailLayout = ({ rightContent, children }: HostDetailLayoutProps) => | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { id } = useParams<{ id: string }>(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const navigate = useNavigate(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const host: hostInfoData | undefined = hostInfo.find(host => host.id === Number(id)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const hostChannelId = Number(id); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data } = useHostChannelInfo(hostChannelId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const handleBackClick = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| navigate(-1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -29,12 +30,12 @@ const HostDetailLayout = ({ rightContent, children }: HostDetailLayoutProps) => | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| rightContent={rightContent} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| color="white" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex justify-center items-center px-6 md:px-10"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex justify-start items-center px-6 md:px-10"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <ProfileCircle profile="hostProfile" className="md:w-28 md:h-28 w-24 h-24" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex flex-col gap-1 md:gap-3 ml-5 text-white"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className="text-lg md:text-xl font-bold">{host?.name}</p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className="flex-wrap text-sm md:text-base">{host?.description}</p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className="text-lg md:text-xl font-bold">{data?.result.hostChannelName}</p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <p className="flex-wrap text-sm md:text-base">{data?.result.channelDescription}</p> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+37
to
+38
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 동적 데이터 렌더링 구현 정적 데이터 대신 API에서 가져온 동적 데이터를 사용하여 호스트 이름과 설명을 렌더링하도록 변경되었습니다. 이는 좋은 개선입니다. 그러나 데이터 로딩 중이거나 오류 발생 시 대체 UI가 없어 사용자 경험에 영향을 줄 수 있습니다. 로딩 상태와 오류 상태에 대한 처리를 추가하는 것을 권장합니다. const HostDetailLayout = ({ rightContent, children }: HostDetailLayoutProps) => {
const { id } = useParams<{ id: string }>();
const navigate = useNavigate();
const hostChannelId = Number(id);
- const { data } = useHostChannelInfo(hostChannelId);
+ const { data, isLoading, error } = useHostChannelInfo(hostChannelId);
const handleBackClick = () => {
navigate(-1);
};
+ // 로딩 중 또는 에러 발생 시 대체 UI 렌더링
+ const renderHostInfo = () => {
+ if (isLoading) {
+ return (
+ <div className="flex flex-col gap-1 md:gap-3 ml-5 text-white">
+ <p className="text-lg md:text-xl font-bold">로딩 중...</p>
+ <p className="flex-wrap text-sm md:text-base">호스트 정보를 불러오고 있습니다.</p>
+ </div>
+ );
+ }
+
+ if (error) {
+ return (
+ <div className="flex flex-col gap-1 md:gap-3 ml-5 text-white">
+ <p className="text-lg md:text-xl font-bold">정보 불러오기 실패</p>
+ <p className="flex-wrap text-sm md:text-base">호스트 정보를 불러오는데 실패했습니다.</p>
+ </div>
+ );
+ }
+
+ return (
+ <div className="flex flex-col gap-1 md:gap-3 ml-5 text-white">
+ <p className="text-lg md:text-xl font-bold">{data?.result.hostChannelName || '이름 없음'}</p>
+ <p className="flex-wrap text-sm md:text-base">{data?.result.channelDescription || '설명 없음'}</p>
+ </div>
+ );
+ };
return (
<div className="bg-white relative">
<div className="top-0 h-48 md:h-56 bg-gradient-to-br from-[#FF5593] to-[rgb(255,117,119)]">
{/* 헤더 영역 */}
<Header
leftButtonLabel="<"
leftButtonClassName="text-xl z-30"
leftButtonClick={handleBackClick}
rightContent={rightContent}
color="white"
/>
<div className="flex justify-start items-center px-6 md:px-10">
<ProfileCircle profile="hostProfile" className="md:w-28 md:h-28 w-24 h-24" />
- <div className="flex flex-col gap-1 md:gap-3 ml-5 text-white">
- <p className="text-lg md:text-xl font-bold">{data?.result.hostChannelName}</p>
- <p className="flex-wrap text-sm md:text-base">{data?.result.channelDescription}</p>
- </div>
+ {renderHostInfo()}
</div>
</div>
{/* 레이아웃 내용 */}
<div className="absolute top-[calc(100%-2vh)] w-full bg-white rounded-t-[20px]">{children}</div>
</div>
);
};📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
뮤테이션 훅 구현이 잘 되어 있습니다.
호스트 채널 정보 업데이트를 위한 커스텀 훅이 적절하게 구현되어 있습니다. URL 파라미터에서 ID를 추출하고 뮤테이션을 설정하는 방식이 깔끔합니다.
다만, 몇 가지 개선 사항이 있습니다:
export const useUpdateHostChannelInfo = () => { const { id } = useParams(); - const hostChannelId = Number(id); + const hostChannelId = id ? Number(id) : undefined; + + if (!hostChannelId || isNaN(hostChannelId)) { + throw new Error('유효하지 않은 호스트 채널 ID입니다.'); + } const mutation = useMutation({ mutationFn: (dto: UpdateHostChannelInfoRequest) => updateHostInfo(hostChannelId, dto), + onSuccess: () => { + // React Query 클라이언트 import 필요 + // queryClient.invalidateQueries(['hostChannelInfo', hostChannelId]); + }, }); return mutation; };