@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 +21 -0
- package/LICENSE +21 -0
- package/README.md +85 -0
- package/dist/index.d.mts +253 -0
- package/dist/index.d.ts +253 -0
- package/dist/index.js +295 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +282 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +82 -0
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
|
package/dist/index.d.mts
ADDED
|
@@ -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 };
|
package/dist/index.d.ts
ADDED
|
@@ -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 };
|