@thinkingcat/subscription-ui 1.0.7 → 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,12 +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(cardForm)
115
+ body: JSON.stringify({ billingKey: issue.billingKey })
99
116
  });
100
117
  const data = await res.json();
101
118
  if (!data.success) {
@@ -104,8 +121,6 @@ function SubscribePage({
104
121
  }
105
122
  await fetchCards();
106
123
  setSelectedCard(data.card);
107
- setShowCardForm(false);
108
- setCardForm({ cardNumber: "", expiryDate: "", cvc: "", cardPassword: "", cardHolderName: "", birthNumber: "" });
109
124
  } catch {
110
125
  setCardError("\uCE74\uB4DC \uB4F1\uB85D \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
111
126
  } finally {
@@ -252,7 +267,7 @@ function SubscribePage({
252
267
  ] }),
253
268
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
254
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" }),
255
- /* @__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: [
256
271
  card.expiryMonth,
257
272
  "/",
258
273
  card.expiryYear
@@ -262,60 +277,16 @@ function SubscribePage({
262
277
  },
263
278
  card.id
264
279
  )) }),
265
- !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)(
266
282
  "button",
267
283
  {
268
- onClick: () => setShowCardForm(true),
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",
270
- 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"
271
288
  }
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 }),
284
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
285
- "input",
286
- {
287
- type,
288
- placeholder,
289
- value: cardForm[key],
290
- onChange: (e) => setCardForm((f) => ({ ...f, [key]: e.target.value })),
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"
292
- }
293
- )
294
- ] }, key)) }),
295
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-2 pt-1", children: [
296
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
297
- "button",
298
- {
299
- onClick: () => {
300
- setShowCardForm(false);
301
- setCardError("");
302
- },
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",
304
- children: "\uCDE8\uC18C"
305
- }
306
- ),
307
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
308
- "button",
309
- {
310
- onClick: handleRegisterCard,
311
- disabled: cardSubmitting,
312
- style: { backgroundColor: primaryColor },
313
- className: "flex-1 py-2.5 rounded-lg text-white text-sm font-medium hover:opacity-90 disabled:opacity-50 transition-all",
314
- children: cardSubmitting ? "\uB4F1\uB85D \uC911..." : "\uCE74\uB4DC \uB4F1\uB85D"
315
- }
316
- )
317
- ] })
318
- ] }),
289
+ ),
319
290
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex gap-3 mt-6", children: [
320
291
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
321
292
  "button",
@@ -545,7 +516,10 @@ function SubscriptionManagement({
545
516
  const res = await fetch(cardsApiPath, {
546
517
  method: "POST",
547
518
  headers: { "Content-Type": "application/json" },
548
- body: JSON.stringify(cardForm)
519
+ body: JSON.stringify({
520
+ ...cardForm,
521
+ cardNumber: cardForm.cardNumber.replace(/\s/g, "")
522
+ })
549
523
  });
550
524
  const data = await res.json();
551
525
  if (!data.success) {
@@ -688,7 +662,22 @@ function SubscriptionManagement({
688
662
  type,
689
663
  placeholder,
690
664
  value: cardForm[key],
691
- onChange: (e) => setCardForm((f) => ({ ...f, [key]: e.target.value })),
665
+ onChange: (e) => {
666
+ const val = e.target.value;
667
+ if (key === "cardNumber") {
668
+ const formatted = val.replace(/\D/g, "").replace(/(\d{4})(?=\d)/g, "$1 ").slice(0, 19);
669
+ setCardForm((f) => ({ ...f, [key]: formatted }));
670
+ } else if (key === "expiryDate") {
671
+ const digits = val.replace(/\D/g, "");
672
+ let formatted = digits;
673
+ if (digits.length > 2) {
674
+ formatted = `${digits.slice(0, 2)}/${digits.slice(2, 4)}`;
675
+ }
676
+ setCardForm((f) => ({ ...f, [key]: formatted.slice(0, 5) }));
677
+ } else {
678
+ setCardForm((f) => ({ ...f, [key]: val }));
679
+ }
680
+ },
692
681
  className: "w-full bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-lg px-3 py-2 text-gray-900 dark:text-white text-sm placeholder-gray-300 dark:placeholder-gray-600 focus:outline-none focus:border-blue-500 transition-colors"
693
682
  }
694
683
  )
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,12 +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(cardForm)
79
+ body: JSON.stringify({ billingKey: issue.billingKey })
73
80
  });
74
81
  const data = await res.json();
75
82
  if (!data.success) {
@@ -78,8 +85,6 @@ function SubscribePage({
78
85
  }
79
86
  await fetchCards();
80
87
  setSelectedCard(data.card);
81
- setShowCardForm(false);
82
- setCardForm({ cardNumber: "", expiryDate: "", cvc: "", cardPassword: "", cardHolderName: "", birthNumber: "" });
83
88
  } catch {
84
89
  setCardError("\uCE74\uB4DC \uB4F1\uB85D \uC911 \uC624\uB958\uAC00 \uBC1C\uC0DD\uD588\uC2B5\uB2C8\uB2E4.");
85
90
  } finally {
@@ -226,7 +231,7 @@ function SubscribePage({
226
231
  ] }),
227
232
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-shrink-0", children: [
228
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" }),
229
- /* @__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: [
230
235
  card.expiryMonth,
231
236
  "/",
232
237
  card.expiryYear
@@ -236,60 +241,16 @@ function SubscribePage({
236
241
  },
237
242
  card.id
238
243
  )) }),
239
- !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(
240
246
  "button",
241
247
  {
242
- onClick: () => setShowCardForm(true),
243
- 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",
244
- 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"
245
252
  }
246
- ) : /* @__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: [
247
- /* @__PURE__ */ jsx("h3", { className: "text-gray-900 dark:text-white text-sm font-medium", children: "\uCE74\uB4DC \uC815\uBCF4 \uC785\uB825" }),
248
- 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 }),
249
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3", children: [
250
- { label: "\uCE74\uB4DC \uBC88\uD638", key: "cardNumber", placeholder: "0000-0000-0000-0000", type: "text", colSpan: true },
251
- { label: "\uB9CC\uB8CC\uC77C", key: "expiryDate", placeholder: "MM/YY", type: "text", colSpan: false },
252
- { label: "CVC", key: "cvc", placeholder: "3\uC790\uB9AC", type: "password", colSpan: false },
253
- { label: "\uCE74\uB4DC \uBE44\uBC00\uBC88\uD638 \uC55E 2\uC790\uB9AC", key: "cardPassword", placeholder: "\u2022\u2022", type: "password", colSpan: false },
254
- { label: "\uCE74\uB4DC \uC18C\uC720\uC790\uBA85", key: "cardHolderName", placeholder: "\uD64D\uAE38\uB3D9", type: "text", colSpan: false },
255
- { label: "\uC0DD\uB144\uC6D4\uC77C 6\uC790\uB9AC / \uC0AC\uC5C5\uC790\uBC88\uD638 10\uC790\uB9AC", key: "birthNumber", placeholder: "990101", type: "text", colSpan: true }
256
- ].map(({ label, key, placeholder, type, colSpan }) => /* @__PURE__ */ jsxs("div", { className: colSpan ? "sm:col-span-2" : "", children: [
257
- /* @__PURE__ */ jsx("label", { className: "text-xs text-gray-500 dark:text-gray-400 block mb-1", children: label }),
258
- /* @__PURE__ */ jsx(
259
- "input",
260
- {
261
- type,
262
- placeholder,
263
- value: cardForm[key],
264
- onChange: (e) => setCardForm((f) => ({ ...f, [key]: e.target.value })),
265
- 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"
266
- }
267
- )
268
- ] }, key)) }),
269
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2 pt-1", children: [
270
- /* @__PURE__ */ jsx(
271
- "button",
272
- {
273
- onClick: () => {
274
- setShowCardForm(false);
275
- setCardError("");
276
- },
277
- 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",
278
- children: "\uCDE8\uC18C"
279
- }
280
- ),
281
- /* @__PURE__ */ jsx(
282
- "button",
283
- {
284
- onClick: handleRegisterCard,
285
- disabled: cardSubmitting,
286
- style: { backgroundColor: primaryColor },
287
- className: "flex-1 py-2.5 rounded-lg text-white text-sm font-medium hover:opacity-90 disabled:opacity-50 transition-all",
288
- children: cardSubmitting ? "\uB4F1\uB85D \uC911..." : "\uCE74\uB4DC \uB4F1\uB85D"
289
- }
290
- )
291
- ] })
292
- ] }),
253
+ ),
293
254
  /* @__PURE__ */ jsxs("div", { className: "flex gap-3 mt-6", children: [
294
255
  /* @__PURE__ */ jsx(
295
256
  "button",
@@ -519,7 +480,10 @@ function SubscriptionManagement({
519
480
  const res = await fetch(cardsApiPath, {
520
481
  method: "POST",
521
482
  headers: { "Content-Type": "application/json" },
522
- body: JSON.stringify(cardForm)
483
+ body: JSON.stringify({
484
+ ...cardForm,
485
+ cardNumber: cardForm.cardNumber.replace(/\s/g, "")
486
+ })
523
487
  });
524
488
  const data = await res.json();
525
489
  if (!data.success) {
@@ -662,7 +626,22 @@ function SubscriptionManagement({
662
626
  type,
663
627
  placeholder,
664
628
  value: cardForm[key],
665
- onChange: (e) => setCardForm((f) => ({ ...f, [key]: e.target.value })),
629
+ onChange: (e) => {
630
+ const val = e.target.value;
631
+ if (key === "cardNumber") {
632
+ const formatted = val.replace(/\D/g, "").replace(/(\d{4})(?=\d)/g, "$1 ").slice(0, 19);
633
+ setCardForm((f) => ({ ...f, [key]: formatted }));
634
+ } else if (key === "expiryDate") {
635
+ const digits = val.replace(/\D/g, "");
636
+ let formatted = digits;
637
+ if (digits.length > 2) {
638
+ formatted = `${digits.slice(0, 2)}/${digits.slice(2, 4)}`;
639
+ }
640
+ setCardForm((f) => ({ ...f, [key]: formatted.slice(0, 5) }));
641
+ } else {
642
+ setCardForm((f) => ({ ...f, [key]: val }));
643
+ }
644
+ },
666
645
  className: "w-full bg-white dark:bg-white/5 border border-gray-200 dark:border-white/10 rounded-lg px-3 py-2 text-gray-900 dark:text-white text-sm placeholder-gray-300 dark:placeholder-gray-600 focus:outline-none focus:border-blue-500 transition-colors"
667
646
  }
668
647
  )
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.0.7",
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",
@@ -20,7 +20,12 @@
20
20
  "dev": "tsup src/index.ts --format cjs,esm --dts --external react --external react-dom --external next --watch",
21
21
  "prepublishOnly": "npm run build"
22
22
  },
23
- "keywords": ["subscription", "ui", "react", "thinkingcat"],
23
+ "keywords": [
24
+ "subscription",
25
+ "ui",
26
+ "react",
27
+ "thinkingcat"
28
+ ],
24
29
  "author": "ThinkingCat",
25
30
  "license": "MIT",
26
31
  "peerDependencies": {
@@ -40,5 +45,8 @@
40
45
  "files": [
41
46
  "dist",
42
47
  "README.md"
43
- ]
48
+ ],
49
+ "dependencies": {
50
+ "@portone/browser-sdk": "^0.0.19"
51
+ }
44
52
  }