@thinkingcat/subscription-ui 1.0.0 → 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 +57 -2
- package/dist/index.d.ts +57 -2
- package/dist/index.js +369 -91
- package/dist/index.mjs +368 -90
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
"use client";
|
|
2
3
|
var __defProp = Object.defineProperty;
|
|
3
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
@@ -20,7 +21,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
21
|
// src/index.ts
|
|
21
22
|
var index_exports = {};
|
|
22
23
|
__export(index_exports, {
|
|
23
|
-
SubscribePage: () => SubscribePage
|
|
24
|
+
SubscribePage: () => SubscribePage,
|
|
25
|
+
SubscriptionManagement: () => SubscriptionManagement
|
|
24
26
|
});
|
|
25
27
|
module.exports = __toCommonJS(index_exports);
|
|
26
28
|
|
|
@@ -35,7 +37,8 @@ function SubscribePage({
|
|
|
35
37
|
callbackUrl = "/",
|
|
36
38
|
serviceName = "\uC11C\uBE44\uC2A4",
|
|
37
39
|
primaryColor = DEFAULT_PRIMARY,
|
|
38
|
-
showExpiredBanner = true
|
|
40
|
+
showExpiredBanner = true,
|
|
41
|
+
theme = "system"
|
|
39
42
|
}) {
|
|
40
43
|
const [step, setStep] = (0, import_react.useState)("plan");
|
|
41
44
|
const [plans, setPlans] = (0, import_react.useState)([]);
|
|
@@ -125,8 +128,10 @@ function SubscribePage({
|
|
|
125
128
|
return;
|
|
126
129
|
}
|
|
127
130
|
setSuccess(true);
|
|
131
|
+
const separator = callbackUrl.includes("?") ? "&" : "?";
|
|
132
|
+
const redirectUrl = `${callbackUrl}${separator}subscribed=true`;
|
|
128
133
|
setTimeout(() => {
|
|
129
|
-
window.location.href =
|
|
134
|
+
window.location.href = redirectUrl;
|
|
130
135
|
}, 2e3);
|
|
131
136
|
} catch {
|
|
132
137
|
setError("\uAD6C\uB3C5 \uCC98\uB9AC \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
|
|
@@ -137,12 +142,15 @@ function SubscribePage({
|
|
|
137
142
|
function fmt(price) {
|
|
138
143
|
return price.toLocaleString("ko-KR") + "\uC6D0";
|
|
139
144
|
}
|
|
140
|
-
|
|
145
|
+
const themeClass = theme === "dark" ? "dark" : theme === "light" ? "light-mode-override" : "";
|
|
146
|
+
if (loading) {
|
|
147
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PageLayout, { themeClass, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center justify-center h-64 text-gray-400 dark:text-gray-500", children: "\uBD88\uB7EC\uC624\uB294 \uC911..." }) });
|
|
148
|
+
}
|
|
141
149
|
if (success) {
|
|
142
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PageLayout, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center justify-center h-
|
|
143
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-16 h-16 rounded-full bg-green-500/20 flex items-center justify-center mx-auto mb-4", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CheckIcon, { className: "w-8 h-8 text-green-400" }) }),
|
|
144
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-xl font-bold text-white mb-2", children: "\uAD6C\uB3C5\uC774 \uC2DC\uC791\uB418\uC5C8\uC2B5\uB2C8\uB2E4!" }),
|
|
145
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-gray-400 text-sm", children: "\uC7A0\uC2DC \uD6C4 \uC774\uB3D9\uD569\uB2C8\uB2E4..." })
|
|
150
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PageLayout, { themeClass, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center justify-center min-h-[60vh]", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "text-center px-4", children: [
|
|
151
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-16 h-16 rounded-full bg-green-500/20 flex items-center justify-center mx-auto mb-4", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CheckIcon, { className: "w-8 h-8 text-green-500 dark:text-green-400" }) }),
|
|
152
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-xl font-bold text-gray-900 dark:text-white mb-2", children: "\uAD6C\uB3C5\uC774 \uC2DC\uC791\uB418\uC5C8\uC2B5\uB2C8\uB2E4!" }),
|
|
153
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-gray-500 dark:text-gray-400 text-sm", children: "\uC7A0\uC2DC \uD6C4 \uC774\uB3D9\uD569\uB2C8\uB2E4..." })
|
|
146
154
|
] }) }) });
|
|
147
155
|
}
|
|
148
156
|
const steps = [
|
|
@@ -150,56 +158,71 @@ function SubscribePage({
|
|
|
150
158
|
{ key: "card", label: "\uACB0\uC81C\uC218\uB2E8" },
|
|
151
159
|
{ key: "confirm", label: "\uD655\uC778" }
|
|
152
160
|
];
|
|
153
|
-
|
|
154
|
-
|
|
161
|
+
const currentStepIndex = steps.findIndex((s) => s.key === step);
|
|
162
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(PageLayout, { themeClass, children: [
|
|
163
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "border-b border-gray-200 dark:border-white/10 px-4 sm:px-6 py-4", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "max-w-3xl mx-auto flex flex-col sm:flex-row sm:items-center gap-3 sm:gap-0 sm:justify-between", children: [
|
|
155
164
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
156
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h1", { className: "text-lg font-bold text-white", children: [
|
|
165
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("h1", { className: "text-base sm:text-lg font-bold text-gray-900 dark:text-white", children: [
|
|
157
166
|
serviceName,
|
|
158
167
|
" \uAD6C\uB3C5"
|
|
159
168
|
] }),
|
|
160
|
-
showExpiredBanner && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-xs text-amber-400 mt-0.5", children: "\uAD6C\uB3C5\uC774 \uB9CC\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC11C\uBE44\uC2A4\uB97C \uACC4\uC18D \uC774\uC6A9\uD558\uB824\uBA74 \uAD6C\uB3C5\uC774 \uD544\uC694\uD569\uB2C8\uB2E4." })
|
|
169
|
+
showExpiredBanner && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-xs text-amber-600 dark:text-amber-400 mt-0.5", children: "\uAD6C\uB3C5\uC774 \uB9CC\uB8CC\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uC11C\uBE44\uC2A4\uB97C \uACC4\uC18D \uC774\uC6A9\uD558\uB824\uBA74 \uAD6C\uB3C5\uC774 \uD544\uC694\uD569\uB2C8\uB2E4." })
|
|
161
170
|
] }),
|
|
162
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center gap-2", children: steps.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
163
|
-
i > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
164
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
171
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex items-center gap-1 sm:gap-2", children: steps.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-1 sm:gap-2", children: [
|
|
172
|
+
i > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
165
173
|
"div",
|
|
166
174
|
{
|
|
167
|
-
className: `w-6
|
|
168
|
-
style: step === s.key ? { backgroundColor: primaryColor } : void 0,
|
|
169
|
-
children: i + 1
|
|
175
|
+
className: `w-6 sm:w-10 h-px transition-colors ${i <= currentStepIndex ? "bg-blue-500" : "bg-gray-200 dark:bg-white/20"}`
|
|
170
176
|
}
|
|
171
|
-
)
|
|
177
|
+
),
|
|
178
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-1.5", children: [
|
|
179
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
180
|
+
"div",
|
|
181
|
+
{
|
|
182
|
+
className: `w-6 h-6 rounded-full flex items-center justify-center text-xs font-medium transition-all ${step === s.key ? "text-white shadow-sm" : i < currentStepIndex ? "text-white" : "bg-gray-100 dark:bg-white/10 text-gray-400 dark:text-gray-500"}`,
|
|
183
|
+
style: step === s.key ? { backgroundColor: primaryColor } : i < currentStepIndex ? { backgroundColor: primaryColor, opacity: 0.5 } : void 0,
|
|
184
|
+
children: i < currentStepIndex ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CheckIcon, { className: "w-3 h-3" }) : i + 1
|
|
185
|
+
}
|
|
186
|
+
),
|
|
187
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
188
|
+
"span",
|
|
189
|
+
{
|
|
190
|
+
className: `hidden sm:inline text-xs font-medium ${step === s.key ? "text-gray-900 dark:text-white" : "text-gray-400 dark:text-gray-500"}`,
|
|
191
|
+
children: s.label
|
|
192
|
+
}
|
|
193
|
+
)
|
|
194
|
+
] })
|
|
172
195
|
] }, s.key)) })
|
|
173
196
|
] }) }),
|
|
174
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "max-w-3xl mx-auto px-4 pb-
|
|
175
|
-
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mb-
|
|
197
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "max-w-3xl mx-auto px-4 sm:px-6 py-6 sm:py-8 pb-16", children: [
|
|
198
|
+
error && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mb-5 p-3 bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-500/30 rounded-xl text-red-600 dark:text-red-400 text-sm", children: error }),
|
|
176
199
|
step === "plan" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
177
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-white font-semibold mb-4", children: "\uD50C\uB79C\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694" }),
|
|
200
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-gray-900 dark:text-white font-semibold mb-4 text-sm sm:text-base", children: "\uD50C\uB79C\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694" }),
|
|
178
201
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid gap-3", children: plans.map((plan) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
179
202
|
"button",
|
|
180
203
|
{
|
|
181
204
|
onClick: () => setSelectedPlan(plan),
|
|
182
|
-
className: `w-full text-left p-4 rounded-xl border transition-all ${selectedPlan?.id === plan.id ? "border-blue-500 bg-blue-500/10" : "border-white/10 bg-white/5 hover:border-white/20"}`,
|
|
205
|
+
className: `w-full text-left p-4 rounded-xl border transition-all ${selectedPlan?.id === plan.id ? "border-blue-500 bg-blue-50 dark:bg-blue-500/10" : "border-gray-200 dark:border-white/10 bg-white dark:bg-white/5 hover:border-gray-300 dark:hover:border-white/20 hover:bg-gray-50 dark:hover:bg-white/8"}`,
|
|
183
206
|
children: [
|
|
184
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-
|
|
185
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
186
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-white font-medium", children: plan.name }),
|
|
187
|
-
plan.isPopular && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "px-2 py-0.5 bg-blue-500/20 text-blue-400 text-xs rounded-full border border-blue-500/30", children: "\uC778\uAE30" })
|
|
207
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-start justify-between gap-3 mb-2", children: [
|
|
208
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2 flex-wrap", children: [
|
|
209
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-gray-900 dark:text-white font-medium text-sm sm:text-base", children: plan.name }),
|
|
210
|
+
plan.isPopular && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "px-2 py-0.5 bg-blue-100 dark:bg-blue-500/20 text-blue-600 dark:text-blue-400 text-xs rounded-full border border-blue-200 dark:border-blue-500/30", children: "\uC778\uAE30" })
|
|
188
211
|
] }),
|
|
189
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "text-right", children: [
|
|
190
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-white font-bold", children: fmt(plan.price) }),
|
|
191
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-gray-400 text-xs ml-1", children: [
|
|
212
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "text-right flex-shrink-0", children: [
|
|
213
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-gray-900 dark:text-white font-bold text-sm sm:text-base", children: fmt(plan.price) }),
|
|
214
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-gray-400 dark:text-gray-500 text-xs ml-1", children: [
|
|
192
215
|
"/ ",
|
|
193
216
|
plan.interval,
|
|
194
217
|
"\uAC1C\uC6D4"
|
|
195
218
|
] })
|
|
196
219
|
] })
|
|
197
220
|
] }),
|
|
198
|
-
plan.description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-gray-400 text-sm mb-2", children: plan.description }),
|
|
199
|
-
plan.features.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "space-y-1", children: plan.features.slice(0, 3).map((f,
|
|
200
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CheckIcon, { className: "w-3 h-3 text-green-400 flex-shrink-0" }),
|
|
221
|
+
plan.description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-gray-500 dark:text-gray-400 text-sm mb-2", children: plan.description }),
|
|
222
|
+
plan.features.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { className: "space-y-1", children: plan.features.slice(0, 3).map((f, idx) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { className: "flex items-center gap-1.5 text-xs text-gray-600 dark:text-gray-300", children: [
|
|
223
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CheckIcon, { className: "w-3 h-3 text-green-500 dark:text-green-400 flex-shrink-0" }),
|
|
201
224
|
f
|
|
202
|
-
] },
|
|
225
|
+
] }, idx)) })
|
|
203
226
|
]
|
|
204
227
|
},
|
|
205
228
|
plan.id
|
|
@@ -210,26 +233,26 @@ function SubscribePage({
|
|
|
210
233
|
disabled: !selectedPlan,
|
|
211
234
|
onClick: () => setStep("card"),
|
|
212
235
|
style: selectedPlan ? { backgroundColor: primaryColor } : void 0,
|
|
213
|
-
className: `mt-6 w-full py-3 rounded-xl font-medium transition-all ${selectedPlan ? "text-white hover:opacity-90" : "bg-white/10 text-gray-500 cursor-not-allowed"}`,
|
|
236
|
+
className: `mt-6 w-full py-3 rounded-xl font-medium transition-all text-sm sm:text-base ${selectedPlan ? "text-white hover:opacity-90 shadow-sm" : "bg-gray-100 dark:bg-white/10 text-gray-400 dark:text-gray-500 cursor-not-allowed"}`,
|
|
214
237
|
children: "\uB2E4\uC74C \u2014 \uACB0\uC81C\uC218\uB2E8 \uC120\uD0DD"
|
|
215
238
|
}
|
|
216
239
|
)
|
|
217
240
|
] }),
|
|
218
241
|
step === "card" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
219
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-white font-semibold mb-4", children: "\uACB0\uC81C\uC218\uB2E8\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694" }),
|
|
242
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-gray-900 dark:text-white font-semibold mb-4 text-sm sm:text-base", children: "\uACB0\uC81C\uC218\uB2E8\uC744 \uC120\uD0DD\uD574\uC8FC\uC138\uC694" }),
|
|
220
243
|
cards.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid gap-3 mb-4", children: cards.map((card) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
221
244
|
"button",
|
|
222
245
|
{
|
|
223
246
|
onClick: () => setSelectedCard(card),
|
|
224
|
-
className: `w-full text-left p-4 rounded-xl border transition-all ${selectedCard?.id === card.id ? "border-blue-500 bg-blue-500/10" : "border-white/10 bg-white/5 hover:border-white/20"}`,
|
|
225
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between", children: [
|
|
226
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
227
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-white font-medium", children: card.cardBrand }),
|
|
228
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-gray-400 text-sm ml-2", children: card.maskedCardNumber })
|
|
247
|
+
className: `w-full text-left p-4 rounded-xl border transition-all ${selectedCard?.id === card.id ? "border-blue-500 bg-blue-50 dark:bg-blue-500/10" : "border-gray-200 dark:border-white/10 bg-white dark:bg-white/5 hover:border-gray-300 dark:hover:border-white/20"}`,
|
|
248
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between gap-2", children: [
|
|
249
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "min-w-0", children: [
|
|
250
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-gray-900 dark:text-white font-medium text-sm", children: card.cardBrand }),
|
|
251
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-gray-500 dark:text-gray-400 text-sm ml-2", children: card.maskedCardNumber })
|
|
229
252
|
] }),
|
|
230
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
231
|
-
card.isDefault && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "px-2 py-0.5 bg-blue-500/20 text-blue-400 text-xs rounded-full border border-blue-500/30", children: "\uAE30\uBCF8" }),
|
|
232
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-gray-400 text-xs", children: [
|
|
253
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
|
|
254
|
+
card.isDefault && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "px-2 py-0.5 bg-blue-100 dark:bg-blue-500/20 text-blue-600 dark:text-blue-400 text-xs rounded-full border border-blue-200 dark:border-blue-500/30", children: "\uAE30\uBCF8" }),
|
|
255
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-gray-400 dark:text-gray-500 text-xs", children: [
|
|
233
256
|
card.expiryMonth,
|
|
234
257
|
"/",
|
|
235
258
|
card.expiryYear
|
|
@@ -243,21 +266,21 @@ function SubscribePage({
|
|
|
243
266
|
"button",
|
|
244
267
|
{
|
|
245
268
|
onClick: () => setShowCardForm(true),
|
|
246
|
-
className: "w-full p-4 rounded-xl border border-dashed border-white/20 text-gray-400 hover:border-white/40 hover:text-gray-300 transition-all text-sm",
|
|
269
|
+
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",
|
|
247
270
|
children: "+ \uC0C8 \uCE74\uB4DC \uCD94\uAC00"
|
|
248
271
|
}
|
|
249
|
-
) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "p-4 rounded-xl border border-white/10 bg-white/5 space-y-3", children: [
|
|
250
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "text-white text-sm font-medium
|
|
251
|
-
cardError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "p-3 bg-red-500/10 border border-red-500/30 rounded-lg text-red-400 text-xs", children: cardError }),
|
|
252
|
-
[
|
|
253
|
-
{ label: "\uCE74\uB4DC \uBC88\uD638", key: "cardNumber", placeholder: "0000-0000-0000-0000", type: "text" },
|
|
254
|
-
{ label: "\uB9CC\uB8CC\uC77C", key: "expiryDate", placeholder: "MM/YY", type: "text" },
|
|
255
|
-
{ label: "CVC", key: "cvc", placeholder: "3\uC790\uB9AC", type: "password" },
|
|
256
|
-
{ label: "\uCE74\uB4DC \uBE44\uBC00\uBC88\uD638 \uC55E 2\uC790\uB9AC", key: "cardPassword", placeholder: "\u2022\u2022", type: "password" },
|
|
257
|
-
{ label: "\uCE74\uB4DC \uC18C\uC720\uC790\uBA85", key: "cardHolderName", placeholder: "\uD64D\uAE38\uB3D9", type: "text" },
|
|
258
|
-
{ label: "\uC0DD\uB144\uC6D4\uC77C 6\uC790\uB9AC / \uC0AC\uC5C5\uC790\uBC88\uD638 10\uC790\uB9AC", key: "birthNumber", placeholder: "990101", type: "text" }
|
|
259
|
-
].map(({ label, key, placeholder, type }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
260
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "text-xs text-gray-400 block mb-1", children: label }),
|
|
272
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime.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: [
|
|
273
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "text-gray-900 dark:text-white text-sm font-medium", children: "\uCE74\uB4DC \uC815\uBCF4 \uC785\uB825" }),
|
|
274
|
+
cardError && /* @__PURE__ */ (0, import_jsx_runtime.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 }),
|
|
275
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3", children: [
|
|
276
|
+
{ label: "\uCE74\uB4DC \uBC88\uD638", key: "cardNumber", placeholder: "0000-0000-0000-0000", type: "text", colSpan: true },
|
|
277
|
+
{ label: "\uB9CC\uB8CC\uC77C", key: "expiryDate", placeholder: "MM/YY", type: "text", colSpan: false },
|
|
278
|
+
{ label: "CVC", key: "cvc", placeholder: "3\uC790\uB9AC", type: "password", colSpan: false },
|
|
279
|
+
{ label: "\uCE74\uB4DC \uBE44\uBC00\uBC88\uD638 \uC55E 2\uC790\uB9AC", key: "cardPassword", placeholder: "\u2022\u2022", type: "password", colSpan: false },
|
|
280
|
+
{ label: "\uCE74\uB4DC \uC18C\uC720\uC790\uBA85", key: "cardHolderName", placeholder: "\uD64D\uAE38\uB3D9", type: "text", colSpan: false },
|
|
281
|
+
{ label: "\uC0DD\uB144\uC6D4\uC77C 6\uC790\uB9AC / \uC0AC\uC5C5\uC790\uBC88\uD638 10\uC790\uB9AC", key: "birthNumber", placeholder: "990101", type: "text", colSpan: true }
|
|
282
|
+
].map(({ label, key, placeholder, type, colSpan }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: colSpan ? "sm:col-span-2" : "", children: [
|
|
283
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "text-xs text-gray-500 dark:text-gray-400 block mb-1", children: label }),
|
|
261
284
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
262
285
|
"input",
|
|
263
286
|
{
|
|
@@ -265,10 +288,10 @@ function SubscribePage({
|
|
|
265
288
|
placeholder,
|
|
266
289
|
value: cardForm[key],
|
|
267
290
|
onChange: (e) => setCardForm((f) => ({ ...f, [key]: e.target.value })),
|
|
268
|
-
className: "w-full bg-white/5 border border-white/10 rounded-lg px-3 py-2 text-white text-sm placeholder-gray-600 focus:outline-none focus:border-blue-500"
|
|
291
|
+
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 dark:focus:border-blue-500 transition-colors"
|
|
269
292
|
}
|
|
270
293
|
)
|
|
271
|
-
] }, key)),
|
|
294
|
+
] }, key)) }),
|
|
272
295
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-2 pt-1", children: [
|
|
273
296
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
274
297
|
"button",
|
|
@@ -277,7 +300,7 @@ function SubscribePage({
|
|
|
277
300
|
setShowCardForm(false);
|
|
278
301
|
setCardError("");
|
|
279
302
|
},
|
|
280
|
-
className: "flex-1 py-2 rounded-lg border border-white/10 text-gray-400 text-sm hover:border-white/20 transition-all",
|
|
303
|
+
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",
|
|
281
304
|
children: "\uCDE8\uC18C"
|
|
282
305
|
}
|
|
283
306
|
),
|
|
@@ -287,36 +310,46 @@ function SubscribePage({
|
|
|
287
310
|
onClick: handleRegisterCard,
|
|
288
311
|
disabled: cardSubmitting,
|
|
289
312
|
style: { backgroundColor: primaryColor },
|
|
290
|
-
className: "flex-1 py-2 rounded-lg text-white text-sm font-medium hover:opacity-90 disabled:opacity-50 transition-all",
|
|
313
|
+
className: "flex-1 py-2.5 rounded-lg text-white text-sm font-medium hover:opacity-90 disabled:opacity-50 transition-all",
|
|
291
314
|
children: cardSubmitting ? "\uB4F1\uB85D \uC911..." : "\uCE74\uB4DC \uB4F1\uB85D"
|
|
292
315
|
}
|
|
293
316
|
)
|
|
294
317
|
] })
|
|
295
318
|
] }),
|
|
296
319
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-3 mt-6", children: [
|
|
297
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { onClick: () => setStep("plan"), className: "flex-1 py-3 rounded-xl border border-white/10 text-gray-400 font-medium hover:border-white/20 transition-all", children: "\uC774\uC804" }),
|
|
298
320
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
321
|
+
"button",
|
|
322
|
+
{
|
|
323
|
+
onClick: () => setStep("plan"),
|
|
324
|
+
className: "flex-1 py-3 rounded-xl border border-gray-200 dark:border-white/10 text-gray-500 dark:text-gray-400 font-medium text-sm hover:border-gray-300 dark:hover:border-white/20 transition-all",
|
|
325
|
+
children: "\uC774\uC804"
|
|
326
|
+
}
|
|
327
|
+
),
|
|
328
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
299
329
|
"button",
|
|
300
330
|
{
|
|
301
331
|
disabled: !selectedCard,
|
|
302
332
|
onClick: () => setStep("confirm"),
|
|
303
333
|
style: selectedCard ? { backgroundColor: primaryColor } : void 0,
|
|
304
|
-
className: `flex-1 py-3 rounded-xl font-medium transition-all ${selectedCard ? "text-white hover:opacity-90" : "bg-white/10 text-gray-500 cursor-not-allowed"}`,
|
|
305
|
-
children:
|
|
334
|
+
className: `flex-1 py-3 rounded-xl font-medium text-sm transition-all ${selectedCard ? "text-white hover:opacity-90 shadow-sm" : "bg-gray-100 dark:bg-white/10 text-gray-400 dark:text-gray-500 cursor-not-allowed"}`,
|
|
335
|
+
children: [
|
|
336
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "hidden sm:inline", children: "\uB2E4\uC74C \u2014 \uCD5C\uC885 \uD655\uC778" }),
|
|
337
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "sm:hidden", children: "\uB2E4\uC74C" })
|
|
338
|
+
]
|
|
306
339
|
}
|
|
307
340
|
)
|
|
308
341
|
] })
|
|
309
342
|
] }),
|
|
310
343
|
step === "confirm" && selectedPlan && selectedCard && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
311
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-white font-semibold mb-
|
|
344
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { className: "text-gray-900 dark:text-white font-semibold mb-5 text-sm sm:text-base", children: "\uAD6C\uB3C5 \uB0B4\uC6A9\uC744 \uD655\uC778\uD574\uC8FC\uC138\uC694" }),
|
|
312
345
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-3 mb-6", children: [
|
|
313
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "p-4 rounded-xl border border-white/10 bg-white/5", children: [
|
|
314
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-xs text-gray-400 mb-1", children: "\uC120\uD0DD \uD50C\uB79C" }),
|
|
315
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between", children: [
|
|
316
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-white font-medium", children: selectedPlan.name }),
|
|
317
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-white font-bold", children: [
|
|
346
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "p-4 rounded-xl border border-gray-200 dark:border-white/10 bg-white dark:bg-white/5", children: [
|
|
347
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-xs text-gray-400 dark:text-gray-500 mb-1.5", children: "\uC120\uD0DD \uD50C\uB79C" }),
|
|
348
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between gap-3", children: [
|
|
349
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-gray-900 dark:text-white font-medium text-sm", children: selectedPlan.name }),
|
|
350
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-gray-900 dark:text-white font-bold text-sm flex-shrink-0", children: [
|
|
318
351
|
fmt(selectedPlan.price),
|
|
319
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-gray-400 text-xs font-normal ml-1", children: [
|
|
352
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-gray-400 dark:text-gray-500 text-xs font-normal ml-1", children: [
|
|
320
353
|
"/ ",
|
|
321
354
|
selectedPlan.interval,
|
|
322
355
|
"\uAC1C\uC6D4"
|
|
@@ -324,34 +357,48 @@ function SubscribePage({
|
|
|
324
357
|
] })
|
|
325
358
|
] })
|
|
326
359
|
] }),
|
|
327
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "p-4 rounded-xl border border-white/10 bg-white/5", children: [
|
|
328
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-xs text-gray-400 mb-1", children: "\uACB0\uC81C\uC218\uB2E8" }),
|
|
360
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "p-4 rounded-xl border border-gray-200 dark:border-white/10 bg-white dark:bg-white/5", children: [
|
|
361
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-xs text-gray-400 dark:text-gray-500 mb-1.5", children: "\uACB0\uC81C\uC218\uB2E8" }),
|
|
329
362
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
|
|
330
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-white font-medium", children: selectedCard.cardBrand }),
|
|
331
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-gray-400 text-sm", children: selectedCard.maskedCardNumber })
|
|
363
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-gray-900 dark:text-white font-medium text-sm", children: selectedCard.cardBrand }),
|
|
364
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-gray-500 dark:text-gray-400 text-sm", children: selectedCard.maskedCardNumber })
|
|
332
365
|
] })
|
|
333
366
|
] }),
|
|
334
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
367
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
368
|
+
"div",
|
|
369
|
+
{
|
|
370
|
+
className: "p-4 rounded-xl border bg-blue-50/50 dark:bg-blue-500/5",
|
|
371
|
+
style: { borderColor: `${primaryColor}40` },
|
|
372
|
+
children: [
|
|
373
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between gap-3", children: [
|
|
374
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-gray-600 dark:text-gray-300 text-sm", children: "\uACB0\uC81C \uAE08\uC561" }),
|
|
375
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "font-bold text-xl", style: { color: primaryColor }, children: fmt(selectedPlan.price) })
|
|
376
|
+
] }),
|
|
377
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { className: "text-gray-400 dark:text-gray-500 text-xs mt-1.5", children: [
|
|
378
|
+
"\uC624\uB298 \uACB0\uC81C \uD6C4 ",
|
|
379
|
+
selectedPlan.interval,
|
|
380
|
+
"\uAC1C\uC6D4 \uAC04 \uC774\uC6A9 \uAC00\uB2A5\uD569\uB2C8\uB2E4"
|
|
381
|
+
] })
|
|
382
|
+
]
|
|
383
|
+
}
|
|
384
|
+
)
|
|
345
385
|
] }),
|
|
346
386
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-3", children: [
|
|
347
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
387
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
388
|
+
"button",
|
|
389
|
+
{
|
|
390
|
+
onClick: () => setStep("card"),
|
|
391
|
+
className: "flex-1 py-3 rounded-xl border border-gray-200 dark:border-white/10 text-gray-500 dark:text-gray-400 font-medium text-sm hover:border-gray-300 dark:hover:border-white/20 transition-all",
|
|
392
|
+
children: "\uC774\uC804"
|
|
393
|
+
}
|
|
394
|
+
),
|
|
348
395
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
349
396
|
"button",
|
|
350
397
|
{
|
|
351
398
|
onClick: handleSubscribe,
|
|
352
399
|
disabled: submitting,
|
|
353
400
|
style: { backgroundColor: primaryColor },
|
|
354
|
-
className: "flex-1 py-3 rounded-xl text-white font-medium hover:opacity-90 disabled:opacity-50 transition-all",
|
|
401
|
+
className: "flex-1 py-3 rounded-xl text-white font-medium text-sm hover:opacity-90 disabled:opacity-50 transition-all shadow-sm",
|
|
355
402
|
children: submitting ? "\uCC98\uB9AC \uC911..." : `${fmt(selectedPlan.price)} \uACB0\uC81C\uD558\uAE30`
|
|
356
403
|
}
|
|
357
404
|
)
|
|
@@ -360,13 +407,244 @@ function SubscribePage({
|
|
|
360
407
|
] })
|
|
361
408
|
] });
|
|
362
409
|
}
|
|
363
|
-
function PageLayout({ children }) {
|
|
364
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "min-h-screen bg-gray-950", children });
|
|
410
|
+
function PageLayout({ children, themeClass }) {
|
|
411
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: themeClass, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "min-h-screen bg-white dark:bg-gray-950 transition-colors", children }) });
|
|
365
412
|
}
|
|
366
413
|
function CheckIcon({ className }) {
|
|
367
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" }) });
|
|
368
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
|
+
}
|
|
369
646
|
// Annotate the CommonJS export names for ESM import in node:
|
|
370
647
|
0 && (module.exports = {
|
|
371
|
-
SubscribePage
|
|
648
|
+
SubscribePage,
|
|
649
|
+
SubscriptionManagement
|
|
372
650
|
});
|