@thinkingcat/subscription-ui 1.0.3 → 1.0.4
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 +49 -1
- package/dist/index.d.ts +49 -1
- package/dist/index.js +237 -3
- package/dist/index.mjs +235 -2
- package/package.json +1 -1
package/dist/index.d.mts
CHANGED
|
@@ -44,7 +44,55 @@ interface SubscribePageConfig {
|
|
|
44
44
|
*/
|
|
45
45
|
theme?: "light" | "dark" | "system";
|
|
46
46
|
}
|
|
47
|
+
interface Subscription {
|
|
48
|
+
id: string;
|
|
49
|
+
status: string;
|
|
50
|
+
startDate: string;
|
|
51
|
+
endDate: string;
|
|
52
|
+
currentPeriodStart: string;
|
|
53
|
+
currentPeriodEnd: string;
|
|
54
|
+
autoRenew: boolean;
|
|
55
|
+
isTrial: boolean;
|
|
56
|
+
plan: {
|
|
57
|
+
id: string;
|
|
58
|
+
name: string;
|
|
59
|
+
price: number;
|
|
60
|
+
interval: number;
|
|
61
|
+
};
|
|
62
|
+
service: {
|
|
63
|
+
serviceId: string;
|
|
64
|
+
displayName: string;
|
|
65
|
+
};
|
|
66
|
+
remainingDays: number;
|
|
67
|
+
statusMessage: string;
|
|
68
|
+
}
|
|
69
|
+
interface Payment {
|
|
70
|
+
id: string;
|
|
71
|
+
paymentId: string;
|
|
72
|
+
orderName: string;
|
|
73
|
+
amount: number;
|
|
74
|
+
status: string;
|
|
75
|
+
paidAt: string;
|
|
76
|
+
createdAt: string;
|
|
77
|
+
planName: string | null;
|
|
78
|
+
serviceName: string | null;
|
|
79
|
+
maskedCardNumber: string | null;
|
|
80
|
+
cardBrand: string | null;
|
|
81
|
+
subscriptionId: string | null;
|
|
82
|
+
}
|
|
83
|
+
interface SubscriptionManagementConfig {
|
|
84
|
+
/** 구독 조회/취소 API 경로 (default: /api/subscribe/subscriptions) */
|
|
85
|
+
subscriptionsApiPath?: string;
|
|
86
|
+
/** 결제 이력/취소 API 경로 (default: /api/subscribe/payments) */
|
|
87
|
+
paymentsApiPath?: string;
|
|
88
|
+
/** 브랜드 색상 (default: #60A5FA) */
|
|
89
|
+
primaryColor?: string;
|
|
90
|
+
/** 테마 (default: "system") */
|
|
91
|
+
theme?: "light" | "dark" | "system";
|
|
92
|
+
}
|
|
47
93
|
|
|
48
94
|
declare function SubscribePage({ plansApiPath, cardsApiPath, payApiPath, callbackUrl, serviceName, primaryColor, showExpiredBanner, theme, }: SubscribePageConfig): react_jsx_runtime.JSX.Element;
|
|
49
95
|
|
|
50
|
-
|
|
96
|
+
declare function SubscriptionManagement({ subscriptionsApiPath, paymentsApiPath, primaryColor, theme, }: SubscriptionManagementConfig): react_jsx_runtime.JSX.Element;
|
|
97
|
+
|
|
98
|
+
export { type Card, type Payment, type Plan, SubscribePage, type SubscribePageConfig, type Subscription, SubscriptionManagement, type SubscriptionManagementConfig };
|
package/dist/index.d.ts
CHANGED
|
@@ -44,7 +44,55 @@ interface SubscribePageConfig {
|
|
|
44
44
|
*/
|
|
45
45
|
theme?: "light" | "dark" | "system";
|
|
46
46
|
}
|
|
47
|
+
interface Subscription {
|
|
48
|
+
id: string;
|
|
49
|
+
status: string;
|
|
50
|
+
startDate: string;
|
|
51
|
+
endDate: string;
|
|
52
|
+
currentPeriodStart: string;
|
|
53
|
+
currentPeriodEnd: string;
|
|
54
|
+
autoRenew: boolean;
|
|
55
|
+
isTrial: boolean;
|
|
56
|
+
plan: {
|
|
57
|
+
id: string;
|
|
58
|
+
name: string;
|
|
59
|
+
price: number;
|
|
60
|
+
interval: number;
|
|
61
|
+
};
|
|
62
|
+
service: {
|
|
63
|
+
serviceId: string;
|
|
64
|
+
displayName: string;
|
|
65
|
+
};
|
|
66
|
+
remainingDays: number;
|
|
67
|
+
statusMessage: string;
|
|
68
|
+
}
|
|
69
|
+
interface Payment {
|
|
70
|
+
id: string;
|
|
71
|
+
paymentId: string;
|
|
72
|
+
orderName: string;
|
|
73
|
+
amount: number;
|
|
74
|
+
status: string;
|
|
75
|
+
paidAt: string;
|
|
76
|
+
createdAt: string;
|
|
77
|
+
planName: string | null;
|
|
78
|
+
serviceName: string | null;
|
|
79
|
+
maskedCardNumber: string | null;
|
|
80
|
+
cardBrand: string | null;
|
|
81
|
+
subscriptionId: string | null;
|
|
82
|
+
}
|
|
83
|
+
interface SubscriptionManagementConfig {
|
|
84
|
+
/** 구독 조회/취소 API 경로 (default: /api/subscribe/subscriptions) */
|
|
85
|
+
subscriptionsApiPath?: string;
|
|
86
|
+
/** 결제 이력/취소 API 경로 (default: /api/subscribe/payments) */
|
|
87
|
+
paymentsApiPath?: string;
|
|
88
|
+
/** 브랜드 색상 (default: #60A5FA) */
|
|
89
|
+
primaryColor?: string;
|
|
90
|
+
/** 테마 (default: "system") */
|
|
91
|
+
theme?: "light" | "dark" | "system";
|
|
92
|
+
}
|
|
47
93
|
|
|
48
94
|
declare function SubscribePage({ plansApiPath, cardsApiPath, payApiPath, callbackUrl, serviceName, primaryColor, showExpiredBanner, theme, }: SubscribePageConfig): react_jsx_runtime.JSX.Element;
|
|
49
95
|
|
|
50
|
-
|
|
96
|
+
declare function SubscriptionManagement({ subscriptionsApiPath, paymentsApiPath, primaryColor, theme, }: SubscriptionManagementConfig): react_jsx_runtime.JSX.Element;
|
|
97
|
+
|
|
98
|
+
export { type Card, type Payment, type Plan, SubscribePage, type SubscribePageConfig, type Subscription, SubscriptionManagement, type SubscriptionManagementConfig };
|
package/dist/index.js
CHANGED
|
@@ -21,7 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
// src/index.ts
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
|
-
SubscribePage: () => SubscribePage
|
|
24
|
+
SubscribePage: () => SubscribePage,
|
|
25
|
+
SubscriptionManagement: () => SubscriptionManagement
|
|
25
26
|
});
|
|
26
27
|
module.exports = __toCommonJS(index_exports);
|
|
27
28
|
|
|
@@ -127,8 +128,10 @@ function SubscribePage({
|
|
|
127
128
|
return;
|
|
128
129
|
}
|
|
129
130
|
setSuccess(true);
|
|
131
|
+
const separator = callbackUrl.includes("?") ? "&" : "?";
|
|
132
|
+
const redirectUrl = `${callbackUrl}${separator}subscribed=true`;
|
|
130
133
|
setTimeout(() => {
|
|
131
|
-
window.location.href =
|
|
134
|
+
window.location.href = redirectUrl;
|
|
132
135
|
}, 2e3);
|
|
133
136
|
} catch {
|
|
134
137
|
setError("\uAD6C\uB3C5 \uCC98\uB9AC \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
@@ -410,7 +413,238 @@ function PageLayout({ children, themeClass }) {
|
|
|
410
413
|
function CheckIcon({ className }) {
|
|
411
414
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { className, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) });
|
|
412
415
|
}
|
|
416
|
+
|
|
417
|
+
// src/components/SubscriptionManagement.tsx
|
|
418
|
+
var import_react2 = require("react");
|
|
419
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
420
|
+
var DEFAULT_PRIMARY2 = "#60A5FA";
|
|
421
|
+
function SubscriptionManagement({
|
|
422
|
+
subscriptionsApiPath = "/api/subscribe/subscriptions",
|
|
423
|
+
paymentsApiPath = "/api/subscribe/payments",
|
|
424
|
+
primaryColor = DEFAULT_PRIMARY2,
|
|
425
|
+
theme = "system"
|
|
426
|
+
}) {
|
|
427
|
+
const [tab, setTab] = (0, import_react2.useState)("subscription");
|
|
428
|
+
const [subscriptions, setSubscriptions] = (0, import_react2.useState)([]);
|
|
429
|
+
const [payments, setPayments] = (0, import_react2.useState)([]);
|
|
430
|
+
const [loading, setLoading] = (0, import_react2.useState)(true);
|
|
431
|
+
const [error, setError] = (0, import_react2.useState)("");
|
|
432
|
+
const [actionLoading, setActionLoading] = (0, import_react2.useState)(null);
|
|
433
|
+
const [confirmModal, setConfirmModal] = (0, import_react2.useState)(null);
|
|
434
|
+
const themeClass = theme === "dark" ? "dark" : theme === "light" ? "light-mode-override" : "";
|
|
435
|
+
(0, import_react2.useEffect)(() => {
|
|
436
|
+
fetchSubscriptions();
|
|
437
|
+
fetchPayments();
|
|
438
|
+
}, []);
|
|
439
|
+
async function fetchSubscriptions() {
|
|
440
|
+
try {
|
|
441
|
+
const res = await fetch(subscriptionsApiPath);
|
|
442
|
+
const data = await res.json();
|
|
443
|
+
if (data.success) setSubscriptions(data.subscriptions);
|
|
444
|
+
} catch {
|
|
445
|
+
} finally {
|
|
446
|
+
setLoading(false);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
async function fetchPayments() {
|
|
450
|
+
try {
|
|
451
|
+
const res = await fetch(paymentsApiPath);
|
|
452
|
+
const data = await res.json();
|
|
453
|
+
if (data.success) setPayments(data.payments);
|
|
454
|
+
} catch {
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
async function handleCancelSubscription(subscriptionId) {
|
|
458
|
+
setActionLoading(subscriptionId);
|
|
459
|
+
setError("");
|
|
460
|
+
try {
|
|
461
|
+
const res = await fetch(subscriptionsApiPath, {
|
|
462
|
+
method: "DELETE",
|
|
463
|
+
headers: { "Content-Type": "application/json" },
|
|
464
|
+
body: JSON.stringify({ subscriptionId })
|
|
465
|
+
});
|
|
466
|
+
const data = await res.json();
|
|
467
|
+
if (!data.success) {
|
|
468
|
+
setError(data.error || "\uAD6C\uB3C5 \uCDE8\uC18C\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
await fetchSubscriptions();
|
|
472
|
+
} catch {
|
|
473
|
+
setError("\uAD6C\uB3C5 \uCDE8\uC18C \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
474
|
+
} finally {
|
|
475
|
+
setActionLoading(null);
|
|
476
|
+
setConfirmModal(null);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
async function handleCancelPayment(paymentId) {
|
|
480
|
+
setActionLoading(paymentId);
|
|
481
|
+
setError("");
|
|
482
|
+
try {
|
|
483
|
+
const res = await fetch(paymentsApiPath, {
|
|
484
|
+
method: "POST",
|
|
485
|
+
headers: { "Content-Type": "application/json" },
|
|
486
|
+
body: JSON.stringify({ paymentId })
|
|
487
|
+
});
|
|
488
|
+
const data = await res.json();
|
|
489
|
+
if (!data.success) {
|
|
490
|
+
setError(data.error || "\uACB0\uC81C \uCDE8\uC18C\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
await fetchPayments();
|
|
494
|
+
await fetchSubscriptions();
|
|
495
|
+
} catch {
|
|
496
|
+
setError("\uACB0\uC81C \uCDE8\uC18C \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
497
|
+
} finally {
|
|
498
|
+
setActionLoading(null);
|
|
499
|
+
setConfirmModal(null);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
function fmt(price) {
|
|
503
|
+
return price.toLocaleString("ko-KR") + "\uC6D0";
|
|
504
|
+
}
|
|
505
|
+
function fmtDate(dateStr) {
|
|
506
|
+
return new Date(dateStr).toLocaleDateString("ko-KR", { year: "numeric", month: "long", day: "numeric" });
|
|
507
|
+
}
|
|
508
|
+
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 };
|
|
512
|
+
}
|
|
513
|
+
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 };
|
|
518
|
+
}
|
|
519
|
+
function canCancelPayment(p) {
|
|
520
|
+
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));
|
|
523
|
+
return daysDiff <= 7;
|
|
524
|
+
}
|
|
525
|
+
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..." }) });
|
|
526
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: themeClass, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-4", children: [
|
|
527
|
+
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)(
|
|
532
|
+
"button",
|
|
533
|
+
{
|
|
534
|
+
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"}`,
|
|
536
|
+
style: tab === key ? { color: primaryColor, borderColor: primaryColor } : void 0,
|
|
537
|
+
children: label
|
|
538
|
+
},
|
|
539
|
+
key
|
|
540
|
+
)) }),
|
|
541
|
+
tab === "subscription" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "space-y-3", children: subscriptions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "text-center py-12 text-gray-400 dark:text-gray-500 text-sm", children: "\uD65C\uC131 \uAD6C\uB3C5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }) : subscriptions.map((sub) => {
|
|
542
|
+
const badge = statusBadge(sub.status);
|
|
543
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "p-4 sm:p-5 rounded-xl border border-gray-200 dark:border-white/10 bg-white dark:bg-white/5", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3", children: [
|
|
544
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "space-y-2 flex-1 min-w-0", children: [
|
|
545
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2 flex-wrap", children: [
|
|
546
|
+
/* @__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 }),
|
|
548
|
+
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
|
+
] }),
|
|
550
|
+
/* @__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
|
+
] })
|
|
583
|
+
] })
|
|
584
|
+
] }),
|
|
585
|
+
sub.status === "ACTIVE" && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
586
|
+
"button",
|
|
587
|
+
{
|
|
588
|
+
onClick: () => setConfirmModal({ type: "cancel_subscription", id: sub.id, label: sub.plan.name }),
|
|
589
|
+
disabled: actionLoading === sub.id,
|
|
590
|
+
className: "flex-shrink-0 px-4 py-2 rounded-lg border border-red-200 dark:border-red-500/30 text-red-600 dark:text-red-400 text-sm font-medium hover:bg-red-50 dark:hover:bg-red-500/10 disabled:opacity-50 transition-all",
|
|
591
|
+
children: "\uAD6C\uB3C5 \uCDE8\uC18C"
|
|
592
|
+
}
|
|
593
|
+
)
|
|
594
|
+
] }) }, sub.id);
|
|
595
|
+
}) }),
|
|
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: [
|
|
597
|
+
/* @__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
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: payments.map((p) => {
|
|
599
|
+
const badge = paymentStatusBadge(p.status);
|
|
600
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { className: "border-b border-gray-100 dark:border-white/5 last:border-0", children: [
|
|
601
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { className: "py-3 px-3 text-gray-600 dark:text-gray-300 whitespace-nowrap", children: fmtDate(p.paidAt || p.createdAt) }),
|
|
602
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { className: "py-3 px-3 text-gray-900 dark:text-white font-medium", children: p.orderName }),
|
|
603
|
+
/* @__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
|
+
/* @__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 }) }),
|
|
606
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("td", { className: "py-3 px-3", children: canCancelPayment(p) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
607
|
+
"button",
|
|
608
|
+
{
|
|
609
|
+
onClick: () => setConfirmModal({ type: "cancel_payment", id: p.id, label: p.orderName }),
|
|
610
|
+
disabled: actionLoading === p.id,
|
|
611
|
+
className: "text-xs text-red-500 dark:text-red-400 hover:underline disabled:opacity-50",
|
|
612
|
+
children: "\uCDE8\uC18C"
|
|
613
|
+
}
|
|
614
|
+
) })
|
|
615
|
+
] }, p.id);
|
|
616
|
+
}) })
|
|
617
|
+
] }) }) }),
|
|
618
|
+
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" }),
|
|
620
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { className: "text-gray-500 dark:text-gray-400 text-sm mb-5", children: [
|
|
621
|
+
/* @__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."
|
|
623
|
+
] }),
|
|
624
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2", children: [
|
|
625
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
626
|
+
"button",
|
|
627
|
+
{
|
|
628
|
+
onClick: () => setConfirmModal(null),
|
|
629
|
+
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",
|
|
630
|
+
children: "\uC544\uB2C8\uC624"
|
|
631
|
+
}
|
|
632
|
+
),
|
|
633
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
634
|
+
"button",
|
|
635
|
+
{
|
|
636
|
+
onClick: () => confirmModal.type === "cancel_subscription" ? handleCancelSubscription(confirmModal.id) : handleCancelPayment(confirmModal.id),
|
|
637
|
+
disabled: !!actionLoading,
|
|
638
|
+
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"
|
|
640
|
+
}
|
|
641
|
+
)
|
|
642
|
+
] })
|
|
643
|
+
] }) })
|
|
644
|
+
] }) });
|
|
645
|
+
}
|
|
413
646
|
// Annotate the CommonJS export names for ESM import in node:
|
|
414
647
|
0 && (module.exports = {
|
|
415
|
-
SubscribePage
|
|
648
|
+
SubscribePage,
|
|
649
|
+
SubscriptionManagement
|
|
416
650
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -102,8 +102,10 @@ function SubscribePage({
|
|
|
102
102
|
return;
|
|
103
103
|
}
|
|
104
104
|
setSuccess(true);
|
|
105
|
+
const separator = callbackUrl.includes("?") ? "&" : "?";
|
|
106
|
+
const redirectUrl = `${callbackUrl}${separator}subscribed=true`;
|
|
105
107
|
setTimeout(() => {
|
|
106
|
-
window.location.href =
|
|
108
|
+
window.location.href = redirectUrl;
|
|
107
109
|
}, 2e3);
|
|
108
110
|
} catch {
|
|
109
111
|
setError("\uAD6C\uB3C5 \uCC98\uB9AC \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
@@ -385,6 +387,237 @@ function PageLayout({ children, themeClass }) {
|
|
|
385
387
|
function CheckIcon({ className }) {
|
|
386
388
|
return /* @__PURE__ */ jsx("svg", { className, fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M5 13l4 4L19 7" }) });
|
|
387
389
|
}
|
|
390
|
+
|
|
391
|
+
// src/components/SubscriptionManagement.tsx
|
|
392
|
+
import { useState as useState2, useEffect as useEffect2 } from "react";
|
|
393
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
394
|
+
var DEFAULT_PRIMARY2 = "#60A5FA";
|
|
395
|
+
function SubscriptionManagement({
|
|
396
|
+
subscriptionsApiPath = "/api/subscribe/subscriptions",
|
|
397
|
+
paymentsApiPath = "/api/subscribe/payments",
|
|
398
|
+
primaryColor = DEFAULT_PRIMARY2,
|
|
399
|
+
theme = "system"
|
|
400
|
+
}) {
|
|
401
|
+
const [tab, setTab] = useState2("subscription");
|
|
402
|
+
const [subscriptions, setSubscriptions] = useState2([]);
|
|
403
|
+
const [payments, setPayments] = useState2([]);
|
|
404
|
+
const [loading, setLoading] = useState2(true);
|
|
405
|
+
const [error, setError] = useState2("");
|
|
406
|
+
const [actionLoading, setActionLoading] = useState2(null);
|
|
407
|
+
const [confirmModal, setConfirmModal] = useState2(null);
|
|
408
|
+
const themeClass = theme === "dark" ? "dark" : theme === "light" ? "light-mode-override" : "";
|
|
409
|
+
useEffect2(() => {
|
|
410
|
+
fetchSubscriptions();
|
|
411
|
+
fetchPayments();
|
|
412
|
+
}, []);
|
|
413
|
+
async function fetchSubscriptions() {
|
|
414
|
+
try {
|
|
415
|
+
const res = await fetch(subscriptionsApiPath);
|
|
416
|
+
const data = await res.json();
|
|
417
|
+
if (data.success) setSubscriptions(data.subscriptions);
|
|
418
|
+
} catch {
|
|
419
|
+
} finally {
|
|
420
|
+
setLoading(false);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
async function fetchPayments() {
|
|
424
|
+
try {
|
|
425
|
+
const res = await fetch(paymentsApiPath);
|
|
426
|
+
const data = await res.json();
|
|
427
|
+
if (data.success) setPayments(data.payments);
|
|
428
|
+
} catch {
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
async function handleCancelSubscription(subscriptionId) {
|
|
432
|
+
setActionLoading(subscriptionId);
|
|
433
|
+
setError("");
|
|
434
|
+
try {
|
|
435
|
+
const res = await fetch(subscriptionsApiPath, {
|
|
436
|
+
method: "DELETE",
|
|
437
|
+
headers: { "Content-Type": "application/json" },
|
|
438
|
+
body: JSON.stringify({ subscriptionId })
|
|
439
|
+
});
|
|
440
|
+
const data = await res.json();
|
|
441
|
+
if (!data.success) {
|
|
442
|
+
setError(data.error || "\uAD6C\uB3C5 \uCDE8\uC18C\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
|
|
443
|
+
return;
|
|
444
|
+
}
|
|
445
|
+
await fetchSubscriptions();
|
|
446
|
+
} catch {
|
|
447
|
+
setError("\uAD6C\uB3C5 \uCDE8\uC18C \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
448
|
+
} finally {
|
|
449
|
+
setActionLoading(null);
|
|
450
|
+
setConfirmModal(null);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
async function handleCancelPayment(paymentId) {
|
|
454
|
+
setActionLoading(paymentId);
|
|
455
|
+
setError("");
|
|
456
|
+
try {
|
|
457
|
+
const res = await fetch(paymentsApiPath, {
|
|
458
|
+
method: "POST",
|
|
459
|
+
headers: { "Content-Type": "application/json" },
|
|
460
|
+
body: JSON.stringify({ paymentId })
|
|
461
|
+
});
|
|
462
|
+
const data = await res.json();
|
|
463
|
+
if (!data.success) {
|
|
464
|
+
setError(data.error || "\uACB0\uC81C \uCDE8\uC18C\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.");
|
|
465
|
+
return;
|
|
466
|
+
}
|
|
467
|
+
await fetchPayments();
|
|
468
|
+
await fetchSubscriptions();
|
|
469
|
+
} catch {
|
|
470
|
+
setError("\uACB0\uC81C \uCDE8\uC18C \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
471
|
+
} finally {
|
|
472
|
+
setActionLoading(null);
|
|
473
|
+
setConfirmModal(null);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function fmt(price) {
|
|
477
|
+
return price.toLocaleString("ko-KR") + "\uC6D0";
|
|
478
|
+
}
|
|
479
|
+
function fmtDate(dateStr) {
|
|
480
|
+
return new Date(dateStr).toLocaleDateString("ko-KR", { year: "numeric", month: "long", day: "numeric" });
|
|
481
|
+
}
|
|
482
|
+
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 };
|
|
486
|
+
}
|
|
487
|
+
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 };
|
|
492
|
+
}
|
|
493
|
+
function canCancelPayment(p) {
|
|
494
|
+
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));
|
|
497
|
+
return daysDiff <= 7;
|
|
498
|
+
}
|
|
499
|
+
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..." }) });
|
|
500
|
+
return /* @__PURE__ */ jsx2("div", { className: themeClass, children: /* @__PURE__ */ jsxs2("div", { className: "space-y-4", children: [
|
|
501
|
+
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(
|
|
506
|
+
"button",
|
|
507
|
+
{
|
|
508
|
+
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"}`,
|
|
510
|
+
style: tab === key ? { color: primaryColor, borderColor: primaryColor } : void 0,
|
|
511
|
+
children: label
|
|
512
|
+
},
|
|
513
|
+
key
|
|
514
|
+
)) }),
|
|
515
|
+
tab === "subscription" && /* @__PURE__ */ jsx2("div", { className: "space-y-3", children: subscriptions.length === 0 ? /* @__PURE__ */ jsx2("div", { className: "text-center py-12 text-gray-400 dark:text-gray-500 text-sm", children: "\uD65C\uC131 \uAD6C\uB3C5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." }) : subscriptions.map((sub) => {
|
|
516
|
+
const badge = statusBadge(sub.status);
|
|
517
|
+
return /* @__PURE__ */ jsx2("div", { className: "p-4 sm:p-5 rounded-xl border border-gray-200 dark:border-white/10 bg-white dark:bg-white/5", children: /* @__PURE__ */ jsxs2("div", { className: "flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3", children: [
|
|
518
|
+
/* @__PURE__ */ jsxs2("div", { className: "space-y-2 flex-1 min-w-0", children: [
|
|
519
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2 flex-wrap", children: [
|
|
520
|
+
/* @__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 }),
|
|
522
|
+
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
|
+
] }),
|
|
524
|
+
/* @__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
|
+
] })
|
|
557
|
+
] })
|
|
558
|
+
] }),
|
|
559
|
+
sub.status === "ACTIVE" && /* @__PURE__ */ jsx2(
|
|
560
|
+
"button",
|
|
561
|
+
{
|
|
562
|
+
onClick: () => setConfirmModal({ type: "cancel_subscription", id: sub.id, label: sub.plan.name }),
|
|
563
|
+
disabled: actionLoading === sub.id,
|
|
564
|
+
className: "flex-shrink-0 px-4 py-2 rounded-lg border border-red-200 dark:border-red-500/30 text-red-600 dark:text-red-400 text-sm font-medium hover:bg-red-50 dark:hover:bg-red-500/10 disabled:opacity-50 transition-all",
|
|
565
|
+
children: "\uAD6C\uB3C5 \uCDE8\uC18C"
|
|
566
|
+
}
|
|
567
|
+
)
|
|
568
|
+
] }) }, sub.id);
|
|
569
|
+
}) }),
|
|
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: [
|
|
571
|
+
/* @__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
|
+
/* @__PURE__ */ jsx2("tbody", { children: payments.map((p) => {
|
|
573
|
+
const badge = paymentStatusBadge(p.status);
|
|
574
|
+
return /* @__PURE__ */ jsxs2("tr", { className: "border-b border-gray-100 dark:border-white/5 last:border-0", children: [
|
|
575
|
+
/* @__PURE__ */ jsx2("td", { className: "py-3 px-3 text-gray-600 dark:text-gray-300 whitespace-nowrap", children: fmtDate(p.paidAt || p.createdAt) }),
|
|
576
|
+
/* @__PURE__ */ jsx2("td", { className: "py-3 px-3 text-gray-900 dark:text-white font-medium", children: p.orderName }),
|
|
577
|
+
/* @__PURE__ */ jsx2("td", { className: "py-3 px-3 text-gray-900 dark:text-white font-semibold whitespace-nowrap", children: fmt(p.amount) }),
|
|
578
|
+
/* @__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 }) }),
|
|
580
|
+
/* @__PURE__ */ jsx2("td", { className: "py-3 px-3", children: canCancelPayment(p) && /* @__PURE__ */ jsx2(
|
|
581
|
+
"button",
|
|
582
|
+
{
|
|
583
|
+
onClick: () => setConfirmModal({ type: "cancel_payment", id: p.id, label: p.orderName }),
|
|
584
|
+
disabled: actionLoading === p.id,
|
|
585
|
+
className: "text-xs text-red-500 dark:text-red-400 hover:underline disabled:opacity-50",
|
|
586
|
+
children: "\uCDE8\uC18C"
|
|
587
|
+
}
|
|
588
|
+
) })
|
|
589
|
+
] }, p.id);
|
|
590
|
+
}) })
|
|
591
|
+
] }) }) }),
|
|
592
|
+
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" }),
|
|
594
|
+
/* @__PURE__ */ jsxs2("p", { className: "text-gray-500 dark:text-gray-400 text-sm mb-5", children: [
|
|
595
|
+
/* @__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."
|
|
597
|
+
] }),
|
|
598
|
+
/* @__PURE__ */ jsxs2("div", { className: "flex gap-2", children: [
|
|
599
|
+
/* @__PURE__ */ jsx2(
|
|
600
|
+
"button",
|
|
601
|
+
{
|
|
602
|
+
onClick: () => setConfirmModal(null),
|
|
603
|
+
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",
|
|
604
|
+
children: "\uC544\uB2C8\uC624"
|
|
605
|
+
}
|
|
606
|
+
),
|
|
607
|
+
/* @__PURE__ */ jsx2(
|
|
608
|
+
"button",
|
|
609
|
+
{
|
|
610
|
+
onClick: () => confirmModal.type === "cancel_subscription" ? handleCancelSubscription(confirmModal.id) : handleCancelPayment(confirmModal.id),
|
|
611
|
+
disabled: !!actionLoading,
|
|
612
|
+
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"
|
|
614
|
+
}
|
|
615
|
+
)
|
|
616
|
+
] })
|
|
617
|
+
] }) })
|
|
618
|
+
] }) });
|
|
619
|
+
}
|
|
388
620
|
export {
|
|
389
|
-
SubscribePage
|
|
621
|
+
SubscribePage,
|
|
622
|
+
SubscriptionManagement
|
|
390
623
|
};
|