@spaceinvoices/react-ui 0.4.8 → 0.4.11

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 (276) hide show
  1. package/README.md +24 -8
  2. package/cli/dist/index.js +89 -26
  3. package/package.json +4 -1
  4. package/spaceinvoices.schema.json +6 -1
  5. package/src/common/autocomplete.tsx +69 -6
  6. package/src/components/advance-invoices/create/create-advance-invoice-form.tsx +124 -285
  7. package/src/components/advance-invoices/list/list-table.tsx +10 -3
  8. package/src/components/advance-invoices/list/locales/de.ts +2 -0
  9. package/src/components/advance-invoices/list/locales/en.ts +1 -0
  10. package/src/components/advance-invoices/list/locales/es.ts +1 -0
  11. package/src/components/advance-invoices/list/locales/fr.ts +1 -0
  12. package/src/components/advance-invoices/list/locales/hr.ts +1 -0
  13. package/src/components/advance-invoices/list/locales/it.ts +1 -0
  14. package/src/components/advance-invoices/list/locales/nl.ts +1 -0
  15. package/src/components/advance-invoices/list/locales/pl.ts +1 -0
  16. package/src/components/advance-invoices/list/locales/pt.ts +1 -0
  17. package/src/components/advance-invoices/list/locales/sl.ts +1 -0
  18. package/src/components/advance-invoices/list/use-advance-invoice-download.ts +1 -12
  19. package/src/components/credit-notes/create/create-credit-note-form.tsx +116 -238
  20. package/src/components/credit-notes/list/list-table.tsx +6 -3
  21. package/src/components/credit-notes/list/use-credit-note-download.ts +1 -12
  22. package/src/components/customers/customer-autocomplete.tsx +64 -11
  23. package/src/components/customers/customer-list-table/customer-list-table.tsx +3 -2
  24. package/src/components/dashboard/collection-rate-card/collection-rate-card.tsx +9 -1
  25. package/src/components/dashboard/collection-rate-card/locales/bg.ts +3 -0
  26. package/src/components/dashboard/collection-rate-card/locales/cs.ts +3 -0
  27. package/src/components/dashboard/collection-rate-card/locales/et.ts +3 -0
  28. package/src/components/dashboard/collection-rate-card/locales/fi.ts +3 -0
  29. package/src/components/dashboard/collection-rate-card/locales/is.ts +3 -0
  30. package/src/components/dashboard/collection-rate-card/locales/nb.ts +3 -0
  31. package/src/components/dashboard/collection-rate-card/locales/sk.ts +3 -0
  32. package/src/components/dashboard/collection-rate-card/locales/sv.ts +3 -0
  33. package/src/components/dashboard/invoice-status-chart/invoice-status-chart.tsx +10 -2
  34. package/src/components/dashboard/invoice-status-chart/locales/bg.ts +10 -0
  35. package/src/components/dashboard/invoice-status-chart/locales/cs.ts +10 -0
  36. package/src/components/dashboard/invoice-status-chart/locales/de.ts +1 -0
  37. package/src/components/dashboard/invoice-status-chart/locales/es.ts +1 -0
  38. package/src/components/dashboard/invoice-status-chart/locales/et.ts +10 -0
  39. package/src/components/dashboard/invoice-status-chart/locales/fi.ts +10 -0
  40. package/src/components/dashboard/invoice-status-chart/locales/fr.ts +1 -0
  41. package/src/components/dashboard/invoice-status-chart/locales/hr.ts +1 -0
  42. package/src/components/dashboard/invoice-status-chart/locales/is.ts +10 -0
  43. package/src/components/dashboard/invoice-status-chart/locales/it.ts +1 -0
  44. package/src/components/dashboard/invoice-status-chart/locales/nb.ts +10 -0
  45. package/src/components/dashboard/invoice-status-chart/locales/nl.ts +1 -0
  46. package/src/components/dashboard/invoice-status-chart/locales/pl.ts +1 -0
  47. package/src/components/dashboard/invoice-status-chart/locales/pt.ts +1 -0
  48. package/src/components/dashboard/invoice-status-chart/locales/sk.ts +10 -0
  49. package/src/components/dashboard/invoice-status-chart/locales/sl.ts +1 -0
  50. package/src/components/dashboard/invoice-status-chart/locales/sv.ts +10 -0
  51. package/src/components/dashboard/payment-methods-chart/locales/bg.ts +12 -0
  52. package/src/components/dashboard/payment-methods-chart/locales/cs.ts +12 -0
  53. package/src/components/dashboard/payment-methods-chart/locales/et.ts +12 -0
  54. package/src/components/dashboard/payment-methods-chart/locales/fi.ts +12 -0
  55. package/src/components/dashboard/payment-methods-chart/locales/is.ts +12 -0
  56. package/src/components/dashboard/payment-methods-chart/locales/nb.ts +12 -0
  57. package/src/components/dashboard/payment-methods-chart/locales/sk.ts +12 -0
  58. package/src/components/dashboard/payment-methods-chart/locales/sv.ts +12 -0
  59. package/src/components/dashboard/payment-methods-chart/payment-methods-chart.tsx +9 -1
  60. package/src/components/dashboard/payment-trend-chart/locales/bg.ts +6 -0
  61. package/src/components/dashboard/payment-trend-chart/locales/cs.ts +6 -0
  62. package/src/components/dashboard/payment-trend-chart/locales/de.ts +1 -0
  63. package/src/components/dashboard/payment-trend-chart/locales/es.ts +1 -0
  64. package/src/components/dashboard/payment-trend-chart/locales/et.ts +6 -0
  65. package/src/components/dashboard/payment-trend-chart/locales/fi.ts +6 -0
  66. package/src/components/dashboard/payment-trend-chart/locales/fr.ts +1 -0
  67. package/src/components/dashboard/payment-trend-chart/locales/hr.ts +1 -0
  68. package/src/components/dashboard/payment-trend-chart/locales/is.ts +6 -0
  69. package/src/components/dashboard/payment-trend-chart/locales/it.ts +1 -0
  70. package/src/components/dashboard/payment-trend-chart/locales/nb.ts +6 -0
  71. package/src/components/dashboard/payment-trend-chart/locales/nl.ts +1 -0
  72. package/src/components/dashboard/payment-trend-chart/locales/pl.ts +1 -0
  73. package/src/components/dashboard/payment-trend-chart/locales/pt.ts +1 -0
  74. package/src/components/dashboard/payment-trend-chart/locales/sk.ts +6 -0
  75. package/src/components/dashboard/payment-trend-chart/locales/sl.ts +1 -0
  76. package/src/components/dashboard/payment-trend-chart/locales/sv.ts +6 -0
  77. package/src/components/dashboard/payment-trend-chart/payment-trend-chart.tsx +15 -8
  78. package/src/components/dashboard/revenue-trend-chart/locales/bg.ts +6 -0
  79. package/src/components/dashboard/revenue-trend-chart/locales/cs.ts +6 -0
  80. package/src/components/dashboard/revenue-trend-chart/locales/de.ts +1 -0
  81. package/src/components/dashboard/revenue-trend-chart/locales/es.ts +1 -0
  82. package/src/components/dashboard/revenue-trend-chart/locales/et.ts +6 -0
  83. package/src/components/dashboard/revenue-trend-chart/locales/fi.ts +6 -0
  84. package/src/components/dashboard/revenue-trend-chart/locales/fr.ts +1 -0
  85. package/src/components/dashboard/revenue-trend-chart/locales/hr.ts +1 -0
  86. package/src/components/dashboard/revenue-trend-chart/locales/is.ts +6 -0
  87. package/src/components/dashboard/revenue-trend-chart/locales/it.ts +1 -0
  88. package/src/components/dashboard/revenue-trend-chart/locales/nb.ts +6 -0
  89. package/src/components/dashboard/revenue-trend-chart/locales/nl.ts +1 -0
  90. package/src/components/dashboard/revenue-trend-chart/locales/pl.ts +1 -0
  91. package/src/components/dashboard/revenue-trend-chart/locales/pt.ts +1 -0
  92. package/src/components/dashboard/revenue-trend-chart/locales/sk.ts +6 -0
  93. package/src/components/dashboard/revenue-trend-chart/locales/sl.ts +1 -0
  94. package/src/components/dashboard/revenue-trend-chart/locales/sv.ts +6 -0
  95. package/src/components/dashboard/revenue-trend-chart/revenue-trend-chart.tsx +15 -8
  96. package/src/components/dashboard/tax-collected-card/locales.ts +110 -0
  97. package/src/components/dashboard/tax-collected-card/tax-collected-card.tsx +8 -2
  98. package/src/components/dashboard/tax-collected-card/use-tax-collected.ts +4 -4
  99. package/src/components/dashboard/top-customers-chart/locales/bg.ts +7 -0
  100. package/src/components/dashboard/top-customers-chart/locales/cs.ts +7 -0
  101. package/src/components/dashboard/top-customers-chart/locales/de.ts +2 -0
  102. package/src/components/dashboard/top-customers-chart/locales/es.ts +2 -0
  103. package/src/components/dashboard/top-customers-chart/locales/et.ts +7 -0
  104. package/src/components/dashboard/top-customers-chart/locales/fi.ts +7 -0
  105. package/src/components/dashboard/top-customers-chart/locales/fr.ts +2 -0
  106. package/src/components/dashboard/top-customers-chart/locales/hr.ts +2 -0
  107. package/src/components/dashboard/top-customers-chart/locales/is.ts +7 -0
  108. package/src/components/dashboard/top-customers-chart/locales/it.ts +2 -0
  109. package/src/components/dashboard/top-customers-chart/locales/nb.ts +7 -0
  110. package/src/components/dashboard/top-customers-chart/locales/nl.ts +2 -0
  111. package/src/components/dashboard/top-customers-chart/locales/pl.ts +2 -0
  112. package/src/components/dashboard/top-customers-chart/locales/pt.ts +2 -0
  113. package/src/components/dashboard/top-customers-chart/locales/sk.ts +7 -0
  114. package/src/components/dashboard/top-customers-chart/locales/sl.ts +2 -0
  115. package/src/components/dashboard/top-customers-chart/locales/sv.ts +7 -0
  116. package/src/components/dashboard/top-customers-chart/top-customers-chart.tsx +23 -12
  117. package/src/components/delivery-notes/create/create-delivery-note-form.tsx +33 -20
  118. package/src/components/delivery-notes/list/list-table.tsx +22 -13
  119. package/src/components/delivery-notes/list/locales/de.ts +2 -0
  120. package/src/components/delivery-notes/list/locales/en.ts +1 -0
  121. package/src/components/delivery-notes/list/locales/es.ts +1 -0
  122. package/src/components/delivery-notes/list/locales/fr.ts +1 -0
  123. package/src/components/delivery-notes/list/locales/hr.ts +1 -0
  124. package/src/components/delivery-notes/list/locales/it.ts +1 -0
  125. package/src/components/delivery-notes/list/locales/nl.ts +1 -0
  126. package/src/components/delivery-notes/list/locales/pl.ts +1 -0
  127. package/src/components/delivery-notes/list/locales/pt.ts +1 -0
  128. package/src/components/delivery-notes/list/locales/sl.ts +1 -0
  129. package/src/components/delivery-notes/list/use-delivery-note-download.ts +1 -12
  130. package/src/components/documents/create/document-add-item-form.tsx +28 -16
  131. package/src/components/documents/create/document-add-item-tax-rate-field.tsx +12 -2
  132. package/src/components/documents/create/document-items-section.tsx +70 -39
  133. package/src/components/documents/create/document-recipient-section.tsx +10 -1
  134. package/src/components/documents/create/live-preview.tsx +113 -15
  135. package/src/components/documents/create/prepare-document-submission.ts +35 -16
  136. package/src/components/documents/create/use-document-customer-form.ts +14 -3
  137. package/src/components/documents/documents.hooks.ts +7 -2
  138. package/src/components/documents/shared/document-preview-display.tsx +136 -67
  139. package/src/components/documents/shared/scaled-document-preview.tsx +45 -5
  140. package/src/components/documents/view/document-actions-bar.tsx +284 -182
  141. package/src/components/documents/view/document-activities-list.tsx +3 -0
  142. package/src/components/documents/view/document-payments-list.tsx +3 -0
  143. package/src/components/documents/view/locales/de.ts +8 -0
  144. package/src/components/documents/view/locales/es.ts +8 -0
  145. package/src/components/documents/view/locales/fr.ts +8 -0
  146. package/src/components/documents/view/locales/hr.ts +8 -0
  147. package/src/components/documents/view/locales/it.ts +8 -0
  148. package/src/components/documents/view/locales/nl.ts +8 -0
  149. package/src/components/documents/view/locales/pl.ts +8 -0
  150. package/src/components/documents/view/locales/pt.ts +8 -0
  151. package/src/components/documents/view/locales/sl.ts +8 -0
  152. package/src/components/documents/view/use-document-download.ts +14 -25
  153. package/src/components/entities/create-entity-form.tsx +101 -16
  154. package/src/components/entities/entity-settings-form/entity-settings-form.tsx +10 -10
  155. package/src/components/entities/entity-settings-form/locales/de.ts +10 -0
  156. package/src/components/entities/entity-settings-form/locales/es.ts +10 -0
  157. package/src/components/entities/entity-settings-form/locales/fr.ts +10 -0
  158. package/src/components/entities/entity-settings-form/locales/hr.ts +10 -0
  159. package/src/components/entities/entity-settings-form/locales/it.ts +10 -0
  160. package/src/components/entities/entity-settings-form/locales/nl.ts +10 -0
  161. package/src/components/entities/entity-settings-form/locales/pl.ts +10 -0
  162. package/src/components/entities/entity-settings-form/locales/pt.ts +10 -0
  163. package/src/components/entities/entity-settings-form/locales/sl.ts +10 -0
  164. package/src/components/entities/fina-settings-form/fina-operator-required-dialog.tsx +3 -3
  165. package/src/components/entities/fina-settings-form/fina-settings-form.tsx +78 -124
  166. package/src/components/entities/fina-settings-form/sections/certificate-settings-section.tsx +8 -1
  167. package/src/components/entities/fina-settings-form/sections/premises-management-section.tsx +14 -2
  168. package/src/components/entities/fina-settings-form/sections/register-premise-dialog.tsx +7 -2
  169. package/src/components/entities/furs-settings-form/furs-settings-form.tsx +56 -130
  170. package/src/components/entities/furs-settings-form/sections/certificate-settings-section.tsx +8 -1
  171. package/src/components/entities/furs-settings-form/sections/enable-fiscalization-section.tsx +1 -0
  172. package/src/components/entities/furs-settings-form/sections/general-settings-section.tsx +15 -2
  173. package/src/components/entities/furs-settings-form/sections/premises-management-section.tsx +20 -3
  174. package/src/components/entities/furs-settings-form/sections/register-premise-dialog.tsx +38 -12
  175. package/src/components/entities/settings/defaults-settings-form.tsx +6 -6
  176. package/src/components/entities/settings/eslog-settings-form.tsx +13 -1
  177. package/src/components/entities/settings/pdf-template-selector/demo-invoice-data.ts +3 -22
  178. package/src/components/entities/shared/fiscalization-step-flow.ts +77 -0
  179. package/src/components/entities/shared/fiscalization-step-tabs.tsx +71 -0
  180. package/src/components/estimates/create/create-estimate-form.tsx +34 -21
  181. package/src/components/estimates/list/list-table.tsx +23 -14
  182. package/src/components/estimates/list/locales/de.ts +2 -0
  183. package/src/components/estimates/list/locales/en.ts +1 -0
  184. package/src/components/estimates/list/locales/es.ts +1 -0
  185. package/src/components/estimates/list/locales/fr.ts +1 -0
  186. package/src/components/estimates/list/locales/hr.ts +1 -0
  187. package/src/components/estimates/list/locales/it.ts +1 -0
  188. package/src/components/estimates/list/locales/nl.ts +1 -0
  189. package/src/components/estimates/list/locales/pl.ts +1 -0
  190. package/src/components/estimates/list/locales/pt.ts +1 -0
  191. package/src/components/estimates/list/locales/sl.ts +1 -0
  192. package/src/components/estimates/list/use-estimate-download.ts +1 -12
  193. package/src/components/export/document-export-form.tsx +33 -7
  194. package/src/components/export/sales-per-item-export-form.tsx +23 -7
  195. package/src/components/invoices/create/create-invoice-form.tsx +295 -329
  196. package/src/components/invoices/create/prepare-invoice-submission.ts +0 -8
  197. package/src/components/invoices/list/list-table.tsx +7 -4
  198. package/src/components/invoices/list/use-invoice-download.ts +1 -11
  199. package/src/components/invoices/send-email-dialog/locales/de.ts +20 -0
  200. package/src/components/invoices/send-email-dialog/locales/es.ts +20 -0
  201. package/src/components/invoices/send-email-dialog/locales/fr.ts +20 -0
  202. package/src/components/invoices/send-email-dialog/locales/hr.ts +20 -0
  203. package/src/components/invoices/send-email-dialog/locales/it.ts +20 -0
  204. package/src/components/invoices/send-email-dialog/locales/nl.ts +20 -0
  205. package/src/components/invoices/send-email-dialog/locales/pl.ts +20 -0
  206. package/src/components/invoices/send-email-dialog/locales/pt.ts +20 -0
  207. package/src/components/invoices/send-email-dialog/locales/sl.ts +20 -0
  208. package/src/components/invoices/send-email-dialog/send-email-dialog.tsx +77 -8
  209. package/src/components/invoices/view/eslog-info-display.tsx +17 -1
  210. package/src/components/invoices/view/fiscalization-status-card.tsx +7 -3
  211. package/src/components/items/item-combobox.tsx +26 -6
  212. package/src/components/items/item-list-table/item-list-table.tsx +5 -2
  213. package/src/components/payments/create-payment-form/index.ts +1 -0
  214. package/src/components/payments/list/list-table.tsx +14 -4
  215. package/src/components/recurring-invoices/list/list-table.tsx +7 -4
  216. package/src/components/request-logs/locales.ts +412 -0
  217. package/src/components/request-logs/request-log-detail.tsx +37 -21
  218. package/src/components/request-logs/request-log-list-table.tsx +57 -11
  219. package/src/components/table/data-table.tsx +5 -2
  220. package/src/components/table/date-cell.tsx +3 -1
  221. package/src/components/table/filter-bar.tsx +14 -2
  222. package/src/components/table/hooks/use-table-query.ts +1 -1
  223. package/src/components/table/locales.ts +1116 -0
  224. package/src/components/table/search-input.tsx +12 -3
  225. package/src/components/table/selection-toolbar.tsx +23 -6
  226. package/src/components/table/table-empty-state.tsx +43 -3
  227. package/src/components/table/table-no-results.tsx +3 -3
  228. package/src/components/table/table-pagination.tsx +4 -3
  229. package/src/components/table/types.ts +1 -0
  230. package/src/components/tax-reports/index.ts +1 -0
  231. package/src/components/tax-reports/kir-export-form.tsx +46 -8
  232. package/src/components/tax-reports/slovenia-tax-profile-step.tsx +191 -0
  233. package/src/components/tax-reports/slovenia-yearly-export-form.tsx +509 -0
  234. package/src/components/tax-reports/slovenia-yearly-review-step.tsx +253 -0
  235. package/src/components/tax-reports/slovenia-yearly-summary.tsx +19 -0
  236. package/src/components/taxes/tax-list-table/tax-list-table.tsx +3 -2
  237. package/src/components/ui/sidebar.tsx +3 -2
  238. package/src/components/ui/sticky-form-footer.tsx +7 -1
  239. package/src/components/webhook-logs/index.ts +6 -0
  240. package/src/components/webhook-logs/locales.ts +392 -0
  241. package/src/components/webhook-logs/webhook-delivery-detail.tsx +255 -0
  242. package/src/components/webhook-logs/webhook-delivery-list-table.tsx +278 -0
  243. package/src/components/wl-subscription/index.ts +1 -0
  244. package/src/components/wl-subscription/locked-feature.tsx +1 -0
  245. package/src/components/wl-subscription/paywall.tsx +193 -0
  246. package/src/components/wl-subscription/upgrade-modal.tsx +93 -29
  247. package/src/generate-schemas.ts +12 -7
  248. package/src/generated/schemas/customer.ts +2 -0
  249. package/src/generated/schemas/entity.ts +134 -0
  250. package/src/generated/schemas/exportsloveniayearlynormiranireport_body.ts +27 -0
  251. package/src/generated/schemas/index.ts +2 -0
  252. package/src/generated/schemas/me.ts +20 -1
  253. package/src/generated/schemas/renderadvanceinvoicepreview_body.ts +40 -34
  254. package/src/generated/schemas/rendercreditnotepreview_body.ts +42 -36
  255. package/src/generated/schemas/renderdeliverynotepreview_body.ts +23 -13
  256. package/src/generated/schemas/renderestimatepreview_body.ts +23 -13
  257. package/src/generated/schemas/renderinvoicepreview_body.ts +40 -34
  258. package/src/generated/schemas/sendemail_body.ts +44 -0
  259. package/src/generated/schemas/sloveniataxprofile.ts +42 -0
  260. package/src/generated/schemas/startpdfexport_body.ts +91 -1
  261. package/src/generated/schemas/webhook.ts +10 -0
  262. package/src/hooks/use-duplicate-document.ts +51 -13
  263. package/src/hooks/use-eslog-validation.ts +59 -0
  264. package/src/hooks/use-premise-selection.ts +186 -0
  265. package/src/lib/browser-cookies.ts +4 -4
  266. package/src/lib/date-fns-locale.ts +48 -0
  267. package/src/lib/fiscalization-options.ts +81 -0
  268. package/src/lib/locale.ts +38 -0
  269. package/src/lib/template-variables.tsx +1 -1
  270. package/src/lib/translation.ts +14 -3
  271. package/src/providers/entities-context.tsx +1 -0
  272. package/src/providers/entities-provider.tsx +102 -3
  273. package/src/providers/form-footer-context.tsx +37 -4
  274. package/src/providers/sdk-provider.tsx +7 -2
  275. package/src/providers/white-label-provider.tsx +4 -1
  276. package/src/providers/wl-subscription-provider.tsx +90 -3
@@ -26,6 +26,10 @@ type EntitiesProviderProps = {
26
26
  cookieDomain?: string;
27
27
  /** When provided (from URL param), this entity ID is used as the source of truth instead of the cookie */
28
28
  urlEntityId?: string;
29
+ /** When provided, determines environment from URL instead of cookie. true = sandbox, false = live. */
30
+ isSandbox?: boolean;
31
+ /** Called when urlEntityId is not found in any environment, allowing parent to try other accounts */
32
+ onEntityNotFound?: (entityId: string) => boolean | void | Promise<boolean | void>;
29
33
  };
30
34
 
31
35
  export function EntitiesProvider({
@@ -34,6 +38,8 @@ export function EntitiesProvider({
34
38
  onNoEntities,
35
39
  cookieDomain,
36
40
  urlEntityId,
41
+ isSandbox,
42
+ onEntityNotFound,
37
43
  }: EntitiesProviderProps) {
38
44
  const { sdk, isInitialized } = useSDK();
39
45
  const [cookies, setCookie, removeCookie] = useCookies([ACTIVE_ENTITY_COOKIE, ACTIVE_ENVIRONMENT_COOKIE]);
@@ -44,12 +50,24 @@ export function EntitiesProvider({
44
50
  // URL entity ID takes precedence over cookie
45
51
  const resolvedEntityId = urlEntityId ?? initialEntityIdFromCookie;
46
52
 
53
+ // When isSandbox is provided (URL-driven), use it as source of truth; otherwise fall back to cookie
47
54
  const resolvedInitialEnvironment: EntityEnvironment =
48
- initialEnvironmentFromCookie ?? (initialActiveEntity?.environment as EntityEnvironment | undefined) ?? "live";
55
+ isSandbox !== undefined
56
+ ? isSandbox
57
+ ? "sandbox"
58
+ : "live"
59
+ : (initialEnvironmentFromCookie ?? (initialActiveEntity?.environment as EntityEnvironment | undefined) ?? "live");
49
60
 
50
61
  const [environment, setEnvironmentState] = useState<EntityEnvironment>(resolvedInitialEnvironment);
51
62
  const previousEnvironment = useRef(environment);
52
63
 
64
+ // Sync environment with isSandbox prop when it changes (URL-driven navigation)
65
+ useEffect(() => {
66
+ if (isSandbox === undefined) return;
67
+ const target: EntityEnvironment = isSandbox ? "sandbox" : "live";
68
+ setEnvironmentState((current) => (current === target ? current : target));
69
+ }, [isSandbox]);
70
+
53
71
  // Store the initial entity ID (from URL or cookie) so we can match it when entities load
54
72
  const initialEntityIdRef = useRef(resolvedEntityId);
55
73
 
@@ -65,6 +83,7 @@ export function EntitiesProvider({
65
83
  const {
66
84
  data: entities = [],
67
85
  isLoading,
86
+ isFetching,
68
87
  refetch,
69
88
  isError,
70
89
  error,
@@ -88,10 +107,50 @@ export function EntitiesProvider({
88
107
  });
89
108
 
90
109
  // When no entities in current environment, check the other before giving up
110
+ // Skip auto-switch when isSandbox is explicitly set (URL-driven) — respect the user's intent
91
111
  const hasCalledNoEntities = useRef(false);
92
112
  const hasTriedFallback = useRef(false);
113
+ const resolvingUrlEntityRef = useRef<string | null>(null);
114
+ const emptyUrlEntityRefetchKeyRef = useRef<string | null>(null);
93
115
  useEffect(() => {
94
- if (isLoading || entities.length > 0 || hasCalledNoEntities.current) return;
116
+ if (isLoading || isFetching || entities.length > 0 || hasCalledNoEntities.current) return;
117
+
118
+ if (urlEntityId) {
119
+ const refetchKey = `${urlEntityId}:${environment}`;
120
+ if (emptyUrlEntityRefetchKeyRef.current !== refetchKey) {
121
+ emptyUrlEntityRefetchKeyRef.current = refetchKey;
122
+ void refetch();
123
+ return;
124
+ }
125
+
126
+ if (resolvingUrlEntityRef.current === urlEntityId) return;
127
+ resolvingUrlEntityRef.current = urlEntityId;
128
+
129
+ if (!onEntityNotFound) {
130
+ hasCalledNoEntities.current = true;
131
+ onNoEntities?.();
132
+ return;
133
+ }
134
+
135
+ void Promise.resolve(onEntityNotFound(urlEntityId))
136
+ .then((resolved) => {
137
+ if (resolved) return;
138
+ hasCalledNoEntities.current = true;
139
+ onNoEntities?.();
140
+ })
141
+ .catch(() => {
142
+ hasCalledNoEntities.current = true;
143
+ onNoEntities?.();
144
+ });
145
+ return;
146
+ }
147
+
148
+ // When environment is explicitly set via URL, don't auto-switch to the other environment
149
+ if (isSandbox !== undefined) {
150
+ hasCalledNoEntities.current = true;
151
+ onNoEntities?.();
152
+ return;
153
+ }
95
154
 
96
155
  // Try the other environment before calling onNoEntities
97
156
  if (!hasTriedFallback.current && sdk) {
@@ -118,7 +177,18 @@ export function EntitiesProvider({
118
177
  if (!hasTriedFallback.current) return;
119
178
  hasCalledNoEntities.current = true;
120
179
  onNoEntities?.();
121
- }, [isLoading, entities.length, onNoEntities, sdk, environment]);
180
+ }, [
181
+ isLoading,
182
+ isFetching,
183
+ entities.length,
184
+ onEntityNotFound,
185
+ onNoEntities,
186
+ refetch,
187
+ sdk,
188
+ environment,
189
+ isSandbox,
190
+ urlEntityId,
191
+ ]);
122
192
 
123
193
  // Memoize entities to prevent unnecessary re-renders
124
194
  const memoizedEntities = useMemo(() => entities, [entities]);
@@ -130,6 +200,13 @@ export function EntitiesProvider({
130
200
  }
131
201
  }, [environment]);
132
202
 
203
+ useEffect(() => {
204
+ if (entities.length > 0) {
205
+ emptyUrlEntityRefetchKeyRef.current = null;
206
+ resolvingUrlEntityRef.current = null;
207
+ }
208
+ }, [entities.length]);
209
+
133
210
  // Sync active entity when entities list changes
134
211
  // Use ref to read current activeEntityState without causing re-runs
135
212
  const activeEntityRef = useRef(activeEntityState);
@@ -139,6 +216,8 @@ export function EntitiesProvider({
139
216
  useEffect(() => {
140
217
  if (urlEntityId) {
141
218
  initialEntityIdRef.current = urlEntityId;
219
+ resolvingUrlEntityRef.current = null;
220
+ emptyUrlEntityRefetchKeyRef.current = null;
142
221
  }
143
222
  }, [urlEntityId]);
144
223
 
@@ -201,6 +280,26 @@ export function EntitiesProvider({
201
280
  setEnvironmentState(altEnv);
202
281
  }, [urlEntityId, memoizedEntities, isLoading, environment]);
203
282
 
283
+ // After environment fallback has been attempted and entity is still not found, notify parent
284
+ const entityNotFoundCallbackFired = useRef<string | null>(null);
285
+ useEffect(() => {
286
+ if (!urlEntityId || !onEntityNotFound || isLoading) return;
287
+ if (memoizedEntities.length === 0) return;
288
+
289
+ const found = memoizedEntities.some((e) => e.id === urlEntityId);
290
+ if (found) {
291
+ entityNotFoundCallbackFired.current = null;
292
+ return;
293
+ }
294
+
295
+ // Only fire after the environment fallback has already been attempted for this entity
296
+ if (urlEntityFallbackAttempted.current !== urlEntityId) return;
297
+ if (entityNotFoundCallbackFired.current === urlEntityId) return;
298
+
299
+ entityNotFoundCallbackFired.current = urlEntityId;
300
+ onEntityNotFound(urlEntityId);
301
+ }, [urlEntityId, memoizedEntities, isLoading, onEntityNotFound]);
302
+
204
303
  const cookieOpts = useMemo(
205
304
  () => ({
206
305
  path: "/",
@@ -1,4 +1,4 @@
1
- import { createContext, type ReactNode, useContext, useEffect, useState } from "react";
1
+ import { createContext, type ReactNode, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
2
2
 
3
3
  type SecondaryAction = {
4
4
  label: string;
@@ -54,6 +54,37 @@ export function useFormFooterRegistration({
54
54
  secondaryAction,
55
55
  }: UseFormFooterRegistrationProps) {
56
56
  const { setFormFooter } = useFormFooterContext();
57
+ const onSubmitRef = useRef(onSubmit);
58
+ const secondaryActionRef = useRef(secondaryAction);
59
+
60
+ useEffect(() => {
61
+ onSubmitRef.current = onSubmit;
62
+ }, [onSubmit]);
63
+
64
+ useEffect(() => {
65
+ secondaryActionRef.current = secondaryAction;
66
+ }, [secondaryAction]);
67
+
68
+ const stableOnSubmit = useCallback(() => {
69
+ onSubmitRef.current?.();
70
+ }, []);
71
+
72
+ const hasSecondaryAction = !!secondaryAction;
73
+ const secondaryActionLabel = secondaryAction?.label;
74
+ const secondaryActionPending = secondaryAction?.isPending;
75
+ const stableSecondaryActionOnClick = useCallback(() => {
76
+ secondaryActionRef.current?.onClick();
77
+ }, []);
78
+
79
+ const stableSecondaryAction = useMemo(() => {
80
+ if (!hasSecondaryAction || !secondaryActionLabel) return undefined;
81
+
82
+ return {
83
+ label: secondaryActionLabel,
84
+ isPending: secondaryActionPending,
85
+ onClick: stableSecondaryActionOnClick,
86
+ };
87
+ }, [hasSecondaryAction, secondaryActionLabel, secondaryActionPending, stableSecondaryActionOnClick]);
57
88
 
58
89
  useEffect(() => {
59
90
  setFormFooter({
@@ -61,12 +92,14 @@ export function useFormFooterRegistration({
61
92
  isPending,
62
93
  isDirty,
63
94
  label,
64
- onSubmit,
65
- secondaryAction,
95
+ onSubmit: onSubmit ? stableOnSubmit : undefined,
96
+ secondaryAction: stableSecondaryAction,
66
97
  });
98
+ }, [formId, isPending, isDirty, label, onSubmit, setFormFooter, stableOnSubmit, stableSecondaryAction]);
67
99
 
100
+ useEffect(() => {
68
101
  return () => {
69
102
  setFormFooter(null);
70
103
  };
71
- }, [formId, isPending, isDirty, label, onSubmit, secondaryAction, setFormFooter]);
104
+ }, [setFormFooter]);
72
105
  }
@@ -16,6 +16,8 @@ export type SDKContextType = {
16
16
  isLoading: boolean;
17
17
  error: Error | null;
18
18
  reinitialize: () => Promise<void>;
19
+ /** Optional explicit access token (used by embed mode where cookies aren't available) */
20
+ accessToken?: string | null;
19
21
  };
20
22
 
21
23
  /**
@@ -154,13 +156,16 @@ export function useSDKOptional() {
154
156
 
155
157
  /**
156
158
  * Get access token from SDK context (helper for WLSubscriptionProvider)
159
+ * Checks context.accessToken first (set by embed mode), then falls back to cookies
157
160
  */
158
161
  export function useAccessToken(): string | null {
159
162
  const context = useContext(SDKContext);
160
163
  if (!context?.sdk) return null;
161
164
 
162
- // Access token is stored in SDK configuration
163
- // We need to get it from the auth cookie since SDK doesn't expose it directly
165
+ // Embed mode passes token explicitly via context
166
+ if (context.accessToken) return context.accessToken;
167
+
168
+ // Normal mode: read from auth cookie
164
169
  const token = getCookie(AUTH_COOKIES.TOKEN);
165
170
  return token ?? null;
166
171
  }
@@ -1,3 +1,4 @@
1
+ import { getClientHeaders } from "@spaceinvoices/js-sdk";
1
2
  import type { ReactNode } from "react";
2
3
  import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
3
4
 
@@ -44,7 +45,9 @@ export function WhiteLabelProvider({ children, apiBaseUrl = "" }: WhiteLabelProv
44
45
  useEffect(() => {
45
46
  const fetchConfig = async () => {
46
47
  try {
47
- const response = await fetch(`${apiBaseUrl}/white-labels/current`);
48
+ const response = await fetch(`${apiBaseUrl}/white-labels/current`, {
49
+ headers: apiBaseUrl ? getClientHeaders("ui") : undefined,
50
+ });
48
51
  if (response.ok) {
49
52
  const data = await response.json();
50
53
  // Map snake_case API response to camelCase internal state
@@ -1,3 +1,4 @@
1
+ import { getClientHeaders } from "@spaceinvoices/js-sdk";
1
2
  import type { ReactNode } from "react";
2
3
  import { createContext, useCallback, useContext, useEffect, useMemo, useState } from "react";
3
4
  import { useEntitiesOptional } from "./entities-context";
@@ -9,6 +10,8 @@ import { useAccessToken } from "./sdk-provider";
9
10
 
10
11
  export type PlanLimits = {
11
12
  documents_per_month: number | null;
13
+ invoices_per_month: number | null;
14
+ overage_price_cents: number | null;
12
15
  } | null;
13
16
 
14
17
  export type WhiteLabelPlan = {
@@ -26,6 +29,8 @@ export type WhiteLabelPlan = {
26
29
  export type UsageStats = {
27
30
  documents_count: number;
28
31
  documents_limit: number | null;
32
+ invoices_count: number;
33
+ invoices_limit: number | null;
29
34
  period_start: string;
30
35
  period_end: string;
31
36
  };
@@ -36,6 +41,14 @@ export type CurrentSubscription = {
36
41
  billing_interval: string | null;
37
42
  current_period_start: string;
38
43
  current_period_end: string;
44
+ trial_ends_at: string | null;
45
+ trial_days_remaining: number | null;
46
+ cancel_at: string | null;
47
+ payment_method: {
48
+ last4: string | null;
49
+ brand: string | null;
50
+ has_card: boolean;
51
+ } | null;
39
52
  usage: UsageStats;
40
53
  };
41
54
 
@@ -49,7 +62,8 @@ export type GatedFeature =
49
62
  | "custom_templates"
50
63
  | "api_access"
51
64
  | "webhooks"
52
- | "priority_support";
65
+ | "priority_support"
66
+ | "e_invoicing";
53
67
 
54
68
  // ============================================
55
69
  // CONTEXT
@@ -63,12 +77,19 @@ type WLSubscriptionContextType = {
63
77
  isLoading: boolean;
64
78
  error: string | null;
65
79
 
80
+ // Trial state
81
+ isTrialActive: boolean;
82
+ isTrialExpired: boolean;
83
+ trialDaysRemaining: number | null;
84
+ needsPayment: boolean;
85
+
66
86
  // Feature/limit checks
67
87
  hasFeature: (feature: GatedFeature | string) => boolean;
68
88
  isOverLimit: (resource: "documents") => boolean;
69
89
  getUsagePercentage: (resource: "documents") => number;
70
90
 
71
- // Refresh data
91
+ // Actions
92
+ createCheckout: (planSlug: string, billingInterval?: "monthly" | "yearly") => Promise<string>;
72
93
  refresh: () => Promise<void>;
73
94
  };
74
95
 
@@ -97,9 +118,19 @@ const DEFAULT_SUBSCRIPTION: CurrentSubscription = {
97
118
  billing_interval: null,
98
119
  current_period_start: new Date().toISOString(),
99
120
  current_period_end: new Date().toISOString(),
121
+ trial_ends_at: null,
122
+ trial_days_remaining: null,
123
+ cancel_at: null,
124
+ payment_method: {
125
+ last4: null,
126
+ brand: null,
127
+ has_card: false,
128
+ },
100
129
  usage: {
101
130
  documents_count: 0,
102
131
  documents_limit: null,
132
+ invoices_count: 0,
133
+ invoices_limit: null,
103
134
  period_start: new Date().toISOString(),
104
135
  period_end: new Date().toISOString(),
105
136
  },
@@ -146,6 +177,7 @@ export function WLSubscriptionProvider({ children, apiBaseUrl }: WLSubscriptionP
146
177
  const headers = {
147
178
  Authorization: `Bearer ${accessToken}`,
148
179
  "x-entity-id": entityId,
180
+ ...getClientHeaders("ui"),
149
181
  "Content-Type": "application/json",
150
182
  };
151
183
 
@@ -232,6 +264,42 @@ export function WLSubscriptionProvider({ children, apiBaseUrl }: WLSubscriptionP
232
264
  [subscription],
233
265
  );
234
266
 
267
+ // Return the in-app billing page URL for plan activation.
268
+ const createCheckout = useCallback(
269
+ async (planSlug: string, billingInterval: "monthly" | "yearly" = "monthly"): Promise<string> => {
270
+ if (!entityId || !accessToken) {
271
+ throw new Error("Not authenticated");
272
+ }
273
+
274
+ const url = new URL(`/app/${entityId}/settings/billing`, window.location.origin);
275
+ url.searchParams.set("plan", planSlug);
276
+ url.searchParams.set("interval", billingInterval);
277
+ return url.toString();
278
+ },
279
+ [entityId, accessToken],
280
+ );
281
+
282
+ // Compute trial state
283
+ const isTrialActive =
284
+ subscription.status === "trialing" &&
285
+ subscription.trial_ends_at != null &&
286
+ new Date(subscription.trial_ends_at) > new Date();
287
+
288
+ const isTrialExpiredState =
289
+ subscription.status === "trialing" &&
290
+ subscription.trial_ends_at != null &&
291
+ new Date(subscription.trial_ends_at) <= new Date();
292
+
293
+ const trialDaysRemaining = subscription.trial_days_remaining;
294
+
295
+ // needsPayment: trial expired, or no free plan and no active Stripe subscription
296
+ const needsPayment =
297
+ isTrialExpiredState ||
298
+ (subscription.status === "active" &&
299
+ !subscription.plan.is_free &&
300
+ subscription.billing_interval === null &&
301
+ subscription.plan.slug !== "unlimited");
302
+
235
303
  const value = useMemo(
236
304
  () => ({
237
305
  subscription,
@@ -240,12 +308,31 @@ export function WLSubscriptionProvider({ children, apiBaseUrl }: WLSubscriptionP
240
308
  availablePlans,
241
309
  isLoading,
242
310
  error,
311
+ isTrialActive,
312
+ isTrialExpired: isTrialExpiredState,
313
+ trialDaysRemaining,
314
+ needsPayment,
243
315
  hasFeature,
244
316
  isOverLimit,
245
317
  getUsagePercentage,
318
+ createCheckout,
246
319
  refresh: fetchSubscription,
247
320
  }),
248
- [subscription, availablePlans, isLoading, error, hasFeature, isOverLimit, getUsagePercentage, fetchSubscription],
321
+ [
322
+ subscription,
323
+ availablePlans,
324
+ isLoading,
325
+ error,
326
+ isTrialActive,
327
+ isTrialExpiredState,
328
+ trialDaysRemaining,
329
+ needsPayment,
330
+ hasFeature,
331
+ isOverLimit,
332
+ getUsagePercentage,
333
+ createCheckout,
334
+ fetchSubscription,
335
+ ],
249
336
  );
250
337
 
251
338
  return <WLSubscriptionContext.Provider value={value}>{children}</WLSubscriptionContext.Provider>;