@voyant-travel/admin 0.111.0
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/LICENSE +201 -0
- package/README.md +285 -0
- package/dist/app/extension-routes.d.ts +99 -0
- package/dist/app/extension-routes.d.ts.map +1 -0
- package/dist/app/extension-routes.js +134 -0
- package/dist/app/index.d.ts +9 -0
- package/dist/app/index.d.ts.map +1 -0
- package/dist/app/index.js +4 -0
- package/dist/app/root.d.ts +47 -0
- package/dist/app/root.d.ts.map +1 -0
- package/dist/app/root.js +55 -0
- package/dist/app/router.d.ts +30 -0
- package/dist/app/router.d.ts.map +1 -0
- package/dist/app/router.js +51 -0
- package/dist/app/workspace.d.ts +84 -0
- package/dist/app/workspace.d.ts.map +1 -0
- package/dist/app/workspace.js +87 -0
- package/dist/components/admin-breadcrumbs.d.ts +18 -0
- package/dist/components/admin-breadcrumbs.d.ts.map +1 -0
- package/dist/components/admin-breadcrumbs.js +84 -0
- package/dist/components/admin-nav-group.d.ts +11 -0
- package/dist/components/admin-nav-group.d.ts.map +1 -0
- package/dist/components/admin-nav-group.js +49 -0
- package/dist/components/admin-nav-link.d.ts +10 -0
- package/dist/components/admin-nav-link.d.ts.map +1 -0
- package/dist/components/admin-nav-link.js +5 -0
- package/dist/components/admin-page-head.d.ts +17 -0
- package/dist/components/admin-page-head.d.ts.map +1 -0
- package/dist/components/admin-page-head.js +107 -0
- package/dist/components/admin-widget-slot.d.ts +8 -0
- package/dist/components/admin-widget-slot.d.ts.map +1 -0
- package/dist/components/admin-widget-slot.js +19 -0
- package/dist/components/brand/voyant-mark.d.ts +3 -0
- package/dist/components/brand/voyant-mark.d.ts.map +1 -0
- package/dist/components/brand/voyant-mark.js +4 -0
- package/dist/components/brand/voyant-wordmark.d.ts +3 -0
- package/dist/components/brand/voyant-wordmark.d.ts.map +1 -0
- package/dist/components/brand/voyant-wordmark.js +4 -0
- package/dist/components/operator-admin-bootstrap-gate.d.ts +26 -0
- package/dist/components/operator-admin-bootstrap-gate.d.ts.map +1 -0
- package/dist/components/operator-admin-bootstrap-gate.js +22 -0
- package/dist/components/operator-admin-page-shell.d.ts +13 -0
- package/dist/components/operator-admin-page-shell.d.ts.map +1 -0
- package/dist/components/operator-admin-page-shell.js +6 -0
- package/dist/components/operator-admin-sidebar.d.ts +57 -0
- package/dist/components/operator-admin-sidebar.d.ts.map +1 -0
- package/dist/components/operator-admin-sidebar.js +104 -0
- package/dist/components/operator-admin-user-menu.d.ts +10 -0
- package/dist/components/operator-admin-user-menu.d.ts.map +1 -0
- package/dist/components/operator-admin-user-menu.js +19 -0
- package/dist/components/team-settings-page.d.ts +10 -0
- package/dist/components/team-settings-page.d.ts.map +1 -0
- package/dist/components/team-settings-page.js +149 -0
- package/dist/dashboard/dashboard-empty-states.d.ts +67 -0
- package/dist/dashboard/dashboard-empty-states.d.ts.map +1 -0
- package/dist/dashboard/dashboard-empty-states.js +65 -0
- package/dist/dashboard/dashboard-kpi-card.d.ts +13 -0
- package/dist/dashboard/dashboard-kpi-card.d.ts.map +1 -0
- package/dist/dashboard/dashboard-kpi-card.js +12 -0
- package/dist/dashboard/dashboard-page.d.ts +7 -0
- package/dist/dashboard/dashboard-page.d.ts.map +1 -0
- package/dist/dashboard/dashboard-page.js +150 -0
- package/dist/dashboard/dashboard-query-options.d.ts +224 -0
- package/dist/dashboard/dashboard-query-options.d.ts.map +1 -0
- package/dist/dashboard/dashboard-query-options.js +153 -0
- package/dist/dashboard/dashboard-skeleton.d.ts +13 -0
- package/dist/dashboard/dashboard-skeleton.d.ts.map +1 -0
- package/dist/dashboard/dashboard-skeleton.js +28 -0
- package/dist/extensions.d.ts +254 -0
- package/dist/extensions.d.ts.map +1 -0
- package/dist/extensions.js +139 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +53 -0
- package/dist/lib/i18n.d.ts +2 -0
- package/dist/lib/i18n.d.ts.map +1 -0
- package/dist/lib/i18n.js +1 -0
- package/dist/lib/initials.d.ts +24 -0
- package/dist/lib/initials.d.ts.map +1 -0
- package/dist/lib/initials.js +45 -0
- package/dist/navigation/destinations.d.ts +83 -0
- package/dist/navigation/destinations.d.ts.map +1 -0
- package/dist/navigation/destinations.js +65 -0
- package/dist/navigation/operator-navigation.d.ts +10 -0
- package/dist/navigation/operator-navigation.d.ts.map +1 -0
- package/dist/navigation/operator-navigation.js +191 -0
- package/dist/providers/admin-extensions.d.ts +9 -0
- package/dist/providers/admin-extensions.d.ts.map +1 -0
- package/dist/providers/admin-extensions.js +10 -0
- package/dist/providers/admin-provider.d.ts +53 -0
- package/dist/providers/admin-provider.d.ts.map +1 -0
- package/dist/providers/admin-provider.js +26 -0
- package/dist/providers/locale-preferences.d.ts +12 -0
- package/dist/providers/locale-preferences.d.ts.map +1 -0
- package/dist/providers/locale-preferences.js +32 -0
- package/dist/providers/locale.d.ts +23 -0
- package/dist/providers/locale.d.ts.map +1 -0
- package/dist/providers/locale.js +98 -0
- package/dist/providers/operator-admin-messages.d.ts +14 -0
- package/dist/providers/operator-admin-messages.d.ts.map +1 -0
- package/dist/providers/operator-admin-messages.js +16 -0
- package/dist/providers/operator-admin-shell.d.ts +35 -0
- package/dist/providers/operator-admin-shell.d.ts.map +1 -0
- package/dist/providers/operator-admin-shell.js +20 -0
- package/dist/providers/query-client.d.ts +19 -0
- package/dist/providers/query-client.d.ts.map +1 -0
- package/dist/providers/query-client.js +34 -0
- package/dist/providers/theme.d.ts +29 -0
- package/dist/providers/theme.d.ts.map +1 -0
- package/dist/providers/theme.js +63 -0
- package/dist/types.d.ts +60 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/package.json +222 -0
- package/src/styles.css +11 -0
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
/**
|
|
3
|
+
* Semantic admin navigation destinations (packaged-admin RFC §4.7).
|
|
4
|
+
*
|
|
5
|
+
* Package-owned admin pages cannot import a host's typed route tree, so they
|
|
6
|
+
* never navigate by route path. Instead they navigate to a semantic
|
|
7
|
+
* destination KEY; the host registers a key → href resolver map once.
|
|
8
|
+
*
|
|
9
|
+
* This interface ships empty. Domain packages declare the destinations their
|
|
10
|
+
* pages need via TypeScript declaration merging (the same `Register` trick
|
|
11
|
+
* TanStack Router uses):
|
|
12
|
+
*
|
|
13
|
+
* ```ts
|
|
14
|
+
* // inside @voyant-travel/catalog-react
|
|
15
|
+
* declare module "@voyant-travel/admin" {
|
|
16
|
+
* interface AdminDestinations {
|
|
17
|
+
* "supplier.detail": { supplierId: string }
|
|
18
|
+
* }
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* Naming convention: `<entity>.<action>` (e.g. `"product.detail"`,
|
|
23
|
+
* `"bookingJourney.start"`). The host then provides a resolver per declared
|
|
24
|
+
* key — `satisfies AdminDestinationResolvers` makes the map exhaustive.
|
|
25
|
+
*/
|
|
26
|
+
export interface AdminDestinations {
|
|
27
|
+
}
|
|
28
|
+
/** Union of all declared destination keys (empty until packages augment). */
|
|
29
|
+
export type AdminDestinationKey = keyof AdminDestinations & string;
|
|
30
|
+
/**
|
|
31
|
+
* The host's resolver map: one `params → href` function per declared
|
|
32
|
+
* destination key. Mapped over {@link AdminDestinations}, so `satisfies
|
|
33
|
+
* AdminDestinationResolvers` fails to compile when a declared key is missing
|
|
34
|
+
* a resolver — exhaustiveness is the host's proof of contract.
|
|
35
|
+
*/
|
|
36
|
+
export type AdminDestinationResolvers = {
|
|
37
|
+
[K in AdminDestinationKey]: (params: AdminDestinations[K]) => string;
|
|
38
|
+
};
|
|
39
|
+
/** Resolve a destination key + params to an href. Returned by {@link useAdminHref}. */
|
|
40
|
+
export type AdminHrefResolver = <K extends AdminDestinationKey>(key: K, params: AdminDestinations[K]) => string;
|
|
41
|
+
/**
|
|
42
|
+
* History options for a destination navigation. Packaged pages that REDIRECT
|
|
43
|
+
* (an alias route forwarding to its canonical page, a deep link that resolves
|
|
44
|
+
* straight into another flow) pass `replace: true` so the intermediate URL
|
|
45
|
+
* never lands in history — the same semantics a route-level redirect has.
|
|
46
|
+
*/
|
|
47
|
+
export interface AdminNavigateOptions {
|
|
48
|
+
/** Replace the current history entry instead of pushing a new one. */
|
|
49
|
+
replace?: boolean;
|
|
50
|
+
}
|
|
51
|
+
/** Navigate to a destination key + params. Returned by {@link useAdminNavigate}. */
|
|
52
|
+
export type AdminDestinationNavigator = <K extends AdminDestinationKey>(key: K, params: AdminDestinations[K], options?: AdminNavigateOptions) => void;
|
|
53
|
+
export interface AdminNavigationProviderProps {
|
|
54
|
+
/** Host resolver map covering every destination key the mounted packages declare. */
|
|
55
|
+
resolvers: AdminDestinationResolvers;
|
|
56
|
+
/**
|
|
57
|
+
* Host-injected navigation primitive — typically the app router's
|
|
58
|
+
* href-based navigate. Keeping it injected keeps this package
|
|
59
|
+
* router-agnostic. Hosts should honor `options.replace` (map it onto the
|
|
60
|
+
* router's history-replace mode) so packaged redirects behave like route
|
|
61
|
+
* redirects; ignoring it only costs an extra history entry.
|
|
62
|
+
*/
|
|
63
|
+
navigate: (href: string, options?: AdminNavigateOptions) => void;
|
|
64
|
+
children: React.ReactNode;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Provides destination resolution + navigation to packaged admin pages.
|
|
68
|
+
* Hosts mount it once around the workspace (e.g. via `AdminWorkspaceShell`'s
|
|
69
|
+
* `destinations` prop in `@voyant-travel/admin/app/workspace`).
|
|
70
|
+
*/
|
|
71
|
+
export declare function AdminNavigationProvider({ resolvers, navigate, children, }: AdminNavigationProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
72
|
+
/**
|
|
73
|
+
* Resolve destination keys to hrefs (for `<a href>` / link props). When a key
|
|
74
|
+
* cannot be resolved, warns once per key and returns `"#"` — render paths
|
|
75
|
+
* never throw over a missing link target.
|
|
76
|
+
*/
|
|
77
|
+
export declare function useAdminHref(): AdminHrefResolver;
|
|
78
|
+
/**
|
|
79
|
+
* Navigate to a destination: resolve the href, then call the host-injected
|
|
80
|
+
* `navigate`. When the key cannot be resolved, warns once per key and no-ops.
|
|
81
|
+
*/
|
|
82
|
+
export declare function useAdminNavigate(): AdminDestinationNavigator;
|
|
83
|
+
//# sourceMappingURL=destinations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"destinations.d.ts","sourceRoot":"","sources":["../../src/navigation/destinations.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,MAAM,WAAW,iBAAiB;CAAG;AAErC,6EAA6E;AAC7E,MAAM,MAAM,mBAAmB,GAAG,MAAM,iBAAiB,GAAG,MAAM,CAAA;AAElE;;;;;GAKG;AACH,MAAM,MAAM,yBAAyB,GAAG;KACrC,CAAC,IAAI,mBAAmB,GAAG,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,KAAK,MAAM;CACrE,CAAA;AAED,uFAAuF;AACvF,MAAM,MAAM,iBAAiB,GAAG,CAAC,CAAC,SAAS,mBAAmB,EAC5D,GAAG,EAAE,CAAC,EACN,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,KACzB,MAAM,CAAA;AAEX;;;;;GAKG;AACH,MAAM,WAAW,oBAAoB;IACnC,sEAAsE;IACtE,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB;AAED,oFAAoF;AACpF,MAAM,MAAM,yBAAyB,GAAG,CAAC,CAAC,SAAS,mBAAmB,EACpE,GAAG,EAAE,CAAC,EACN,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,EAC5B,OAAO,CAAC,EAAE,oBAAoB,KAC3B,IAAI,CAAA;AAST,MAAM,WAAW,4BAA4B;IAC3C,qFAAqF;IACrF,SAAS,EAAE,yBAAyB,CAAA;IACpC;;;;;;OAMG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,oBAAoB,KAAK,IAAI,CAAA;IAChE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAA;CAC1B;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,EACtC,SAAS,EACT,QAAQ,EACR,QAAQ,GACT,EAAE,4BAA4B,2CAI9B;AAoDD;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,iBAAiB,CAOhD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,IAAI,yBAAyB,CAW5D"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
const AdminNavigationContext = React.createContext(null);
|
|
4
|
+
/**
|
|
5
|
+
* Provides destination resolution + navigation to packaged admin pages.
|
|
6
|
+
* Hosts mount it once around the workspace (e.g. via `AdminWorkspaceShell`'s
|
|
7
|
+
* `destinations` prop in `@voyant-travel/admin/app/workspace`).
|
|
8
|
+
*/
|
|
9
|
+
export function AdminNavigationProvider({ resolvers, navigate, children, }) {
|
|
10
|
+
const value = React.useMemo(() => ({ resolvers, navigate }), [resolvers, navigate]);
|
|
11
|
+
return _jsx(AdminNavigationContext.Provider, { value: value, children: children });
|
|
12
|
+
}
|
|
13
|
+
/** Keys already warned about — unresolvable destinations warn once, not per render. */
|
|
14
|
+
const warnedDestinationKeys = new Set();
|
|
15
|
+
function warnOnce(key, message, ...rest) {
|
|
16
|
+
if (warnedDestinationKeys.has(key))
|
|
17
|
+
return;
|
|
18
|
+
warnedDestinationKeys.add(key);
|
|
19
|
+
console.warn(message, ...rest);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Resolve a destination to an href, or `null` when unresolvable (no provider,
|
|
23
|
+
* no resolver for the key, or a throwing resolver). Never throws — these run
|
|
24
|
+
* in render paths, and a broken link must not take the page down with it.
|
|
25
|
+
*/
|
|
26
|
+
function resolveDestinationHref(context, key, params) {
|
|
27
|
+
if (!context) {
|
|
28
|
+
warnOnce(key, `[voyant-admin] Destination "${key}" was resolved outside an <AdminNavigationProvider>; falling back to "#".`);
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
const resolver = context.resolvers[key];
|
|
32
|
+
if (!resolver) {
|
|
33
|
+
warnOnce(key, `[voyant-admin] No resolver registered for destination "${key}"; falling back to "#". Add it to the host's AdminNavigationProvider resolvers.`);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
return resolver(params);
|
|
38
|
+
}
|
|
39
|
+
catch (error) {
|
|
40
|
+
warnOnce(key, `[voyant-admin] Resolver for destination "${key}" threw; falling back to "#".`, error);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Resolve destination keys to hrefs (for `<a href>` / link props). When a key
|
|
46
|
+
* cannot be resolved, warns once per key and returns `"#"` — render paths
|
|
47
|
+
* never throw over a missing link target.
|
|
48
|
+
*/
|
|
49
|
+
export function useAdminHref() {
|
|
50
|
+
const context = React.useContext(AdminNavigationContext);
|
|
51
|
+
return React.useCallback((key, params) => resolveDestinationHref(context, key, params) ?? "#", [context]);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Navigate to a destination: resolve the href, then call the host-injected
|
|
55
|
+
* `navigate`. When the key cannot be resolved, warns once per key and no-ops.
|
|
56
|
+
*/
|
|
57
|
+
export function useAdminNavigate() {
|
|
58
|
+
const context = React.useContext(AdminNavigationContext);
|
|
59
|
+
return React.useCallback((key, params, options) => {
|
|
60
|
+
const href = resolveDestinationHref(context, key, params);
|
|
61
|
+
if (href === null || context === null)
|
|
62
|
+
return;
|
|
63
|
+
context.navigate(href, options);
|
|
64
|
+
}, [context]);
|
|
65
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { OperatorAdminMessages } from "../providers/operator-admin-messages.js";
|
|
2
|
+
import type { NavItem } from "../types.js";
|
|
3
|
+
export type OperatorAdminNavigationIconName = "availability" | "bookings" | "catalog" | "channelSync" | "dashboard" | "finance" | "flights" | "legal" | "notifications" | "organizations" | "people" | "products" | "resources" | "settings" | "suppliers";
|
|
4
|
+
export type OperatorAdminNavigationIcons = Partial<Record<OperatorAdminNavigationIconName, NavItem["icon"]>>;
|
|
5
|
+
export interface CreateOperatorAdminNavigationOptions {
|
|
6
|
+
messages: OperatorAdminMessages["nav"];
|
|
7
|
+
icons?: OperatorAdminNavigationIcons;
|
|
8
|
+
}
|
|
9
|
+
export declare function createOperatorAdminNavigation({ icons, messages, }: CreateOperatorAdminNavigationOptions): NavItem[];
|
|
10
|
+
//# sourceMappingURL=operator-navigation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operator-navigation.d.ts","sourceRoot":"","sources":["../../src/navigation/operator-navigation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,yCAAyC,CAAA;AACpF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAE1C,MAAM,MAAM,+BAA+B,GACvC,cAAc,GACd,UAAU,GACV,SAAS,GACT,aAAa,GACb,WAAW,GACX,SAAS,GACT,SAAS,GACT,OAAO,GACP,eAAe,GACf,eAAe,GACf,QAAQ,GACR,UAAU,GACV,WAAW,GACX,UAAU,GACV,WAAW,CAAA;AAEf,MAAM,MAAM,4BAA4B,GAAG,OAAO,CAChD,MAAM,CAAC,+BAA+B,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CACzD,CAAA;AAED,MAAM,WAAW,oCAAoC;IACnD,QAAQ,EAAE,qBAAqB,CAAC,KAAK,CAAC,CAAA;IACtC,KAAK,CAAC,EAAE,4BAA4B,CAAA;CACrC;AAED,wBAAgB,6BAA6B,CAAC,EAC5C,KAAU,EACV,QAAQ,GACT,EAAE,oCAAoC,GAAG,OAAO,EAAE,CA8LlD"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
export function createOperatorAdminNavigation({ icons = {}, messages, }) {
|
|
2
|
+
return [
|
|
3
|
+
{
|
|
4
|
+
id: "dashboard",
|
|
5
|
+
title: messages.dashboard,
|
|
6
|
+
url: "/",
|
|
7
|
+
icon: icons.dashboard,
|
|
8
|
+
},
|
|
9
|
+
{
|
|
10
|
+
id: "catalog",
|
|
11
|
+
title: messages.catalog,
|
|
12
|
+
url: "/catalog/products",
|
|
13
|
+
icon: icons.catalog,
|
|
14
|
+
items: [
|
|
15
|
+
{
|
|
16
|
+
id: "catalog-products",
|
|
17
|
+
title: messages.catalogProducts,
|
|
18
|
+
url: "/catalog/products",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
id: "catalog-excursions",
|
|
22
|
+
title: messages.catalogExcursions,
|
|
23
|
+
url: "/catalog/excursions",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: "catalog-tours",
|
|
27
|
+
title: messages.catalogTours,
|
|
28
|
+
url: "/catalog/tours",
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: "catalog-cruises",
|
|
32
|
+
title: messages.catalogCruises,
|
|
33
|
+
url: "/catalog/cruises",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "catalog-accommodations",
|
|
37
|
+
title: messages.catalogAccommodations,
|
|
38
|
+
url: "/catalog/accommodations",
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
id: "flights",
|
|
44
|
+
title: messages.flights,
|
|
45
|
+
url: "/flights",
|
|
46
|
+
icon: icons.flights,
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "products",
|
|
50
|
+
title: messages.products,
|
|
51
|
+
url: "/products",
|
|
52
|
+
icon: icons.products,
|
|
53
|
+
items: [
|
|
54
|
+
{
|
|
55
|
+
id: "product-categories",
|
|
56
|
+
title: messages.categories,
|
|
57
|
+
url: "/products/categories",
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "availability",
|
|
63
|
+
title: messages.availability,
|
|
64
|
+
url: "/operations/availability",
|
|
65
|
+
icon: icons.availability,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: "bookings",
|
|
69
|
+
title: messages.bookings,
|
|
70
|
+
url: "/bookings",
|
|
71
|
+
icon: icons.bookings,
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "notifications",
|
|
75
|
+
title: messages.notifications,
|
|
76
|
+
url: "/notifications/templates",
|
|
77
|
+
icon: icons.notifications,
|
|
78
|
+
items: [
|
|
79
|
+
{
|
|
80
|
+
id: "notification-templates",
|
|
81
|
+
title: messages.notificationTemplates,
|
|
82
|
+
url: "/notifications/templates",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "notification-reminder-rules",
|
|
86
|
+
title: messages.notificationReminderRules,
|
|
87
|
+
url: "/notifications/reminder-rules",
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
id: "notification-deliveries",
|
|
91
|
+
title: messages.notificationDeliveries,
|
|
92
|
+
url: "/notifications/deliveries",
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: "notification-reminder-runs",
|
|
96
|
+
title: messages.notificationReminderRuns,
|
|
97
|
+
url: "/notifications/reminder-runs",
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: "notification-preview",
|
|
101
|
+
title: messages.notificationPreview,
|
|
102
|
+
url: "/notifications/preview",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
id: "notification-settings",
|
|
106
|
+
title: messages.notificationSettings,
|
|
107
|
+
url: "/notifications/settings",
|
|
108
|
+
},
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "suppliers",
|
|
113
|
+
title: messages.suppliers,
|
|
114
|
+
url: "/suppliers",
|
|
115
|
+
icon: icons.suppliers,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
id: "people",
|
|
119
|
+
title: messages.people,
|
|
120
|
+
url: "/people",
|
|
121
|
+
icon: icons.people,
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: "organizations",
|
|
125
|
+
title: messages.organizations,
|
|
126
|
+
url: "/organizations",
|
|
127
|
+
icon: icons.organizations,
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: "resources",
|
|
131
|
+
title: messages.resources,
|
|
132
|
+
url: "/operations/resources",
|
|
133
|
+
icon: icons.resources,
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: "finance",
|
|
137
|
+
title: messages.finance,
|
|
138
|
+
url: "/finance/invoices",
|
|
139
|
+
icon: icons.finance,
|
|
140
|
+
items: [
|
|
141
|
+
{ id: "invoices", title: messages.invoices, url: "/finance/invoices" },
|
|
142
|
+
{
|
|
143
|
+
id: "invoice-number-series",
|
|
144
|
+
title: messages.invoiceNumberSeries,
|
|
145
|
+
url: "/finance/invoice-number-series",
|
|
146
|
+
},
|
|
147
|
+
{ id: "payments", title: messages.payments, url: "/finance/payments" },
|
|
148
|
+
{
|
|
149
|
+
id: "supplier-invoices",
|
|
150
|
+
title: messages.supplierInvoices,
|
|
151
|
+
url: "/finance/supplier-invoices",
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
id: "profitability",
|
|
155
|
+
title: messages.profitability,
|
|
156
|
+
url: "/finance/profitability",
|
|
157
|
+
},
|
|
158
|
+
],
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: "legal",
|
|
162
|
+
title: messages.legal,
|
|
163
|
+
url: "/legal/contracts",
|
|
164
|
+
icon: icons.legal,
|
|
165
|
+
items: [
|
|
166
|
+
{ id: "contracts", title: messages.contracts, url: "/legal/contracts" },
|
|
167
|
+
{
|
|
168
|
+
id: "contract-templates",
|
|
169
|
+
title: messages.contractTemplates,
|
|
170
|
+
url: "/legal/templates",
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: "policies",
|
|
174
|
+
title: messages.policies,
|
|
175
|
+
url: "/legal/policies",
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
id: "number-series",
|
|
179
|
+
title: messages.contractNumberSeries,
|
|
180
|
+
url: "/legal/number-series",
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
id: "channel-sync",
|
|
186
|
+
title: messages.channelSync,
|
|
187
|
+
url: "/channel-sync",
|
|
188
|
+
icon: icons.channelSync,
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { AdminExtension } from "../extensions.js";
|
|
3
|
+
export interface AdminExtensionsProviderProps {
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
extensions?: ReadonlyArray<AdminExtension>;
|
|
6
|
+
}
|
|
7
|
+
export declare function AdminExtensionsProvider({ children, extensions, }: AdminExtensionsProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export declare function useAdminExtensions(): ReadonlyArray<AdminExtension>;
|
|
9
|
+
//# sourceMappingURL=admin-extensions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-extensions.d.ts","sourceRoot":"","sources":["../../src/providers/admin-extensions.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,SAAS,EAAc,MAAM,OAAO,CAAA;AAEjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AAItD,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,EAAE,SAAS,CAAA;IACnB,UAAU,CAAC,EAAE,aAAa,CAAC,cAAc,CAAC,CAAA;CAC3C;AAED,wBAAgB,uBAAuB,CAAC,EACtC,QAAQ,EACR,UAAe,GAChB,EAAE,4BAA4B,2CAI9B;AAED,wBAAgB,kBAAkB,IAAI,aAAa,CAAC,cAAc,CAAC,CAElE"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext } from "react";
|
|
4
|
+
const AdminExtensionsContext = createContext([]);
|
|
5
|
+
export function AdminExtensionsProvider({ children, extensions = [], }) {
|
|
6
|
+
return (_jsx(AdminExtensionsContext.Provider, { value: extensions, children: children }));
|
|
7
|
+
}
|
|
8
|
+
export function useAdminExtensions() {
|
|
9
|
+
return useContext(AdminExtensionsContext);
|
|
10
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { type QueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
import type { ThemeMode } from "../types.js";
|
|
4
|
+
export interface AdminProviderProps {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
/**
|
|
7
|
+
* Pre-built QueryClient. If not provided, one is created per render-tree
|
|
8
|
+
* via `makeQueryClient()`. Pass your own for server-rendering setups where
|
|
9
|
+
* the client should be created outside React.
|
|
10
|
+
*/
|
|
11
|
+
queryClient?: QueryClient;
|
|
12
|
+
/** Initial theme. Defaults to "system". */
|
|
13
|
+
defaultTheme?: ThemeMode;
|
|
14
|
+
/**
|
|
15
|
+
* localStorage key for theme persistence. Pass `null` to disable.
|
|
16
|
+
* Defaults to `"theme"`.
|
|
17
|
+
*/
|
|
18
|
+
themeStorageKey?: string | null;
|
|
19
|
+
/** Initial locale. Defaults to browser locale with `en` fallback. */
|
|
20
|
+
defaultLocale?: string;
|
|
21
|
+
/** Initial timezone. Defaults to browser timezone. */
|
|
22
|
+
defaultTimeZone?: string | null;
|
|
23
|
+
/**
|
|
24
|
+
* localStorage key for locale persistence. Pass `null` to disable.
|
|
25
|
+
* Defaults to `"admin-locale"`.
|
|
26
|
+
*/
|
|
27
|
+
localeStorageKey?: string | null;
|
|
28
|
+
/**
|
|
29
|
+
* localStorage key for timezone persistence. Pass `null` to disable.
|
|
30
|
+
* Defaults to `"admin-timezone"`.
|
|
31
|
+
*/
|
|
32
|
+
timeZoneStorageKey?: string | null;
|
|
33
|
+
/** Supported admin locales. Defaults to `["en", "ro"]`. */
|
|
34
|
+
supportedLocales?: readonly string[];
|
|
35
|
+
/** Fallback locale when no supported locale is resolved. Defaults to `"en"`. */
|
|
36
|
+
fallbackLocale?: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Composes the shared admin providers — QueryClient, Theme, and Locale — so
|
|
40
|
+
* starters don't have to wire each one individually. Note: this does NOT include
|
|
41
|
+
* `<VoyantReactProvider>` (from `@voyant-travel/react`) because its API base
|
|
42
|
+
* URL is starter-specific. Wrap AdminProvider's children with your
|
|
43
|
+
* VoyantReactProvider at the same level.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* <AdminProvider>
|
|
47
|
+
* <VoyantReactProvider baseUrl="/api">
|
|
48
|
+
* <App />
|
|
49
|
+
* </VoyantReactProvider>
|
|
50
|
+
* </AdminProvider>
|
|
51
|
+
*/
|
|
52
|
+
export declare function AdminProvider({ children, queryClient, defaultTheme, themeStorageKey, defaultLocale, defaultTimeZone, localeStorageKey, timeZoneStorageKey, supportedLocales, fallbackLocale, }: AdminProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
53
|
+
//# sourceMappingURL=admin-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-provider.d.ts","sourceRoot":"","sources":["../../src/providers/admin-provider.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,WAAW,EAAuB,MAAM,uBAAuB,CAAA;AAC7E,OAAO,EAAE,KAAK,SAAS,EAAY,MAAM,OAAO,CAAA;AAEhD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AAK5C,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,SAAS,CAAA;IACnB;;;;OAIG;IACH,WAAW,CAAC,EAAE,WAAW,CAAA;IACzB,2CAA2C;IAC3C,YAAY,CAAC,EAAE,SAAS,CAAA;IACxB;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,qEAAqE;IACrE,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,sDAAsD;IACtD,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,gFAAgF;IAChF,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,EAC5B,QAAQ,EACR,WAAW,EACX,YAAuB,EACvB,eAAyB,EACzB,aAAa,EACb,eAAsB,EACtB,gBAAiC,EACjC,kBAAqC,EACrC,gBAAgB,EAChB,cAAc,GACf,EAAE,kBAAkB,2CAoBpB"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { QueryClientProvider } from "@tanstack/react-query";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { LocaleProvider } from "./locale.js";
|
|
6
|
+
import { makeQueryClient } from "./query-client.js";
|
|
7
|
+
import { ThemeProvider } from "./theme.js";
|
|
8
|
+
/**
|
|
9
|
+
* Composes the shared admin providers — QueryClient, Theme, and Locale — so
|
|
10
|
+
* starters don't have to wire each one individually. Note: this does NOT include
|
|
11
|
+
* `<VoyantReactProvider>` (from `@voyant-travel/react`) because its API base
|
|
12
|
+
* URL is starter-specific. Wrap AdminProvider's children with your
|
|
13
|
+
* VoyantReactProvider at the same level.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* <AdminProvider>
|
|
17
|
+
* <VoyantReactProvider baseUrl="/api">
|
|
18
|
+
* <App />
|
|
19
|
+
* </VoyantReactProvider>
|
|
20
|
+
* </AdminProvider>
|
|
21
|
+
*/
|
|
22
|
+
export function AdminProvider({ children, queryClient, defaultTheme = "system", themeStorageKey = "theme", defaultLocale, defaultTimeZone = null, localeStorageKey = "admin-locale", timeZoneStorageKey = "admin-timezone", supportedLocales, fallbackLocale, }) {
|
|
23
|
+
// Keep a single QueryClient instance per mount when one isn't passed in.
|
|
24
|
+
const [client] = useState(() => queryClient ?? makeQueryClient());
|
|
25
|
+
return (_jsx(QueryClientProvider, { client: client, children: _jsx(ThemeProvider, { defaultTheme: defaultTheme, storageKey: themeStorageKey, children: _jsx(LocaleProvider, { defaultLocale: defaultLocale, defaultTimeZone: defaultTimeZone, localeStorageKey: localeStorageKey, timeZoneStorageKey: timeZoneStorageKey, supportedLocales: supportedLocales, fallbackLocale: fallbackLocale, children: children }) }) }));
|
|
26
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface AdminLocalePreferenceSource {
|
|
2
|
+
locale?: string | null;
|
|
3
|
+
timeZone?: string | null;
|
|
4
|
+
timezone?: string | null;
|
|
5
|
+
}
|
|
6
|
+
export interface AdminLocalePreferenceSyncProps {
|
|
7
|
+
source: AdminLocalePreferenceSource | null | undefined;
|
|
8
|
+
localeStorageKey?: string | null;
|
|
9
|
+
timeZoneStorageKey?: string | null;
|
|
10
|
+
}
|
|
11
|
+
export declare function AdminLocalePreferenceSync({ source, localeStorageKey, timeZoneStorageKey, }: AdminLocalePreferenceSyncProps): null;
|
|
12
|
+
//# sourceMappingURL=locale-preferences.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locale-preferences.d.ts","sourceRoot":"","sources":["../../src/providers/locale-preferences.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,2BAA2B;IAC1C,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,MAAM,WAAW,8BAA8B;IAC7C,MAAM,EAAE,2BAA2B,GAAG,IAAI,GAAG,SAAS,CAAA;IACtD,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACnC;AAED,wBAAgB,yBAAyB,CAAC,EACxC,MAAM,EACN,gBAAiC,EACjC,kBAAqC,GACtC,EAAE,8BAA8B,QAoChC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useEffect } from "react";
|
|
3
|
+
import { useLocale } from "./locale.js";
|
|
4
|
+
export function AdminLocalePreferenceSync({ source, localeStorageKey = "admin-locale", timeZoneStorageKey = "admin-timezone", }) {
|
|
5
|
+
const { locale, setLocale, setTimeZone, timeZone } = useLocale();
|
|
6
|
+
const preferredTimeZone = source?.timeZone ?? source?.timezone ?? null;
|
|
7
|
+
useEffect(() => {
|
|
8
|
+
if (!source || typeof window === "undefined") {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if (source.locale &&
|
|
12
|
+
source.locale !== locale &&
|
|
13
|
+
(!localeStorageKey || !window.localStorage.getItem(localeStorageKey))) {
|
|
14
|
+
setLocale(source.locale);
|
|
15
|
+
}
|
|
16
|
+
if (preferredTimeZone &&
|
|
17
|
+
preferredTimeZone !== timeZone &&
|
|
18
|
+
(!timeZoneStorageKey || !window.localStorage.getItem(timeZoneStorageKey))) {
|
|
19
|
+
setTimeZone(preferredTimeZone);
|
|
20
|
+
}
|
|
21
|
+
}, [
|
|
22
|
+
locale,
|
|
23
|
+
localeStorageKey,
|
|
24
|
+
preferredTimeZone,
|
|
25
|
+
setLocale,
|
|
26
|
+
setTimeZone,
|
|
27
|
+
source,
|
|
28
|
+
timeZone,
|
|
29
|
+
timeZoneStorageKey,
|
|
30
|
+
]);
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export declare const DEFAULT_ADMIN_LOCALES: readonly ["en", "ro"];
|
|
3
|
+
export declare const DEFAULT_ADMIN_LOCALE = "en";
|
|
4
|
+
export interface LocaleContextValue {
|
|
5
|
+
locale: string;
|
|
6
|
+
resolvedLocale: string;
|
|
7
|
+
setLocale: (locale: string) => void;
|
|
8
|
+
timeZone: string | null;
|
|
9
|
+
setTimeZone: (timeZone: string | null) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface LocaleProviderProps {
|
|
12
|
+
children: ReactNode;
|
|
13
|
+
defaultLocale?: string;
|
|
14
|
+
defaultTimeZone?: string | null;
|
|
15
|
+
localeStorageKey?: string | null;
|
|
16
|
+
timeZoneStorageKey?: string | null;
|
|
17
|
+
supportedLocales?: readonly string[];
|
|
18
|
+
fallbackLocale?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function LocaleProvider({ children, defaultLocale, defaultTimeZone, localeStorageKey, timeZoneStorageKey, supportedLocales, fallbackLocale, }: LocaleProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
21
|
+
export declare function useLocale(): LocaleContextValue;
|
|
22
|
+
export declare function resolveAdminLocale(locale: string | null | undefined, supportedLocales?: readonly string[], fallbackLocale?: string): string;
|
|
23
|
+
//# sourceMappingURL=locale.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locale.d.ts","sourceRoot":"","sources":["../../src/providers/locale.tsx"],"names":[],"mappings":"AAEA,OAAO,EAEL,KAAK,SAAS,EAMf,MAAM,OAAO,CAAA;AAEd,eAAO,MAAM,qBAAqB,uBAAwB,CAAA;AAC1D,eAAO,MAAM,oBAAoB,OAAO,CAAA;AAExC,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,CAAA;IACtB,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;IACnC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAA;CAC/C;AA6CD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,SAAS,CAAA;IACnB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED,wBAAgB,cAAc,CAAC,EAC7B,QAAQ,EACR,aAAa,EACb,eAAsB,EACtB,gBAAiC,EACjC,kBAAqC,EACrC,gBAAwC,EACxC,cAAqC,GACtC,EAAE,mBAAmB,2CAgFrB;AAED,wBAAgB,SAAS,IAAI,kBAAkB,CAO9C;AAED,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACjC,gBAAgB,GAAE,SAAS,MAAM,EAA0B,EAC3D,cAAc,SAAuB,GACpC,MAAM,CAER"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from "react";
|
|
4
|
+
export const DEFAULT_ADMIN_LOCALES = ["en", "ro"];
|
|
5
|
+
export const DEFAULT_ADMIN_LOCALE = "en";
|
|
6
|
+
const LocaleContext = createContext(undefined);
|
|
7
|
+
function pickSupportedLocale(locale, supportedLocales, fallbackLocale) {
|
|
8
|
+
if (!locale) {
|
|
9
|
+
return fallbackLocale;
|
|
10
|
+
}
|
|
11
|
+
const normalized = locale.trim().toLowerCase();
|
|
12
|
+
if (!normalized) {
|
|
13
|
+
return fallbackLocale;
|
|
14
|
+
}
|
|
15
|
+
const directMatch = supportedLocales.find((candidate) => candidate.toLowerCase() === normalized);
|
|
16
|
+
if (directMatch) {
|
|
17
|
+
return directMatch;
|
|
18
|
+
}
|
|
19
|
+
const languageMatch = supportedLocales.find((candidate) => candidate.toLowerCase() === normalized.split("-")[0]);
|
|
20
|
+
return languageMatch ?? fallbackLocale;
|
|
21
|
+
}
|
|
22
|
+
function readStoredValue(storageKey) {
|
|
23
|
+
if (typeof window === "undefined" || storageKey === null) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return window.localStorage.getItem(storageKey);
|
|
27
|
+
}
|
|
28
|
+
function getBrowserLocale() {
|
|
29
|
+
if (typeof navigator === "undefined") {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
return navigator.language ?? null;
|
|
33
|
+
}
|
|
34
|
+
export function LocaleProvider({ children, defaultLocale, defaultTimeZone = null, localeStorageKey = "admin-locale", timeZoneStorageKey = "admin-timezone", supportedLocales = DEFAULT_ADMIN_LOCALES, fallbackLocale = DEFAULT_ADMIN_LOCALE, }) {
|
|
35
|
+
const [locale, setLocaleState] = useState(() => {
|
|
36
|
+
const storedLocale = readStoredValue(localeStorageKey);
|
|
37
|
+
if (storedLocale) {
|
|
38
|
+
return storedLocale;
|
|
39
|
+
}
|
|
40
|
+
if (defaultLocale) {
|
|
41
|
+
return defaultLocale;
|
|
42
|
+
}
|
|
43
|
+
const browserLocale = getBrowserLocale();
|
|
44
|
+
return browserLocale ?? fallbackLocale;
|
|
45
|
+
});
|
|
46
|
+
const [timeZone, setTimeZoneState] = useState(() => {
|
|
47
|
+
const storedTimeZone = readStoredValue(timeZoneStorageKey);
|
|
48
|
+
if (storedTimeZone) {
|
|
49
|
+
return storedTimeZone;
|
|
50
|
+
}
|
|
51
|
+
if (defaultTimeZone !== undefined) {
|
|
52
|
+
return defaultTimeZone;
|
|
53
|
+
}
|
|
54
|
+
return Intl.DateTimeFormat().resolvedOptions().timeZone ?? null;
|
|
55
|
+
});
|
|
56
|
+
const resolvedLocale = useMemo(() => pickSupportedLocale(locale, supportedLocales, fallbackLocale), [fallbackLocale, locale, supportedLocales]);
|
|
57
|
+
const setLocale = useCallback((nextLocale) => {
|
|
58
|
+
setLocaleState(nextLocale);
|
|
59
|
+
if (typeof window !== "undefined" && localeStorageKey !== null) {
|
|
60
|
+
window.localStorage.setItem(localeStorageKey, nextLocale);
|
|
61
|
+
}
|
|
62
|
+
}, [localeStorageKey]);
|
|
63
|
+
const setTimeZone = useCallback((nextTimeZone) => {
|
|
64
|
+
setTimeZoneState(nextTimeZone);
|
|
65
|
+
if (typeof window === "undefined" || timeZoneStorageKey === null) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (nextTimeZone) {
|
|
69
|
+
window.localStorage.setItem(timeZoneStorageKey, nextTimeZone);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
window.localStorage.removeItem(timeZoneStorageKey);
|
|
73
|
+
}
|
|
74
|
+
}, [timeZoneStorageKey]);
|
|
75
|
+
useEffect(() => {
|
|
76
|
+
if (typeof document === "undefined") {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
document.documentElement.lang = resolvedLocale;
|
|
80
|
+
}, [resolvedLocale]);
|
|
81
|
+
return (_jsx(LocaleContext.Provider, { value: {
|
|
82
|
+
locale,
|
|
83
|
+
resolvedLocale,
|
|
84
|
+
setLocale,
|
|
85
|
+
timeZone,
|
|
86
|
+
setTimeZone,
|
|
87
|
+
}, children: children }));
|
|
88
|
+
}
|
|
89
|
+
export function useLocale() {
|
|
90
|
+
const context = useContext(LocaleContext);
|
|
91
|
+
if (!context) {
|
|
92
|
+
throw new Error("useLocale must be used within <LocaleProvider>");
|
|
93
|
+
}
|
|
94
|
+
return context;
|
|
95
|
+
}
|
|
96
|
+
export function resolveAdminLocale(locale, supportedLocales = DEFAULT_ADMIN_LOCALES, fallbackLocale = DEFAULT_ADMIN_LOCALE) {
|
|
97
|
+
return pickSupportedLocale(locale, supportedLocales, fallbackLocale);
|
|
98
|
+
}
|