Skip to content
Open
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"react-router-dom": "^7.11.0",
"react-spinners": "^0.17.0",
"swiper": "^12.0.3",
"tailwind-merge": "^3.5.0",
"tailwindcss": "^4.1.18",
"zod": "^4.2.1",
"zustand": "^5.0.10"
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

71 changes: 44 additions & 27 deletions src/components/common/Button.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,47 @@
import Pencil from "../../assets/icons/pencil-primary-500.svg";
import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from "react";
import { twMerge } from "tailwind-merge";

interface ButtonProps {
onClick?: () => void;
text: string;
type: "button" | "submit" | "reset";
disabled?: boolean;
bgColor?: "primary" | "white";
interface ButtonType extends ButtonHTMLAttributes<HTMLButtonElement> {
children: ReactNode;
size?: "Btn_L" | "Btn_M" | "Btn_S";
variant?: "primary" | "gray" | "transparent";
}
Comment on lines +4 to 8

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

sizevariant가 필수 속성으로 선언되어 있지만 기본값이 존재합니다.

인터페이스에서 sizevariant가 필수(? 없음)로 선언되어 있으나, 컴포넌트 내부에서 기본값("Btn_M", "primary")을 제공하고 있습니다. 이로 인해 TypeScript에서 props 전달 시 항상 명시해야 하는 것으로 인식됩니다.

🛠️ 제안 수정안
 interface ButtonType extends ButtonHTMLAttributes<HTMLButtonElement> {
   children: ReactNode;
-  size: "Btn_L" | "Btn_M" | "Btn_S";
-  variant: "primary" | "gray" | "transparent";
+  size?: "Btn_L" | "Btn_M" | "Btn_S";
+  variant?: "primary" | "gray" | "transparent";
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/common/Button.tsx` around lines 4 - 8, The ButtonType
interface declares size and variant as required even though the Button component
provides defaults ("Btn_M" and "primary"); update the ButtonType definition to
make size and variant optional (add ?), leaving the component's defaulting logic
in the Button component intact so callers need not always pass those props;
reference ButtonType and the Button component to locate the change.


export default function Button({
onClick,
text,
type,
disabled,
bgColor = "primary",
}: ButtonProps) {
return (
<button
className={`w-[343px] px-[10px] py-[16px] flex items-center justify-center gap-[10px] rounded-xl font-semibold text-[20px] leading-[22px] text-center cursor-pointer
${bgColor == "primary" ? "bg-primary-700 text-white" : "bg-white text-primary-500 border border-primary-500"}`}
onClick={onClick}
type={type}
disabled={disabled}
>
{bgColor === "white" && <img src={Pencil} />}
{text}
</button>
);
}
const SIZES = {
Btn_L: "px-[10px] py-[16px]",
Btn_S: "px-[24px] py-[6px]",
Btn_M: "px-[10px] py-[16px]", // padding을 className에 직접 지정 가능
};
const VARIANTS = {
primary: "bg-primary-700 text-white",
gray: "bg-gray-100",
transparent: "bg-transparent border-1 border-primary-700",
};

const Button = forwardRef<HTMLButtonElement, ButtonType>(
(
{ children, size = "Btn_L", variant = "primary", className, ...props },
ref,
) => {
const sizeClass = SIZES[size];
const variantClass = VARIANTS[variant];
const disabledClass = props.disabled ? "opacity-50 cursor-not-allowed" : "";
return (
<button
ref={ref}
className={twMerge(
"rounded-xl flex justify-center items-center transition-colors",
sizeClass,
variantClass,
disabledClass,
className,
)}
{...props}
>
Comment on lines +23 to +40

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Button 컴포넌트 전체 내용 확인 (라인 수 먼저 확인)
wc -l src/components/common/Button.tsx

Repository: Urisik-serv/URISIK_FRONTEND

Length of output: 106


🏁 Script executed:

# Button.tsx 파일 내용 확인
cat -n src/components/common/Button.tsx

Repository: Urisik-serv/URISIK_FRONTEND

Length of output: 1694


🏁 Script executed:

# Button 사용처 검색 - type 속성이 없는 경우
rg -nP --type=tsx '<Button\b(?![^>]*\btype=)' -g '!**/*.test.tsx' || echo "No matches found or pattern issue"

Repository: Urisik-serv/URISIK_FRONTEND

Length of output: 149


🏁 Script executed:

# form 내 Button 사용 패턴 확인
rg -nP --type=tsx '<form' -A 10 -B 2 -g '!**/*.test.tsx' | head -50

Repository: Urisik-serv/URISIK_FRONTEND

Length of output: 98


🏁 Script executed:

# Button 컴포넌트의 타입 정의 확인
rg -nP --type=tsx 'type.*Button.*=' -A 5 src/components/common/Button.tsx || rg -nP 'interface|type' src/components/common/Button.tsx | head -20

Repository: Urisik-serv/URISIK_FRONTEND

Length of output: 269


🏁 Script executed:

# Button 사용처 검색 (파일 패턴으로 필터링)
rg '<Button\b' --glob='*.tsx' --glob='*.ts' -g '!**/*.test.tsx' -g '!**/*.test.ts' | head -30

Repository: Urisik-serv/URISIK_FRONTEND

Length of output: 801


🏁 Script executed:

# form 내 Button 사용 패턴 확인
rg '<form' --glob='*.tsx' --glob='*.ts' -A 15 -g '!**/*.test.tsx' | head -60

Repository: Urisik-serv/URISIK_FRONTEND

Length of output: 2585


🏁 Script executed:

# Button 컴포넌트에서 type 속성 명시 여부 재확인
cat src/components/common/Button.tsx | grep -n 'type\|<button'

Repository: Urisik-serv/URISIK_FRONTEND

Length of output: 168


🏁 Script executed:

# Button 사용 시 type 속성이 명시된 경우와 미명시된 경우 비교
rg '<Button' --glob='*.tsx' -A 2 | head -50

Repository: Urisik-serv/URISIK_FRONTEND

Length of output: 2571


🏁 Script executed:

# ProfileDataForm.tsx 전체 내용 확인
cat -n src/components/profile/ProfileDataForm.tsx | head -100

Repository: Urisik-serv/URISIK_FRONTEND

Length of output: 3584


🏁 Script executed:

# Button이 form 내부에 있고 type 속성이 명시되었는지 확인
rg '<form|<Button|type=' src/components/profile/ProfileDataForm.tsx -n

Repository: Urisik-serv/URISIK_FRONTEND

Length of output: 406


🏁 Script executed:

# 모든 Button 사용처에서 type 속성 여부 확인
rg '<Button' --glob='*.tsx' -A 5 | grep -E '(<Button|type=)' | head -60

Repository: Urisik-serv/URISIK_FRONTEND

Length of output: 1560


🏁 Script executed:

# ProfileDataForm 전체 확인 - Button 주변 코드
sed -n '100,270p' src/components/profile/ProfileDataForm.tsx | cat -n

Repository: Urisik-serv/URISIK_FRONTEND

Length of output: 6687


🏁 Script executed:

# family-invite-page.tsx에서 Button 사용 확인
cat -n src/pages/family/family-invite-page.tsx | grep -A 5 -B 5 '<Button'

Repository: Urisik-serv/URISIK_FRONTEND

Length of output: 427


🏁 Script executed:

# family-invite-page.tsx 전체 구조 확인
wc -l src/pages/family/family-invite-page.tsx && head -80 src/pages/family/family-invite-page.tsx | tail -30

Repository: Urisik-serv/URISIK_FRONTEND

Length of output: 1144


🏁 Script executed:

# family-invite-page.tsx 전체 구조 파악 및 Button 주변 context 확인
cat -n src/pages/family/family-invite-page.tsx

Repository: Urisik-serv/URISIK_FRONTEND

Length of output: 4927


Button 컴포넌트에 기본 type="button" 기본값 추가 필요

현재 Button 컴포넌트는 <button> 요소에서 type 속성을 명시하지 않으므로, 폼 내부에서 사용될 경우 HTML 기본값인 type="submit"으로 동작하여 의도치 않은 폼 제출이 발생할 수 있습니다. 컴포넌트 props에서 type 기본값을 "button"으로 설정하는 것이 필요합니다.

제안 수정안
 const Button = forwardRef<HTMLButtonElement, ButtonType>(
   (
-    { children, size = "Btn_L", variant = "primary", className, ...props },
+    {
+      children,
+      size = "Btn_L",
+      variant = "primary",
+      className,
+      type = "button",
+      ...props
+    },
     ref,
   ) => {
@@
       <button
         ref={ref}
+        type={type}
         className={twMerge(
           "rounded-xl flex justify-center items-center transition-colors",
           sizeClass,
           variantClass,
           disabledClass,
           className,
         )}
         {...props}
       >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/common/Button.tsx` around lines 23 - 40, The Button component
currently omits an explicit button type so it defaults to "submit" inside forms;
update the component's props/defaults to set type = "button" by default (e.g.,
in the props destructuring where children, size, variant, className, ...props
are defined) and ensure the rendered <button> uses that type while still
allowing callers to override via props; also update the ButtonProps/type
annotation (if present) to include HTMLButtonElement types that allow "button" |
"submit" | "reset".

{children}
</button>
);
},
);
Button.displayName = "Button";
export default Button;
22 changes: 20 additions & 2 deletions src/components/mypage/GetDateRangeModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,15 @@ export default function GetDateRangeModal({
<DateBlock date={to} />
</div>
<div className="pt-[25px]">
<Button text={"적용"} type="button" onClick={handleFinalApply} />
<Button
size="Btn_L"
variant="primary"
type="button"
className="w-[343px]"
onClick={handleFinalApply}
>
<span className="text-xl font-semibold leading-[22px]">적용</span>
</Button>
</div>
</div>
</div>
Expand All @@ -163,7 +171,17 @@ export default function GetDateRangeModal({
onSelect={handleSelect}
/>
<div className="pt-4">
<Button text="선택" type="button" onClick={handleApplyDate} />
<Button
size="Btn_L"
variant="primary"
type="button"
className="w-[343px]"
onClick={handleApplyDate}
>
<span className="text-xl font-semibold leading-[22px]">
선택
</span>
</Button>
</div>
</div>
</div>
Expand Down
12 changes: 7 additions & 5 deletions src/components/onboarding/ThirdStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ export default function ThirdStep() {
</div>
<div className="pt-[50px] pb-8 flex justify-center">
<Button
text={"시작"}
size="Btn_L"
variant="primary"
onClick={() => navigate("/agreement")}
type="button"
onClick={() => {
navigate("../agreement");
}}
/>
className="w-full"
>
<span className="text-xl font-semibold leading-[22px]">시작</span>
</Button>
</div>
</div>
</>
Expand Down
10 changes: 8 additions & 2 deletions src/components/profile/ProfileDataForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -256,10 +256,16 @@ export default function ProfileDataForm({

<div className="pt-[5px] pb-10">
<Button
text={profileMutation.isPending ? "처리 중..." : "완료"}
size="Btn_L"
variant="primary"
type="submit"
className="w-[343px]"
disabled={profileMutation.isPending}
/>
>
<span className="text-xl font-semibold leading-[22px]">
{profileMutation.isPending ? "처리 중..." : "완료"}
</span>
</Button>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</div>
</form>
);
Expand Down
12 changes: 8 additions & 4 deletions src/pages/auth/terms-agreement-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,13 +122,17 @@ export default function TermsAgreementPage() {
/>
</div>
</div>
<div className="fixed bottom-0 left-1/2 -translate-x-1/2 flex justify-center pb-10">
<div className="flex justify-center pt-[159px]">
<Button
text={`다음`}
size="Btn_L"
variant="primary"
onClick={handleSubmit}
type="button"
className="w-[343px]"
disabled={!isValid || isAgreeing}
onClick={handleSubmit}
/>
>
<span className="text-xl font-semibold leading-[22px]">다음</span>
</Button>
</div>
</div>
);
Expand Down
10 changes: 9 additions & 1 deletion src/pages/family/allergies-search-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,15 @@ export default function AllergiesSearchPage() {
</div>
</div>
<div className="pt-[420px]">
<Button text={"완료"} type="submit" onClick={handleComplete} />
<Button
size="Btn_L"
variant="primary"
onClick={handleComplete}
type="button"
className="w-[343px]"
>
<span className="text-xl font-semibold leading-[22px]">완료</span>
</Button>
</div>
</div>
</div>
Expand Down
10 changes: 7 additions & 3 deletions src/pages/family/family-create-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,15 @@ export default function FamilyCreatePage() {
</div>
<div className="fixed bottom-0 left-1/2 -translate-x-1/2 pb-10">
<Button
text="다음"
type="submit"
size="Btn_L"
variant="primary"
onClick={handleSubmit}
type="submit"
className="w-[343px]"
disabled={isCreating}
/>
>
<span className="text-xl font-semibold leading-[22px]">다음</span>
</Button>
</div>
</div>
</div>
Expand Down
10 changes: 7 additions & 3 deletions src/pages/family/family-invite-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,17 @@ export default function FamilyInvitePage() {
</div>
<div className="pt-[156px]">
<Button
text="다음"
type="submit"
size="Btn_L"
variant="primary"
onClick={() => {
navigate("/");
}}
type="button"
className="w-[343px]"
disabled={isInviting}
/>
>
<span className="text-xl font-semibold leading-[22px]">다음</span>
</Button>
</div>
</div>
</div>
Expand Down
12 changes: 9 additions & 3 deletions src/pages/meal-plan/meal-plan-create-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,16 @@ const MealPlanCreatePage = () => {
</div>
<div className="fixed bottom-0 left-1/2 -translate-x-1/2 flex justify-center pb-10">
<Button
text="식단 생성"
size="Btn_L"
variant="primary"
type="button"
onClick={() => handleCreate()}
/>
className="w-[343px]"
onClick={handleCreate}
>
<span className="text-xl font-semibold leading-[22px]">
식단 생성
</span>
</Button>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
</div>
</div>
</>
Expand Down
18 changes: 9 additions & 9 deletions src/pages/meal-plan/meal-plan-edit-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -424,16 +424,16 @@ function BottomSheet({

<div className="fixed bottom-0 left-1/2 -translate-x-1/2 flex justify-center pb-10">
<Button
text={isOpen ? "바꾸기" : "수정완료"}
size="Btn_L"
variant="primary"
type="button"
onClick={() => {
if (isOpen) {
handleChange();
} else {
setIsModalOpen(true);
}
}}
/>
className="w-[343px]"
onClick={handleChange}
>
<span className="text-xl font-semibold leading-[22px]">
{isOpen ? "바꾸기" : "수정완료"}
</span>
</Button>
</div>
</div>
);
Expand Down
31 changes: 15 additions & 16 deletions src/pages/meal-plan/meal-plan-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -171,20 +171,15 @@ function TodayMealTab({ data }: { data: TodayMeal }) {
</div>
{!data.isReviewed && (
<div className="pt-6">
{!isDone ? (
<Button
type="button"
text="식사 완료"
onClick={() => setIsDone(true)}
/>
) : (
<Button
type="button"
text="리뷰 작성"
bgColor="white"
onClick={() => setIsOpen(true)}
/>
)}
<Button
onClick={isDone ? () => setIsOpen(true) : () => setIsDone(true)}
size="Btn_L"
variant="primary"
type="button"
className="w-[343px]"
>
{isDone ? "리뷰 작성" : "식사 완료"}
</Button>
Comment on lines +174 to +182

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

children 스타일링이 다른 Button 사용처와 일관되지 않습니다.

PR 내 다른 Button 사용처(meal-plan-create-page.tsx, meal-plan-edit-page.tsx)에서는 children을 <span className="text-xl font-semibold leading-[22px]">으로 감싸고 있지만, 여기서는 plain string을 직접 전달하고 있습니다. 일관된 스타일링을 위해 span으로 감싸는 것을 권장합니다.

♻️ 일관된 스타일링을 위한 수정 제안
          <Button
            onClick={isDone ? () => setIsOpen(true) : () => setIsDone(true)}
            size="Btn_L"
            variant="primary"
            type="button"
            className="w-[343px]"
          >
-            {isDone ? "리뷰 작성" : "식사 완료"}
+            <span className="text-xl font-semibold leading-[22px]">
+              {isDone ? "리뷰 작성" : "식사 완료"}
+            </span>
          </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.

Suggested change
<Button
onClick={isDone ? () => setIsOpen(true) : () => setIsDone(true)}
size="Btn_L"
variant="primary"
type="button"
className="w-[343px]"
>
{isDone ? "리뷰 작성" : "식사 완료"}
</Button>
<Button
onClick={isDone ? () => setIsOpen(true) : () => setIsDone(true)}
size="Btn_L"
variant="primary"
type="button"
className="w-[343px]"
>
<span className="text-xl font-semibold leading-[22px]">
{isDone ? "리뷰 작성" : "식사 완료"}
</span>
</Button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/pages/meal-plan/meal-plan-page.tsx` around lines 174 - 182, The Button
rendering in the meal plan page uses plain string children while other pages use
a styled span; update the Button (the JSX with onClick using isDone ? () =>
setIsOpen(true) : () => setIsDone(true)) so both children branches are wrapped
in <span className="text-xl font-semibold leading-[22px]"> to match
meal-plan-create-page.tsx and meal-plan-edit-page.tsx and ensure consistent
typography for the Button label.

</div>
)}
<div className="pt-11 pb-29">
Expand Down Expand Up @@ -409,8 +404,10 @@ function ReviewModal({ recipeId, onClick, type }: ReviewModalProps) {
</div>
<div className="w-full p-[10px] flex justify-center">
<Button
size="Btn_L"
variant="primary"
type="button"
text="등록"
className="w-[343px]"
onClick={() => {
const reviewData: createReview = {
recipeId,
Expand All @@ -421,7 +418,9 @@ function ReviewModal({ recipeId, onClick, type }: ReviewModalProps) {
}
handleSubmitReview(reviewData);
}}
/>
>
<span className="text-xl font-semibold leading-[22px]">등록</span>
</Button>
</div>
</div>
</div>
Expand Down
Loading