-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(my-info): 내 정보 페이지 API 연동 (#29)
* 카드 추가 모달 구현 * type 명 변경 * feat: 카드 조회 api 연동 * CustomDialog * 카드 추가하기 기능 구현 * 카드삭제 기능 구현 * 터치버튼 * http * 쿼리키 분리 * 카드 추가 후 에디터로 이동 * ui 일부 수정 * 제목 색상 변경 * delete
- Loading branch information
Showing
30 changed files
with
576 additions
and
174 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed
BIN
-6.71 MB
.yarn/cache/@img-sharp-libvips-darwin-arm64-npm-1.0.2-6c9ede770e-8.zip
Binary file not shown.
Binary file not shown.
Binary file renamed
BIN
+33.9 MB
...-darwin-arm64-npm-14.2.4-86d534c3ee-8.zip → ...wc-darwin-x64-npm-14.2.4-56f1600da7-8.zip
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] }); | ||
}, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}, | ||
}); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
174
src/app/(sidebar)/(my-info)/components/AddInfoCardDialog.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
Oops, something went wrong.