@thinkingcat/subscription-ui 1.0.8 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
 
3
3
  interface Plan {
4
4
  id: string;
@@ -13,12 +13,12 @@ interface Plan {
13
13
  }
14
14
  interface Card {
15
15
  id: string;
16
- maskedCardNumber: string;
17
- cardBrand: string;
18
- cardType?: string;
19
- cardHolderName: string;
20
- expiryMonth: string;
21
- expiryYear: string;
16
+ maskedCardNumber: string | null;
17
+ cardBrand: string | null;
18
+ cardType?: string | null;
19
+ cardHolderName?: string | null;
20
+ expiryMonth?: string | null;
21
+ expiryYear?: string | null;
22
22
  isDefault: boolean;
23
23
  }
24
24
  interface SubscribePageConfig {
@@ -43,6 +43,13 @@ interface SubscribePageConfig {
43
43
  * - "system": OS/HTML 다크모드 설정 자동 적용
44
44
  */
45
45
  theme?: "light" | "dark" | "system";
46
+ /**
47
+ * 포트원 상점 ID (공개값, 제품의 NEXT_PUBLIC_PORTONE_STORE_ID).
48
+ * 카드 등록을 포트원 결제창으로 진행하는 데 필요하다 — 없으면 카드 추가가 막힌다.
49
+ */
50
+ portOneStoreId?: string;
51
+ /** 포트원 채널 키 (공개값, NEXT_PUBLIC_PORTONE_CHANNEL_KEY). */
52
+ portOneChannelKey?: string;
46
53
  }
47
54
  interface Subscription {
48
55
  id: string;
@@ -93,8 +100,8 @@ interface SubscriptionManagementConfig {
93
100
  theme?: "light" | "dark" | "system";
94
101
  }
95
102
 
96
- declare function SubscribePage({ plansApiPath, cardsApiPath, payApiPath, callbackUrl, serviceName, primaryColor, showExpiredBanner, theme, }: SubscribePageConfig): react_jsx_runtime.JSX.Element;
103
+ declare function SubscribePage({ plansApiPath, cardsApiPath, payApiPath, callbackUrl, serviceName, primaryColor, showExpiredBanner, theme, portOneStoreId, portOneChannelKey, }: SubscribePageConfig): react.JSX.Element;
97
104
 
98
- declare function SubscriptionManagement({ subscriptionsApiPath, paymentsApiPath, cardsApiPath, primaryColor, theme, }: SubscriptionManagementConfig): react_jsx_runtime.JSX.Element;
105
+ declare function SubscriptionManagement({ subscriptionsApiPath, paymentsApiPath, cardsApiPath, primaryColor, theme, }: SubscriptionManagementConfig): react.JSX.Element;
99
106
 
100
107
  export { type Card, type Payment, type Plan, SubscribePage, type SubscribePageConfig, type Subscription, SubscriptionManagement, type SubscriptionManagementConfig };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import * as react_jsx_runtime from 'react/jsx-runtime';
1
+ import * as react from 'react';
2
2
 
3
3
  interface Plan {
4
4
  id: string;
@@ -13,12 +13,12 @@ interface Plan {
13
13
  }
14
14
  interface Card {
15
15
  id: string;
16
- maskedCardNumber: string;
17
- cardBrand: string;
18
- cardType?: string;
19
- cardHolderName: string;
20
- expiryMonth: string;
21
- expiryYear: string;
16
+ maskedCardNumber: string | null;
17
+ cardBrand: string | null;
18
+ cardType?: string | null;
19
+ cardHolderName?: string | null;
20
+ expiryMonth?: string | null;
21
+ expiryYear?: string | null;
22
22
  isDefault: boolean;
23
23
  }
24
24
  interface SubscribePageConfig {
@@ -43,6 +43,13 @@ interface SubscribePageConfig {
43
43
  * - "system": OS/HTML 다크모드 설정 자동 적용
44
44
  */
45
45
  theme?: "light" | "dark" | "system";
46
+ /**
47
+ * 포트원 상점 ID (공개값, 제품의 NEXT_PUBLIC_PORTONE_STORE_ID).
48
+ * 카드 등록을 포트원 결제창으로 진행하는 데 필요하다 — 없으면 카드 추가가 막힌다.
49
+ */
50
+ portOneStoreId?: string;
51
+ /** 포트원 채널 키 (공개값, NEXT_PUBLIC_PORTONE_CHANNEL_KEY). */
52
+ portOneChannelKey?: string;
46
53
  }
47
54
  interface Subscription {
48
55
  id: string;
@@ -93,8 +100,8 @@ interface SubscriptionManagementConfig {
93
100
  theme?: "light" | "dark" | "system";
94
101
  }
95
102
 
96
- declare function SubscribePage({ plansApiPath, cardsApiPath, payApiPath, callbackUrl, serviceName, primaryColor, showExpiredBanner, theme, }: SubscribePageConfig): react_jsx_runtime.JSX.Element;
103
+ declare function SubscribePage({ plansApiPath, cardsApiPath, payApiPath, callbackUrl, serviceName, primaryColor, showExpiredBanner, theme, portOneStoreId, portOneChannelKey, }: SubscribePageConfig): react.JSX.Element;
97
104
 
98
- declare function SubscriptionManagement({ subscriptionsApiPath, paymentsApiPath, cardsApiPath, primaryColor, theme, }: SubscriptionManagementConfig): react_jsx_runtime.JSX.Element;
105
+ declare function SubscriptionManagement({ subscriptionsApiPath, paymentsApiPath, cardsApiPath, primaryColor, theme, }: SubscriptionManagementConfig): react.JSX.Element;
99
106
 
100
107
  export { type Card, type Payment, type Plan, SubscribePage, type SubscribePageConfig, type Subscription, SubscriptionManagement, type SubscriptionManagementConfig };
package/dist/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  "use client";
3
+ var __create = Object.create;
3
4
  var __defProp = Object.defineProperty;
4
5
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
6
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
9
  var __export = (target, all) => {
8
10
  for (var name in all)
@@ -16,6 +18,14 @@ var __copyProps = (to, from, except, desc) => {
16
18
  }
17
19
  return to;
18
20
  };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
19
29
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
30
 
21
31
  // src/index.ts
@@ -28,6 +38,7 @@ module.exports = __toCommonJS(index_exports);
28
38
 
29
39
  // src/components/SubscribePage.tsx
30
40
  var import_react = require("react");
41
+ var import_v2 = __toESM(require("@portone/browser-sdk/v2"));
31
42
  var import_jsx_runtime = require("react/jsx-runtime");
32
43
  var DEFAULT_PRIMARY = "#60A5FA";
33
44
  function SubscribePage({
@@ -38,7 +49,9 @@ function SubscribePage({
38
49
  serviceName = "\uC11C\uBE44\uC2A4",
39
50
  primaryColor = DEFAULT_PRIMARY,
40
51
  showExpiredBanner = true,
41
- theme = "system"
52
+ theme = "system",
53
+ portOneStoreId,
54
+ portOneChannelKey
42
55
  }) {
43
56
  const [step, setStep] = (0, import_react.useState)("plan");
44
57
  const [plans, setPlans] = (0, import_react.useState)([]);
@@ -49,15 +62,6 @@ function SubscribePage({
49
62
  const [submitting, setSubmitting] = (0, import_react.useState)(false);
50
63
  const [error, setError] = (0, import_react.useState)("");
51
64
  const [success, setSuccess] = (0, import_react.useState)(false);
52
- const [showCardForm, setShowCardForm] = (0, import_react.useState)(false);
53
- const [cardForm, setCardForm] = (0, import_react.useState)({
54
- cardNumber: "",
55
- expiryDate: "",
56
- cvc: "",
57
- cardPassword: "",
58
- cardHolderName: "",
59
- birthNumber: ""
60
- });
61
65
  const [cardError, setCardError] = (0, import_react.useState)("");
62
66
  const [cardSubmitting, setCardSubmitting] = (0, import_react.useState)(false);
63
67
  (0, import_react.useEffect)(() => {
@@ -90,15 +94,25 @@ function SubscribePage({
90
94
  }
91
95
  async function handleRegisterCard() {
92
96
  setCardError("");
97
+ if (!portOneStoreId || !portOneChannelKey) {
98
+ setCardError("\uACB0\uC81C \uC124\uC815\uC774 \uB204\uB77D\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uAD00\uB9AC\uC790\uC5D0\uAC8C \uBB38\uC758\uD558\uC138\uC694.");
99
+ return;
100
+ }
93
101
  setCardSubmitting(true);
94
102
  try {
103
+ const issue = await import_v2.default.requestIssueBillingKey({
104
+ storeId: portOneStoreId,
105
+ channelKey: portOneChannelKey,
106
+ billingKeyMethod: "CARD"
107
+ });
108
+ if (!issue || issue.code !== void 0) {
109
+ setCardError(issue?.message || "\uCE74\uB4DC \uB4F1\uB85D\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
110
+ return;
111
+ }
95
112
  const res = await fetch(cardsApiPath, {
96
113
  method: "POST",
97
114
  headers: { "Content-Type": "application/json" },
98
- body: JSON.stringify({
99
- ...cardForm,
100
- cardNumber: cardForm.cardNumber.replace(/\s/g, "")
101
- })
115
+ body: JSON.stringify({ billingKey: issue.billingKey })
102
116
  });
103
117
  const data = await res.json();
104
118
  if (!data.success) {
@@ -107,8 +121,6 @@ function SubscribePage({
107
121
  }
108
122
  await fetchCards();
109
123
  setSelectedCard(data.card);
110
- setShowCardForm(false);
111
- setCardForm({ cardNumber: "", expiryDate: "", cvc: "", cardPassword: "", cardHolderName: "", birthNumber: "" });
112
124
  } catch {
113
125
  setCardError("\uCE74\uB4DC \uB4F1\uB85D \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
114
126
  } finally {
@@ -255,7 +267,7 @@ function SubscribePage({
255
267
  ] }),
256
268
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
257
269
  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" }),
258
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-gray-400 dark:text-gray-500 text-xs", children: [
270
+ card.expiryMonth && card.expiryYear && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-gray-400 dark:text-gray-500 text-xs", children: [
259
271
  card.expiryMonth,
260
272
  "/",
261
273
  card.expiryYear
@@ -265,75 +277,16 @@ function SubscribePage({
265
277
  },
266
278
  card.id
267
279
  )) }),
268
- !showCardForm ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
280
+ cardError && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "mb-3 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 }),
281
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
269
282
  "button",
270
283
  {
271
- onClick: () => setShowCardForm(true),
272
- 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",
273
- children: "+ \uC0C8 \uCE74\uB4DC \uCD94\uAC00"
284
+ onClick: handleRegisterCard,
285
+ disabled: cardSubmitting,
286
+ 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 disabled:opacity-50 transition-all text-sm",
287
+ children: cardSubmitting ? "\uCE74\uB4DC \uB4F1\uB85D \uCC3D\uC744 \uC5EC\uB294 \uC911..." : "+ \uC0C8 \uCE74\uB4DC \uCD94\uAC00"
274
288
  }
275
- ) : /* @__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: [
276
- /* @__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" }),
277
- 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 }),
278
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3", children: [
279
- { label: "\uCE74\uB4DC \uBC88\uD638", key: "cardNumber", placeholder: "0000-0000-0000-0000", type: "text", colSpan: true },
280
- { label: "\uB9CC\uB8CC\uC77C", key: "expiryDate", placeholder: "MM/YY", type: "text", colSpan: false },
281
- { label: "CVC", key: "cvc", placeholder: "3\uC790\uB9AC", type: "password", colSpan: false },
282
- { label: "\uCE74\uB4DC \uBE44\uBC00\uBC88\uD638 \uC55E 2\uC790\uB9AC", key: "cardPassword", placeholder: "\u2022\u2022", type: "password", colSpan: false },
283
- { label: "\uCE74\uB4DC \uC18C\uC720\uC790\uBA85", key: "cardHolderName", placeholder: "\uD64D\uAE38\uB3D9", type: "text", colSpan: false },
284
- { label: "\uC0DD\uB144\uC6D4\uC77C 6\uC790\uB9AC / \uC0AC\uC5C5\uC790\uBC88\uD638 10\uC790\uB9AC", key: "birthNumber", placeholder: "990101", type: "text", colSpan: true }
285
- ].map(({ label, key, placeholder, type, colSpan }) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: colSpan ? "sm:col-span-2" : "", children: [
286
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "text-xs text-gray-500 dark:text-gray-400 block mb-1", children: label }),
287
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
288
- "input",
289
- {
290
- type,
291
- placeholder,
292
- value: cardForm[key],
293
- onChange: (e) => {
294
- const val = e.target.value;
295
- if (key === "cardNumber") {
296
- const formatted = val.replace(/\D/g, "").replace(/(\d{4})(?=\d)/g, "$1 ").slice(0, 19);
297
- setCardForm((f) => ({ ...f, [key]: formatted }));
298
- } else if (key === "expiryDate") {
299
- const digits = val.replace(/\D/g, "");
300
- let formatted = digits;
301
- if (digits.length > 2) {
302
- formatted = `${digits.slice(0, 2)}/${digits.slice(2, 4)}`;
303
- }
304
- setCardForm((f) => ({ ...f, [key]: formatted.slice(0, 5) }));
305
- } else {
306
- setCardForm((f) => ({ ...f, [key]: val }));
307
- }
308
- },
309
- 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"
310
- }
311
- )
312
- ] }, key)) }),
313
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-2 pt-1", children: [
314
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
315
- "button",
316
- {
317
- onClick: () => {
318
- setShowCardForm(false);
319
- setCardError("");
320
- },
321
- 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",
322
- children: "\uCDE8\uC18C"
323
- }
324
- ),
325
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
326
- "button",
327
- {
328
- onClick: handleRegisterCard,
329
- disabled: cardSubmitting,
330
- style: { backgroundColor: primaryColor },
331
- className: "flex-1 py-2.5 rounded-lg text-white text-sm font-medium hover:opacity-90 disabled:opacity-50 transition-all",
332
- children: cardSubmitting ? "\uB4F1\uB85D \uC911..." : "\uCE74\uB4DC \uB4F1\uB85D"
333
- }
334
- )
335
- ] })
336
- ] }),
289
+ ),
337
290
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-3 mt-6", children: [
338
291
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
339
292
  "button",
package/dist/index.mjs CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  // src/components/SubscribePage.tsx
4
4
  import { useState, useEffect } from "react";
5
+ import PortOne from "@portone/browser-sdk/v2";
5
6
  import { jsx, jsxs } from "react/jsx-runtime";
6
7
  var DEFAULT_PRIMARY = "#60A5FA";
7
8
  function SubscribePage({
@@ -12,7 +13,9 @@ function SubscribePage({
12
13
  serviceName = "\uC11C\uBE44\uC2A4",
13
14
  primaryColor = DEFAULT_PRIMARY,
14
15
  showExpiredBanner = true,
15
- theme = "system"
16
+ theme = "system",
17
+ portOneStoreId,
18
+ portOneChannelKey
16
19
  }) {
17
20
  const [step, setStep] = useState("plan");
18
21
  const [plans, setPlans] = useState([]);
@@ -23,15 +26,6 @@ function SubscribePage({
23
26
  const [submitting, setSubmitting] = useState(false);
24
27
  const [error, setError] = useState("");
25
28
  const [success, setSuccess] = useState(false);
26
- const [showCardForm, setShowCardForm] = useState(false);
27
- const [cardForm, setCardForm] = useState({
28
- cardNumber: "",
29
- expiryDate: "",
30
- cvc: "",
31
- cardPassword: "",
32
- cardHolderName: "",
33
- birthNumber: ""
34
- });
35
29
  const [cardError, setCardError] = useState("");
36
30
  const [cardSubmitting, setCardSubmitting] = useState(false);
37
31
  useEffect(() => {
@@ -64,15 +58,25 @@ function SubscribePage({
64
58
  }
65
59
  async function handleRegisterCard() {
66
60
  setCardError("");
61
+ if (!portOneStoreId || !portOneChannelKey) {
62
+ setCardError("\uACB0\uC81C \uC124\uC815\uC774 \uB204\uB77D\uB418\uC5C8\uC2B5\uB2C8\uB2E4. \uAD00\uB9AC\uC790\uC5D0\uAC8C \uBB38\uC758\uD558\uC138\uC694.");
63
+ return;
64
+ }
67
65
  setCardSubmitting(true);
68
66
  try {
67
+ const issue = await PortOne.requestIssueBillingKey({
68
+ storeId: portOneStoreId,
69
+ channelKey: portOneChannelKey,
70
+ billingKeyMethod: "CARD"
71
+ });
72
+ if (!issue || issue.code !== void 0) {
73
+ setCardError(issue?.message || "\uCE74\uB4DC \uB4F1\uB85D\uC774 \uCDE8\uC18C\uB418\uC5C8\uC2B5\uB2C8\uB2E4.");
74
+ return;
75
+ }
69
76
  const res = await fetch(cardsApiPath, {
70
77
  method: "POST",
71
78
  headers: { "Content-Type": "application/json" },
72
- body: JSON.stringify({
73
- ...cardForm,
74
- cardNumber: cardForm.cardNumber.replace(/\s/g, "")
75
- })
79
+ body: JSON.stringify({ billingKey: issue.billingKey })
76
80
  });
77
81
  const data = await res.json();
78
82
  if (!data.success) {
@@ -81,8 +85,6 @@ function SubscribePage({
81
85
  }
82
86
  await fetchCards();
83
87
  setSelectedCard(data.card);
84
- setShowCardForm(false);
85
- setCardForm({ cardNumber: "", expiryDate: "", cvc: "", cardPassword: "", cardHolderName: "", birthNumber: "" });
86
88
  } catch {
87
89
  setCardError("\uCE74\uB4DC \uB4F1\uB85D \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
88
90
  } finally {
@@ -229,7 +231,7 @@ function SubscribePage({
229
231
  ] }),
230
232
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
231
233
  card.isDefault && /* @__PURE__ */ 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" }),
232
- /* @__PURE__ */ jsxs("span", { className: "text-gray-400 dark:text-gray-500 text-xs", children: [
234
+ card.expiryMonth && card.expiryYear && /* @__PURE__ */ jsxs("span", { className: "text-gray-400 dark:text-gray-500 text-xs", children: [
233
235
  card.expiryMonth,
234
236
  "/",
235
237
  card.expiryYear
@@ -239,75 +241,16 @@ function SubscribePage({
239
241
  },
240
242
  card.id
241
243
  )) }),
242
- !showCardForm ? /* @__PURE__ */ jsx(
244
+ cardError && /* @__PURE__ */ jsx("div", { className: "mb-3 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 }),
245
+ /* @__PURE__ */ jsx(
243
246
  "button",
244
247
  {
245
- onClick: () => setShowCardForm(true),
246
- 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
- children: "+ \uC0C8 \uCE74\uB4DC \uCD94\uAC00"
248
+ onClick: handleRegisterCard,
249
+ disabled: cardSubmitting,
250
+ 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 disabled:opacity-50 transition-all text-sm",
251
+ children: cardSubmitting ? "\uCE74\uB4DC \uB4F1\uB85D \uCC3D\uC744 \uC5EC\uB294 \uC911..." : "+ \uC0C8 \uCE74\uB4DC \uCD94\uAC00"
248
252
  }
249
- ) : /* @__PURE__ */ 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: [
250
- /* @__PURE__ */ jsx("h3", { className: "text-gray-900 dark:text-white text-sm font-medium", children: "\uCE74\uB4DC \uC815\uBCF4 \uC785\uB825" }),
251
- cardError && /* @__PURE__ */ 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 }),
252
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3", children: [
253
- { label: "\uCE74\uB4DC \uBC88\uD638", key: "cardNumber", placeholder: "0000-0000-0000-0000", type: "text", colSpan: true },
254
- { label: "\uB9CC\uB8CC\uC77C", key: "expiryDate", placeholder: "MM/YY", type: "text", colSpan: false },
255
- { label: "CVC", key: "cvc", placeholder: "3\uC790\uB9AC", type: "password", colSpan: false },
256
- { label: "\uCE74\uB4DC \uBE44\uBC00\uBC88\uD638 \uC55E 2\uC790\uB9AC", key: "cardPassword", placeholder: "\u2022\u2022", type: "password", colSpan: false },
257
- { label: "\uCE74\uB4DC \uC18C\uC720\uC790\uBA85", key: "cardHolderName", placeholder: "\uD64D\uAE38\uB3D9", type: "text", colSpan: false },
258
- { label: "\uC0DD\uB144\uC6D4\uC77C 6\uC790\uB9AC / \uC0AC\uC5C5\uC790\uBC88\uD638 10\uC790\uB9AC", key: "birthNumber", placeholder: "990101", type: "text", colSpan: true }
259
- ].map(({ label, key, placeholder, type, colSpan }) => /* @__PURE__ */ jsxs("div", { className: colSpan ? "sm:col-span-2" : "", children: [
260
- /* @__PURE__ */ jsx("label", { className: "text-xs text-gray-500 dark:text-gray-400 block mb-1", children: label }),
261
- /* @__PURE__ */ jsx(
262
- "input",
263
- {
264
- type,
265
- placeholder,
266
- value: cardForm[key],
267
- onChange: (e) => {
268
- const val = e.target.value;
269
- if (key === "cardNumber") {
270
- const formatted = val.replace(/\D/g, "").replace(/(\d{4})(?=\d)/g, "$1 ").slice(0, 19);
271
- setCardForm((f) => ({ ...f, [key]: formatted }));
272
- } else if (key === "expiryDate") {
273
- const digits = val.replace(/\D/g, "");
274
- let formatted = digits;
275
- if (digits.length > 2) {
276
- formatted = `${digits.slice(0, 2)}/${digits.slice(2, 4)}`;
277
- }
278
- setCardForm((f) => ({ ...f, [key]: formatted.slice(0, 5) }));
279
- } else {
280
- setCardForm((f) => ({ ...f, [key]: val }));
281
- }
282
- },
283
- 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"
284
- }
285
- )
286
- ] }, key)) }),
287
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-1", children: [
288
- /* @__PURE__ */ jsx(
289
- "button",
290
- {
291
- onClick: () => {
292
- setShowCardForm(false);
293
- setCardError("");
294
- },
295
- 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",
296
- children: "\uCDE8\uC18C"
297
- }
298
- ),
299
- /* @__PURE__ */ jsx(
300
- "button",
301
- {
302
- onClick: handleRegisterCard,
303
- disabled: cardSubmitting,
304
- style: { backgroundColor: primaryColor },
305
- className: "flex-1 py-2.5 rounded-lg text-white text-sm font-medium hover:opacity-90 disabled:opacity-50 transition-all",
306
- children: cardSubmitting ? "\uB4F1\uB85D \uC911..." : "\uCE74\uB4DC \uB4F1\uB85D"
307
- }
308
- )
309
- ] })
310
- ] }),
253
+ ),
311
254
  /* @__PURE__ */ jsxs("div", { className: "flex gap-3 mt-6", children: [
312
255
  /* @__PURE__ */ jsx(
313
256
  "button",
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.0.8",
6
+ "version": "1.1.0",
7
7
  "description": "Subscription UI components for ThinkingCat services",
8
8
  "main": "dist/index.js",
9
9
  "module": "dist/index.mjs",
@@ -45,5 +45,8 @@
45
45
  "files": [
46
46
  "dist",
47
47
  "README.md"
48
- ]
49
- }
48
+ ],
49
+ "dependencies": {
50
+ "@portone/browser-sdk": "^0.0.19"
51
+ }
52
+ }