@carlonicora/nextjs-jsonapi 1.14.0 → 1.16.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.
Files changed (279) hide show
  1. package/dist/{ApiResponseInterface-B4QdWh-y.d.mts → ApiResponseInterface-BvWIeLkq.d.ts} +2 -1
  2. package/dist/{ApiResponseInterface-QLDnxLA9.d.ts → ApiResponseInterface-CAbw0sv7.d.mts} +2 -1
  3. package/dist/{BlockNoteEditor-436ZHDY3.mjs → BlockNoteEditor-HFX7Z5BQ.mjs} +5 -5
  4. package/dist/{BlockNoteEditor-WCK43JHX.js → BlockNoteEditor-MBFDWP7X.js} +15 -15
  5. package/dist/{BlockNoteEditor-WCK43JHX.js.map → BlockNoteEditor-MBFDWP7X.js.map} +1 -1
  6. package/dist/JsonApiRequest-45CLE65I.js +24 -0
  7. package/dist/{JsonApiRequest-FXZCYIER.js.map → JsonApiRequest-45CLE65I.js.map} +1 -1
  8. package/dist/{JsonApiRequest-HFWXMKMA.mjs → JsonApiRequest-6IPS3DZJ.mjs} +2 -2
  9. package/dist/{chunk-R6K76UTR.js → chunk-2AZLCF6D.js} +1687 -183
  10. package/dist/chunk-2AZLCF6D.js.map +1 -0
  11. package/dist/{chunk-2FCG3K64.mjs → chunk-5RAUCUAA.mjs} +3722 -396
  12. package/dist/chunk-5RAUCUAA.mjs.map +1 -0
  13. package/dist/{chunk-TGBXBUWM.mjs → chunk-BCKYJQ3K.mjs} +8 -1
  14. package/dist/chunk-BCKYJQ3K.mjs.map +1 -0
  15. package/dist/{chunk-A333VMBO.mjs → chunk-BCQSE3EU.mjs} +1654 -150
  16. package/dist/chunk-BCQSE3EU.mjs.map +1 -0
  17. package/dist/{chunk-FPZPD4JI.js → chunk-GPGJNTHP.js} +17 -10
  18. package/dist/chunk-GPGJNTHP.js.map +1 -0
  19. package/dist/{chunk-45QMJETP.js → chunk-ONB2DAIV.js} +4090 -764
  20. package/dist/chunk-ONB2DAIV.js.map +1 -0
  21. package/dist/{chunk-SJIVGCNM.mjs → chunk-POKIJ56Q.mjs} +7 -2
  22. package/dist/chunk-POKIJ56Q.mjs.map +1 -0
  23. package/dist/{chunk-6YD42BP6.js → chunk-R5QSSISB.js} +14 -9
  24. package/dist/chunk-R5QSSISB.js.map +1 -0
  25. package/dist/client/index.d.mts +5 -5
  26. package/dist/client/index.d.ts +5 -5
  27. package/dist/client/index.js +7 -5
  28. package/dist/client/index.js.map +1 -1
  29. package/dist/client/index.mjs +6 -4
  30. package/dist/components/index.d.mts +253 -9
  31. package/dist/components/index.d.ts +253 -9
  32. package/dist/components/index.js +83 -5
  33. package/dist/components/index.js.map +1 -1
  34. package/dist/components/index.mjs +82 -4
  35. package/dist/{config-eceYM5kN.d.ts → config-CWsTwnsK.d.mts} +7 -2
  36. package/dist/{config-C5tGGrYf.d.mts → config-DEaUbBqR.d.ts} +7 -2
  37. package/dist/{content.interface-TB2MfJGs.d.ts → content.interface-D_4b4RQt.d.ts} +1 -1
  38. package/dist/{content.interface-CxBBC7ec.d.mts → content.interface-Dk4UZcJM.d.mts} +1 -1
  39. package/dist/contexts/index.d.mts +2 -2
  40. package/dist/contexts/index.d.ts +2 -2
  41. package/dist/contexts/index.js +5 -5
  42. package/dist/contexts/index.mjs +4 -4
  43. package/dist/core/index.d.mts +528 -22
  44. package/dist/core/index.d.ts +528 -22
  45. package/dist/core/index.js +53 -3
  46. package/dist/core/index.js.map +1 -1
  47. package/dist/core/index.mjs +52 -2
  48. package/dist/index.d.mts +7 -7
  49. package/dist/index.d.ts +7 -7
  50. package/dist/index.js +56 -4
  51. package/dist/index.js.map +1 -1
  52. package/dist/index.mjs +55 -3
  53. package/dist/{notification.interface-lG6UpTpt.d.mts → notification.interface-BllkURRm.d.mts} +1 -2
  54. package/dist/{notification.interface-lG6UpTpt.d.ts → notification.interface-BllkURRm.d.ts} +1 -2
  55. package/dist/{s3.service-DP_hsssD.d.mts → s3.service-BEfGqho0.d.ts} +20 -2
  56. package/dist/{s3.service-Dq-PTUNa.d.ts → s3.service-DIQRYe93.d.mts} +20 -2
  57. package/dist/scripts/generate-web-module/generator.d.ts.map +1 -1
  58. package/dist/scripts/generate-web-module/generator.js +66 -0
  59. package/dist/scripts/generate-web-module/generator.js.map +1 -1
  60. package/dist/scripts/generate-web-module/templates/data/interface.template.js +8 -1
  61. package/dist/scripts/generate-web-module/templates/data/interface.template.js.map +1 -1
  62. package/dist/scripts/generate-web-module/templates/data/model.template.js +26 -3
  63. package/dist/scripts/generate-web-module/templates/data/model.template.js.map +1 -1
  64. package/dist/scripts/generate-web-module/templates/index.d.ts +8 -0
  65. package/dist/scripts/generate-web-module/templates/index.d.ts.map +1 -1
  66. package/dist/scripts/generate-web-module/templates/index.js +18 -1
  67. package/dist/scripts/generate-web-module/templates/index.js.map +1 -1
  68. package/dist/scripts/generate-web-module/templates/project/bootstrapper.template.d.ts +7 -0
  69. package/dist/scripts/generate-web-module/templates/project/bootstrapper.template.d.ts.map +1 -0
  70. package/dist/scripts/generate-web-module/templates/project/bootstrapper.template.js +141 -0
  71. package/dist/scripts/generate-web-module/templates/project/bootstrapper.template.js.map +1 -0
  72. package/dist/scripts/generate-web-module/templates/project/env.template.d.ts +7 -0
  73. package/dist/scripts/generate-web-module/templates/project/env.template.d.ts.map +1 -0
  74. package/dist/scripts/generate-web-module/templates/project/env.template.js +110 -0
  75. package/dist/scripts/generate-web-module/templates/project/env.template.js.map +1 -0
  76. package/dist/scripts/generate-web-module/templates/project/main-layout.template.d.ts +7 -0
  77. package/dist/scripts/generate-web-module/templates/project/main-layout.template.d.ts.map +1 -0
  78. package/dist/scripts/generate-web-module/templates/project/main-layout.template.js +101 -0
  79. package/dist/scripts/generate-web-module/templates/project/main-layout.template.js.map +1 -0
  80. package/dist/scripts/generate-web-module/templates/project/middleware-env.template.d.ts +7 -0
  81. package/dist/scripts/generate-web-module/templates/project/middleware-env.template.d.ts.map +1 -0
  82. package/dist/scripts/generate-web-module/templates/project/middleware-env.template.js +66 -0
  83. package/dist/scripts/generate-web-module/templates/project/middleware-env.template.js.map +1 -0
  84. package/dist/scripts/generate-web-module/templates/project/settings-container.template.d.ts +7 -0
  85. package/dist/scripts/generate-web-module/templates/project/settings-container.template.d.ts.map +1 -0
  86. package/dist/scripts/generate-web-module/templates/project/settings-container.template.js +257 -0
  87. package/dist/scripts/generate-web-module/templates/project/settings-container.template.js.map +1 -0
  88. package/dist/scripts/generate-web-module/templates/project/settings-context.template.d.ts +7 -0
  89. package/dist/scripts/generate-web-module/templates/project/settings-context.template.d.ts.map +1 -0
  90. package/dist/scripts/generate-web-module/templates/project/settings-context.template.js +124 -0
  91. package/dist/scripts/generate-web-module/templates/project/settings-context.template.js.map +1 -0
  92. package/dist/scripts/generate-web-module/templates/project/settings-module-page.template.d.ts +7 -0
  93. package/dist/scripts/generate-web-module/templates/project/settings-module-page.template.d.ts.map +1 -0
  94. package/dist/scripts/generate-web-module/templates/project/settings-module-page.template.js +78 -0
  95. package/dist/scripts/generate-web-module/templates/project/settings-module-page.template.js.map +1 -0
  96. package/dist/scripts/generate-web-module/templates/project/settings-page.template.d.ts +7 -0
  97. package/dist/scripts/generate-web-module/templates/project/settings-page.template.d.ts.map +1 -0
  98. package/dist/scripts/generate-web-module/templates/project/settings-page.template.js +75 -0
  99. package/dist/scripts/generate-web-module/templates/project/settings-page.template.js.map +1 -0
  100. package/dist/scripts/generate-web-module/types/template-data.interface.d.ts +1 -1
  101. package/dist/scripts/generate-web-module/types/template-data.interface.d.ts.map +1 -1
  102. package/dist/server/index.d.mts +4 -4
  103. package/dist/server/index.d.ts +4 -4
  104. package/dist/server/index.js +12 -12
  105. package/dist/server/index.mjs +2 -2
  106. package/dist/stripe-subscription.interface-C63L6hVg.d.mts +226 -0
  107. package/dist/stripe-subscription.interface-CUvNDvw5.d.ts +226 -0
  108. package/dist/{useSocket-Bua6MwLi.d.mts → useSocket-BpenBR2z.d.mts} +1 -1
  109. package/dist/{useSocket-D5dhUp4m.d.ts → useSocket-D-QYA0Sr.d.ts} +1 -1
  110. package/package.json +9 -1
  111. package/scripts/generate-web-module/generator.ts +83 -0
  112. package/scripts/generate-web-module/templates/data/interface.template.ts +7 -1
  113. package/scripts/generate-web-module/templates/data/model.template.ts +28 -5
  114. package/scripts/generate-web-module/templates/index.ts +10 -0
  115. package/scripts/generate-web-module/templates/project/bootstrapper.template.ts +108 -0
  116. package/scripts/generate-web-module/templates/project/env.template.ts +77 -0
  117. package/scripts/generate-web-module/templates/project/main-layout.template.tsx +68 -0
  118. package/scripts/generate-web-module/templates/project/middleware-env.template.ts +33 -0
  119. package/scripts/generate-web-module/templates/project/settings-container.template.tsx +224 -0
  120. package/scripts/generate-web-module/templates/project/settings-context.template.tsx +91 -0
  121. package/scripts/generate-web-module/templates/project/settings-module-page.template.tsx +45 -0
  122. package/scripts/generate-web-module/templates/project/settings-page.template.tsx +42 -0
  123. package/scripts/generate-web-module/types/template-data.interface.ts +1 -1
  124. package/src/client/config.ts +9 -0
  125. package/src/components/index.ts +7 -0
  126. package/src/core/abstracts/AbstractApiData.ts +79 -16
  127. package/src/core/abstracts/AbstractService.ts +104 -0
  128. package/src/core/endpoint/EndpointCreator.ts +7 -4
  129. package/src/core/index.ts +12 -4
  130. package/src/core/interfaces/ApiResponseInterface.ts +1 -0
  131. package/src/core/registry/ModuleRegistry.ts +11 -2
  132. package/src/core/utils/translateResponse.ts +17 -0
  133. package/src/features/billing/components/cards/BillingUsageSummaryCard.tsx +97 -0
  134. package/src/features/billing/components/cards/CustomerInfoCard.tsx +112 -0
  135. package/src/features/billing/components/cards/InvoicesSummaryCard.tsx +114 -0
  136. package/src/features/billing/components/cards/PaymentMethodSummaryCard.tsx +119 -0
  137. package/src/features/billing/components/cards/SubscriptionSummaryCard.tsx +146 -0
  138. package/src/features/billing/components/cards/index.ts +5 -0
  139. package/src/features/billing/components/containers/BillingDashboardContainer.tsx +427 -0
  140. package/src/features/billing/components/containers/index.ts +1 -0
  141. package/src/features/billing/components/index.ts +6 -0
  142. package/src/features/billing/components/modals/BillingDetailModal.tsx +36 -0
  143. package/src/features/billing/components/modals/index.ts +1 -0
  144. package/src/features/billing/components/providers/StripeProvider.tsx +48 -0
  145. package/src/features/billing/components/providers/index.ts +1 -0
  146. package/src/features/billing/components/utils/currency.ts +49 -0
  147. package/src/features/billing/components/utils/date.ts +21 -0
  148. package/src/features/billing/components/utils/index.ts +2 -0
  149. package/src/features/billing/components/widgets/BillingAlertBanner.tsx +63 -0
  150. package/src/features/billing/components/widgets/index.ts +1 -0
  151. package/src/features/billing/data/Billing.ts +17 -0
  152. package/src/features/billing/data/billing.service.ts +58 -0
  153. package/src/features/billing/data/index.ts +5 -0
  154. package/src/features/billing/index.ts +3 -0
  155. package/src/features/billing/modules/billing.module.ts +9 -0
  156. package/src/features/billing/modules/index.ts +1 -0
  157. package/src/features/billing/stripe-customer/components/containers/PaymentMethodsContainer.tsx +79 -0
  158. package/src/features/billing/stripe-customer/components/containers/index.ts +1 -0
  159. package/src/features/billing/stripe-customer/components/details/PaymentMethodCard.tsx +151 -0
  160. package/src/features/billing/stripe-customer/components/details/index.ts +1 -0
  161. package/src/features/billing/stripe-customer/components/forms/PaymentMethodEditor.tsx +186 -0
  162. package/src/features/billing/stripe-customer/components/forms/index.ts +1 -0
  163. package/src/features/billing/stripe-customer/components/index.ts +4 -0
  164. package/src/features/billing/stripe-customer/components/lists/PaymentMethodsList.tsx +19 -0
  165. package/src/features/billing/stripe-customer/components/lists/index.ts +1 -0
  166. package/src/features/billing/stripe-customer/data/index.ts +5 -0
  167. package/src/features/billing/stripe-customer/data/payment-method.interface.ts +27 -0
  168. package/src/features/billing/stripe-customer/data/payment-method.ts +119 -0
  169. package/src/features/billing/stripe-customer/data/stripe-customer.interface.ts +16 -0
  170. package/src/features/billing/stripe-customer/data/stripe-customer.service.ts +128 -0
  171. package/src/features/billing/stripe-customer/data/stripe-customer.ts +71 -0
  172. package/src/features/billing/stripe-customer/index.ts +3 -0
  173. package/src/features/billing/stripe-customer/stripe-customer.module.ts +9 -0
  174. package/src/features/billing/stripe-customer/stripe-payment-method.module.ts +9 -0
  175. package/src/features/billing/stripe-invoice/components/containers/InvoicesContainer.tsx +66 -0
  176. package/src/features/billing/stripe-invoice/components/containers/index.ts +1 -0
  177. package/src/features/billing/stripe-invoice/components/details/InvoiceDetails.tsx +172 -0
  178. package/src/features/billing/stripe-invoice/components/details/index.ts +1 -0
  179. package/src/features/billing/stripe-invoice/components/index.ts +4 -0
  180. package/src/features/billing/stripe-invoice/components/lists/InvoicesList.tsx +84 -0
  181. package/src/features/billing/stripe-invoice/components/lists/index.ts +1 -0
  182. package/src/features/billing/stripe-invoice/components/widgets/InvoiceStatusBadge.tsx +41 -0
  183. package/src/features/billing/stripe-invoice/components/widgets/index.ts +1 -0
  184. package/src/features/billing/stripe-invoice/data/index.ts +3 -0
  185. package/src/features/billing/stripe-invoice/data/stripe-invoice.interface.ts +65 -0
  186. package/src/features/billing/stripe-invoice/data/stripe-invoice.service.ts +64 -0
  187. package/src/features/billing/stripe-invoice/data/stripe-invoice.ts +177 -0
  188. package/src/features/billing/stripe-invoice/index.ts +2 -0
  189. package/src/features/billing/stripe-invoice/stripe-invoice.module.ts +9 -0
  190. package/src/features/billing/stripe-price/components/forms/PriceEditor.tsx +304 -0
  191. package/src/features/billing/stripe-price/components/forms/index.ts +1 -0
  192. package/src/features/billing/stripe-price/components/index.ts +2 -0
  193. package/src/features/billing/stripe-price/components/lists/PricesList.tsx +283 -0
  194. package/src/features/billing/stripe-price/components/lists/index.ts +1 -0
  195. package/src/features/billing/stripe-price/data/index.ts +3 -0
  196. package/src/features/billing/stripe-price/data/stripe-price.interface.ts +48 -0
  197. package/src/features/billing/stripe-price/data/stripe-price.service.ts +123 -0
  198. package/src/features/billing/stripe-price/data/stripe-price.ts +156 -0
  199. package/src/features/billing/stripe-price/index.ts +2 -0
  200. package/src/features/billing/stripe-price/stripe-price.module.ts +9 -0
  201. package/src/features/billing/stripe-product/components/containers/ProductsAdminContainer.tsx +86 -0
  202. package/src/features/billing/stripe-product/components/containers/index.ts +1 -0
  203. package/src/features/billing/stripe-product/components/forms/ProductEditor.tsx +100 -0
  204. package/src/features/billing/stripe-product/components/forms/index.ts +1 -0
  205. package/src/features/billing/stripe-product/components/index.ts +3 -0
  206. package/src/features/billing/stripe-product/components/lists/ProductsList.tsx +206 -0
  207. package/src/features/billing/stripe-product/components/lists/index.ts +1 -0
  208. package/src/features/billing/stripe-product/data/index.ts +3 -0
  209. package/src/features/billing/stripe-product/data/stripe-product.interface.ts +18 -0
  210. package/src/features/billing/stripe-product/data/stripe-product.service.ts +112 -0
  211. package/src/features/billing/stripe-product/data/stripe-product.ts +74 -0
  212. package/src/features/billing/stripe-product/index.ts +2 -0
  213. package/src/features/billing/stripe-product/stripe-product.module.ts +9 -0
  214. package/src/features/billing/stripe-subscription/components/containers/SubscriptionsContainer.tsx +304 -0
  215. package/src/features/billing/stripe-subscription/components/containers/index.ts +1 -0
  216. package/src/features/billing/stripe-subscription/components/details/SubscriptionDetails.tsx +223 -0
  217. package/src/features/billing/stripe-subscription/components/details/index.ts +1 -0
  218. package/src/features/billing/stripe-subscription/components/forms/CancelSubscriptionDialog.tsx +116 -0
  219. package/src/features/billing/stripe-subscription/components/forms/SubscriptionEditor.tsx +331 -0
  220. package/src/features/billing/stripe-subscription/components/forms/index.ts +2 -0
  221. package/src/features/billing/stripe-subscription/components/index.ts +5 -0
  222. package/src/features/billing/stripe-subscription/components/lists/SubscriptionsList.tsx +104 -0
  223. package/src/features/billing/stripe-subscription/components/lists/index.ts +1 -0
  224. package/src/features/billing/stripe-subscription/components/widgets/PricingCard.tsx +95 -0
  225. package/src/features/billing/stripe-subscription/components/widgets/PricingCardsGrid.tsx +110 -0
  226. package/src/features/billing/stripe-subscription/components/widgets/ProrationPreview.tsx +41 -0
  227. package/src/features/billing/stripe-subscription/components/widgets/SubscriptionStatusBadge.tsx +60 -0
  228. package/src/features/billing/stripe-subscription/components/widgets/index.ts +4 -0
  229. package/src/features/billing/stripe-subscription/data/index.ts +3 -0
  230. package/src/features/billing/stripe-subscription/data/stripe-subscription.interface.ts +66 -0
  231. package/src/features/billing/stripe-subscription/data/stripe-subscription.service.ts +193 -0
  232. package/src/features/billing/stripe-subscription/data/stripe-subscription.ts +135 -0
  233. package/src/features/billing/stripe-subscription/hooks/index.ts +1 -0
  234. package/src/features/billing/stripe-subscription/hooks/useConfirmSubscriptionPayment.ts +111 -0
  235. package/src/features/billing/stripe-subscription/index.ts +5 -0
  236. package/src/features/billing/stripe-subscription/stripe-subscription.module.ts +9 -0
  237. package/src/features/billing/stripe-usage/components/containers/UsageContainer.tsx +109 -0
  238. package/src/features/billing/stripe-usage/components/containers/index.ts +1 -0
  239. package/src/features/billing/stripe-usage/components/details/UsageSummaryCard.tsx +90 -0
  240. package/src/features/billing/stripe-usage/components/details/index.ts +1 -0
  241. package/src/features/billing/stripe-usage/components/index.ts +4 -0
  242. package/src/features/billing/stripe-usage/components/lists/UsageHistoryTable.tsx +72 -0
  243. package/src/features/billing/stripe-usage/components/lists/index.ts +1 -0
  244. package/src/features/billing/stripe-usage/components/widgets/UsageSummaryCards.tsx +19 -0
  245. package/src/features/billing/stripe-usage/components/widgets/index.ts +1 -0
  246. package/src/features/billing/stripe-usage/data/index.ts +3 -0
  247. package/src/features/billing/stripe-usage/data/stripe-usage.interface.ts +55 -0
  248. package/src/features/billing/stripe-usage/data/stripe-usage.service.ts +129 -0
  249. package/src/features/billing/stripe-usage/data/stripe-usage.ts +70 -0
  250. package/src/features/billing/stripe-usage/index.ts +2 -0
  251. package/src/features/billing/stripe-usage/stripe-usage.module.ts +9 -0
  252. package/src/features/company/components/forms/CompanyEditor.tsx +2 -2
  253. package/src/features/company/components/forms/CompanyLicense.tsx +4 -4
  254. package/src/features/company/contexts/CompanyContext.tsx +2 -2
  255. package/src/features/feature/components/forms/FormFeatures.tsx +13 -106
  256. package/src/features/feature/data/feature.interface.ts +1 -1
  257. package/src/features/feature/data/feature.ts +4 -4
  258. package/src/features/index.ts +7 -0
  259. package/src/features/module/data/module.interface.ts +0 -1
  260. package/src/features/module/data/module.ts +0 -6
  261. package/src/features/user/components/lists/ContributorsList.tsx +2 -2
  262. package/src/features/user/components/widgets/UserAvatar.tsx +1 -1
  263. package/src/index.ts +1 -1
  264. package/src/shadcnui/custom/link.tsx +16 -6
  265. package/src/utils/blocknote-diff.util.ts +2 -1
  266. package/src/utils/blocknote-word-diff-renderer.util.ts +8 -7
  267. package/dist/AuthComponent-hxOPs9o8.d.mts +0 -11
  268. package/dist/AuthComponent-hxOPs9o8.d.ts +0 -11
  269. package/dist/JsonApiRequest-FXZCYIER.js +0 -24
  270. package/dist/chunk-2FCG3K64.mjs.map +0 -1
  271. package/dist/chunk-45QMJETP.js.map +0 -1
  272. package/dist/chunk-6YD42BP6.js.map +0 -1
  273. package/dist/chunk-A333VMBO.mjs.map +0 -1
  274. package/dist/chunk-FPZPD4JI.js.map +0 -1
  275. package/dist/chunk-R6K76UTR.js.map +0 -1
  276. package/dist/chunk-SJIVGCNM.mjs.map +0 -1
  277. package/dist/chunk-TGBXBUWM.mjs.map +0 -1
  278. /package/dist/{BlockNoteEditor-436ZHDY3.mjs.map → BlockNoteEditor-HFX7Z5BQ.mjs.map} +0 -0
  279. /package/dist/{JsonApiRequest-HFWXMKMA.mjs.map → JsonApiRequest-6IPS3DZJ.mjs.map} +0 -0
@@ -0,0 +1,304 @@
1
+ "use client";
2
+
3
+ import { zodResolver } from "@hookform/resolvers/zod";
4
+ import { AlertCircle, PlusIcon, XIcon } from "lucide-react";
5
+ import { useState } from "react";
6
+ import { SubmitHandler, useForm } from "react-hook-form";
7
+ import { v4 } from "uuid";
8
+ import { z } from "zod";
9
+ import { FormCheckbox, FormInput, FormSelect, FormTextarea } from "../../../../../components";
10
+ import { CommonEditorButtons } from "../../../../../components/forms/CommonEditorButtons";
11
+ import {
12
+ Button,
13
+ Dialog,
14
+ DialogContent,
15
+ DialogDescription,
16
+ DialogHeader,
17
+ DialogTitle,
18
+ Form,
19
+ FormControl,
20
+ FormItem,
21
+ FormLabel,
22
+ Input,
23
+ } from "../../../../../shadcnui";
24
+ import { StripePriceInterface, StripePriceService } from "../../data";
25
+
26
+ type PriceEditorProps = {
27
+ productId: string;
28
+ price?: StripePriceInterface;
29
+ open: boolean;
30
+ onOpenChange: (open: boolean) => void;
31
+ onSuccess: () => void;
32
+ };
33
+
34
+ type PriceFormValues = {
35
+ unitAmount: number;
36
+ currency: string;
37
+ interval: "one_time" | "day" | "week" | "month" | "year";
38
+ intervalCount?: number;
39
+ usageType?: "licensed" | "metered";
40
+ nickname?: string;
41
+ active: boolean;
42
+ description?: string;
43
+ features: string[];
44
+ };
45
+
46
+ export function PriceEditor({ productId, price, open, onOpenChange, onSuccess }: PriceEditorProps) {
47
+ const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
48
+
49
+ const formSchema = z.object({
50
+ unitAmount: z.preprocess(
51
+ (val) => (typeof val === "string" ? parseFloat(val) : val),
52
+ z.number().min(0, { message: "Amount must be 0 or greater" }),
53
+ ),
54
+ currency: z.string().min(1, { message: "Currency is required" }),
55
+ interval: z.enum(["one_time", "day", "week", "month", "year"]),
56
+ intervalCount: z.preprocess(
57
+ (val) => (val === "" || val === undefined ? undefined : typeof val === "string" ? parseInt(val, 10) : val),
58
+ z.number().min(1).optional(),
59
+ ),
60
+ usageType: z.enum(["licensed", "metered"]).optional(),
61
+ nickname: z.string().optional(),
62
+ active: z.boolean(),
63
+ description: z.string().optional(),
64
+ features: z.array(z.string()),
65
+ });
66
+
67
+ const isEditMode = !!price;
68
+
69
+ // Convert cents to dollars for display
70
+ const defaultUnitAmount = price?.unitAmount ? price.unitAmount / 100 : 0;
71
+
72
+ const form = useForm<PriceFormValues>({
73
+ resolver: zodResolver(formSchema) as any,
74
+ defaultValues: {
75
+ unitAmount: defaultUnitAmount,
76
+ currency: price?.currency || "usd",
77
+ interval: price?.priceType === "one_time" ? "one_time" : price?.recurring?.interval || "month",
78
+ intervalCount: price?.recurring?.intervalCount || 1,
79
+ usageType: price?.recurring?.usageType || "licensed",
80
+ nickname: price?.nickname || "",
81
+ active: price?.active ?? true,
82
+ description: price?.description || "",
83
+ features: price?.features || [],
84
+ },
85
+ });
86
+
87
+ const watchInterval = form.watch("interval");
88
+ const isRecurring = watchInterval !== "one_time";
89
+
90
+ const onSubmit: SubmitHandler<PriceFormValues> = async (values) => {
91
+ setIsSubmitting(true);
92
+
93
+ try {
94
+ // Convert dollars to cents
95
+ const unitAmountInCents = Math.round(values.unitAmount * 100);
96
+
97
+ if (isEditMode) {
98
+ // Update existing price (nickname, description, features can be updated - Stripe fields are limited)
99
+ await StripePriceService.updatePrice({
100
+ id: price.id,
101
+ nickname: values.nickname || undefined,
102
+ description: values.description || undefined,
103
+ features: values.features.filter((f) => f.trim()) || undefined,
104
+ });
105
+ } else {
106
+ // Create new price
107
+ const createInput: any = {
108
+ id: v4(),
109
+ productId: productId,
110
+ currency: values.currency,
111
+ unitAmount: unitAmountInCents,
112
+ };
113
+
114
+ // Add recurring details if interval is not one_time
115
+ if (isRecurring) {
116
+ createInput.recurring = {
117
+ interval: values.interval as "day" | "week" | "month" | "year",
118
+ intervalCount: values.intervalCount || 1,
119
+ usageType: values.usageType || "licensed",
120
+ };
121
+ }
122
+
123
+ if (values.nickname) {
124
+ createInput.nickname = values.nickname;
125
+ }
126
+
127
+ if (values.description) {
128
+ createInput.description = values.description;
129
+ }
130
+
131
+ const filteredFeatures = values.features.filter((f) => f.trim());
132
+ if (filteredFeatures.length > 0) {
133
+ createInput.features = filteredFeatures;
134
+ }
135
+
136
+ await StripePriceService.createPrice(createInput);
137
+ }
138
+
139
+ onSuccess();
140
+ onOpenChange(false);
141
+ } catch (error) {
142
+ console.error("[PriceEditor] Failed to save price:", error);
143
+ } finally {
144
+ setIsSubmitting(false);
145
+ }
146
+ };
147
+
148
+ const currencyOptions = [
149
+ { id: "usd", text: "USD ($)" },
150
+ { id: "eur", text: "EUR (€)" },
151
+ { id: "gbp", text: "GBP (£)" },
152
+ ];
153
+
154
+ const intervalOptions = [
155
+ { id: "one_time", text: "One-time" },
156
+ { id: "day", text: "Daily" },
157
+ { id: "week", text: "Weekly" },
158
+ { id: "month", text: "Monthly" },
159
+ { id: "year", text: "Yearly" },
160
+ ];
161
+
162
+ const usageTypeOptions = [
163
+ { id: "licensed", text: "Licensed (per unit)" },
164
+ { id: "metered", text: "Metered (usage-based)" },
165
+ ];
166
+
167
+ return (
168
+ <Dialog open={open} onOpenChange={onOpenChange}>
169
+ <DialogContent className="max-w-2xl">
170
+ <DialogHeader>
171
+ <DialogTitle>{isEditMode ? "Edit Price" : "Create Price"}</DialogTitle>
172
+ <DialogDescription>
173
+ {isEditMode
174
+ ? "Update the price details. Note: Only nickname and active status can be changed."
175
+ : "Create a new price for this product"}
176
+ </DialogDescription>
177
+ </DialogHeader>
178
+
179
+ {isEditMode && (
180
+ <div className="bg-blue-50 border border-blue-200 rounded-lg p-4 flex gap-x-3">
181
+ <AlertCircle className="h-5 w-5 text-blue-600 flex-shrink-0 mt-0.5" />
182
+ <div className="text-sm text-blue-800">
183
+ <p className="font-semibold mb-1">Stripe Price Immutability</p>
184
+ <p>
185
+ Due to Stripe's architecture, only the nickname and active status can be modified after creation. To
186
+ change amount, currency, or billing interval, create a new price.
187
+ </p>
188
+ </div>
189
+ </div>
190
+ )}
191
+
192
+ <Form {...form}>
193
+ <form onSubmit={form.handleSubmit(onSubmit)} className="flex flex-col gap-y-4">
194
+ <div className="grid grid-cols-2 gap-x-4">
195
+ <FormInput
196
+ form={form}
197
+ id="unitAmount"
198
+ name="Amount (in dollars)"
199
+ placeholder="9.99"
200
+ disabled={isEditMode}
201
+ isRequired
202
+ />
203
+
204
+ <FormSelect form={form} id="currency" name="Currency" values={currencyOptions} disabled={isEditMode} />
205
+ </div>
206
+
207
+ <FormSelect
208
+ form={form}
209
+ id="interval"
210
+ name="Billing Interval"
211
+ values={intervalOptions}
212
+ disabled={isEditMode}
213
+ />
214
+
215
+ {isRecurring && (
216
+ <div className="grid grid-cols-2 gap-x-4">
217
+ <FormInput
218
+ form={form}
219
+ id="intervalCount"
220
+ name="Interval Count"
221
+ placeholder="1"
222
+ type="number"
223
+ disabled={isEditMode}
224
+ />
225
+
226
+ <FormSelect
227
+ form={form}
228
+ id="usageType"
229
+ name="Usage Type"
230
+ values={usageTypeOptions}
231
+ disabled={isEditMode}
232
+ />
233
+ </div>
234
+ )}
235
+
236
+ <FormInput
237
+ form={form}
238
+ id="nickname"
239
+ name="Nickname (optional)"
240
+ placeholder="e.g., Standard Plan, Pro Tier"
241
+ />
242
+
243
+ <FormTextarea
244
+ form={form}
245
+ id="description"
246
+ name="Description (optional)"
247
+ placeholder="Describe what this price tier includes..."
248
+ className="min-h-24"
249
+ />
250
+
251
+ {/* Features List */}
252
+ <FormItem>
253
+ <FormLabel>Features (optional)</FormLabel>
254
+ <div className="space-y-2">
255
+ {form.watch("features").map((_, index) => (
256
+ <div key={index} className="flex gap-2">
257
+ <FormControl>
258
+ <Input
259
+ {...form.register(`features.${index}`)}
260
+ placeholder={`Feature ${index + 1}`}
261
+ className="flex-1"
262
+ />
263
+ </FormControl>
264
+ <Button
265
+ type="button"
266
+ variant="outline"
267
+ size="icon"
268
+ onClick={() => {
269
+ const currentFeatures = form.getValues("features");
270
+ form.setValue(
271
+ "features",
272
+ currentFeatures.filter((_, i) => i !== index),
273
+ );
274
+ }}
275
+ >
276
+ <XIcon className="h-4 w-4" />
277
+ </Button>
278
+ </div>
279
+ ))}
280
+ <Button
281
+ type="button"
282
+ variant="outline"
283
+ size="sm"
284
+ onClick={() => {
285
+ const currentFeatures = form.getValues("features");
286
+ form.setValue("features", [...currentFeatures, ""]);
287
+ }}
288
+ className="mt-2"
289
+ >
290
+ <PlusIcon className="h-4 w-4 mr-2" />
291
+ Add Feature
292
+ </Button>
293
+ </div>
294
+ </FormItem>
295
+
296
+ <FormCheckbox form={form} id="active" name="Active" />
297
+
298
+ <CommonEditorButtons isEdit={isEditMode} form={form} disabled={isSubmitting} setOpen={onOpenChange} />
299
+ </form>
300
+ </Form>
301
+ </DialogContent>
302
+ </Dialog>
303
+ );
304
+ }
@@ -0,0 +1 @@
1
+ export * from "./PriceEditor";
@@ -0,0 +1,2 @@
1
+ export * from "./forms";
2
+ export * from "./lists";
@@ -0,0 +1,283 @@
1
+ "use client";
2
+
3
+ import { Archive, DollarSign, Edit, RotateCcw } from "lucide-react";
4
+ import { useEffect, useState } from "react";
5
+ import {
6
+ AlertDialog,
7
+ AlertDialogAction,
8
+ AlertDialogCancel,
9
+ AlertDialogContent,
10
+ AlertDialogDescription,
11
+ AlertDialogFooter,
12
+ AlertDialogHeader,
13
+ AlertDialogTitle,
14
+ Button,
15
+ } from "../../../../../shadcnui";
16
+ import { formatCurrency } from "../../../components/utils/currency";
17
+ import { StripePriceService } from "../../data";
18
+ import { StripePriceInterface } from "../../data/stripe-price.interface";
19
+ import { PriceEditor } from "../forms/PriceEditor";
20
+
21
+ type PricesListProps = {
22
+ productId: string;
23
+ onPricesChange: () => void;
24
+ };
25
+
26
+ export function PricesList({ productId, onPricesChange }: PricesListProps) {
27
+ const [prices, setPrices] = useState<StripePriceInterface[]>([]);
28
+ const [loading, setLoading] = useState<boolean>(true);
29
+ const [showCreatePrice, setShowCreatePrice] = useState<boolean>(false);
30
+ const [editingPrice, setEditingPrice] = useState<StripePriceInterface | null>(null);
31
+ const [priceToArchive, setPriceToArchive] = useState<StripePriceInterface | null>(null);
32
+ const [priceToReactivate, setPriceToReactivate] = useState<StripePriceInterface | null>(null);
33
+ const [archivingPriceId, setArchivingPriceId] = useState<string | null>(null);
34
+ const [reactivatingPriceId, setReactivatingPriceId] = useState<string | null>(null);
35
+
36
+ const loadPrices = async () => {
37
+ setLoading(true);
38
+ try {
39
+ const fetchedPrices = await StripePriceService.listPrices({ productId });
40
+ setPrices(fetchedPrices);
41
+ } catch (error) {
42
+ console.error("[PricesList] Failed to load prices:", error);
43
+ } finally {
44
+ setLoading(false);
45
+ }
46
+ };
47
+
48
+ useEffect(() => {
49
+ loadPrices();
50
+ }, [productId]);
51
+
52
+ const handleArchive = async () => {
53
+ if (!priceToArchive) {
54
+ return;
55
+ }
56
+
57
+ setArchivingPriceId(priceToArchive.id);
58
+ try {
59
+ await StripePriceService.archivePrice({ id: priceToArchive.id });
60
+ setPriceToArchive(null); // Close dialog on success
61
+ await loadPrices();
62
+ onPricesChange();
63
+ } catch (error) {
64
+ console.error("[PricesList] Failed to archive price:", error);
65
+ // Keep dialog open on error so user can retry or cancel
66
+ } finally {
67
+ setArchivingPriceId(null);
68
+ }
69
+ };
70
+
71
+ const handleReactivate = async () => {
72
+ if (!priceToReactivate) {
73
+ return;
74
+ }
75
+
76
+ setReactivatingPriceId(priceToReactivate.id);
77
+ try {
78
+ await StripePriceService.reactivatePrice({ id: priceToReactivate.id });
79
+ setPriceToReactivate(null); // Close dialog on success
80
+ await loadPrices();
81
+ onPricesChange();
82
+ } catch (error) {
83
+ console.error("[PricesList] Failed to reactivate price:", error);
84
+ // Keep dialog open on error so user can retry or cancel
85
+ } finally {
86
+ setReactivatingPriceId(null);
87
+ }
88
+ };
89
+
90
+ const formatInterval = (price: StripePriceInterface): string => {
91
+ if (price.priceType === "one_time") {
92
+ return "one-time";
93
+ }
94
+
95
+ if (price.recurring) {
96
+ const count = price.recurring.intervalCount;
97
+ const interval = price.recurring.interval;
98
+
99
+ if (count === 1) {
100
+ return `/ ${interval}`;
101
+ } else {
102
+ return `/ ${count} ${interval}s`;
103
+ }
104
+ }
105
+
106
+ return "";
107
+ };
108
+
109
+ if (loading) {
110
+ return (
111
+ <div className="flex items-center justify-center py-8">
112
+ <p className="text-muted-foreground">Loading prices...</p>
113
+ </div>
114
+ );
115
+ }
116
+
117
+ return (
118
+ <div className="flex flex-col gap-y-4">
119
+ <div className="flex items-center justify-between mb-4">
120
+ <h4 className="text-lg font-semibold">Prices</h4>
121
+ <Button size="sm" onClick={() => setShowCreatePrice(true)}>
122
+ Add Price
123
+ </Button>
124
+ </div>
125
+
126
+ {/* Empty State */}
127
+ {prices.length === 0 && (
128
+ <div className="bg-background flex flex-col items-center justify-center gap-y-3 rounded-lg border border-dashed p-8">
129
+ <DollarSign className="text-muted-foreground h-12 w-12" />
130
+ <p className="text-muted-foreground text-sm">No prices yet. Add a price to enable subscriptions.</p>
131
+ <Button size="sm" onClick={() => setShowCreatePrice(true)}>
132
+ Add Price
133
+ </Button>
134
+ </div>
135
+ )}
136
+
137
+ {/* Prices Grid */}
138
+ {prices.length > 0 && (
139
+ <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
140
+ {prices.map((price) => {
141
+ const isArchiving = archivingPriceId === price.id;
142
+ const isReactivating = reactivatingPriceId === price.id;
143
+
144
+ return (
145
+ <div key={price.id} className="border rounded-lg bg-white p-4 hover:shadow-sm transition-shadow">
146
+ <div className="flex items-start justify-between mb-3">
147
+ <DollarSign className="h-5 w-5 text-primary" />
148
+ <div className="flex gap-1">
149
+ <Button variant="ghost" size="sm" onClick={() => setEditingPrice(price)} className="h-8 w-8 p-0">
150
+ <Edit className="h-4 w-4" />
151
+ </Button>
152
+ {price.active ? (
153
+ <Button
154
+ variant="ghost"
155
+ size="sm"
156
+ onClick={() => setPriceToArchive(price)}
157
+ className="h-8 w-8 p-0"
158
+ disabled={isArchiving}
159
+ >
160
+ <Archive className="h-4 w-4" />
161
+ </Button>
162
+ ) : (
163
+ <Button
164
+ variant="ghost"
165
+ size="sm"
166
+ onClick={() => setPriceToReactivate(price)}
167
+ className="h-8 w-8 p-0"
168
+ disabled={isReactivating}
169
+ >
170
+ <RotateCcw className="h-4 w-4" />
171
+ </Button>
172
+ )}
173
+ </div>
174
+ </div>
175
+
176
+ <div className="mb-2">
177
+ <div className="text-2xl font-bold">
178
+ {formatCurrency(price.unitAmount, price.currency)}{" "}
179
+ <span className="text-muted-foreground text-sm font-normal">{formatInterval(price)}</span>
180
+ </div>
181
+ </div>
182
+
183
+ {price.metadata?.nickname && <p className="text-sm font-medium mb-2">{price.metadata.nickname}</p>}
184
+
185
+ <div className="flex flex-wrap gap-2">
186
+ {price.active ? (
187
+ <span className="bg-green-100 text-green-800 text-xs px-2 py-1 rounded-full font-medium">Active</span>
188
+ ) : (
189
+ <span className="bg-gray-100 text-gray-800 text-xs px-2 py-1 rounded-full font-medium">Inactive</span>
190
+ )}
191
+
192
+ {price.recurring?.usageType === "metered" && (
193
+ <span className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full font-medium">Metered</span>
194
+ )}
195
+
196
+ <span className="bg-gray-100 text-gray-800 text-xs px-2 py-1 rounded-full font-medium uppercase">
197
+ {price.currency}
198
+ </span>
199
+ </div>
200
+ </div>
201
+ );
202
+ })}
203
+ </div>
204
+ )}
205
+
206
+ {/* Create Price Modal */}
207
+ {showCreatePrice && (
208
+ <PriceEditor
209
+ productId={productId}
210
+ open={showCreatePrice}
211
+ onOpenChange={setShowCreatePrice}
212
+ onSuccess={() => {
213
+ loadPrices();
214
+ onPricesChange();
215
+ }}
216
+ />
217
+ )}
218
+
219
+ {/* Edit Price Modal */}
220
+ {editingPrice && (
221
+ <PriceEditor
222
+ productId={productId}
223
+ price={editingPrice}
224
+ open={!!editingPrice}
225
+ onOpenChange={(open) => !open && setEditingPrice(null)}
226
+ onSuccess={() => {
227
+ loadPrices();
228
+ onPricesChange();
229
+ setEditingPrice(null);
230
+ }}
231
+ />
232
+ )}
233
+
234
+ {/* Archive Price Confirmation Dialog */}
235
+ <AlertDialog open={!!priceToArchive} onOpenChange={(open) => !open && setPriceToArchive(null)}>
236
+ <AlertDialogContent>
237
+ <AlertDialogHeader>
238
+ <AlertDialogTitle>Archive Price</AlertDialogTitle>
239
+ <AlertDialogDescription>
240
+ Are you sure you want to archive the price for{" "}
241
+ {priceToArchive && `${formatCurrency(priceToArchive.unitAmount, priceToArchive.currency)} ${formatInterval(priceToArchive)}`}
242
+ ? This will prevent new subscriptions but existing ones will continue.
243
+ </AlertDialogDescription>
244
+ </AlertDialogHeader>
245
+ <AlertDialogFooter>
246
+ <AlertDialogCancel disabled={!!archivingPriceId}>Cancel</AlertDialogCancel>
247
+ <AlertDialogAction
248
+ onClick={handleArchive}
249
+ disabled={!!archivingPriceId}
250
+ className="bg-red-600 hover:bg-red-700"
251
+ >
252
+ {archivingPriceId ? "Archiving..." : "Archive"}
253
+ </AlertDialogAction>
254
+ </AlertDialogFooter>
255
+ </AlertDialogContent>
256
+ </AlertDialog>
257
+
258
+ {/* Reactivate Price Confirmation Dialog */}
259
+ <AlertDialog open={!!priceToReactivate} onOpenChange={(open) => !open && setPriceToReactivate(null)}>
260
+ <AlertDialogContent>
261
+ <AlertDialogHeader>
262
+ <AlertDialogTitle>Reactivate Price</AlertDialogTitle>
263
+ <AlertDialogDescription>
264
+ Are you sure you want to reactivate the price for{" "}
265
+ {priceToReactivate && `${formatCurrency(priceToReactivate.unitAmount, priceToReactivate.currency)} ${formatInterval(priceToReactivate)}`}
266
+ ? This will allow new subscriptions again.
267
+ </AlertDialogDescription>
268
+ </AlertDialogHeader>
269
+ <AlertDialogFooter>
270
+ <AlertDialogCancel disabled={!!reactivatingPriceId}>Cancel</AlertDialogCancel>
271
+ <AlertDialogAction
272
+ onClick={handleReactivate}
273
+ disabled={!!reactivatingPriceId}
274
+ className="bg-green-600 hover:bg-green-700"
275
+ >
276
+ {reactivatingPriceId ? "Reactivating..." : "Reactivate"}
277
+ </AlertDialogAction>
278
+ </AlertDialogFooter>
279
+ </AlertDialogContent>
280
+ </AlertDialog>
281
+ </div>
282
+ );
283
+ }
@@ -0,0 +1 @@
1
+ export * from "./PricesList";
@@ -0,0 +1,3 @@
1
+ export * from "./stripe-price";
2
+ export * from "./stripe-price.interface";
3
+ export * from "./stripe-price.service";
@@ -0,0 +1,48 @@
1
+ import { ApiDataInterface } from "../../../../core";
2
+ import { StripeProductInterface } from "../../stripe-product";
3
+
4
+ // ============================================================================
5
+ // Stripe Price Interfaces
6
+ // ============================================================================
7
+
8
+ export interface StripePriceInterface extends ApiDataInterface {
9
+ get stripePriceId(): string;
10
+ get productId(): string;
11
+ get product(): StripeProductInterface | undefined;
12
+ get active(): boolean;
13
+ get currency(): string;
14
+ get unitAmount(): number | undefined;
15
+ get recurring(): PriceRecurring | undefined;
16
+ get priceType(): "one_time" | "recurring";
17
+ get nickname(): string | undefined;
18
+ get lookupKey(): string | undefined;
19
+ get metadata(): Record<string, any> | undefined;
20
+ get description(): string | undefined;
21
+ get features(): string[] | undefined;
22
+ }
23
+
24
+ export interface PriceRecurring {
25
+ interval: "day" | "week" | "month" | "year";
26
+ intervalCount: number;
27
+ usageType?: "metered" | "licensed";
28
+ }
29
+
30
+ // ============================================================================
31
+ // Stripe Price Input DTOs
32
+ // ============================================================================
33
+
34
+ export type StripePriceInput = {
35
+ id: string;
36
+ productId?: string; // Required for create, not for update
37
+ currency?: string; // Required for create, not for update
38
+ unitAmount?: number;
39
+ nickname?: string;
40
+ recurring?: {
41
+ interval: "day" | "week" | "month" | "year";
42
+ intervalCount?: number;
43
+ usageType?: "metered" | "licensed";
44
+ };
45
+ metadata?: Record<string, any>;
46
+ description?: string;
47
+ features?: string[];
48
+ };