Conversation
📝 WalkthroughWalkthroughThis PR establishes a foundational design system with CSS tokens and typography utilities, introduces a main app layout structure with supporting pages, and adds an extensive library of reusable UI components across feature areas including book stories, profiles, news, search, settings, and home features. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @hongik-luke, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 이 PR은 애플리케이션의 핵심 UI 컴포넌트와 기본 레이아웃을 구축하는 데 중점을 둡니다. 다양한 페이지에 필요한 재사용 가능한 UI 요소들을 정의하고, 일관된 디자인 시스템을 위한 글로벌 스타일 및 타이포그래피를 설정했습니다. 이를 통해 향후 기능 개발의 기반을 마련하고 사용자 경험의 일관성을 확보하는 것을 목표로 합니다. Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
이 PR은 새로운 UI 컴포넌트를 대거 추가하고 애플리케이션의 기본 구조와 전역 스타일을 설정하는 중요한 변경사항을 담고 있습니다. 전반적인 구조는 좋지만, 몇 가지 개선점이 필요해 보입니다. home_bookclub.tsx에서 로직 및 스타일링과 관련된 몇 가지 심각한 문제를 발견했으며, button_without_img.tsx에서는 hover 효과 구현 방식에 대한 개선이 필요합니다. 또한 여러 컴포넌트(news_list, notification_element)에 걸쳐 코드 중복이 상당하여 리팩토링이 필요합니다. 그 외에도 하드코딩된 색상, 일관성 없는 이름 지정, 접근성 문제(대체 텍스트/레이블 누락) 등이 다수 발견되었습니다. 자세한 내용은 개별 코멘트를 참고해주세요.
| text-[13px] flex items-center justify-center gap-2" | ||
| > | ||
| <Image src="/icon_plus.svg" alt="icon_plus" width={24} height={24} /> | ||
| <span className="text-color-white Subhead_4_1">모임 생성하기</span> |
| > | ||
| {open ? ( | ||
| <div className="flex items-center justify-center gap-1"> | ||
| <span className="text-[color:var(--Gray_7)] Body_1_2 font-weight: 500">접기</span> |
There was a problem hiding this comment.
| key={group.id} | ||
| className="flex w-[288px] h-[52px] py-3 px-4 items-center rounded-lg bg-white" | ||
| > | ||
| <span className="text-[color:var(--Gray--7)] h-6 Subhead_4_1 "> |
|
|
||
| export default function HomeBookclub({ groups }: Props) { | ||
| const count = groups.length; | ||
| const isMany = count >= 5; |
There was a problem hiding this comment.
| "use client"; | ||
| import Image from "next/image"; | ||
|
|
||
| type NewsListProps = { | ||
| imageUrl: string; | ||
| title: string; | ||
| content: string; | ||
| date: string; // "2025-10-09" 같은 문자열 | ||
| className?: string; | ||
| }; | ||
|
|
||
| export default function Setting_NewsList({ | ||
| imageUrl, | ||
| title, | ||
| content, | ||
| date, | ||
| className = "", | ||
| }: NewsListProps) { | ||
| return ( | ||
| <div | ||
| className={[ | ||
| "inline-flex w-full max-w-[1000px] p-[20px] items-start", | ||
| "rounded-[8px] border border-[color:var(--Subbrown_4,#EAE5E2)] bg-white gap-6", | ||
| className, | ||
| ].join(" ")} | ||
| > | ||
| {/* left image */} | ||
| <div className="relative w-[100px] h-[145px] shrink-0 "> | ||
| <Image | ||
| src={imageUrl} | ||
| alt={title} | ||
| fill | ||
| sizes="100px" | ||
| className="object-cover" | ||
| /> | ||
| </div> | ||
|
|
||
| {/* middle + right */} | ||
| <div className="flex flex-1 min-w-0 items-start"> | ||
| {/* middle text */} | ||
| <div className="flex flex-col gap-[8px] min-w-0 flex-1"> | ||
| <p className="text-[#000] Subhead_3 truncate">{title}</p> | ||
| <p className="text-[color:var(--Gray_4,#8D8D8D)] Body_1_2 line-clamp-6"> | ||
| {content} | ||
| </p> | ||
| </div> | ||
|
|
||
| {/* 최소 120px 확보 + 날짜 오른쪽 고정 */} | ||
| <div className="shrink-0 min-w-[180px] text-right"> | ||
| <p className="text-[color:var(--Gray_3,#BBB)] Body_1_2">{date}</p> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
| <div className="flex flex-1 min-w-0 items-start"> | ||
| {/* middle text */} | ||
| <div className="flex flex-col gap-[8px] min-w-0 flex-1"> | ||
| <p className="text-[#000] Subhead_3 truncate">{title}</p> |
| authorNickname: string; | ||
| authorId: string | number; | ||
|
|
||
| profileImgSrc?: string; // 기본: "/profile.svg" |
| </div> | ||
| ) : ( | ||
| <div className="flex items-center justify-center gap-1"> | ||
| <span className="text-[color:var(--Gray_7)] Body_1_2 Body_1_2">전체보기</span> |
| border border-[color:var(--premary_2,#9A7A6B)] | ||
| bg-[color:var(--background,#F9F7F6)] | ||
| text-[color:var(--premary_2,#9A7A6B)] |
There was a problem hiding this comment.
CSS 변수 이름에 오타가 있습니다. premary_2가 아니라 primary_2가 올바른 변수명입니다. 이 오타로 인해 스타일이 올바르게 적용되지 않을 수 있습니다.
| border border-[color:var(--premary_2,#9A7A6B)] | |
| bg-[color:var(--background,#F9F7F6)] | |
| text-[color:var(--premary_2,#9A7A6B)] | |
| border border-[color:var(--primary_2,#9A7A6B)] | |
| bg-[color:var(--background,#F9F7F6)] | |
| text-[color:var(--primary_2,#9A7A6B)] |
| <p className="text-[#000] Subhead_3 truncate">{bookName}</p> | ||
| <p className="text-[#757575] Subhead_4_1 truncate">{author}</p> |
There was a problem hiding this comment.
색상 값이 하드코딩되어 있습니다. 디자인 시스템의 일관성을 유지하기 위해 globals.css에 정의된 CSS 변수(예: var(--Black), var(--Gray_5))를 사용해주세요.
| <p className="text-[#000] Subhead_3 truncate">{bookName}</p> | |
| <p className="text-[#757575] Subhead_4_1 truncate">{author}</p> | |
| <p className="text-[color:var(--Black)] Subhead_3 truncate">{bookName}</p> | |
| <p className="text-[color:var(--Gray_5)] Subhead_4_1 truncate">{author}</p> |
There was a problem hiding this comment.
Actionable comments posted: 11
Note
Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.
♻️ Duplicate comments (2)
src/components/base-ui/home/notification_element.tsx (1)
1-47: LGTM!The component logic is correct. The conditional rendering for the latest notification indicator and the message construction based on type are well-implemented. Note: duplication with
Profile/notification_element.tsxwas flagged in the other file's review.src/components/base-ui/News/news_list.tsx (1)
1-55: Code duplication with Setting_NewsList component.This component is nearly identical to
src/components/base-ui/Settings/setting_news_list.tsx. The duplication concern and refactoring recommendation have been documented in the review of that file.
🟡 Minor comments (10)
src/components/base-ui/home/home_bookclub.tsx-72-72 (1)
72-72: Remove duplicate class.
Body_1_2is specified twice.🔎 Proposed fix
- <span className="text-[color:var(--Gray_7)] Body_1_2 Body_1_2">전체보기</span> + <span className="text-[color:var(--Gray_7)] Body_1_2">전체보기</span>src/components/base-ui/home/home_bookclub.tsx-49-51 (1)
49-51: Fix CSS variable name typo.
--Gray--7(double dash) should be--Gray_7(underscore) to match the design system convention used elsewhere.🔎 Proposed fix
- <span className="text-[color:var(--Gray--7)] h-6 Subhead_4_1 "> + <span className="text-[color:var(--Gray_7)] h-6 Subhead_4_1">src/components/base-ui/home/home_bookclub.tsx-67-68 (1)
67-68: Invalid CSS syntax in className.
font-weight: 500is CSS syntax, not a valid Tailwind class. Usefont-mediuminstead.🔎 Proposed fix
- <span className="text-[color:var(--Gray_7)] Body_1_2 font-weight: 500">접기</span> + <span className="text-[color:var(--Gray_7)] Body_1_2 font-medium">접기</span>src/components/base-ui/home/home_bookclub.tsx-63-63 (1)
63-63: Fix CSS variable name typo.
--Gray--3should be--Gray_3to match the design system convention.🔎 Proposed fix
- className="w-full h-[38px] rounded-[6px] bg-transparent text-[13px] flex items-center justify-center gap-[6px] text-[color:var(--Gray--3)]" + className="w-full h-[38px] rounded-[6px] bg-transparent text-[13px] flex items-center justify-center gap-[6px] text-[color:var(--Gray_3)]"src/components/base-ui/home/list_subscribe_element.tsx-24-32 (1)
24-32: Fixsizesprop mismatch.The container is
32x32butsizes="42px". These should match for optimal image loading.🔎 Proposed fix
- sizes="42px" + sizes="32px"src/components/base-ui/home/home_bookclub.tsx-32-33 (1)
32-33: Fix image path and use Next.js Image component.The
srcis missing a leading/and uses a native<img>instead of Next.jsImage, which is inconsistent with the rest of the component.🔎 Proposed fix
- <img src="logo2.svg" alt= "로고" className="mx-auto mb-4 mt-[118px]" /> + <Image src="/logo2.svg" alt="로고" width={100} height={100} className="mx-auto mb-4 mt-[118px]" />Committable suggestion skipped: line range outside the PR's diff.
src/components/base-ui/Profile/mypage_profile.tsx-7-7 (1)
7-7: Update comment to match implementation.The comment states the default is
/profile.svg, but line 25 uses/profile3.svgas the default value.🔎 Fix comment
- profileImgSrc?: string; // default: /profile.svg + profileImgSrc?: string; // default: /profile3.svgsrc/components/base-ui/Profile/mypage_profile.tsx-6-22 (1)
6-22: Export the MypageProfileProps type.The type is not exported, making it unavailable to consumers who might need it for type-safe prop passing or composition. This is inconsistent with the PR summary, which indicates the type should be part of the public API surface.
🔎 Add export keyword
-type MypageProfileProps = { +export type MypageProfileProps = { profileImgSrc?: string; // default: /profile.svg name: string;src/components/base-ui/Search/search_bookresult.tsx-55-55 (1)
55-55: Fix typo:flex1should beflex-1.The className
flex1is not a valid Tailwind utility. This should beflex-1to apply the flexbox grow property.🔎 Proposed fix
- <p className="flex1 h-full text-[color:var(--Gray_4,#8D8D8D)] Body_1_2 line-clamp-6"> + <p className="flex-1 h-full text-[color:var(--Gray_4,#8D8D8D)] Body_1_2 line-clamp-6"> {clippedDetail} </p>src/components/base-ui/Search/search_recommendbook.tsx-35-35 (1)
35-35: Imagesizesattribute doesn't match container dimensions.The container width is
332px, but thesizesattribute is set to"244px". This mismatch may cause Next.js to fetch a suboptimally-sized image, affecting performance and quality.🔎 Suggested fix
- <Image src={coverSrc} alt={title} fill sizes="244px" className="object-cover" /> + <Image src={coverSrc} alt={title} fill sizes="332px" className="object-cover" />Alternatively, if the image area is intended to be smaller due to padding or other factors, adjust the sizes value to match the actual rendered width.
🧹 Nitpick comments (21)
src/components/base-ui/Profile/notification_element.tsx (1)
1-47: Consolidate duplicate notification components.This component is nearly identical to
src/components/base-ui/home/notification_element.tsx. Both define the same types (NotificationType,NotificationElementProps), the same logic, and the same rendering. Consider extracting a single sharedNotificationElementcomponent (e.g., in acommon/orshared/folder) and importing it where needed, rather than maintaining two copies.src/components/base-ui/Profile/others_profile.tsx (2)
17-17: Remove unusedonSettingsprop.
onSettingsis declared in the type but never destructured or used in the component.🔎 Proposed fix
- onSettings?: () => void;
100-103: Consider guarding optional callback.
onReportClickis optional, but unlikeonToggleSubscribe, it's passed directly toonClick. For consistency and to prevent potential issues, consider using optional chaining.🔎 Proposed fix
- onClick={onReportClick} + onClick={() => onReportClick?.()}src/components/base-ui/home/list_subscribe_element.tsx (1)
49-49: Remove redundant color classes.
text-whiteandtext-[color:var(--White,#FFF)]are redundant; keep one for consistency with the design system.🔎 Proposed fix
- className="flex px-[17px] py-[8px] justify-center items-center gap-[10px] rounded-[8px] bg-[#9A7A6B] text-white text-[color:var(--White,#FFF)] text-[12px] font-semibold leading-[100%] tracking-[-0.012px] whitespace-nowrap" + className="flex px-[17px] py-[8px] justify-center items-center gap-[10px] rounded-[8px] bg-[color:var(--primary_2,#9A7A6B)] text-[color:var(--White,#FFF)] text-[12px] font-semibold leading-[100%] tracking-[-0.012px] whitespace-nowrap"src/components/base-ui/BookStory/bookstory_text.tsx (3)
20-27: Consider a flicker-free approach for textarea auto-resize.Setting
heightto"0px"forces a reflow and can cause a visible flash as the textarea shrinks momentarily before expanding toscrollHeight. Consider usingheight: "auto"instead or maintaining a minimum height to reduce this flicker.🔎 Alternative approach to reduce flicker
useLayoutEffect(() => { const el = textareaRef.current; if (!el) return; - el.style.height = "0px"; // 먼저 줄여서 scrollHeight 정확히 계산 + el.style.height = "auto"; // Avoids visible shrinking el.style.height = `${el.scrollHeight}px`; }, [detail]);
82-82: Remove redundantrowsattribute.Since the textarea height is dynamically controlled via the
useLayoutEffecthook, therows={6}attribute has no practical effect and can be removed for clarity.🔎 Suggested cleanup
<textarea ref={textareaRef} value={detail} onChange={(e) => onChangeDetail(e.target.value)} onKeyDown={handleDetailKeyDown} placeholder="내용을 자유롭게 입력해주세요." - rows={6} className=" w-full resize-none bg-transparent outline-none text-[color:var(--Gray_7,#2C2C2C)] Subhead_4_1 placeholder:text-[color:var(--Gray_3,#BBB)] whitespace-pre-wrap " />
5-10: Consider usingexport typefor type-only exports.For better tree-shaking and explicit intent, use the
export typesyntax when exporting type definitions.🔎 TypeScript best practice
-type BookstoryTextProps = { +export type BookstoryTextProps = { title: string; detail: string; onChangeTitle: (v: string) => void; onChangeDetail: (v: string) => void; };src/components/base-ui/BookStory/bookstory_choosebook.tsx (1)
13-49: Consider simplifying CSS variable syntax for Tailwind v4.The component structure and Next.js Image usage are solid. However, Tailwind CSS v4 introduces a cleaner syntax for CSS variables: you can use
border-(--Subbrown_4)andtext-(--Gray_4)instead ofborder-[color:var(--Subbrown_4)]andtext-[color:var(--Gray_4)]. The current syntax still works but is more verbose than necessary.Optional: Tailwind v4 CSS variable syntax
- <div className={`w-full max-w-[1040px] flex items-center p-[20px] ${className} bg-white border border-[color:var(--Subbrown_4)] rounded-[8px]`}> + <div className={`w-full max-w-[1040px] flex items-center p-[20px] ${className} bg-white border border-(--Subbrown_4) rounded-[8px]`}>- <p className="mt-[12px] text-[color:var(--Gray_4,#8D8D8D)] Body_1_2 line-clamp-4 overflow-hidden"> + <p className="mt-[12px] text-(--Gray_4) Body_1_2 line-clamp-4 overflow-hidden">Note: If you want to keep the fallback color
#8D8D8D, you can define it in your@themedirective or keep the current syntax.src/components/base-ui/Profile/mypage_profile.tsx (3)
38-42: Consider simplifying className concatenation.The array-join pattern for className composition is verbose. Template literals or a utility like
clsx/cnwould be more concise and maintainable.Alternative approaches
Using template literal:
className={`w-[734px] h-[244px] flex flex-col ${className}`}Or with clsx (if available in the project):
className={clsx("w-[734px] h-[244px] flex flex-col", className)}
60-60: Consider extracting hard-coded strings for internationalization.Multiple UI strings are hard-coded in Korean ("구독중", "구독자", "프로필 편집", "책 이야기 쓰기", "소식 문의하기"). While this may be acceptable for the current scope, consider extracting these strings to support future internationalization or easier text updates.
Also applies to: 66-66, 94-94, 123-123, 138-138
111-139: Consider extracting shared button styles.The two action buttons have identical styling with only content and click handlers differing. Consider extracting to a shared component or constant, or leveraging the
ButtonWithoutImgcomponent mentioned in the PR description.Example refactor
const actionButtonClass = ` flex w-[355px] h-[48px] px-[16px] py-[12px] justify-center items-center gap-[10px] rounded-[8px] bg-[color:var(--Primary_1,#7B6154)] text-[color:var(--White,#FFF)] Subhead_4_1 whitespace-nowrap `; // Then use: <button onClick={onLeftButtonClick} className={actionButtonClass}> 책 이야기 쓰기 </button>src/components/base-ui/News/recommendbook_element.tsx (1)
5-15: Consider exporting the BookCoverCardProps type.The
BookCoverCardPropstype is not currently exported. If other components or tests need to reference this type externally, consider addingexportbeforetype.🔎 Proposed fix to export the type
-type BookCoverCardProps = { +export type BookCoverCardProps = {src/components/base-ui/BookStory/bookstory_detail.tsx (1)
7-24: Consider exporting the BookstoryDetailProps type.Similar to the News component, the
BookstoryDetailPropstype is not exported. If external components or tests need to reference this type, consider adding theexportkeyword.🔎 Proposed fix to export the type
-type BookstoryDetailProps = { +export type BookstoryDetailProps = {src/app/globals.css (1)
70-70: Consider separating truncate from Body_1.The
Body_1utility includestruncate, which forces all text using this class to be truncated. This may not be desirable in all use cases. Consider creating a separate variant (e.g.,Body_1_truncate) or removingtruncatefrom the base utility to allow more flexible usage.🔎 Proposed refactor
/* Body */ - .Body_1 { @apply text-[14px] font-semibold leading-[145%] tracking-[-0.014px] truncate; } + .Body_1 { @apply text-[14px] font-semibold leading-[145%] tracking-[-0.014px]; } .Body_1_1 { @apply text-[14px] font-semibold leading-[145%] tracking-[-0.014px]; }This aligns
Body_1with the naming pattern of other variants (Body_1_1,Body_1_2, etc.) whereBody_1would be the base and you can add truncate inline where needed:Body_1 truncate.src/app/(main)/page.tsx (1)
1-3: Placeholder implementation detected.The home page currently renders an empty div. Based on the PR objectives mentioning home UI components (home_bookclub, list_subscribe_element, notification_element), this page should eventually integrate those components to display the actual home page content.
Would you like me to open an issue to track the integration of home UI components into this page?
src/components/layout/NavItem.tsx (1)
13-16: Consider using a className utility for cleaner conditional styling.The array
.join(" ")pattern works but is verbose. Consider usingclsxor acnutility function for more maintainable conditional className composition.🔎 Refactor using clsx utility
First, install clsx if not already present:
npm install clsxThen refactor the component:
import Link from "next/link"; +import clsx from "clsx"; interface NavItemProps { href: string; label: string; active: boolean; } export function NavItem({ href, label, active }: NavItemProps) { return ( <Link href={href} - className={[ - "flex w-32 items-center justify-center gap-2.5 p-2.5", - active ? "border-b-2 border-white" : "border-b-2 border-transparent", - ].join(" ")} + className={clsx( + "flex w-32 items-center justify-center gap-2.5 p-2.5", + active ? "border-b-2 border-white" : "border-b-2 border-transparent" + )} > <span - className={[ - "text-center text-xl font-semibold leading-7", - active ? "text-white" : "text-gray-200", - ].join(" ")} + className={clsx( + "text-center text-xl font-semibold leading-7", + active ? "text-white" : "text-gray-200" + )} > {label} </span> </Link> ); }Also applies to: 19-22
src/components/base-ui/button_without_img.tsx (1)
52-77: Consider using CSS :hover instead of manual state.The manual hover state management with
useStatecauses re-renders on every mouse enter/leave event. Since you're already usingtransition-colors, you could simplify this with pure CSS using the:hoverpseudo-class and CSS variables.🔎 Alternative CSS-only hover approach
export default function ButtonWithoutImg({ text, onClick, bgColorVar = "--primary_2", borderColorVar = "--primary_2", textColorVar = "--White", hoverBgColorVar, hoverBorderColorVar, hoverTextColorVar, width = 132, height = 44, disabled = false, type = "button", className = "", }: ButtonWithoutImgProps) { - const [isHover, setIsHover] = useState(false); - - const bg = toCssColor(isHover && hoverBgColorVar ? hoverBgColorVar : bgColorVar); - const border = toCssColor(isHover && hoverBorderColorVar ? hoverBorderColorVar : borderColorVar); - const textColor = toCssColor(isHover && hoverTextColorVar ? hoverTextColorVar : textColorVar); + const bg = toCssColor(bgColorVar); + const border = toCssColor(borderColorVar); + const textColor = toCssColor(textColorVar); + + const hoverBg = hoverBgColorVar ? toCssColor(hoverBgColorVar) : undefined; + const hoverBorder = hoverBorderColorVar ? toCssColor(hoverBorderColorVar) : undefined; + const hoverText = hoverTextColorVar ? toCssColor(hoverTextColorVar) : undefined; return ( <button type={type} disabled={disabled} onClick={disabled ? undefined : onClick} - onMouseEnter={disabled ? undefined : () => setIsHover(true)} - onMouseLeave={disabled ? undefined : () => setIsHover(false)} style={{ + '--bg-color': bg, + '--border-color': border, + '--text-color': textColor, + '--hover-bg-color': hoverBg || bg, + '--hover-border-color': hoverBorder || border, + '--hover-text-color': hoverText || textColor, width, height, - backgroundColor: bg, - borderColor: border, - color: textColor, - }} + } as React.CSSProperties} className={[ "flex px-[16px] py-[12px] justify-center items-center gap-[10px]", "rounded-[8px] border", "Body_1_2 whitespace-nowrap", + "[background-color:var(--bg-color)] [border-color:var(--border-color)] [color:var(--text-color)]", + "hover:[background-color:var(--hover-bg-color)] hover:[border-color:var(--hover-border-color)] hover:[color:var(--hover-text-color)]", "transition-colors", disabled ? "opacity-50 cursor-not-allowed" : "cursor-pointer", className, ].join(" ")} > {text} </button> ); }src/components/base-ui/Settings/setting_report_list.tsx (2)
22-28: Consider responsive width instead of fixed pixel width.The fixed
w-[1000px]will cause horizontal overflow on smaller screens. Consider usingw-full max-w-[1000px]to allow the component to adapt to narrower viewports.🔎 Proposed fix
<div className={` - flex w-[1000px] p-[20px] items-start gap-[40px] + flex w-full max-w-[1000px] p-[20px] items-start gap-[40px] rounded-[8px] border border-[color:var(--Subbrown_4,#EAE5E2)] bg-[color:var(--White,#FFF)] ${className} `} >
46-46: Consider adding an avatar URL prop.The hard-coded
/profile5.svgavatar limits flexibility. Consider adding an optionalavatarUrlprop to allow customization per reporter.🔎 Proposed enhancement
Update the props type:
type SettingReportListProps = { badgeText: string; reporterName: string; reportedAt: string; content: string; + avatarUrl?: string; className?: string; };Update the component:
export default function Setting_ReportList({ badgeText, reporterName, reportedAt, content, + avatarUrl = "/profile5.svg", className = "", }: SettingReportListProps) { // ... - <Image src="/profile5.svg" alt="" width={24} height={24} /> + <Image src={avatarUrl} alt="" width={24} height={24} />src/components/base-ui/Search/search_recommendbook.tsx (2)
5-15: Consider exporting the type for better reusability.The
BookCoverCardPropstype is well-structured. However, exporting it would allow parent components, tests, or documentation to reference the type explicitly, improving type safety and developer experience.🔎 Optional: Export the type
-type BookCoverCardProps = { +export type BookCoverCardProps = { imgUrl?: string; title: string; author: string; liked: boolean; onLikeChange: (next: boolean) => void; onCardClick?: () => void; className?: string; };
38-47: Consider adding an accessible label to the like button.The like button functions correctly with proper event propagation handling. However, it lacks an
aria-labelor accessible text, which would improve screen reader support.🔎 Optional: Add aria-label for accessibility
<button type="button" + aria-label={liked ? "Unlike this book" : "Like this book"} onClick={(e) => { e.stopPropagation(); onLikeChange(!liked); }} className="w-[24px] h-[24px] shrink-0" > <Image src={liked ? "/red_heart.svg" : "/gray_heart.svg"} alt="" width={24} height={24} /> </button>
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
⛔ Files ignored due to path filters (20)
package-lock.jsonis excluded by!**/package-lock.jsonpublic/ArrowDown.svgis excluded by!**/*.svgpublic/ArrowTop.svgis excluded by!**/*.svgpublic/Edit_icon.svgis excluded by!**/*.svgpublic/Setting_icon.svgis excluded by!**/*.svgpublic/booksample.svgis excluded by!**/*.svgpublic/gray_heart.svgis excluded by!**/*.svgpublic/icon_plus.svgis excluded by!**/*.svgpublic/logo.svgis excluded by!**/*.svgpublic/logo2.svgis excluded by!**/*.svgpublic/notification.svgis excluded by!**/*.svgpublic/pencil_icon.svgis excluded by!**/*.svgpublic/plus.svgis excluded by!**/*.svgpublic/profile.svgis excluded by!**/*.svgpublic/profile2.svgis excluded by!**/*.svgpublic/profile3.svgis excluded by!**/*.svgpublic/profile4.svgis excluded by!**/*.svgpublic/profile5.svgis excluded by!**/*.svgpublic/red_heart.svgis excluded by!**/*.svgpublic/search.svgis excluded by!**/*.svg
📒 Files selected for processing (27)
.github/ISSUE_TEMPLATE/custom.mdsrc/app/(main)/layout.tsxsrc/app/(main)/page.tsxsrc/app/(main)/ui-test/page.tsxsrc/app/globals.csssrc/app/layout.tsxsrc/app/page.backup.tsxsrc/components/base-ui/BookStory/bookstory_choosebook.tsxsrc/components/base-ui/BookStory/bookstory_detail.tsxsrc/components/base-ui/BookStory/bookstory_text.tsxsrc/components/base-ui/News/news_list.tsxsrc/components/base-ui/News/recommendbook_element.tsxsrc/components/base-ui/Profile/mypage_profile.tsxsrc/components/base-ui/Profile/notification_element.tsxsrc/components/base-ui/Profile/others_profile.tsxsrc/components/base-ui/Profile/subscribe_element.tsxsrc/components/base-ui/Search/search_bookresult.tsxsrc/components/base-ui/Search/search_recommendbook.tsxsrc/components/base-ui/Settings/setting_news_list.tsxsrc/components/base-ui/Settings/setting_report_list.tsxsrc/components/base-ui/button_without_img.tsxsrc/components/base-ui/home/home_bookclub.tsxsrc/components/base-ui/home/list_subscribe_element.tsxsrc/components/base-ui/home/notification_element.tsxsrc/components/layout/Header.tsxsrc/components/layout/NavItem.tsxtsconfig.json
💤 Files with no reviewable changes (1)
- src/app/layout.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
src/app/(main)/layout.tsx (1)
src/components/layout/Header.tsx (1)
Header(15-97)
src/components/layout/Header.tsx (1)
src/components/layout/NavItem.tsx (1)
NavItem(9-28)
src/app/(main)/ui-test/page.tsx (1)
src/components/base-ui/Settings/setting_report_list.tsx (1)
Setting_ReportList(14-65)
🔇 Additional comments (28)
src/components/base-ui/Profile/subscribe_element.tsx (1)
1-55: LGTM!The component is well-structured with proper TypeScript types, safe optional callback invocation via
onToggleSubscribe?.(!isSubscribed), and correct usage of Next.jsImagewithfillandsizes. The conditional button styling based on subscription state is clear.src/components/base-ui/Profile/others_profile.tsx (1)
6-118: LGTM overall!The component structure is clean with proper TypeScript typing, correct Next.js
Imageusage withfillandsizes, and well-organized layout. The conditional styling for the subscribe button based onisSubscribedstate is well-implemented.src/components/base-ui/home/list_subscribe_element.tsx (1)
1-55: LGTM overall!The component is well-structured with proper TypeScript types and correct Next.js
Imageusage. The layout and styling are consistent with other subscribe-related components in the PR.src/components/base-ui/BookStory/bookstory_choosebook.tsx (3)
1-2: LGTM!Imports are correct. The explicit React import is harmless and works fine with React 19's JSX transform.
4-11: LGTM!The type definition is well-structured with appropriate field types and optional props for extensibility.
70-75: LGTM!JSX structure is correctly balanced with all opening and closing tags properly matched.
.github/ISSUE_TEMPLATE/custom.md (1)
5-27: LGTM!The issue template improvements add helpful guidance text and a new issue type option, making the template more user-friendly.
tsconfig.json (1)
38-39: LGTM!The additions of
.next/types/**/*.tsand.next/dev/types/**/*.tsproperly support Next.js 16's generated type declarations, ensuring full TypeScript coverage for the framework-generated types.src/components/base-ui/News/recommendbook_element.tsx (1)
26-56: LGTM!The component implementation follows Next.js best practices:
- Proper default fallback for missing images
- Correct use of
fillwith explicitsizesfor optimization- Event propagation correctly managed for nested click handlers
- Text overflow handled appropriately with truncate and min-w-0
src/components/base-ui/BookStory/bookstory_detail.tsx (1)
40-107: LGTM!The component follows Next.js and React best practices:
- Proper default values for optional props
- Correct Image usage with explicit parent dimensions and sizes
- Meaningful alt attributes for accessibility
- Proper text truncation with flex layout (min-w-0)
- Link component used appropriately for navigation
src/app/globals.css (2)
4-33: LGTM!The design token structure is well-organized with a comprehensive color palette. The custom property naming convention (using underscores) is consistent throughout the codebase and properly referenced by components.
52-52: LGTM!The font family update to Pretendard with proper fallbacks provides excellent Korean typography support while maintaining system font fallbacks.
src/components/layout/NavItem.tsx (1)
1-7: LGTM!The interface definition is clear and includes all necessary props for a navigation item component.
src/app/(main)/layout.tsx (1)
1-11: LGTM!The layout implementation is clean and follows Next.js App Router conventions. It properly integrates the Header component and wraps children in a semantic
<main>element.src/app/(main)/ui-test/page.tsx (1)
10-18: LGTM!The test page correctly demonstrates the
Setting_ReportListcomponent with intentionally long content to verify text wrapping and overflow behavior.src/components/layout/Header.tsx (4)
1-7: LGTM!The imports are appropriate, and the
"use client"directive is correctly placed since the component uses theusePathnamehook.
8-13: LGTM!The navigation structure is well-defined with clear labels and corresponding routes.
36-39: Active state logic works but has a potential edge case.The current logic uses
pathname.startsWith(item.href)for non-root routes. This could cause issues if you add nested or similarly-named routes in the future (e.g.,/groupsand/groups-adminwould both match when visiting/groups-admin).Consider whether you need more precise matching in the future. For now, this works with the current route structure.
24-91: All required image assets are present in the public directory (logo.svg,search.svg,notification.svg,profile.svg). No 404 errors will occur from these assets.src/components/base-ui/button_without_img.tsx (2)
25-31: LGTM: Color normalization helper.The
toCssColorhelper correctly handles multiple color formats (CSS variables,--prefixes, and raw values).
33-51: LGTM: Well-structured component props.The component signature is clear, type-safe, and provides sensible defaults for colors and dimensions.
src/components/base-ui/Search/search_bookresult.tsx (3)
44-45: Verify remotePatterns for external book cover images.The
imgUrlprop can contain external URLs. Ensure yournext.config.jsincludes appropriateremotePatternsfor book cover image sources (e.g., book API domains).
35-43: LGTM: Card click handling.The optional
onCardClickhandler and conditional cursor styling are implemented correctly.
61-91: LGTM: Button event handling with stopPropagation.The like and pencil buttons correctly use
stopPropagation()to prevent triggering the card'sonCardClickhandler. The optionalonPencilClickis safely handled with optional chaining.src/components/base-ui/Settings/setting_report_list.tsx (1)
14-20: LGTM: Clear component props definition.The props are well-documented with inline comments showing expected formats, and the default className is handled appropriately.
src/components/base-ui/Search/search_recommendbook.tsx (3)
1-4: LGTM!Correct setup for a client-side component with Next.js Image.
17-26: LGTM!Component signature and fallback logic are correct. The coverSrc resolution properly handles missing or empty imgUrl values.
49-52: LGTM!Text content rendering correctly follows the established CSS conventions (color tokens and typography classes) and includes proper overflow handling with truncate.
| "use client"; | ||
| import React from "react"; | ||
| import { useState } from "react"; | ||
| import Subscribe_elements from "@/components/base-ui/Profile/subscribe_element"; |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Remove unused import and state.
The Subscribe_elements import and the subs state variable are declared but never used in the component.
🔎 Remove unused code
"use client";
import React from "react";
-import { useState } from "react";
-import Subscribe_elements from "@/components/base-ui/Profile/subscribe_element";
import Setting_ReportList from "@/components/base-ui/Settings/setting_report_list";
export default function UiTestClient() {
- const [subs, setSubs] = useState(false);
-
return (
<div className="h-[2000px] bg-gray-100 p-8">If you plan to use these later for testing Subscribe_elements, keep them and add a comment explaining the future intent.
Also applies to: 8-8
🤖 Prompt for AI Agents
In src/app/(main)/ui-test/page.tsx around lines 4 and 8, the import
Subscribe_elements and the subs state variable are declared but unused; remove
the unused import line and the subs state declaration (or, if you intend to use
them later, keep both and add a clear comment explaining the future intent),
then run TypeScript/ESLint to ensure no remaining unused-symbol errors.
| .Headline_1 { @apply text-[48px] font-bold leading-[135%] tracking-[-0.1%]; } | ||
| .Headline_2 { @apply text-[36px] font-bold leading-[135%] tracking-[-0.1%]; } | ||
| .Headline_3 { @apply text-[32px] font-bold leading-[135%] tracking-[-0.1%]; } |
There was a problem hiding this comment.
Critical: Invalid tracking (letter-spacing) percentage values.
Lines 57-59 and 62-67 use tracking-[-0.1%], but the CSS letter-spacing property does not accept percentage values—only length units (px, em, rem, etc.) are valid. These declarations will be ignored by browsers, causing the intended letter-spacing to not apply.
🔎 Proposed fix
The value -0.1% likely intends -0.001em (which is roughly 0.1% of the font size). Update all instances:
/* Headline */
- .Headline_1 { @apply text-[48px] font-bold leading-[135%] tracking-[-0.1%]; }
- .Headline_2 { @apply text-[36px] font-bold leading-[135%] tracking-[-0.1%]; }
- .Headline_3 { @apply text-[32px] font-bold leading-[135%] tracking-[-0.1%]; }
+ .Headline_1 { @apply text-[48px] font-bold leading-[135%] tracking-[-0.001em]; }
+ .Headline_2 { @apply text-[36px] font-bold leading-[135%] tracking-[-0.001em]; }
+ .Headline_3 { @apply text-[32px] font-bold leading-[135%] tracking-[-0.001em]; }
/* Subhead */
- .Subhead_1 { @apply text-[24px] font-semibold leading-[135%] tracking-[-0.1%]; }
- .Subhead_2 { @apply text-[20px] font-semibold leading-[135%] tracking-[-0.1%]; }
- .Subhead_3_1 { @apply text-[18px] font-semibold leading-[135%] tracking-[-0.1%]; }
- .Subhead_3_2 { @apply text-[18px] font-medium leading-[135%] tracking-[-0.1%]; }
+ .Subhead_1 { @apply text-[24px] font-semibold leading-[135%] tracking-[-0.001em]; }
+ .Subhead_2 { @apply text-[20px] font-semibold leading-[135%] tracking-[-0.001em]; }
+ .Subhead_3_1 { @apply text-[18px] font-semibold leading-[135%] tracking-[-0.001em]; }
+ .Subhead_3_2 { @apply text-[18px] font-medium leading-[135%] tracking-[-0.001em]; }Alternatively, verify the intended value from your design specifications and use the correct length unit.
Also applies to: 62-67
🤖 Prompt for AI Agents
In src/app/globals.css around lines 57-59 (and also lines 62-67), the classes
use tracking-[-0.1%], but letter-spacing does not accept percentage values;
replace each tracking-[-0.1%] with a proper length unit (e.g.,
tracking-[-0.001em] or tracking-[-0.1px] depending on design) so the rule is
applied, and update all instances across the file; verify with the design spec
and adjust the chosen unit/value accordingly.
| <button | ||
| type="button" | ||
| onClick={onButtonClick} | ||
| className=" | ||
| flex w-[132px] h-[44px] | ||
| px-[16px] py-[12px] | ||
| justify-center items-center gap-[10px] | ||
| rounded-[8px] | ||
| border border-[color:var(--premary_2,#9A7A6B)] | ||
| bg-[color:var(--background,#F9F7F6)] | ||
| text-[color:var(--premary_2,#9A7A6B)] | ||
| Body_1_2 | ||
| whitespace-nowrap | ||
| " | ||
| > | ||
| 변경하기 | ||
| </button> |
There was a problem hiding this comment.
Fix typo in CSS variable name: premary → primary.
The button has a typo in the CSS variable name on lines 61 and 63: --premary_2 should be --primary_2. This will cause the variable lookup to fail and fall back to the hardcoded hex color, breaking design system consistency and potentially causing styling issues if the variable is later defined correctly elsewhere.
🔎 Proposed fix for the CSS variable typo
className="
flex w-[132px] h-[44px]
px-[16px] py-[12px]
justify-center items-center gap-[10px]
rounded-[8px]
- border border-[color:var(--premary_2,#9A7A6B)]
+ border border-[color:var(--primary_2,#9A7A6B)]
bg-[color:var(--background,#F9F7F6)]
- text-[color:var(--premary_2,#9A7A6B)]
+ text-[color:var(--primary_2,#9A7A6B)]
Body_1_2
whitespace-nowrap
"📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <button | |
| type="button" | |
| onClick={onButtonClick} | |
| className=" | |
| flex w-[132px] h-[44px] | |
| px-[16px] py-[12px] | |
| justify-center items-center gap-[10px] | |
| rounded-[8px] | |
| border border-[color:var(--premary_2,#9A7A6B)] | |
| bg-[color:var(--background,#F9F7F6)] | |
| text-[color:var(--premary_2,#9A7A6B)] | |
| Body_1_2 | |
| whitespace-nowrap | |
| " | |
| > | |
| 변경하기 | |
| </button> | |
| <button | |
| type="button" | |
| onClick={onButtonClick} | |
| className=" | |
| flex w-[132px] h-[44px] | |
| px-[16px] py-[12px] | |
| justify-center items-center gap-[10px] | |
| rounded-[8px] | |
| border border-[color:var(--primary_2,#9A7A6B)] | |
| bg-[color:var(--background,#F9F7F6)] | |
| text-[color:var(--primary_2,#9A7A6B)] | |
| Body_1_2 | |
| whitespace-nowrap | |
| " | |
| > | |
| 변경하기 | |
| </button> |
🤖 Prompt for AI Agents
In src/components/base-ui/BookStory/bookstory_choosebook.tsx around lines 53 to
69, the button's CSS variable name contains a typo: replace all occurrences of
--premary_2 with --primary_2 (both in the border and text color usages) so the
component uses the correct design-system variable; update the className strings
accordingly and ensure no other instances of the misspelled variable remain in
the file.
| const handleDetailKeyDown = useCallback( | ||
| (e: React.KeyboardEvent<HTMLTextAreaElement>) => { | ||
| if (e.key !== "Tab") return; | ||
|
|
||
| e.preventDefault(); | ||
|
|
||
| const el = e.currentTarget; | ||
| const start = el.selectionStart ?? 0; | ||
| const end = el.selectionEnd ?? 0; | ||
|
|
||
| const insert = " "; | ||
| const next = detail.slice(0, start) + insert + detail.slice(end); | ||
|
|
||
| onChangeDetail(next); | ||
|
|
||
| requestAnimationFrame(() => { | ||
| el.selectionStart = el.selectionEnd = start + insert.length; | ||
| }); | ||
| }, | ||
| [detail, onChangeDetail] | ||
| ); |
There was a problem hiding this comment.
Tab key handling with controlled input may be unreliable.
Using requestAnimationFrame to restore cursor position after calling onChangeDetail is fragile. When onChangeDetail updates parent state, React re-renders this component and updates the textarea value, which can reset the cursor position unpredictably—potentially before or after your requestAnimationFrame callback runs.
A more robust approach is to track the desired cursor position in a ref and apply it in useLayoutEffect after the detail value changes.
🔎 More reliable implementation
const textareaRef = useRef<HTMLTextAreaElement | null>(null);
+ const cursorPosRef = useRef<number | null>(null);
// ✅ 내용에 맞춰 textarea 높이 자동 조절
useLayoutEffect(() => {
const el = textareaRef.current;
if (!el) return;
el.style.height = "0px";
el.style.height = `${el.scrollHeight}px`;
+
+ // Restore cursor position if pending
+ if (cursorPosRef.current !== null) {
+ el.selectionStart = el.selectionEnd = cursorPosRef.current;
+ cursorPosRef.current = null;
+ }
}, [detail]);
// textarea에서 Tab을 "들여쓰기"로 처리
const handleDetailKeyDown = useCallback(
(e: React.KeyboardEvent<HTMLTextAreaElement>) => {
if (e.key !== "Tab") return;
e.preventDefault();
const el = e.currentTarget;
const start = el.selectionStart ?? 0;
const end = el.selectionEnd ?? 0;
const insert = " ";
const next = detail.slice(0, start) + insert + detail.slice(end);
+ // Store desired cursor position
+ cursorPosRef.current = start + insert.length;
onChangeDetail(next);
-
- requestAnimationFrame(() => {
- el.selectionStart = el.selectionEnd = start + insert.length;
- });
},
[detail, onChangeDetail]
);Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/components/base-ui/BookStory/bookstory_text.tsx around lines 30-50, the
Tab key handler uses requestAnimationFrame to restore the textarea cursor which
is fragile with controlled inputs; instead, store the desired cursor position in
a ref inside the handler (set a ref to start + insert.length), call
onChangeDetail as before, remove the requestAnimationFrame, and then in a
useLayoutEffect that watches the detail value, if the ref has a stored position
set the textarea selectionStart/selectionEnd to that value and clear the ref;
ensure the textarea element is referenced via a ref accessible to
useLayoutEffect and keep dependencies minimal to avoid extra renders.
| "use client"; | ||
|
|
||
| import React from "react"; | ||
| import Image from "next/image"; |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -maxdepth 2 -name "package.json" -type f | head -5Repository: checkmo2025/FE
Length of output: 72
🏁 Script executed:
cat package.json | grep -A 5 '"dependencies"' | head -20Repository: checkmo2025/FE
Length of output: 176
🏁 Script executed:
cat -n src/components/base-ui/Profile/mypage_profile.tsx | head -50Repository: checkmo2025/FE
Length of output: 1511
🌐 Web query:
Next.js 16.0.1 release date features compatibility
💡 Result:
-
Release date: Next.js 16.0.1 was published in late October 2025 (stable 16.0.1 listed 2025-10-28). [1][2]
-
Notable features / highlights in the Next.js 16.x line (present in 16.0.x):
- Cache Components / "use cache" + Partial Pre-Rendering improvements. [3]
- Turbopack stable and default bundler (big dev/build perf gains). [3]
- React upgrades (React 19.x integration and related new APIs). [3]
- Routing, caching API updates (new refresh/updateTag/revalidateTag semantics) and Build Adapters API (alpha). [3]
- Several breaking changes (async route params, image defaults, changes to App Router behavior). [3]
-
Compatibility / ecosystem notes:
- Many ecosystem packages needed updates for Next 16; some libraries (auth SDKs, adapters, etc.) reported peer-dependency conflicts with [email protected] — expect to need updated versions or to use --legacy-peer-deps temporarily. [2][4]
- Several security vulnerabilities affecting early 16.x releases were disclosed in Dec 2025; upgrading to later 16.x security patches (e.g., 16.0.7+ / 16.1.x) was recommended. If you run 16.0.1, check for patched 16.x releases. [5][1]
If you want, I can: (a) fetch the exact GitHub/npm release entries for v16.0.1, (b) list breaking-change upgrade steps, or (c) check a specific third‑party package for Next 16 compatibility.
🌐 Web query:
React 19.2.0 release date stability
💡 Result:
React 19.2.0 (stable) was released October 1, 2025. [1]
Notes on stability:
- The React team published 19.2 as a stable release on the official React blog. [1]
- Prior to the stable release there were multiple 19.2.0-canary builds published to npm (canary = prerelease). Use the stable tag ([email protected]) from the official release rather than canary builds for production. [2][3][4]
Upgrade Next.js from 16.0.1 to 16.0.7 or later due to security vulnerabilities.
React 19.2.0 is stable and all features used ("use client" directive, Image component with fill prop, React patterns) are fully supported in these versions. However, Next.js 16.0.1 has known security vulnerabilities disclosed in December 2025. Upgrade to Next.js 16.0.7 or 16.1.x to receive security patches. Also verify that ecosystem dependencies (auth SDKs, adapters, etc.) are compatible with Next.js 16, as some packages reported peer-dependency conflicts requiring --legacy-peer-deps or updated versions.
🤖 Prompt for AI Agents
In src/components/base-ui/Profile/mypage_profile.tsx (lines 1-4): the project is
using Next.js 16.0.1 which has known security vulnerabilities; update the
Next.js dependency in package.json to 16.0.7 or a later 16.x (or 16.1.x) safe
release, run a fresh install to update the lockfile (npm/yarn/pnpm), resolve or
update any peer-dependency conflicts for auth SDKs/adapters (upgrade those
packages or use appropriate install flags only as a last resort), run the test
suite and a local build to catch runtime/API changes, and commit the updated
package.json and lockfile once compatibility is confirmed.
| <div className="flex items-start"> | ||
| <div className="py-[9px] pr-[36px] shrink-0"> | ||
| <div className="relative w-[138px] h-[138px] overflow-hidden rounded-full"> | ||
| <Image src={profileImgSrc} alt={name} fill className="object-cover" sizes="138px" /> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, check for the existence of image assets referenced in the component
echo "=== Checking for image assets in public directory ==="
for asset in profile3.svg Edit_icon.svg setting_icon.svg; do
if fd -t f "^${asset}$" public 2>/dev/null; then
echo "✓ Found: $asset"
else
echo "✗ Missing: $asset"
fi
done
# Also check what files exist in the public directory for reference
echo ""
echo "=== All files in public directory ==="
fd -t f . public 2>/dev/null | head -20Repository: checkmo2025/FE
Length of output: 681
🏁 Script executed:
#!/bin/bash
# Inspect the file at the specified lines to see what images are referenced
echo "=== Checking lines 47, 92, 101 in mypage_profile.tsx ==="
sed -n '45,49p; 90,94p; 99,103p' src/components/base-ui/Profile/mypage_profile.tsxRepository: checkmo2025/FE
Length of output: 843
Fix filename case mismatch at line 101.
The image reference at line 101 uses /setting_icon.svg (lowercase), but the actual file in the public directory is Setting_icon.svg (capital S). On case-sensitive filesystems, this will cause the image to fail loading. Update the reference to /Setting_icon.svg to match the actual filename.
Line 92's reference to /Edit_icon.svg is correct. Line 47 uses a dynamic source variable and does not directly reference a static asset.
🤖 Prompt for AI Agents
In src/components/base-ui/Profile/mypage_profile.tsx around line 101, the image
src references "/setting_icon.svg" which mismatches the actual public file
"Setting_icon.svg" (capital S); update the import string to "/Setting_icon.svg"
so the filename case matches exactly on case-sensitive filesystems and leave the
"/Edit_icon.svg" and dynamic profile image usage unchanged.
| <div className="flex-1 min-w-0"> | ||
| <div className="flex items-start justify-between"> | ||
| <div className="min-w-0"> | ||
| <p className="text-[color:var(--Gray_7,#2C2C2C)] Subhead_1 truncate">{name}</p> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, let's look for CSS/SCSS files and check for these class definitions
echo "=== Searching for custom typography class definitions ==="
rg -t css -t scss "Subhead_1|Subhead_4_1|Subhead_4|Body_1_2" --no-heading -C 1
echo ""
echo "=== Checking for Tailwind config or alternative CSS approach ==="
fd -t f -e "\.config\." -e "globals\." -e "tailwind\." | head -20Repository: checkmo2025/FE
Length of output: 227
🏁 Script executed:
#!/bin/bash
# Let's examine the target file to see the usage pattern
echo "=== Examining mypage_profile.tsx ==="
head -100 src/components/base-ui/Profile/mypage_profile.tsx | tail -50
echo ""
echo "=== Checking specific lines (54, 59, 65, 75, 93) ==="
sed -n '54p;59p;65p;75p;93p' src/components/base-ui/Profile/mypage_profile.tsxRepository: checkmo2025/FE
Length of output: 2541
🏁 Script executed:
#!/bin/bash
# Search for where these classes might be defined more broadly
echo "=== Searching all files for class definitions ==="
rg "Subhead_1|Subhead_4_1|Subhead_4|Body_1_2" --no-heading -l | head -20Repository: checkmo2025/FE
Length of output: 958
🏁 Script executed:
#!/bin/bash
# Check the globals.css file
echo "=== Checking src/app/globals.css ==="
cat src/app/globals.cssRepository: checkmo2025/FE
Length of output: 2654
Add missing .Subhead_4 typography class definition.
Three custom typography classes are properly defined in src/app/globals.css (Subhead_1, Subhead_4_1, Body_1_2), but .Subhead_4 is missing. This class is used at line 93 but has no corresponding definition in the utilities layer. Add the definition to globals.css or replace with an existing class like Subhead_4_1.
🤖 Prompt for AI Agents
In src/components/base-ui/Profile/mypage_profile.tsx around line 54 (and note
.Subhead_4 is used at line 93), the component references a non-existent
.Subhead_4 typography class; add a .Subhead_4 rule to src/app/globals.css in the
utilities layer (mirroring the existing Subhead_4_1 styles) or replace the usage
in mypage_profile.tsx with the existing Subhead_4_1 class to restore consistent
typography.
| <button type="button" onClick={onSettings} className="w-[24px] h-[24px] "> | ||
| <Image src="/setting_icon.svg" alt="" width={24} height={24} className="" /> | ||
| </button> |
There was a problem hiding this comment.
Add accessible label to icon-only settings button.
The settings button contains only an icon with no text label or aria-label, making it inaccessible to screen reader users. Add an aria-label attribute to describe the button's purpose.
🔎 Add aria-label
- <button type="button" onClick={onSettings} className="w-[24px] h-[24px] ">
+ <button type="button" onClick={onSettings} aria-label="설정 열기" className="w-[24px] h-[24px] ">
<Image src="/setting_icon.svg" alt="" width={24} height={24} className="" />
</button>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <button type="button" onClick={onSettings} className="w-[24px] h-[24px] "> | |
| <Image src="/setting_icon.svg" alt="" width={24} height={24} className="" /> | |
| </button> | |
| <button type="button" onClick={onSettings} aria-label="설정 열기" className="w-[24px] h-[24px] "> | |
| <Image src="/setting_icon.svg" alt="" width={24} height={24} className="" /> | |
| </button> |
🤖 Prompt for AI Agents
In src/components/base-ui/Profile/mypage_profile.tsx around lines 100 to 102,
the settings button is icon-only and lacks an accessible name; add an aria-label
(e.g., aria-label="Open settings" or a localized equivalent) to the <button> so
screen readers can describe its purpose, ensuring the attribute is concise and
matches the button's action.
| "use client"; | ||
| import Image from "next/image"; | ||
|
|
||
| type NewsListProps = { | ||
| imageUrl: string; | ||
| title: string; | ||
| content: string; | ||
| date: string; // "2025-10-09" 같은 문자열 | ||
| className?: string; | ||
| }; | ||
|
|
||
| export default function Setting_NewsList({ | ||
| imageUrl, | ||
| title, | ||
| content, | ||
| date, | ||
| className = "", | ||
| }: NewsListProps) { | ||
| return ( | ||
| <div | ||
| className={[ | ||
| "inline-flex w-full max-w-[1000px] p-[20px] items-start", | ||
| "rounded-[8px] border border-[color:var(--Subbrown_4,#EAE5E2)] bg-white gap-6", | ||
| className, | ||
| ].join(" ")} | ||
| > | ||
| {/* left image */} | ||
| <div className="relative w-[100px] h-[145px] shrink-0 "> | ||
| <Image | ||
| src={imageUrl} | ||
| alt={title} | ||
| fill | ||
| sizes="100px" | ||
| className="object-cover" | ||
| /> | ||
| </div> | ||
|
|
||
| {/* middle + right */} | ||
| <div className="flex flex-1 min-w-0 items-start"> | ||
| {/* middle text */} | ||
| <div className="flex flex-col gap-[8px] min-w-0 flex-1"> | ||
| <p className="text-[#000] Subhead_3 truncate">{title}</p> | ||
| <p className="text-[color:var(--Gray_4,#8D8D8D)] Body_1_2 line-clamp-6"> | ||
| {content} | ||
| </p> | ||
| </div> | ||
|
|
||
| {/* 최소 120px 확보 + 날짜 오른쪽 고정 */} | ||
| <div className="shrink-0 min-w-[180px] text-right"> | ||
| <p className="text-[color:var(--Gray_3,#BBB)] Body_1_2">{date}</p> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Extract shared component to eliminate duplication.
This component is nearly identical to src/components/base-ui/News/news_list.tsx, differing only in max-w-[1000px] vs max-w-[1040px]. Consider extracting a shared base component that accepts maxWidth as a prop, then wrap it in domain-specific exports.
🔎 Proposed refactor to extract shared component
Create a new shared component:
// src/components/base-ui/shared/BaseNewsList.tsx
"use client";
import Image from "next/image";
type BaseNewsListProps = {
imageUrl: string;
title: string;
content: string;
date: string;
maxWidth?: string;
className?: string;
};
export default function BaseNewsList({
imageUrl,
title,
content,
date,
maxWidth = "1040px",
className = "",
}: BaseNewsListProps) {
return (
<div
className={[
`inline-flex w-full max-w-[${maxWidth}] p-[20px] items-start`,
"rounded-[8px] border border-[color:var(--Subbrown_4,#EAE5E2)] bg-white gap-6",
className,
].join(" ")}
>
{/* ... rest of implementation ... */}
</div>
);
}Then update this file:
-"use client";
-import Image from "next/image";
-
-type NewsListProps = {
- imageUrl: string;
- title: string;
- content: string;
- date: string;
- className?: string;
-};
-
-export default function Setting_NewsList({
- imageUrl,
- title,
- content,
- date,
- className = "",
-}: NewsListProps) {
- return (
- <div className={[/* ... */].join(" ")}>
- {/* ... */}
- </div>
- );
-}
+import BaseNewsList from "../shared/BaseNewsList";
+
+type NewsListProps = {
+ imageUrl: string;
+ title: string;
+ content: string;
+ date: string;
+ className?: string;
+};
+
+export default function Setting_NewsList(props: NewsListProps) {
+ return <BaseNewsList {...props} maxWidth="1000px" />;
+}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/components/base-ui/Settings/setting_news_list.tsx lines 1-55: the
component largely duplicates src/components/base-ui/News/news_list.tsx; extract
a shared BaseNewsList component (e.g.,
src/components/base-ui/shared/BaseNewsList.tsx) that contains the full markup
and accepts props for imageUrl, title, content, date and a maxWidthClass or
className prop to control the max-w utility (avoid building Tailwind classes
with string interpolation—accept the full class like "max-w-[1000px]" or
"max-w-[1040px]" from the caller). Replace this file with a thin wrapper that
imports BaseNewsList and renders it with maxWidthClass="max-w-[1000px]" (and
similarly update the other news_list to pass "max-w-[1040px]"), forwarding any
className and props unchanged.
| <Image | ||
| src={imageUrl} | ||
| alt={title} | ||
| fill | ||
| sizes="100px" | ||
| className="object-cover" | ||
| /> |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# Find next.config files in the repository
fd -e js -e ts -e mjs -e cjs 'next\.config'Repository: checkmo2025/FE
Length of output: 72
🏁 Script executed:
# Examine the component file to understand the context
cat -n src/components/base-ui/Settings/setting_news_list.tsx | head -60Repository: checkmo2025/FE
Length of output: 1845
🏁 Script executed:
cat -n next.config.tsRepository: checkmo2025/FE
Length of output: 269
Add remotePatterns configuration for external image URLs.
The Image component loads external URLs via the imageUrl prop, but next.config.ts lacks the required remotePatterns configuration. Without this, Next.js will throw a "hostname is not allowed" error at runtime.
Add remotePatterns to next.config.ts:
const nextConfig: NextConfig = {
reactCompiler: true,
images: {
remotePatterns: [
{
protocol: 'https',
hostname: '**', // Adjust to specific domains for production
},
],
},
};Replace the wildcard with specific hostnames you expect to load (e.g., example.com, cdn.example.com).
🤖 Prompt for AI Agents
In src/components/base-ui/Settings/setting_news_list.tsx lines 29-35 the Image
component uses external imageUrl values but next.config.ts lacks
images.remotePatterns, causing "hostname is not allowed" runtime errors; update
next.config.ts to add an images.remotePatterns array that allows the external
hosts (e.g., entries with protocol: 'https' and hostname: 'example.com' or
'cdn.example.com' or a '**' wildcard for dev), replace the wildcard with the
specific domains you expect for production, and restart the Next.js server so
the new image host rules take effect.
💡 To Reviewers
이 PR은 feat-10 작업입니다.
원본 레포에 push 권한이 없어서 fork에서 PR 올렸습니다
🔥 작업 내용 (가능한 구체적으로 작성해 주세요)
파일구조
[global css] 글씨체, 색 호출방법
button_without_img
각 페이지 별 생성한 ui
파일구조
src/components/base_ui
-> 하위
/home
/Auth
/Group-search
/Group-create
/BookStory
/News
/Search
/Profile
/Settings
/button_without_img.tsx
[global css] 글씨체, 색 호출방법
ex)
Subhead_4_1 ->
/* Subhead_4.1 /
font-family: "Pretendard Variable";
font-size: 18px;
font-style: normal;
font-weight: 500;
line-height: 135%; / 24.3px /
letter-spacing: -0.018px;
가 호출됨
글씨체 상위에 / Subhead_4.1 */ 이렇게 표기되어 있으니 호출하시고 색만 신경써주시면 됩니다.
호출 규칙
이름: 대소문자 포함 그대로
[.] 은 _로 대체
색 토큰은 Gray_7 같은 형식 ( ex text-[color:var(--Gray_7)]
button_without_img
이미지가 없을 시에 사용할 수 있는 버튼 폼
사용 예시 (비어있을 시 기본값이 들어감)
아무 내용 없을 시에 기본값이 들어감
// 아래 기본값 겸 사용방법
<ButtonWithoutImg
text="등록"
onClick={() => {} }
bgColorVar="--premary_2"
borderColorVar="--premary_2"
textColorVar="--White"
hoverBgColorVar="--premary_2"
hoverBorderColorVar="--premary_2"
hoverTextColorVar="--White"
width={132}
height={44}
/>
책모_홈화면
로그인/회원가입
개별로 만드는게 나아보여 생략하였습니다.
모임 검색 / 모임 생성
-> 제 쪽이라서 일단 보류했습니다.
책이야기
소식
검색
마이페이지&다른사람 프로필
notification_element
홈화면에 있는 것과 같습니다 고로 생략합니다 notification_element
subscribe
설정
setting_news_list
소식의 news_list와 max크기 빼곤 다르지 않습니다 고로 생략합니다.
setting_report_list
🤔 추후 작업 예정
모임 검색 / 모임 생성
📸 작업 결과 (스크린샷)
🔗 관련 이슈
Feat-10
Summary by CodeRabbit
Release Notes
New Features
Style
✏️ Tip: You can customize this high-level summary in your review settings.