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
12 changes: 0 additions & 12 deletions src/features/event/api/host.ts

This file was deleted.

20 changes: 0 additions & 20 deletions src/features/event/hooks/useHostHook.ts

This file was deleted.

6 changes: 0 additions & 6 deletions src/features/event/model/hostCreation.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/features/event/ui/EventFunnel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { useNavigate } from 'react-router-dom';
import { EventFunnelInterface, StepNames } from '../../../shared/types/funnelType';
import { useFunnelState } from '../model/FunnelContext';
import { useEventCreation } from '../hooks/useEventHook';
import { useHostCreation } from '../hooks/useHostHook';
import { useHostCreation } from '../../host/hook/useHostHook';

const EventFunnel = ({ onNext, onPrev, Funnel, Step, currentStep }: EventFunnelInterface) => {
const navigate = useNavigate();
Expand Down
15 changes: 12 additions & 3 deletions src/features/host/api/host.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { axiosClient } from '../../../shared/types/api/http-client';
import { UpdateHostChannelInfoRequest } from '../model/host';
import { HostCreationRequest, UpdateHostChannelInfoRequest } from '../model/host';

const updateHostInfo = async (hostChannelId: number, dto: UpdateHostChannelInfoRequest) => {
export const createHost = async (data: HostCreationRequest) => {
const response = await axiosClient.post('/host-channels', data);
return response.data;
};
Comment on lines +4 to +7
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

에러 처리와 반환 타입 명시를 고려해보세요.

createHost 함수는 구조적으로는 잘 작성되었지만, 에러 처리와 반환 타입이 명시되지 않았습니다. API 호출 실패 시의 처리와 타입 안전성을 위해 개선을 권장합니다.

-export const createHost = async (data: HostCreationRequest) => {
+export const createHost = async (data: HostCreationRequest): Promise<ApiResponse<null>> => {
+  try {
    const response = await axiosClient.post('/host-channels', data);
    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
export const createHost = async (data: HostCreationRequest) => {
const response = await axiosClient.post('/host-channels', data);
return response.data;
};
export const createHost = async (data: HostCreationRequest): Promise<ApiResponse<null>> => {
try {
const response = await axiosClient.post('/host-channels', data);
return response.data;
} catch (error) {
throw error;
}
};
🤖 Prompt for AI Agents
In src/features/host/api/host.ts around lines 4 to 7, the createHost function
lacks error handling and does not explicitly specify its return type. To fix
this, add a try-catch block around the axiosClient.post call to handle potential
API call failures gracefully, and define the function's return type to ensure
type safety and clarity about the expected response structure.


export const updateHostInfo = async (hostChannelId: number, dto: UpdateHostChannelInfoRequest) => {
const response = await axiosClient.put(`/host-channels/${hostChannelId}`, dto);
return response.data;
};
export default updateHostInfo;

export const deleteHost = async (hostChannelId: number) => {
const response = await axiosClient.delete(`/host-channels/${hostChannelId}`);
return response.data;
};
Comment on lines +14 to +17
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

에러 처리와 반환 타입 명시를 고려해보세요.

deleteHost 함수도 createHost와 마찬가지로 에러 처리와 반환 타입 명시가 필요합니다.

-export const deleteHost = async (hostChannelId: number) => {
+export const deleteHost = async (hostChannelId: number): Promise<ApiResponse<null>> => {
+  try {
    const response = await axiosClient.delete(`/host-channels/${hostChannelId}`);
    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
export const deleteHost = async (hostChannelId: number) => {
const response = await axiosClient.delete(`/host-channels/${hostChannelId}`);
return response.data;
};
export const deleteHost = async (hostChannelId: number): Promise<ApiResponse<null>> => {
try {
const response = await axiosClient.delete(`/host-channels/${hostChannelId}`);
return response.data;
} catch (error) {
throw error;
}
};
🤖 Prompt for AI Agents
In src/features/host/api/host.ts around lines 14 to 17, the deleteHost function
lacks error handling and does not explicitly specify its return type. Add a
try-catch block to handle potential errors from the axios delete request, and
define the function's return type to match the expected response data structure
for better type safety and clarity.

7 changes: 7 additions & 0 deletions src/features/host/api/hostInvitation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { axiosClient } from '../../../shared/types/api/http-client';
import { HostInvitationRequest } from '../model/hostInvitation';

export const inviteMember = async (hostChannelId: number, data: HostInvitationRequest) => {
const response = await axiosClient.post(`/host-channels/${hostChannelId}/members`, data);
return response.data;
};
Comment on lines +4 to +7
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

에러 처리 방식을 확인해주세요.

현재 함수에서는 별도의 에러 처리가 없습니다. 이것이 의도된 것인지, 아니면 호출하는 훅에서 처리하는지 확인해보세요. API 호출 실패 시 적절한 에러 메시지나 처리가 필요할 수 있습니다.

다음 스크립트로 관련 훅의 에러 처리 방식을 확인해보겠습니다:


🏁 Script executed:

#!/bin/bash
# Description: 호스트 초대 관련 훅에서 에러 처리 방식 확인

# inviteMember 함수를 사용하는 훅들 찾기
rg -A 10 "inviteMember" --type ts

# React Query의 에러 처리 패턴 확인
rg -A 5 "onError" src/features/host/hook/

Length of output: 5843


에러 처리 방식 보완 필요
현재 inviteMembers 호출 시 API 오류에 대한 사용자 알림이 없어, onError 콜백을 전달하지 않으면 에러가 묵살됩니다. useInviteHostHook에 기본 에러 처리 로직을 추가하거나, 호출부에서 onError 콜백을 명시적으로 전달해 주세요.

검토/수정 위치:

  • src/features/host/hook/useInviteHostHook.ts
    inviteMembers.catch()에서 기본 알림(alert 등) 추가
  • src/pages/menu/ui/myHost/HostEditPage.tsx
    inviteMembers(emails, onSuccess) 호출 시 onError 콜백 전달 예:
    inviteMembers(
      emails,
      () => setEmails([]),
      () => alert('초대에 실패했습니다.')
    );
🤖 Prompt for AI Agents
In src/features/host/api/hostInvitation.ts lines 4 to 7, the inviteMember
function lacks error handling, which can cause API call failures to be silently
ignored. To fix this, ensure that the function either includes try-catch blocks
to handle errors and provide meaningful messages or that the calling hooks
explicitly pass onError callbacks to handle errors. Additionally, update
src/features/host/hook/useInviteHostHook.ts to add default error handling in the
inviteMembers catch block, such as showing an alert, and modify
src/pages/menu/ui/myHost/HostEditPage.tsx to pass an onError callback when
calling inviteMembers to notify users on failure.

21 changes: 19 additions & 2 deletions src/features/host/hook/useHostHook.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,27 @@
import { useMutation } from '@tanstack/react-query';
import updateHostInfo from '../api/host';
import { UpdateHostChannelInfoRequest } from '../model/host';
import { HostCreationRequest, UpdateHostChannelInfoRequest } from '../model/host';
import { ApiResponse } from '../../../shared/types/api/apiResponse';
import { createHost, deleteHost, updateHostInfo } from '../api/host';

export const useHostCreation = () => {
return useMutation<ApiResponse<null>, Error, HostCreationRequest>({
mutationFn: async (requestBody: HostCreationRequest) => {
return await createHost(requestBody);
},
});
};

export const useUpdateHostChannelInfo = (hostChannelId: number) => {
const mutation = useMutation({
mutationFn: (dto: UpdateHostChannelInfoRequest) => updateHostInfo(hostChannelId, dto),
});
return mutation;
};

export const useHostDeletion = () => {
return useMutation<ApiResponse<null>, Error, number>({
mutationFn: async (hostChannelId: number) => {
return await deleteHost(hostChannelId);
},
});
};
36 changes: 36 additions & 0 deletions src/features/host/hook/useHostInfoHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { useQueryClient } from '@tanstack/react-query';
import { useUpdateHostChannelInfo } from './useHostHook';
import { HostChannelInfoResponse } from '../../../entities/host/model/hostChannelInfo';

export const useHostInfoSave = (
hostChannelId: number,
hostInfo: HostChannelInfoResponse,
channelDescription: string
) => {
const queryClient = useQueryClient();
const { mutate } = useUpdateHostChannelInfo(hostChannelId);

const handleSave = () => {
if (!hostInfo?.result.id) return;

const updatedData = {
hostChannelId,
profileImageUrl: hostInfo.result.profileImageUrl,
hostChannelName: hostInfo.result.hostChannelName,
hostEmail: hostInfo.result.email,
channelDescription,
};

mutate(updatedData, {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['hostInfo', hostChannelId] });
alert('저장되었습니다.');
},
onError: () => {
alert('저장에 실패했습니다.');
},
});
};

return { handleSave };
};
12 changes: 12 additions & 0 deletions src/features/host/hook/useHostInvitation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { useMutation } from '@tanstack/react-query';
import { ApiResponse } from '../../../shared/types/api/apiResponse';
import { HostInvitationRequest } from '../model/hostInvitation';
import { inviteMember } from '../api/hostInvitation';

export const useHostInvitation = (hostChannelId: number) => {
return useMutation<ApiResponse<null>, Error, HostInvitationRequest>({
mutationFn: async (requestBody: HostInvitationRequest) => {
return await inviteMember(hostChannelId, requestBody);
},
});
};
40 changes: 40 additions & 0 deletions src/features/host/hook/useInviteHostHook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useQueryClient } from '@tanstack/react-query';
import { useHostInvitation } from './useHostInvitation';

export const useInviteMembers = (hostChannelId: number) => {
const queryClient = useQueryClient();
const { mutate: inviteMember } = useHostInvitation(hostChannelId);

const inviteMembers = (emails: string[], onSuccess?: () => void, onError?: () => void) => {
if (emails.length === 0) {
alert('초대할 이메일을 입력해주세요.');
return;
}

const invitationPromises = emails.map(
email =>
new Promise((resolve, reject) => {
inviteMember(
{ email },
{
onSuccess: resolve,
onError: reject,
}
);
})
);

Promise.all(invitationPromises)
.then(() => {
alert('초대가 전송되었습니다.');
queryClient.invalidateQueries({ queryKey: ['hostInfo', hostChannelId] });
onSuccess?.();
})
.catch(() => {
alert('초대 중 일부 실패했습니다.');
onError?.();
});
};
Comment on lines +8 to +37
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Promise 처리 방식 개선과 사용자 피드백 향상을 고려해보세요.

현재 구현에서 몇 가지 개선할 점이 있습니다:

  1. Promise.all 사용의 문제점: 하나의 초대가 실패하면 전체가 실패로 처리됩니다. Promise.allSettled를 사용하여 개별 결과를 처리하는 것을 고려해보세요.

  2. 사용자 피드백: alert 대신 toast나 다른 UI 컴포넌트 사용을 권장합니다.

  3. 더 상세한 에러 정보: 어떤 이메일이 실패했는지 사용자에게 알려주면 좋겠습니다.

- Promise.all(invitationPromises)
+ Promise.allSettled(invitationPromises)
    .then((results) => {
+     const failures = results.filter(result => result.status === 'rejected');
+     const successes = results.filter(result => result.status === 'fulfilled');
      
+     if (failures.length === 0) {
-       alert('초대가 전송되었습니다.');
+       // toast: '모든 초대가 성공적으로 전송되었습니다.'
        queryClient.invalidateQueries({ queryKey: ['hostInfo', hostChannelId] });
        onSuccess?.();
+     } else if (successes.length > 0) {
+       // toast: `${successes.length}명 초대 성공, ${failures.length}명 실패`
+       queryClient.invalidateQueries({ queryKey: ['hostInfo', hostChannelId] });
+       onSuccess?.();
+     } else {
+       // toast: '모든 초대가 실패했습니다.'
+       onError?.();
+     }
    })
-   .catch(() => {
-     alert('초대 중 일부 실패했습니다.');
-     onError?.();
-   });
📝 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 inviteMembers = (emails: string[], onSuccess?: () => void, onError?: () => void) => {
if (emails.length === 0) {
alert('초대할 이메일을 입력해주세요.');
return;
}
const invitationPromises = emails.map(
email =>
new Promise((resolve, reject) => {
inviteMember(
{ email },
{
onSuccess: resolve,
onError: reject,
}
);
})
);
Promise.all(invitationPromises)
.then(() => {
alert('초대가 전송되었습니다.');
queryClient.invalidateQueries({ queryKey: ['hostInfo', hostChannelId] });
onSuccess?.();
})
.catch(() => {
alert('초대 중 일부 실패했습니다.');
onError?.();
});
};
const inviteMembers = (emails: string[], onSuccess?: () => void, onError?: () => void) => {
if (emails.length === 0) {
alert('초대할 이메일을 입력해주세요.');
return;
}
const invitationPromises = emails.map(
email =>
new Promise((resolve, reject) => {
inviteMember(
{ email },
{
onSuccess: resolve,
onError: reject,
}
);
})
);
Promise.allSettled(invitationPromises)
.then((results) => {
const failures = results.filter(r => r.status === 'rejected');
const successes = results.filter(r => r.status === 'fulfilled');
if (failures.length === 0) {
// toast: '모든 초대가 성공적으로 전송되었습니다.'
queryClient.invalidateQueries({ queryKey: ['hostInfo', hostChannelId] });
onSuccess?.();
} else if (successes.length > 0) {
// toast: `${successes.length}명 초대 성공, ${failures.length}명 실패`
queryClient.invalidateQueries({ queryKey: ['hostInfo', hostChannelId] });
onSuccess?.();
} else {
// toast: '모든 초대가 실패했습니다.'
onError?.();
}
});
};
🤖 Prompt for AI Agents
In src/features/host/hook/useInviteHostHook.ts between lines 8 and 37, replace
Promise.all with Promise.allSettled to handle each invitation result
individually so that one failure does not reject all. Collect and identify which
emails failed and provide this detailed feedback to the user. Also, replace
alert calls with a toast or UI notification component for better user
experience.


return { inviteMembers };
};
7 changes: 7 additions & 0 deletions src/features/host/model/host.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
export interface HostCreationRequest {
profileImageUrl: string;
hostChannelName: string;
hostEmail: string;
channelDescription: string;
}

export interface UpdateHostChannelInfoRequest {
profileImageUrl: string;
hostChannelName: string;
Expand Down
3 changes: 3 additions & 0 deletions src/features/host/model/hostInvitation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface HostInvitationRequest {
email: string;
}
8 changes: 6 additions & 2 deletions src/features/menu/ui/MemberEmailInput.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { useState, KeyboardEvent } from 'react';
import EmailInputBase from '../../../shared/ui/EmailInputBase';

const MemberEmailInput = () => {
const [emails, setEmails] = useState<string[]>([]);
interface MemberEmailInputProps {
emails: string[];
setEmails: React.Dispatch<React.SetStateAction<string[]>>;
}

const MemberEmailInput = ({ emails, setEmails }: MemberEmailInputProps) => {
const [inputValue, setInputValue] = useState('');

const handleKeyDown = (e: KeyboardEvent<HTMLInputElement>) => {
Expand Down
2 changes: 1 addition & 1 deletion src/pages/event/ui/host/HostSelectionPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useFunnelState } from '../../../../features/event/model/FunnelContext';
import useHostChannelList from '../../../../entities/host/hook/useHostChannelListHook';
import IconButton from '../../../../../design-system/ui/buttons/IconButton';
import CloseButton from '../../../../../public/assets/event-manage/creation/CloseBtn.svg';
import { useHostDeletion } from '../../../../features/event/hooks/useHostHook';
import { useHostDeletion } from '../../../../features/host/hook/useHostHook';

interface HostSelectionPageProps {
onNext: (nextStep: string) => void;
Expand Down
45 changes: 10 additions & 35 deletions src/pages/menu/ui/myHost/HostEditPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,21 @@ import TertiaryButton from '../../../../../design-system/ui/buttons/TertiaryButt
import { useParams } from 'react-router-dom';
import MemberEmailInput from '../../../../features/menu/ui/MemberEmailInput';
import useHostChannelInfo from '../../../../entities/host/hook/useHostChannelInfoHook';
import { useUpdateHostChannelInfo } from '../../../../features/host/hook/useHostHook';
import { useQueryClient } from '@tanstack/react-query';
import { useHostInfoSave } from '../../../../features/host/hook/useHostInfoHook';
import { useInviteMembers } from '../../../../features/host/hook/useInviteHostHook';

const HostEditPage = () => {
const { id } = useParams<{ id: string }>();
const [selectedHost, setSelectedHost] = useState(true);
const [selectedInfo, setSelectedInfo] = useState(false);
const [tags, setTags] = useState<string[]>([]);
const [inputValue, setInputValue] = useState('');
const [channelDescription, setChannelDescription] = useState('');
const [emails, setEmails] = useState<string[]>([]);

const hostChannelId = Number(id);
const { data: hostInfo } = useHostChannelInfo(hostChannelId);
const { mutate } = useUpdateHostChannelInfo(hostChannelId);
const queryClient = useQueryClient();
const { inviteMembers } = useInviteMembers(hostChannelId);

const { handleSave } = useHostInfoSave(hostChannelId, hostInfo!, channelDescription);

const handeHostInfoClick = () => {
setSelectedHost(true);
Expand All @@ -32,35 +32,10 @@ const HostEditPage = () => {
setSelectedHost(false);
};

const handleSave = () => {
const handleInviteMembers = () => {
if (!hostInfo?.result.id) return;

const updatedData = {
hostChannelId,
profileImageUrl: hostInfo.result.profileImageUrl,
hostChannelName: hostInfo.result.hostChannelName,
hostEmail: hostInfo.result.email,
channelDescription,
};

mutate(updatedData, {
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['hostInfo', hostChannelId],
});
alert('저장되었습니다.');
},
onError: () => {
alert('저장에 실패했습니다.');
},
});
};

const handleAddClick = () => {
if (inputValue.trim() && !tags.includes(inputValue.trim())) {
setTags([...tags, inputValue.trim()]);
setInputValue('');
}
inviteMembers(emails, () => setEmails([]));
};
Comment on lines +35 to 39
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

에러 처리 및 사용자 피드백 개선을 고려해보세요.

handleInviteMembers 함수는 기본적인 기능은 잘 구현되었지만, 에러 처리와 사용자 피드백이 부족합니다. 초대 실패 시의 처리와 성공/실패에 대한 사용자 알림을 추가하는 것을 권장합니다.

const handleInviteMembers = () => {
  if (!hostInfo?.result.id) return;

-  inviteMembers(emails, () => setEmails([]));
+  inviteMembers(emails, {
+    onSuccess: () => {
+      setEmails([]);
+      // 성공 토스트 메시지 표시
+    },
+    onError: (error) => {
+      // 에러 토스트 메시지 표시
+      console.error('멤버 초대 실패:', 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
const handleInviteMembers = () => {
if (!hostInfo?.result.id) return;
const updatedData = {
hostChannelId,
profileImageUrl: hostInfo.result.profileImageUrl,
hostChannelName: hostInfo.result.hostChannelName,
hostEmail: hostInfo.result.email,
channelDescription,
};
mutate(updatedData, {
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ['hostInfo', hostChannelId],
});
alert('저장되었습니다.');
},
onError: () => {
alert('저장에 실패했습니다.');
},
});
};
const handleAddClick = () => {
if (inputValue.trim() && !tags.includes(inputValue.trim())) {
setTags([...tags, inputValue.trim()]);
setInputValue('');
}
inviteMembers(emails, () => setEmails([]));
};
const handleInviteMembers = () => {
if (!hostInfo?.result.id) return;
inviteMembers(emails, {
onSuccess: () => {
setEmails([]);
// 성공 토스트 메시지 표시
},
onError: (error) => {
// 에러 토스트 메시지 표시
console.error('멤버 초대 실패:', error);
}
});
};
🤖 Prompt for AI Agents
In src/pages/menu/ui/myHost/HostEditPage.tsx around lines 35 to 39, the
handleInviteMembers function lacks error handling and user feedback. Modify the
function to handle possible errors from inviteMembers by adding a callback or
promise handling that detects success or failure. On success, clear the emails
and show a success notification to the user; on failure, display an error
message to inform the user about the invite failure.


useEffect(() => {
Expand Down Expand Up @@ -144,13 +119,13 @@ const HostEditPage = () => {
이메일로 회원을 검색해 추가 할 수 있습니다. 삭제 하려면 추가된 이메일 아이콘의 x를 눌러주세요.{' '}
</p>
</div>
<MemberEmailInput />
<MemberEmailInput emails={emails} setEmails={setEmails} />
<TertiaryButton
type="button"
label="전송"
size="large"
color="pink"
onClick={handleAddClick}
onClick={handleInviteMembers}
className="mb-4"
/>
</div>
Expand Down