-
Notifications
You must be signed in to change notification settings - Fork 0
[URECA-76] Feat: 추천 페이지 구현 #75
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
Changes from all commits
394c8e5
863e3b9
cc86f36
e0f058d
249bf7f
34f6874
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 | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,27 +1,43 @@ | ||||||||||||||||||||||||||||||||||||||||
| 'use client'; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| import React from 'react'; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| import { BottomNav } from '@/components/layout/bottom-navigation'; | ||||||||||||||||||||||||||||||||||||||||
| import { Header } from '@/components/layout/header'; | ||||||||||||||||||||||||||||||||||||||||
| import { Loading } from '@/components/loading'; | ||||||||||||||||||||||||||||||||||||||||
| import { ProductList } from '@/components/recommend/product-list'; | ||||||||||||||||||||||||||||||||||||||||
| import { | ||||||||||||||||||||||||||||||||||||||||
| dummyCategoriesApi, | ||||||||||||||||||||||||||||||||||||||||
| dummyProductsApi, | ||||||||||||||||||||||||||||||||||||||||
| } from '@/services/recommend/MockupRecommendApi.client'; | ||||||||||||||||||||||||||||||||||||||||
| import { useRecommendMe } from '@/hooks/recommend/useRecommendMe'; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const page = () => { | ||||||||||||||||||||||||||||||||||||||||
| const categories = dummyCategoriesApi; | ||||||||||||||||||||||||||||||||||||||||
| const products = dummyProductsApi; | ||||||||||||||||||||||||||||||||||||||||
| const { data, isLoading, isError, refetch } = useRecommendMe(); | ||||||||||||||||||||||||||||||||||||||||
| const categories = data?.categories ?? []; | ||||||||||||||||||||||||||||||||||||||||
| const products = data?.products ?? []; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||
| <div className="mx-auto w-full overflow-hidden"> | ||||||||||||||||||||||||||||||||||||||||
| <div className="mx-auto w-full overflow-hidden pb-10"> | ||||||||||||||||||||||||||||||||||||||||
| <Header /> | ||||||||||||||||||||||||||||||||||||||||
| {categories.map((i) => ( | ||||||||||||||||||||||||||||||||||||||||
| <ProductList | ||||||||||||||||||||||||||||||||||||||||
| key={i.category_id} | ||||||||||||||||||||||||||||||||||||||||
| category={i} | ||||||||||||||||||||||||||||||||||||||||
| products={products.filter((p) => p.category === i.code)} | ||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||
| {isLoading && ( | ||||||||||||||||||||||||||||||||||||||||
| <div className="mt-4"> | ||||||||||||||||||||||||||||||||||||||||
| <Loading /> | ||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||
| {isError && ( | ||||||||||||||||||||||||||||||||||||||||
| <div className="px-6 py-4 text-sm text-red-600"> | ||||||||||||||||||||||||||||||||||||||||
| Failed to load recommendations. | ||||||||||||||||||||||||||||||||||||||||
| <button type="button" className="ml-2 underline" onClick={() => refetch()}> | ||||||||||||||||||||||||||||||||||||||||
| Retry | ||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||
| {!isLoading && | ||||||||||||||||||||||||||||||||||||||||
| !isError && | ||||||||||||||||||||||||||||||||||||||||
| categories.map((i) => ( | ||||||||||||||||||||||||||||||||||||||||
| <ProductList | ||||||||||||||||||||||||||||||||||||||||
| key={i.id} | ||||||||||||||||||||||||||||||||||||||||
| category={i} | ||||||||||||||||||||||||||||||||||||||||
| products={products.filter((p) => p.categoryId === i.id)} | ||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+32
to
+39
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. 데이터가 비어 있을 때 빈 상태 UI를 추가해주세요. 🧭 제안 변경- {!isLoading &&
- !isError &&
- categories.map((i) => (
+ {!isLoading && !isError && categories.length === 0 && (
+ <div className="px-6 py-4 text-sm text-gray-400">추천 항목이 없습니다.</div>
+ )}
+ {!isLoading &&
+ !isError &&
+ categories.map((i) => (
<ProductList
key={i.id}
category={i}
products={products.filter((p) => p.categoryId === i.id)}
/>
))}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| ))} | ||||||||||||||||||||||||||||||||||||||||
| <BottomNav /> | ||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,11 +1,40 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { RecommendCardList } from '@/components/counseling-recommend/recommend-cardlist/RecommendCardList'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { TitleCard } from '@/components/counseling-recommend/titlecard/TitleCard'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 'use client'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useParams } from 'next/navigation'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { RecommendCardList } from '@/components/counseling-recommend/recommend-cardlist'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { TitleCard } from '@/components/counseling-recommend/title-card'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useRecommendSummary } from '@/hooks/recommend/useRecommendSummary'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import { useSummaryDetail } from '@/hooks/summary/useSummaryDetail'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const page = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const params = useParams(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const rawId = Array.isArray(params?.summaryId) ? params.summaryId[0] : params?.summaryId; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const summaryId = Number(rawId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const safeId = Number.isFinite(summaryId) ? summaryId : 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data: summaryData } = useSummaryDetail(safeId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { data: recommendData, isLoading, isError, refetch } = useRecommendSummary(safeId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const products = recommendData?.products ?? []; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const title = summaryData?.title ?? '추천 상품'; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+11
to
+20
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. 유효하지 않은 summaryId가 API 호출을 유발합니다.
✅ 제안 수정안 const rawId = Array.isArray(params?.summaryId) ? params.summaryId[0] : params?.summaryId;
const summaryId = Number(rawId);
- const safeId = Number.isFinite(summaryId) ? summaryId : 0;
+ const isValidId = Number.isInteger(summaryId) && summaryId > 0;
+ const safeId = isValidId ? summaryId : Number.NaN;
const { data: summaryData } = useSummaryDetail(safeId);
const { data: recommendData, isLoading, isError, refetch } = useRecommendSummary(safeId);
const products = recommendData?.products ?? [];
+
+ if (!isValidId) {
+ return <div className="mx-7.5 my-4 text-sm text-red-500">잘못된 접근입니다.</div>;
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="mx-7.5 my-4"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TitleCard title="5G 요금제 변경 상담" /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <RecommendCardList /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <TitleCard title={title} /> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {isLoading && <div className="mt-4 text-sm text-gray-400">추천 상품 불러오는 중...</div>} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {isError && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="mt-4 text-sm text-red-500"> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 추천 상품을 불러오지 못했습니다. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <button type="button" className="ml-2 underline" onClick={() => refetch()}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 다시 시도 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {!isLoading && !isError && products.length === 0 && ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="mt-4 text-sm text-gray-400">추천 상품이 없습니다.</div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+25
to
+35
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. 로딩/에러/빈 상태에 접근성 시그널이 부족합니다. 현재 메시지는 스크린 리더에 즉시 전달되지 않을 수 있습니다. 대안: ♿ 제안 수정안- {isLoading && <div className="mt-4 text-sm text-gray-400">추천 상품 불러오는 중...</div>}
+ {isLoading && (
+ <div className="mt-4 text-sm text-gray-400" role="status" aria-live="polite">
+ 추천 상품 불러오는 중...
+ </div>
+ )}
- {isError && (
- <div className="mt-4 text-sm text-red-500">
+ {isError && (
+ <div className="mt-4 text-sm text-red-500" role="alert" aria-live="assertive">
추천 상품을 불러오지 못했습니다.
<button type="button" className="ml-2 underline" onClick={() => refetch()}>
다시 시도
</button>
</div>
)}
- {!isLoading && !isError && products.length === 0 && (
- <div className="mt-4 text-sm text-gray-400">추천 상품이 없습니다.</div>
- )}
+ {!isLoading && !isError && products.length === 0 && (
+ <div className="mt-4 text-sm text-gray-400" role="status" aria-live="polite">
+ 추천 상품이 없습니다.
+ </div>
+ )}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {!isLoading && !isError && products.length > 0 && <RecommendCardList products={products} />} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,13 +5,15 @@ import Image from 'next/image'; | |||||||||||||||||||||||||||||||||||||||||||
| import { useRouter } from 'next/navigation'; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import { Bookmark } from 'lucide-react'; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import BoxText from "@/assets/icons/summary/BoxText.png"; | ||||||||||||||||||||||||||||||||||||||||||||
| import GoBack from '@/assets/icons/header/chevron-left.svg'; | ||||||||||||||||||||||||||||||||||||||||||||
| import File1 from '@/assets/icons/summary/File1.png'; | ||||||||||||||||||||||||||||||||||||||||||||
| import Profile from '@/assets/icons/summary/Profile.png'; | ||||||||||||||||||||||||||||||||||||||||||||
| import Topic from '@/assets/icons/summary/Topic.png'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { RecommendCardList } from '@/components/counseling-recommend/recommend-cardlist'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { BottomNav } from '@/components/layout/bottom-navigation'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { useMe } from '@/hooks/auth/useMe'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { useRecommendSummary } from '@/hooks/recommend/useRecommendSummary'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { useToggleBookmark } from '@/hooks/summary/useToggleBookmark'; | ||||||||||||||||||||||||||||||||||||||||||||
| import type { ApiSummaryDetail } from '@/types/summary/summary'; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -28,6 +30,13 @@ export const SummarySuccessPage = ({ data }: SummarySuccessPageProps) => { | |||||||||||||||||||||||||||||||||||||||||||
| const { summaryId, title, subject, keywords = [], points = [], createdAt, isBookmarked } = data; | ||||||||||||||||||||||||||||||||||||||||||||
| const [bookmarked, setBookmarked] = useState(isBookmarked); | ||||||||||||||||||||||||||||||||||||||||||||
| const { mutateAsync, isPending } = useToggleBookmark({ summaryId, userId }); | ||||||||||||||||||||||||||||||||||||||||||||
| const { | ||||||||||||||||||||||||||||||||||||||||||||
| data: recommendData, | ||||||||||||||||||||||||||||||||||||||||||||
| isLoading: recommendLoading, | ||||||||||||||||||||||||||||||||||||||||||||
| isError: recommendError, | ||||||||||||||||||||||||||||||||||||||||||||
| refetch: refetchRecommend, | ||||||||||||||||||||||||||||||||||||||||||||
| } = useRecommendSummary(summaryId); | ||||||||||||||||||||||||||||||||||||||||||||
| const recommendProducts = recommendData?.products ?? []; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const disabled = meLoading || meError || !Number.isFinite(userId) || isPending; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -131,6 +140,37 @@ export const SummarySuccessPage = ({ data }: SummarySuccessPageProps) => { | |||||||||||||||||||||||||||||||||||||||||||
| </ul> | ||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||
| <div className="mt-6 px-4"> | ||||||||||||||||||||||||||||||||||||||||||||
| <div className="mb-2 flex items-center gap-2"> | ||||||||||||||||||||||||||||||||||||||||||||
| <Image src={BoxText} alt="" width={15} height={15} /> | ||||||||||||||||||||||||||||||||||||||||||||
| <h2 className="text-sm font-semibold">추천 상품</h2> | ||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| {recommendLoading && ( | ||||||||||||||||||||||||||||||||||||||||||||
| <div className="rounded-2xl bg-white px-4 py-3 text-[13px] text-gray-400"> | ||||||||||||||||||||||||||||||||||||||||||||
| 추천 상품 불러오는 중... | ||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| {recommendError && ( | ||||||||||||||||||||||||||||||||||||||||||||
| <div className="rounded-2xl bg-white px-4 py-3 text-[13px] text-red-500"> | ||||||||||||||||||||||||||||||||||||||||||||
| 추천 상품을 불러오지 못했습니다. | ||||||||||||||||||||||||||||||||||||||||||||
| <button type="button" className="ml-2 underline" onClick={() => refetchRecommend()}> | ||||||||||||||||||||||||||||||||||||||||||||
| 다시 시도 | ||||||||||||||||||||||||||||||||||||||||||||
| </button> | ||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+155
to
+162
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. 에러 상태 UI 개선 필요
🛠️ 개선 제안 {recommendError && (
<div className="rounded-2xl bg-white px-4 py-3 text-[13px] text-red-500">
추천 상품을 불러오지 못했습니다.
- <button type="button" className="ml-2 underline" onClick={() => refetchRecommend()}>
+ <button
+ type="button"
+ className="ml-2 underline"
+ aria-label="추천 상품 다시 불러오기"
+ onClick={() => void refetchRecommend()}
+ >
다시 시도
</button>
</div>
)}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| {!recommendLoading && !recommendError && recommendProducts.length === 0 && ( | ||||||||||||||||||||||||||||||||||||||||||||
| <div className="rounded-2xl bg-white px-4 py-3 text-[13px] text-gray-400"> | ||||||||||||||||||||||||||||||||||||||||||||
| 추천 상품이 없습니다. | ||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| {!recommendLoading && !recommendError && recommendProducts.length > 0 && ( | ||||||||||||||||||||||||||||||||||||||||||||
| <RecommendCardList products={recommendProducts} /> | ||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| <BottomNav /> | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,38 +4,39 @@ import Link from 'next/link'; | |
| import Check from '@/assets/images/recommend/check.svg'; | ||
| import type { ProductProps } from '@/types/product/dto'; | ||
|
|
||
| export const RecommendCard = ({ | ||
| is_monthly, | ||
| product, | ||
| best, | ||
| }: { | ||
| is_monthly: boolean; | ||
| type RecommendCardProps = { | ||
| product: ProductProps; | ||
| best: string; | ||
| }) => { | ||
| return ( | ||
| <div className={`flex w-200 max-w-200 flex-col rounded-2xl ${best}`}> | ||
| <div className="mx-10 my-5"> | ||
| <div className="text-[14px] font-semibold">{product.name}</div> | ||
| <div className="text-primary-500 mb-2 text-[20px] font-bold"> | ||
| {is_monthly ? '월 ' : null} | ||
| {product.price.toLocaleString()}원{product.is_sale ? '부터' : null} | ||
| is_monthly: boolean; | ||
| }; | ||
|
Comment on lines
+7
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. 🧩 Analysis chain🏁 Script executed: # 1. is_monthly 사용처 전체 검색
rg -n "is_monthly" --type tsx --type ts
# 2. 해당 컴포넌트 파일의 전체 내용 확인
wc -l src/components/counseling-recommend/recommend-card/index.tsxRepository: U-nify/Unity-client Length of output: 171 🏁 Script executed: # 1. Search for is_monthly without type filter
rg "is_monthly" -n
# 2. Read the complete component file
cat -n src/components/counseling-recommend/recommend-card/index.tsxRepository: U-nify/Unity-client Length of output: 1885
컴포넌트의 구조적 문제입니다. 13줄의 구조 분해(destructuring)에서 이는 불필요한 props 규약이 유지되어 API가 혼란스러워지고, 향후 유지보수 시 개발자가 "이 prop이 뭐하는 건데?" 하며 불필요한 추적을 하게 됩니다. 제거 방법:
이렇게 정리하면 API 계약이 명확해지고 재사용성이 높아집니다. 🤖 Prompt for AI Agents |
||
|
|
||
| export const RecommendCard = ({ product, best }: RecommendCardProps) => { | ||
| const priceLabel = typeof product.price === 'number' ? product.price.toLocaleString() : null; | ||
|
|
||
| const content = ( | ||
| <div className={`grid w-[160px] rounded-2xl ${best} `}> | ||
| <div className="mx-4 my-4 grid gap-2"> | ||
| <div className="line-clamp-3 text-[14px] font-semibold break-keep">{product.name}</div> | ||
| <div className="text-primary-500 text-[20px] font-bold"> | ||
| {priceLabel ?? <span className="text-gray-400">TBD</span>} | ||
| </div> | ||
| <div className="grid gap-1"> | ||
| {product.content?.split(', ').map((text, idx) => ( | ||
| <div key={idx} className="grid grid-cols-[auto_1fr] items-start gap-1 text-sm"> | ||
| <Check aria-hidden="true" focusable="false" /> | ||
| <span>{text}</span> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| {product.content?.split(', ').map((text, idx) => ( | ||
| <div key={idx} className="flex items-center"> | ||
| <Check aria-hidden="true" focusable="false" /> | ||
| {text} | ||
| </div> | ||
| ))} | ||
| <Link | ||
| href={product.link} | ||
| target="_blank" | ||
| rel="noopener noreferrer" | ||
| className="hover:bg-gray-light border-gray-light mt-2 inline-flex w-full items-center justify-center rounded-md border bg-transparent px-4 py-2 text-sm text-black shadow-[0_0_2px_#eeeeee] transition-colors" | ||
| > | ||
| 보러가기 | ||
| </Link> | ||
| </div> | ||
| </div> | ||
| ); | ||
|
|
||
| return product.link ? ( | ||
| <Link href={product.link} target="_blank" rel="noopener noreferrer" className="block"> | ||
| {content} | ||
| </Link> | ||
| ) : ( | ||
| content | ||
| ); | ||
| }; | ||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import React from 'react'; | ||
|
|
||
| import type { ProductProps } from '@/types/product/dto'; | ||
|
|
||
| import { RecommendCard } from '../recommend-card'; | ||
|
|
||
| export const RecommendCardList = ({ products }: { products: ProductProps[] }) => { | ||
| return ( | ||
| <div className="mt-3 grid w-full grid-cols-3 justify-items-center gap-4 md:grid-cols-4 lg:grid-cols-5"> | ||
| {products.map((product, idx) => ( | ||
| <RecommendCard | ||
| key={product.productId} | ||
| is_monthly={true} | ||
| product={product} | ||
| best={idx === 0 ? 'border border-primary-500 bg-white' : 'border border-gray bg-white'} | ||
| /> | ||
| ))} | ||
| </div> | ||
| ); | ||
| }; |
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.
에러 상태 메시지 i18n + 접근성 알림을 추가해주세요.
현재 문구가 영어라 UI 언어와 불일치하고, 스크린리더 알림도 없습니다.
role="alert"/aria-live적용과 한글 문구를 권장합니다.♿ 제안 변경
📝 Committable suggestion