diff --git a/src/entities/character/config/character.ts b/src/entities/character/config/character.ts deleted file mode 100644 index 932953aa..00000000 --- a/src/entities/character/config/character.ts +++ /dev/null @@ -1,46 +0,0 @@ -export const CHARACTER_DATA = { - '천사 모또': { - imageSize: { - big: { width: '15rem' }, - small: { width: '12rem' }, - }, - description: '정산의 수호천사 등장!', - }, - '러키 모또': { - imageSize: { - big: { width: '12.5rem' }, - small: { width: '10rem' }, - }, - description: '정산 성공! 좋은 일만 가득하길~', - }, - '딸기 또또': { - imageSize: { - big: { width: '12.5rem' }, - small: { width: '10rem', height: '10rem' }, - }, - description: '정산 완료! 달콤한 하루 보내~', - }, - '잠꾸러기 또또': { - imageSize: { - big: { width: '12.5rem' }, - small: { width: '10rem' }, - }, - description: '정산 끝났어? 이제 푹 잘 수 있겠네~', - }, - '마법사 또또': { - imageSize: { - big: { height: '13.72844rem' }, - small: { height: '10.98275rem' }, - }, - description: '정산? 아브라카다브라! 해결 완료~', - }, -} satisfies Record< - string, - { - imageSize: { - big: { width?: string; height?: string }; - small: { width?: string; height?: string }; - }; - description: string; - } ->; diff --git a/src/entities/character/model/character.type.ts b/src/entities/character/model/character.type.ts index fad4f658..df7d3670 100644 --- a/src/entities/character/model/character.type.ts +++ b/src/entities/character/model/character.type.ts @@ -1,11 +1,7 @@ -import { CHARACTER_DATA } from '@/entities/character/config/character'; - -// 새 캐릭터 추가 시 CHARACTER_DATA에만 추가하면 타입이 자동으로 확장됨 -export type CharacterType = keyof typeof CHARACTER_DATA; export type StarCount = 1 | 2 | 3; export interface CharacterData { - name: CharacterType; + name: string; rarity: StarCount; imageUrl: string; imageBigUrl: string; diff --git a/src/features/character-management/ui/CharacterBottomSheet/index.styles.ts b/src/features/character-management/ui/CharacterBottomSheet/index.styles.ts index 2b19c3c4..b3c626fa 100644 --- a/src/features/character-management/ui/CharacterBottomSheet/index.styles.ts +++ b/src/features/character-management/ui/CharacterBottomSheet/index.styles.ts @@ -18,6 +18,12 @@ export const CharacterImageContainer = styled.div` height: 11.25rem; `; +export const CharacterImage = styled.img` + max-width: 100%; + max-height: 100%; + object-fit: contain; +`; + export const DescriptionContainer = styled.div` display: flex; flex-direction: column; diff --git a/src/features/character-management/ui/CharacterBottomSheet/index.tsx b/src/features/character-management/ui/CharacterBottomSheet/index.tsx index 996a6954..c7658c37 100644 --- a/src/features/character-management/ui/CharacterBottomSheet/index.tsx +++ b/src/features/character-management/ui/CharacterBottomSheet/index.tsx @@ -1,6 +1,5 @@ import { useNavigate, generatePath, useLoaderData } from 'react-router'; import { BottomSheet, ActionArea } from '@/shared/design-system/ui'; -import { CHARACTER_DATA } from '@/entities/character/config/character'; import { ROUTE } from '@/shared/config/route'; import useGetCharacter from '@/features/character-management/api/useGetCharacter'; import * as S from './index.styles'; @@ -30,13 +29,7 @@ function CharacterBottomSheet({ open, setOpen }: CharacterBottomSheetProps) { > - {data.name} + 두둥, {data.name} 등장! diff --git a/src/features/character-management/ui/CharacterItem/index.tsx b/src/features/character-management/ui/CharacterItem/index.tsx index 5257a6bb..9ec1a996 100644 --- a/src/features/character-management/ui/CharacterItem/index.tsx +++ b/src/features/character-management/ui/CharacterItem/index.tsx @@ -17,11 +17,11 @@ interface CharacterCardProps { } function CharacterCard({ character }: CharacterCardProps) { - const { imageUrl, name, acquiredAt } = character; + const { imageBigUrl, name, acquiredAt } = character; return ( - + {name} {acquiredAt ? format(acquiredAt, 'yyyy.MM.dd') : null} diff --git a/src/pages/characterShare/CharacterSharePage.styles.ts b/src/pages/characterShare/CharacterSharePage.styles.ts index e9366271..f32a7316 100644 --- a/src/pages/characterShare/CharacterSharePage.styles.ts +++ b/src/pages/characterShare/CharacterSharePage.styles.ts @@ -23,24 +23,10 @@ export const CharacterCardContainer = styled.div` display: flex; width: 100%; justify-content: center; - padding: ${getToken('padding.3')}; + padding: 0.5rem 2.5rem; background-color: ${getToken('bg.neutral')}; `; -export const CharacterCard = styled.div` - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - box-sizing: border-box; - width: 19.375rem; - height: 25rem; - flex-shrink: 0; - border-radius: ${getToken('radius.xl')}; - border: 1px solid ${getToken('border.normal')}; - background-color: ${getToken('bg.normal')}; -`; - export const EmptyStateTitle = styled.h1` ${applyTypography('typography.heading.medium')}; margin: 0; @@ -56,27 +42,13 @@ export const PageTitle = styled.h1` margin: 0; `; -export const CharacterName = styled.span` - ${applyTypography('typography.heading.small')}; - color: ${getToken('fg.normal')}; -`; - -export const CharacterDescription = styled.span` - ${applyTypography('typography.body.medium')}; - color: ${getToken('fg.alternative')}; -`; - export const DownloadButton = styled(TextButton)` margin-top: 0.75rem; margin-bottom: 1.25rem; `; -export const CharacterImageContainer = styled.div` - display: flex; - width: 15rem; - height: 14.25rem; - justify-content: center; - align-items: center; - flex-shrink: 0; - margin-top: 1.5rem; +export const CharacterImage = styled.img` + width: 100%; + max-width: 19.375rem; + height: auto; `; diff --git a/src/pages/characterShare/CharacterSharePage.tsx b/src/pages/characterShare/CharacterSharePage.tsx index 88a64d06..8a251d40 100644 --- a/src/pages/characterShare/CharacterSharePage.tsx +++ b/src/pages/characterShare/CharacterSharePage.tsx @@ -1,42 +1,31 @@ -import { useRef } from 'react'; -import { toPng } from 'html-to-image'; import saveAs from 'file-saver'; import { ActionArea, Header, showToast } from '@/shared/design-system/ui'; import { useLoaderData, useNavigate } from 'react-router'; import { ArrowLeft, Download } from '@/shared/assets/svgs/icon'; import { getToken } from '@/shared/design-system'; import { PageLayout } from '@/shared/ui/PageLayout'; -import { CHARACTER_DATA } from '@/entities/character/config/character'; -import { StarChip } from '@/features/character-management/ui'; import useGetCharacter from '@/features/character-management/api/useGetCharacter'; import * as S from './CharacterSharePage.styles'; +const sanitizeFilename = (name: string): string => { + return name.replace(/[/\\:*?"<>|]/g, '_'); +}; + function CharacterSharePage() { const { groupToken } = useLoaderData(); const { data, isLoading, isError } = useGetCharacter(groupToken); const navigate = useNavigate(); - const imageRef = useRef(null); - const handleDownload = () => { + const handleDownload = async () => { if (!data) return; - // 돔 요소를 이미지로 변환 - if (imageRef.current) { - // 390x390 사이즈로 이미지 다운로드 - toPng(imageRef.current, { width: 390, height: 390 }) - .then((dataUrl) => { - // 이미지 다운로드 - saveAs(dataUrl, `${data.name}.png`); - showToast({ - type: 'success', - content: '이미지 저장 완료!', - }); - }) - .catch(() => { - showToast({ - type: 'error', - content: '이미지 저장 실패!', - }); - }); + try { + const response = await fetch(data.imageUrl); + if (!response.ok) throw new Error(); + const blob = await response.blob(); + saveAs(blob, `${sanitizeFilename(data.name)}.png`); + showToast({ type: 'success', content: '이미지 저장 완료!' }); + } catch { + showToast({ type: 'error', content: '이미지 저장 실패!' }); } }; @@ -93,23 +82,8 @@ function CharacterSharePage() { 캐릭터를 획득했어요! - - - - - {data.name} - - {data.name} - - {CHARACTER_DATA[data.name].description} - - + +