-
Notifications
You must be signed in to change notification settings - Fork 0
캐릭터 도감 컴포넌트 추가 #25
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
캐릭터 도감 컴포넌트 추가 #25
Changes from all commits
89418ee
d014dba
681bf6e
3d2ef1f
b33dce7
f9f7e15
01523d7
c0384d7
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,9 @@ | ||
| import axiosInstance from '@/shared/api/axios'; | ||
| import { CharacterItemsResponse } from '../model/character.type'; | ||
|
|
||
| export const getCharacterCollection = () => | ||
| axiosInstance | ||
| .get<CharacterItemsResponse>('/character/collection', { | ||
| useMock: true, | ||
| }) | ||
| .then((res) => res.data); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| export { default as useGetCharacterCollection } from './useGetCharacterCollection'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| import { getCharacterCollection } from '@/entities/character/api/character'; | ||
| import { useSuspenseQuery } from '@tanstack/react-query'; | ||
|
|
||
| const useGetCharacterCollection = () => | ||
| useSuspenseQuery({ | ||
| queryKey: ['characters'], | ||
| queryFn: getCharacterCollection, | ||
| // 획득한 캐릭터가 가장 앞에 오도록 정렬 | ||
| select: (data) => | ||
| [...data.characters].sort((a, b) => { | ||
| if (a.isUnlocked === b.isUnlocked) return 0; | ||
| return a.isUnlocked ? -1 : 1; | ||
| }), | ||
| }); | ||
|
|
||
| export default useGetCharacterCollection; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import styled from 'styled-components'; | ||
|
|
||
| export const CharacterList = styled.ul` | ||
| display: grid; | ||
| grid-template-columns: repeat(2, 1fr); | ||
| gap: ${({ theme }) => theme.unit[8]}; | ||
| padding: ${({ theme }) => theme.unit[20]}; | ||
| `; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| import { useGetCharacterCollection } from '@/features/character-management/api/index'; | ||
| import CharacterItem from '../CharacterItem'; | ||
| import * as S from './index.styles'; | ||
|
|
||
| function CharacterList() { | ||
| const { data: characterCollection } = useGetCharacterCollection(); | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| return ( | ||
| <S.CharacterList> | ||
| {characterCollection.map((character) => ( | ||
| <CharacterItem key={character.id} character={character} /> | ||
| ))} | ||
| </S.CharacterList> | ||
| ); | ||
| } | ||
|
|
||
| export default CharacterList; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| import styled from 'styled-components'; | ||
|
|
||
| export const CardContainerBase = styled.li` | ||
| box-sizing: border-box; | ||
| aspect-ratio: 171 / 196; | ||
|
Contributor
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. 카드 비율이 해당 비율로 고정이라 aspect-ratio를 쓰신 걸까요?
Contributor
Author
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. 넵 맞습니다! 피그마에 카드 크기가 |
||
| min-height: 12.25rem; | ||
| display: flex; | ||
| flex-direction: column; | ||
| align-items: center; | ||
| gap: ${({ theme }) => theme.unit[4]}; | ||
| border-radius: ${({ theme }) => theme.radius.default}; | ||
| background-color: ${({ theme }) => theme.color.semantic.orange.subtle}; | ||
| `; | ||
|
|
||
| export const CardContainer = styled(CardContainerBase)` | ||
| justify-content: flex-end; | ||
| padding-top: ${({ theme }) => theme.unit[32]}; | ||
| padding-bottom: ${({ theme }) => theme.unit[16]}; | ||
| padding-left: ${({ theme }) => theme.unit[16]}; | ||
| padding-right: ${({ theme }) => theme.unit[16]}; | ||
| border: 1px solid ${({ theme }) => theme.color.semantic.orange.subtle}; | ||
| `; | ||
|
|
||
| export const LockedCharacterCard = styled(CardContainerBase)` | ||
| justify-content: center; | ||
| padding-top: ${({ theme }) => theme.unit[28]}; | ||
| padding-bottom: ${({ theme }) => theme.unit[36]}; | ||
| padding-left: ${({ theme }) => theme.unit[16]}; | ||
| padding-right: ${({ theme }) => theme.unit[16]}; | ||
| border: 1px dashed ${({ theme }) => theme.color.semantic.border.default}; | ||
| `; | ||
|
|
||
| export const CharacterImage = styled.img` | ||
| width: 100%; | ||
| flex-shrink: 1; | ||
| min-height: 0; | ||
| object-fit: contain; | ||
| margin-bottom: ${({ theme }) => theme.unit[16]}; | ||
| `; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import { format } from 'date-fns'; | ||
| import { CharacterItemData } from '@/entities/character/model/character.type'; | ||
| import { Certified } from '@/shared/assets/svgs/icon'; | ||
| import Text from '@/shared/ui/Text'; | ||
| import * as S from './index.styles'; | ||
|
|
||
| function LockedCharacterCard() { | ||
| return ( | ||
| <S.LockedCharacterCard aria-label="잠긴 캐릭터"> | ||
| <Certified width={62} /> | ||
| </S.LockedCharacterCard> | ||
| ); | ||
| } | ||
|
|
||
| interface CharacterCardProps { | ||
| character: CharacterItemData; | ||
| } | ||
|
|
||
| function CharacterCard({ character }: CharacterCardProps) { | ||
| const { imageUrl, name, unlockedAt } = character; | ||
|
|
||
| return ( | ||
| <S.CardContainer> | ||
| <S.CharacterImage src={imageUrl} alt={name} /> | ||
| <Text variant="body2Sb">{name}</Text> | ||
| <Text variant="caption"> | ||
| {unlockedAt ? format(new Date(unlockedAt), 'yyyy.MM.dd') : null} | ||
|
Contributor
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. 자주 쓰이는 포맷이라면 날짜 포맷팅 유틸로 따로 분리해도 좋을 것 같습니다!
Contributor
Author
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. 저희 전체 코드에서 날짜 포맷팅을 사용하는 패턴이 아래 4가지더라고요! 각각 포맷이 달라서,,, 이 부분은 유틸로 분리하지 않고 그냥 두도록 하겠습니다 😂 |
||
| </Text> | ||
|
yoouyeon marked this conversation as resolved.
|
||
| </S.CardContainer> | ||
| ); | ||
| } | ||
|
|
||
| interface CharacterItemProps { | ||
| character: CharacterItemData; | ||
| } | ||
|
|
||
| function CharacterItem({ character }: CharacterItemProps) { | ||
| if (!character.isUnlocked) return <LockedCharacterCard />; | ||
| return <CharacterCard character={character} />; | ||
| } | ||
|
|
||
| export default CharacterItem; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import styled from 'styled-components'; | ||
|
|
||
| export const Container = styled.div` | ||
| display: flex; | ||
| flex-direction: column; | ||
| padding: ${({ theme }) => `${theme.unit[28]} 0`}; | ||
| `; | ||
|
|
||
| export const TitleWrapper = styled.div` | ||
| padding: ${({ theme }) => `${theme.unit[8]} ${theme.unit[20]}`}; | ||
| `; | ||
|
|
||
| export const CharacterGrid = styled.div` | ||
| padding: ${({ theme }) => theme.unit[20]}; | ||
| `; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import { Suspense } from 'react'; | ||
| import Text from '@/shared/ui/Text'; | ||
| import CharacterGrid from '../CharacterGrid'; | ||
| import * as S from './index.styles'; | ||
|
|
||
| function CharacterSection() { | ||
| return ( | ||
| <S.Container> | ||
| <S.TitleWrapper> | ||
| <Text color="semantic.text.strong" variant="title"> | ||
| 캐릭터 도감 | ||
| </Text> | ||
| </S.TitleWrapper> | ||
| <Suspense fallback={<S.CharacterGrid>로딩 중...</S.CharacterGrid>}> | ||
| <CharacterGrid /> | ||
| </Suspense> | ||
| </S.Container> | ||
| ); | ||
| } | ||
|
|
||
| export default CharacterSection; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| export { default as CharacterBottomSheet } from './CharacterBottomSheet'; | ||
| export { default as CharacterGrid } from './CharacterGrid'; | ||
| export { default as CharacterItem } from './CharacterItem'; | ||
| export { default as CharacterSection } from './CharacterSection'; | ||
| export { default as StarChip } from './StarChip'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| import { http, HttpResponse, passthrough } from 'msw'; | ||
| import { CharacterItemsResponse } from '@/entities/character/model/character.type'; | ||
| import getIsMocked from '@/mocks/lib/getIsMocked'; | ||
|
|
||
| const characterHandlers = [ | ||
| http.get('/api/v1/character/collection', async ({ request }) => { | ||
| if (!getIsMocked(request)) return passthrough(); | ||
|
|
||
| const dummyCharacterCollectionResponse: CharacterItemsResponse = { | ||
| characters: [ | ||
| { | ||
| id: 1, | ||
| name: '마법사 또또', | ||
| isUnlocked: true, | ||
| imageUrl: | ||
| 'https://ylsrfiwjufyciwoidcaz.supabase.co/storage/v1/object/public/moddo/wizard_ddoddo.png', | ||
| imageBigUrl: | ||
| 'https://ylsrfiwjufyciwoidcaz.supabase.co/storage/v1/object/public/moddo/wizard_ddoddo.png', | ||
| rarity: 3, | ||
| unlockedAt: '2025-02-03T00:00:00Z', | ||
| }, | ||
| { | ||
| id: 2, | ||
| name: '천사 모또', | ||
| isUnlocked: true, | ||
| imageUrl: | ||
| 'https://ylsrfiwjufyciwoidcaz.supabase.co/storage/v1/object/public/moddo/angel_moddo.png', | ||
| imageBigUrl: | ||
| 'https://ylsrfiwjufyciwoidcaz.supabase.co/storage/v1/object/public/moddo/angel_moddo.png', | ||
| rarity: 2, | ||
| unlockedAt: '2025-02-03T00:00:00Z', | ||
| }, | ||
| { | ||
| id: 3, | ||
| name: '딸기 또또', | ||
| isUnlocked: false, | ||
| imageUrl: | ||
| 'https://ylsrfiwjufyciwoidcaz.supabase.co/storage/v1/object/public/moddo/strawberry_ddoddo.png', | ||
| imageBigUrl: | ||
| 'https://ylsrfiwjufyciwoidcaz.supabase.co/storage/v1/object/public/moddo/strawberry_ddoddo.png', | ||
| rarity: 1, | ||
| unlockedAt: null, | ||
| }, | ||
| ], | ||
| }; | ||
|
|
||
| return HttpResponse.json(dummyCharacterCollectionResponse); | ||
| }), | ||
| ]; | ||
|
|
||
| export default characterHandlers; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| import type { SVGProps } from 'react'; | ||
|
|
||
| const SvgCertified = (props: SVGProps<SVGSVGElement>) => ( | ||
| <svg | ||
| xmlns="http://www.w3.org/2000/svg" | ||
| fill="none" | ||
| viewBox="0 0 61 61" | ||
| {...props} | ||
| > | ||
| <path fill="#D2D4D5" d="M10.415 23.805h40.171v26.78H10.415z" /> | ||
| <path | ||
| fill="#D2D4D5" | ||
| d="M35.073 35.074a4.58 4.58 0 0 1-2.538 4.097v4.545a2.033 2.033 0 0 1-4.066 0v-4.54a4.575 4.575 0 1 1 6.604-4.102" | ||
| /> | ||
| <path | ||
| fill="#D2D4D5" | ||
| fillRule="evenodd" | ||
| d="M45.202 20.332c-.116-2.765-.56-6.049-2.124-8.867-1.013-1.822-2.494-3.452-4.617-4.61-2.109-1.15-4.737-1.773-7.963-1.773s-5.854.623-7.963 1.773c-2.123 1.158-3.604 2.788-4.616 4.61-1.566 2.818-2.008 6.102-2.124 8.867H12.2a4.575 4.575 0 0 0-4.575 4.575v23.892a4.575 4.575 0 0 0 4.575 4.575h36.598a4.575 4.575 0 0 0 4.575-4.575V24.907a4.575 4.575 0 0 0-4.575-4.575zm-25.335 0c.116-2.433.5-4.901 1.606-6.892.682-1.227 1.637-2.266 3.01-3.015 1.385-.755 3.31-1.276 6.015-1.276s4.63.52 6.016 1.276c1.372.749 2.327 1.788 3.009 3.015 1.105 1.99 1.49 4.459 1.606 6.892zm-2.075 4.067H12.2a.51.51 0 0 0-.508.508v23.892c0 .28.227.508.508.508h36.598c.28 0 .508-.228.508-.508V24.907a.51.51 0 0 0-.508-.508H17.792" | ||
| clipRule="evenodd" | ||
| /> | ||
| <path | ||
| fill="#F1F3F5" | ||
| d="M35.073 35.075a4.58 4.58 0 0 1-2.538 4.097v4.545a2.033 2.033 0 0 1-4.066 0v-4.54a4.575 4.575 0 1 1 6.604-4.102" | ||
| /> | ||
| </svg> | ||
| ); | ||
| export default SvgCertified; |
Uh oh!
There was an error while loading. Please reload this page.