Skip to content

Feat: 쿠폰 등록 화면 구현 및 API 연결 - #115

Open
minnngo wants to merge 5 commits into
developfrom
feature/JDDEV-73—coupon-ui-feat

Hidden character warning

The head ref may contain hidden characters: "feature/JDDEV-73\u2014coupon-ui-feat"
Open

Feat: 쿠폰 등록 화면 구현 및 API 연결#115
minnngo wants to merge 5 commits into
developfrom
feature/JDDEV-73—coupon-ui-feat

Conversation

@minnngo

@minnngo minnngo commented Jul 29, 2026

Copy link
Copy Markdown
Collaborator

🔗 관련 이슈

  • JDDEV-73

📝 개요

  • 크레딧 화면에서 쿠폰 코드를 등록하고 크레딧을 충전할 수 있는 플로우를 추가했습니다.
  • 쿠폰 등록 API 응답에 따라 오류 상태 또는 성공 토스트를 표시합니다.

⌨️ 작업 상세 내용

  • 크레딧 화면에 쿠폰 등록하기 버튼 추가
  • 쿠폰 코드 입력 모달 UI 및 열기·닫기 동작 구현
  • 모달 외부 클릭 및 Escape 키 닫기 처리
  • POST /api/payments/coupons/redeem API 연결
  • API 요청 중 입력창 및 등록 버튼 비활성화
  • 등록 실패 시 입력창 오류 스타일과 안내 문구 표시
  • 등록 성공 시 충전된 크레딧 수량을 토스트로 표시
  • 공통 토스트 위치를 뷰포트 우측·하단 28px로 조정
  • 공통 AutoGrow 입력 컴포넌트에 하단 영역 노출 옵션 추가

Summary by CodeRabbit

  • New Features
    • Added coupon registration from the credit page.
    • Added coupon entry modal with validation, submission feedback, and success confirmation.
    • Successfully redeemed coupons now display the credited amount in a toast notification.
  • Bug Fixes
    • Modal dialogs can now be closed with the Escape key while preserving page scrolling behavior.
    • Submit buttons are disabled during processing to prevent duplicate submissions.
  • Style
    • Improved page header spacing and adjusted toast positioning.
    • Updated modal backdrop styling and input layout options.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The credit page adds coupon registration through a modal, redemption API, disabled submission state, Escape-to-close behavior, and a success toast. Shared input, header, overlay, and toast styling are updated to support the new flow.

Changes

Coupon redemption

Layer / File(s) Summary
Coupon redemption API
jobdri/src/lib/api/credit.ts
Adds redeemCoupon and its result contract, including POST handling, unauthorized responses, validation, and error propagation.
Coupon modal and shared controls
jobdri/src/components/common/credit/CouponRegistrationModal.tsx, jobdri/src/components/common/modal/ModalOverlay.tsx, jobdri/src/components/common/input/*, jobdri/src/components/common/buttons/ButtonCtaModal.tsx
Adds coupon input submission, validation, error handling, disabled CTA behavior, Escape-to-close handling, overflow restoration, and optional input bottom controls.
Credit page coupon feedback
jobdri/src/app/credit/page.tsx, jobdri/src/components/common/toast/Toast.tsx, jobdri/src/components/common/PageHeader.tsx
Adds the registration trigger, modal wiring, success amount state, auto-dismissed toast, and related layout positioning updates.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CreditContent
  participant CouponRegistrationModal
  participant redeemCoupon
  participant Toast
  CreditContent->>CouponRegistrationModal: open coupon modal
  CouponRegistrationModal->>redeemCoupon: submit coupon code
  redeemCoupon-->>CouponRegistrationModal: return creditAmount
  CouponRegistrationModal-->>CreditContent: report success
  CreditContent->>Toast: display credited amount
Loading

Possibly related PRs

Suggested labels: ⭐ Feature, 🎨 UI

Suggested reviewers: yiyoonseo

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding the coupon registration UI and wiring it to the API.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/JDDEV-73—coupon-ui-feat

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
jobdri/src/components/common/buttons/ButtonCtaModal.tsx (1)

83-89: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Cancel button remains enabled during submission.

submitDisabled only gates the submit button. A user can click "닫기" while redeemCoupon is in flight, closing the modal while the request continues; if it later resolves, onSuccess/error effects still fire against the parent's state, producing a toast (or silently dropping an error) after the user thought they cancelled.

♻️ Proposed fix
           <Button
             label={cancelLabel}
             size="large"
             styleType={cancelStyleType}
             onClick={onCancel}
+            disabled={submitDisabled}
             className={clsx("h-[46px] flex-1", cancelClassName)}
           />
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jobdri/src/components/common/buttons/ButtonCtaModal.tsx` around lines 83 -
89, Update the cancel Button in ButtonCtaModal so it is disabled whenever
submitDisabled indicates redemption is in progress, preventing onCancel from
closing the modal during the in-flight redeemCoupon request while preserving
normal cancellation otherwise.
🧹 Nitpick comments (1)
jobdri/src/components/common/input/InputTextAreaFixedShared.tsx (1)

38-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Make showBottomLine the bottom-line guard for the fixed variants too.

InputTextAreaFixedS and InputTextAreaFixedL both accept and forward showBottomLine, but InputTextAreaFixedBase still renders InputTextAreaFixedBottom unconditionally at jobdri/src/components/common/input/InputTextAreaFixedShared.tsx:340. Passing showBottomLine={false} to those variants currently still shows the bottom line, so the shared prop contract is inconsistent.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jobdri/src/components/common/input/InputTextAreaFixedShared.tsx` at line 38,
Update InputTextAreaFixedBase so rendering InputTextAreaFixedBottom is guarded
by the showBottomLine prop. Preserve the existing bottom-line rendering when
showBottomLine is true or unspecified, while suppressing it when false for
InputTextAreaFixedS and InputTextAreaFixedL.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@jobdri/src/app/credit/page.tsx`:
- Around line 108-114: Update the Toast message in the couponToastCreditAmount
rendering block to format the amount with toLocaleString(), matching the
existing plan.price formatting in the credit page. Keep the current Korean
message and null-check behavior unchanged.

In `@jobdri/src/components/common/credit/CouponRegistrationModal.tsx`:
- Around line 37-44: Update the coupon submission handler around redeemCoupon to
capture the thrown error and display its specific message via setError instead
of always using the generic invalid-code text. Preserve the existing fallback
message for non-Error or message-less failures, and keep setIsSubmitting(false)
in the finally path.

---

Outside diff comments:
In `@jobdri/src/components/common/buttons/ButtonCtaModal.tsx`:
- Around line 83-89: Update the cancel Button in ButtonCtaModal so it is
disabled whenever submitDisabled indicates redemption is in progress, preventing
onCancel from closing the modal during the in-flight redeemCoupon request while
preserving normal cancellation otherwise.

---

Nitpick comments:
In `@jobdri/src/components/common/input/InputTextAreaFixedShared.tsx`:
- Line 38: Update InputTextAreaFixedBase so rendering InputTextAreaFixedBottom
is guarded by the showBottomLine prop. Preserve the existing bottom-line
rendering when showBottomLine is true or unspecified, while suppressing it when
false for InputTextAreaFixedS and InputTextAreaFixedL.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 6e06d68a-b9e2-4cc2-9781-9658eee7b3b5

📥 Commits

Reviewing files that changed from the base of the PR and between 4fac529 and bbf055a.

📒 Files selected for processing (9)
  • jobdri/src/app/credit/page.tsx
  • jobdri/src/components/common/PageHeader.tsx
  • jobdri/src/components/common/buttons/ButtonCtaModal.tsx
  • jobdri/src/components/common/credit/CouponRegistrationModal.tsx
  • jobdri/src/components/common/input/InputTextAreaAutoGrowS.tsx
  • jobdri/src/components/common/input/InputTextAreaFixedShared.tsx
  • jobdri/src/components/common/modal/ModalOverlay.tsx
  • jobdri/src/components/common/toast/Toast.tsx
  • jobdri/src/lib/api/credit.ts

Comment on lines +108 to +114
{couponToastCreditAmount !== null && (
<Toast
message={`${couponToastCreditAmount}크레딧이 충전되었습니다!`}
variant="check"
onClose={() => setCouponToastCreditAmount(null)}
/>
)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Toast credit amount isn't locale-formatted.

couponToastCreditAmount is interpolated raw, unlike plan.price.toLocaleString() used elsewhere in this file (line 129). Larger credited amounts will render without thousands separators.

🐛 Proposed fix
-          message={`${couponToastCreditAmount}크레딧이 충전되었습니다!`}
+          message={`${couponToastCreditAmount.toLocaleString()}크레딧이 충전되었습니다!`}
📝 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
{couponToastCreditAmount !== null && (
<Toast
message={`${couponToastCreditAmount}크레딧이 충전되었습니다!`}
variant="check"
onClose={() => setCouponToastCreditAmount(null)}
/>
)}
{couponToastCreditAmount !== null && (
<Toast
message={`${couponToastCreditAmount.toLocaleString()}크레딧이 충전되었습니다!`}
variant="check"
onClose={() => setCouponToastCreditAmount(null)}
/>
)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jobdri/src/app/credit/page.tsx` around lines 108 - 114, Update the Toast
message in the couponToastCreditAmount rendering block to format the amount with
toLocaleString(), matching the existing plan.price formatting in the credit
page. Keep the current Korean message and null-check behavior unchanged.

Comment on lines +37 to +44
try {
const result = await redeemCoupon(couponCode);
onSuccess(result.creditAmount);
} catch {
setError("쿠폰 번호를 확인해주세요.");
} finally {
setIsSubmitting(false);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Specific coupon-redemption error is discarded.

redeemCoupon (credit.ts) deliberately throws payload.error || payload.message || fallback to surface specific reasons (invalid code, already redeemed, expired, server error), but this bare catch {} always shows the same generic "쿠폰 번호를 확인해주세요." Users lose meaningful feedback about why redemption actually failed.

🐛 Proposed fix to surface the actual error message
-    } catch {
-      setError("쿠폰 번호를 확인해주세요.");
+    } catch (err) {
+      setError(err instanceof Error ? err.message : "쿠폰 번호를 확인해주세요.");
     } finally {
📝 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
try {
const result = await redeemCoupon(couponCode);
onSuccess(result.creditAmount);
} catch {
setError("쿠폰 번호를 확인해주세요.");
} finally {
setIsSubmitting(false);
}
try {
const result = await redeemCoupon(couponCode);
onSuccess(result.creditAmount);
} catch (err) {
setError(err instanceof Error ? err.message : "쿠폰 번호를 확인해주세요.");
} finally {
setIsSubmitting(false);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@jobdri/src/components/common/credit/CouponRegistrationModal.tsx` around lines
37 - 44, Update the coupon submission handler around redeemCoupon to capture the
thrown error and display its specific message via setError instead of always
using the generic invalid-code text. Preserve the existing fallback message for
non-Error or message-less failures, and keep setIsSubmitting(false) in the
finally path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant