@thinkingcat/subscription-ui 1.0.4 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -85,6 +85,8 @@ interface SubscriptionManagementConfig {
85
85
  subscriptionsApiPath?: string;
86
86
  /** 결제 이력/취소 API 경로 (default: /api/subscribe/payments) */
87
87
  paymentsApiPath?: string;
88
+ /** 카드 조회/등록/삭제 API 경로 (default: /api/subscribe/cards) */
89
+ cardsApiPath?: string;
88
90
  /** 브랜드 색상 (default: #60A5FA) */
89
91
  primaryColor?: string;
90
92
  /** 테마 (default: "system") */
@@ -93,6 +95,6 @@ interface SubscriptionManagementConfig {
93
95
 
94
96
  declare function SubscribePage({ plansApiPath, cardsApiPath, payApiPath, callbackUrl, serviceName, primaryColor, showExpiredBanner, theme, }: SubscribePageConfig): react_jsx_runtime.JSX.Element;
95
97
 
96
- declare function SubscriptionManagement({ subscriptionsApiPath, paymentsApiPath, primaryColor, theme, }: SubscriptionManagementConfig): react_jsx_runtime.JSX.Element;
98
+ declare function SubscriptionManagement({ subscriptionsApiPath, paymentsApiPath, cardsApiPath, primaryColor, theme, }: SubscriptionManagementConfig): react_jsx_runtime.JSX.Element;
97
99
 
98
100
  export { type Card, type Payment, type Plan, SubscribePage, type SubscribePageConfig, type Subscription, SubscriptionManagement, type SubscriptionManagementConfig };
package/dist/index.d.ts CHANGED
@@ -85,6 +85,8 @@ interface SubscriptionManagementConfig {
85
85
  subscriptionsApiPath?: string;
86
86
  /** 결제 이력/취소 API 경로 (default: /api/subscribe/payments) */
87
87
  paymentsApiPath?: string;
88
+ /** 카드 조회/등록/삭제 API 경로 (default: /api/subscribe/cards) */
89
+ cardsApiPath?: string;
88
90
  /** 브랜드 색상 (default: #60A5FA) */
89
91
  primaryColor?: string;
90
92
  /** 테마 (default: "system") */
@@ -93,6 +95,6 @@ interface SubscriptionManagementConfig {
93
95
 
94
96
  declare function SubscribePage({ plansApiPath, cardsApiPath, payApiPath, callbackUrl, serviceName, primaryColor, showExpiredBanner, theme, }: SubscribePageConfig): react_jsx_runtime.JSX.Element;
95
97
 
96
- declare function SubscriptionManagement({ subscriptionsApiPath, paymentsApiPath, primaryColor, theme, }: SubscriptionManagementConfig): react_jsx_runtime.JSX.Element;
98
+ declare function SubscriptionManagement({ subscriptionsApiPath, paymentsApiPath, cardsApiPath, primaryColor, theme, }: SubscriptionManagementConfig): react_jsx_runtime.JSX.Element;
97
99
 
98
100
  export { type Card, type Payment, type Plan, SubscribePage, type SubscribePageConfig, type Subscription, SubscriptionManagement, type SubscriptionManagementConfig };
package/dist/index.js CHANGED
@@ -421,20 +421,32 @@ var DEFAULT_PRIMARY2 = "#60A5FA";
421
421
  function SubscriptionManagement({
422
422
  subscriptionsApiPath = "/api/subscribe/subscriptions",
423
423
  paymentsApiPath = "/api/subscribe/payments",
424
+ cardsApiPath = "/api/subscribe/cards",
424
425
  primaryColor = DEFAULT_PRIMARY2,
425
426
  theme = "system"
426
427
  }) {
427
428
  const [tab, setTab] = (0, import_react2.useState)("subscription");
428
429
  const [subscriptions, setSubscriptions] = (0, import_react2.useState)([]);
429
430
  const [payments, setPayments] = (0, import_react2.useState)([]);
431
+ const [cards, setCards] = (0, import_react2.useState)([]);
430
432
  const [loading, setLoading] = (0, import_react2.useState)(true);
431
433
  const [error, setError] = (0, import_react2.useState)("");
432
434
  const [actionLoading, setActionLoading] = (0, import_react2.useState)(null);
433
435
  const [confirmModal, setConfirmModal] = (0, import_react2.useState)(null);
436
+ const [showCardForm, setShowCardForm] = (0, import_react2.useState)(false);
437
+ const [cardForm, setCardForm] = (0, import_react2.useState)({
438
+ cardNumber: "",
439
+ expiryDate: "",
440
+ cvc: "",
441
+ cardPassword: "",
442
+ cardHolderName: "",
443
+ birthNumber: ""
444
+ });
445
+ const [cardError, setCardError] = (0, import_react2.useState)("");
446
+ const [cardSubmitting, setCardSubmitting] = (0, import_react2.useState)(false);
434
447
  const themeClass = theme === "dark" ? "dark" : theme === "light" ? "light-mode-override" : "";
435
448
  (0, import_react2.useEffect)(() => {
436
- fetchSubscriptions();
437
- fetchPayments();
449
+ Promise.all([fetchSubscriptions(), fetchPayments(), fetchCards()]).finally(() => setLoading(false));
438
450
  }, []);
439
451
  async function fetchSubscriptions() {
440
452
  try {
@@ -442,8 +454,6 @@ function SubscriptionManagement({
442
454
  const data = await res.json();
443
455
  if (data.success) setSubscriptions(data.subscriptions);
444
456
  } catch {
445
- } finally {
446
- setLoading(false);
447
457
  }
448
458
  }
449
459
  async function fetchPayments() {
@@ -454,6 +464,14 @@ function SubscriptionManagement({
454
464
  } catch {
455
465
  }
456
466
  }
467
+ async function fetchCards() {
468
+ try {
469
+ const res = await fetch(cardsApiPath);
470
+ const data = await res.json();
471
+ if (data.success) setCards(data.cards);
472
+ } catch {
473
+ }
474
+ }
457
475
  async function handleCancelSubscription(subscriptionId) {
458
476
  setActionLoading(subscriptionId);
459
477
  setError("");
@@ -490,8 +508,7 @@ function SubscriptionManagement({
490
508
  setError(data.error || "\uACB0\uC81C \uCDE8\uC18C\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
491
509
  return;
492
510
  }
493
- await fetchPayments();
494
- await fetchSubscriptions();
511
+ await Promise.all([fetchPayments(), fetchSubscriptions()]);
495
512
  } catch {
496
513
  setError("\uACB0\uC81C \uCDE8\uC18C \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
497
514
  } finally {
@@ -499,6 +516,51 @@ function SubscriptionManagement({
499
516
  setConfirmModal(null);
500
517
  }
501
518
  }
519
+ async function handleDeleteCard(cardId) {
520
+ setActionLoading(cardId);
521
+ setError("");
522
+ try {
523
+ const res = await fetch(cardsApiPath, {
524
+ method: "DELETE",
525
+ headers: { "Content-Type": "application/json" },
526
+ body: JSON.stringify({ cardId })
527
+ });
528
+ const data = await res.json();
529
+ if (!data.success) {
530
+ setError(data.error || "\uCE74\uB4DC \uC0AD\uC81C\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
531
+ return;
532
+ }
533
+ await fetchCards();
534
+ } catch {
535
+ setError("\uCE74\uB4DC \uC0AD\uC81C \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
536
+ } finally {
537
+ setActionLoading(null);
538
+ setConfirmModal(null);
539
+ }
540
+ }
541
+ async function handleRegisterCard() {
542
+ setCardError("");
543
+ setCardSubmitting(true);
544
+ try {
545
+ const res = await fetch(cardsApiPath, {
546
+ method: "POST",
547
+ headers: { "Content-Type": "application/json" },
548
+ body: JSON.stringify(cardForm)
549
+ });
550
+ const data = await res.json();
551
+ if (!data.success) {
552
+ setCardError(data.error || "\uCE74\uB4DC \uB4F1\uB85D\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
553
+ return;
554
+ }
555
+ await fetchCards();
556
+ setShowCardForm(false);
557
+ setCardForm({ cardNumber: "", expiryDate: "", cvc: "", cardPassword: "", cardHolderName: "", birthNumber: "" });
558
+ } catch {
559
+ setCardError("\uCE74\uB4DC \uB4F1\uB85D \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
560
+ } finally {
561
+ setCardSubmitting(false);
562
+ }
563
+ }
502
564
  function fmt(price) {
503
565
  return price.toLocaleString("ko-KR") + "\uC6D0";
504
566
  }
@@ -506,35 +568,39 @@ function SubscriptionManagement({
506
568
  return new Date(dateStr).toLocaleDateString("ko-KR", { year: "numeric", month: "long", day: "numeric" });
507
569
  }
508
570
  function statusBadge(status) {
509
- if (status === "ACTIVE") return { bg: "bg-green-100 dark:bg-green-500/20", text: "text-green-700 dark:text-green-400", label: "\uD65C\uC131" };
510
- if (status === "CANCELLED") return { bg: "bg-red-100 dark:bg-red-500/20", text: "text-red-700 dark:text-red-400", label: "\uCDE8\uC18C\uB428" };
511
- return { bg: "bg-gray-100 dark:bg-gray-700", text: "text-gray-600 dark:text-gray-400", label: status };
571
+ if (status === "ACTIVE") return { cls: "bg-green-100 dark:bg-green-500/20 text-green-700 dark:text-green-400", label: "\uD65C\uC131" };
572
+ if (status === "CANCELLED") return { cls: "bg-red-100 dark:bg-red-500/20 text-red-700 dark:text-red-400", label: "\uCDE8\uC18C\uB428" };
573
+ return { cls: "bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400", label: status };
512
574
  }
513
575
  function paymentStatusBadge(status) {
514
- if (status === "COMPLETED") return { bg: "bg-green-100 dark:bg-green-500/20", text: "text-green-700 dark:text-green-400", label: "\uC644\uB8CC" };
515
- if (status === "CANCELLED") return { bg: "bg-red-100 dark:bg-red-500/20", text: "text-red-700 dark:text-red-400", label: "\uCDE8\uC18C\uB428" };
516
- if (status === "FAILED") return { bg: "bg-orange-100 dark:bg-orange-500/20", text: "text-orange-700 dark:text-orange-400", label: "\uC2E4\uD328" };
517
- return { bg: "bg-gray-100 dark:bg-gray-700", text: "text-gray-600 dark:text-gray-400", label: status };
576
+ if (status === "COMPLETED") return { cls: "bg-green-100 dark:bg-green-500/20 text-green-700 dark:text-green-400", label: "\uC644\uB8CC" };
577
+ if (status === "CANCELLED") return { cls: "bg-red-100 dark:bg-red-500/20 text-red-700 dark:text-red-400", label: "\uCDE8\uC18C\uB428" };
578
+ if (status === "FAILED") return { cls: "bg-orange-100 dark:bg-orange-500/20 text-orange-700 dark:text-orange-400", label: "\uC2E4\uD328" };
579
+ return { cls: "bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400", label: status };
518
580
  }
519
581
  function canCancelPayment(p) {
520
582
  if (p.status !== "COMPLETED") return false;
521
- const paidDate = new Date(p.paidAt || p.createdAt);
522
- const daysDiff = Math.ceil((Date.now() - paidDate.getTime()) / (1e3 * 3600 * 24));
583
+ const daysDiff = Math.ceil((Date.now() - new Date(p.paidAt || p.createdAt).getTime()) / (1e3 * 3600 * 24));
523
584
  return daysDiff <= 7;
524
585
  }
525
586
  if (loading) return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: themeClass, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-gray-400 py-8 text-center", children: "\uBD88\uB7EC\uC624\uB294 \uC911..." }) });
587
+ const tabs = [
588
+ { key: "subscription", label: "\uAD6C\uB3C5 \uC815\uBCF4" },
589
+ { key: "cards", label: "\uACB0\uC81C\uC218\uB2E8" },
590
+ { key: "payments", label: "\uACB0\uC81C \uC774\uB825" }
591
+ ];
526
592
  return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: themeClass, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-4", children: [
527
593
  error && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "p-3 bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/30 rounded-lg text-red-600 dark:text-red-400 text-sm", children: error }),
528
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex border-b border-gray-200 dark:border-white/10", children: [
529
- { key: "subscription", label: "\uAD6C\uB3C5 \uC815\uBCF4" },
530
- { key: "payments", label: "\uACB0\uC81C \uC774\uB825" }
531
- ].map(({ key, label }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
594
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex border-b border-gray-200 dark:border-white/10", children: tabs.map(({ key, label }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
532
595
  "button",
533
596
  {
534
597
  onClick: () => setTab(key),
535
- className: `px-4 py-2.5 text-sm font-medium border-b-2 transition-colors -mb-px ${tab === key ? "border-current text-blue-600 dark:text-blue-400" : "border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"}`,
598
+ className: `px-4 py-2.5 text-sm font-medium border-b-2 transition-colors -mb-px ${tab === key ? "border-current" : "border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"}`,
536
599
  style: tab === key ? { color: primaryColor, borderColor: primaryColor } : void 0,
537
- children: label
600
+ children: [
601
+ label,
602
+ key === "cards" && cards.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "ml-1.5 px-1.5 py-0.5 rounded-full text-xs bg-gray-100 dark:bg-white/10 text-gray-500 dark:text-gray-400", children: cards.length })
603
+ ]
538
604
  },
539
605
  key
540
606
  )) }),
@@ -544,42 +610,16 @@ function SubscriptionManagement({
544
610
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-2 flex-1 min-w-0", children: [
545
611
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2 flex-wrap", children: [
546
612
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-gray-900 dark:text-white font-semibold text-base", children: sub.plan.name }),
547
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: `px-2 py-0.5 rounded-full text-xs font-medium ${badge.bg} ${badge.text}`, children: badge.label }),
613
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: `px-2 py-0.5 rounded-full text-xs font-medium ${badge.cls}`, children: badge.label }),
548
614
  sub.isTrial && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "px-2 py-0.5 rounded-full text-xs font-medium bg-purple-100 dark:bg-purple-500/20 text-purple-700 dark:text-purple-400", children: "\uCCB4\uD5D8" })
549
615
  ] }),
550
616
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-x-8 gap-y-1 text-sm", children: [
551
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
552
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-gray-400 dark:text-gray-500", children: "\uAE08\uC561" }),
553
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "ml-2 text-gray-700 dark:text-gray-300 font-medium", children: [
554
- fmt(sub.plan.price),
555
- " / ",
556
- sub.plan.interval,
557
- "\uAC1C\uC6D4"
558
- ] })
559
- ] }),
560
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
561
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-gray-400 dark:text-gray-500", children: "\uC0C1\uD0DC" }),
562
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "ml-2 text-gray-700 dark:text-gray-300", children: sub.statusMessage })
563
- ] }),
564
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
565
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-gray-400 dark:text-gray-500", children: "\uC2DC\uC791\uC77C" }),
566
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "ml-2 text-gray-700 dark:text-gray-300", children: fmtDate(sub.startDate) })
567
- ] }),
568
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
569
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-gray-400 dark:text-gray-500", children: "\uB9CC\uB8CC\uC77C" }),
570
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "ml-2 text-gray-700 dark:text-gray-300", children: fmtDate(sub.endDate) })
571
- ] }),
572
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
573
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-gray-400 dark:text-gray-500", children: "\uB0A8\uC740 \uAE30\uAC04" }),
574
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "ml-2 text-gray-700 dark:text-gray-300 font-medium", children: [
575
- sub.remainingDays,
576
- "\uC77C"
577
- ] })
578
- ] }),
579
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
580
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-gray-400 dark:text-gray-500", children: "\uC790\uB3D9\uACB0\uC81C" }),
581
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "ml-2 text-gray-700 dark:text-gray-300", children: sub.autoRenew ? "\uC0AC\uC6A9" : "\uD574\uC81C" })
582
- ] })
617
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InfoRow, { label: "\uAE08\uC561", value: `${fmt(sub.plan.price)} / ${sub.plan.interval}\uAC1C\uC6D4`, bold: true }),
618
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InfoRow, { label: "\uC0C1\uD0DC", value: sub.statusMessage }),
619
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InfoRow, { label: "\uC2DC\uC791\uC77C", value: fmtDate(sub.startDate) }),
620
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InfoRow, { label: "\uB9CC\uB8CC\uC77C", value: fmtDate(sub.endDate) }),
621
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InfoRow, { label: "\uB0A8\uC740 \uAE30\uAC04", value: `${sub.remainingDays}\uC77C`, bold: true }),
622
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(InfoRow, { label: "\uC790\uB3D9\uACB0\uC81C", value: sub.autoRenew ? "\uC0AC\uC6A9" : "\uD574\uC81C" })
583
623
  ] })
584
624
  ] }),
585
625
  sub.status === "ACTIVE" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -593,7 +633,92 @@ function SubscriptionManagement({
593
633
  )
594
634
  ] }) }, sub.id);
595
635
  }) }),
596
- tab === "payments" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-2", children: payments.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-center py-12 text-gray-400 dark:text-gray-500 text-sm", children: "\uACB0\uC81C \uC774\uB825\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "overflow-x-auto", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("table", { className: "w-full text-sm", children: [
636
+ tab === "cards" && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-3", children: [
637
+ cards.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-2", children: cards.map((card) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "p-4 rounded-xl border border-gray-200 dark:border-white/10 bg-white dark:bg-white/5 flex items-center justify-between gap-3", children: [
638
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-3 min-w-0", children: [
639
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "w-10 h-7 rounded bg-gray-100 dark:bg-white/10 flex items-center justify-center text-xs font-bold text-gray-500 dark:text-gray-400 flex-shrink-0", children: card.cardBrand?.slice(0, 4) || "CARD" }),
640
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "min-w-0", children: [
641
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", children: [
642
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-gray-900 dark:text-white font-medium text-sm", children: card.cardBrand }),
643
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-gray-500 dark:text-gray-400 text-sm", children: card.maskedCardNumber }),
644
+ card.isDefault && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "px-1.5 py-0.5 rounded text-xs font-medium bg-blue-100 dark:bg-blue-500/20 text-blue-600 dark:text-blue-400", children: "\uAE30\uBCF8" })
645
+ ] }),
646
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-xs text-gray-400 dark:text-gray-500 mt-0.5", children: [
647
+ card.cardHolderName,
648
+ " \xB7 ",
649
+ card.expiryMonth,
650
+ "/",
651
+ card.expiryYear
652
+ ] })
653
+ ] })
654
+ ] }),
655
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
656
+ "button",
657
+ {
658
+ onClick: () => setConfirmModal({ type: "delete_card", id: card.id, label: `${card.cardBrand} ${card.maskedCardNumber}` }),
659
+ disabled: actionLoading === card.id,
660
+ className: "text-xs text-red-500 dark:text-red-400 hover:underline disabled:opacity-50 flex-shrink-0",
661
+ children: "\uC0AD\uC81C"
662
+ }
663
+ )
664
+ ] }, card.id)) }),
665
+ !showCardForm ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
666
+ "button",
667
+ {
668
+ onClick: () => setShowCardForm(true),
669
+ className: "w-full p-4 rounded-xl border border-dashed border-gray-300 dark:border-white/20 text-gray-400 dark:text-gray-500 hover:border-gray-400 dark:hover:border-white/40 hover:text-gray-500 dark:hover:text-gray-300 transition-all text-sm",
670
+ children: "+ \uC0C8 \uCE74\uB4DC \uCD94\uAC00"
671
+ }
672
+ ) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "p-4 rounded-xl border border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-white/5 space-y-3", children: [
673
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "text-gray-900 dark:text-white text-sm font-medium", children: "\uCE74\uB4DC \uC815\uBCF4 \uC785\uB825" }),
674
+ cardError && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "p-3 bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/30 rounded-lg text-red-600 dark:text-red-400 text-xs", children: cardError }),
675
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3", children: [
676
+ { label: "\uCE74\uB4DC \uBC88\uD638", key: "cardNumber", placeholder: "0000-0000-0000-0000", type: "text", full: true },
677
+ { label: "\uB9CC\uB8CC\uC77C", key: "expiryDate", placeholder: "MM/YY", type: "text", full: false },
678
+ { label: "CVC", key: "cvc", placeholder: "3\uC790\uB9AC", type: "password", full: false },
679
+ { label: "\uCE74\uB4DC \uBE44\uBC00\uBC88\uD638 \uC55E 2\uC790\uB9AC", key: "cardPassword", placeholder: "\u2022\u2022", type: "password", full: false },
680
+ { label: "\uCE74\uB4DC \uC18C\uC720\uC790\uBA85", key: "cardHolderName", placeholder: "\uD64D\uAE38\uB3D9", type: "text", full: false },
681
+ { label: "\uC0DD\uB144\uC6D4\uC77C 6\uC790\uB9AC / \uC0AC\uC5C5\uC790\uBC88\uD638 10\uC790\uB9AC", key: "birthNumber", placeholder: "990101", type: "text", full: true }
682
+ ].map(({ label, key, placeholder, type, full }) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: full ? "sm:col-span-2" : "", children: [
683
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { className: "text-xs text-gray-500 dark:text-gray-400 block mb-1", children: label }),
684
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
685
+ "input",
686
+ {
687
+ type,
688
+ placeholder,
689
+ value: cardForm[key],
690
+ onChange: (e) => setCardForm((f) => ({ ...f, [key]: e.target.value })),
691
+ className: "w-full bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-lg px-3 py-2 text-gray-900 dark:text-white text-sm placeholder-gray-300 dark:placeholder-gray-600 focus:outline-none focus:border-blue-500 transition-colors"
692
+ }
693
+ )
694
+ ] }, key)) }),
695
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2 pt-1", children: [
696
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
697
+ "button",
698
+ {
699
+ onClick: () => {
700
+ setShowCardForm(false);
701
+ setCardError("");
702
+ },
703
+ className: "flex-1 py-2.5 rounded-lg border border-gray-200 dark:border-white/10 text-gray-500 dark:text-gray-400 text-sm hover:border-gray-300 dark:hover:border-white/20 transition-all",
704
+ children: "\uCDE8\uC18C"
705
+ }
706
+ ),
707
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
708
+ "button",
709
+ {
710
+ onClick: handleRegisterCard,
711
+ disabled: cardSubmitting,
712
+ style: { backgroundColor: primaryColor },
713
+ className: "flex-1 py-2.5 rounded-lg text-white text-sm font-medium hover:opacity-90 disabled:opacity-50 transition-all",
714
+ children: cardSubmitting ? "\uB4F1\uB85D \uC911..." : "\uCE74\uB4DC \uB4F1\uB85D"
715
+ }
716
+ )
717
+ ] })
718
+ ] }),
719
+ cards.length === 0 && !showCardForm && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-center py-8 text-gray-400 dark:text-gray-500 text-sm", children: "\uB4F1\uB85D\uB41C \uACB0\uC81C\uC218\uB2E8\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." })
720
+ ] }),
721
+ tab === "payments" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-2", children: payments.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-center py-12 text-gray-400 dark:text-gray-500 text-sm", children: "\uACB0\uC81C \uC774\uB825\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "overflow-x-auto -mx-4 sm:mx-0", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("table", { className: "w-full text-sm min-w-[600px]", children: [
597
722
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tr", { className: "border-b border-gray-200 dark:border-white/10", children: ["\uC77C\uC2DC", "\uB0B4\uC6A9", "\uAE08\uC561", "\uACB0\uC81C\uC218\uB2E8", "\uC0C1\uD0DC", ""].map((h) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("th", { className: "text-left py-2.5 px-3 text-xs font-medium text-gray-400 dark:text-gray-500 whitespace-nowrap", children: h }, h)) }) }),
598
723
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: payments.map((p) => {
599
724
  const badge = paymentStatusBadge(p.status);
@@ -602,7 +727,7 @@ function SubscriptionManagement({
602
727
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { className: "py-3 px-3 text-gray-900 dark:text-white font-medium", children: p.orderName }),
603
728
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { className: "py-3 px-3 text-gray-900 dark:text-white font-semibold whitespace-nowrap", children: fmt(p.amount) }),
604
729
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { className: "py-3 px-3 text-gray-500 dark:text-gray-400 whitespace-nowrap", children: p.cardBrand && p.maskedCardNumber ? `${p.cardBrand} ${p.maskedCardNumber}` : "-" }),
605
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { className: "py-3 px-3", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: `px-2 py-0.5 rounded-full text-xs font-medium ${badge.bg} ${badge.text}`, children: badge.label }) }),
730
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { className: "py-3 px-3", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: `px-2 py-0.5 rounded-full text-xs font-medium ${badge.cls}`, children: badge.label }) }),
606
731
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { className: "py-3 px-3", children: canCancelPayment(p) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
607
732
  "button",
608
733
  {
@@ -616,10 +741,10 @@ function SubscriptionManagement({
616
741
  }) })
617
742
  ] }) }) }),
618
743
  confirmModal && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "bg-white dark:bg-gray-900 rounded-2xl p-6 max-w-sm w-full shadow-xl", children: [
619
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "text-gray-900 dark:text-white font-semibold text-base mb-2", children: confirmModal.type === "cancel_subscription" ? "\uAD6C\uB3C5 \uCDE8\uC18C" : "\uACB0\uC81C \uCDE8\uC18C" }),
744
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("h3", { className: "text-gray-900 dark:text-white font-semibold text-base mb-2", children: confirmModal.type === "cancel_subscription" ? "\uAD6C\uB3C5 \uCDE8\uC18C" : confirmModal.type === "cancel_payment" ? "\uACB0\uC81C \uCDE8\uC18C" : "\uCE74\uB4DC \uC0AD\uC81C" }),
620
745
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "text-gray-500 dark:text-gray-400 text-sm mb-5", children: [
621
746
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-gray-900 dark:text-white font-medium", children: confirmModal.label }),
622
- confirmModal.type === "cancel_subscription" ? "\uC744(\uB97C) \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \uD604\uC7AC \uAD6C\uB3C5 \uAE30\uAC04\uC774 \uB05D\uB098\uBA74 \uC11C\uBE44\uC2A4\uB97C \uC774\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." : "\uC758 \uACB0\uC81C\uB97C \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \uC774 \uC791\uC5C5\uC740 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."
747
+ confirmModal.type === "cancel_subscription" ? "\uC744(\uB97C) \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \uD604\uC7AC \uAD6C\uB3C5 \uAE30\uAC04\uC774 \uB05D\uB098\uBA74 \uC11C\uBE44\uC2A4\uB97C \uC774\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." : confirmModal.type === "cancel_payment" ? "\uC758 \uACB0\uC81C\uB97C \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \uC774 \uC791\uC5C5\uC740 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." : "\uC744(\uB97C) \uC0AD\uC81C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \uC0AD\uC81C\uB41C \uCE74\uB4DC\uB294 \uBCF5\uAD6C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."
623
748
  ] }),
624
749
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2", children: [
625
750
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
@@ -633,16 +758,22 @@ function SubscriptionManagement({
633
758
  /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
634
759
  "button",
635
760
  {
636
- onClick: () => confirmModal.type === "cancel_subscription" ? handleCancelSubscription(confirmModal.id) : handleCancelPayment(confirmModal.id),
761
+ onClick: () => confirmModal.type === "cancel_subscription" ? handleCancelSubscription(confirmModal.id) : confirmModal.type === "cancel_payment" ? handleCancelPayment(confirmModal.id) : handleDeleteCard(confirmModal.id),
637
762
  disabled: !!actionLoading,
638
763
  className: "flex-1 py-2.5 rounded-lg bg-red-500 text-white text-sm font-medium hover:bg-red-600 disabled:opacity-50 transition-all",
639
- children: actionLoading ? "\uCC98\uB9AC \uC911..." : "\uC608, \uCDE8\uC18C\uD569\uB2C8\uB2E4"
764
+ children: actionLoading ? "\uCC98\uB9AC \uC911..." : "\uC608, \uD655\uC778\uD569\uB2C8\uB2E4"
640
765
  }
641
766
  )
642
767
  ] })
643
768
  ] }) })
644
769
  ] }) });
645
770
  }
771
+ function InfoRow({ label, value, bold }) {
772
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { children: [
773
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-gray-400 dark:text-gray-500", children: label }),
774
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: `ml-2 text-gray-700 dark:text-gray-300 ${bold ? "font-medium" : ""}`, children: value })
775
+ ] });
776
+ }
646
777
  // Annotate the CommonJS export names for ESM import in node:
647
778
  0 && (module.exports = {
648
779
  SubscribePage,
package/dist/index.mjs CHANGED
@@ -395,20 +395,32 @@ var DEFAULT_PRIMARY2 = "#60A5FA";
395
395
  function SubscriptionManagement({
396
396
  subscriptionsApiPath = "/api/subscribe/subscriptions",
397
397
  paymentsApiPath = "/api/subscribe/payments",
398
+ cardsApiPath = "/api/subscribe/cards",
398
399
  primaryColor = DEFAULT_PRIMARY2,
399
400
  theme = "system"
400
401
  }) {
401
402
  const [tab, setTab] = useState2("subscription");
402
403
  const [subscriptions, setSubscriptions] = useState2([]);
403
404
  const [payments, setPayments] = useState2([]);
405
+ const [cards, setCards] = useState2([]);
404
406
  const [loading, setLoading] = useState2(true);
405
407
  const [error, setError] = useState2("");
406
408
  const [actionLoading, setActionLoading] = useState2(null);
407
409
  const [confirmModal, setConfirmModal] = useState2(null);
410
+ const [showCardForm, setShowCardForm] = useState2(false);
411
+ const [cardForm, setCardForm] = useState2({
412
+ cardNumber: "",
413
+ expiryDate: "",
414
+ cvc: "",
415
+ cardPassword: "",
416
+ cardHolderName: "",
417
+ birthNumber: ""
418
+ });
419
+ const [cardError, setCardError] = useState2("");
420
+ const [cardSubmitting, setCardSubmitting] = useState2(false);
408
421
  const themeClass = theme === "dark" ? "dark" : theme === "light" ? "light-mode-override" : "";
409
422
  useEffect2(() => {
410
- fetchSubscriptions();
411
- fetchPayments();
423
+ Promise.all([fetchSubscriptions(), fetchPayments(), fetchCards()]).finally(() => setLoading(false));
412
424
  }, []);
413
425
  async function fetchSubscriptions() {
414
426
  try {
@@ -416,8 +428,6 @@ function SubscriptionManagement({
416
428
  const data = await res.json();
417
429
  if (data.success) setSubscriptions(data.subscriptions);
418
430
  } catch {
419
- } finally {
420
- setLoading(false);
421
431
  }
422
432
  }
423
433
  async function fetchPayments() {
@@ -428,6 +438,14 @@ function SubscriptionManagement({
428
438
  } catch {
429
439
  }
430
440
  }
441
+ async function fetchCards() {
442
+ try {
443
+ const res = await fetch(cardsApiPath);
444
+ const data = await res.json();
445
+ if (data.success) setCards(data.cards);
446
+ } catch {
447
+ }
448
+ }
431
449
  async function handleCancelSubscription(subscriptionId) {
432
450
  setActionLoading(subscriptionId);
433
451
  setError("");
@@ -464,8 +482,7 @@ function SubscriptionManagement({
464
482
  setError(data.error || "\uACB0\uC81C \uCDE8\uC18C\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
465
483
  return;
466
484
  }
467
- await fetchPayments();
468
- await fetchSubscriptions();
485
+ await Promise.all([fetchPayments(), fetchSubscriptions()]);
469
486
  } catch {
470
487
  setError("\uACB0\uC81C \uCDE8\uC18C \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
471
488
  } finally {
@@ -473,6 +490,51 @@ function SubscriptionManagement({
473
490
  setConfirmModal(null);
474
491
  }
475
492
  }
493
+ async function handleDeleteCard(cardId) {
494
+ setActionLoading(cardId);
495
+ setError("");
496
+ try {
497
+ const res = await fetch(cardsApiPath, {
498
+ method: "DELETE",
499
+ headers: { "Content-Type": "application/json" },
500
+ body: JSON.stringify({ cardId })
501
+ });
502
+ const data = await res.json();
503
+ if (!data.success) {
504
+ setError(data.error || "\uCE74\uB4DC \uC0AD\uC81C\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
505
+ return;
506
+ }
507
+ await fetchCards();
508
+ } catch {
509
+ setError("\uCE74\uB4DC \uC0AD\uC81C \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
510
+ } finally {
511
+ setActionLoading(null);
512
+ setConfirmModal(null);
513
+ }
514
+ }
515
+ async function handleRegisterCard() {
516
+ setCardError("");
517
+ setCardSubmitting(true);
518
+ try {
519
+ const res = await fetch(cardsApiPath, {
520
+ method: "POST",
521
+ headers: { "Content-Type": "application/json" },
522
+ body: JSON.stringify(cardForm)
523
+ });
524
+ const data = await res.json();
525
+ if (!data.success) {
526
+ setCardError(data.error || "\uCE74\uB4DC \uB4F1\uB85D\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
527
+ return;
528
+ }
529
+ await fetchCards();
530
+ setShowCardForm(false);
531
+ setCardForm({ cardNumber: "", expiryDate: "", cvc: "", cardPassword: "", cardHolderName: "", birthNumber: "" });
532
+ } catch {
533
+ setCardError("\uCE74\uB4DC \uB4F1\uB85D \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
534
+ } finally {
535
+ setCardSubmitting(false);
536
+ }
537
+ }
476
538
  function fmt(price) {
477
539
  return price.toLocaleString("ko-KR") + "\uC6D0";
478
540
  }
@@ -480,35 +542,39 @@ function SubscriptionManagement({
480
542
  return new Date(dateStr).toLocaleDateString("ko-KR", { year: "numeric", month: "long", day: "numeric" });
481
543
  }
482
544
  function statusBadge(status) {
483
- if (status === "ACTIVE") return { bg: "bg-green-100 dark:bg-green-500/20", text: "text-green-700 dark:text-green-400", label: "\uD65C\uC131" };
484
- if (status === "CANCELLED") return { bg: "bg-red-100 dark:bg-red-500/20", text: "text-red-700 dark:text-red-400", label: "\uCDE8\uC18C\uB428" };
485
- return { bg: "bg-gray-100 dark:bg-gray-700", text: "text-gray-600 dark:text-gray-400", label: status };
545
+ if (status === "ACTIVE") return { cls: "bg-green-100 dark:bg-green-500/20 text-green-700 dark:text-green-400", label: "\uD65C\uC131" };
546
+ if (status === "CANCELLED") return { cls: "bg-red-100 dark:bg-red-500/20 text-red-700 dark:text-red-400", label: "\uCDE8\uC18C\uB428" };
547
+ return { cls: "bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400", label: status };
486
548
  }
487
549
  function paymentStatusBadge(status) {
488
- if (status === "COMPLETED") return { bg: "bg-green-100 dark:bg-green-500/20", text: "text-green-700 dark:text-green-400", label: "\uC644\uB8CC" };
489
- if (status === "CANCELLED") return { bg: "bg-red-100 dark:bg-red-500/20", text: "text-red-700 dark:text-red-400", label: "\uCDE8\uC18C\uB428" };
490
- if (status === "FAILED") return { bg: "bg-orange-100 dark:bg-orange-500/20", text: "text-orange-700 dark:text-orange-400", label: "\uC2E4\uD328" };
491
- return { bg: "bg-gray-100 dark:bg-gray-700", text: "text-gray-600 dark:text-gray-400", label: status };
550
+ if (status === "COMPLETED") return { cls: "bg-green-100 dark:bg-green-500/20 text-green-700 dark:text-green-400", label: "\uC644\uB8CC" };
551
+ if (status === "CANCELLED") return { cls: "bg-red-100 dark:bg-red-500/20 text-red-700 dark:text-red-400", label: "\uCDE8\uC18C\uB428" };
552
+ if (status === "FAILED") return { cls: "bg-orange-100 dark:bg-orange-500/20 text-orange-700 dark:text-orange-400", label: "\uC2E4\uD328" };
553
+ return { cls: "bg-gray-100 dark:bg-gray-700 text-gray-600 dark:text-gray-400", label: status };
492
554
  }
493
555
  function canCancelPayment(p) {
494
556
  if (p.status !== "COMPLETED") return false;
495
- const paidDate = new Date(p.paidAt || p.createdAt);
496
- const daysDiff = Math.ceil((Date.now() - paidDate.getTime()) / (1e3 * 3600 * 24));
557
+ const daysDiff = Math.ceil((Date.now() - new Date(p.paidAt || p.createdAt).getTime()) / (1e3 * 3600 * 24));
497
558
  return daysDiff <= 7;
498
559
  }
499
560
  if (loading) return /* @__PURE__ */ jsx2("div", { className: themeClass, children: /* @__PURE__ */ jsx2("div", { className: "text-gray-400 py-8 text-center", children: "\uBD88\uB7EC\uC624\uB294 \uC911..." }) });
561
+ const tabs = [
562
+ { key: "subscription", label: "\uAD6C\uB3C5 \uC815\uBCF4" },
563
+ { key: "cards", label: "\uACB0\uC81C\uC218\uB2E8" },
564
+ { key: "payments", label: "\uACB0\uC81C \uC774\uB825" }
565
+ ];
500
566
  return /* @__PURE__ */ jsx2("div", { className: themeClass, children: /* @__PURE__ */ jsxs2("div", { className: "space-y-4", children: [
501
567
  error && /* @__PURE__ */ jsx2("div", { className: "p-3 bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/30 rounded-lg text-red-600 dark:text-red-400 text-sm", children: error }),
502
- /* @__PURE__ */ jsx2("div", { className: "flex border-b border-gray-200 dark:border-white/10", children: [
503
- { key: "subscription", label: "\uAD6C\uB3C5 \uC815\uBCF4" },
504
- { key: "payments", label: "\uACB0\uC81C \uC774\uB825" }
505
- ].map(({ key, label }) => /* @__PURE__ */ jsx2(
568
+ /* @__PURE__ */ jsx2("div", { className: "flex border-b border-gray-200 dark:border-white/10", children: tabs.map(({ key, label }) => /* @__PURE__ */ jsxs2(
506
569
  "button",
507
570
  {
508
571
  onClick: () => setTab(key),
509
- className: `px-4 py-2.5 text-sm font-medium border-b-2 transition-colors -mb-px ${tab === key ? "border-current text-blue-600 dark:text-blue-400" : "border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"}`,
572
+ className: `px-4 py-2.5 text-sm font-medium border-b-2 transition-colors -mb-px ${tab === key ? "border-current" : "border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300"}`,
510
573
  style: tab === key ? { color: primaryColor, borderColor: primaryColor } : void 0,
511
- children: label
574
+ children: [
575
+ label,
576
+ key === "cards" && cards.length > 0 && /* @__PURE__ */ jsx2("span", { className: "ml-1.5 px-1.5 py-0.5 rounded-full text-xs bg-gray-100 dark:bg-white/10 text-gray-500 dark:text-gray-400", children: cards.length })
577
+ ]
512
578
  },
513
579
  key
514
580
  )) }),
@@ -518,42 +584,16 @@ function SubscriptionManagement({
518
584
  /* @__PURE__ */ jsxs2("div", { className: "space-y-2 flex-1 min-w-0", children: [
519
585
  /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 flex-wrap", children: [
520
586
  /* @__PURE__ */ jsx2("span", { className: "text-gray-900 dark:text-white font-semibold text-base", children: sub.plan.name }),
521
- /* @__PURE__ */ jsx2("span", { className: `px-2 py-0.5 rounded-full text-xs font-medium ${badge.bg} ${badge.text}`, children: badge.label }),
587
+ /* @__PURE__ */ jsx2("span", { className: `px-2 py-0.5 rounded-full text-xs font-medium ${badge.cls}`, children: badge.label }),
522
588
  sub.isTrial && /* @__PURE__ */ jsx2("span", { className: "px-2 py-0.5 rounded-full text-xs font-medium bg-purple-100 dark:bg-purple-500/20 text-purple-700 dark:text-purple-400", children: "\uCCB4\uD5D8" })
523
589
  ] }),
524
590
  /* @__PURE__ */ jsxs2("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-x-8 gap-y-1 text-sm", children: [
525
- /* @__PURE__ */ jsxs2("div", { children: [
526
- /* @__PURE__ */ jsx2("span", { className: "text-gray-400 dark:text-gray-500", children: "\uAE08\uC561" }),
527
- /* @__PURE__ */ jsxs2("span", { className: "ml-2 text-gray-700 dark:text-gray-300 font-medium", children: [
528
- fmt(sub.plan.price),
529
- " / ",
530
- sub.plan.interval,
531
- "\uAC1C\uC6D4"
532
- ] })
533
- ] }),
534
- /* @__PURE__ */ jsxs2("div", { children: [
535
- /* @__PURE__ */ jsx2("span", { className: "text-gray-400 dark:text-gray-500", children: "\uC0C1\uD0DC" }),
536
- /* @__PURE__ */ jsx2("span", { className: "ml-2 text-gray-700 dark:text-gray-300", children: sub.statusMessage })
537
- ] }),
538
- /* @__PURE__ */ jsxs2("div", { children: [
539
- /* @__PURE__ */ jsx2("span", { className: "text-gray-400 dark:text-gray-500", children: "\uC2DC\uC791\uC77C" }),
540
- /* @__PURE__ */ jsx2("span", { className: "ml-2 text-gray-700 dark:text-gray-300", children: fmtDate(sub.startDate) })
541
- ] }),
542
- /* @__PURE__ */ jsxs2("div", { children: [
543
- /* @__PURE__ */ jsx2("span", { className: "text-gray-400 dark:text-gray-500", children: "\uB9CC\uB8CC\uC77C" }),
544
- /* @__PURE__ */ jsx2("span", { className: "ml-2 text-gray-700 dark:text-gray-300", children: fmtDate(sub.endDate) })
545
- ] }),
546
- /* @__PURE__ */ jsxs2("div", { children: [
547
- /* @__PURE__ */ jsx2("span", { className: "text-gray-400 dark:text-gray-500", children: "\uB0A8\uC740 \uAE30\uAC04" }),
548
- /* @__PURE__ */ jsxs2("span", { className: "ml-2 text-gray-700 dark:text-gray-300 font-medium", children: [
549
- sub.remainingDays,
550
- "\uC77C"
551
- ] })
552
- ] }),
553
- /* @__PURE__ */ jsxs2("div", { children: [
554
- /* @__PURE__ */ jsx2("span", { className: "text-gray-400 dark:text-gray-500", children: "\uC790\uB3D9\uACB0\uC81C" }),
555
- /* @__PURE__ */ jsx2("span", { className: "ml-2 text-gray-700 dark:text-gray-300", children: sub.autoRenew ? "\uC0AC\uC6A9" : "\uD574\uC81C" })
556
- ] })
591
+ /* @__PURE__ */ jsx2(InfoRow, { label: "\uAE08\uC561", value: `${fmt(sub.plan.price)} / ${sub.plan.interval}\uAC1C\uC6D4`, bold: true }),
592
+ /* @__PURE__ */ jsx2(InfoRow, { label: "\uC0C1\uD0DC", value: sub.statusMessage }),
593
+ /* @__PURE__ */ jsx2(InfoRow, { label: "\uC2DC\uC791\uC77C", value: fmtDate(sub.startDate) }),
594
+ /* @__PURE__ */ jsx2(InfoRow, { label: "\uB9CC\uB8CC\uC77C", value: fmtDate(sub.endDate) }),
595
+ /* @__PURE__ */ jsx2(InfoRow, { label: "\uB0A8\uC740 \uAE30\uAC04", value: `${sub.remainingDays}\uC77C`, bold: true }),
596
+ /* @__PURE__ */ jsx2(InfoRow, { label: "\uC790\uB3D9\uACB0\uC81C", value: sub.autoRenew ? "\uC0AC\uC6A9" : "\uD574\uC81C" })
557
597
  ] })
558
598
  ] }),
559
599
  sub.status === "ACTIVE" && /* @__PURE__ */ jsx2(
@@ -567,7 +607,92 @@ function SubscriptionManagement({
567
607
  )
568
608
  ] }) }, sub.id);
569
609
  }) }),
570
- tab === "payments" && /* @__PURE__ */ jsx2("div", { className: "space-y-2", children: payments.length === 0 ? /* @__PURE__ */ jsx2("div", { className: "text-center py-12 text-gray-400 dark:text-gray-500 text-sm", children: "\uACB0\uC81C \uC774\uB825\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }) : /* @__PURE__ */ jsx2("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxs2("table", { className: "w-full text-sm", children: [
610
+ tab === "cards" && /* @__PURE__ */ jsxs2("div", { className: "space-y-3", children: [
611
+ cards.length > 0 && /* @__PURE__ */ jsx2("div", { className: "space-y-2", children: cards.map((card) => /* @__PURE__ */ jsxs2("div", { className: "p-4 rounded-xl border border-gray-200 dark:border-white/10 bg-white dark:bg-white/5 flex items-center justify-between gap-3", children: [
612
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-3 min-w-0", children: [
613
+ /* @__PURE__ */ jsx2("div", { className: "w-10 h-7 rounded bg-gray-100 dark:bg-white/10 flex items-center justify-center text-xs font-bold text-gray-500 dark:text-gray-400 flex-shrink-0", children: card.cardBrand?.slice(0, 4) || "CARD" }),
614
+ /* @__PURE__ */ jsxs2("div", { className: "min-w-0", children: [
615
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
616
+ /* @__PURE__ */ jsx2("span", { className: "text-gray-900 dark:text-white font-medium text-sm", children: card.cardBrand }),
617
+ /* @__PURE__ */ jsx2("span", { className: "text-gray-500 dark:text-gray-400 text-sm", children: card.maskedCardNumber }),
618
+ card.isDefault && /* @__PURE__ */ jsx2("span", { className: "px-1.5 py-0.5 rounded text-xs font-medium bg-blue-100 dark:bg-blue-500/20 text-blue-600 dark:text-blue-400", children: "\uAE30\uBCF8" })
619
+ ] }),
620
+ /* @__PURE__ */ jsxs2("div", { className: "text-xs text-gray-400 dark:text-gray-500 mt-0.5", children: [
621
+ card.cardHolderName,
622
+ " \xB7 ",
623
+ card.expiryMonth,
624
+ "/",
625
+ card.expiryYear
626
+ ] })
627
+ ] })
628
+ ] }),
629
+ /* @__PURE__ */ jsx2(
630
+ "button",
631
+ {
632
+ onClick: () => setConfirmModal({ type: "delete_card", id: card.id, label: `${card.cardBrand} ${card.maskedCardNumber}` }),
633
+ disabled: actionLoading === card.id,
634
+ className: "text-xs text-red-500 dark:text-red-400 hover:underline disabled:opacity-50 flex-shrink-0",
635
+ children: "\uC0AD\uC81C"
636
+ }
637
+ )
638
+ ] }, card.id)) }),
639
+ !showCardForm ? /* @__PURE__ */ jsx2(
640
+ "button",
641
+ {
642
+ onClick: () => setShowCardForm(true),
643
+ className: "w-full p-4 rounded-xl border border-dashed border-gray-300 dark:border-white/20 text-gray-400 dark:text-gray-500 hover:border-gray-400 dark:hover:border-white/40 hover:text-gray-500 dark:hover:text-gray-300 transition-all text-sm",
644
+ children: "+ \uC0C8 \uCE74\uB4DC \uCD94\uAC00"
645
+ }
646
+ ) : /* @__PURE__ */ jsxs2("div", { className: "p-4 rounded-xl border border-gray-200 dark:border-white/10 bg-gray-50 dark:bg-white/5 space-y-3", children: [
647
+ /* @__PURE__ */ jsx2("h3", { className: "text-gray-900 dark:text-white text-sm font-medium", children: "\uCE74\uB4DC \uC815\uBCF4 \uC785\uB825" }),
648
+ cardError && /* @__PURE__ */ jsx2("div", { className: "p-3 bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/30 rounded-lg text-red-600 dark:text-red-400 text-xs", children: cardError }),
649
+ /* @__PURE__ */ jsx2("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3", children: [
650
+ { label: "\uCE74\uB4DC \uBC88\uD638", key: "cardNumber", placeholder: "0000-0000-0000-0000", type: "text", full: true },
651
+ { label: "\uB9CC\uB8CC\uC77C", key: "expiryDate", placeholder: "MM/YY", type: "text", full: false },
652
+ { label: "CVC", key: "cvc", placeholder: "3\uC790\uB9AC", type: "password", full: false },
653
+ { label: "\uCE74\uB4DC \uBE44\uBC00\uBC88\uD638 \uC55E 2\uC790\uB9AC", key: "cardPassword", placeholder: "\u2022\u2022", type: "password", full: false },
654
+ { label: "\uCE74\uB4DC \uC18C\uC720\uC790\uBA85", key: "cardHolderName", placeholder: "\uD64D\uAE38\uB3D9", type: "text", full: false },
655
+ { label: "\uC0DD\uB144\uC6D4\uC77C 6\uC790\uB9AC / \uC0AC\uC5C5\uC790\uBC88\uD638 10\uC790\uB9AC", key: "birthNumber", placeholder: "990101", type: "text", full: true }
656
+ ].map(({ label, key, placeholder, type, full }) => /* @__PURE__ */ jsxs2("div", { className: full ? "sm:col-span-2" : "", children: [
657
+ /* @__PURE__ */ jsx2("label", { className: "text-xs text-gray-500 dark:text-gray-400 block mb-1", children: label }),
658
+ /* @__PURE__ */ jsx2(
659
+ "input",
660
+ {
661
+ type,
662
+ placeholder,
663
+ value: cardForm[key],
664
+ onChange: (e) => setCardForm((f) => ({ ...f, [key]: e.target.value })),
665
+ className: "w-full bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-lg px-3 py-2 text-gray-900 dark:text-white text-sm placeholder-gray-300 dark:placeholder-gray-600 focus:outline-none focus:border-blue-500 transition-colors"
666
+ }
667
+ )
668
+ ] }, key)) }),
669
+ /* @__PURE__ */ jsxs2("div", { className: "flex gap-2 pt-1", children: [
670
+ /* @__PURE__ */ jsx2(
671
+ "button",
672
+ {
673
+ onClick: () => {
674
+ setShowCardForm(false);
675
+ setCardError("");
676
+ },
677
+ className: "flex-1 py-2.5 rounded-lg border border-gray-200 dark:border-white/10 text-gray-500 dark:text-gray-400 text-sm hover:border-gray-300 dark:hover:border-white/20 transition-all",
678
+ children: "\uCDE8\uC18C"
679
+ }
680
+ ),
681
+ /* @__PURE__ */ jsx2(
682
+ "button",
683
+ {
684
+ onClick: handleRegisterCard,
685
+ disabled: cardSubmitting,
686
+ style: { backgroundColor: primaryColor },
687
+ className: "flex-1 py-2.5 rounded-lg text-white text-sm font-medium hover:opacity-90 disabled:opacity-50 transition-all",
688
+ children: cardSubmitting ? "\uB4F1\uB85D \uC911..." : "\uCE74\uB4DC \uB4F1\uB85D"
689
+ }
690
+ )
691
+ ] })
692
+ ] }),
693
+ cards.length === 0 && !showCardForm && /* @__PURE__ */ jsx2("div", { className: "text-center py-8 text-gray-400 dark:text-gray-500 text-sm", children: "\uB4F1\uB85D\uB41C \uACB0\uC81C\uC218\uB2E8\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." })
694
+ ] }),
695
+ tab === "payments" && /* @__PURE__ */ jsx2("div", { className: "space-y-2", children: payments.length === 0 ? /* @__PURE__ */ jsx2("div", { className: "text-center py-12 text-gray-400 dark:text-gray-500 text-sm", children: "\uACB0\uC81C \uC774\uB825\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }) : /* @__PURE__ */ jsx2("div", { className: "overflow-x-auto -mx-4 sm:mx-0", children: /* @__PURE__ */ jsxs2("table", { className: "w-full text-sm min-w-[600px]", children: [
571
696
  /* @__PURE__ */ jsx2("thead", { children: /* @__PURE__ */ jsx2("tr", { className: "border-b border-gray-200 dark:border-white/10", children: ["\uC77C\uC2DC", "\uB0B4\uC6A9", "\uAE08\uC561", "\uACB0\uC81C\uC218\uB2E8", "\uC0C1\uD0DC", ""].map((h) => /* @__PURE__ */ jsx2("th", { className: "text-left py-2.5 px-3 text-xs font-medium text-gray-400 dark:text-gray-500 whitespace-nowrap", children: h }, h)) }) }),
572
697
  /* @__PURE__ */ jsx2("tbody", { children: payments.map((p) => {
573
698
  const badge = paymentStatusBadge(p.status);
@@ -576,7 +701,7 @@ function SubscriptionManagement({
576
701
  /* @__PURE__ */ jsx2("td", { className: "py-3 px-3 text-gray-900 dark:text-white font-medium", children: p.orderName }),
577
702
  /* @__PURE__ */ jsx2("td", { className: "py-3 px-3 text-gray-900 dark:text-white font-semibold whitespace-nowrap", children: fmt(p.amount) }),
578
703
  /* @__PURE__ */ jsx2("td", { className: "py-3 px-3 text-gray-500 dark:text-gray-400 whitespace-nowrap", children: p.cardBrand && p.maskedCardNumber ? `${p.cardBrand} ${p.maskedCardNumber}` : "-" }),
579
- /* @__PURE__ */ jsx2("td", { className: "py-3 px-3", children: /* @__PURE__ */ jsx2("span", { className: `px-2 py-0.5 rounded-full text-xs font-medium ${badge.bg} ${badge.text}`, children: badge.label }) }),
704
+ /* @__PURE__ */ jsx2("td", { className: "py-3 px-3", children: /* @__PURE__ */ jsx2("span", { className: `px-2 py-0.5 rounded-full text-xs font-medium ${badge.cls}`, children: badge.label }) }),
580
705
  /* @__PURE__ */ jsx2("td", { className: "py-3 px-3", children: canCancelPayment(p) && /* @__PURE__ */ jsx2(
581
706
  "button",
582
707
  {
@@ -590,10 +715,10 @@ function SubscriptionManagement({
590
715
  }) })
591
716
  ] }) }) }),
592
717
  confirmModal && /* @__PURE__ */ jsx2("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50 p-4", children: /* @__PURE__ */ jsxs2("div", { className: "bg-white dark:bg-gray-900 rounded-2xl p-6 max-w-sm w-full shadow-xl", children: [
593
- /* @__PURE__ */ jsx2("h3", { className: "text-gray-900 dark:text-white font-semibold text-base mb-2", children: confirmModal.type === "cancel_subscription" ? "\uAD6C\uB3C5 \uCDE8\uC18C" : "\uACB0\uC81C \uCDE8\uC18C" }),
718
+ /* @__PURE__ */ jsx2("h3", { className: "text-gray-900 dark:text-white font-semibold text-base mb-2", children: confirmModal.type === "cancel_subscription" ? "\uAD6C\uB3C5 \uCDE8\uC18C" : confirmModal.type === "cancel_payment" ? "\uACB0\uC81C \uCDE8\uC18C" : "\uCE74\uB4DC \uC0AD\uC81C" }),
594
719
  /* @__PURE__ */ jsxs2("p", { className: "text-gray-500 dark:text-gray-400 text-sm mb-5", children: [
595
720
  /* @__PURE__ */ jsx2("span", { className: "text-gray-900 dark:text-white font-medium", children: confirmModal.label }),
596
- confirmModal.type === "cancel_subscription" ? "\uC744(\uB97C) \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \uD604\uC7AC \uAD6C\uB3C5 \uAE30\uAC04\uC774 \uB05D\uB098\uBA74 \uC11C\uBE44\uC2A4\uB97C \uC774\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." : "\uC758 \uACB0\uC81C\uB97C \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \uC774 \uC791\uC5C5\uC740 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."
721
+ confirmModal.type === "cancel_subscription" ? "\uC744(\uB97C) \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \uD604\uC7AC \uAD6C\uB3C5 \uAE30\uAC04\uC774 \uB05D\uB098\uBA74 \uC11C\uBE44\uC2A4\uB97C \uC774\uC6A9\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." : confirmModal.type === "cancel_payment" ? "\uC758 \uACB0\uC81C\uB97C \uCDE8\uC18C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \uC774 \uC791\uC5C5\uC740 \uB418\uB3CC\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4." : "\uC744(\uB97C) \uC0AD\uC81C\uD558\uC2DC\uACA0\uC2B5\uB2C8\uAE4C? \uC0AD\uC81C\uB41C \uCE74\uB4DC\uB294 \uBCF5\uAD6C\uD560 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4."
597
722
  ] }),
598
723
  /* @__PURE__ */ jsxs2("div", { className: "flex gap-2", children: [
599
724
  /* @__PURE__ */ jsx2(
@@ -607,16 +732,22 @@ function SubscriptionManagement({
607
732
  /* @__PURE__ */ jsx2(
608
733
  "button",
609
734
  {
610
- onClick: () => confirmModal.type === "cancel_subscription" ? handleCancelSubscription(confirmModal.id) : handleCancelPayment(confirmModal.id),
735
+ onClick: () => confirmModal.type === "cancel_subscription" ? handleCancelSubscription(confirmModal.id) : confirmModal.type === "cancel_payment" ? handleCancelPayment(confirmModal.id) : handleDeleteCard(confirmModal.id),
611
736
  disabled: !!actionLoading,
612
737
  className: "flex-1 py-2.5 rounded-lg bg-red-500 text-white text-sm font-medium hover:bg-red-600 disabled:opacity-50 transition-all",
613
- children: actionLoading ? "\uCC98\uB9AC \uC911..." : "\uC608, \uCDE8\uC18C\uD569\uB2C8\uB2E4"
738
+ children: actionLoading ? "\uCC98\uB9AC \uC911..." : "\uC608, \uD655\uC778\uD569\uB2C8\uB2E4"
614
739
  }
615
740
  )
616
741
  ] })
617
742
  ] }) })
618
743
  ] }) });
619
744
  }
745
+ function InfoRow({ label, value, bold }) {
746
+ return /* @__PURE__ */ jsxs2("div", { children: [
747
+ /* @__PURE__ */ jsx2("span", { className: "text-gray-400 dark:text-gray-500", children: label }),
748
+ /* @__PURE__ */ jsx2("span", { className: `ml-2 text-gray-700 dark:text-gray-300 ${bold ? "font-medium" : ""}`, children: value })
749
+ ] });
750
+ }
620
751
  export {
621
752
  SubscribePage,
622
753
  SubscriptionManagement
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.0.4",
6
+ "version": "1.0.5",
7
7
  "description": "Subscription UI components for ThinkingCat services",
8
8
  "main": "dist/index.js",
9
9
  "module": "dist/index.mjs",