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
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,15 @@ import type { ApiErrorResponseT } from '@/types/api';

import { postTournamentItemLink } from '../_apis/postTournamentItemLink';

export const usePostTournamentItemLink = (tournamentId: number) => {
type UsePostTournamentItemLinkOptionsT = {
/** 입력 폼처럼 에러를 화면 안에서 안내하는 경우 false — 4xx 토스트를 끈다 */
showErrorToast?: boolean;
};

export const usePostTournamentItemLink = (
tournamentId: number,
{ showErrorToast = true }: UsePostTournamentItemLinkOptionsT = {}
) => {
const queryClient = useQueryClient();

const { mutate: postTournamentItemLinkMutation, isPending: isPostTournamentItemLinkPending } =
Expand All @@ -25,7 +33,7 @@ export const usePostTournamentItemLink = (tournamentId: number) => {

if (status < 500) {
const clientErrorMessage = detail ?? '요청을 처리하지 못했습니다.';
toast.error(clientErrorMessage);
if (showErrorToast) toast.error(clientErrorMessage);
return;
}

Expand Down
49 changes: 27 additions & 22 deletions apps/web/src/components/get-item-dialog/ByLinkDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Input from '@/components/input';
import { usePostWishLink } from '@/hooks/usePostWishLink';
import type { ItemTypeT } from '@/types/item';
import { URL_PATTERN, extractUrlFromText } from '@/utils/extractUrl';
import { getApiErrorMessage } from '@/utils/getApiErrorMessage';

type ByLinkProps = {
type: ItemTypeT;
Expand All @@ -20,20 +21,23 @@ type ByLinkProps = {

function ByLinkDialog({ type, open, onOpenChange }: ByLinkProps) {
const { id: tournamentId } = useParams<{ id: string }>();
const { postWishLinkMutation, isPostWishLinkPending } = usePostWishLink();

const { postWishLinkMutation, isPostWishLinkPending } = usePostWishLink({
showErrorToast: false,
});
const { postTournamentItemLinkMutation, isPostTournamentItemLinkPending } =
usePostTournamentItemLink(Number(tournamentId));
usePostTournamentItemLink(Number(tournamentId), { showErrorToast: false });

const [url, setUrl] = useState('');
const [hasError, setHasError] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);

const trimmedUrl = url.trim();
const isEmpty = trimmedUrl.length === 0;
const isPending = isPostWishLinkPending || isPostTournamentItemLinkPending;

const resetState = () => {
setUrl('');
setHasError(false);
setErrorMessage(null);
};

const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
Expand All @@ -44,30 +48,31 @@ function ByLinkDialog({ type, open, onOpenChange }: ByLinkProps) {
const submitUrl = URL_PATTERN.test(trimmedUrl) ? trimmedUrl : extractUrlFromText(trimmedUrl);

if (!submitUrl) {
setHasError(true);
setErrorMessage('올바른 URL 형식으로 입력해주세요.');
return;
}

if (!submitUrl.startsWith('https://')) {
setErrorMessage('https 링크만 등록할 수 있어요');
return;
}

/** 닫기/초기화는 성공 시에만 — 실패 시 URL을 고칠 수 있게 유지. 위시리스트 이동은 usePostWishLink 훅이 조건부로 처리 */
if (type === 'wish')
postWishLinkMutation(submitUrl, {
onSuccess: () => {
onOpenChange(false);
resetState();
},
});
else
postTournamentItemLinkMutation(submitUrl, {
onSuccess: () => {
onOpenChange(false);
resetState();
},
});
const mutationOptions = {
onSuccess: () => {
onOpenChange(false);
resetState();
},
onError: (error: Error) => setErrorMessage(getApiErrorMessage(error)),
};

if (type === 'wish') postWishLinkMutation(submitUrl, mutationOptions);
else postTournamentItemLinkMutation(submitUrl, mutationOptions);
};

const handleChange = (value: string) => {
setUrl(value);
if (hasError) setHasError(false);
if (errorMessage) setErrorMessage(null);
};

/** 상품 설명 + URL 형태로 붙여넣으면 URL 만 입력창에 반영한다 */
Expand Down Expand Up @@ -99,8 +104,8 @@ function ByLinkDialog({ type, open, onOpenChange }: ByLinkProps) {
onChange={event => handleChange(event.target.value)}
onPaste={handlePaste}
left={<LinkIconFill className="size-5" />}
aria-invalid={hasError}
{...(hasError ? { helperText: '올바른 URL 형식으로 입력해주세요.' } : {})}
aria-invalid={Boolean(errorMessage)}
{...(errorMessage ? { helperText: errorMessage } : {})}
autoFocus
/>
<Button
Expand Down
9 changes: 7 additions & 2 deletions apps/web/src/hooks/usePostWishLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ import { ROUTES } from '@/consts/route';
import type { ApiErrorResponseT } from '@/types/api';
import { logAnalyticsEvent } from '@/utils/analytics';

export const usePostWishLink = () => {
type UsePostWishLinkOptionsT = {
/** 입력 폼처럼 에러를 화면 안에서 안내하는 경우 false — 4xx 토스트를 끈다 */
showErrorToast?: boolean;
};

export const usePostWishLink = ({ showErrorToast = true }: UsePostWishLinkOptionsT = {}) => {
const router = useRouter();
const pathname = usePathname();
const queryClient = useQueryClient();
Expand All @@ -35,7 +40,7 @@ export const usePostWishLink = () => {

if (status < 500) {
const clientErrorMessage = detail ?? '요청을 처리하지 못했습니다.';
toast.error(clientErrorMessage);
if (showErrorToast) toast.error(clientErrorMessage);
return;
}

Expand Down
Loading