@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,420 @@
1
+ import { zodResolver } from "@hookform/resolvers/zod";
2
+ import type { Entity } from "@spaceinvoices/js-sdk";
3
+ import type { FC } from "react";
4
+ import { useForm } from "react-hook-form";
5
+ import type { z } from "zod";
6
+ import { Button } from "@/ui/components/ui/button";
7
+ import {
8
+ Dialog,
9
+ DialogContent,
10
+ DialogDescription,
11
+ DialogFooter,
12
+ DialogHeader,
13
+ DialogTitle,
14
+ } from "@/ui/components/ui/dialog";
15
+ import {
16
+ Form,
17
+ FormControl,
18
+ FormDescription,
19
+ FormField,
20
+ FormItem,
21
+ FormLabel,
22
+ FormMessage,
23
+ } from "@/ui/components/ui/form";
24
+ import { Input } from "@/ui/components/ui/input";
25
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/ui/components/ui/select";
26
+ import { registerFursMovablePremiseSchema, registerFursRealEstatePremiseSchema } from "@/ui/generated/schemas";
27
+ import { useRegisterMovablePremise, useRegisterRealEstatePremise } from "../furs-settings.hooks";
28
+
29
+ // Use auto-generated schemas from OpenAPI spec
30
+ // These are automatically kept in sync with the API
31
+ const realEstatePremiseSchema = registerFursRealEstatePremiseSchema;
32
+ const movablePremiseSchema = registerFursMovablePremiseSchema;
33
+
34
+ type RealEstatePremiseForm = z.infer<typeof realEstatePremiseSchema>;
35
+ type MovablePremiseForm = z.infer<typeof movablePremiseSchema>;
36
+
37
+ interface RegisterPremiseDialogProps {
38
+ open: boolean;
39
+ onOpenChange: (open: boolean) => void;
40
+ entity: Entity;
41
+ type: "real-estate" | "movable";
42
+ t: (key: string) => string;
43
+ onSuccess?: () => void;
44
+ onError?: (error: unknown) => void;
45
+ }
46
+
47
+ export const RegisterPremiseDialog: FC<RegisterPremiseDialogProps> = ({
48
+ open,
49
+ onOpenChange,
50
+ entity,
51
+ type,
52
+ t,
53
+ onSuccess,
54
+ onError,
55
+ }) => {
56
+ const isRealEstate = type === "real-estate";
57
+
58
+ // Real Estate Form
59
+ const realEstateForm = useForm<RealEstatePremiseForm>({
60
+ resolver: zodResolver(realEstatePremiseSchema),
61
+ defaultValues: {
62
+ business_premise_name: "",
63
+ real_estate: {
64
+ cadastral_number: "",
65
+ building_number: "",
66
+ building_section: "",
67
+ community: "",
68
+ city: "",
69
+ street: "",
70
+ house_number: "",
71
+ house_number_additional: "",
72
+ postal_code: "",
73
+ },
74
+ numbering_strategy: "C",
75
+ },
76
+ });
77
+
78
+ // Movable Form
79
+ const movableForm = useForm<MovablePremiseForm>({
80
+ resolver: zodResolver(movablePremiseSchema),
81
+ defaultValues: {
82
+ business_premise_name: "",
83
+ movable_premise: {
84
+ premise_type: "A",
85
+ },
86
+ numbering_strategy: "C",
87
+ },
88
+ });
89
+
90
+ const { mutate: registerRealEstate, isPending: isRealEstatePending } = useRegisterRealEstatePremise({
91
+ onSuccess: () => {
92
+ realEstateForm.reset();
93
+ onSuccess?.();
94
+ },
95
+ onError,
96
+ });
97
+
98
+ const { mutate: registerMovable, isPending: isMovablePending } = useRegisterMovablePremise({
99
+ onSuccess: () => {
100
+ movableForm.reset();
101
+ onSuccess?.();
102
+ },
103
+ onError,
104
+ });
105
+
106
+ const handleRealEstateSubmit = (data: RealEstatePremiseForm) => {
107
+ registerRealEstate({
108
+ entityId: entity.id,
109
+ data,
110
+ });
111
+ };
112
+
113
+ const handleMovableSubmit = (data: MovablePremiseForm) => {
114
+ registerMovable({
115
+ entityId: entity.id,
116
+ data,
117
+ });
118
+ };
119
+
120
+ const isPending = isRealEstatePending || isMovablePending;
121
+
122
+ return (
123
+ <Dialog open={open} onOpenChange={onOpenChange}>
124
+ <DialogContent className="max-h-[90vh] max-w-2xl overflow-y-auto">
125
+ <DialogHeader>
126
+ <DialogTitle>{isRealEstate ? t("Register Real Estate Premise") : t("Register Movable Premise")}</DialogTitle>
127
+ <DialogDescription>
128
+ {t(
129
+ "Register a new business premise with FURS. After registration, you'll need to manually add at least one electronic device for this premise.",
130
+ )}
131
+ </DialogDescription>
132
+ </DialogHeader>
133
+
134
+ {isRealEstate ? (
135
+ <Form {...realEstateForm}>
136
+ <form onSubmit={realEstateForm.handleSubmit(handleRealEstateSubmit)} className="space-y-4">
137
+ {/* Premise Name */}
138
+ <FormField
139
+ control={realEstateForm.control}
140
+ name="business_premise_name"
141
+ render={({ field }) => (
142
+ <FormItem>
143
+ <FormLabel>{t("Premise Name")}</FormLabel>
144
+ <FormControl>
145
+ <Input placeholder="P1" {...field} />
146
+ </FormControl>
147
+ <FormDescription>{t("Unique identifier for this premise (e.g., P1, P2)")}</FormDescription>
148
+ <FormMessage />
149
+ </FormItem>
150
+ )}
151
+ />
152
+
153
+ {/* Property Information */}
154
+ <FormField
155
+ control={realEstateForm.control}
156
+ name="real_estate.cadastral_number"
157
+ render={({ field }) => (
158
+ <FormItem>
159
+ <FormLabel>{t("Cadastral Number")} *</FormLabel>
160
+ <FormControl>
161
+ <Input type="text" inputMode="numeric" pattern="[0-9]*" placeholder="123" {...field} />
162
+ </FormControl>
163
+ <FormDescription>{t("Required by FURS (must be numeric)")}</FormDescription>
164
+ <FormMessage />
165
+ </FormItem>
166
+ )}
167
+ />
168
+
169
+ {/* Building Information */}
170
+ <div className="grid grid-cols-2 gap-4">
171
+ <FormField
172
+ control={realEstateForm.control}
173
+ name="real_estate.building_number"
174
+ render={({ field }) => (
175
+ <FormItem>
176
+ <FormLabel>{t("Building Number")}</FormLabel>
177
+ <FormControl>
178
+ <Input
179
+ type="text"
180
+ inputMode="numeric"
181
+ pattern="[0-9]*"
182
+ placeholder="456"
183
+ {...field}
184
+ value={field.value || ""}
185
+ />
186
+ </FormControl>
187
+ <FormDescription className="text-xs">{t("Must be numeric (optional)")}</FormDescription>
188
+ <FormMessage />
189
+ </FormItem>
190
+ )}
191
+ />
192
+ <FormField
193
+ control={realEstateForm.control}
194
+ name="real_estate.building_section"
195
+ render={({ field }) => (
196
+ <FormItem>
197
+ <FormLabel>{t("Building Section")}</FormLabel>
198
+ <FormControl>
199
+ <Input
200
+ type="text"
201
+ inputMode="numeric"
202
+ pattern="[0-9]*"
203
+ placeholder="1"
204
+ {...field}
205
+ value={field.value || ""}
206
+ />
207
+ </FormControl>
208
+ <FormDescription className="text-xs">{t("Must be numeric (optional)")}</FormDescription>
209
+ <FormMessage />
210
+ </FormItem>
211
+ )}
212
+ />
213
+ </div>
214
+
215
+ {/* Community */}
216
+ <FormField
217
+ control={realEstateForm.control}
218
+ name="real_estate.community"
219
+ render={({ field }) => (
220
+ <FormItem>
221
+ <FormLabel>{t("Community")} *</FormLabel>
222
+ <FormControl>
223
+ <Input placeholder="Ljubljana" {...field} />
224
+ </FormControl>
225
+ <FormDescription>{t("Slovenian administrative community (občina) name")}</FormDescription>
226
+ <FormMessage />
227
+ </FormItem>
228
+ )}
229
+ />
230
+
231
+ {/* Address */}
232
+ <FormField
233
+ control={realEstateForm.control}
234
+ name="real_estate.street"
235
+ render={({ field }) => (
236
+ <FormItem>
237
+ <FormLabel>{t("Street")}</FormLabel>
238
+ <FormControl>
239
+ <Input placeholder="Dunajska cesta" {...field} value={field.value || ""} />
240
+ </FormControl>
241
+ <FormMessage />
242
+ </FormItem>
243
+ )}
244
+ />
245
+
246
+ <div className="grid grid-cols-2 gap-4">
247
+ <FormField
248
+ control={realEstateForm.control}
249
+ name="real_estate.house_number"
250
+ render={({ field }) => (
251
+ <FormItem>
252
+ <FormLabel>{t("House Number")}</FormLabel>
253
+ <FormControl>
254
+ <Input placeholder="22" {...field} value={field.value || ""} />
255
+ </FormControl>
256
+ <FormMessage />
257
+ </FormItem>
258
+ )}
259
+ />
260
+ <FormField
261
+ control={realEstateForm.control}
262
+ name="real_estate.house_number_additional"
263
+ render={({ field }) => (
264
+ <FormItem>
265
+ <FormLabel>{t("Additional")}</FormLabel>
266
+ <FormControl>
267
+ <Input placeholder="A" {...field} value={field.value || ""} />
268
+ </FormControl>
269
+ <FormMessage />
270
+ </FormItem>
271
+ )}
272
+ />
273
+ </div>
274
+
275
+ <div className="grid grid-cols-2 gap-4">
276
+ <FormField
277
+ control={realEstateForm.control}
278
+ name="real_estate.city"
279
+ render={({ field }) => (
280
+ <FormItem>
281
+ <FormLabel>{t("City")}</FormLabel>
282
+ <FormControl>
283
+ <Input placeholder="Ljubljana" {...field} value={field.value || ""} />
284
+ </FormControl>
285
+ <FormMessage />
286
+ </FormItem>
287
+ )}
288
+ />
289
+ <FormField
290
+ control={realEstateForm.control}
291
+ name="real_estate.postal_code"
292
+ render={({ field }) => (
293
+ <FormItem>
294
+ <FormLabel>{t("Postal Code")}</FormLabel>
295
+ <FormControl>
296
+ <Input placeholder="1000" {...field} value={field.value || ""} />
297
+ </FormControl>
298
+ <FormMessage />
299
+ </FormItem>
300
+ )}
301
+ />
302
+ </div>
303
+
304
+ {/* Numbering Strategy */}
305
+ <FormField
306
+ control={realEstateForm.control}
307
+ name="numbering_strategy"
308
+ render={({ field }) => (
309
+ <FormItem>
310
+ <FormLabel>{t("Numbering Strategy")}</FormLabel>
311
+ <Select onValueChange={field.onChange} value={field.value}>
312
+ <FormControl>
313
+ <SelectTrigger>
314
+ <SelectValue placeholder="Select strategy" />
315
+ </SelectTrigger>
316
+ </FormControl>
317
+ <SelectContent>
318
+ <SelectItem value="B">{t("Strategy B (Device-level)")}</SelectItem>
319
+ <SelectItem value="C">{t("Strategy C (Centralized)")}</SelectItem>
320
+ </SelectContent>
321
+ </Select>
322
+ <FormDescription>{t("Choose how invoice numbers are generated")}</FormDescription>
323
+ <FormMessage />
324
+ </FormItem>
325
+ )}
326
+ />
327
+
328
+ <DialogFooter>
329
+ <Button type="button" variant="outline" onClick={() => onOpenChange(false)} disabled={isPending}>
330
+ {t("Cancel")}
331
+ </Button>
332
+ <Button type="submit" disabled={isPending}>
333
+ {isPending ? t("Registering...") : t("Register Premise")}
334
+ </Button>
335
+ </DialogFooter>
336
+ </form>
337
+ </Form>
338
+ ) : (
339
+ <Form {...movableForm}>
340
+ <form onSubmit={movableForm.handleSubmit(handleMovableSubmit)} className="space-y-4">
341
+ {/* Premise Name */}
342
+ <FormField
343
+ control={movableForm.control}
344
+ name="business_premise_name"
345
+ render={({ field }) => (
346
+ <FormItem>
347
+ <FormLabel>{t("Premise Name")}</FormLabel>
348
+ <FormControl>
349
+ <Input placeholder="P1" {...field} />
350
+ </FormControl>
351
+ <FormDescription>{t("Unique identifier for this premise (e.g., P1, P2)")}</FormDescription>
352
+ <FormMessage />
353
+ </FormItem>
354
+ )}
355
+ />
356
+
357
+ {/* Premise Type */}
358
+ <FormField
359
+ control={movableForm.control}
360
+ name="movable_premise.premise_type"
361
+ render={({ field }) => (
362
+ <FormItem>
363
+ <FormLabel>{t("Premise Type")}</FormLabel>
364
+ <Select onValueChange={field.onChange} value={field.value}>
365
+ <FormControl>
366
+ <SelectTrigger>
367
+ <SelectValue placeholder="Select type" />
368
+ </SelectTrigger>
369
+ </FormControl>
370
+ <SelectContent>
371
+ <SelectItem value="A">{t("A - Vehicle")}</SelectItem>
372
+ <SelectItem value="B">{t("B - Object at Market/Fair")}</SelectItem>
373
+ <SelectItem value="C">{t("C - Other Movable")}</SelectItem>
374
+ </SelectContent>
375
+ </Select>
376
+ <FormDescription>{t("Type of movable business premise")}</FormDescription>
377
+ <FormMessage />
378
+ </FormItem>
379
+ )}
380
+ />
381
+
382
+ {/* Numbering Strategy */}
383
+ <FormField
384
+ control={movableForm.control}
385
+ name="numbering_strategy"
386
+ render={({ field }) => (
387
+ <FormItem>
388
+ <FormLabel>{t("Numbering Strategy")}</FormLabel>
389
+ <Select onValueChange={field.onChange} value={field.value}>
390
+ <FormControl>
391
+ <SelectTrigger>
392
+ <SelectValue placeholder="Select strategy" />
393
+ </SelectTrigger>
394
+ </FormControl>
395
+ <SelectContent>
396
+ <SelectItem value="B">{t("Strategy B (Device-level)")}</SelectItem>
397
+ <SelectItem value="C">{t("Strategy C (Centralized)")}</SelectItem>
398
+ </SelectContent>
399
+ </Select>
400
+ <FormDescription>{t("Choose how invoice numbers are generated")}</FormDescription>
401
+ <FormMessage />
402
+ </FormItem>
403
+ )}
404
+ />
405
+
406
+ <DialogFooter>
407
+ <Button type="button" variant="outline" onClick={() => onOpenChange(false)} disabled={isPending}>
408
+ {t("Cancel")}
409
+ </Button>
410
+ <Button type="submit" disabled={isPending}>
411
+ {isPending ? t("Registering...") : t("Register Premise")}
412
+ </Button>
413
+ </DialogFooter>
414
+ </form>
415
+ </Form>
416
+ )}
417
+ </DialogContent>
418
+ </Dialog>
419
+ );
420
+ };
@@ -0,0 +1,2 @@
1
+ export const ACTIVE_ENTITY_COOKIE = "l.ent" as const;
2
+ export const ACTIVE_ENVIRONMENT_COOKIE = "l.env" as const;
@@ -0,0 +1,274 @@
1
+ import { zodResolver } from "@hookform/resolvers/zod";
2
+ import type { Entity } from "@spaceinvoices/js-sdk";
3
+ import { useEffect, useState } from "react";
4
+ import { useForm } from "react-hook-form";
5
+ import { z } from "zod";
6
+ import {
7
+ Form,
8
+ FormControl,
9
+ FormDescription,
10
+ FormField,
11
+ FormItem,
12
+ FormLabel,
13
+ FormMessage,
14
+ } from "@/ui/components/ui/form";
15
+ import { Input } from "@/ui/components/ui/input";
16
+ import type { ComponentTranslationProps } from "@/ui/lib/translation";
17
+ import { createTranslation } from "@/ui/lib/translation";
18
+ import { useFormFooterRegistration } from "@/ui/providers/form-footer-context";
19
+ import { useSDK } from "@/ui/providers/sdk-provider";
20
+ import { useUpdateEntity } from "../entities.hooks";
21
+ import { ImageUploadWithCrop } from "../entity-settings-form/image-upload-with-crop";
22
+ import de from "../entity-settings-form/locales/de";
23
+ import sl from "../entity-settings-form/locales/sl";
24
+
25
+ const translations = { sl, de } as const;
26
+
27
+ const brandingSettingsSchema = z.object({
28
+ primary_color: z
29
+ .union([z.string(), z.null()])
30
+ .refine((val) => !val || /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(val), {
31
+ message: "Must be a valid hex color (e.g., #5c6ac4)",
32
+ })
33
+ .optional(),
34
+ has_logo: z.union([z.boolean(), z.null()]).optional(),
35
+ has_signature: z.union([z.boolean(), z.null()]).optional(),
36
+ });
37
+
38
+ type BrandingSettingsSchema = z.infer<typeof brandingSettingsSchema>;
39
+
40
+ export type BrandingSettingsFormProps = {
41
+ entity: Entity;
42
+ cloudinaryCloudName?: string;
43
+ onSuccess?: (data: Entity) => void;
44
+ onError?: (error: unknown) => void;
45
+ onUploadSuccess?: () => void;
46
+ } & ComponentTranslationProps;
47
+
48
+ export function BrandingSettingsForm({
49
+ entity,
50
+ cloudinaryCloudName,
51
+ t: translateProp,
52
+ namespace,
53
+ locale,
54
+ onSuccess,
55
+ onError,
56
+ onUploadSuccess,
57
+ }: BrandingSettingsFormProps) {
58
+ const t = createTranslation({ t: translateProp, namespace, locale, translations });
59
+ const { sdk } = useSDK();
60
+
61
+ const currentSettings = (entity.settings as any) || {};
62
+
63
+ const [isUploading, setIsUploading] = useState(false);
64
+ const [isUploadingSignature, setIsUploadingSignature] = useState(false);
65
+ const [logoTimestamp, setLogoTimestamp] = useState(Date.now());
66
+ const [signatureTimestamp, setSignatureTimestamp] = useState(Date.now());
67
+ const [uploadedLogoUrl, setUploadedLogoUrl] = useState<string | null>(null);
68
+ const [uploadedSignatureUrl, setUploadedSignatureUrl] = useState<string | null>(null);
69
+ const [fetchedLogoUrl, setFetchedLogoUrl] = useState<string | null>(null);
70
+ const [fetchedSignatureUrl, setFetchedSignatureUrl] = useState<string | null>(null);
71
+
72
+ const form = useForm<BrandingSettingsSchema>({
73
+ resolver: zodResolver(brandingSettingsSchema),
74
+ defaultValues: {
75
+ primary_color: currentSettings.primary_color || null,
76
+ has_logo: currentSettings.has_logo || null,
77
+ has_signature: currentSettings.has_signature || null,
78
+ },
79
+ });
80
+
81
+ // Fetch logo and signature URLs
82
+ useEffect(() => {
83
+ async function fetchFileUrls() {
84
+ try {
85
+ const files = await sdk.files.list({ entity_id: entity.id });
86
+ const logoFile = files.data.find((f) => f.category === "logo");
87
+ const signatureFile = files.data.find((f) => f.category === "signature");
88
+
89
+ if (logoFile) {
90
+ setFetchedLogoUrl(logoFile.secureUrl);
91
+ if (!form.getValues("has_logo")) form.setValue("has_logo", true);
92
+ }
93
+ if (signatureFile) {
94
+ setFetchedSignatureUrl(signatureFile.secureUrl);
95
+ if (!form.getValues("has_signature")) form.setValue("has_signature", true);
96
+ }
97
+ } catch (error) {
98
+ console.error("Failed to fetch file URLs:", error);
99
+ }
100
+ }
101
+ fetchFileUrls();
102
+ }, [entity.id, sdk.files, form]);
103
+
104
+ const hasLogo = form.watch("has_logo");
105
+ const hasSignature = form.watch("has_signature");
106
+
107
+ const entityTimestamp = entity.updated_at ? new Date(entity.updated_at).getTime() : logoTimestamp;
108
+ const logoUrl =
109
+ hasLogo && cloudinaryCloudName
110
+ ? uploadedLogoUrl ||
111
+ fetchedLogoUrl ||
112
+ `https://res.cloudinary.com/${cloudinaryCloudName}/image/upload/leka/entities/${entity.id}/logos/logo_${entity.id}.png?v=${entityTimestamp}`
113
+ : undefined;
114
+
115
+ const signatureEntityTimestamp = entity.updated_at ? new Date(entity.updated_at).getTime() : signatureTimestamp;
116
+ const signatureUrl =
117
+ hasSignature && cloudinaryCloudName
118
+ ? uploadedSignatureUrl ||
119
+ fetchedSignatureUrl ||
120
+ `https://res.cloudinary.com/${cloudinaryCloudName}/image/upload/leka/entities/${entity.id}/signatures/signature_${entity.id}.png?v=${signatureEntityTimestamp}`
121
+ : undefined;
122
+
123
+ const { mutate: updateEntity, isPending } = useUpdateEntity({
124
+ entityId: entity.id,
125
+ onSuccess: (data) => {
126
+ form.reset(form.getValues());
127
+ onSuccess?.(data);
128
+ },
129
+ onError,
130
+ });
131
+
132
+ useFormFooterRegistration({
133
+ formId: "branding-settings-form",
134
+ isPending,
135
+ isDirty: form.formState.isDirty,
136
+ label: t("Save Settings"),
137
+ });
138
+
139
+ const handleImageUpload = async (file: File): Promise<{ secureUrl: string }> => {
140
+ setIsUploading(true);
141
+ try {
142
+ // SDK expects { file: Blob } as first arg, SDKMethodOptions as last
143
+ const result = await sdk.upload.uploadImage({ file }, { entity_id: entity.id });
144
+ form.setValue("has_logo", true);
145
+ setUploadedLogoUrl(result.secureUrl);
146
+ setLogoTimestamp(Date.now());
147
+ onUploadSuccess?.();
148
+ return result;
149
+ } catch (error) {
150
+ console.error("Upload failed:", error);
151
+ onError?.(error);
152
+ throw error;
153
+ } finally {
154
+ setIsUploading(false);
155
+ }
156
+ };
157
+
158
+ const handleSignatureUpload = async (file: File): Promise<{ secureUrl: string }> => {
159
+ setIsUploadingSignature(true);
160
+ try {
161
+ // SDK expects { file, category } as first arg, SDKMethodOptions as last
162
+ const result = await sdk.files.uploadFile({ file, category: "signature" }, { entity_id: entity.id });
163
+ form.setValue("has_signature", true);
164
+ setUploadedSignatureUrl(result.secureUrl);
165
+ setSignatureTimestamp(Date.now());
166
+ onUploadSuccess?.();
167
+ return { secureUrl: result.secureUrl };
168
+ } catch (error) {
169
+ console.error("Signature upload failed:", error);
170
+ onError?.(error);
171
+ throw error;
172
+ } finally {
173
+ setIsUploadingSignature(false);
174
+ }
175
+ };
176
+
177
+ const onSubmit = (values: BrandingSettingsSchema) => {
178
+ updateEntity({
179
+ id: entity.id,
180
+ data: {
181
+ settings: {
182
+ ...currentSettings,
183
+ primary_color: values.primary_color || undefined,
184
+ has_logo: values.has_logo || undefined,
185
+ has_signature: values.has_signature || undefined,
186
+ },
187
+ },
188
+ });
189
+ };
190
+
191
+ return (
192
+ <Form {...form}>
193
+ <form id="branding-settings-form" onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
194
+ <FormField
195
+ control={form.control}
196
+ name="primary_color"
197
+ render={({ field }) => (
198
+ <FormItem className="max-w-xs">
199
+ <FormLabel className="font-medium text-base">{t("Primary Color")}</FormLabel>
200
+ <FormControl>
201
+ <div className="flex items-center gap-3">
202
+ <div className="relative">
203
+ <Input
204
+ type="color"
205
+ {...field}
206
+ value={field.value || "#5c6ac4"}
207
+ className="h-12 w-16 cursor-pointer rounded-md border-2 p-1.5 shadow-sm transition-all hover:border-primary/50 hover:shadow-md"
208
+ />
209
+ </div>
210
+ <Input
211
+ type="text"
212
+ value={field.value || ""}
213
+ onChange={(e) => field.onChange(e.target.value)}
214
+ placeholder="#5c6ac4"
215
+ className="w-32 font-mono text-sm"
216
+ />
217
+ </div>
218
+ </FormControl>
219
+ <FormDescription className="text-xs">{t("Color used in invoices and documents")}</FormDescription>
220
+ <FormMessage />
221
+ </FormItem>
222
+ )}
223
+ />
224
+
225
+ <div className="grid gap-6 border-t pt-6 lg:grid-cols-2">
226
+ <FormField
227
+ control={form.control}
228
+ name="has_logo"
229
+ render={({ field }) => (
230
+ <FormItem>
231
+ <FormLabel className="font-medium text-base">{t("Logo")}</FormLabel>
232
+ <FormControl>
233
+ <ImageUploadWithCrop
234
+ value={logoUrl || ""}
235
+ onChange={(url) => field.onChange(!!url)}
236
+ onUpload={handleImageUpload}
237
+ translate={t}
238
+ isUploading={isUploading}
239
+ />
240
+ </FormControl>
241
+ <FormDescription className="text-xs">{t("Upload your company logo for invoices")}</FormDescription>
242
+ <FormMessage />
243
+ </FormItem>
244
+ )}
245
+ />
246
+
247
+ <FormField
248
+ control={form.control}
249
+ name="has_signature"
250
+ render={({ field }) => (
251
+ <FormItem>
252
+ <FormLabel className="font-medium text-base">{t("Signature")}</FormLabel>
253
+ <FormControl>
254
+ <ImageUploadWithCrop
255
+ value={signatureUrl || ""}
256
+ onChange={(url) => field.onChange(!!url)}
257
+ onUpload={handleSignatureUpload}
258
+ translate={t}
259
+ isUploading={isUploadingSignature}
260
+ imageType="signature"
261
+ />
262
+ </FormControl>
263
+ <FormDescription className="text-xs">
264
+ {t("Upload a signature image for PDFs (optional)")}
265
+ </FormDescription>
266
+ <FormMessage />
267
+ </FormItem>
268
+ )}
269
+ />
270
+ </div>
271
+ </form>
272
+ </Form>
273
+ );
274
+ }