@dloizides/tenant-theme-web 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,21 @@
1
+ # Changelog
2
+
3
+ All notable changes to `@dloizides/tenant-theme-web` are documented here.
4
+
5
+ ## [1.0.0]
6
+
7
+ ### Added
8
+
9
+ - Initial extraction (task #195) of the byte-identical tenant-theme stack shared by `erevna-web`
10
+ and `katalogos-web`:
11
+ - `fetchTenantTheme(tenantId, opts)` — ETag-conditional fetch + DTO -> `TenantThemeConfig` mapping
12
+ via an injected `httpGet` transport, an app-supplied `defaultThemeConfig` fallback palette, and
13
+ an optional `baseURL`.
14
+ - `saveTenantTheme(tenantId, config, httpPut)` + `toApiThemeRequest(config)` — the PUT body mapper.
15
+ - `toTenantThemeConfig` / `hasThemeData` — DTO mapper helpers.
16
+ - localStorage ETag cache (`readThemeCache`/`writeThemeCache`/`clearThemeCache`/
17
+ `clearAllThemeCaches`) with the `tenant-theme-{id}` key convention + 24h expiry, plus an
18
+ injectable `configureThemeCacheLogger`.
19
+ - The config/DTO/cache/port type surface.
20
+ - Intentionally react-query-free: the consuming app owns its `QueryClient` and theme hooks, and the
21
+ theme preset VALUES stay per-app.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 dloizides
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # @dloizides/tenant-theme-web
2
+
3
+ Product-agnostic RN-web **tenant-theme client** for the dloizides.com portfolio.
4
+
5
+ Owns the tenant-theme logic that `erevna-web` and `katalogos-web` shared byte-for-byte:
6
+
7
+ - `fetchTenantTheme(tenantId, opts)` — ETag-conditional `GET /api/tenants/{id}/theme`, mapping
8
+ the flat API DTO into a `TenantThemeConfig` (returns `{ themeConfig, etag, notModified }`).
9
+ - `saveTenantTheme(tenantId, config, httpPut)` + `toApiThemeRequest(config)` — the
10
+ `PUT /api/tenants/{id}/theme` body mapper.
11
+ - `toTenantThemeConfig` / `hasThemeData` — the DTO mapper helpers.
12
+ - A localStorage **ETag cache**: `readThemeCache`, `writeThemeCache`, `clearThemeCache`,
13
+ `clearAllThemeCaches` (24h expiry, `tenant-theme-{id}` key convention).
14
+
15
+ App-specific concerns are **ports** the consumer supplies:
16
+
17
+ - the **HTTP transport** (`httpGet` / `httpPut`) — wire to the app's axios apiClient /
18
+ `@dloizides/bff-web-client` / Orval mutator;
19
+ - the **theme preset VALUES** (`defaultThemeConfig`) — each product keeps its own palette;
20
+ - the **API base URL** (`baseURL`).
21
+
22
+ This package is intentionally **react-query-free** — the consuming app owns its `QueryClient` and
23
+ its theme hooks. It never imports a product, realm, hardcoded URL, or palette.
24
+
25
+ ## Quick start
26
+
27
+ ```ts
28
+ import {
29
+ fetchTenantTheme,
30
+ saveTenantTheme,
31
+ readThemeCache,
32
+ writeThemeCache,
33
+ configureThemeCacheLogger,
34
+ type HttpGet,
35
+ type HttpPut,
36
+ } from '@dloizides/tenant-theme-web';
37
+
38
+ import { DEFAULT_THEME_CONFIG } from '../theme/presets'; // app-owned palette
39
+ import { apiClient } from '../lib/api/apiClient';
40
+
41
+ // 1. Wire the transports
42
+ const httpGet: HttpGet = (args) => apiClient.request({ method: 'GET', ...args });
43
+ const httpPut: HttpPut = (args) => apiClient.request({ method: 'PUT', ...args }).then((r) => r.data);
44
+
45
+ // 2. Optional: route cache-failure warnings to your logger
46
+ configureThemeCacheLogger((ctx, msg, err) => loggingService.warn(ctx, msg, err));
47
+
48
+ // 3. Fetch (ETag-conditional)
49
+ const cached = readThemeCache(tenantId);
50
+ const res = await fetchTenantTheme(tenantId, {
51
+ httpGet,
52
+ defaultThemeConfig: DEFAULT_THEME_CONFIG,
53
+ baseURL: env.IDENTITY_API_URL,
54
+ cachedEtag: cached?.etag,
55
+ });
56
+ if (!res.notModified && res.themeConfig) {
57
+ writeThemeCache(tenantId, res.themeConfig, res.etag ?? '');
58
+ }
59
+
60
+ // 4. Save
61
+ await saveTenantTheme(tenantId, config, httpPut);
62
+ ```
63
+
64
+ ## Ports
65
+
66
+ | Port | Type | Owned by | Notes |
67
+ |------|------|----------|-------|
68
+ | `httpGet` | `HttpGet` | app | Must resolve for statuses `validateStatus` accepts (2xx + 304), reject otherwise with axios-style `error.response.status`. |
69
+ | `httpPut` | `HttpPut` | app | Resolves with the parsed response body. |
70
+ | `defaultThemeConfig` | `TenantThemeConfig` | app | The product's `DEFAULT_THEME_CONFIG` fallback palette. |
71
+ | `baseURL` | `string?` | app | Identity API base; omit to use the transport's own base. |
72
+ | cache warn logger | `ThemeCacheWarn?` | app | Via `configureThemeCacheLogger`; defaults to silent. |
73
+
74
+ ## Scripts
75
+
76
+ ```bash
77
+ npm run build # tsup -> dual CJS/ESM + d.ts
78
+ npm test # jest (100% coverage gate)
79
+ npm run lint # eslint
80
+ npm run typecheck # tsc --noEmit
81
+ ```
82
+
83
+ ## License
84
+
85
+ MIT
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Shared types for `@dloizides/tenant-theme-web`.
3
+ *
4
+ * The package owns the tenant-theme *logic* (the ETag-conditional fetch, the
5
+ * DTO -> config mapper, the localStorage ETag cache, the save-request body
6
+ * mapper). Every app-specific concern is a **port** the consuming app supplies:
7
+ * its HTTP transport ({@link HttpGet} / {@link HttpPut}) and its theme preset
8
+ * *values* (the `defaultThemeConfig` fallback). The package never imports a
9
+ * product, realm, hardcoded URL, or hardcoded palette.
10
+ */
11
+ /**
12
+ * Mode-specific color tokens shared by both light and dark configurations.
13
+ * All values are hex strings (e.g. '#ffffff'). The *shape* is owned by this
14
+ * package; the actual values per preset stay per-app.
15
+ */
16
+ interface ThemeModeColors {
17
+ /** Page/body background */
18
+ background: string;
19
+ /** Cards, panels */
20
+ surface: string;
21
+ /** Modals, dropdowns */
22
+ surfaceElevated: string;
23
+ /** Primary text */
24
+ text: string;
25
+ /** Secondary/muted text */
26
+ textSecondary: string;
27
+ /** Default borders */
28
+ border: string;
29
+ /** Divider lines */
30
+ divider: string;
31
+ }
32
+ /** Optional semantic/status color overrides. Omitted values fall back to defaults. */
33
+ interface SemanticColorOverrides {
34
+ success?: string;
35
+ warning?: string;
36
+ error?: string;
37
+ info?: string;
38
+ }
39
+ /** Optional typography overrides. */
40
+ interface TypographyConfig {
41
+ /** System font family name */
42
+ fontFamily?: string;
43
+ /** Heading size multiplier (default 1.0) */
44
+ headingScale?: number;
45
+ }
46
+ /** Branding assets stored as ContentService references. */
47
+ interface BrandingConfig {
48
+ /** GUID referencing a ContentService item for the logo */
49
+ logoContentId: string | null;
50
+ /** GUID referencing a ContentService item for the favicon */
51
+ faviconContentId: string | null;
52
+ /** Which built-in preset was used as the base */
53
+ presetId: string | null;
54
+ }
55
+ /**
56
+ * Per-tenant theme configuration. Kept flat and small (~2-5KB) since it is
57
+ * fetched on every app load. All color values are hex strings.
58
+ */
59
+ interface TenantThemeConfig {
60
+ /** Brand primary color (hex) */
61
+ primary: string;
62
+ /** Brand secondary color (hex) */
63
+ secondary: string;
64
+ /** Accent/highlight color (hex) */
65
+ accent: string;
66
+ /** Optional semantic/status color overrides */
67
+ semantic?: SemanticColorOverrides;
68
+ /** Light mode tokens */
69
+ light: ThemeModeColors;
70
+ /** Dark mode tokens */
71
+ dark: ThemeModeColors;
72
+ /** Optional typography overrides */
73
+ typography?: TypographyConfig;
74
+ /** Branding assets */
75
+ branding: BrandingConfig;
76
+ }
77
+ /** Colors DTO returned by the API. */
78
+ interface ApiThemeColorsDto {
79
+ primary?: string | null;
80
+ primaryLight?: string | null;
81
+ primaryDark?: string | null;
82
+ secondary?: string | null;
83
+ background?: string | null;
84
+ surface?: string | null;
85
+ error?: string | null;
86
+ onPrimary?: string | null;
87
+ onBackground?: string | null;
88
+ onSurface?: string | null;
89
+ }
90
+ /** Raw response shape from GET /api/tenants/{tenantId}/theme */
91
+ interface ApiThemeResponseDto {
92
+ presetId?: string | null;
93
+ colors?: ApiThemeColorsDto | null;
94
+ darkColors?: ApiThemeColorsDto | null;
95
+ typography?: {
96
+ fontFamily?: string | null;
97
+ headerScale?: number | null;
98
+ bodyScale?: number | null;
99
+ } | null;
100
+ logoContentId?: string | null;
101
+ faviconContentId?: string | null;
102
+ }
103
+ /** Colors sub-DTO of the save (PUT) request body. */
104
+ interface ApiThemeRequestColors {
105
+ primary: string;
106
+ secondary: string;
107
+ background: string;
108
+ surface: string;
109
+ error: string | null;
110
+ onBackground: string;
111
+ onSurface: string;
112
+ }
113
+ /** Request body for PUT /api/tenants/{tenantId}/theme. */
114
+ interface ApiThemeRequest {
115
+ presetId: string | null;
116
+ colors: ApiThemeRequestColors;
117
+ darkColors: ApiThemeRequestColors;
118
+ typography: {
119
+ fontFamily: string | null;
120
+ headerScale: number | null;
121
+ } | null;
122
+ logoContentId: string | null;
123
+ faviconContentId: string | null;
124
+ }
125
+ /** Minimal HTTP response shape the package reads (status + data + headers). */
126
+ interface HttpResponse<TData = unknown> {
127
+ status: number;
128
+ data: TData;
129
+ headers: Record<string, unknown>;
130
+ }
131
+ /** Arguments for the {@link HttpGet} port. */
132
+ interface HttpGetArgs {
133
+ url: string;
134
+ baseURL?: string;
135
+ headers: Record<string, string>;
136
+ signal?: AbortSignal;
137
+ /** Treat these statuses as resolved (not thrown). Mirrors axios `validateStatus`. */
138
+ validateStatus: (status: number) => boolean;
139
+ }
140
+ /**
141
+ * App-supplied GET transport. The app wires this to its axios/BFF apiClient.
142
+ * Must resolve for any status that {@link HttpGetArgs.validateStatus} accepts,
143
+ * and reject (with an axios-style `error.response.status`) otherwise.
144
+ */
145
+ type HttpGet = <TData = unknown>(args: HttpGetArgs) => Promise<HttpResponse<TData>>;
146
+ /** Arguments for the {@link HttpPut} port. */
147
+ interface HttpPutArgs<TBody = unknown> {
148
+ url: string;
149
+ headers: Record<string, string>;
150
+ data: TBody;
151
+ }
152
+ /**
153
+ * App-supplied PUT transport. The app wires this to its Orval mutator / BFF
154
+ * apiClient. Resolves with the parsed response body.
155
+ */
156
+ type HttpPut = <TResp = unknown, TBody = unknown>(args: HttpPutArgs<TBody>) => Promise<TResp>;
157
+ /** Shape of the cached theme data stored in localStorage. */
158
+ interface CachedThemeData {
159
+ config: TenantThemeConfig;
160
+ logoUrl: string | null;
161
+ faviconUrl: string | null;
162
+ etag: string;
163
+ cachedAt: number;
164
+ }
165
+
166
+ /** Options for {@link fetchTenantTheme}. */
167
+ interface FetchTenantThemeOptions {
168
+ signal?: AbortSignal;
169
+ /** Cached ETag to send as the If-None-Match header. */
170
+ cachedEtag?: string;
171
+ /** App-supplied GET transport (wire to the app's apiClient/bff-web-client). */
172
+ httpGet: HttpGet;
173
+ /** App-supplied fallback palette (the product's DEFAULT_THEME_CONFIG). */
174
+ defaultThemeConfig: TenantThemeConfig;
175
+ /** Optional API base URL. Omit to use the transport's own base. */
176
+ baseURL?: string;
177
+ }
178
+ /** Frontend-facing response shape consumed by the theme hook. */
179
+ interface TenantThemeResponse {
180
+ themeConfig: TenantThemeConfig | null;
181
+ etag: string | null;
182
+ /** True when the server returned 304 Not Modified (use cached data). */
183
+ notModified: boolean;
184
+ }
185
+ /**
186
+ * Fetch the tenant theme configuration via the app-supplied transport and
187
+ * transform the API DTO into {@link TenantThemeConfig}. Returns a null config
188
+ * when the tenant has no theme configured (200 empty DTO or 404).
189
+ *
190
+ * Supports ETag-based conditional requests:
191
+ * - Pass `cachedEtag` to send the If-None-Match header
192
+ * - On 304 Not Modified, returns `{ notModified: true }` so the caller uses
193
+ * its cached data
194
+ */
195
+ declare function fetchTenantTheme(tenantId: string, options: FetchTenantThemeOptions): Promise<TenantThemeResponse>;
196
+
197
+ /**
198
+ * Transforms a raw API DTO into a {@link TenantThemeConfig}, filling any missing
199
+ * field from the app-supplied `defaultThemeConfig`.
200
+ */
201
+ declare function toTenantThemeConfig(dto: ApiThemeResponseDto, defaultThemeConfig: TenantThemeConfig): TenantThemeConfig;
202
+ /** True when the DTO carries any theme payload worth mapping. */
203
+ declare function hasThemeData(dto: ApiThemeResponseDto): boolean;
204
+
205
+ /**
206
+ * Tenant-theme save (PUT) logic.
207
+ *
208
+ * Maps a {@link TenantThemeConfig} into the API's UpdateTenantThemeRequest body
209
+ * and PUTs it via the app-supplied {@link HttpPut} transport.
210
+ *
211
+ * Endpoint: PUT /api/tenants/{tenantId}/theme
212
+ */
213
+
214
+ /** Response shape from PUT /api/tenants/{tenantId}/theme */
215
+ interface SaveThemeResponse {
216
+ success: boolean;
217
+ }
218
+ /** Maps a frontend {@link TenantThemeConfig} to the API's request body. */
219
+ declare function toApiThemeRequest(config: TenantThemeConfig): ApiThemeRequest;
220
+ /**
221
+ * Save the tenant theme via the app-supplied PUT transport.
222
+ */
223
+ declare function saveTenantTheme(tenantId: string, config: TenantThemeConfig, httpPut: HttpPut): Promise<SaveThemeResponse>;
224
+
225
+ declare const CACHE_KEY_PREFIX = "tenant-theme-";
226
+ declare const MAX_CACHE_AGE_MS: number;
227
+ /** Minimal warn-only logger contract for cache failures. */
228
+ type ThemeCacheWarn = (context: string, message: string, error?: unknown) => void;
229
+ /**
230
+ * Supply a warn logger the cache calls when a localStorage operation fails.
231
+ * Pass `undefined` to revert to silent (no-op) behavior.
232
+ */
233
+ declare function configureThemeCacheLogger(logger: ThemeCacheWarn | undefined): void;
234
+ /**
235
+ * Read cached theme data from localStorage.
236
+ * Returns null if cache is missing, expired, or corrupt.
237
+ */
238
+ declare function readThemeCache(tenantId: string): CachedThemeData | null;
239
+ /**
240
+ * Write theme data to localStorage cache.
241
+ */
242
+ declare function writeThemeCache(tenantId: string, config: TenantThemeConfig, etag: string): void;
243
+ /**
244
+ * Clear cached theme data for a specific tenant.
245
+ */
246
+ declare function clearThemeCache(tenantId: string): void;
247
+ /**
248
+ * Clear all tenant theme caches from localStorage.
249
+ * Used during logout to remove any tenant-specific data.
250
+ */
251
+ declare function clearAllThemeCaches(): void;
252
+
253
+ export { type ApiThemeColorsDto, type ApiThemeRequest, type ApiThemeRequestColors, type ApiThemeResponseDto, type BrandingConfig, CACHE_KEY_PREFIX, type CachedThemeData, type FetchTenantThemeOptions, type HttpGet, type HttpGetArgs, type HttpPut, type HttpPutArgs, type HttpResponse, MAX_CACHE_AGE_MS, type SaveThemeResponse, type SemanticColorOverrides, type TenantThemeConfig, type TenantThemeResponse, type ThemeCacheWarn, type ThemeModeColors, type TypographyConfig, clearAllThemeCaches, clearThemeCache, configureThemeCacheLogger, fetchTenantTheme, hasThemeData, readThemeCache, saveTenantTheme, toApiThemeRequest, toTenantThemeConfig, writeThemeCache };
@@ -0,0 +1,253 @@
1
+ /**
2
+ * Shared types for `@dloizides/tenant-theme-web`.
3
+ *
4
+ * The package owns the tenant-theme *logic* (the ETag-conditional fetch, the
5
+ * DTO -> config mapper, the localStorage ETag cache, the save-request body
6
+ * mapper). Every app-specific concern is a **port** the consuming app supplies:
7
+ * its HTTP transport ({@link HttpGet} / {@link HttpPut}) and its theme preset
8
+ * *values* (the `defaultThemeConfig` fallback). The package never imports a
9
+ * product, realm, hardcoded URL, or hardcoded palette.
10
+ */
11
+ /**
12
+ * Mode-specific color tokens shared by both light and dark configurations.
13
+ * All values are hex strings (e.g. '#ffffff'). The *shape* is owned by this
14
+ * package; the actual values per preset stay per-app.
15
+ */
16
+ interface ThemeModeColors {
17
+ /** Page/body background */
18
+ background: string;
19
+ /** Cards, panels */
20
+ surface: string;
21
+ /** Modals, dropdowns */
22
+ surfaceElevated: string;
23
+ /** Primary text */
24
+ text: string;
25
+ /** Secondary/muted text */
26
+ textSecondary: string;
27
+ /** Default borders */
28
+ border: string;
29
+ /** Divider lines */
30
+ divider: string;
31
+ }
32
+ /** Optional semantic/status color overrides. Omitted values fall back to defaults. */
33
+ interface SemanticColorOverrides {
34
+ success?: string;
35
+ warning?: string;
36
+ error?: string;
37
+ info?: string;
38
+ }
39
+ /** Optional typography overrides. */
40
+ interface TypographyConfig {
41
+ /** System font family name */
42
+ fontFamily?: string;
43
+ /** Heading size multiplier (default 1.0) */
44
+ headingScale?: number;
45
+ }
46
+ /** Branding assets stored as ContentService references. */
47
+ interface BrandingConfig {
48
+ /** GUID referencing a ContentService item for the logo */
49
+ logoContentId: string | null;
50
+ /** GUID referencing a ContentService item for the favicon */
51
+ faviconContentId: string | null;
52
+ /** Which built-in preset was used as the base */
53
+ presetId: string | null;
54
+ }
55
+ /**
56
+ * Per-tenant theme configuration. Kept flat and small (~2-5KB) since it is
57
+ * fetched on every app load. All color values are hex strings.
58
+ */
59
+ interface TenantThemeConfig {
60
+ /** Brand primary color (hex) */
61
+ primary: string;
62
+ /** Brand secondary color (hex) */
63
+ secondary: string;
64
+ /** Accent/highlight color (hex) */
65
+ accent: string;
66
+ /** Optional semantic/status color overrides */
67
+ semantic?: SemanticColorOverrides;
68
+ /** Light mode tokens */
69
+ light: ThemeModeColors;
70
+ /** Dark mode tokens */
71
+ dark: ThemeModeColors;
72
+ /** Optional typography overrides */
73
+ typography?: TypographyConfig;
74
+ /** Branding assets */
75
+ branding: BrandingConfig;
76
+ }
77
+ /** Colors DTO returned by the API. */
78
+ interface ApiThemeColorsDto {
79
+ primary?: string | null;
80
+ primaryLight?: string | null;
81
+ primaryDark?: string | null;
82
+ secondary?: string | null;
83
+ background?: string | null;
84
+ surface?: string | null;
85
+ error?: string | null;
86
+ onPrimary?: string | null;
87
+ onBackground?: string | null;
88
+ onSurface?: string | null;
89
+ }
90
+ /** Raw response shape from GET /api/tenants/{tenantId}/theme */
91
+ interface ApiThemeResponseDto {
92
+ presetId?: string | null;
93
+ colors?: ApiThemeColorsDto | null;
94
+ darkColors?: ApiThemeColorsDto | null;
95
+ typography?: {
96
+ fontFamily?: string | null;
97
+ headerScale?: number | null;
98
+ bodyScale?: number | null;
99
+ } | null;
100
+ logoContentId?: string | null;
101
+ faviconContentId?: string | null;
102
+ }
103
+ /** Colors sub-DTO of the save (PUT) request body. */
104
+ interface ApiThemeRequestColors {
105
+ primary: string;
106
+ secondary: string;
107
+ background: string;
108
+ surface: string;
109
+ error: string | null;
110
+ onBackground: string;
111
+ onSurface: string;
112
+ }
113
+ /** Request body for PUT /api/tenants/{tenantId}/theme. */
114
+ interface ApiThemeRequest {
115
+ presetId: string | null;
116
+ colors: ApiThemeRequestColors;
117
+ darkColors: ApiThemeRequestColors;
118
+ typography: {
119
+ fontFamily: string | null;
120
+ headerScale: number | null;
121
+ } | null;
122
+ logoContentId: string | null;
123
+ faviconContentId: string | null;
124
+ }
125
+ /** Minimal HTTP response shape the package reads (status + data + headers). */
126
+ interface HttpResponse<TData = unknown> {
127
+ status: number;
128
+ data: TData;
129
+ headers: Record<string, unknown>;
130
+ }
131
+ /** Arguments for the {@link HttpGet} port. */
132
+ interface HttpGetArgs {
133
+ url: string;
134
+ baseURL?: string;
135
+ headers: Record<string, string>;
136
+ signal?: AbortSignal;
137
+ /** Treat these statuses as resolved (not thrown). Mirrors axios `validateStatus`. */
138
+ validateStatus: (status: number) => boolean;
139
+ }
140
+ /**
141
+ * App-supplied GET transport. The app wires this to its axios/BFF apiClient.
142
+ * Must resolve for any status that {@link HttpGetArgs.validateStatus} accepts,
143
+ * and reject (with an axios-style `error.response.status`) otherwise.
144
+ */
145
+ type HttpGet = <TData = unknown>(args: HttpGetArgs) => Promise<HttpResponse<TData>>;
146
+ /** Arguments for the {@link HttpPut} port. */
147
+ interface HttpPutArgs<TBody = unknown> {
148
+ url: string;
149
+ headers: Record<string, string>;
150
+ data: TBody;
151
+ }
152
+ /**
153
+ * App-supplied PUT transport. The app wires this to its Orval mutator / BFF
154
+ * apiClient. Resolves with the parsed response body.
155
+ */
156
+ type HttpPut = <TResp = unknown, TBody = unknown>(args: HttpPutArgs<TBody>) => Promise<TResp>;
157
+ /** Shape of the cached theme data stored in localStorage. */
158
+ interface CachedThemeData {
159
+ config: TenantThemeConfig;
160
+ logoUrl: string | null;
161
+ faviconUrl: string | null;
162
+ etag: string;
163
+ cachedAt: number;
164
+ }
165
+
166
+ /** Options for {@link fetchTenantTheme}. */
167
+ interface FetchTenantThemeOptions {
168
+ signal?: AbortSignal;
169
+ /** Cached ETag to send as the If-None-Match header. */
170
+ cachedEtag?: string;
171
+ /** App-supplied GET transport (wire to the app's apiClient/bff-web-client). */
172
+ httpGet: HttpGet;
173
+ /** App-supplied fallback palette (the product's DEFAULT_THEME_CONFIG). */
174
+ defaultThemeConfig: TenantThemeConfig;
175
+ /** Optional API base URL. Omit to use the transport's own base. */
176
+ baseURL?: string;
177
+ }
178
+ /** Frontend-facing response shape consumed by the theme hook. */
179
+ interface TenantThemeResponse {
180
+ themeConfig: TenantThemeConfig | null;
181
+ etag: string | null;
182
+ /** True when the server returned 304 Not Modified (use cached data). */
183
+ notModified: boolean;
184
+ }
185
+ /**
186
+ * Fetch the tenant theme configuration via the app-supplied transport and
187
+ * transform the API DTO into {@link TenantThemeConfig}. Returns a null config
188
+ * when the tenant has no theme configured (200 empty DTO or 404).
189
+ *
190
+ * Supports ETag-based conditional requests:
191
+ * - Pass `cachedEtag` to send the If-None-Match header
192
+ * - On 304 Not Modified, returns `{ notModified: true }` so the caller uses
193
+ * its cached data
194
+ */
195
+ declare function fetchTenantTheme(tenantId: string, options: FetchTenantThemeOptions): Promise<TenantThemeResponse>;
196
+
197
+ /**
198
+ * Transforms a raw API DTO into a {@link TenantThemeConfig}, filling any missing
199
+ * field from the app-supplied `defaultThemeConfig`.
200
+ */
201
+ declare function toTenantThemeConfig(dto: ApiThemeResponseDto, defaultThemeConfig: TenantThemeConfig): TenantThemeConfig;
202
+ /** True when the DTO carries any theme payload worth mapping. */
203
+ declare function hasThemeData(dto: ApiThemeResponseDto): boolean;
204
+
205
+ /**
206
+ * Tenant-theme save (PUT) logic.
207
+ *
208
+ * Maps a {@link TenantThemeConfig} into the API's UpdateTenantThemeRequest body
209
+ * and PUTs it via the app-supplied {@link HttpPut} transport.
210
+ *
211
+ * Endpoint: PUT /api/tenants/{tenantId}/theme
212
+ */
213
+
214
+ /** Response shape from PUT /api/tenants/{tenantId}/theme */
215
+ interface SaveThemeResponse {
216
+ success: boolean;
217
+ }
218
+ /** Maps a frontend {@link TenantThemeConfig} to the API's request body. */
219
+ declare function toApiThemeRequest(config: TenantThemeConfig): ApiThemeRequest;
220
+ /**
221
+ * Save the tenant theme via the app-supplied PUT transport.
222
+ */
223
+ declare function saveTenantTheme(tenantId: string, config: TenantThemeConfig, httpPut: HttpPut): Promise<SaveThemeResponse>;
224
+
225
+ declare const CACHE_KEY_PREFIX = "tenant-theme-";
226
+ declare const MAX_CACHE_AGE_MS: number;
227
+ /** Minimal warn-only logger contract for cache failures. */
228
+ type ThemeCacheWarn = (context: string, message: string, error?: unknown) => void;
229
+ /**
230
+ * Supply a warn logger the cache calls when a localStorage operation fails.
231
+ * Pass `undefined` to revert to silent (no-op) behavior.
232
+ */
233
+ declare function configureThemeCacheLogger(logger: ThemeCacheWarn | undefined): void;
234
+ /**
235
+ * Read cached theme data from localStorage.
236
+ * Returns null if cache is missing, expired, or corrupt.
237
+ */
238
+ declare function readThemeCache(tenantId: string): CachedThemeData | null;
239
+ /**
240
+ * Write theme data to localStorage cache.
241
+ */
242
+ declare function writeThemeCache(tenantId: string, config: TenantThemeConfig, etag: string): void;
243
+ /**
244
+ * Clear cached theme data for a specific tenant.
245
+ */
246
+ declare function clearThemeCache(tenantId: string): void;
247
+ /**
248
+ * Clear all tenant theme caches from localStorage.
249
+ * Used during logout to remove any tenant-specific data.
250
+ */
251
+ declare function clearAllThemeCaches(): void;
252
+
253
+ export { type ApiThemeColorsDto, type ApiThemeRequest, type ApiThemeRequestColors, type ApiThemeResponseDto, type BrandingConfig, CACHE_KEY_PREFIX, type CachedThemeData, type FetchTenantThemeOptions, type HttpGet, type HttpGetArgs, type HttpPut, type HttpPutArgs, type HttpResponse, MAX_CACHE_AGE_MS, type SaveThemeResponse, type SemanticColorOverrides, type TenantThemeConfig, type TenantThemeResponse, type ThemeCacheWarn, type ThemeModeColors, type TypographyConfig, clearAllThemeCaches, clearThemeCache, configureThemeCacheLogger, fetchTenantTheme, hasThemeData, readThemeCache, saveTenantTheme, toApiThemeRequest, toTenantThemeConfig, writeThemeCache };