-
Notifications
You must be signed in to change notification settings - Fork 1
Feat: 쿠폰 등록 화면 구현 및 API 연결 #115
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
The head ref may contain hidden characters: "feature/JDDEV-73\u2014coupon-ui-feat"
Changes from all commits
e2bacd2
09f1c11
d185f75
121b129
bbf055a
90aa980
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,106 @@ | ||||||||||||||||||||||||||||||||||
| "use client"; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| import { useState } from "react"; | ||||||||||||||||||||||||||||||||||
| import { ButtonCtaModal } from "@/components/common/buttons"; | ||||||||||||||||||||||||||||||||||
| import { InputTextAreaAutoGrowS } from "@/components/common/input"; | ||||||||||||||||||||||||||||||||||
| import { ModalOverlay } from "@/components/common/modal/ModalOverlay"; | ||||||||||||||||||||||||||||||||||
| import { redeemCoupon } from "@/lib/api/credit"; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| interface CouponRegistrationModalProps { | ||||||||||||||||||||||||||||||||||
| onClose: () => void; | ||||||||||||||||||||||||||||||||||
| onSuccess: (creditAmount: number) => void; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| export default function CouponRegistrationModal({ | ||||||||||||||||||||||||||||||||||
| onClose, | ||||||||||||||||||||||||||||||||||
| onSuccess, | ||||||||||||||||||||||||||||||||||
| }: CouponRegistrationModalProps) { | ||||||||||||||||||||||||||||||||||
| const [couponNumber, setCouponNumber] = useState(""); | ||||||||||||||||||||||||||||||||||
| const [error, setError] = useState(""); | ||||||||||||||||||||||||||||||||||
| const [isSubmitting, setIsSubmitting] = useState(false); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const handleCouponNumberChange = (value: string) => { | ||||||||||||||||||||||||||||||||||
| setCouponNumber(value); | ||||||||||||||||||||||||||||||||||
| setError(""); | ||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| const handleSubmit = async () => { | ||||||||||||||||||||||||||||||||||
| const couponCode = couponNumber.trim(); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (!couponCode || isSubmitting) { | ||||||||||||||||||||||||||||||||||
| setError("쿠폰 번호를 확인해주세요."); | ||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| setIsSubmitting(true); | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||
| const result = await redeemCoupon(couponCode); | ||||||||||||||||||||||||||||||||||
| onSuccess(result.creditAmount); | ||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||
| setError("쿠폰 번호를 확인해주세요."); | ||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||
| setIsSubmitting(false); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+37
to
+44
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Specific coupon-redemption error is discarded.
🐛 Proposed fix to surface the actual error message- } catch {
- setError("쿠폰 번호를 확인해주세요.");
+ } catch (err) {
+ setError(err instanceof Error ? err.message : "쿠폰 번호를 확인해주세요.");
} finally {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||
| <ModalOverlay onClose={onClose}> | ||||||||||||||||||||||||||||||||||
| <div | ||||||||||||||||||||||||||||||||||
| role="dialog" | ||||||||||||||||||||||||||||||||||
| aria-modal="true" | ||||||||||||||||||||||||||||||||||
| aria-labelledby="coupon-registration-title" | ||||||||||||||||||||||||||||||||||
| aria-describedby="coupon-registration-description" | ||||||||||||||||||||||||||||||||||
| className="flex w-[400px] shrink-0 flex-col items-center justify-center gap-0 rounded-card-l bg-bg-contents-default shadow-modal" | ||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||
| <div className="flex flex-col items-start gap-4 self-stretch px-7 pt-7"> | ||||||||||||||||||||||||||||||||||
| <div className="flex flex-col items-start gap-3 self-stretch"> | ||||||||||||||||||||||||||||||||||
| <h2 | ||||||||||||||||||||||||||||||||||
| id="coupon-registration-title" | ||||||||||||||||||||||||||||||||||
| className="self-stretch text-t20-semibold text-text-neutral-title [font-feature-settings:'liga'_off,'clig'_off]" | ||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||
| 쿠폰 등록하기 | ||||||||||||||||||||||||||||||||||
| </h2> | ||||||||||||||||||||||||||||||||||
| <p | ||||||||||||||||||||||||||||||||||
| id="coupon-registration-description" | ||||||||||||||||||||||||||||||||||
| className="self-stretch text-sub14-med text-text-neutral-description [font-feature-settings:'liga'_off,'clig'_off]" | ||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||
| “-”와 띄어쓰기 없이 쿠폰번호를 입력해주세요. | ||||||||||||||||||||||||||||||||||
| </p> | ||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| <div className="flex flex-col items-start gap-2.5 self-stretch px-7 pt-6"> | ||||||||||||||||||||||||||||||||||
| <InputTextAreaAutoGrowS | ||||||||||||||||||||||||||||||||||
| aria-label="쿠폰 일련 번호" | ||||||||||||||||||||||||||||||||||
| autoFocus | ||||||||||||||||||||||||||||||||||
| label="" | ||||||||||||||||||||||||||||||||||
| required={false} | ||||||||||||||||||||||||||||||||||
| placeholder="일련 번호를 입력해주세요." | ||||||||||||||||||||||||||||||||||
| value={couponNumber} | ||||||||||||||||||||||||||||||||||
| onChange={handleCouponNumberChange} | ||||||||||||||||||||||||||||||||||
| error={error} | ||||||||||||||||||||||||||||||||||
| disabled={isSubmitting} | ||||||||||||||||||||||||||||||||||
| showAddButton={false} | ||||||||||||||||||||||||||||||||||
| showBottomLine={false} | ||||||||||||||||||||||||||||||||||
| className="gap-1" | ||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| <div className="flex items-end justify-end gap-2 self-stretch px-5 pt-8 pb-5"> | ||||||||||||||||||||||||||||||||||
| <ButtonCtaModal | ||||||||||||||||||||||||||||||||||
| stack="stack2_horizontal" | ||||||||||||||||||||||||||||||||||
| cancelLabel="닫기" | ||||||||||||||||||||||||||||||||||
| label="등록하기" | ||||||||||||||||||||||||||||||||||
| cancelStyleType="quaternary" | ||||||||||||||||||||||||||||||||||
| submitStyleType="secondary" | ||||||||||||||||||||||||||||||||||
| onCancel={onClose} | ||||||||||||||||||||||||||||||||||
| onSubmit={handleSubmit} | ||||||||||||||||||||||||||||||||||
| submitDisabled={isSubmitting} | ||||||||||||||||||||||||||||||||||
| className="w-full !pb-0" | ||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
| </ModalOverlay> | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Toast credit amount isn't locale-formatted.
couponToastCreditAmountis interpolated raw, unlikeplan.price.toLocaleString()used elsewhere in this file (line 129). Larger credited amounts will render without thousands separators.🐛 Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents