@owlmeans/web-wl 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -714
- package/build/modules.d.ts +2 -2
- package/build/modules.d.ts.map +1 -1
- package/build/modules.js +1 -1
- package/build/modules.js.map +1 -1
- package/build/service.js +1 -1
- package/build/service.js.map +1 -1
- package/package.json +7 -6
- package/src/modules.ts +3 -3
- package/src/service.ts +2 -2
- package/tsconfig.json +5 -10
package/README.md
CHANGED
|
@@ -1,744 +1,54 @@
|
|
|
1
1
|
# @owlmeans/web-wl
|
|
2
2
|
|
|
3
|
-
Web
|
|
3
|
+
Web whitelabel — service factory, module declarations, and React components for managing whitelabel content in browser apps.
|
|
4
4
|
|
|
5
5
|
## Overview
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
- **Caching Strategy**: Efficient client-side caching of whitelabeling configurations
|
|
12
|
-
- **API Integration**: Seamless integration with server-side whitelabeling providers
|
|
13
|
-
- **Performance Optimization**: Optimized loading and rendering of dynamic branding content
|
|
14
|
-
|
|
15
|
-
This package follows the OwlMeans "quadra" pattern as the **web** implementation, complementing:
|
|
16
|
-
- **@owlmeans/wled**: Common whitelabeling declarations and base functionality *(base package)*
|
|
17
|
-
- **@owlmeans/client-wl**: Base client whitelabeling functionality
|
|
18
|
-
- **@owlmeans/server-wl**: Server-side whitelabeling implementation
|
|
19
|
-
- **@owlmeans/web-wl**: Web browser whitelabeling implementation *(this package)*
|
|
7
|
+
- `makeWlService(alias?)` — web-side whitelabel service factory
|
|
8
|
+
- `wlModules` (exported as `modules`) — web-side whitelabel module declarations
|
|
9
|
+
- Components for displaying whitelabel content
|
|
10
|
+
- `DEFAULT_ALIAS` — `'wl-web-serivce'`
|
|
20
11
|
|
|
21
12
|
## Installation
|
|
22
13
|
|
|
23
14
|
```bash
|
|
24
|
-
|
|
15
|
+
bun add @owlmeans/web-wl
|
|
25
16
|
```
|
|
26
17
|
|
|
27
|
-
##
|
|
28
|
-
|
|
29
|
-
This package requires and integrates with:
|
|
30
|
-
- `@owlmeans/wled`: Core whitelabeling types and modules
|
|
31
|
-
- `@owlmeans/client`: Base client functionality
|
|
32
|
-
- `@owlmeans/client-module`: Client module system for API calls
|
|
33
|
-
- `@owlmeans/context`: Context management and service registration
|
|
34
|
-
- React: Peer dependency for web components
|
|
35
|
-
|
|
36
|
-
## Key Concepts
|
|
37
|
-
|
|
38
|
-
### Web Whitelabeling Service
|
|
39
|
-
Browser-optimized service that:
|
|
40
|
-
- **Loads Whitelabeling Data**: Fetches entity-specific branding from server APIs
|
|
41
|
-
- **Caches Configurations**: Implements client-side caching for performance
|
|
42
|
-
- **Extracts Data**: Provides convenient access to specific whitelabeling data types
|
|
43
|
-
- **Handles Errors**: Graceful error handling for failed whitelabeling requests
|
|
44
|
-
|
|
45
|
-
### React Component Integration
|
|
46
|
-
React components that automatically adapt to whitelabeling configurations:
|
|
47
|
-
- **Dynamic Branding**: Components that change appearance based on entity branding
|
|
48
|
-
- **Conditional Rendering**: Show/hide content based on whitelabeling rules
|
|
49
|
-
- **Theme Integration**: Automatic theme switching based on entity configurations
|
|
50
|
-
- **Performance**: Optimized rendering with minimal re-renders
|
|
18
|
+
## Usage
|
|
51
19
|
|
|
52
|
-
|
|
53
|
-
Efficient caching strategy:
|
|
54
|
-
- **Memory Caching**: In-memory cache for frequently accessed configurations
|
|
55
|
-
- **Cache Invalidation**: Smart cache invalidation strategies
|
|
56
|
-
- **Performance**: Reduced API calls and improved user experience
|
|
57
|
-
|
|
58
|
-
## API Reference
|
|
59
|
-
|
|
60
|
-
### Factory Functions
|
|
61
|
-
|
|
62
|
-
#### `makeWlService(alias?: string): WlWebService`
|
|
63
|
-
|
|
64
|
-
Creates a web whitelabeling service instance with caching capabilities.
|
|
20
|
+
Register the service and module declarations:
|
|
65
21
|
|
|
66
22
|
```typescript
|
|
67
|
-
import { makeWlService } from '@owlmeans/web-wl'
|
|
23
|
+
import { makeWlService, modules as wlModules } from '@owlmeans/web-wl'
|
|
68
24
|
|
|
69
|
-
|
|
70
|
-
|
|
25
|
+
context.registerService(makeWlService())
|
|
26
|
+
const modules = [...baseModules, ...wlModules, ...appModules]
|
|
71
27
|
```
|
|
72
28
|
|
|
73
|
-
|
|
74
|
-
- `alias`: Optional service alias (defaults to `DEFAULT_ALIAS`)
|
|
75
|
-
|
|
76
|
-
**Returns:** `WlWebService` - Web whitelabeling service instance
|
|
29
|
+
Downstream variants (e.g., `@owlmeans/web-wl-manager`) extend this with manager-store helpers like `setupWlManagerStore<C, T>(context)`.
|
|
77
30
|
|
|
78
|
-
|
|
31
|
+
## API
|
|
79
32
|
|
|
80
|
-
|
|
33
|
+
### `makeWlService(alias?)`
|
|
81
34
|
|
|
82
|
-
|
|
35
|
+
Creates the web whitelabel service. `alias` defaults to `DEFAULT_ALIAS` (`'wl-web-serivce'`).
|
|
83
36
|
|
|
84
|
-
|
|
85
|
-
interface WlWebService extends InitializedService {
|
|
86
|
-
load: (entityId: string) => Promise<ProvidedWLSet>
|
|
87
|
-
extract: <T>(key: string, set: ProvidedWLSet) => T | undefined
|
|
88
|
-
}
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
**Methods:**
|
|
92
|
-
|
|
93
|
-
**`load(entityId: string): Promise<ProvidedWLSet>`**
|
|
94
|
-
- **Purpose**: Load complete whitelabeling data set for an entity
|
|
95
|
-
- **Parameters**: `entityId` - Entity identifier
|
|
96
|
-
- **Returns**: Promise resolving to whitelabeling data set
|
|
97
|
-
- **Behavior**:
|
|
98
|
-
- Checks cache first for existing data
|
|
99
|
-
- Makes API call to server if not cached
|
|
100
|
-
- Caches successful responses
|
|
101
|
-
- Returns comprehensive whitelabeling data
|
|
102
|
-
|
|
103
|
-
**`extract<T>(key: string, set: ProvidedWLSet): T | undefined`**
|
|
104
|
-
- **Purpose**: Extract specific whitelabeling data type from a data set
|
|
105
|
-
- **Parameters**:
|
|
106
|
-
- `key` - Provider key (e.g., 'company-provider', 'styles-provider')
|
|
107
|
-
- `set` - Whitelabeling data set
|
|
108
|
-
- **Returns**: Extracted data of type T or undefined if not found
|
|
109
|
-
- **Usage**: Convenient access to specific provider data
|
|
110
|
-
|
|
111
|
-
#### `ProvidedWLSet`
|
|
112
|
-
|
|
113
|
-
Collection of whitelabeling data from multiple providers.
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
interface ProvidedWLSet<T extends Record<string, any> = Record<string, any>> {
|
|
117
|
-
[providerKey: string]: ProvidedWL<T>
|
|
118
|
-
}
|
|
119
|
-
```
|
|
37
|
+
### `modules`
|
|
120
38
|
|
|
121
|
-
|
|
39
|
+
Array of web-side module declarations for whitelabel content (re-exported as `wlModules` in downstream variants).
|
|
122
40
|
|
|
123
|
-
|
|
41
|
+
### Components
|
|
124
42
|
|
|
125
|
-
|
|
126
|
-
interface Config extends ClientConfig {
|
|
127
|
-
// Inherits client configuration
|
|
128
|
-
// Plus whitelabeling-specific web settings
|
|
129
|
-
}
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
#### `Context<C extends Config = Config>`
|
|
133
|
-
|
|
134
|
-
Web context interface with whitelabeling support.
|
|
135
|
-
|
|
136
|
-
```typescript
|
|
137
|
-
interface Context<C extends Config = Config> extends ClientContext<C> {
|
|
138
|
-
// Inherits client context functionality
|
|
139
|
-
// With typed configuration for whitelabeling
|
|
140
|
-
}
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
### React Components
|
|
144
|
-
|
|
145
|
-
#### Whitelabeling Provider Component
|
|
146
|
-
|
|
147
|
-
```typescript
|
|
148
|
-
interface WlProviderProps {
|
|
149
|
-
entityId: string // Entity to load whitelabeling for
|
|
150
|
-
children: React.ReactNode // Child components
|
|
151
|
-
fallback?: React.ReactNode // Fallback content while loading
|
|
152
|
-
onError?: (error: Error) => void // Error handler
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
const WlProvider: FC<WlProviderProps> = (props) => { /* ... */ }
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
#### Whitelabel-aware Components
|
|
159
|
-
|
|
160
|
-
```typescript
|
|
161
|
-
interface WlBrandProps {
|
|
162
|
-
entityId: string
|
|
163
|
-
type: 'square' | 'wide' // Logo type
|
|
164
|
-
alt?: string // Alt text
|
|
165
|
-
fallback?: React.ReactNode // Fallback if no logo
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
const WlBrand: FC<WlBrandProps> = (props) => { /* ... */ }
|
|
169
|
-
|
|
170
|
-
interface WlThemeProps {
|
|
171
|
-
entityId: string
|
|
172
|
-
children: React.ReactNode
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const WlTheme: FC<WlThemeProps> = (props) => { /* ... */ }
|
|
176
|
-
```
|
|
43
|
+
React components from `./components` (re-exported at root) for rendering whitelabel content.
|
|
177
44
|
|
|
178
45
|
### Constants
|
|
179
46
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
Default service alias for whitelabeling service.
|
|
183
|
-
|
|
184
|
-
```typescript
|
|
185
|
-
const DEFAULT_ALIAS = 'wl-web-service'
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
## Usage Examples
|
|
189
|
-
|
|
190
|
-
### Basic Web Whitelabeling Setup
|
|
191
|
-
|
|
192
|
-
```typescript
|
|
193
|
-
import { makeWlService } from '@owlmeans/web-wl'
|
|
194
|
-
import { makeWebContext } from '@owlmeans/web-client'
|
|
195
|
-
|
|
196
|
-
// Create web context
|
|
197
|
-
const context = makeWebContext(config)
|
|
198
|
-
|
|
199
|
-
// Create and register whitelabeling service
|
|
200
|
-
const wlService = makeWlService()
|
|
201
|
-
context.registerService(wlService)
|
|
202
|
-
|
|
203
|
-
// Initialize context
|
|
204
|
-
await context.configure().init()
|
|
205
|
-
|
|
206
|
-
// Load whitelabeling data for an entity
|
|
207
|
-
const entityId = 'company-123'
|
|
208
|
-
const whitelabelData = await wlService.load(entityId)
|
|
209
|
-
|
|
210
|
-
console.log('Whitelabeling data:', whitelabelData)
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
### React Component Integration
|
|
214
|
-
|
|
215
|
-
```typescript
|
|
216
|
-
import React, { useEffect, useState } from 'react'
|
|
217
|
-
import { useContext } from '@owlmeans/client'
|
|
218
|
-
import type { WlWebService, ProvidedWLSet } from '@owlmeans/web-wl'
|
|
219
|
-
import type { CompanyInfo, CustomStyles, CustomMedia } from '@owlmeans/wled'
|
|
220
|
-
|
|
221
|
-
interface WhitelabeledAppProps {
|
|
222
|
-
entityId: string
|
|
223
|
-
children: React.ReactNode
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
const WhitelabeledApp: React.FC<WhitelabeledAppProps> = ({ entityId, children }) => {
|
|
227
|
-
const context = useContext()
|
|
228
|
-
const [whitelabelData, setWhitelabelData] = useState<ProvidedWLSet | null>(null)
|
|
229
|
-
const [loading, setLoading] = useState(true)
|
|
230
|
-
const [error, setError] = useState<Error | null>(null)
|
|
231
|
-
|
|
232
|
-
useEffect(() => {
|
|
233
|
-
const loadWhitelabeling = async () => {
|
|
234
|
-
try {
|
|
235
|
-
setLoading(true)
|
|
236
|
-
const wlService = context.service<WlWebService>('wl-web-service')
|
|
237
|
-
const data = await wlService.load(entityId)
|
|
238
|
-
setWhitelabelData(data)
|
|
239
|
-
} catch (err) {
|
|
240
|
-
setError(err as Error)
|
|
241
|
-
} finally {
|
|
242
|
-
setLoading(false)
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
loadWhitelabeling()
|
|
247
|
-
}, [entityId, context])
|
|
248
|
-
|
|
249
|
-
if (loading) {
|
|
250
|
-
return <div>Loading branding...</div>
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (error) {
|
|
254
|
-
return <div>Error loading branding: {error.message}</div>
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (!whitelabelData) {
|
|
258
|
-
return <div>No branding data available</div>
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Extract specific whitelabeling data
|
|
262
|
-
const wlService = context.service<WlWebService>('wl-web-service')
|
|
263
|
-
const companyInfo = wlService.extract<CompanyInfo>('company-provider', whitelabelData)
|
|
264
|
-
const customStyles = wlService.extract<CustomStyles>('styles-provider', whitelabelData)
|
|
265
|
-
const customMedia = wlService.extract<CustomMedia>('media-provider', whitelabelData)
|
|
266
|
-
|
|
267
|
-
return (
|
|
268
|
-
<WhitelabelThemeProvider styles={customStyles}>
|
|
269
|
-
<div className="whitelabeled-app">
|
|
270
|
-
<header>
|
|
271
|
-
{customMedia?.brand?.wideLogo && (
|
|
272
|
-
<img
|
|
273
|
-
src={customMedia.brand.wideLogo}
|
|
274
|
-
alt={companyInfo?.fullName || 'Logo'}
|
|
275
|
-
className="company-logo"
|
|
276
|
-
/>
|
|
277
|
-
)}
|
|
278
|
-
<h1>{companyInfo?.fullName || 'Application'}</h1>
|
|
279
|
-
</header>
|
|
280
|
-
|
|
281
|
-
<main>
|
|
282
|
-
{children}
|
|
283
|
-
</main>
|
|
284
|
-
|
|
285
|
-
<footer>
|
|
286
|
-
<p>{companyInfo?.description}</p>
|
|
287
|
-
</footer>
|
|
288
|
-
</div>
|
|
289
|
-
</WhitelabelThemeProvider>
|
|
290
|
-
)
|
|
291
|
-
}
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
### Dynamic Theme Application
|
|
295
|
-
|
|
296
|
-
```typescript
|
|
297
|
-
import React, { useMemo } from 'react'
|
|
298
|
-
import { ThemeProvider, createTheme } from '@mui/material/styles'
|
|
299
|
-
import type { CustomStyles } from '@owlmeans/wled'
|
|
300
|
-
|
|
301
|
-
interface WhitelabelThemeProviderProps {
|
|
302
|
-
styles?: CustomStyles
|
|
303
|
-
children: React.ReactNode
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
const WhitelabelThemeProvider: React.FC<WhitelabelThemeProviderProps> = ({
|
|
307
|
-
styles,
|
|
308
|
-
children
|
|
309
|
-
}) => {
|
|
310
|
-
const theme = useMemo(() => {
|
|
311
|
-
const baseTheme = createTheme()
|
|
312
|
-
|
|
313
|
-
if (!styles) {
|
|
314
|
-
return baseTheme
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return createTheme({
|
|
318
|
-
...baseTheme,
|
|
319
|
-
palette: {
|
|
320
|
-
...baseTheme.palette,
|
|
321
|
-
primary: {
|
|
322
|
-
main: styles.colors.primaryColor,
|
|
323
|
-
contrastText: '#ffffff'
|
|
324
|
-
},
|
|
325
|
-
secondary: {
|
|
326
|
-
main: styles.colors.secondaryColor || baseTheme.palette.secondary.main
|
|
327
|
-
},
|
|
328
|
-
background: {
|
|
329
|
-
default: styles.colors.primaryBackground || baseTheme.palette.background.default,
|
|
330
|
-
paper: styles.colors.secondaryBackground || baseTheme.palette.background.paper
|
|
331
|
-
}
|
|
332
|
-
},
|
|
333
|
-
typography: {
|
|
334
|
-
...baseTheme.typography,
|
|
335
|
-
fontFamily: styles.font.fontFamily || baseTheme.typography.fontFamily,
|
|
336
|
-
fontSize: styles.font.basicSize || baseTheme.typography.fontSize
|
|
337
|
-
}
|
|
338
|
-
})
|
|
339
|
-
}, [styles])
|
|
340
|
-
|
|
341
|
-
return (
|
|
342
|
-
<ThemeProvider theme={theme}>
|
|
343
|
-
{children}
|
|
344
|
-
</ThemeProvider>
|
|
345
|
-
)
|
|
346
|
-
}
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
### Whitelabel-aware Components
|
|
350
|
-
|
|
351
|
-
```typescript
|
|
352
|
-
import React from 'react'
|
|
353
|
-
import { useContext } from '@owlmeans/client'
|
|
354
|
-
import type { WlWebService } from '@owlmeans/web-wl'
|
|
355
|
-
import type { CustomMedia } from '@owlmeans/wled'
|
|
356
|
-
|
|
357
|
-
interface CompanyLogoProps {
|
|
358
|
-
entityId: string
|
|
359
|
-
type?: 'square' | 'wide'
|
|
360
|
-
className?: string
|
|
361
|
-
alt?: string
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const CompanyLogo: React.FC<CompanyLogoProps> = ({
|
|
365
|
-
entityId,
|
|
366
|
-
type = 'wide',
|
|
367
|
-
className,
|
|
368
|
-
alt = 'Company Logo'
|
|
369
|
-
}) => {
|
|
370
|
-
const context = useContext()
|
|
371
|
-
const [logoUrl, setLogoUrl] = React.useState<string | null>(null)
|
|
372
|
-
|
|
373
|
-
React.useEffect(() => {
|
|
374
|
-
const loadLogo = async () => {
|
|
375
|
-
try {
|
|
376
|
-
const wlService = context.service<WlWebService>('wl-web-service')
|
|
377
|
-
const whitelabelData = await wlService.load(entityId)
|
|
378
|
-
const mediaData = wlService.extract<CustomMedia>('media-provider', whitelabelData)
|
|
379
|
-
|
|
380
|
-
const logo = type === 'square'
|
|
381
|
-
? mediaData?.brand?.squareLogo
|
|
382
|
-
: mediaData?.brand?.wideLogo
|
|
383
|
-
|
|
384
|
-
setLogoUrl(logo || null)
|
|
385
|
-
} catch (error) {
|
|
386
|
-
console.error('Failed to load company logo:', error)
|
|
387
|
-
setLogoUrl(null)
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
loadLogo()
|
|
392
|
-
}, [entityId, type, context])
|
|
393
|
-
|
|
394
|
-
if (!logoUrl) {
|
|
395
|
-
return (
|
|
396
|
-
<div className={`company-logo-placeholder ${className || ''}`}>
|
|
397
|
-
{alt}
|
|
398
|
-
</div>
|
|
399
|
-
)
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
return (
|
|
403
|
-
<img
|
|
404
|
-
src={logoUrl}
|
|
405
|
-
alt={alt}
|
|
406
|
-
className={`company-logo ${className || ''}`}
|
|
407
|
-
/>
|
|
408
|
-
)
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// Usage
|
|
412
|
-
<CompanyLogo
|
|
413
|
-
entityId="company-123"
|
|
414
|
-
type="wide"
|
|
415
|
-
className="header-logo"
|
|
416
|
-
alt="Acme Corporation"
|
|
417
|
-
/>
|
|
418
|
-
```
|
|
419
|
-
|
|
420
|
-
### Custom Hook for Whitelabeling
|
|
421
|
-
|
|
422
|
-
```typescript
|
|
423
|
-
import { useState, useEffect } from 'react'
|
|
424
|
-
import { useContext } from '@owlmeans/client'
|
|
425
|
-
import type { WlWebService, ProvidedWLSet } from '@owlmeans/web-wl'
|
|
426
|
-
|
|
427
|
-
interface UseWhitelabelResult<T = any> {
|
|
428
|
-
data: T | null
|
|
429
|
-
loading: boolean
|
|
430
|
-
error: Error | null
|
|
431
|
-
reload: () => void
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
export const useWhitelabel = <T = any>(
|
|
435
|
-
entityId: string,
|
|
436
|
-
providerKey?: string
|
|
437
|
-
): UseWhitelabelResult<T> => {
|
|
438
|
-
const context = useContext()
|
|
439
|
-
const [data, setData] = useState<T | null>(null)
|
|
440
|
-
const [loading, setLoading] = useState(true)
|
|
441
|
-
const [error, setError] = useState<Error | null>(null)
|
|
442
|
-
|
|
443
|
-
const loadData = async () => {
|
|
444
|
-
try {
|
|
445
|
-
setLoading(true)
|
|
446
|
-
setError(null)
|
|
447
|
-
|
|
448
|
-
const wlService = context.service<WlWebService>('wl-web-service')
|
|
449
|
-
const whitelabelData = await wlService.load(entityId)
|
|
450
|
-
|
|
451
|
-
if (providerKey) {
|
|
452
|
-
const extracted = wlService.extract<T>(providerKey, whitelabelData)
|
|
453
|
-
setData(extracted || null)
|
|
454
|
-
} else {
|
|
455
|
-
setData(whitelabelData as T)
|
|
456
|
-
}
|
|
457
|
-
} catch (err) {
|
|
458
|
-
setError(err as Error)
|
|
459
|
-
setData(null)
|
|
460
|
-
} finally {
|
|
461
|
-
setLoading(false)
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
useEffect(() => {
|
|
466
|
-
loadData()
|
|
467
|
-
}, [entityId, providerKey])
|
|
468
|
-
|
|
469
|
-
return {
|
|
470
|
-
data,
|
|
471
|
-
loading,
|
|
472
|
-
error,
|
|
473
|
-
reload: loadData
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
// Usage examples
|
|
478
|
-
const CompanyInfoDisplay: React.FC<{ entityId: string }> = ({ entityId }) => {
|
|
479
|
-
const { data: companyInfo, loading, error } = useWhitelabel<CompanyInfo>(
|
|
480
|
-
entityId,
|
|
481
|
-
'company-provider'
|
|
482
|
-
)
|
|
483
|
-
|
|
484
|
-
if (loading) return <div>Loading company info...</div>
|
|
485
|
-
if (error) return <div>Error: {error.message}</div>
|
|
486
|
-
if (!companyInfo) return <div>No company information available</div>
|
|
487
|
-
|
|
488
|
-
return (
|
|
489
|
-
<div>
|
|
490
|
-
<h2>{companyInfo.fullName}</h2>
|
|
491
|
-
<p>{companyInfo.description}</p>
|
|
492
|
-
</div>
|
|
493
|
-
)
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
const StylesDisplay: React.FC<{ entityId: string }> = ({ entityId }) => {
|
|
497
|
-
const { data: styles, loading, error } = useWhitelabel<CustomStyles>(
|
|
498
|
-
entityId,
|
|
499
|
-
'styles-provider'
|
|
500
|
-
)
|
|
501
|
-
|
|
502
|
-
if (loading || error || !styles) return null
|
|
503
|
-
|
|
504
|
-
return (
|
|
505
|
-
<div style={{
|
|
506
|
-
backgroundColor: styles.colors.primaryBackground,
|
|
507
|
-
color: styles.colors.primaryColor,
|
|
508
|
-
fontFamily: styles.font.fontFamily
|
|
509
|
-
}}>
|
|
510
|
-
Custom styled content
|
|
511
|
-
</div>
|
|
512
|
-
)
|
|
513
|
-
}
|
|
514
|
-
```
|
|
515
|
-
|
|
516
|
-
### Multi-entity Whitelabeling
|
|
517
|
-
|
|
518
|
-
```typescript
|
|
519
|
-
import React, { useState } from 'react'
|
|
520
|
-
import { useWhitelabel } from './useWhitelabel'
|
|
521
|
-
|
|
522
|
-
const MultiEntityDemo: React.FC = () => {
|
|
523
|
-
const [selectedEntity, setSelectedEntity] = useState('entity-1')
|
|
524
|
-
|
|
525
|
-
const entities = [
|
|
526
|
-
{ id: 'entity-1', name: 'Company A' },
|
|
527
|
-
{ id: 'entity-2', name: 'Company B' },
|
|
528
|
-
{ id: 'entity-3', name: 'Company C' }
|
|
529
|
-
]
|
|
530
|
-
|
|
531
|
-
const { data: whitelabelData, loading } = useWhitelabel(selectedEntity)
|
|
532
|
-
|
|
533
|
-
return (
|
|
534
|
-
<div>
|
|
535
|
-
<select
|
|
536
|
-
value={selectedEntity}
|
|
537
|
-
onChange={(e) => setSelectedEntity(e.target.value)}
|
|
538
|
-
>
|
|
539
|
-
{entities.map(entity => (
|
|
540
|
-
<option key={entity.id} value={entity.id}>
|
|
541
|
-
{entity.name}
|
|
542
|
-
</option>
|
|
543
|
-
))}
|
|
544
|
-
</select>
|
|
545
|
-
|
|
546
|
-
{loading ? (
|
|
547
|
-
<div>Loading whitelabel data...</div>
|
|
548
|
-
) : (
|
|
549
|
-
<WhitelabeledApp entityId={selectedEntity}>
|
|
550
|
-
<div>Content for {selectedEntity}</div>
|
|
551
|
-
</WhitelabeledApp>
|
|
552
|
-
)}
|
|
553
|
-
</div>
|
|
554
|
-
)
|
|
555
|
-
}
|
|
556
|
-
```
|
|
557
|
-
|
|
558
|
-
## Caching Strategy
|
|
559
|
-
|
|
560
|
-
### Memory Cache Implementation
|
|
561
|
-
|
|
562
|
-
```typescript
|
|
563
|
-
// Internal caching implementation (simplified)
|
|
564
|
-
class WhitelabelCache {
|
|
565
|
-
private cache = new Map<string, ProvidedWLSet>()
|
|
566
|
-
private ttl = 5 * 60 * 1000 // 5 minutes
|
|
567
|
-
private timestamps = new Map<string, number>()
|
|
568
|
-
|
|
569
|
-
set(entityId: string, data: ProvidedWLSet): void {
|
|
570
|
-
this.cache.set(entityId, data)
|
|
571
|
-
this.timestamps.set(entityId, Date.now())
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
get(entityId: string): ProvidedWLSet | undefined {
|
|
575
|
-
const timestamp = this.timestamps.get(entityId)
|
|
576
|
-
if (timestamp && Date.now() - timestamp > this.ttl) {
|
|
577
|
-
this.cache.delete(entityId)
|
|
578
|
-
this.timestamps.delete(entityId)
|
|
579
|
-
return undefined
|
|
580
|
-
}
|
|
581
|
-
return this.cache.get(entityId)
|
|
582
|
-
}
|
|
583
|
-
|
|
584
|
-
clear(): void {
|
|
585
|
-
this.cache.clear()
|
|
586
|
-
this.timestamps.clear()
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
invalidate(entityId: string): void {
|
|
590
|
-
this.cache.delete(entityId)
|
|
591
|
-
this.timestamps.delete(entityId)
|
|
592
|
-
}
|
|
593
|
-
}
|
|
594
|
-
```
|
|
595
|
-
|
|
596
|
-
### Cache Management
|
|
597
|
-
|
|
598
|
-
```typescript
|
|
599
|
-
// Utility functions for cache management
|
|
600
|
-
export const clearWhitelabelCache = (context: Context) => {
|
|
601
|
-
const wlService = context.service<WlWebService>('wl-web-service')
|
|
602
|
-
// Cache clearing would be implemented in the service
|
|
603
|
-
}
|
|
604
|
-
|
|
605
|
-
export const preloadWhitelabeling = async (
|
|
606
|
-
context: Context,
|
|
607
|
-
entityIds: string[]
|
|
608
|
-
) => {
|
|
609
|
-
const wlService = context.service<WlWebService>('wl-web-service')
|
|
610
|
-
|
|
611
|
-
// Preload whitelabeling data for multiple entities
|
|
612
|
-
await Promise.all(
|
|
613
|
-
entityIds.map(entityId => wlService.load(entityId))
|
|
614
|
-
)
|
|
615
|
-
}
|
|
616
|
-
```
|
|
617
|
-
|
|
618
|
-
## Error Handling
|
|
619
|
-
|
|
620
|
-
### Graceful Error Handling
|
|
621
|
-
|
|
622
|
-
```typescript
|
|
623
|
-
const ErrorBoundaryWhitelabel: React.FC<{
|
|
624
|
-
children: React.ReactNode
|
|
625
|
-
fallback?: React.ReactNode
|
|
626
|
-
}> = ({ children, fallback }) => {
|
|
627
|
-
return (
|
|
628
|
-
<ErrorBoundary
|
|
629
|
-
fallback={fallback || <div>Failed to load branding</div>}
|
|
630
|
-
onError={(error) => {
|
|
631
|
-
console.error('Whitelabeling error:', error)
|
|
632
|
-
// Report to error tracking service
|
|
633
|
-
}}
|
|
634
|
-
>
|
|
635
|
-
{children}
|
|
636
|
-
</ErrorBoundary>
|
|
637
|
-
)
|
|
638
|
-
}
|
|
639
|
-
|
|
640
|
-
// Usage
|
|
641
|
-
<ErrorBoundaryWhitelabel fallback={<DefaultBranding />}>
|
|
642
|
-
<WhitelabeledApp entityId={entityId}>
|
|
643
|
-
<AppContent />
|
|
644
|
-
</WhitelabeledApp>
|
|
645
|
-
</ErrorBoundaryWhitelabel>
|
|
646
|
-
```
|
|
647
|
-
|
|
648
|
-
## Performance Optimization
|
|
649
|
-
|
|
650
|
-
### Lazy Loading and Code Splitting
|
|
651
|
-
|
|
652
|
-
```typescript
|
|
653
|
-
import React, { lazy, Suspense } from 'react'
|
|
654
|
-
|
|
655
|
-
// Lazy load whitelabel components
|
|
656
|
-
const WhitelabeledDashboard = lazy(() => import('./WhitelabeledDashboard'))
|
|
657
|
-
|
|
658
|
-
const App: React.FC = () => (
|
|
659
|
-
<Suspense fallback={<div>Loading...</div>}>
|
|
660
|
-
<WhitelabeledDashboard entityId="company-123" />
|
|
661
|
-
</Suspense>
|
|
662
|
-
)
|
|
663
|
-
```
|
|
664
|
-
|
|
665
|
-
### Memoization
|
|
666
|
-
|
|
667
|
-
```typescript
|
|
668
|
-
import React, { memo, useMemo } from 'react'
|
|
669
|
-
|
|
670
|
-
const MemoizedWhitelabelComponent = memo<{ entityId: string }>(({ entityId }) => {
|
|
671
|
-
const { data } = useWhitelabel(entityId, 'company-provider')
|
|
672
|
-
|
|
673
|
-
const memoizedContent = useMemo(() => {
|
|
674
|
-
if (!data) return null
|
|
675
|
-
|
|
676
|
-
return (
|
|
677
|
-
<div>
|
|
678
|
-
<h1>{data.fullName}</h1>
|
|
679
|
-
<p>{data.description}</p>
|
|
680
|
-
</div>
|
|
681
|
-
)
|
|
682
|
-
}, [data])
|
|
683
|
-
|
|
684
|
-
return memoizedContent
|
|
685
|
-
})
|
|
686
|
-
```
|
|
687
|
-
|
|
688
|
-
## Integration with OwlMeans Ecosystem
|
|
689
|
-
|
|
690
|
-
### Context Integration
|
|
691
|
-
```typescript
|
|
692
|
-
import { makeWebContext } from '@owlmeans/web-client'
|
|
693
|
-
|
|
694
|
-
const context = makeWebContext(config)
|
|
695
|
-
const wlService = context.service<WlWebService>('wl-web-service')
|
|
696
|
-
```
|
|
697
|
-
|
|
698
|
-
### Module System Integration
|
|
699
|
-
```typescript
|
|
700
|
-
import { modules } from '@owlmeans/web-wl'
|
|
701
|
-
|
|
702
|
-
// Modules are automatically integrated via service
|
|
703
|
-
context.registerService(wlService)
|
|
704
|
-
```
|
|
705
|
-
|
|
706
|
-
### Client Integration
|
|
707
|
-
```typescript
|
|
708
|
-
import { useContext } from '@owlmeans/client'
|
|
709
|
-
|
|
710
|
-
const wlService = useContext().service<WlWebService>('wl-web-service')
|
|
711
|
-
```
|
|
712
|
-
|
|
713
|
-
## Best Practices
|
|
714
|
-
|
|
715
|
-
1. **Caching**: Implement appropriate caching strategies for performance
|
|
716
|
-
2. **Error Handling**: Provide graceful fallbacks for failed whitelabeling requests
|
|
717
|
-
3. **Performance**: Use memoization and lazy loading for large applications
|
|
718
|
-
4. **User Experience**: Show loading states during whitelabeling data fetch
|
|
719
|
-
5. **Accessibility**: Ensure dynamic content maintains accessibility standards
|
|
720
|
-
6. **Testing**: Test with different entity configurations and error scenarios
|
|
47
|
+
- `DEFAULT_ALIAS` — `'wl-web-serivce'`
|
|
721
48
|
|
|
722
49
|
## Related Packages
|
|
723
50
|
|
|
724
|
-
-
|
|
725
|
-
-
|
|
726
|
-
-
|
|
727
|
-
-
|
|
728
|
-
- **@owlmeans/client-module**: Client module system for API calls
|
|
729
|
-
|
|
730
|
-
## TypeScript Support
|
|
731
|
-
|
|
732
|
-
This package is written in TypeScript and provides full type safety:
|
|
733
|
-
|
|
734
|
-
```typescript
|
|
735
|
-
import type {
|
|
736
|
-
WlWebService,
|
|
737
|
-
ProvidedWLSet,
|
|
738
|
-
Config,
|
|
739
|
-
Context
|
|
740
|
-
} from '@owlmeans/web-wl'
|
|
741
|
-
|
|
742
|
-
const wlService: WlWebService = makeWlService()
|
|
743
|
-
const context: Context = makeWebContext(config)
|
|
744
|
-
```
|
|
51
|
+
- [`@owlmeans/wled`](../wled) — shared whitelabel types and constants
|
|
52
|
+
- [`@owlmeans/client-wl`](../client-wl) — client-side whitelabel placeholder
|
|
53
|
+
- [`@owlmeans/web-client`](../web-client) — base web context this service runs in
|
|
54
|
+
- [`@owlmeans/web-panel`](../web-panel) — typical app-side `makeContext` foundation
|
package/build/modules.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export declare const modules:
|
|
1
|
+
import type { ClientEntrypoint } from '@owlmeans/client-entrypoint';
|
|
2
|
+
export declare const modules: ClientEntrypoint<unknown>[];
|
|
3
3
|
//# sourceMappingURL=modules.d.ts.map
|
package/build/modules.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"modules.d.ts","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAA;AAKnE,eAAO,MAAM,OAAO,EAAgB,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAA"}
|
package/build/modules.js
CHANGED
package/build/modules.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modules.js","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"modules.js","sourceRoot":"","sources":["../src/modules.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,6BAA6B,CAAA;AAErD,OAAO,EAAE,UAAU,EAAE,OAAO,IAAI,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAEjE,OAAO,CAAC,SAAS,EAAE,UAAU,CAAC,CAAA;AAE9B,MAAM,CAAC,MAAM,OAAO,GAAG,SAAwC,CAAA"}
|
package/build/service.js
CHANGED
|
@@ -9,7 +9,7 @@ export const makeWlService = (alias = DEFAULT_ALIAS) => {
|
|
|
9
9
|
return cache[entityId];
|
|
10
10
|
}
|
|
11
11
|
const context = service.assertCtx();
|
|
12
|
-
const module = context.
|
|
12
|
+
const module = context.entrypoint(WL_PROVIDE);
|
|
13
13
|
const [wlSet] = await module.call({ params: { entity: entityId } });
|
|
14
14
|
return cache[entityId] = wlSet;
|
|
15
15
|
},
|
package/build/service.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAG3C,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,QAAgB,aAAa,EAAgB,EAAE;IAC3E,MAAM,KAAK,GAA0C,EAAE,CAAA;IAEvD,MAAM,OAAO,GAAiB,aAAa,CAAe,KAAK,EAAE;QAC/D,IAAI,EAAE,KAAK,EAAC,QAAQ,EAAC,EAAE;YACrB,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC;gBAC5B,OAAO,KAAK,CAAC,QAAQ,CAAC,CAAA;YACxB,CAAC;YAED,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,EAAmB,CAAA;YAEpD,MAAM,MAAM,GAAG,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAE3C,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAA;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAG3C,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,QAAgB,aAAa,EAAgB,EAAE;IAC3E,MAAM,KAAK,GAA0C,EAAE,CAAA;IAEvD,MAAM,OAAO,GAAiB,aAAa,CAAe,KAAK,EAAE;QAC/D,IAAI,EAAE,KAAK,EAAC,QAAQ,EAAC,EAAE;YACrB,IAAI,KAAK,CAAC,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC;gBAC5B,OAAO,KAAK,CAAC,QAAQ,CAAC,CAAA;YACxB,CAAC;YAED,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,EAAmB,CAAA;YAEpD,MAAM,MAAM,GAAG,OAAO,CAAC,UAAU,CAAkC,UAAU,CAAC,CAAA;YAC9E,MAAM,CAAC,KAAK,CAAC,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAA;YAEnE,OAAO,KAAK,CAAC,QAAQ,CAAC,GAAG,KAA2B,CAAA;QACtD,CAAC;QAED,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC;KAChC,CAAC,CAAA;IAEF,OAAO,OAAO,CAAA;AAChB,CAAC,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@owlmeans/web-wl",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -21,18 +21,19 @@
|
|
|
21
21
|
}
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@owlmeans/client": "^0.1.
|
|
25
|
-
"@owlmeans/client-
|
|
26
|
-
"@owlmeans/context": "^0.1.
|
|
27
|
-
"@owlmeans/wled": "^0.1.
|
|
24
|
+
"@owlmeans/client": "^0.1.3",
|
|
25
|
+
"@owlmeans/client-entrypoint": "^0.1.3",
|
|
26
|
+
"@owlmeans/context": "^0.1.3",
|
|
27
|
+
"@owlmeans/wled": "^0.1.3"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"react": "*"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
+
"@owlmeans/dep-config": "workspace:*",
|
|
33
34
|
"@types/react": "^19.2.7",
|
|
34
35
|
"nodemon": "^3.1.11",
|
|
35
|
-
"typescript": "^
|
|
36
|
+
"typescript": "^6.0.2"
|
|
36
37
|
},
|
|
37
38
|
"publishConfig": {
|
|
38
39
|
"access": "public"
|
package/src/modules.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
|
|
2
|
-
import { elevate } from '@owlmeans/client-
|
|
3
|
-
import type {
|
|
2
|
+
import { elevate } from '@owlmeans/client-entrypoint'
|
|
3
|
+
import type { ClientEntrypoint } from '@owlmeans/client-entrypoint'
|
|
4
4
|
import { WL_PROVIDE, modules as wlModules } from '@owlmeans/wled'
|
|
5
5
|
|
|
6
6
|
elevate(wlModules, WL_PROVIDE)
|
|
7
7
|
|
|
8
|
-
export const modules = wlModules as
|
|
8
|
+
export const modules = wlModules as ClientEntrypoint<unknown>[]
|
package/src/service.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ClientEntrypoint } from '@owlmeans/client-entrypoint'
|
|
2
2
|
import { DEFAULT_ALIAS } from './consts.js'
|
|
3
3
|
import type { Config, Context, ProvidedWLSet, WlWebService } from './types.js'
|
|
4
4
|
import { createService } from '@owlmeans/context'
|
|
@@ -16,7 +16,7 @@ export const makeWlService = (alias: string = DEFAULT_ALIAS): WlWebService => {
|
|
|
16
16
|
|
|
17
17
|
const context = service.assertCtx<Config, Context>()
|
|
18
18
|
|
|
19
|
-
const module = context.
|
|
19
|
+
const module = context.entrypoint<ClientEntrypoint<ProvidedWLSet>>(WL_PROVIDE)
|
|
20
20
|
const [wlSet] = await module.call({ params: { entity: entityId } })
|
|
21
21
|
|
|
22
22
|
return cache[entityId] = wlSet as ProvidedWLSet<any>
|
package/tsconfig.json
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"extends": [
|
|
3
|
-
"
|
|
4
|
-
"
|
|
3
|
+
"@owlmeans/dep-config/tsconfig.base.json",
|
|
4
|
+
"@owlmeans/dep-config/tsconfig.react.json"
|
|
5
5
|
],
|
|
6
6
|
"compilerOptions": {
|
|
7
|
-
"rootDir": "./src/",
|
|
8
|
-
"outDir": "./build/"
|
|
9
|
-
"moduleResolution": "Bundler",
|
|
7
|
+
"rootDir": "./src/",
|
|
8
|
+
"outDir": "./build/"
|
|
10
9
|
},
|
|
11
|
-
"exclude": [
|
|
12
|
-
"./dist/**/*",
|
|
13
|
-
"./build/**/*",
|
|
14
|
-
"./*.ts"
|
|
15
|
-
]
|
|
10
|
+
"exclude": ["./dist/**/*", "./build/**/*", "./*.ts"]
|
|
16
11
|
}
|