spry-apps-dropdown 3.0.1 → 3.0.2

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 CHANGED
@@ -1,71 +1,17 @@
1
1
  # spry-apps-dropdown
2
- ## Integration Guide
3
2
 
4
- ### 1. OIDC/Keycloak Setup
5
-
6
- This package requires OIDC authentication. Use `react-oidc-context` or `@react-keycloak/web` for best results.
7
-
8
- **Example OIDC config:**
9
-
10
- ```tsx
11
- const oidcConfig = {
12
- authority: 'https://auth.sprylogin.com/realms/sprylogin',
13
- client_id: 'my-app-client',
14
- redirect_uri: window.location.origin,
15
- post_logout_redirect_uri: window.location.origin,
16
- onSigninCallback: () => {
17
- window.history.replaceState({}, document.title, window.location.pathname)
18
- },
19
- }
20
- ```
21
-
22
- ### 2. Multi-Account Management
23
-
24
- The package supports multi-account switching, add-account, and soft logout. All account state is stored in localStorage for cross-app/iframe sync.
25
-
26
- **Syncing across apps/iframe:**
27
- - Use the same localStorage key (`spry_accounts`) in all apps.
28
- - When an account is added, removed, or switched, update localStorage and reload state in all apps.
29
- - For iframe/bridge integration, use a shared localStorage or a custom syncBridge client.
30
-
31
- ### 3. Add Another Account Flow
32
-
33
- When adding another account:
34
- - A flag (`spry_add_account_pending`) is set in localStorage.
35
- - After soft logout, the app checks this flag and triggers OIDC login for the new account.
36
- - On callback, the new account is added and set as active.
37
-
38
- ### 4. Cross-App Sync
39
-
40
- To sync active account and account removal across apps:
41
- - Listen for changes to `spry_accounts` in localStorage (using the `storage` event or polling).
42
- - When the accounts state changes, update your app's state and UI.
43
- - If using an iframe bridge, ensure the bridge propagates account changes to all connected apps.
44
-
45
- ### 5. Troubleshooting
46
-
47
- - If new accounts are not added after login, ensure your OIDC provider updates the user context before the callback runs.
48
- - If cross-app sync is not working, check that all apps use the same localStorage key and listen for changes.
49
- - For CORS or redirect issues, verify your OIDC config and Keycloak setup.
50
-
51
- ### 6. Example Integration
52
-
53
- See the Quick Start and Usage sections below for full code examples.
54
-
55
-
56
- A React component library for displaying Spry apps dropdown and profile menu with dynamic API integration. Features a Google-style UI with beautiful animations.
3
+ A React component library for Spry apps dropdown and profile menu with **multi-account switching**. Features a Google-style UI with beautiful animations and automatic Keycloak authentication.
57
4
 
58
5
  ## Features
59
6
 
60
7
  - 🎨 Beautiful Material-UI design with smooth animations (Google-inspired)
61
- - 🔄 Automatic data fetching from API
62
- - 💾 Built-in caching and refetching
8
+ - 👥 **Multi-account switching** - Users can log into multiple accounts and switch between them
9
+ - 🔄 Automatic data fetching from API with caching
10
+ - 🔐 Built-in Keycloak/OIDC authentication via react-oidc-context
63
11
  - ⚡ Loading and error states
64
12
  - 📱 Responsive layout
65
13
  - 🎯 TypeScript support
66
- - 🔌 Easy integration
67
- - 👤 Profile menu with avatar, account management, and sign out (always shows Manage your Account)
68
- - 🎛️ Combined TopBar component with both apps dropdown and profile menu
14
+ - 🔌 Easy integration - Just 3 steps!
69
15
 
70
16
  ## Installation
71
17
 
@@ -73,31 +19,33 @@ A React component library for displaying Spry apps dropdown and profile menu wit
73
19
  npm install spry-apps-dropdown react-oidc-context oidc-client-ts
74
20
  ```
75
21
 
76
- This package expects `react`, `react-dom`, `react-oidc-context`, and `oidc-client-ts` to be provided by the consuming app.
22
+ **Peer Dependencies:**
23
+ - `react` ^18.0.0 || ^19.0.0
24
+ - `react-dom` ^18.0.0 || ^19.0.0
25
+ - `react-oidc-context` ^3.3.0
26
+ - `oidc-client-ts` ^1.0.0
27
+
28
+ ---
29
+
30
+ ## Quick Start (3 Steps)
77
31
 
78
- ## React OIDC Quick Start (Lowest Effort)
32
+ ### Step 1: Wrap Your App with `SpryAuthProvider`
79
33
 
80
34
  ```tsx
35
+ // main.tsx or index.tsx
81
36
  import React from 'react'
82
37
  import ReactDOM from 'react-dom/client'
83
- import { SpryAuthProvider, TopBar, useSpryAccountManager } from 'spry-apps-dropdown'
38
+ import { SpryAuthProvider } from 'spry-apps-dropdown'
39
+ import App from './App'
84
40
 
85
41
  const oidcConfig = {
86
42
  authority: 'https://auth.sprylogin.com/realms/sprylogin',
87
- client_id: 'my-app-client',
43
+ client_id: 'your-client-id',
88
44
  redirect_uri: window.location.origin,
89
45
  post_logout_redirect_uri: window.location.origin,
90
- }
91
-
92
- function App() {
93
- const accountManager = useSpryAccountManager()
94
-
95
- return (
96
- <TopBar
97
- apiUrl="https://sprylogin.com/apps-api"
98
- accountManager={accountManager}
99
- />
100
- )
46
+ onSigninCallback: () => {
47
+ window.history.replaceState({}, document.title, window.location.pathname)
48
+ },
101
49
  }
102
50
 
103
51
  ReactDOM.createRoot(document.getElementById('root')!).render(
@@ -109,253 +57,252 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
109
57
  )
110
58
  ```
111
59
 
112
- ## Usage
113
-
114
- ### TopBar Component (Easiest - Recommended for v2.0+)
115
-
116
- The `TopBar` component combines both apps dropdown and profile menu in a single component, just like Google's interface:
60
+ ### Step 2: Use `useSpryAccountManager` Hook
117
61
 
118
62
  ```tsx
119
- import { TopBar } from 'spry-apps-dropdown'
63
+ // App.tsx
64
+ import { useSpryAccountManager } from 'spry-apps-dropdown'
65
+
66
+ function App() {
67
+ const accountManager = useSpryAccountManager()
120
68
 
121
- function MyAppBar() {
122
69
  return (
123
- <AppBar>
124
- <Toolbar>
125
- <Typography variant="h6" sx={{ flexGrow: 1 }}>
126
- My App
127
- </Typography>
128
-
129
- <TopBar
130
- apiUrl="https://your-api.com"
131
- onSignOut={() => handleLogout()}
132
- getAuthToken={() => yourAuthToken}
133
- appsRefetchInterval={30000} // 30 seconds
134
- appsCacheTime={30000}
135
- profileRefetchInterval={5 * 60 * 1000} // 5 minutes
136
- profileCacheTime={5 * 60 * 1000}
137
- />
138
- </Toolbar>
139
- </AppBar>
70
+ <div>
71
+ {/* Your app content */}
72
+ </div>
140
73
  )
141
74
  }
142
75
  ```
143
76
 
144
- ### Profile Menu Only
145
-
146
- Use just the profile menu component:
77
+ ### Step 3: Add the TopBar Component
147
78
 
148
79
  ```tsx
149
- import { ProfileMenuConnected } from 'spry-apps-dropdown'
80
+ // App.tsx
81
+ import { TopBar, useSpryAuth, useSpryAccountManager } from 'spry-apps-dropdown'
82
+
83
+ function App() {
84
+ const auth = useSpryAuth()
85
+ const accountManager = useSpryAccountManager()
150
86
 
151
- function MyComponent() {
152
87
  return (
153
- <ProfileMenuConnected
154
- apiUrl="https://your-api.com"
155
- onSignOut={() => handleLogout()}
156
- getAuthToken={() => yourAuthToken}
157
- refetchInterval={5 * 60 * 1000} // 5 minutes
158
- cacheTime={5 * 60 * 1000}
159
- />
88
+ <div>
89
+ <AppBar>
90
+ <Toolbar>
91
+ <Typography variant="h6" sx={{ flexGrow: 1 }}>
92
+ My App
93
+ </Typography>
94
+
95
+ <TopBar
96
+ apiUrl="https://sprylogin.com/apps-api"
97
+ onSignOut={() => auth.signoutRedirect()}
98
+ getAuthToken={() => auth.user?.access_token || null}
99
+ accountManager={accountManager}
100
+ />
101
+ </Toolbar>
102
+ </AppBar>
103
+
104
+ {/* Your app content */}
105
+ </div>
160
106
  )
161
107
  }
162
108
  ```
163
109
 
164
- ### Apps Dropdown Only (Basic Usage)
110
+ **That's it!** Your app now has:
111
+ - ✅ Multi-account switching
112
+ - ✅ Apps dropdown
113
+ - ✅ Profile menu
114
+ - ✅ Automatic authentication
115
+
116
+ ---
165
117
 
166
- Import `AppsDropdownConnected` for automatic API integration:
118
+ ## Using Auth Token in Your API Calls
119
+
120
+ Get the current user's token using `useSpryAuth()`:
167
121
 
168
122
  ```tsx
169
- import { useState, useRef } from 'react'
170
- import { IconButton } from '@mui/material'
171
- import { Apps } from '@mui/icons-material'
172
- import { AppsDropdownConnected } from 'spry-apps-dropdown'
123
+ import { useSpryAuth } from 'spry-apps-dropdown'
173
124
 
174
125
  function MyComponent() {
175
- const [appsMenuOpen, setAppsMenuOpen] = useState(false)
176
- const appsButtonRef = useRef<HTMLButtonElement>(null)
126
+ const auth = useSpryAuth()
177
127
 
178
- return (
179
- <>
180
- <IconButton
181
- ref={appsButtonRef}
182
- onClick={() => setAppsMenuOpen(!appsMenuOpen)}
183
- >
184
- <Apps />
185
- </IconButton>
186
-
187
- <AppsDropdownConnected
188
- apiUrl="https://sprylogin.com/apps-api"
189
- open={appsMenuOpen}
190
- onClose={() => setAppsMenuOpen(false)}
191
- buttonRef={appsButtonRef}
192
- />
193
- </>
194
- )
128
+ useEffect(() => {
129
+ const token = auth.user?.access_token
130
+
131
+ fetch('/api/data', {
132
+ headers: {
133
+ 'Authorization': `Bearer ${token}`
134
+ }
135
+ })
136
+ }, [auth.user])
195
137
  }
196
138
  ```
197
139
 
198
- ### Advanced Usage
199
-
200
- Use `AppsDropdown` directly with custom data source:
140
+ **For React Query:**
201
141
 
202
142
  ```tsx
203
- import { AppsDropdown, useAppsData } from 'spry-apps-dropdown'
143
+ import { useSpryAuth } from 'spry-apps-dropdown'
144
+ import { useQuery } from '@tanstack/react-query'
204
145
 
205
146
  function MyComponent() {
206
- const [appsMenuOpen, setAppsMenuOpen] = useState(false)
207
- const appsButtonRef = useRef<HTMLButtonElement>(null)
208
-
209
- // Use the hook for more control
210
- const { apps, isLoading, error, refetch } = useAppsData('https://sprylogin.com/apps-api', {
211
- refetchInterval: 10 * 60 * 1000, // 10 minutes
212
- cacheTime: 10 * 60 * 1000 // 10 minutes
147
+ const auth = useSpryAuth()
148
+
149
+ const { data } = useQuery({
150
+ queryKey: ['data'],
151
+ queryFn: async () => {
152
+ const token = auth.user?.access_token
153
+ const res = await fetch('/api/data', {
154
+ headers: { 'Authorization': `Bearer ${token}` }
155
+ })
156
+ return res.json()
157
+ },
158
+ enabled: !!auth.user,
213
159
  })
214
-
215
- return (
216
- <>
217
- <IconButton ref={appsButtonRef} onClick={() => setAppsMenuOpen(!appsMenuOpen)}>
218
- <Apps />
219
- </IconButton>
220
-
221
- <AppsDropdown
222
- apps={apps}
223
- open={appsMenuOpen}
224
- onClose={() => setAppsMenuOpen(false)}
225
- buttonRef={appsButtonRef}
226
- isLoading={isLoading}
227
- error={error}
228
- onRetry={refetch}
229
- />
230
- </>
231
- )
232
160
  }
233
161
  ```
234
162
 
163
+ ---
164
+
165
+ ## Multi-Account Switching
166
+
167
+ The package automatically handles multi-account switching:
168
+
169
+ 1. **User clicks "Add Another Account"** in ProfileMenu
170
+ 2. **Redirects to Keycloak** for login (with `prompt=login`)
171
+ 3. **New account is added** to localStorage
172
+ 4. **User can switch** between accounts from ProfileMenu
173
+ 5. **API calls automatically use** the active account's token
174
+
175
+ **No manual code needed** - it just works!
176
+
177
+ ---
178
+
235
179
  ## API Reference
236
180
 
237
- ### `TopBar` (New in v2.0)
181
+ ### `TopBar` Component
238
182
 
239
- Combined component with both apps dropdown and profile menu, positioned side by side like Google's interface.
183
+ Combined apps dropdown + profile menu (recommended).
240
184
 
241
185
  #### Props
242
186
 
243
187
  | Prop | Type | Required | Default | Description |
244
188
  |------|------|----------|---------|-------------|
245
189
  | `apiUrl` | `string` | Yes | - | Base URL for apps API |
190
+ | `accountManager` | `UseAccountManagerReturn` | Yes | - | Account manager from `useSpryAccountManager()` |
246
191
  | `profileApiUrl` | `string` | No | Same as `apiUrl` | Base URL for profile API |
247
192
  | `onSignOut` | `() => void` | No | - | Callback when user signs out |
248
- | `getAuthToken` | `() => string \| null \| Promise<string \| null>` | No | - | Function to get auth token |
193
+ | `getAuthToken` | `() => string \| null` | No | - | Function to get auth token |
249
194
  | `profileHeaders` | `Record<string, string>` | No | - | Custom headers for profile API |
250
195
  | `showAppsDropdown` | `boolean` | No | `true` | Show apps dropdown |
251
196
  | `showProfileMenu` | `boolean` | No | `true` | Show profile menu |
252
- | `appsRefetchInterval` | `number` | No | `300000` (5 min) | Apps refetch interval in ms |
253
- | `appsCacheTime` | `number` | No | `300000` (5 min) | Apps cache duration in ms |
254
- | `profileRefetchInterval` | `number` | No | `300000` (5 min) | Profile refetch interval in ms |
255
- | `profileCacheTime` | `number` | No | `300000` (5 min) | Profile cache duration in ms |
197
+ | `appsRefetchInterval` | `number` | No | `300000` (5 min) | Apps refetch interval (ms) |
198
+ | `appsCacheTime` | `number` | No | `300000` (5 min) | Apps cache duration (ms) |
199
+ | `profileRefetchInterval` | `number` | No | `300000` (5 min) | Profile refetch interval (ms) |
200
+ | `profileCacheTime` | `number` | No | `300000` (5 min) | Profile cache duration (ms) |
256
201
 
257
- ### `ProfileMenuConnected` (New in v2.0)
202
+ ---
258
203
 
259
- Profile menu component with automatic API integration.
204
+ ### `SpryAuthProvider` Component
260
205
 
261
- #### Props
262
-
263
- | Prop | Type | Required | Default | Description |
264
- |------|------|----------|---------|-------------|
265
- | `apiUrl` | `string` | Yes | - | Base URL for profile API |
266
- | `onSignOut` | `() => void` | No | - | Callback when user signs out |
267
- | `onManageAccount` | `() => void` | No | - | Callback for Manage your Account button. If not provided, it redirects to `https://sprylogin.com/my-account/` |
268
- | `getAuthToken` | `() => string \| null \| Promise<string \| null>` | No | - | Function to get auth token |
269
- | `headers` | `Record<string, string>` | No | - | Custom headers for API requests |
270
- | `refetchInterval` | `number` | No | `300000` (5 min) | Auto-refetch interval in ms |
271
- | `cacheTime` | `number` | No | `300000` (5 min) | Cache duration in ms |
272
-
273
- ### Manage Account behavior
274
-
275
- The Manage your Account button is always shown. It uses this order:
276
-
277
- 1. `onManageAccount` callback (if provided)
278
- 2. `profile.manageAccountUrl` (if present)
279
- 3. Default: `https://sprylogin.com/my-account/`
280
-
281
- ### `AppsDropdownConnected`
282
-
283
- The main component with automatic API integration.
206
+ Wraps your app with Keycloak/OIDC authentication.
284
207
 
285
208
  #### Props
286
209
 
287
- | Prop | Type | Required | Default | Description |
288
- |------|------|----------|---------|-------------|
289
- | `apiUrl` | `string` | Yes | - | Base URL of the Spry Apps API |
290
- | `open` | `boolean` | Yes | - | Whether the dropdown is open |
291
- | `onClose` | `() => void` | Yes | - | Callback when dropdown closes |
292
- | `buttonRef` | `React.RefObject<HTMLButtonElement>` | No | - | Ref for positioning |
293
- | `refetchInterval` | `number` | No | `300000` (5 min) | Auto-refetch interval in ms |
294
- | `cacheTime` | `number` | No | `300000` (5 min) | Cache duration in ms |
295
-
296
- ### `AppsDropdown`
210
+ | Prop | Type | Required | Description |
211
+ |------|------|----------|-------------|
212
+ | `config` | `UserManagerSettings` | Yes | OIDC configuration object |
213
+ | `children` | `ReactNode` | Yes | Your app components |
297
214
 
298
- The presentational component for custom implementations.
215
+ **Example config:**
299
216
 
300
- #### Props
217
+ ```tsx
218
+ const oidcConfig = {
219
+ authority: 'https://auth.sprylogin.com/realms/sprylogin',
220
+ client_id: 'my-app-client',
221
+ redirect_uri: window.location.origin,
222
+ post_logout_redirect_uri: window.location.origin,
223
+ onSigninCallback: () => {
224
+ window.history.replaceState({}, document.title, window.location.pathname)
225
+ },
226
+ }
227
+ ```
301
228
 
302
- | Prop | Type | Required | Default | Description |
303
- |------|------|----------|---------|-------------|
304
- | `apps` | `App[]` | Yes | - | Array of apps to display |
305
- | `open` | `boolean` | Yes | - | Whether the dropdown is open |
306
- | `onClose` | `() => void` | Yes | - | Callback when dropdown closes |
307
- | `buttonRef` | `React.RefObject<HTMLButtonElement>` | No | - | Ref for positioning |
308
- | `isLoading` | `boolean` | No | `false` | Loading state |
309
- | `error` | `string \| null` | No | `null` | Error message |
310
- | `onRetry` | `() => void` | No | - | Retry callback |
229
+ ---
311
230
 
312
- ### `useAppsData`
231
+ ### `useSpryAccountManager()` Hook
313
232
 
314
- Hook for fetching apps data.
233
+ Returns account manager for multi-account switching.
315
234
 
316
- #### Parameters
235
+ #### Returns
317
236
 
318
237
  ```typescript
319
- useAppsData(apiUrl: string, options?: {
320
- refetchInterval?: number // Default: 300000 (5 minutes)
321
- cacheTime?: number // Default: 300000 (5 minutes)
322
- })
238
+ {
239
+ accounts: StoredAccount[] // All logged-in accounts
240
+ activeAccount: StoredAccount // Currently active account
241
+ switchAccount: (id: string) => Promise<void>
242
+ addNewAccount: () => Promise<void>
243
+ removeAccount: (id: string) => Promise<void>
244
+ refreshAccountToken: (id: string) => Promise<void>
245
+ }
323
246
  ```
324
247
 
248
+ ---
249
+
250
+ ### `useSpryAuth()` Hook
251
+
252
+ Returns the current authentication state (alias for `useAuth()` from react-oidc-context).
253
+
325
254
  #### Returns
326
255
 
327
256
  ```typescript
328
257
  {
329
- apps: App[] // Array of apps
330
- isLoading: boolean // Loading state
331
- error: string | null // Error message if any
332
- refetch: () => Promise<void> // Manual refetch function
258
+ user: User | null // Current user object
259
+ isAuthenticated: boolean // Is user logged in
260
+ isLoading: boolean // Is auth loading
261
+ signinRedirect: () => Promise<void>
262
+ signoutRedirect: () => Promise<void>
263
+ // ... other react-oidc-context methods
333
264
  }
334
265
  ```
335
266
 
336
- ### Types
267
+ ---
337
268
 
338
- ```typescript
339
- interface App {
340
- id: string
341
- name: string
342
- description: string
343
- url: string
344
- iconUrl: string
345
- color: string
346
- order: number
347
- createdAt: string
348
- updatedAt: string
269
+ ## Advanced: Handling Account Switches
270
+
271
+ If you're using React Query and want to refetch data when accounts switch:
272
+
273
+ ```tsx
274
+ import { useSpryAccountManager } from 'spry-apps-dropdown'
275
+ import { useQueryClient } from '@tanstack/react-query'
276
+ import { useEffect } from 'react'
277
+
278
+ function useMultiAccountManager() {
279
+ const queryClient = useQueryClient()
280
+ const accountManager = useSpryAccountManager()
281
+
282
+ // Listen for account switches and invalidate queries
283
+ useEffect(() => {
284
+ const handleAccountSwitch = () => {
285
+ queryClient.invalidateQueries() // Refetch all data
286
+ }
287
+
288
+ window.addEventListener('auth:account-switched', handleAccountSwitch)
289
+ return () => window.removeEventListener('auth:account-switched', handleAccountSwitch)
290
+ }, [queryClient])
291
+
292
+ return accountManager
349
293
  }
350
294
  ```
351
295
 
296
+ ---
297
+
352
298
  ## Backend API Requirements
353
299
 
354
- The component expects the following API endpoint structure:
300
+ Your backend should expose these endpoints:
355
301
 
356
302
  ### `GET /api/apps`
357
303
 
358
- Returns:
304
+ Returns list of apps for the dropdown.
305
+
359
306
  ```json
360
307
  {
361
308
  "apps": [
@@ -375,26 +322,17 @@ Returns:
375
322
  }
376
323
  ```
377
324
 
378
- ### `GET /api/profile` (New in v2.0)
325
+ ### `GET /api/profile`
379
326
 
380
- Returns user profile information. Supports multiple authentication methods:
327
+ Returns user profile information (requires Bearer token).
381
328
 
382
- **Option 1: JWT Token (via Authorization header)**
383
329
  ```http
384
330
  GET /api/profile
385
331
  Authorization: Bearer <jwt-token>
386
332
  ```
387
333
 
388
- **Option 2: Custom Headers**
389
- ```http
390
- GET /api/profile
391
- X-User-Email: user@example.com
392
- X-User-FirstName: John
393
- X-User-LastName: Doe
394
- X-User-Avatar: https://example.com/avatar.jpg
395
- ```
396
-
397
334
  **Response:**
335
+
398
336
  ```json
399
337
  {
400
338
  "email": "user@example.com",
@@ -405,52 +343,113 @@ X-User-Avatar: https://example.com/avatar.jpg
405
343
  }
406
344
  ```
407
345
 
408
- See [spry-apps-server](https://github.com/your-org/spry-apps-server) for the reference backend implementation.
346
+ ---
409
347
 
410
- ## Styling
348
+ ## Customization
411
349
 
412
- The component uses Material-UI components. You can customize the appearance using the MUI theme:
350
+ ### Custom Theme
413
351
 
414
352
  ```tsx
415
353
  import { ThemeProvider, createTheme } from '@mui/material/styles'
416
354
 
417
355
  const theme = createTheme({
418
- // Your custom theme
356
+ palette: {
357
+ primary: {
358
+ main: '#1a73e8',
359
+ },
360
+ },
419
361
  })
420
362
 
421
- function App() {
422
- return (
423
- <ThemeProvider theme={theme}>
424
- <AppsDropdownConnected {...props} />
425
- </ThemeProvider>
426
- )
427
- }
363
+ <ThemeProvider theme={theme}>
364
+ <SpryAuthProvider config={oidcConfig}>
365
+ <App />
366
+ </SpryAuthProvider>
367
+ </ThemeProvider>
428
368
  ```
429
369
 
430
- ## Caching
370
+ ### Hide Apps Dropdown or Profile Menu
371
+
372
+ ```tsx
373
+ <TopBar
374
+ apiUrl="https://sprylogin.com/apps-api"
375
+ showAppsDropdown={false} // Hide apps dropdown
376
+ showProfileMenu={true} // Show only profile menu
377
+ accountManager={accountManager}
378
+ />
379
+ ```
431
380
 
432
- The component automatically caches apps data in `localStorage` to provide instant loading on subsequent visits. The cache respects the `cacheTime` option.
381
+ ---
433
382
 
434
- ## Error Handling
383
+ ## Troubleshooting
435
384
 
436
- - Network errors are caught and displayed with a retry button
437
- - Cached data is used as fallback when API is unavailable
438
- - Loading spinner shown during initial fetch
385
+ ### "User not authenticated" error
386
+
387
+ Make sure you wrapped your app with `SpryAuthProvider`:
388
+
389
+ ```tsx
390
+ <SpryAuthProvider config={oidcConfig}>
391
+ <App />
392
+ </SpryAuthProvider>
393
+ ```
394
+
395
+ ### Account switching doesn't work
396
+
397
+ Pass the `accountManager` prop to `TopBar`:
398
+
399
+ ```tsx
400
+ const accountManager = useSpryAccountManager()
401
+
402
+ <TopBar accountManager={accountManager} {...otherProps} />
403
+ ```
404
+
405
+ ### API calls use wrong token after switching
406
+
407
+ Use `useSpryAuth()` to get the current token:
408
+
409
+ ```tsx
410
+ const auth = useSpryAuth()
411
+ const token = auth.user?.access_token
412
+ ```
413
+
414
+ ---
415
+
416
+ ## TypeScript Support
417
+
418
+ Full TypeScript support included. Types are automatically available:
419
+
420
+ ```tsx
421
+ import type {
422
+ UseAccountManagerReturn,
423
+ StoredAccount,
424
+ OIDCUser,
425
+ TopBarProps
426
+ } from 'spry-apps-dropdown'
427
+ ```
428
+
429
+ ---
439
430
 
440
431
  ## Browser Support
441
432
 
442
433
  - Modern browsers with ES2020 support
443
434
  - Requires localStorage API
444
435
 
436
+ ---
437
+
445
438
  ## License
446
439
 
447
440
  MIT
448
441
 
449
- ## Contributing
442
+ ---
450
443
 
451
- Contributions are welcome! Please open an issue or submit a pull request.
452
-
453
- ## Related
444
+ ## Related Documentation
454
445
 
446
+ - [INTEGRATION.md](./INTEGRATION.md) - Detailed multi-account integration guide
455
447
  - [Spry Apps Server](https://github.com/your-org/spry-apps-server) - Backend API server
456
- - [Spry Account](https://github.com/your-org/spry-account) - Reference implementation
448
+
449
+ ---
450
+
451
+ ## Support
452
+
453
+ For issues or questions:
454
+ - Open an issue on GitHub
455
+ - Check INTEGRATION.md for detailed guides