Skip to content

Commit

Permalink
feat(my-info): 내 정보 페이지 API 연동 (#29)
Browse files Browse the repository at this point in the history
* 카드 추가 모달 구현

* type 명 변경

* feat: 카드 조회 api 연동

* CustomDialog

* 카드 추가하기 기능 구현

* 카드삭제 기능 구현

* 터치버튼

* http

* 쿼리키 분리

* 카드 추가 후 에디터로 이동

* ui 일부 수정

* 제목 색상 변경

* delete
  • Loading branch information
woo-jk authored Aug 24, 2024
1 parent 3142dbf commit 5355ad6
Show file tree
Hide file tree
Showing 30 changed files with 580 additions and 176 deletions.
1 change: 1 addition & 0 deletions .pnp.cjs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion src/apis/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ axiosInstance.interceptors.response.use(

const createApiMethod =
(instance: AxiosInstance, method: Method) =>
<T>(config: AxiosRequestConfig): Promise<AxiosResponse> =>
<T>(config: AxiosRequestConfig): Promise<AxiosResponse<T>> =>
instance({ ...config, method });

export const http = {
Expand Down
22 changes: 22 additions & 0 deletions src/app/(sidebar)/(my-info)/apis/useDeleteCard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { http } from '@/apis/http';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { GET_INFO_CARD_LIST } from './useGetInfoCardList';
import { GET_CARD_TYPE_COUNT } from './useGetCardTypeCount';

const deleteCard = (cardId: number) => {
return http.delete({
url: `/cards/${cardId}`,
});
};

export const useDeleteCard = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: deleteCard,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [GET_INFO_CARD_LIST] });
queryClient.invalidateQueries({ queryKey: [GET_CARD_TYPE_COUNT] });
},
});
};
22 changes: 22 additions & 0 deletions src/app/(sidebar)/(my-info)/apis/useGetCardTags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { http } from '@/apis/http';
import { TagType } from '@/types';
import { useQuery } from '@tanstack/react-query';

export const GET_TAGS = 'tags';

type GetCardTagsRseponse = TagType[];

const getCardTags = () => {
return http.get<GetCardTagsRseponse>({ url: `/tags` });
};

export const useGetCardTags = () => {
return useQuery({
queryKey: [GET_TAGS],
queryFn: async () => {
const res = await getCardTags();

return res.data;
},
});
};
25 changes: 25 additions & 0 deletions src/app/(sidebar)/(my-info)/apis/useGetCardTypeCount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useQuery } from '@tanstack/react-query';
import { http } from '../../../../apis/http';

export const GET_CARD_TYPE_COUNT = 'card-type-count';

type GetCardTypeCountResponse = {
경험_정리: number;
자기소개서: number;
면접_질문: number;
};

const getCardTypeCount = () => {
return http.get<GetCardTypeCountResponse>({ url: `/cards/type-count` });
};

export const useGetCardTypeCount = () => {
return useQuery({
queryKey: [GET_CARD_TYPE_COUNT],
queryFn: async () => {
const res = await getCardTypeCount();

return res.data;
},
});
};
22 changes: 22 additions & 0 deletions src/app/(sidebar)/(my-info)/apis/useGetInfoCardList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { useQuery } from '@tanstack/react-query';
import { http } from '../../../../apis/http';
import { InfoCardType, InfoType } from '@/types/info';

export const GET_INFO_CARD_LIST = 'info-card-list';

type GetInfoCardListResponse = InfoCardType[];

const getInfoCardList = (cardType: InfoType) => {
return http.get<GetInfoCardListResponse>({ url: `/cards?type=${cardType}` });
};

export const useGetInfoCardList = (cardType: InfoType) => {
return useQuery({
queryKey: [GET_INFO_CARD_LIST, cardType],
queryFn: async () => {
const res = await getInfoCardList(cardType);

return res.data;
},
});
};
35 changes: 35 additions & 0 deletions src/app/(sidebar)/(my-info)/apis/usePostCard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { http } from '@/apis/http';
import { InfoType } from '@/types';
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { GET_INFO_CARD_LIST } from './useGetInfoCardList';
import { GET_CARD_TYPE_COUNT } from './useGetCardTypeCount';

interface PostCardResponse {
cardId: number;
}

const postCard = (cardType: InfoType, tagIdList: number[]) => {
return http.post<PostCardResponse>({
url: `/card`,
data: {
cardTypeValueList: [cardType],
tagIdList,
},
});
};

export const usePostCard = () => {
const queryClient = useQueryClient();

return useMutation({
mutationFn: async ({ cardType, tagIdList }: { cardType: InfoType; tagIdList: number[] }) => {
const res = await postCard(cardType, tagIdList);

return res.data;
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [GET_INFO_CARD_LIST] });
queryClient.invalidateQueries({ queryKey: [GET_CARD_TYPE_COUNT] });
},
});
};
174 changes: 174 additions & 0 deletions src/app/(sidebar)/(my-info)/components/AddInfoCardDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { Button, Icon } from '@/system/components';
import { Dialog, DialogClose, DialogContent, DialogTitle, DialogTrigger } from '@/system/components/Dialog/Dialog';
import { TagType, InfoType, INFO_TYPES } from '@/types/info';
import { PropsWithChildren, useState } from 'react';
import { TagSelector } from '../../write/[id]/components/TagSelector/TagSelector';
import { If } from '@/system/utils/If';
import { cn } from '@/utils/tailwind-util';
import { Spacing } from '@/system/utils/Spacing';
import { useGetCardTags } from '../apis/useGetCardTags';
import { usePostCard } from '../apis/usePostCard';
import { TouchButton } from '@/components/TouchButton';
import { useRouter } from 'next/navigation';

export function AddInfoCardDialog({ children }: PropsWithChildren) {
const router = useRouter();

const [selectedTagList, setSelectedTagList] = useState<TagType[]>([]);
const [selectedType, setSelectedType] = useState<InfoType | null>(null);

const [isOpenTagSelector, setIsOpenTagSelector] = useState(false);
const [isOpenTypeSelector, setIsOpenTypeSelector] = useState(false);

const { data: tagList } = useGetCardTags();
const { mutateAsync: mutatePostCard } = usePostCard();

const abilityTagList = tagList?.filter((tag) => tag.type === '역량') ?? [];
const personalityTagList = tagList?.filter((tag) => tag.type === '인성') ?? [];

const handleCreateCard = async () => {
if (!selectedType || !selectedTagList.length) return;

const res = await mutatePostCard({
cardType: selectedType,
tagIdList: selectedTagList.map(({ id }) => id),
});

router.push(`/write/${res.cardId}`);
};

return (
<Dialog
onOpenChange={(open) => {
if (!open) {
setSelectedTagList([]);
setSelectedType(null);
}
}}>
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent className="flex flex-col gap-24">
<div className="flex flex-col gap-4">
<DialogTitle className="text-neutral-95 text-body1 font-semibold">
작성할 글에 대한 태그를 추가해주세요
</DialogTitle>
<p className="text-neutral-35 text-caption1">태그를 등록하고 나중에 쉽게 탐색해보세요.</p>
</div>

<div className="flex flex-col gap-20">
<div className="flex flex-col gap-8">
<TagSelector
className="w-full"
disabled={selectedTagList.length === 3}
onChange={(open) => setIsOpenTagSelector(open)}>
<TagSelector.Trigger className="w-full bg-neutral-1 h-46 border-b-0 py-10 px-12 text-neutral-30">
<div className="w-full flex justify-between">
<If condition={!selectedTagList.length}>키워드 태그를 선택해주세요</If>
<If condition={!!selectedTagList.length}>
<ul className="flex gap-8">
{selectedTagList.map((tag) => (
<TagSelector.RemovalbleTag
key={tag.id}
className={cn(
tag.type === '역량' && 'text-blue-blue-text-1 bg-blue-blue-bg-1',
tag.type === '인성' && 'text-blue-purple-text-1 bg-blue-purple-bg-1',
)}
color={tag.type === '역량' ? '#418CC3' : '#9C6BB3'}
onClick={(event) => {
event.stopPropagation();
setSelectedTagList((prev) => prev.filter(({ id }) => id !== tag.id));
}}>
<li>{tag.name}</li>
</TagSelector.RemovalbleTag>
))}
</ul>
</If>
{!isOpenTagSelector && <Icon name="downChevron" color="#878A93" size={20} />}
</div>
</TagSelector.Trigger>

<TagSelector.Content className="w-full left-0 top-46 border-t-0 px-16 pt-16 pb-24">
<TagSelector.Notice>최대 3개까지 선택 가능해요!</TagSelector.Notice>

<TagSelector.TagList title="역량 태그">
{abilityTagList.map((tag) => (
<TagSelector.Tag
key={tag.id}
className="text-blue-blue-text-1 bg-blue-blue-bg-1"
onClick={() => {
if (selectedTagList.length < 3 && !selectedTagList.find(({ id }) => id === tag.id)) {
setSelectedTagList((prev) => [...prev, tag]);
}
}}>
{tag.name}
</TagSelector.Tag>
))}
</TagSelector.TagList>

<Spacing direction="column" size={20} />

<TagSelector.TagList title="인성 태그">
{personalityTagList.map((tag) => (
<TagSelector.Tag
key={tag.id}
className="text-blue-purple-text-1 bg-blue-purple-bg-1"
onClick={() => {
if (selectedTagList.length < 3 && !selectedTagList.find(({ id }) => id === tag.id)) {
setSelectedTagList((prev) => [...prev, tag]);
}
}}>
{tag.name}
</TagSelector.Tag>
))}
</TagSelector.TagList>
</TagSelector.Content>
</TagSelector>
<TagSelector className="w-full" onChange={(open) => setIsOpenTypeSelector(open)}>
<TagSelector.Trigger className="w-full bg-neutral-1 h-46 border-b-0 py-10 px-12 text-neutral-30">
<div className="w-full flex justify-between">
<If condition={selectedType == null}>글의 종류를 선택해주세요</If>
<If condition={selectedType != null}>
<ul className="flex gap-8">
<TagSelector.RemovalbleTag
className="text-yellow-1 bg-yellow-bg-1"
color="#D77B0F"
onClick={(event) => {
event.stopPropagation();
setSelectedType(null);
}}>
<li>{selectedType?.replaceAll('_', ' ')}</li>
</TagSelector.RemovalbleTag>
</ul>
</If>
{!isOpenTypeSelector && <Icon name="downChevron" color="#878A93" size={20} />}
</div>
</TagSelector.Trigger>
<TagSelector.Content className="w-full left-0 top-46 border-t-0 px-16 pt-16 pb-20">
<TagSelector.Notice className="pb-12">1개만 선택 가능해요!</TagSelector.Notice>
<TagSelector.TagList>
{INFO_TYPES.map((type) => (
<TagSelector.Tag
key={type}
className="text-yellow-1 bg-yellow-bg-1"
onClick={() => {
setSelectedType(type);
}}>
{type.replaceAll('_', ' ')}
</TagSelector.Tag>
))}
</TagSelector.TagList>
</TagSelector.Content>
</TagSelector>
</div>
<DialogClose asChild>
<TouchButton
className="rounded-6 bg-neutral-95 text-white py-13 disabled:bg-neutral-5 disabled:text-neutral-30"
disabled={!selectedTagList.length || !selectedType}
onClick={handleCreateCard}>
선택 완료
</TouchButton>
</DialogClose>
</div>
</DialogContent>
</Dialog>
);
}
Loading

0 comments on commit 5355ad6

Please sign in to comment.