@visma-swno/customer-onboarding-wizard 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,182 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>Customer Onboarding Wizard</title>
8
+
9
+ <style>
10
+ /* Load Inter font - Self-hosted */
11
+ @font-face {
12
+ font-family: 'Inter';
13
+ src: url('/assets/Inter-Regular.woff2') format('woff2');
14
+ font-weight: 400;
15
+ font-style: normal;
16
+ font-display: swap;
17
+ }
18
+
19
+ @font-face {
20
+ font-family: 'Inter';
21
+ src: url('/assets/Inter-Medium.woff2') format('woff2');
22
+ font-weight: 500;
23
+ font-style: normal;
24
+ font-display: swap;
25
+ }
26
+
27
+ @font-face {
28
+ font-family: 'Inter';
29
+ src: url('/assets/Inter-SemiBold.woff2') format('woff2');
30
+ font-weight: 600;
31
+ font-style: normal;
32
+ font-display: swap;
33
+ }
34
+
35
+ /* Configure component font */
36
+ vsn-customer-onboarding-wizard {
37
+ --font-family: Inter, ui-sans-serif, system-ui, sans-serif;
38
+ }
39
+
40
+ /* Basic page styles */
41
+ body {
42
+ font-family: 'Inter', sans-serif;
43
+ margin: 0;
44
+ padding: 0;
45
+ }
46
+ </style>
47
+ <script type="module" crossorigin src="/assets/index.js"></script>
48
+ </head>
49
+
50
+ <body>
51
+ <!-- Your web components go here -->
52
+ <div id="app">
53
+ <vsn-customer-onboarding-wizard customerId="CUST-12345" baseUrl="http://localhost:3000" taskId="demo-task-12345"
54
+ locale="en-US" step="welcome" adminUrl="https://admin-user-access.stag.visma.net/context/customer/user-access">
55
+ </vsn-customer-onboarding-wizard>
56
+ </div>
57
+
58
+ <script>
59
+ // ============================================
60
+ // AUTHENTICATION TOKEN HANDLING
61
+ // ============================================
62
+ // CRITICAL: This listener MUST be set up BEFORE the component loads
63
+ // The component requests an authentication token via the 'request-token' event.
64
+ // The host application must listen for this event and provide the token.
65
+ //
66
+ // SECURITY: Follow OAuth2 best practices
67
+ // - Store tokens in sessionStorage (cleared on tab close)
68
+ // - NEVER log tokens to console in production
69
+ // - Use HTTPS in production environments
70
+ // - Implement proper error handling
71
+
72
+ // Example 1: Synchronous token retrieval from sessionStorage
73
+ // Uncomment to use:
74
+ /*
75
+ document.addEventListener('request-token', (event) => {
76
+ const token = sessionStorage.getItem('accessToken');
77
+
78
+ if (event.detail && typeof event.detail.callback === 'function') {
79
+ event.detail.callback(token);
80
+ }
81
+ });
82
+ */
83
+
84
+ // Example 2: Asynchronous token retrieval from server
85
+ // Currently active for demonstration
86
+ document.addEventListener('request-token', async (event) => {
87
+ try {
88
+ // Using Vite proxy - requests to /v1/* are proxied to http://localhost:3000
89
+ // In production, configure your hosting to proxy API requests or use full API URL
90
+ const response = await fetch('/v1/me/token', {
91
+ headers: {
92
+ 'Accept': 'application/json'
93
+ }
94
+ });
95
+
96
+ if (!response.ok) {
97
+ throw new Error(`Token retrieval failed: ${response.status}`);
98
+ }
99
+
100
+ const data = await response.json();
101
+
102
+ // Validate response structure
103
+ if (!data?.accessToken) {
104
+ throw new Error('Invalid token response format');
105
+ }
106
+
107
+ if (event.detail && typeof event.detail.callback === 'function') {
108
+ event.detail.callback(data.accessToken);
109
+ }
110
+ } catch (error) {
111
+ console.error('Failed to retrieve authentication token');
112
+
113
+ if (event.detail && typeof event.detail.callback === 'function') {
114
+ event.detail.callback(null);
115
+ }
116
+ }
117
+ });
118
+ </script>
119
+
120
+ <script>
121
+ // ============================================
122
+ // HTTP ERROR HANDLING
123
+ // ============================================
124
+ // The component emits 'http-error' events ONLY for authentication/authorization errors:
125
+ // - 401 Unauthorized: Token expired/invalid
126
+ // - 403 Forbidden: Insufficient permissions
127
+ //
128
+ // Other errors (400, 422, 500, network errors) are automatically displayed
129
+ // by the component via an internal error banner. No handling required by host app.
130
+ //
131
+ // Host application responsibilities:
132
+ // - Handle 401: Refresh token and retry request
133
+ // - Handle 403: Redirect to access request page or show permission denied
134
+
135
+ document.addEventListener('http-error', async (event) => {
136
+ const { status, statusText, endpoint, retry } = event.detail;
137
+
138
+ console.log(`Auth Error: ${status} ${statusText}`, { endpoint });
139
+
140
+ // Handle 401 Unauthorized - refresh token and retry
141
+ if (status === 401) {
142
+ console.log('Token expired. Attempting to refresh...');
143
+
144
+ try {
145
+ // Fetch new token
146
+ const response = await fetch('/v1/me/token', {
147
+ headers: { 'Accept': 'application/json' }
148
+ });
149
+
150
+ if (response.ok) {
151
+ const data = await response.json();
152
+
153
+ // Update token in component
154
+ const wizard = document.querySelector('vsn-customer-onboarding-wizard');
155
+ if (wizard && wizard.updateToken) {
156
+ wizard.updateToken(data.accessToken);
157
+ }
158
+
159
+ console.log('Token refreshed. Retrying request...');
160
+
161
+ // Retry the failed request with new token
162
+ await retry();
163
+ }
164
+ } catch (error) {
165
+ console.error('Failed to refresh token. Redirecting to login...');
166
+ // In production: redirect to login page
167
+ // window.location.href = '/login';
168
+ }
169
+ }
170
+
171
+ // Handle 403 Forbidden
172
+ if (status === 403) {
173
+ console.error('Permission denied:', endpoint);
174
+ // In production: redirect or show modal
175
+ // window.location.href = '/request-access';
176
+ }
177
+ });
178
+ </script>
179
+
180
+ </body>
181
+
182
+ </html>
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@visma-swno/customer-onboarding-wizard",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "dist/assets/index.js",
6
+ "types": "dist/assets/index.d.ts",
7
+ "files": [
8
+ "dist/**/*"
9
+ ],
10
+ "scripts": {
11
+ "dev": "vite --open",
12
+ "build": "tsc && vite build --emptyOutDir",
13
+ "preview": "vite preview",
14
+ "mock:server": "node server.js",
15
+ "test": "vitest run",
16
+ "test:watch": "vitest",
17
+ "test:coverage": "vitest run --coverage",
18
+ "generate:tokens": "node scripts/generate-tokens.mjs"
19
+ },
20
+ "peerDependencies": {
21
+ "@lit/task": "^1.0.0",
22
+ "lit": "^3.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@lit/task": "^1.0.2",
26
+ "@testing-library/dom": "^10.4.0",
27
+ "@vitest/coverage-v8": "^3.1.4",
28
+ "@vsn-ux/gaia-styles": "^0.6.6",
29
+ "cors": "^2.8.5",
30
+ "express": "^4.21.2",
31
+ "happy-dom": "^20.8.9",
32
+ "jsdom": "^26.1.0",
33
+ "lit": "^3.2.1",
34
+ "typescript": "~5.7.2",
35
+ "vite": "^6.1.0",
36
+ "vitest": "^3.1.4"
37
+ },
38
+ "publishConfig": {
39
+ "access": "public"
40
+ }
41
+ }