@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,13 @@
1
+ import { cn } from "@/ui/lib/utils"
2
+
3
+ function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
4
+ return (
5
+ <div
6
+ data-slot="skeleton"
7
+ className={cn("bg-muted rounded-md animate-pulse", className)}
8
+ {...props}
9
+ />
10
+ )
11
+ }
12
+
13
+ export { Skeleton }
@@ -0,0 +1,60 @@
1
+ "use client"
2
+
3
+ import { Toaster as Sonner, type ToasterProps } from "sonner"
4
+ import { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from "lucide-react"
5
+
6
+ // Try to use next-themes if available, otherwise fall back to system theme
7
+ let useTheme: () => { theme?: string } = () => ({ theme: "system" })
8
+ try {
9
+ // Dynamic import to avoid hard dependency on next-themes
10
+ const nextThemes = require("next-themes")
11
+ if (nextThemes?.useTheme) {
12
+ useTheme = nextThemes.useTheme
13
+ }
14
+ } catch {
15
+ // next-themes not available, use fallback
16
+ }
17
+
18
+ const Toaster = ({ ...props }: ToasterProps) => {
19
+ const { theme = "system" } = useTheme()
20
+
21
+ return (
22
+ <Sonner
23
+ theme={theme as ToasterProps["theme"]}
24
+ className="toaster group"
25
+ icons={{
26
+ success: (
27
+ <CircleCheckIcon className="size-4" />
28
+ ),
29
+ info: (
30
+ <InfoIcon className="size-4" />
31
+ ),
32
+ warning: (
33
+ <TriangleAlertIcon className="size-4" />
34
+ ),
35
+ error: (
36
+ <OctagonXIcon className="size-4" />
37
+ ),
38
+ loading: (
39
+ <Loader2Icon className="size-4 animate-spin" />
40
+ ),
41
+ }}
42
+ style={
43
+ {
44
+ "--normal-bg": "var(--popover)",
45
+ "--normal-text": "var(--popover-foreground)",
46
+ "--normal-border": "var(--border)",
47
+ "--border-radius": "var(--radius)",
48
+ } as React.CSSProperties
49
+ }
50
+ toastOptions={{
51
+ classNames: {
52
+ toast: "cn-toast",
53
+ },
54
+ }}
55
+ {...props}
56
+ />
57
+ )
58
+ }
59
+
60
+ export { Toaster }
@@ -0,0 +1,10 @@
1
+ import { cn } from "@/ui/lib/utils"
2
+ import { Loader2Icon } from "lucide-react"
3
+
4
+ function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
5
+ return (
6
+ <Loader2Icon role="status" aria-label="Loading" className={cn("size-4 animate-spin", className)} {...props} />
7
+ )
8
+ }
9
+
10
+ export { Spinner }
@@ -0,0 +1,55 @@
1
+ import { Button } from "@/ui/components/ui/button";
2
+ import ButtonLoader from "../button-loader";
3
+ import { cn } from "@/ui/lib/utils";
4
+
5
+ type SecondaryAction = {
6
+ label: string;
7
+ onClick: () => void;
8
+ isPending?: boolean;
9
+ };
10
+
11
+ type StickyFormFooterProps = {
12
+ formId: string;
13
+ isPending: boolean;
14
+ isDirty: boolean;
15
+ label: string;
16
+ onSubmit?: () => void;
17
+ secondaryAction?: SecondaryAction;
18
+ className?: string;
19
+ };
20
+
21
+ export function StickyFormFooter({
22
+ formId,
23
+ isPending,
24
+ isDirty,
25
+ label,
26
+ onSubmit,
27
+ secondaryAction,
28
+ className,
29
+ }: StickyFormFooterProps) {
30
+ // If onSubmit is provided, use onClick, otherwise use form attribute for native form submission
31
+ const buttonProps = onSubmit
32
+ ? { type: "button" as const, onClick: onSubmit }
33
+ : { type: "submit" as const, form: formId };
34
+
35
+ return (
36
+ <div className={cn("sticky bottom-0 z-10 border-t bg-sidebar px-4 py-3", className)}>
37
+ <div className="flex gap-2">
38
+ <Button {...buttonProps} className="cursor-pointer px-8" disabled={isPending || !isDirty}>
39
+ {isPending ? <ButtonLoader /> : label}
40
+ </Button>
41
+ {secondaryAction && (
42
+ <Button
43
+ type="button"
44
+ variant="outline"
45
+ className="cursor-pointer px-8"
46
+ disabled={secondaryAction.isPending || !isDirty}
47
+ onClick={secondaryAction.onClick}
48
+ >
49
+ {secondaryAction.isPending ? <ButtonLoader /> : secondaryAction.label}
50
+ </Button>
51
+ )}
52
+ </div>
53
+ </div>
54
+ );
55
+ }
@@ -0,0 +1,30 @@
1
+ import { Switch as SwitchPrimitive } from "@base-ui/react/switch"
2
+
3
+ import { cn } from "@/ui/lib/utils"
4
+
5
+ function Switch({
6
+ className,
7
+ size = "default",
8
+ ...props
9
+ }: SwitchPrimitive.Root.Props & {
10
+ size?: "sm" | "default"
11
+ }) {
12
+ return (
13
+ <SwitchPrimitive.Root
14
+ data-slot="switch"
15
+ data-size={size}
16
+ className={cn(
17
+ "data-checked:bg-primary data-unchecked:bg-input focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 dark:data-unchecked:bg-input/80 shrink-0 rounded-full border border-transparent shadow-xs focus-visible:ring-[3px] aria-invalid:ring-[3px] data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] peer group/switch relative inline-flex items-center transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 cursor-pointer data-disabled:cursor-not-allowed data-disabled:opacity-50",
18
+ className
19
+ )}
20
+ {...props}
21
+ >
22
+ <SwitchPrimitive.Thumb
23
+ data-slot="switch-thumb"
24
+ className="bg-background dark:data-unchecked:bg-foreground dark:data-checked:bg-primary-foreground rounded-full group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 pointer-events-none block ring-0 transition-transform"
25
+ />
26
+ </SwitchPrimitive.Root>
27
+ )
28
+ }
29
+
30
+ export { Switch }
@@ -0,0 +1,101 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "@/ui/lib/utils"
6
+
7
+ function Table({ className, ...props }: React.ComponentProps<"table">) {
8
+ return (
9
+ <div data-slot="table-container" className="relative w-full overflow-x-auto">
10
+ <table
11
+ data-slot="table"
12
+ className={cn("w-full caption-bottom text-sm", className)}
13
+ {...props}
14
+ />
15
+ </div>
16
+ )
17
+ }
18
+
19
+ function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
20
+ return (
21
+ <thead
22
+ data-slot="table-header"
23
+ className={cn("[&_tr]:border-b", className)}
24
+ {...props}
25
+ />
26
+ )
27
+ }
28
+
29
+ function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
30
+ return (
31
+ <tbody
32
+ data-slot="table-body"
33
+ className={cn("[&_tr:last-child]:border-0", className)}
34
+ {...props}
35
+ />
36
+ )
37
+ }
38
+
39
+ function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
40
+ return (
41
+ <tfoot
42
+ data-slot="table-footer"
43
+ className={cn("bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", className)}
44
+ {...props}
45
+ />
46
+ )
47
+ }
48
+
49
+ function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
50
+ return (
51
+ <tr
52
+ data-slot="table-row"
53
+ className={cn("hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", className)}
54
+ {...props}
55
+ />
56
+ )
57
+ }
58
+
59
+ function TableHead({ className, ...props }: React.ComponentProps<"th">) {
60
+ return (
61
+ <th
62
+ data-slot="table-head"
63
+ className={cn("text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap first:pl-4 last:pr-4 [&:has([role=checkbox])]:pr-0", className)}
64
+ {...props}
65
+ />
66
+ )
67
+ }
68
+
69
+ function TableCell({ className, ...props }: React.ComponentProps<"td">) {
70
+ return (
71
+ <td
72
+ data-slot="table-cell"
73
+ className={cn("p-2 align-middle whitespace-nowrap first:pl-4 last:pr-4 [&:has([role=checkbox])]:pr-0", className)}
74
+ {...props}
75
+ />
76
+ )
77
+ }
78
+
79
+ function TableCaption({
80
+ className,
81
+ ...props
82
+ }: React.ComponentProps<"caption">) {
83
+ return (
84
+ <caption
85
+ data-slot="table-caption"
86
+ className={cn("text-muted-foreground mt-4 text-sm", className)}
87
+ {...props}
88
+ />
89
+ )
90
+ }
91
+
92
+ export {
93
+ Table,
94
+ TableHeader,
95
+ TableBody,
96
+ TableFooter,
97
+ TableHead,
98
+ TableRow,
99
+ TableCell,
100
+ TableCaption,
101
+ }
@@ -0,0 +1,80 @@
1
+ import { Tabs as TabsPrimitive } from "@base-ui/react/tabs"
2
+ import { cva, type VariantProps } from "class-variance-authority"
3
+
4
+ import { cn } from "@/ui/lib/utils"
5
+
6
+ function Tabs({
7
+ className,
8
+ orientation = "horizontal",
9
+ ...props
10
+ }: TabsPrimitive.Root.Props) {
11
+ return (
12
+ <TabsPrimitive.Root
13
+ data-slot="tabs"
14
+ data-orientation={orientation}
15
+ className={cn(
16
+ "gap-2 group/tabs flex data-[orientation=horizontal]:flex-col",
17
+ className
18
+ )}
19
+ {...props}
20
+ />
21
+ )
22
+ }
23
+
24
+ const tabsListVariants = cva(
25
+ "rounded-lg p-[3px] w-full group-data-horizontal/tabs:h-9 data-[variant=line]:rounded-none group/tabs-list text-muted-foreground flex items-center justify-center group-data-[orientation=vertical]/tabs:h-fit group-data-[orientation=vertical]/tabs:flex-col group-data-[orientation=vertical]/tabs:w-fit",
26
+ {
27
+ variants: {
28
+ variant: {
29
+ default: "bg-muted",
30
+ line: "gap-1 bg-transparent",
31
+ },
32
+ },
33
+ defaultVariants: {
34
+ variant: "default",
35
+ },
36
+ }
37
+ )
38
+
39
+ function TabsList({
40
+ className,
41
+ variant = "default",
42
+ ...props
43
+ }: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {
44
+ return (
45
+ <TabsPrimitive.List
46
+ data-slot="tabs-list"
47
+ data-variant={variant}
48
+ className={cn(tabsListVariants({ variant }), className)}
49
+ {...props}
50
+ />
51
+ )
52
+ }
53
+
54
+ function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
55
+ return (
56
+ <TabsPrimitive.Tab
57
+ data-slot="tabs-trigger"
58
+ className={cn(
59
+ "gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg:not([class*='size-'])]:size-4 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring text-foreground/60 hover:text-foreground dark:text-muted-foreground dark:hover:text-foreground relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center whitespace-nowrap transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start focus-visible:ring-[3px] focus-visible:outline-1 cursor-pointer disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0",
60
+ "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
61
+ "data-active:bg-background dark:data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 data-active:text-foreground",
62
+ "after:bg-foreground after:absolute after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
63
+ className
64
+ )}
65
+ {...props}
66
+ />
67
+ )
68
+ }
69
+
70
+ function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
71
+ return (
72
+ <TabsPrimitive.Panel
73
+ data-slot="tabs-content"
74
+ className={cn("text-sm flex-1 outline-none", className)}
75
+ {...props}
76
+ />
77
+ )
78
+ }
79
+
80
+ export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
@@ -0,0 +1,18 @@
1
+ import * as React from "react"
2
+
3
+ import { cn } from "@/ui/lib/utils"
4
+
5
+ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
6
+ return (
7
+ <textarea
8
+ data-slot="textarea"
9
+ className={cn(
10
+ "border-input dark:bg-input/30 focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-md border bg-transparent px-2.5 py-2 text-base shadow-xs transition-[color,box-shadow] focus-visible:ring-[3px] aria-invalid:ring-[3px] md:text-sm placeholder:text-muted-foreground flex field-sizing-content min-h-16 w-full outline-none disabled:cursor-not-allowed disabled:opacity-50",
11
+ className
12
+ )}
13
+ {...props}
14
+ />
15
+ )
16
+ }
17
+
18
+ export { Textarea }
@@ -0,0 +1,89 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { Tooltip as TooltipPrimitive } from "@base-ui/react/tooltip"
5
+
6
+ import { cn } from "@/ui/lib/utils"
7
+
8
+ function TooltipProvider({
9
+ delay = 0,
10
+ ...props
11
+ }: TooltipPrimitive.Provider.Props) {
12
+ return (
13
+ <TooltipPrimitive.Provider
14
+ data-slot="tooltip-provider"
15
+ delay={delay}
16
+ {...props}
17
+ />
18
+ )
19
+ }
20
+
21
+ function Tooltip({ ...props }: TooltipPrimitive.Root.Props) {
22
+ return (
23
+ <TooltipProvider>
24
+ <TooltipPrimitive.Root data-slot="tooltip" {...props} />
25
+ </TooltipProvider>
26
+ )
27
+ }
28
+
29
+ function TooltipTrigger({
30
+ asChild,
31
+ children,
32
+ ...props
33
+ }: TooltipPrimitive.Trigger.Props & { asChild?: boolean }) {
34
+ // Base UI uses render prop instead of asChild
35
+ if (asChild && React.isValidElement(children)) {
36
+ return (
37
+ <TooltipPrimitive.Trigger
38
+ data-slot="tooltip-trigger"
39
+ render={children}
40
+ {...props}
41
+ />
42
+ )
43
+ }
44
+ return (
45
+ <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props}>
46
+ {children}
47
+ </TooltipPrimitive.Trigger>
48
+ )
49
+ }
50
+
51
+ function TooltipContent({
52
+ className,
53
+ side = "top",
54
+ sideOffset = 4,
55
+ align = "center",
56
+ alignOffset = 0,
57
+ children,
58
+ ...props
59
+ }: TooltipPrimitive.Popup.Props &
60
+ Pick<
61
+ TooltipPrimitive.Positioner.Props,
62
+ "align" | "alignOffset" | "side" | "sideOffset"
63
+ >) {
64
+ return (
65
+ <TooltipPrimitive.Portal>
66
+ <TooltipPrimitive.Positioner
67
+ align={align}
68
+ alignOffset={alignOffset}
69
+ side={side}
70
+ sideOffset={sideOffset}
71
+ className="isolate z-50"
72
+ >
73
+ <TooltipPrimitive.Popup
74
+ data-slot="tooltip-content"
75
+ className={cn(
76
+ "data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-[state=delayed-open]:animate-in data-[state=delayed-open]:fade-in-0 data-[state=delayed-open]:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 rounded-md px-3 py-1.5 text-xs bg-foreground text-background z-50 w-fit max-w-xs origin-(--transform-origin)",
77
+ className
78
+ )}
79
+ {...props}
80
+ >
81
+ {children}
82
+ <TooltipPrimitive.Arrow className="size-2.5 translate-y-[calc(-50%_-_2px)] rotate-45 rounded-[2px] bg-foreground fill-foreground z-50 data-[side=bottom]:top-1 data-[side=left]:top-1/2! data-[side=left]:-right-1 data-[side=left]:-translate-y-1/2 data-[side=right]:top-1/2! data-[side=right]:-left-1 data-[side=right]:-translate-y-1/2 data-[side=top]:-bottom-2.5" />
83
+ </TooltipPrimitive.Popup>
84
+ </TooltipPrimitive.Positioner>
85
+ </TooltipPrimitive.Portal>
86
+ )
87
+ }
88
+
89
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }
@@ -0,0 +1,2 @@
1
+ export { LockedBadge, LockedFeature, UsageBadge } from "./locked-feature";
2
+ export { UpgradeModal } from "./upgrade-modal";
@@ -0,0 +1,173 @@
1
+ import { Lock } from "lucide-react";
2
+ import type { ReactNode } from "react";
3
+ import { useState } from "react";
4
+
5
+ import { type GatedFeature, useWLSubscriptionOptional } from "../../providers/wl-subscription-provider";
6
+ import { UpgradeModal } from "./upgrade-modal";
7
+
8
+ type LockedFeatureProps = {
9
+ /** Feature slug to check access for */
10
+ feature: GatedFeature;
11
+ /** Content to render when feature is unlocked */
12
+ children: ReactNode;
13
+ /** Optional custom locked message */
14
+ lockedMessage?: string;
15
+ /** Whether to show the upgrade modal on click (default: true) */
16
+ showUpgradeModal?: boolean;
17
+ /** Custom render for locked state */
18
+ lockedRender?: () => ReactNode;
19
+ };
20
+
21
+ /**
22
+ * LockedFeature wrapper component
23
+ *
24
+ * Wraps content that requires a specific subscription feature.
25
+ * Shows a locked overlay with upgrade prompt if feature is not available.
26
+ *
27
+ * @example
28
+ * <LockedFeature feature="furs">
29
+ * <FursSettingsPage />
30
+ * </LockedFeature>
31
+ */
32
+ export function LockedFeature({
33
+ feature,
34
+ children,
35
+ lockedMessage,
36
+ showUpgradeModal = true,
37
+ lockedRender,
38
+ }: LockedFeatureProps) {
39
+ const subscription = useWLSubscriptionOptional();
40
+ const [isModalOpen, setIsModalOpen] = useState(false);
41
+
42
+ // If no subscription context, feature is unlocked (Space Invoices)
43
+ if (!subscription) {
44
+ return <>{children}</>;
45
+ }
46
+
47
+ const hasFeature = subscription.hasFeature(feature);
48
+
49
+ // Feature is available, render children
50
+ if (hasFeature) {
51
+ return <>{children}</>;
52
+ }
53
+
54
+ // Feature is locked - show locked state
55
+ const handleClick = () => {
56
+ if (showUpgradeModal) {
57
+ setIsModalOpen(true);
58
+ }
59
+ };
60
+
61
+ // Custom locked render
62
+ if (lockedRender) {
63
+ return (
64
+ <>
65
+ <button type="button" className="w-full text-left" onClick={handleClick}>
66
+ {lockedRender()}
67
+ </button>
68
+ {showUpgradeModal && (
69
+ <UpgradeModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} feature={feature} />
70
+ )}
71
+ </>
72
+ );
73
+ }
74
+
75
+ // Default locked state
76
+ return (
77
+ <>
78
+ <button type="button" className="relative w-full cursor-pointer" onClick={handleClick}>
79
+ {/* Blurred content */}
80
+ <div className="pointer-events-none select-none opacity-50 blur-sm">{children}</div>
81
+
82
+ {/* Lock overlay */}
83
+ <div className="absolute inset-0 flex items-center justify-center bg-background/60">
84
+ <div className="flex flex-col items-center gap-2 p-4 text-center">
85
+ <div className="rounded-full bg-muted p-3">
86
+ <Lock className="h-6 w-6 text-muted-foreground" />
87
+ </div>
88
+ <p className="font-medium text-muted-foreground text-sm">
89
+ {lockedMessage || getDefaultLockedMessage(feature)}
90
+ </p>
91
+ <p className="text-muted-foreground text-xs">Click to upgrade</p>
92
+ </div>
93
+ </div>
94
+ </button>
95
+
96
+ {showUpgradeModal && (
97
+ <UpgradeModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} feature={feature} />
98
+ )}
99
+ </>
100
+ );
101
+ }
102
+
103
+ /**
104
+ * LockedBadge component for sidebar items
105
+ * Shows a small lock icon next to locked features
106
+ */
107
+ export function LockedBadge({ feature }: { feature: GatedFeature }) {
108
+ const subscription = useWLSubscriptionOptional();
109
+
110
+ // No subscription context or has feature = no badge
111
+ if (!subscription || subscription.hasFeature(feature)) {
112
+ return null;
113
+ }
114
+
115
+ return (
116
+ <span className="ml-auto">
117
+ <Lock className="h-3.5 w-3.5 text-muted-foreground" />
118
+ </span>
119
+ );
120
+ }
121
+
122
+ /**
123
+ * UsageBadge component showing document usage percentage
124
+ * For displaying in settings or dashboard
125
+ */
126
+ export function UsageBadge() {
127
+ const subscription = useWLSubscriptionOptional();
128
+
129
+ if (!subscription) return null;
130
+
131
+ const percentage = subscription.getUsagePercentage("documents");
132
+ const { usage, plan } = subscription;
133
+
134
+ if (!plan || !usage) return null;
135
+
136
+ if (plan.limits?.documents_per_month === null) {
137
+ return null; // Unlimited - no badge needed
138
+ }
139
+
140
+ const limit = plan.limits?.documents_per_month ?? 0;
141
+ const isNearLimit = percentage >= 80;
142
+ const isAtLimit = percentage >= 100;
143
+
144
+ return (
145
+ <div
146
+ className={`rounded-full px-2 py-1 text-xs ${
147
+ isAtLimit
148
+ ? "bg-destructive/10 text-destructive"
149
+ : isNearLimit
150
+ ? "bg-warning/10 text-warning"
151
+ : "bg-muted text-muted-foreground"
152
+ }`}
153
+ >
154
+ {usage.documents_count}/{limit} docs
155
+ </div>
156
+ );
157
+ }
158
+
159
+ // Helper to get default locked message for a feature
160
+ function getDefaultLockedMessage(feature: GatedFeature): string {
161
+ const messages: Record<GatedFeature, string> = {
162
+ furs: "FURS fiscalization requires a Starter or Advanced plan",
163
+ eslog: "eSlog export requires a Starter or Advanced plan",
164
+ recurring: "Recurring invoices require a Starter or Advanced plan",
165
+ email_sending: "Email sending requires a Starter or Advanced plan",
166
+ custom_templates: "Custom templates require an Advanced plan",
167
+ api_access: "API access requires an Advanced plan",
168
+ webhooks: "Webhooks require an Advanced plan",
169
+ priority_support: "Priority support requires an Advanced plan",
170
+ };
171
+
172
+ return messages[feature] || "This feature requires a plan upgrade";
173
+ }