@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.
- package/README.md +24 -8
- package/cli/dist/index.js +89 -26
- package/package.json +4 -1
- package/spaceinvoices.schema.json +6 -1
- package/src/common/autocomplete.tsx +69 -6
- package/src/components/advance-invoices/create/create-advance-invoice-form.tsx +124 -285
- package/src/components/advance-invoices/list/list-table.tsx +10 -3
- package/src/components/advance-invoices/list/locales/de.ts +2 -0
- package/src/components/advance-invoices/list/locales/en.ts +1 -0
- package/src/components/advance-invoices/list/locales/es.ts +1 -0
- package/src/components/advance-invoices/list/locales/fr.ts +1 -0
- package/src/components/advance-invoices/list/locales/hr.ts +1 -0
- package/src/components/advance-invoices/list/locales/it.ts +1 -0
- package/src/components/advance-invoices/list/locales/nl.ts +1 -0
- package/src/components/advance-invoices/list/locales/pl.ts +1 -0
- package/src/components/advance-invoices/list/locales/pt.ts +1 -0
- package/src/components/advance-invoices/list/locales/sl.ts +1 -0
- package/src/components/advance-invoices/list/use-advance-invoice-download.ts +1 -12
- package/src/components/credit-notes/create/create-credit-note-form.tsx +116 -238
- package/src/components/credit-notes/list/list-table.tsx +6 -3
- package/src/components/credit-notes/list/use-credit-note-download.ts +1 -12
- package/src/components/customers/customer-autocomplete.tsx +64 -11
- package/src/components/customers/customer-list-table/customer-list-table.tsx +3 -2
- package/src/components/dashboard/collection-rate-card/collection-rate-card.tsx +9 -1
- package/src/components/dashboard/collection-rate-card/locales/bg.ts +3 -0
- package/src/components/dashboard/collection-rate-card/locales/cs.ts +3 -0
- package/src/components/dashboard/collection-rate-card/locales/et.ts +3 -0
- package/src/components/dashboard/collection-rate-card/locales/fi.ts +3 -0
- package/src/components/dashboard/collection-rate-card/locales/is.ts +3 -0
- package/src/components/dashboard/collection-rate-card/locales/nb.ts +3 -0
- package/src/components/dashboard/collection-rate-card/locales/sk.ts +3 -0
- package/src/components/dashboard/collection-rate-card/locales/sv.ts +3 -0
- package/src/components/dashboard/invoice-status-chart/invoice-status-chart.tsx +10 -2
- package/src/components/dashboard/invoice-status-chart/locales/bg.ts +10 -0
- package/src/components/dashboard/invoice-status-chart/locales/cs.ts +10 -0
- package/src/components/dashboard/invoice-status-chart/locales/de.ts +1 -0
- package/src/components/dashboard/invoice-status-chart/locales/es.ts +1 -0
- package/src/components/dashboard/invoice-status-chart/locales/et.ts +10 -0
- package/src/components/dashboard/invoice-status-chart/locales/fi.ts +10 -0
- package/src/components/dashboard/invoice-status-chart/locales/fr.ts +1 -0
- package/src/components/dashboard/invoice-status-chart/locales/hr.ts +1 -0
- package/src/components/dashboard/invoice-status-chart/locales/is.ts +10 -0
- package/src/components/dashboard/invoice-status-chart/locales/it.ts +1 -0
- package/src/components/dashboard/invoice-status-chart/locales/nb.ts +10 -0
- package/src/components/dashboard/invoice-status-chart/locales/nl.ts +1 -0
- package/src/components/dashboard/invoice-status-chart/locales/pl.ts +1 -0
- package/src/components/dashboard/invoice-status-chart/locales/pt.ts +1 -0
- package/src/components/dashboard/invoice-status-chart/locales/sk.ts +10 -0
- package/src/components/dashboard/invoice-status-chart/locales/sl.ts +1 -0
- package/src/components/dashboard/invoice-status-chart/locales/sv.ts +10 -0
- package/src/components/dashboard/payment-methods-chart/locales/bg.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/locales/cs.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/locales/et.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/locales/fi.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/locales/is.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/locales/nb.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/locales/sk.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/locales/sv.ts +12 -0
- package/src/components/dashboard/payment-methods-chart/payment-methods-chart.tsx +9 -1
- package/src/components/dashboard/payment-trend-chart/locales/bg.ts +6 -0
- package/src/components/dashboard/payment-trend-chart/locales/cs.ts +6 -0
- package/src/components/dashboard/payment-trend-chart/locales/de.ts +1 -0
- package/src/components/dashboard/payment-trend-chart/locales/es.ts +1 -0
- package/src/components/dashboard/payment-trend-chart/locales/et.ts +6 -0
- package/src/components/dashboard/payment-trend-chart/locales/fi.ts +6 -0
- package/src/components/dashboard/payment-trend-chart/locales/fr.ts +1 -0
- package/src/components/dashboard/payment-trend-chart/locales/hr.ts +1 -0
- package/src/components/dashboard/payment-trend-chart/locales/is.ts +6 -0
- package/src/components/dashboard/payment-trend-chart/locales/it.ts +1 -0
- package/src/components/dashboard/payment-trend-chart/locales/nb.ts +6 -0
- package/src/components/dashboard/payment-trend-chart/locales/nl.ts +1 -0
- package/src/components/dashboard/payment-trend-chart/locales/pl.ts +1 -0
- package/src/components/dashboard/payment-trend-chart/locales/pt.ts +1 -0
- package/src/components/dashboard/payment-trend-chart/locales/sk.ts +6 -0
- package/src/components/dashboard/payment-trend-chart/locales/sl.ts +1 -0
- package/src/components/dashboard/payment-trend-chart/locales/sv.ts +6 -0
- package/src/components/dashboard/payment-trend-chart/payment-trend-chart.tsx +15 -8
- package/src/components/dashboard/revenue-trend-chart/locales/bg.ts +6 -0
- package/src/components/dashboard/revenue-trend-chart/locales/cs.ts +6 -0
- package/src/components/dashboard/revenue-trend-chart/locales/de.ts +1 -0
- package/src/components/dashboard/revenue-trend-chart/locales/es.ts +1 -0
- package/src/components/dashboard/revenue-trend-chart/locales/et.ts +6 -0
- package/src/components/dashboard/revenue-trend-chart/locales/fi.ts +6 -0
- package/src/components/dashboard/revenue-trend-chart/locales/fr.ts +1 -0
- package/src/components/dashboard/revenue-trend-chart/locales/hr.ts +1 -0
- package/src/components/dashboard/revenue-trend-chart/locales/is.ts +6 -0
- package/src/components/dashboard/revenue-trend-chart/locales/it.ts +1 -0
- package/src/components/dashboard/revenue-trend-chart/locales/nb.ts +6 -0
- package/src/components/dashboard/revenue-trend-chart/locales/nl.ts +1 -0
- package/src/components/dashboard/revenue-trend-chart/locales/pl.ts +1 -0
- package/src/components/dashboard/revenue-trend-chart/locales/pt.ts +1 -0
- package/src/components/dashboard/revenue-trend-chart/locales/sk.ts +6 -0
- package/src/components/dashboard/revenue-trend-chart/locales/sl.ts +1 -0
- package/src/components/dashboard/revenue-trend-chart/locales/sv.ts +6 -0
- package/src/components/dashboard/revenue-trend-chart/revenue-trend-chart.tsx +15 -8
- package/src/components/dashboard/tax-collected-card/locales.ts +110 -0
- package/src/components/dashboard/tax-collected-card/tax-collected-card.tsx +8 -2
- package/src/components/dashboard/tax-collected-card/use-tax-collected.ts +4 -4
- package/src/components/dashboard/top-customers-chart/locales/bg.ts +7 -0
- package/src/components/dashboard/top-customers-chart/locales/cs.ts +7 -0
- package/src/components/dashboard/top-customers-chart/locales/de.ts +2 -0
- package/src/components/dashboard/top-customers-chart/locales/es.ts +2 -0
- package/src/components/dashboard/top-customers-chart/locales/et.ts +7 -0
- package/src/components/dashboard/top-customers-chart/locales/fi.ts +7 -0
- package/src/components/dashboard/top-customers-chart/locales/fr.ts +2 -0
- package/src/components/dashboard/top-customers-chart/locales/hr.ts +2 -0
- package/src/components/dashboard/top-customers-chart/locales/is.ts +7 -0
- package/src/components/dashboard/top-customers-chart/locales/it.ts +2 -0
- package/src/components/dashboard/top-customers-chart/locales/nb.ts +7 -0
- package/src/components/dashboard/top-customers-chart/locales/nl.ts +2 -0
- package/src/components/dashboard/top-customers-chart/locales/pl.ts +2 -0
- package/src/components/dashboard/top-customers-chart/locales/pt.ts +2 -0
- package/src/components/dashboard/top-customers-chart/locales/sk.ts +7 -0
- package/src/components/dashboard/top-customers-chart/locales/sl.ts +2 -0
- package/src/components/dashboard/top-customers-chart/locales/sv.ts +7 -0
- package/src/components/dashboard/top-customers-chart/top-customers-chart.tsx +23 -12
- package/src/components/delivery-notes/create/create-delivery-note-form.tsx +33 -20
- package/src/components/delivery-notes/list/list-table.tsx +22 -13
- package/src/components/delivery-notes/list/locales/de.ts +2 -0
- package/src/components/delivery-notes/list/locales/en.ts +1 -0
- package/src/components/delivery-notes/list/locales/es.ts +1 -0
- package/src/components/delivery-notes/list/locales/fr.ts +1 -0
- package/src/components/delivery-notes/list/locales/hr.ts +1 -0
- package/src/components/delivery-notes/list/locales/it.ts +1 -0
- package/src/components/delivery-notes/list/locales/nl.ts +1 -0
- package/src/components/delivery-notes/list/locales/pl.ts +1 -0
- package/src/components/delivery-notes/list/locales/pt.ts +1 -0
- package/src/components/delivery-notes/list/locales/sl.ts +1 -0
- package/src/components/delivery-notes/list/use-delivery-note-download.ts +1 -12
- package/src/components/documents/create/document-add-item-form.tsx +28 -16
- package/src/components/documents/create/document-add-item-tax-rate-field.tsx +12 -2
- package/src/components/documents/create/document-items-section.tsx +70 -39
- package/src/components/documents/create/document-recipient-section.tsx +10 -1
- package/src/components/documents/create/live-preview.tsx +113 -15
- package/src/components/documents/create/prepare-document-submission.ts +35 -16
- package/src/components/documents/create/use-document-customer-form.ts +14 -3
- package/src/components/documents/documents.hooks.ts +7 -2
- package/src/components/documents/shared/document-preview-display.tsx +136 -67
- package/src/components/documents/shared/scaled-document-preview.tsx +45 -5
- package/src/components/documents/view/document-actions-bar.tsx +284 -182
- package/src/components/documents/view/document-activities-list.tsx +3 -0
- package/src/components/documents/view/document-payments-list.tsx +3 -0
- package/src/components/documents/view/locales/de.ts +8 -0
- package/src/components/documents/view/locales/es.ts +8 -0
- package/src/components/documents/view/locales/fr.ts +8 -0
- package/src/components/documents/view/locales/hr.ts +8 -0
- package/src/components/documents/view/locales/it.ts +8 -0
- package/src/components/documents/view/locales/nl.ts +8 -0
- package/src/components/documents/view/locales/pl.ts +8 -0
- package/src/components/documents/view/locales/pt.ts +8 -0
- package/src/components/documents/view/locales/sl.ts +8 -0
- package/src/components/documents/view/use-document-download.ts +14 -25
- package/src/components/entities/create-entity-form.tsx +101 -16
- package/src/components/entities/entity-settings-form/entity-settings-form.tsx +10 -10
- package/src/components/entities/entity-settings-form/locales/de.ts +10 -0
- package/src/components/entities/entity-settings-form/locales/es.ts +10 -0
- package/src/components/entities/entity-settings-form/locales/fr.ts +10 -0
- package/src/components/entities/entity-settings-form/locales/hr.ts +10 -0
- package/src/components/entities/entity-settings-form/locales/it.ts +10 -0
- package/src/components/entities/entity-settings-form/locales/nl.ts +10 -0
- package/src/components/entities/entity-settings-form/locales/pl.ts +10 -0
- package/src/components/entities/entity-settings-form/locales/pt.ts +10 -0
- package/src/components/entities/entity-settings-form/locales/sl.ts +10 -0
- package/src/components/entities/fina-settings-form/fina-operator-required-dialog.tsx +3 -3
- package/src/components/entities/fina-settings-form/fina-settings-form.tsx +78 -124
- package/src/components/entities/fina-settings-form/sections/certificate-settings-section.tsx +8 -1
- package/src/components/entities/fina-settings-form/sections/premises-management-section.tsx +14 -2
- package/src/components/entities/fina-settings-form/sections/register-premise-dialog.tsx +7 -2
- package/src/components/entities/furs-settings-form/furs-settings-form.tsx +56 -130
- package/src/components/entities/furs-settings-form/sections/certificate-settings-section.tsx +8 -1
- package/src/components/entities/furs-settings-form/sections/enable-fiscalization-section.tsx +1 -0
- package/src/components/entities/furs-settings-form/sections/general-settings-section.tsx +15 -2
- package/src/components/entities/furs-settings-form/sections/premises-management-section.tsx +20 -3
- package/src/components/entities/furs-settings-form/sections/register-premise-dialog.tsx +38 -12
- package/src/components/entities/settings/defaults-settings-form.tsx +6 -6
- package/src/components/entities/settings/eslog-settings-form.tsx +13 -1
- package/src/components/entities/settings/pdf-template-selector/demo-invoice-data.ts +3 -22
- package/src/components/entities/shared/fiscalization-step-flow.ts +77 -0
- package/src/components/entities/shared/fiscalization-step-tabs.tsx +71 -0
- package/src/components/estimates/create/create-estimate-form.tsx +34 -21
- package/src/components/estimates/list/list-table.tsx +23 -14
- package/src/components/estimates/list/locales/de.ts +2 -0
- package/src/components/estimates/list/locales/en.ts +1 -0
- package/src/components/estimates/list/locales/es.ts +1 -0
- package/src/components/estimates/list/locales/fr.ts +1 -0
- package/src/components/estimates/list/locales/hr.ts +1 -0
- package/src/components/estimates/list/locales/it.ts +1 -0
- package/src/components/estimates/list/locales/nl.ts +1 -0
- package/src/components/estimates/list/locales/pl.ts +1 -0
- package/src/components/estimates/list/locales/pt.ts +1 -0
- package/src/components/estimates/list/locales/sl.ts +1 -0
- package/src/components/estimates/list/use-estimate-download.ts +1 -12
- package/src/components/export/document-export-form.tsx +33 -7
- package/src/components/export/sales-per-item-export-form.tsx +23 -7
- package/src/components/invoices/create/create-invoice-form.tsx +295 -329
- package/src/components/invoices/create/prepare-invoice-submission.ts +0 -8
- package/src/components/invoices/list/list-table.tsx +7 -4
- package/src/components/invoices/list/use-invoice-download.ts +1 -11
- package/src/components/invoices/send-email-dialog/locales/de.ts +20 -0
- package/src/components/invoices/send-email-dialog/locales/es.ts +20 -0
- package/src/components/invoices/send-email-dialog/locales/fr.ts +20 -0
- package/src/components/invoices/send-email-dialog/locales/hr.ts +20 -0
- package/src/components/invoices/send-email-dialog/locales/it.ts +20 -0
- package/src/components/invoices/send-email-dialog/locales/nl.ts +20 -0
- package/src/components/invoices/send-email-dialog/locales/pl.ts +20 -0
- package/src/components/invoices/send-email-dialog/locales/pt.ts +20 -0
- package/src/components/invoices/send-email-dialog/locales/sl.ts +20 -0
- package/src/components/invoices/send-email-dialog/send-email-dialog.tsx +77 -8
- package/src/components/invoices/view/eslog-info-display.tsx +17 -1
- package/src/components/invoices/view/fiscalization-status-card.tsx +7 -3
- package/src/components/items/item-combobox.tsx +26 -6
- package/src/components/items/item-list-table/item-list-table.tsx +5 -2
- package/src/components/payments/create-payment-form/index.ts +1 -0
- package/src/components/payments/list/list-table.tsx +14 -4
- package/src/components/recurring-invoices/list/list-table.tsx +7 -4
- package/src/components/request-logs/locales.ts +412 -0
- package/src/components/request-logs/request-log-detail.tsx +37 -21
- package/src/components/request-logs/request-log-list-table.tsx +57 -11
- package/src/components/table/data-table.tsx +5 -2
- package/src/components/table/date-cell.tsx +3 -1
- package/src/components/table/filter-bar.tsx +14 -2
- package/src/components/table/hooks/use-table-query.ts +1 -1
- package/src/components/table/locales.ts +1116 -0
- package/src/components/table/search-input.tsx +12 -3
- package/src/components/table/selection-toolbar.tsx +23 -6
- package/src/components/table/table-empty-state.tsx +43 -3
- package/src/components/table/table-no-results.tsx +3 -3
- package/src/components/table/table-pagination.tsx +4 -3
- package/src/components/table/types.ts +1 -0
- package/src/components/tax-reports/index.ts +1 -0
- package/src/components/tax-reports/kir-export-form.tsx +46 -8
- package/src/components/tax-reports/slovenia-tax-profile-step.tsx +191 -0
- package/src/components/tax-reports/slovenia-yearly-export-form.tsx +509 -0
- package/src/components/tax-reports/slovenia-yearly-review-step.tsx +253 -0
- package/src/components/tax-reports/slovenia-yearly-summary.tsx +19 -0
- package/src/components/taxes/tax-list-table/tax-list-table.tsx +3 -2
- package/src/components/ui/sidebar.tsx +3 -2
- package/src/components/ui/sticky-form-footer.tsx +7 -1
- package/src/components/webhook-logs/index.ts +6 -0
- package/src/components/webhook-logs/locales.ts +392 -0
- package/src/components/webhook-logs/webhook-delivery-detail.tsx +255 -0
- package/src/components/webhook-logs/webhook-delivery-list-table.tsx +278 -0
- package/src/components/wl-subscription/index.ts +1 -0
- package/src/components/wl-subscription/locked-feature.tsx +1 -0
- package/src/components/wl-subscription/paywall.tsx +193 -0
- package/src/components/wl-subscription/upgrade-modal.tsx +93 -29
- package/src/generate-schemas.ts +12 -7
- package/src/generated/schemas/customer.ts +2 -0
- package/src/generated/schemas/entity.ts +134 -0
- package/src/generated/schemas/exportsloveniayearlynormiranireport_body.ts +27 -0
- package/src/generated/schemas/index.ts +2 -0
- package/src/generated/schemas/me.ts +20 -1
- package/src/generated/schemas/renderadvanceinvoicepreview_body.ts +40 -34
- package/src/generated/schemas/rendercreditnotepreview_body.ts +42 -36
- package/src/generated/schemas/renderdeliverynotepreview_body.ts +23 -13
- package/src/generated/schemas/renderestimatepreview_body.ts +23 -13
- package/src/generated/schemas/renderinvoicepreview_body.ts +40 -34
- package/src/generated/schemas/sendemail_body.ts +44 -0
- package/src/generated/schemas/sloveniataxprofile.ts +42 -0
- package/src/generated/schemas/startpdfexport_body.ts +91 -1
- package/src/generated/schemas/webhook.ts +10 -0
- package/src/hooks/use-duplicate-document.ts +51 -13
- package/src/hooks/use-eslog-validation.ts +59 -0
- package/src/hooks/use-premise-selection.ts +186 -0
- package/src/lib/browser-cookies.ts +4 -4
- package/src/lib/date-fns-locale.ts +48 -0
- package/src/lib/fiscalization-options.ts +81 -0
- package/src/lib/locale.ts +38 -0
- package/src/lib/template-variables.tsx +1 -1
- package/src/lib/translation.ts +14 -3
- package/src/providers/entities-context.tsx +1 -0
- package/src/providers/entities-provider.tsx +102 -3
- package/src/providers/form-footer-context.tsx +37 -4
- package/src/providers/sdk-provider.tsx +7 -2
- package/src/providers/white-label-provider.tsx +4 -1
- 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
|
-
|
|
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
|
-
}, [
|
|
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
|
-
}, [
|
|
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
|
-
//
|
|
163
|
-
|
|
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
|
-
//
|
|
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
|
-
[
|
|
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>;
|