@spaceinvoices/react-ui 0.1.1

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 (352) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +340 -0
  3. package/cli/dist/index.js +922 -0
  4. package/package.json +87 -0
  5. package/registry.json +600 -0
  6. package/spaceinvoices.schema.json +47 -0
  7. package/src/app.tsx +25 -0
  8. package/src/common/autocomplete.tsx +135 -0
  9. package/src/components/activities/activity-timeline.tsx +160 -0
  10. package/src/components/activities/index.ts +1 -0
  11. package/src/components/activities/locales/de.ts +30 -0
  12. package/src/components/activities/locales/sl.ts +30 -0
  13. package/src/components/advance-invoices/advance-invoices.hooks.ts +75 -0
  14. package/src/components/advance-invoices/create/create-advance-invoice-form.tsx +702 -0
  15. package/src/components/advance-invoices/create/locales/de.ts +29 -0
  16. package/src/components/advance-invoices/create/locales/sl.ts +25 -0
  17. package/src/components/advance-invoices/create/prepare-advance-invoice-submission.ts +74 -0
  18. package/src/components/advance-invoices/index.ts +5 -0
  19. package/src/components/advance-invoices/list/index.ts +3 -0
  20. package/src/components/advance-invoices/list/list-row-actions.tsx +119 -0
  21. package/src/components/advance-invoices/list/list-table.tsx +178 -0
  22. package/src/components/advance-invoices/list/locales/de.ts +32 -0
  23. package/src/components/advance-invoices/list/locales/sl.ts +32 -0
  24. package/src/components/advance-invoices/list/use-advance-invoice-download.ts +63 -0
  25. package/src/components/button-loader.tsx +11 -0
  26. package/src/components/combobox.tsx +96 -0
  27. package/src/components/company-registry/company-registry-autocomplete.tsx +151 -0
  28. package/src/components/company-registry/company-registry.hooks.ts +67 -0
  29. package/src/components/company-registry/index.ts +7 -0
  30. package/src/components/credit-notes/create/create-credit-note-form.tsx +332 -0
  31. package/src/components/credit-notes/create/index.ts +1 -0
  32. package/src/components/credit-notes/create/locales/de.ts +69 -0
  33. package/src/components/credit-notes/create/locales/sl.ts +67 -0
  34. package/src/components/credit-notes/credit-notes.hooks.ts +22 -0
  35. package/src/components/credit-notes/index.ts +10 -0
  36. package/src/components/credit-notes/list/index.ts +3 -0
  37. package/src/components/credit-notes/list/list-row-actions.tsx +116 -0
  38. package/src/components/credit-notes/list/list-table.tsx +183 -0
  39. package/src/components/credit-notes/list/locales/de.ts +33 -0
  40. package/src/components/credit-notes/list/locales/sl.ts +33 -0
  41. package/src/components/credit-notes/list/use-credit-note-download.ts +65 -0
  42. package/src/components/customers/create-customer-form/create-customer-form.tsx +134 -0
  43. package/src/components/customers/create-customer-form/locales/de.ts +20 -0
  44. package/src/components/customers/create-customer-form/locales/sl.ts +20 -0
  45. package/src/components/customers/customer-autocomplete.tsx +173 -0
  46. package/src/components/customers/customer-combobox.tsx +130 -0
  47. package/src/components/customers/customer-list-table/customer-list-row-actions.tsx +48 -0
  48. package/src/components/customers/customer-list-table/customer-list-table.tsx +124 -0
  49. package/src/components/customers/customer-list-table/index.ts +2 -0
  50. package/src/components/customers/customer-list-table/locales/de.ts +16 -0
  51. package/src/components/customers/customer-list-table/locales/sl.ts +16 -0
  52. package/src/components/customers/customers.hooks.test.ts +348 -0
  53. package/src/components/customers/customers.hooks.ts +57 -0
  54. package/src/components/customers/index.ts +5 -0
  55. package/src/components/dashboard/chart-empty-state.tsx +29 -0
  56. package/src/components/dashboard/collection-rate-card/collection-rate-card.tsx +80 -0
  57. package/src/components/dashboard/collection-rate-card/index.ts +4 -0
  58. package/src/components/dashboard/collection-rate-card/locales/sl.ts +3 -0
  59. package/src/components/dashboard/collection-rate-card/use-collection-rate.ts +74 -0
  60. package/src/components/dashboard/index.ts +54 -0
  61. package/src/components/dashboard/invoice-status-chart/index.ts +4 -0
  62. package/src/components/dashboard/invoice-status-chart/invoice-status-chart.tsx +130 -0
  63. package/src/components/dashboard/invoice-status-chart/locales/sl.ts +9 -0
  64. package/src/components/dashboard/invoice-status-chart/use-invoice-status.ts +105 -0
  65. package/src/components/dashboard/loading-card.tsx +19 -0
  66. package/src/components/dashboard/payment-methods-chart/index.ts +4 -0
  67. package/src/components/dashboard/payment-methods-chart/locales/sl.ts +12 -0
  68. package/src/components/dashboard/payment-methods-chart/payment-methods-chart.tsx +152 -0
  69. package/src/components/dashboard/payment-methods-chart/use-payment-methods.ts +50 -0
  70. package/src/components/dashboard/payment-trend-chart/index.ts +4 -0
  71. package/src/components/dashboard/payment-trend-chart/locales/sl.ts +5 -0
  72. package/src/components/dashboard/payment-trend-chart/payment-trend-chart.tsx +137 -0
  73. package/src/components/dashboard/payment-trend-chart/use-payment-trend.ts +92 -0
  74. package/src/components/dashboard/revenue-card.tsx +49 -0
  75. package/src/components/dashboard/revenue-trend-chart/index.ts +4 -0
  76. package/src/components/dashboard/revenue-trend-chart/locales/sl.ts +5 -0
  77. package/src/components/dashboard/revenue-trend-chart/revenue-trend-chart.tsx +137 -0
  78. package/src/components/dashboard/revenue-trend-chart/use-revenue-trend.ts +93 -0
  79. package/src/components/dashboard/shared/index.ts +5 -0
  80. package/src/components/dashboard/shared/use-revenue-data.ts +160 -0
  81. package/src/components/dashboard/shared/use-stats-counts.ts +89 -0
  82. package/src/components/dashboard/shared/use-stats-query.ts +38 -0
  83. package/src/components/dashboard/stat-card.tsx +41 -0
  84. package/src/components/dashboard/tax-collected-card/index.ts +2 -0
  85. package/src/components/dashboard/tax-collected-card/tax-collected-card.tsx +77 -0
  86. package/src/components/dashboard/tax-collected-card/use-tax-collected.ts +145 -0
  87. package/src/components/dashboard/top-customers-chart/index.ts +4 -0
  88. package/src/components/dashboard/top-customers-chart/locales/sl.ts +5 -0
  89. package/src/components/dashboard/top-customers-chart/top-customers-chart.tsx +130 -0
  90. package/src/components/dashboard/top-customers-chart/use-top-customers.ts +72 -0
  91. package/src/components/documents/create/document-add-item-form.tsx +379 -0
  92. package/src/components/documents/create/document-add-item-tax-rate-field.tsx +120 -0
  93. package/src/components/documents/create/document-details-section.tsx +597 -0
  94. package/src/components/documents/create/document-items-section.tsx +133 -0
  95. package/src/components/documents/create/document-recipient-section.tsx +101 -0
  96. package/src/components/documents/create/form-types.ts +36 -0
  97. package/src/components/documents/create/index.ts +9 -0
  98. package/src/components/documents/create/live-preview.tsx +235 -0
  99. package/src/components/documents/create/mark-as-paid-section.tsx +82 -0
  100. package/src/components/documents/create/prepare-document-submission.test.ts +132 -0
  101. package/src/components/documents/create/prepare-document-submission.ts +187 -0
  102. package/src/components/documents/create/prepare-preview-data.test.ts +155 -0
  103. package/src/components/documents/create/prepare-preview-data.ts +16 -0
  104. package/src/components/documents/create/smart-code-insert-button.tsx +139 -0
  105. package/src/components/documents/create/use-document-customer-form.ts +161 -0
  106. package/src/components/documents/document-preview.tsx +13 -0
  107. package/src/components/documents/documents.hooks.ts +146 -0
  108. package/src/components/documents/index.ts +23 -0
  109. package/src/components/documents/shared/document-preview-display.tsx +172 -0
  110. package/src/components/documents/shared/index.ts +3 -0
  111. package/src/components/documents/shared/scaled-document-preview.tsx +70 -0
  112. package/src/components/documents/shared/use-a4-scaling.ts +62 -0
  113. package/src/components/documents/types.ts +61 -0
  114. package/src/components/documents/view/document-actions-bar.tsx +328 -0
  115. package/src/components/documents/view/document-details-card.tsx +179 -0
  116. package/src/components/documents/view/document-payments-list.tsx +256 -0
  117. package/src/components/documents/view/index.ts +4 -0
  118. package/src/components/documents/view/locales/de.ts +85 -0
  119. package/src/components/documents/view/locales/sl.ts +84 -0
  120. package/src/components/documents/view/use-document-download.ts +125 -0
  121. package/src/components/entities/create-entity-form.tsx +105 -0
  122. package/src/components/entities/entities.hooks.ts +50 -0
  123. package/src/components/entities/entity-settings-form/email-template-variables-info.tsx +103 -0
  124. package/src/components/entities/entity-settings-form/entity-settings-form.tsx +1326 -0
  125. package/src/components/entities/entity-settings-form/image-upload-with-crop.tsx +222 -0
  126. package/src/components/entities/entity-settings-form/index.ts +2 -0
  127. package/src/components/entities/entity-settings-form/input-with-preview.tsx +190 -0
  128. package/src/components/entities/entity-settings-form/locales/de.ts +192 -0
  129. package/src/components/entities/entity-settings-form/locales/sl.ts +188 -0
  130. package/src/components/entities/furs-settings-form/furs-settings-form.tsx +410 -0
  131. package/src/components/entities/furs-settings-form/furs-settings.hooks.ts +320 -0
  132. package/src/components/entities/furs-settings-form/index.ts +3 -0
  133. package/src/components/entities/furs-settings-form/locales/de.ts +233 -0
  134. package/src/components/entities/furs-settings-form/locales/en.ts +194 -0
  135. package/src/components/entities/furs-settings-form/locales/sl.ts +196 -0
  136. package/src/components/entities/furs-settings-form/sections/certificate-settings-section.tsx +242 -0
  137. package/src/components/entities/furs-settings-form/sections/enable-fiscalization-section.tsx +139 -0
  138. package/src/components/entities/furs-settings-form/sections/general-settings-section.tsx +252 -0
  139. package/src/components/entities/furs-settings-form/sections/premises-management-section.tsx +370 -0
  140. package/src/components/entities/furs-settings-form/sections/register-premise-dialog.tsx +420 -0
  141. package/src/components/entities/keys.ts +2 -0
  142. package/src/components/entities/settings/branding-settings-form.tsx +274 -0
  143. package/src/components/entities/settings/company-settings-form.tsx +256 -0
  144. package/src/components/entities/settings/defaults-settings-form.tsx +501 -0
  145. package/src/components/entities/settings/email-settings-form.tsx +288 -0
  146. package/src/components/entities/settings/eslog-settings-form.tsx +113 -0
  147. package/src/components/entities/settings/index.ts +8 -0
  148. package/src/components/entities/settings/number-format-settings-form.tsx +244 -0
  149. package/src/components/entities/settings/pdf-template-selector/demo-invoice-data.ts +164 -0
  150. package/src/components/entities/settings/pdf-template-selector/index.ts +2 -0
  151. package/src/components/entities/settings/pdf-template-selector/locales/de.ts +18 -0
  152. package/src/components/entities/settings/pdf-template-selector/locales/sl.ts +18 -0
  153. package/src/components/entities/settings/pdf-template-selector/pdf-template-cards.tsx +49 -0
  154. package/src/components/entities/settings/settings-footer.tsx +16 -0
  155. package/src/components/entities/settings/tax-rules-settings-form.tsx +346 -0
  156. package/src/components/estimates/create/create-estimate-form.tsx +384 -0
  157. package/src/components/estimates/create/locales/de.ts +64 -0
  158. package/src/components/estimates/create/locales/sl.ts +63 -0
  159. package/src/components/estimates/create/prepare-estimate-submission.ts +39 -0
  160. package/src/components/estimates/create/use-estimate-customer-form.ts +5 -0
  161. package/src/components/estimates/estimates.hooks.ts +15 -0
  162. package/src/components/estimates/index.ts +6 -0
  163. package/src/components/estimates/list/index.ts +3 -0
  164. package/src/components/estimates/list/list-row-actions.tsx +103 -0
  165. package/src/components/estimates/list/list-table.tsx +171 -0
  166. package/src/components/estimates/list/locales/de.ts +26 -0
  167. package/src/components/estimates/list/locales/sl.ts +26 -0
  168. package/src/components/estimates/list/use-estimate-download.ts +63 -0
  169. package/src/components/export/document-export-form.tsx +288 -0
  170. package/src/components/export/index.ts +2 -0
  171. package/src/components/form/form-input.tsx +89 -0
  172. package/src/components/form/index.ts +1 -0
  173. package/src/components/invoices/create/create-invoice-form.tsx +852 -0
  174. package/src/components/invoices/create/eslog-validation.test.ts +242 -0
  175. package/src/components/invoices/create/eslog-validation.ts +208 -0
  176. package/src/components/invoices/create/locales/de.ts +118 -0
  177. package/src/components/invoices/create/locales/sl.ts +114 -0
  178. package/src/components/invoices/create/prepare-invoice-submission.test.ts +777 -0
  179. package/src/components/invoices/create/prepare-invoice-submission.ts +79 -0
  180. package/src/components/invoices/create/use-invoice-customer-form.ts +5 -0
  181. package/src/components/invoices/index.ts +9 -0
  182. package/src/components/invoices/invoices-furs.hooks.ts +28 -0
  183. package/src/components/invoices/invoices.hooks.ts +110 -0
  184. package/src/components/invoices/list/index.ts +3 -0
  185. package/src/components/invoices/list/list-row-actions.tsx +132 -0
  186. package/src/components/invoices/list/list-table.tsx +165 -0
  187. package/src/components/invoices/list/locales/de.ts +33 -0
  188. package/src/components/invoices/list/locales/sl.ts +33 -0
  189. package/src/components/invoices/list/use-invoice-download.ts +62 -0
  190. package/src/components/invoices/send-email-dialog/index.ts +1 -0
  191. package/src/components/invoices/send-email-dialog/locales/de.ts +18 -0
  192. package/src/components/invoices/send-email-dialog/locales/sl.ts +17 -0
  193. package/src/components/invoices/send-email-dialog/send-email-dialog.tsx +289 -0
  194. package/src/components/invoices/send-email-dialog.tsx +2 -0
  195. package/src/components/invoices/shared/index.ts +2 -0
  196. package/src/components/invoices/shared/scaled-document-preview.tsx +32 -0
  197. package/src/components/invoices/shared/use-a4-scaling.tsx +39 -0
  198. package/src/components/invoices/view/eslog-info-display.tsx +160 -0
  199. package/src/components/invoices/view/furs-info-display.tsx +213 -0
  200. package/src/components/items/create-item-form/create-item-form.tsx +155 -0
  201. package/src/components/items/create-item-form/locales/de.ts +14 -0
  202. package/src/components/items/create-item-form/locales/en.ts +9 -0
  203. package/src/components/items/create-item-form/locales/sl.ts +14 -0
  204. package/src/components/items/item-combobox.tsx +147 -0
  205. package/src/components/items/item-list-table/item-list-header.tsx +33 -0
  206. package/src/components/items/item-list-table/item-list-row-actions.tsx +48 -0
  207. package/src/components/items/item-list-table/item-list-row.tsx +32 -0
  208. package/src/components/items/item-list-table/item-list-table.tsx +76 -0
  209. package/src/components/items/item-list-table/locales/de.ts +10 -0
  210. package/src/components/items/item-list-table/locales/en.ts +10 -0
  211. package/src/components/items/item-list-table/locales/sl.ts +10 -0
  212. package/src/components/items/items.hooks.ts +63 -0
  213. package/src/components/loading-spinner.tsx +24 -0
  214. package/src/components/payments/create-payment-form/create-payment-form.tsx +222 -0
  215. package/src/components/payments/create-payment-form/locales/de.ts +20 -0
  216. package/src/components/payments/create-payment-form/locales/sl.ts +20 -0
  217. package/src/components/payments/edit-payment-form/edit-payment-form.tsx +230 -0
  218. package/src/components/payments/edit-payment-form/index.ts +1 -0
  219. package/src/components/payments/edit-payment-form/locales/de.ts +20 -0
  220. package/src/components/payments/edit-payment-form/locales/sl.ts +20 -0
  221. package/src/components/payments/index.ts +4 -0
  222. package/src/components/payments/list/index.ts +2 -0
  223. package/src/components/payments/list/list-row-actions.tsx +98 -0
  224. package/src/components/payments/list/list-table.tsx +186 -0
  225. package/src/components/payments/list/locales/de.ts +19 -0
  226. package/src/components/payments/list/locales/sl.ts +19 -0
  227. package/src/components/payments/payments.hooks.ts +15 -0
  228. package/src/components/request-logs/index.ts +3 -0
  229. package/src/components/request-logs/request-log-detail.tsx +242 -0
  230. package/src/components/request-logs/request-log-list-table.tsx +266 -0
  231. package/src/components/request-logs/request-logs-page.tsx +10 -0
  232. package/src/components/table/README.md +410 -0
  233. package/src/components/table/data-table.tsx +251 -0
  234. package/src/components/table/date-cell.tsx +35 -0
  235. package/src/components/table/filter-bar.tsx +114 -0
  236. package/src/components/table/filter-panel.tsx +407 -0
  237. package/src/components/table/hooks/use-table-fetch.ts +17 -0
  238. package/src/components/table/hooks/use-table-query.ts +36 -0
  239. package/src/components/table/hooks/use-table-state.ts +293 -0
  240. package/src/components/table/index.ts +35 -0
  241. package/src/components/table/search-input.tsx +85 -0
  242. package/src/components/table/sortable-header.tsx +56 -0
  243. package/src/components/table/table-empty-state.tsx +40 -0
  244. package/src/components/table/table-no-results.tsx +41 -0
  245. package/src/components/table/table-pagination.tsx +42 -0
  246. package/src/components/table/table-skeleton.tsx +54 -0
  247. package/src/components/table/types.ts +136 -0
  248. package/src/components/tax-reports/index.ts +1 -0
  249. package/src/components/tax-reports/kir-export-form.tsx +172 -0
  250. package/src/components/taxes/create-tax-form/create-tax-form.tsx +112 -0
  251. package/src/components/taxes/create-tax-form/locales/de.ts +8 -0
  252. package/src/components/taxes/create-tax-form/locales/en.ts +7 -0
  253. package/src/components/taxes/create-tax-form/locales/sl.ts +8 -0
  254. package/src/components/taxes/tax-list-table/locales/de.ts +11 -0
  255. package/src/components/taxes/tax-list-table/locales/en.ts +10 -0
  256. package/src/components/taxes/tax-list-table/locales/sl.ts +11 -0
  257. package/src/components/taxes/tax-list-table/tax-list-header.tsx +29 -0
  258. package/src/components/taxes/tax-list-table/tax-list-row-actions.tsx +43 -0
  259. package/src/components/taxes/tax-list-table/tax-list-row.tsx +46 -0
  260. package/src/components/taxes/tax-list-table/tax-list-table.tsx +59 -0
  261. package/src/components/taxes/taxes.hooks.ts +35 -0
  262. package/src/components/ui/alert-dialog.tsx +61 -0
  263. package/src/components/ui/alert.tsx +72 -0
  264. package/src/components/ui/badge.tsx +48 -0
  265. package/src/components/ui/breadcrumb.tsx +132 -0
  266. package/src/components/ui/button.tsx +61 -0
  267. package/src/components/ui/calendar.tsx +213 -0
  268. package/src/components/ui/card.tsx +94 -0
  269. package/src/components/ui/chart.tsx +380 -0
  270. package/src/components/ui/checkbox.tsx +27 -0
  271. package/src/components/ui/collapsible.tsx +56 -0
  272. package/src/components/ui/command.tsx +187 -0
  273. package/src/components/ui/dialog.tsx +187 -0
  274. package/src/components/ui/drawer.tsx +123 -0
  275. package/src/components/ui/dropdown-menu.tsx +291 -0
  276. package/src/components/ui/form.tsx +166 -0
  277. package/src/components/ui/input-group.tsx +149 -0
  278. package/src/components/ui/input.tsx +20 -0
  279. package/src/components/ui/label.tsx +18 -0
  280. package/src/components/ui/loading-spinner.tsx +16 -0
  281. package/src/components/ui/popover.tsx +108 -0
  282. package/src/components/ui/radio-group.tsx +37 -0
  283. package/src/components/ui/select.tsx +200 -0
  284. package/src/components/ui/separator.tsx +23 -0
  285. package/src/components/ui/sheet.tsx +145 -0
  286. package/src/components/ui/sidebar.tsx +771 -0
  287. package/src/components/ui/skeleton.tsx +13 -0
  288. package/src/components/ui/sonner.tsx +60 -0
  289. package/src/components/ui/spinner.tsx +10 -0
  290. package/src/components/ui/sticky-form-footer.tsx +55 -0
  291. package/src/components/ui/switch.tsx +30 -0
  292. package/src/components/ui/table.tsx +101 -0
  293. package/src/components/ui/tabs.tsx +80 -0
  294. package/src/components/ui/textarea.tsx +18 -0
  295. package/src/components/ui/tooltip.tsx +89 -0
  296. package/src/components/wl-subscription/index.ts +2 -0
  297. package/src/components/wl-subscription/locked-feature.tsx +173 -0
  298. package/src/components/wl-subscription/upgrade-modal.tsx +209 -0
  299. package/src/frontend.tsx +28 -0
  300. package/src/generate-schemas.ts +265 -0
  301. package/src/generated/schemas/advanceinvoice.ts +177 -0
  302. package/src/generated/schemas/creditnote.ts +187 -0
  303. package/src/generated/schemas/customer.ts +29 -0
  304. package/src/generated/schemas/entity.ts +252 -0
  305. package/src/generated/schemas/estimate.ts +159 -0
  306. package/src/generated/schemas/furssettings.ts +25 -0
  307. package/src/generated/schemas/index.ts +24 -0
  308. package/src/generated/schemas/invoice.ts +167 -0
  309. package/src/generated/schemas/item.ts +38 -0
  310. package/src/generated/schemas/payment.ts +44 -0
  311. package/src/generated/schemas/previewadvanceinvoice_body.ts +354 -0
  312. package/src/generated/schemas/previewestimate_body.ts +309 -0
  313. package/src/generated/schemas/registerfursmovablepremise_body.ts +22 -0
  314. package/src/generated/schemas/registerfursrealestatepremise_body.ts +32 -0
  315. package/src/generated/schemas/renderdocument_body.ts +594 -0
  316. package/src/generated/schemas/sendemail_body.ts +26 -0
  317. package/src/generated/schemas/startpdfexport_body.ts +20 -0
  318. package/src/generated/schemas/tax.ts +48 -0
  319. package/src/generated/schemas/uploadfile_body.ts +23 -0
  320. package/src/generated/schemas/uploadfurscertificate_body.ts +20 -0
  321. package/src/generated/schemas/userfurssettings.ts +19 -0
  322. package/src/hooks/create-resource-hooks.test.ts +483 -0
  323. package/src/hooks/create-resource-hooks.ts +300 -0
  324. package/src/hooks/use-debounce.ts +12 -0
  325. package/src/hooks/use-duplicate-document.ts +185 -0
  326. package/src/hooks/use-media-query.tsx +19 -0
  327. package/src/hooks/use-mobile.ts +39 -0
  328. package/src/hooks/use-next-document-number.ts +57 -0
  329. package/src/hooks/use-resource-mutation.ts +118 -0
  330. package/src/hooks/use-vies-check.ts +130 -0
  331. package/src/index.css +11 -0
  332. package/src/index.html +13 -0
  333. package/src/index.tsx +12 -0
  334. package/src/lib/auth.ts +4 -0
  335. package/src/lib/browser-cookies.ts +70 -0
  336. package/src/lib/constants.ts +287 -0
  337. package/src/lib/cookies.ts +36 -0
  338. package/src/lib/schemas/advance-invoice.ts +43 -0
  339. package/src/lib/schemas/credit-note.ts +32 -0
  340. package/src/lib/schemas/estimate.ts +31 -0
  341. package/src/lib/schemas/index.ts +18 -0
  342. package/src/lib/schemas/invoice.ts +43 -0
  343. package/src/lib/schemas/shared.ts +79 -0
  344. package/src/lib/translation.ts +38 -0
  345. package/src/lib/utils.ts +6 -0
  346. package/src/providers/entities-context.tsx +41 -0
  347. package/src/providers/entities-provider.tsx +201 -0
  348. package/src/providers/form-footer-context.tsx +72 -0
  349. package/src/providers/sdk-provider.tsx +164 -0
  350. package/src/providers/white-label-provider.tsx +91 -0
  351. package/src/providers/wl-subscription-provider.tsx +277 -0
  352. package/src/utils/string-helpers.ts +111 -0
@@ -0,0 +1,164 @@
1
+ import SDK from "@spaceinvoices/js-sdk";
2
+ import type { ReactNode } from "react";
3
+ import { createContext, useContext, useEffect, useMemo, useState } from "react";
4
+
5
+ import { AUTH_COOKIES } from "@/ui/lib/auth";
6
+ import { flushCookies, getCookie } from "@/ui/lib/browser-cookies";
7
+
8
+ /**
9
+ * SDK context type definition
10
+ * Note: SDK is guaranteed to be non-null when accessing this context
11
+ * The provider blocks rendering until SDK is initialized
12
+ */
13
+ type SDKContextType = {
14
+ sdk: SDK; // Non-null - guaranteed by provider
15
+ isInitialized: boolean;
16
+ isLoading: boolean;
17
+ error: Error | null;
18
+ reinitialize: () => Promise<void>;
19
+ };
20
+
21
+ /**
22
+ * SDK context with default values
23
+ * Note: These defaults are never actually used because the provider
24
+ * blocks rendering until SDK is initialized
25
+ */
26
+ const SDKContext = createContext<SDKContextType | undefined>(undefined);
27
+
28
+ // Add a name to help with debugging
29
+ SDKContext.displayName = "SDKContext";
30
+
31
+ type SDKProviderProps = {
32
+ children: ReactNode;
33
+ onUnauthorized?: (response: Response) => void;
34
+ fallbackLoading?: ReactNode;
35
+ fallbackError?: (error: Error) => ReactNode;
36
+ fallbackUnauthorized?: ReactNode;
37
+ };
38
+
39
+ /**
40
+ * SDK Provider component
41
+ * Responsible for initializing the SDK and providing it to the application
42
+ */
43
+ export function SDKProvider({
44
+ children,
45
+ onUnauthorized,
46
+ fallbackLoading = <div className="p-4">Initializing SDK...</div>,
47
+ fallbackError = (error) => <div className="p-4 text-red-500">Error initializing SDK: {error.message}</div>,
48
+ fallbackUnauthorized = <div className="p-4 text-amber-500">No SDK available. Please log in first.</div>,
49
+ }: SDKProviderProps) {
50
+ const [isLoading, setIsLoading] = useState(true);
51
+ const [error, setError] = useState<Error | null>(null);
52
+ const [isInitialized, setIsInitialized] = useState(false);
53
+ const [sdk, setSdk] = useState<SDK | null>(null);
54
+
55
+ /**
56
+ * Initialize the SDK with the current auth token
57
+ */
58
+ const initializeSDK = async () => {
59
+ setIsLoading(true);
60
+ setError(null);
61
+
62
+ try {
63
+ const token = getCookie(AUTH_COOKIES.TOKEN);
64
+
65
+ if (!token) {
66
+ setIsInitialized(false);
67
+ setSdk(null);
68
+ setIsLoading(false);
69
+ return;
70
+ }
71
+
72
+ const basePath = import.meta.env?.VITE_API_URL || import.meta.env?.BUN_PUBLIC_API_URL || undefined;
73
+ const newSDK = new SDK({
74
+ accessToken: token,
75
+ ...(basePath && { basePath }),
76
+ onUnauthorized: (response: Response) => {
77
+ flushCookies();
78
+ onUnauthorized?.(response);
79
+ },
80
+ });
81
+
82
+ setSdk(newSDK);
83
+ setIsInitialized(true);
84
+ } catch (e) {
85
+ setError(e instanceof Error ? e : new Error("Failed to initialize SDK"));
86
+ setSdk(null);
87
+ setIsInitialized(false);
88
+ } finally {
89
+ setIsLoading(false);
90
+ }
91
+ };
92
+
93
+ // Initialize SDK on component mount
94
+ // biome-ignore lint/correctness/useExhaustiveDependencies: initializeSDK is intentionally omitted - should only run once on mount
95
+ useEffect(() => {
96
+ initializeSDK();
97
+ }, []); // Empty dependency array - only run on mount
98
+
99
+ // IMPORTANT: useMemo must be called BEFORE any conditional returns
100
+ // to satisfy React's Rules of Hooks
101
+ // biome-ignore lint/correctness/useExhaustiveDependencies: initializeSDK is stable (doesn't change) and including it would cause unnecessary re-renders
102
+ const value = useMemo(
103
+ () => ({
104
+ sdk: sdk as SDK, // Will be non-null when children render (checked below)
105
+ isInitialized,
106
+ isLoading,
107
+ error,
108
+ reinitialize: initializeSDK,
109
+ }),
110
+ [sdk, isInitialized, isLoading, error], // Don't include initializeSDK - it's stable but causes re-renders
111
+ );
112
+
113
+ // Render appropriate UI based on SDK state
114
+ // Children only render when SDK is guaranteed non-null
115
+ if (isLoading) {
116
+ return <>{fallbackLoading}</>;
117
+ }
118
+
119
+ if (error) {
120
+ return <>{fallbackError(error)}</>;
121
+ }
122
+
123
+ if (!sdk || !isInitialized) {
124
+ return <>{fallbackUnauthorized}</>;
125
+ }
126
+
127
+ return <SDKContext.Provider value={value}>{children}</SDKContext.Provider>;
128
+ }
129
+
130
+ /**
131
+ * Hook to access the SDK context
132
+ * @throws Error if used outside of SDKProvider
133
+ */
134
+ export function useSDK() {
135
+ const context = useContext(SDKContext);
136
+
137
+ if (context === undefined) {
138
+ throw new Error("useSDK must be used within an SDKProvider");
139
+ }
140
+
141
+ return context;
142
+ }
143
+
144
+ /**
145
+ * Optional hook that returns null if not in SDKProvider
146
+ * Useful for components that may be used with or without SDK context
147
+ */
148
+ export function useSDKOptional() {
149
+ const context = useContext(SDKContext);
150
+ return context ?? null;
151
+ }
152
+
153
+ /**
154
+ * Get access token from SDK context (helper for WLSubscriptionProvider)
155
+ */
156
+ export function useAccessToken(): string | null {
157
+ const context = useContext(SDKContext);
158
+ if (!context?.sdk) return null;
159
+
160
+ // Access token is stored in SDK configuration
161
+ // We need to get it from the auth cookie since SDK doesn't expose it directly
162
+ const token = getCookie(AUTH_COOKIES.TOKEN);
163
+ return token ?? null;
164
+ }
@@ -0,0 +1,91 @@
1
+ import type { ReactNode } from "react";
2
+ import { createContext, useContext, useEffect, useMemo, useState } from "react";
3
+
4
+ export type WhiteLabelConfig = {
5
+ slug: string;
6
+ name: string;
7
+ hiddenFeatures: string[]; // Features hidden from UI (still available via API)
8
+ };
9
+
10
+ // Default config until API responds (Space Invoices = all features visible)
11
+ const DEFAULT_CONFIG: WhiteLabelConfig = {
12
+ slug: "space-invoices",
13
+ name: "Space Invoices",
14
+ hiddenFeatures: [],
15
+ };
16
+
17
+ type WhiteLabelContextType = WhiteLabelConfig & {
18
+ /** Check if a feature is visible (not hidden) for this white-label */
19
+ isFeatureVisible: (feature: string) => boolean;
20
+ isLoading: boolean;
21
+ };
22
+
23
+ const WhiteLabelContext = createContext<WhiteLabelContextType | undefined>(undefined);
24
+
25
+ // Add a name to help with debugging
26
+ WhiteLabelContext.displayName = "WhiteLabelContext";
27
+
28
+ type WhiteLabelProviderProps = {
29
+ children: ReactNode;
30
+ /** Optional API base URL (defaults to empty string for same-origin) */
31
+ apiBaseUrl?: string;
32
+ };
33
+
34
+ /**
35
+ * WhiteLabelProvider component
36
+ * Fetches white-label configuration from the API and provides it to the application
37
+ */
38
+ export function WhiteLabelProvider({ children, apiBaseUrl = "" }: WhiteLabelProviderProps) {
39
+ const [config, setConfig] = useState<WhiteLabelConfig>(DEFAULT_CONFIG);
40
+ const [isLoading, setIsLoading] = useState(true);
41
+
42
+ useEffect(() => {
43
+ const fetchConfig = async () => {
44
+ try {
45
+ const response = await fetch(`${apiBaseUrl}/white-labels/current`);
46
+ if (response.ok) {
47
+ const data = await response.json();
48
+ // Map snake_case API response to camelCase internal state
49
+ setConfig({
50
+ slug: data.slug,
51
+ name: data.name,
52
+ hiddenFeatures: data.hidden_features,
53
+ });
54
+ }
55
+ } catch {
56
+ // Use default config if fetch fails
57
+ } finally {
58
+ setIsLoading(false);
59
+ }
60
+ };
61
+
62
+ fetchConfig();
63
+ }, [apiBaseUrl]);
64
+
65
+ const isFeatureVisible = (feature: string) => !config.hiddenFeatures.includes(feature);
66
+
67
+ const value = useMemo(
68
+ () => ({
69
+ ...config,
70
+ isFeatureVisible,
71
+ isLoading,
72
+ }),
73
+ [config, isLoading, isFeatureVisible],
74
+ );
75
+
76
+ return <WhiteLabelContext.Provider value={value}>{children}</WhiteLabelContext.Provider>;
77
+ }
78
+
79
+ /**
80
+ * Hook to access the white-label context
81
+ * @throws Error if used outside of WhiteLabelProvider
82
+ */
83
+ export function useWhiteLabel() {
84
+ const context = useContext(WhiteLabelContext);
85
+
86
+ if (context === undefined) {
87
+ throw new Error("useWhiteLabel must be used within a WhiteLabelProvider");
88
+ }
89
+
90
+ return context;
91
+ }
@@ -0,0 +1,277 @@
1
+ import type { ReactNode } from "react";
2
+ import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
3
+ import { useEntitiesOptional } from "./entities-context";
4
+ import { useAccessToken } from "./sdk-provider";
5
+
6
+ // ============================================
7
+ // TYPES
8
+ // ============================================
9
+
10
+ export type PlanLimits = {
11
+ documents_per_month: number | null;
12
+ } | null;
13
+
14
+ export type WhiteLabelPlan = {
15
+ id: string;
16
+ slug: string;
17
+ name: string;
18
+ billing_interval: string | null;
19
+ base_price_cents: number | null;
20
+ limits: PlanLimits;
21
+ features: string[];
22
+ is_free: boolean;
23
+ display_order: number;
24
+ };
25
+
26
+ export type UsageStats = {
27
+ documents_count: number;
28
+ documents_limit: number | null;
29
+ period_start: string;
30
+ period_end: string;
31
+ };
32
+
33
+ export type CurrentSubscription = {
34
+ plan: WhiteLabelPlan;
35
+ status: string;
36
+ billing_interval: string | null;
37
+ current_period_start: string;
38
+ current_period_end: string;
39
+ usage: UsageStats;
40
+ };
41
+
42
+ // Known gated features for Apollo
43
+ export type GatedFeature =
44
+ | "furs"
45
+ | "eslog"
46
+ | "recurring"
47
+ | "email_sending"
48
+ | "custom_templates"
49
+ | "api_access"
50
+ | "webhooks"
51
+ | "priority_support";
52
+
53
+ // ============================================
54
+ // CONTEXT
55
+ // ============================================
56
+
57
+ type WLSubscriptionContextType = {
58
+ subscription: CurrentSubscription | null;
59
+ plan: WhiteLabelPlan | null;
60
+ usage: UsageStats | null;
61
+ availablePlans: WhiteLabelPlan[];
62
+ isLoading: boolean;
63
+ error: string | null;
64
+
65
+ // Feature/limit checks
66
+ hasFeature: (feature: GatedFeature | string) => boolean;
67
+ isOverLimit: (resource: "documents") => boolean;
68
+ getUsagePercentage: (resource: "documents") => number;
69
+
70
+ // Refresh data
71
+ refresh: () => Promise<void>;
72
+ };
73
+
74
+ const WLSubscriptionContext = createContext<WLSubscriptionContextType | undefined>(undefined);
75
+
76
+ WLSubscriptionContext.displayName = "WLSubscriptionContext";
77
+
78
+ // ============================================
79
+ // DEFAULT STATE
80
+ // ============================================
81
+
82
+ // Default subscription (unlimited, all features) for non-WL users
83
+ const DEFAULT_SUBSCRIPTION: CurrentSubscription = {
84
+ plan: {
85
+ id: "unlimited",
86
+ slug: "unlimited",
87
+ name: "Unlimited",
88
+ billing_interval: null,
89
+ base_price_cents: null,
90
+ limits: null,
91
+ features: [], // Empty = all features
92
+ is_free: true,
93
+ display_order: 0,
94
+ },
95
+ status: "active",
96
+ billing_interval: null,
97
+ current_period_start: new Date().toISOString(),
98
+ current_period_end: new Date().toISOString(),
99
+ usage: {
100
+ documents_count: 0,
101
+ documents_limit: null,
102
+ period_start: new Date().toISOString(),
103
+ period_end: new Date().toISOString(),
104
+ },
105
+ };
106
+
107
+ // ============================================
108
+ // PROVIDER
109
+ // ============================================
110
+
111
+ type WLSubscriptionProviderProps = {
112
+ children: ReactNode;
113
+ /** API base URL (required for authenticated requests) */
114
+ apiBaseUrl: string;
115
+ };
116
+
117
+ /**
118
+ * WLSubscriptionProvider component
119
+ * Fetches white-label subscription data and provides limit/feature checks.
120
+ * Must be nested inside EntitiesProvider and SDKProvider.
121
+ */
122
+ export function WLSubscriptionProvider({ children, apiBaseUrl }: WLSubscriptionProviderProps) {
123
+ // Get entity and access token from existing context
124
+ const entitiesContext = useEntitiesOptional();
125
+ const accessToken = useAccessToken();
126
+
127
+ const entityId = entitiesContext?.activeEntity?.id ?? null;
128
+ const [subscription, setSubscription] = useState<CurrentSubscription>(DEFAULT_SUBSCRIPTION);
129
+ const [availablePlans, setAvailablePlans] = useState<WhiteLabelPlan[]>([]);
130
+ const [isLoading, setIsLoading] = useState(true);
131
+ const [error, setError] = useState<string | null>(null);
132
+
133
+ const fetchSubscription = useCallback(async () => {
134
+ if (!entityId || !accessToken) {
135
+ setSubscription(DEFAULT_SUBSCRIPTION);
136
+ setAvailablePlans([]);
137
+ setIsLoading(false);
138
+ return;
139
+ }
140
+
141
+ try {
142
+ setIsLoading(true);
143
+ setError(null);
144
+
145
+ const headers = {
146
+ Authorization: `Bearer ${accessToken}`,
147
+ "x-entity-id": entityId,
148
+ "Content-Type": "application/json",
149
+ };
150
+
151
+ // Fetch current subscription
152
+ const subResponse = await fetch(`${apiBaseUrl}/white-label-subscriptions`, { headers });
153
+
154
+ if (subResponse.ok) {
155
+ const subData = await subResponse.json();
156
+ setSubscription(subData);
157
+ } else if (subResponse.status === 404) {
158
+ // No WL subscription = use default (unlimited)
159
+ setSubscription(DEFAULT_SUBSCRIPTION);
160
+ } else {
161
+ throw new Error(`Failed to fetch subscription: ${subResponse.status}`);
162
+ }
163
+
164
+ // Fetch available plans
165
+ const plansResponse = await fetch(`${apiBaseUrl}/white-label-subscriptions/plans`, {
166
+ headers,
167
+ });
168
+
169
+ if (plansResponse.ok) {
170
+ const plansData = await plansResponse.json();
171
+ setAvailablePlans(plansData.plans || []);
172
+ }
173
+ } catch (err) {
174
+ setError(err instanceof Error ? err.message : "Failed to fetch subscription");
175
+ setSubscription(DEFAULT_SUBSCRIPTION);
176
+ } finally {
177
+ setIsLoading(false);
178
+ }
179
+ }, [apiBaseUrl, entityId, accessToken]);
180
+
181
+ useEffect(() => {
182
+ fetchSubscription();
183
+ }, [fetchSubscription]);
184
+
185
+ // Check if feature is available on current plan
186
+ const hasFeature = useCallback(
187
+ (feature: GatedFeature | string): boolean => {
188
+ const plan = subscription.plan;
189
+
190
+ // Empty features array = all features included
191
+ if (plan.features.length === 0) {
192
+ return true;
193
+ }
194
+
195
+ return plan.features.includes(feature);
196
+ },
197
+ [subscription],
198
+ );
199
+
200
+ // Check if over document limit
201
+ const isOverLimit = useCallback(
202
+ (resource: "documents"): boolean => {
203
+ if (resource !== "documents") return false;
204
+
205
+ const { usage, plan } = subscription;
206
+ const limit = plan.limits?.documents_per_month;
207
+
208
+ if (limit === null || limit === undefined) {
209
+ return false; // No limit = never over
210
+ }
211
+
212
+ return usage.documents_count >= limit;
213
+ },
214
+ [subscription],
215
+ );
216
+
217
+ // Get usage percentage for resource
218
+ const getUsagePercentage = useCallback(
219
+ (resource: "documents"): number => {
220
+ if (resource !== "documents") return 0;
221
+
222
+ const { usage, plan } = subscription;
223
+ const limit = plan.limits?.documents_per_month;
224
+
225
+ if (limit === null || limit === undefined || limit === 0) {
226
+ return 0; // No limit = 0%
227
+ }
228
+
229
+ return Math.min(100, Math.round((usage.documents_count / limit) * 100));
230
+ },
231
+ [subscription],
232
+ );
233
+
234
+ const value = useMemo(
235
+ () => ({
236
+ subscription,
237
+ plan: subscription.plan,
238
+ usage: subscription.usage,
239
+ availablePlans,
240
+ isLoading,
241
+ error,
242
+ hasFeature,
243
+ isOverLimit,
244
+ getUsagePercentage,
245
+ refresh: fetchSubscription,
246
+ }),
247
+ [subscription, availablePlans, isLoading, error, hasFeature, isOverLimit, getUsagePercentage, fetchSubscription],
248
+ );
249
+
250
+ return <WLSubscriptionContext.Provider value={value}>{children}</WLSubscriptionContext.Provider>;
251
+ }
252
+
253
+ // ============================================
254
+ // HOOK
255
+ // ============================================
256
+
257
+ /**
258
+ * Hook to access the white-label subscription context
259
+ * @throws Error if used outside of WLSubscriptionProvider
260
+ */
261
+ export function useWLSubscription() {
262
+ const context = useContext(WLSubscriptionContext);
263
+
264
+ if (context === undefined) {
265
+ throw new Error("useWLSubscription must be used within a WLSubscriptionProvider");
266
+ }
267
+
268
+ return context;
269
+ }
270
+
271
+ /**
272
+ * Optional hook that returns null if not in WLSubscriptionProvider
273
+ * Useful for components that may be used with or without subscription context
274
+ */
275
+ export function useWLSubscriptionOptional() {
276
+ return useContext(WLSubscriptionContext) ?? null;
277
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * String utility functions
3
+ */
4
+
5
+ /**
6
+ * Capitalize first letter of string
7
+ * @param str - The string to capitalize
8
+ * @returns The capitalized string
9
+ *
10
+ * @example
11
+ * capitalize("hello") // "Hello"
12
+ * capitalize("HELLO") // "HELLO"
13
+ */
14
+ export function capitalize(str: string): string {
15
+ return str.charAt(0).toUpperCase() + str.slice(1);
16
+ }
17
+
18
+ /**
19
+ * Convert string to kebab-case
20
+ * @param str - The string to convert
21
+ * @returns The kebab-cased string
22
+ *
23
+ * @example
24
+ * toKebabCase("helloWorld") // "hello-world"
25
+ * toKebabCase("HelloWorld") // "hello-world"
26
+ */
27
+ export function toKebabCase(str: string): string {
28
+ return str
29
+ .replace(/([a-z])([A-Z])/g, "$1-$2")
30
+ .replace(/[\s_]+/g, "-")
31
+ .toLowerCase();
32
+ }
33
+
34
+ /**
35
+ * Convert string to PascalCase
36
+ * @param str - The string to convert
37
+ * @returns The PascalCased string
38
+ *
39
+ * @example
40
+ * toPascalCase("hello world") // "HelloWorld"
41
+ * toPascalCase("hello-world") // "HelloWorld"
42
+ */
43
+ export function toPascalCase(str: string): string {
44
+ return str
45
+ .split(/[-_\s]+/)
46
+ .map((word) => capitalize(word))
47
+ .join("");
48
+ }
49
+
50
+ /**
51
+ * Convert string to camelCase
52
+ * @param str - The string to convert
53
+ * @returns The camelCased string
54
+ *
55
+ * @example
56
+ * toCamelCase("hello world") // "helloWorld"
57
+ * toCamelCase("hello-world") // "helloWorld"
58
+ */
59
+ export function toCamelCase(str: string): string {
60
+ const pascal = toPascalCase(str);
61
+ return pascal.charAt(0).toLowerCase() + pascal.slice(1);
62
+ }
63
+
64
+ /**
65
+ * Convert string to Title Case
66
+ * @param str - The string to convert
67
+ * @returns The Title Cased string
68
+ *
69
+ * @example
70
+ * toTitleCase("hello world") // "Hello World"
71
+ * toTitleCase("hello-world") // "Hello World"
72
+ */
73
+ export function toTitleCase(str: string): string {
74
+ return str
75
+ .split(/[-_\s]+/)
76
+ .map((word) => capitalize(word))
77
+ .join(" ");
78
+ }
79
+
80
+ /**
81
+ * Create a URL-friendly slug from a string
82
+ * @param str - The string to slugify
83
+ * @returns The slugified string
84
+ *
85
+ * @example
86
+ * slugify("Hello World!") // "hello-world"
87
+ * slugify("Hello World") // "hello-world"
88
+ */
89
+ export function slugify(str: string): string {
90
+ return str
91
+ .toLowerCase()
92
+ .replace(/[^\w\s-]/g, "")
93
+ .replace(/[\s_]+/g, "-")
94
+ .replace(/^-+|-+$/g, "");
95
+ }
96
+
97
+ /**
98
+ * Truncate a string to a maximum length
99
+ * @param str - The string to truncate
100
+ * @param maxLength - The maximum length
101
+ * @param suffix - The suffix to append (default: "...")
102
+ * @returns The truncated string
103
+ *
104
+ * @example
105
+ * truncate("Hello World", 5) // "Hello..."
106
+ * truncate("Hello", 10) // "Hello"
107
+ */
108
+ export function truncate(str: string, maxLength: number, suffix = "..."): string {
109
+ if (str.length <= maxLength) return str;
110
+ return str.slice(0, maxLength - suffix.length) + suffix;
111
+ }