-
Notifications
You must be signed in to change notification settings - Fork 7
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
[실험실] 같은 레벨, 타입의 펫 한번에 팔기 기능 #248
Changes from all commits
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,36 @@ | ||
import React from 'react'; | ||
import { css } from '_panda/css'; | ||
import { Flex } from '_panda/jsx'; | ||
import { Button, Dialog } from '@gitanimals/ui-panda'; | ||
|
||
export function AlertDialog(props: { | ||
isOpen: boolean; | ||
onClose: () => void; | ||
title: string; | ||
description: React.ReactNode; | ||
}) { | ||
return ( | ||
<Dialog open={props.isOpen} onOpenChange={props.onClose}> | ||
<Dialog.Content> | ||
<Dialog.Title className={titleStyle}>{props.title}</Dialog.Title> | ||
<Dialog.Description className={descriptionStyle}>{props.description}</Dialog.Description> | ||
<Flex gap="8px" justifyContent="flex-end" width="100%"> | ||
<Button onClick={props.onClose} variant="primary" size="m"> | ||
Close | ||
</Button> | ||
</Flex> | ||
</Dialog.Content> | ||
</Dialog> | ||
); | ||
} | ||
const titleStyle = css({ | ||
textStyle: 'glyph20.regular', | ||
textAlign: 'left', | ||
}); | ||
|
||
const descriptionStyle = css({ | ||
textStyle: 'glyph16.regular', | ||
textAlign: 'left', | ||
color: 'white.white_75', | ||
width: '100%', | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import { css } from '_panda/css'; | ||
import { Flex } from '_panda/jsx'; | ||
import { Button, Dialog } from '@gitanimals/ui-panda'; | ||
|
||
export function ConfirmDialog(props: { | ||
isOpen: boolean; | ||
onClose: () => void; | ||
onConfirm: () => void; | ||
title: string; | ||
description: string; | ||
}) { | ||
return ( | ||
<Dialog open={props.isOpen} onOpenChange={props.onClose}> | ||
<Dialog.Content> | ||
<Dialog.Title className={titleStyle}>{props.title}</Dialog.Title> | ||
<Dialog.Description className={descriptionStyle}>{props.description}</Dialog.Description> | ||
<Flex gap="8px" justifyContent="flex-end" width="100%"> | ||
<Button onClick={props.onConfirm} variant="secondary" size="m"> | ||
Ok | ||
</Button> | ||
<Button onClick={props.onClose} variant="primary" size="m"> | ||
Close | ||
</Button> | ||
Comment on lines
+18
to
+23
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. 🛠️ Refactor suggestion 버튼 텍스트 현지화 및 UX 개선이 필요합니다
- <Button onClick={props.onConfirm} variant="secondary" size="m">
- Ok
- </Button>
- <Button onClick={props.onClose} variant="primary" size="m">
- Close
+ <Button onClick={props.onClose} variant="secondary" size="m">
+ 취소
+ </Button>
+ <Button onClick={props.onConfirm} variant="primary" size="m">
+ 확인
</Button>
|
||
</Flex> | ||
</Dialog.Content> | ||
</Dialog> | ||
); | ||
} | ||
const titleStyle = css({ | ||
textStyle: 'glyph20.regular', | ||
textAlign: 'left', | ||
}); | ||
|
||
const descriptionStyle = css({ | ||
textStyle: 'glyph16.regular', | ||
textAlign: 'left', | ||
color: 'white.white_75', | ||
width: '100%', | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { css } from '_panda/css'; | ||
|
||
export default function LaboratoryLayout({ children }: { children: React.ReactNode }) { | ||
return <div className={containerStyle}>{children}</div>; | ||
} | ||
const containerStyle = css({ | ||
minHeight: '100vh', | ||
height: 'fit-content', | ||
backgroundColor: '#019C5A', | ||
padding: '120px 200px', | ||
color: 'white.white_100', | ||
|
||
'@media (max-width: 1400px)': { | ||
padding: '120px 100px', | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { css } from '_panda/css'; | ||
|
||
import { Link } from '@/i18n/routing'; | ||
|
||
export default function LaboratoryPage() { | ||
return ( | ||
<div className={contentStyle}> | ||
<Card href="/laboratory/property-pet-sell"> | ||
<h2>레벨, 타입 같은 펫 한번에 팔기</h2> | ||
<p>펫 레벨, 타입 등 펫 속성을 선택하여 한번에 팔 수 있어요.</p> | ||
</Card> | ||
Comment on lines
+8
to
+11
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. 텍스트 현지화가 필요합니다 한글로 하드코딩된 텍스트를 현지화해야 합니다. 또한 접근성을 위한 aria-label 추가가 필요합니다. - <Card href="/laboratory/property-pet-sell">
- <h2>레벨, 타입 같은 펫 한번에 팔기</h2>
- <p>펫 레벨, 타입 등 펫 속성을 선택하여 한번에 팔 수 있어요.</p>
+ <Card
+ href="/laboratory/property-pet-sell"
+ aria-label={t('Laboratory.property-pet-sell.aria-label')}
+ >
+ <h2>{t('Laboratory.property-pet-sell.title')}</h2>
+ <p>{t('Laboratory.property-pet-sell.description')}</p>
</Card>
|
||
<Card href="#">Card</Card> | ||
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. 미완성된 카드 컴포넌트가 있습니다 개발 중인 카드 컴포넌트가 있습니다. 프로덕션 배포 전에 완성하거나 제거해야 합니다. - <Card href="#">Card</Card> |
||
</div> | ||
); | ||
} | ||
|
||
function Card({ children, href }: { children: React.ReactNode; href: string }) { | ||
return ( | ||
<Link href={href} className={cardStyle}> | ||
{children} | ||
</Link> | ||
); | ||
} | ||
|
||
const contentStyle = css({ | ||
display: 'grid', | ||
gridTemplateColumns: 'repeat(2, 1fr)', | ||
gap: '16px', | ||
}); | ||
|
||
const cardStyle = css({ | ||
background: 'white.white_10', | ||
backdropFilter: 'blur(7px)', | ||
borderRadius: '16px', | ||
p: 8, | ||
display: 'flex', | ||
gap: '10px', | ||
textStyle: 'glyph16.regular', | ||
color: 'white.white_100', | ||
flexDirection: 'column', | ||
|
||
'& h2 ': { | ||
textStyle: 'glyph24.bold', | ||
}, | ||
|
||
'& p': { | ||
textStyle: 'glyph16.regular', | ||
}, | ||
|
||
_hover: { | ||
background: 'white.white_20', | ||
transform: 'translateY(-2px)', | ||
boxShadow: '0 5px 10px rgba(0, 0, 0, 0.1)', | ||
transition: 'background 0.3s ease, transform 0.3s ease', | ||
}, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,165 @@ | ||
'use client'; | ||
|
||
import { memo, useMemo } from 'react'; | ||
import { useTranslations } from 'next-intl'; | ||
import { css, cx } from '_panda/css'; | ||
import { dropPets } from '@gitanimals/api/src/shop/dropPet'; | ||
import { userQueries } from '@gitanimals/react-query/src/user'; | ||
import { LevelBanner } from '@gitanimals/ui-panda'; | ||
import { wrap } from '@suspensive/react'; | ||
import { useSuspenseQuery } from '@tanstack/react-query'; | ||
|
||
import { useDialog } from '@/components/GlobalComponent/useDialog'; | ||
import { trackEvent } from '@/lib/analytics'; | ||
import { customScrollStyle } from '@/styles/scrollStyle'; | ||
import { useClientUser } from '@/utils/clientAuth'; | ||
import { getPersonaImage } from '@/utils/image'; | ||
|
||
export default function PropertyPetSellPage() { | ||
return ( | ||
<div> | ||
<PersonaList /> | ||
</div> | ||
); | ||
} | ||
|
||
interface PetItemType { | ||
ids: string[]; | ||
type: string; | ||
level: string; | ||
} | ||
|
||
const PersonaList = wrap | ||
.ErrorBoundary({ fallback: <div> </div> }) | ||
.Suspense({ fallback: <></> }) | ||
.on(function PersonaList() { | ||
const { name } = useClientUser(); | ||
const { data } = useSuspenseQuery(userQueries.allPersonasOptions(name)); | ||
|
||
const { showDialog } = useDialog(); | ||
|
||
const onPetClick = (ids: string[]) => { | ||
showDialog({ | ||
title: '펫 판매', | ||
description: `펫을 판매하시겠습니까? ${ids.length}마리를 판매합니다.`, | ||
onConfirm: () => onSell(ids), | ||
}); | ||
Comment on lines
+43
to
+46
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. 🛠️ Refactor suggestion 하드코딩된 문자열을 국제화 처리해주세요 현재 Also applies to: 58-68 |
||
}; | ||
|
||
const onSell = async (ids: string[]) => { | ||
const res = await dropPets({ personaIds: ids }); | ||
|
||
const totalPrice = res.success.reduce((acc, curr) => acc + curr.givenPoint, 0); | ||
|
||
trackEvent('laboratory', { | ||
type: '레벨, 타입 같은 펫 한번에 팔기', | ||
}); | ||
|
||
showDialog({ | ||
title: '펫 판매 완료', | ||
description: ( | ||
<div> | ||
<p> | ||
{res.success.length}마리 판매 완료, {res.errors.length}마리 판매 실패 | ||
</p> | ||
<p>총 판매 금액: {totalPrice}P</p> | ||
</div> | ||
), | ||
}); | ||
}; | ||
Comment on lines
+49
to
+69
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. 🛠️ Refactor suggestion API 호출 오류 처리를 추가해주세요
다음과 같이 코드를 수정해 보세요: const onSell = async (ids: string[]) => {
+ try {
const res = await dropPets({ personaIds: ids });
// ... 기존 코드 유지
+ } catch (error) {
+ // 오류 메시지 표시
+ showDialog({
+ title: '오류 발생',
+ description: '펫 판매 중 오류가 발생했습니다. 다시 시도해주세요.',
+ });
+ console.error(error);
+ }
};
|
||
|
||
// 레벨 오름차순 정렬 | ||
const petList = useMemo(() => { | ||
const petItemMap = new Map<string, PetItemType>(); | ||
|
||
data.personas.forEach((persona) => { | ||
const level = persona.level; | ||
const type = persona.type; | ||
const uniqueKey = `${type}-${level}`; | ||
const petItem = petItemMap.get(uniqueKey); | ||
if (petItem) { | ||
petItem.ids.push(persona.id); | ||
} else { | ||
petItemMap.set(uniqueKey, { ids: [persona.id], type, level }); | ||
} | ||
}); | ||
|
||
return Array.from(petItemMap.values()).sort((a, b) => Number(a.level) - Number(b.level)); | ||
}, [data.personas]); | ||
|
||
return ( | ||
<section className={sectionStyle}> | ||
<div className={flexOverflowStyle}> | ||
{petList.map((petItem) => ( | ||
<div key={petItem.type + petItem.level}> | ||
<MemoizedPersonaItem | ||
type={petItem.type} | ||
level={petItem.level} | ||
onClick={() => onPetClick(petItem.ids)} | ||
count={petItem.ids.length} | ||
/> | ||
</div> | ||
))} | ||
</div> | ||
</section> | ||
); | ||
}); | ||
|
||
const sectionStyle = css({ | ||
height: '100%', | ||
maxHeight: '100%', | ||
minHeight: '0', | ||
display: 'flex', | ||
flexDirection: 'column', | ||
gap: '16px', | ||
}); | ||
|
||
const flexOverflowStyle = cx( | ||
css({ | ||
display: 'flex', | ||
overflowY: 'auto', | ||
overflowX: 'hidden', | ||
width: '100%', | ||
gap: '12px 4px', | ||
height: '100%', | ||
minHeight: '0', | ||
flexWrap: 'wrap', | ||
maxHeight: 'calc(100% - 24px)', | ||
}), | ||
customScrollStyle, | ||
); | ||
|
||
interface PersonaItemProps { | ||
type: string; | ||
level: string; | ||
onClick: () => void; | ||
count: number; | ||
} | ||
|
||
function PersonaItem({ type, level, onClick, count }: PersonaItemProps) { | ||
const t = useTranslations('Laboratory.property-pet-sell'); | ||
return ( | ||
<button onClick={onClick} className={css({ outline: 'none', bg: 'transparent' })}> | ||
<div className={levelTagStyle}> | ||
{count} | ||
{t('count')} | ||
</div> | ||
<LevelBanner image={getPersonaImage(type)} level={Number(level)} size="small" /> | ||
</button> | ||
); | ||
} | ||
|
||
const MemoizedPersonaItem = memo(PersonaItem, (prev, next) => { | ||
return prev.type === next.type && prev.level === next.level; | ||
}); | ||
Comment on lines
+139
to
+154
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. 요건 인라인으로 붙여도 될 거 같은데, 따로 하신 이유가 모에용?? 궁금 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. 메모를 나눠돈 이유를 말하시는걸까요?! |
||
|
||
const levelTagStyle = css({ | ||
borderRadius: '4px', | ||
background: 'black.black_25', | ||
padding: '0 8px', | ||
color: 'white.white_75', | ||
textStyle: 'glyph16.bold', | ||
fontSize: '10px', | ||
lineHeight: '20px', | ||
mb: '4px', | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
영문 텍스트의 한글화가 필요합니다.
Laboratory 섹션의 텍스트가 한글로 번역되지 않았습니다.
다음과 같이 수정해주세요: