@csntgao/uni-base 0.1.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.
- package/.prettierignore +4 -0
- package/.prettierrc.json +6 -0
- package/AGENTS.md +519 -0
- package/CLAUDE.md +512 -0
- package/README.md +734 -0
- package/docs//346/225/260/345/255/227/345/221/230/345/267/245/345/256/214/346/225/264/346/212/275/345/261/211/346/216/245/345/205/245/350/257/264/346/230/216.md +110 -0
- package/docs//347/273/204/347/273/207/345/221/230/345/267/245/345/215/225/351/200/211/347/273/204/344/273/266/346/216/245/345/205/245/350/257/264/346/230/216.md +79 -0
- package/docs//347/273/204/347/273/207/345/221/230/345/267/245/345/244/232/351/200/211/347/273/204/344/273/266/346/216/245/345/205/245/350/257/264/346/230/216.md +93 -0
- package/docs//350/264/246/346/210/267/344/270/216/347/231/273/345/275/225/347/273/204/344/273/266/345/212/250/346/200/201/346/216/245/345/205/245/350/257/264/346/230/216.md +201 -0
- package/eslint.config.js +30 -0
- package/examples/vanilla/index.html +33 -0
- package/examples/vanilla/main.js +247 -0
- package/examples/vanilla/package.json +15 -0
- package/examples/vanilla/public/runtime-config.js +10 -0
- package/examples/vanilla/style.css +99 -0
- package/package.json +31 -0
- package/packages/core/package.json +30 -0
- package/packages/core/src/client.ts +1132 -0
- package/packages/core/src/customer-support-client.ts +825 -0
- package/packages/core/src/customer-support-types.ts +123 -0
- package/packages/core/src/index.ts +79 -0
- package/packages/core/src/notification-center-client.ts +462 -0
- package/packages/core/src/notification-center-types.ts +154 -0
- package/packages/core/src/protocol.ts +237 -0
- package/packages/core/src/types.ts +170 -0
- package/packages/core/tests/client.test.ts +760 -0
- package/packages/core/tests/customer-support-client.test.ts +576 -0
- package/packages/core/tests/fake-websocket.ts +55 -0
- package/packages/core/tests/notification-center-client.test.ts +467 -0
- package/packages/core/tests/protocol.test.ts +124 -0
- package/packages/core/tsconfig.json +7 -0
- package/packages/core/tsup.config.ts +11 -0
- package/packages/web-components/package.json +35 -0
- package/packages/web-components/scripts/verify-runtime-build.mjs +111 -0
- package/packages/web-components/src/account-context.ts +1540 -0
- package/packages/web-components/src/application-switcher-transport.ts +243 -0
- package/packages/web-components/src/application-switcher.ts +1103 -0
- package/packages/web-components/src/chat.ts +1754 -0
- package/packages/web-components/src/customer-support-chat.ts +862 -0
- package/packages/web-components/src/customer-support-transport.ts +269 -0
- package/packages/web-components/src/index.ts +256 -0
- package/packages/web-components/src/notification-center.ts +1066 -0
- package/packages/web-components/src/notification-popover.ts +615 -0
- package/packages/web-components/src/organization-multi-employee-selector.ts +1564 -0
- package/packages/web-components/src/organization-onboarding-transport.ts +54 -0
- package/packages/web-components/src/organization-onboarding.ts +1216 -0
- package/packages/web-components/src/organization-selector.ts +534 -0
- package/packages/web-components/src/organization-single-employee-selector.ts +114 -0
- package/packages/web-components/src/prototype-icons.ts +242 -0
- package/packages/web-components/src/safe-image-url.ts +29 -0
- package/packages/web-components/src/safe-markdown.ts +192 -0
- package/packages/web-components/src/uniplat-base-account-context-transport.ts +625 -0
- package/packages/web-components/src/uniplat-base-application-switcher-transport.ts +418 -0
- package/packages/web-components/src/uniplat-base-application-ticket.ts +254 -0
- package/packages/web-components/src/uniplat-base-identity-ticket.ts +202 -0
- package/packages/web-components/src/uniplat-base-notification-center-transport.ts +378 -0
- package/packages/web-components/src/uniplat-base-organization-multi-employee-selector-transport.ts +394 -0
- package/packages/web-components/src/uniplat-base-organization-onboarding-transport.ts +183 -0
- package/packages/web-components/src/uniplat-base-organization-single-employee-selector-transport.ts +14 -0
- package/packages/web-components/src/user-login-transport.ts +563 -0
- package/packages/web-components/src/user-login.ts +2027 -0
- package/packages/web-components/src/user-menu.ts +880 -0
- package/packages/web-components/tests/account-context.test.ts +798 -0
- package/packages/web-components/tests/application-switcher.test.ts +562 -0
- package/packages/web-components/tests/chat.test.ts +1130 -0
- package/packages/web-components/tests/customer-support-chat.test.ts +428 -0
- package/packages/web-components/tests/customer-support-transport.test.ts +79 -0
- package/packages/web-components/tests/notification-center-transport.test.ts +271 -0
- package/packages/web-components/tests/notification-center.test.ts +429 -0
- package/packages/web-components/tests/notification-popover.test.ts +283 -0
- package/packages/web-components/tests/organization-multi-employee-selector-transport.test.ts +332 -0
- package/packages/web-components/tests/organization-multi-employee-selector.test.ts +225 -0
- package/packages/web-components/tests/organization-onboarding.test.ts +260 -0
- package/packages/web-components/tests/organization-selector.test.ts +127 -0
- package/packages/web-components/tests/organization-single-employee-selector.test.ts +145 -0
- package/packages/web-components/tests/uniplat-base-account-context-transport.test.ts +496 -0
- package/packages/web-components/tests/uniplat-base-application-switcher-transport.test.ts +598 -0
- package/packages/web-components/tests/uniplat-base-application-ticket.test.ts +233 -0
- package/packages/web-components/tests/uniplat-base-identity-ticket.test.ts +226 -0
- package/packages/web-components/tests/uniplat-base-organization-onboarding-transport.test.ts +227 -0
- package/packages/web-components/tests/user-login-transport.test.ts +443 -0
- package/packages/web-components/tests/user-login.test.ts +626 -0
- package/packages/web-components/tests/user-menu.test.ts +271 -0
- package/packages/web-components/tsconfig.json +8 -0
- package/packages/web-components/tsup.config.ts +12 -0
- package/packages/web-components/tsup.runtime.config.ts +37 -0
- package/pnpm-workspace.yaml +6 -0
- package/tsconfig.base.json +21 -0
- package/vitest.config.ts +20 -0
- package//344/272/244/346/216/245/346/226/207/346/241/243.md +254 -0
|
@@ -0,0 +1,880 @@
|
|
|
1
|
+
import { LitElement, css, html, nothing } from 'lit'
|
|
2
|
+
import { prototypeIcon, type PrototypeIconName } from './prototype-icons'
|
|
3
|
+
import { normalizeSafeImageUrl } from './safe-image-url'
|
|
4
|
+
|
|
5
|
+
export type UserMenuOrganizationType = 'company' | 'team'
|
|
6
|
+
|
|
7
|
+
export interface UserMenuOrganization {
|
|
8
|
+
id: string
|
|
9
|
+
name: string
|
|
10
|
+
type: UserMenuOrganizationType
|
|
11
|
+
avatarText?: string
|
|
12
|
+
role?: string
|
|
13
|
+
memberCount?: number
|
|
14
|
+
verificationStatus?: 'unverified' | 'pending' | 'rejected' | 'verified' | 'expired'
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface UserMenuConfig {
|
|
18
|
+
displayName: string
|
|
19
|
+
maskedMobile?: string
|
|
20
|
+
avatarText?: string
|
|
21
|
+
avatarUrl?: string
|
|
22
|
+
organizations: readonly UserMenuOrganization[]
|
|
23
|
+
currentOrganizationId?: string
|
|
24
|
+
organizationCount?: number
|
|
25
|
+
accountSettingsLabel?: string
|
|
26
|
+
employeeInfoLabel?: string
|
|
27
|
+
organizationSectionLabel?: string
|
|
28
|
+
createOrganizationLabel?: string
|
|
29
|
+
logoutLabel?: string
|
|
30
|
+
/** Internal account-context hook. Public standalone usage remains event-based. */
|
|
31
|
+
onCreateOrganization?: () => void
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface UserMenuEventMap {
|
|
35
|
+
'account-settings-requested': CustomEvent<Record<string, never>>
|
|
36
|
+
'employee-info-requested': CustomEvent<{ organizationId: string | null }>
|
|
37
|
+
'organization-selected': CustomEvent<{ organizationId: string }>
|
|
38
|
+
'organization-create-requested': CustomEvent<Record<string, never>>
|
|
39
|
+
'logout-requested': CustomEvent<Record<string, never>>
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface ResolvedUserMenuOrganization {
|
|
43
|
+
id: string
|
|
44
|
+
name: string
|
|
45
|
+
type: UserMenuOrganizationType
|
|
46
|
+
avatarText: string
|
|
47
|
+
role: string
|
|
48
|
+
memberCount: number | null
|
|
49
|
+
verificationStatus: 'unverified' | 'pending' | 'rejected' | 'verified' | 'expired' | null
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface UserMenuViewModel {
|
|
53
|
+
displayName: string
|
|
54
|
+
maskedMobile: string
|
|
55
|
+
avatarText: string
|
|
56
|
+
avatarUrl: string
|
|
57
|
+
organizations: readonly ResolvedUserMenuOrganization[]
|
|
58
|
+
currentOrganizationId: string
|
|
59
|
+
organizationCount: number
|
|
60
|
+
accountSettingsLabel: string
|
|
61
|
+
employeeInfoLabel: string
|
|
62
|
+
organizationSectionLabel: string
|
|
63
|
+
createOrganizationLabel: string
|
|
64
|
+
logoutLabel: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const DEFAULT_LABELS = {
|
|
68
|
+
accountSettings: '个人账号设置',
|
|
69
|
+
employeeInfo: '我的员工信息',
|
|
70
|
+
organizationSection: '切换企业 / 团队',
|
|
71
|
+
createOrganization: '创建企业 / 团队',
|
|
72
|
+
logout: '退出登录',
|
|
73
|
+
} as const
|
|
74
|
+
|
|
75
|
+
export class UniplatUserMenu extends LitElement {
|
|
76
|
+
static override properties = {
|
|
77
|
+
viewModel: { state: true },
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
static override styles = css`
|
|
81
|
+
:host {
|
|
82
|
+
--uniplat-user-menu-width: 330px;
|
|
83
|
+
--uniplat-user-menu-primary: #2e6bf5;
|
|
84
|
+
--uniplat-user-menu-text: #25303f;
|
|
85
|
+
--uniplat-user-menu-muted: #8b98a6;
|
|
86
|
+
--uniplat-user-menu-border: #e4ebf3;
|
|
87
|
+
--uniplat-user-menu-surface: #ffffff;
|
|
88
|
+
--uniplat-user-menu-hover: #f4f7fc;
|
|
89
|
+
--uniplat-user-menu-selected: #f0f5ff;
|
|
90
|
+
--uniplat-user-menu-list-max-height: 288px;
|
|
91
|
+
--uniplat-user-menu-font-family:
|
|
92
|
+
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
93
|
+
display: block;
|
|
94
|
+
width: var(--uniplat-user-menu-width);
|
|
95
|
+
max-width: 100%;
|
|
96
|
+
color: var(--uniplat-user-menu-text);
|
|
97
|
+
font-family: var(--uniplat-user-menu-font-family);
|
|
98
|
+
line-height: 1.4;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
* {
|
|
102
|
+
box-sizing: border-box;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
button {
|
|
106
|
+
appearance: none;
|
|
107
|
+
font: inherit;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.panel {
|
|
111
|
+
width: 100%;
|
|
112
|
+
padding: 8px;
|
|
113
|
+
border: 1px solid var(--uniplat-user-menu-border);
|
|
114
|
+
border-radius: 14px;
|
|
115
|
+
background: var(--uniplat-user-menu-surface);
|
|
116
|
+
box-shadow: 0 18px 44px rgba(20, 40, 80, 0.16);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.profile {
|
|
120
|
+
display: flex;
|
|
121
|
+
align-items: center;
|
|
122
|
+
gap: 10px;
|
|
123
|
+
padding: 10px 10px 12px;
|
|
124
|
+
border-bottom: 1px solid #edf1f6;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.avatar {
|
|
128
|
+
position: relative;
|
|
129
|
+
display: grid;
|
|
130
|
+
place-items: center;
|
|
131
|
+
flex: 0 0 auto;
|
|
132
|
+
width: 40px;
|
|
133
|
+
height: 40px;
|
|
134
|
+
border-radius: 50%;
|
|
135
|
+
color: #ffffff;
|
|
136
|
+
background: #233043;
|
|
137
|
+
font-size: 15px;
|
|
138
|
+
font-weight: 700;
|
|
139
|
+
overflow: hidden;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.avatar-fallback {
|
|
143
|
+
display: grid;
|
|
144
|
+
width: 100%;
|
|
145
|
+
height: 100%;
|
|
146
|
+
place-items: center;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.avatar-image {
|
|
150
|
+
position: absolute;
|
|
151
|
+
inset: 0;
|
|
152
|
+
display: block;
|
|
153
|
+
width: 100%;
|
|
154
|
+
height: 100%;
|
|
155
|
+
border-radius: inherit;
|
|
156
|
+
object-fit: cover;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.avatar-image[hidden] {
|
|
160
|
+
display: none;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.profile-copy,
|
|
164
|
+
.action-copy,
|
|
165
|
+
.organization-copy {
|
|
166
|
+
min-width: 0;
|
|
167
|
+
flex: 1;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.profile-name {
|
|
171
|
+
display: block;
|
|
172
|
+
overflow: hidden;
|
|
173
|
+
color: #182634;
|
|
174
|
+
font-size: 14.5px;
|
|
175
|
+
font-weight: 700;
|
|
176
|
+
text-overflow: ellipsis;
|
|
177
|
+
white-space: nowrap;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.profile-mobile {
|
|
181
|
+
display: block;
|
|
182
|
+
margin-top: 2px;
|
|
183
|
+
color: var(--uniplat-user-menu-muted);
|
|
184
|
+
font-size: 11.5px;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
.menu-action,
|
|
188
|
+
.organization,
|
|
189
|
+
.footer-action {
|
|
190
|
+
width: 100%;
|
|
191
|
+
border: 0;
|
|
192
|
+
color: inherit;
|
|
193
|
+
background: transparent;
|
|
194
|
+
cursor: pointer;
|
|
195
|
+
text-align: left;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.menu-action:hover,
|
|
199
|
+
.organization:hover,
|
|
200
|
+
.footer-action:hover {
|
|
201
|
+
background: var(--uniplat-user-menu-hover);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.menu-action:focus-visible,
|
|
205
|
+
.organization:focus-visible,
|
|
206
|
+
.footer-action:focus-visible {
|
|
207
|
+
outline: 2px solid color-mix(in srgb, var(--uniplat-user-menu-primary) 55%, transparent);
|
|
208
|
+
outline-offset: -2px;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
.menu-action-icon,
|
|
212
|
+
.menu-action-chevron,
|
|
213
|
+
.check,
|
|
214
|
+
.footer-icon {
|
|
215
|
+
display: grid;
|
|
216
|
+
place-items: center;
|
|
217
|
+
flex: 0 0 auto;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
.menu-actions {
|
|
221
|
+
display: flex;
|
|
222
|
+
flex-direction: column;
|
|
223
|
+
padding: 6px 0 8px;
|
|
224
|
+
border-bottom: 1px solid #edf1f6;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.menu-action {
|
|
228
|
+
display: flex;
|
|
229
|
+
align-items: center;
|
|
230
|
+
gap: 10px;
|
|
231
|
+
width: 100%;
|
|
232
|
+
padding: 9px 10px;
|
|
233
|
+
border: 0;
|
|
234
|
+
border-radius: 9px;
|
|
235
|
+
color: inherit;
|
|
236
|
+
background: transparent;
|
|
237
|
+
cursor: pointer;
|
|
238
|
+
text-align: left;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.menu-action-icon {
|
|
242
|
+
width: 17px;
|
|
243
|
+
height: 17px;
|
|
244
|
+
color: var(--uniplat-user-menu-primary);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.menu-action-title {
|
|
248
|
+
display: block;
|
|
249
|
+
overflow: hidden;
|
|
250
|
+
color: #25303f;
|
|
251
|
+
font-size: 13.5px;
|
|
252
|
+
font-weight: 600;
|
|
253
|
+
text-overflow: ellipsis;
|
|
254
|
+
white-space: nowrap;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.menu-action-subtitle {
|
|
258
|
+
display: block;
|
|
259
|
+
margin-top: 1px;
|
|
260
|
+
overflow: hidden;
|
|
261
|
+
color: #98a4b2;
|
|
262
|
+
font-size: 11.5px;
|
|
263
|
+
text-overflow: ellipsis;
|
|
264
|
+
white-space: nowrap;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.menu-action-chevron {
|
|
268
|
+
width: 15px;
|
|
269
|
+
height: 15px;
|
|
270
|
+
color: #c7d1dd;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.section-title {
|
|
274
|
+
padding: 10px 10px 6px;
|
|
275
|
+
color: #98a4b2;
|
|
276
|
+
font-size: 11.5px;
|
|
277
|
+
font-weight: 700;
|
|
278
|
+
letter-spacing: 1px;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.organization-list {
|
|
282
|
+
display: flex;
|
|
283
|
+
flex-direction: column;
|
|
284
|
+
gap: 6px;
|
|
285
|
+
max-height: var(--uniplat-user-menu-list-max-height);
|
|
286
|
+
padding: 0 2px 4px;
|
|
287
|
+
overflow-x: hidden;
|
|
288
|
+
overflow-y: auto;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.organization {
|
|
292
|
+
display: flex;
|
|
293
|
+
align-items: center;
|
|
294
|
+
gap: 10px;
|
|
295
|
+
padding: 9px 10px;
|
|
296
|
+
border: 1.5px solid var(--uniplat-user-menu-border);
|
|
297
|
+
border-radius: 10px;
|
|
298
|
+
background: #ffffff;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
.organization.selected {
|
|
302
|
+
border-color: var(--uniplat-user-menu-primary);
|
|
303
|
+
background: #f7faff;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
.organization.selected:hover {
|
|
307
|
+
background: #f7faff;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
.organization-icon {
|
|
311
|
+
display: grid;
|
|
312
|
+
place-items: center;
|
|
313
|
+
width: 30px;
|
|
314
|
+
height: 30px;
|
|
315
|
+
flex: none;
|
|
316
|
+
border-radius: 9px;
|
|
317
|
+
color: #5a6673;
|
|
318
|
+
background: #edf1f6;
|
|
319
|
+
font-size: 12.5px;
|
|
320
|
+
font-weight: 800;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.organization-icon svg {
|
|
324
|
+
width: 15px;
|
|
325
|
+
height: 15px;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
.organization-type.company {
|
|
329
|
+
color: var(--uniplat-user-menu-primary);
|
|
330
|
+
background: #e6eefe;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
.organization-type.team {
|
|
334
|
+
color: #0ca678;
|
|
335
|
+
background: #e3f7ef;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.selected .organization-icon.company {
|
|
339
|
+
color: var(--uniplat-user-menu-primary);
|
|
340
|
+
background: #e6eefe;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
.selected .organization-icon.team {
|
|
344
|
+
color: #0ca678;
|
|
345
|
+
background: #e3f7ef;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.organization-heading {
|
|
349
|
+
display: flex;
|
|
350
|
+
align-items: center;
|
|
351
|
+
gap: 5px;
|
|
352
|
+
min-width: 0;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
.verification-status {
|
|
356
|
+
display: inline-flex;
|
|
357
|
+
align-items: center;
|
|
358
|
+
gap: 3px;
|
|
359
|
+
flex: 0 0 auto;
|
|
360
|
+
padding: 1px 5px;
|
|
361
|
+
border-radius: 4px;
|
|
362
|
+
font-size: 9.5px;
|
|
363
|
+
font-weight: 700;
|
|
364
|
+
white-space: nowrap;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.verification-status svg {
|
|
368
|
+
width: 11px;
|
|
369
|
+
height: 11px;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.verification-status.verified {
|
|
373
|
+
color: #0ca678;
|
|
374
|
+
background: #e3f7ef;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
.verification-status.pending {
|
|
378
|
+
color: #1971c2;
|
|
379
|
+
background: #e2effb;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.verification-status.unverified,
|
|
383
|
+
.verification-status.expired {
|
|
384
|
+
color: #e8590c;
|
|
385
|
+
background: #fff4e2;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.verification-status.rejected {
|
|
389
|
+
color: #e5484d;
|
|
390
|
+
background: #fdeced;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.verification-status.team {
|
|
394
|
+
color: #5a6673;
|
|
395
|
+
background: #edf1f6;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
.organization-name {
|
|
399
|
+
min-width: 0;
|
|
400
|
+
flex: 1;
|
|
401
|
+
overflow: hidden;
|
|
402
|
+
font-size: 13px;
|
|
403
|
+
font-weight: 700;
|
|
404
|
+
text-overflow: ellipsis;
|
|
405
|
+
white-space: nowrap;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
.organization-type {
|
|
409
|
+
flex: 0 0 auto;
|
|
410
|
+
padding: 0 5px;
|
|
411
|
+
border-radius: 4px;
|
|
412
|
+
font-size: 9.5px;
|
|
413
|
+
font-weight: 700;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.organization-meta {
|
|
417
|
+
display: block;
|
|
418
|
+
margin-top: 2px;
|
|
419
|
+
overflow: hidden;
|
|
420
|
+
color: var(--uniplat-user-menu-muted);
|
|
421
|
+
font-size: 11px;
|
|
422
|
+
text-overflow: ellipsis;
|
|
423
|
+
white-space: nowrap;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
.organization-meta.attention {
|
|
427
|
+
color: #e8590c;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
.check {
|
|
431
|
+
width: 17px;
|
|
432
|
+
height: 17px;
|
|
433
|
+
color: var(--uniplat-user-menu-primary);
|
|
434
|
+
visibility: hidden;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
.selected .check {
|
|
438
|
+
visibility: visible;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
.empty {
|
|
442
|
+
padding: 14px 10px 16px;
|
|
443
|
+
color: var(--uniplat-user-menu-muted);
|
|
444
|
+
font-size: 12px;
|
|
445
|
+
text-align: center;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.footer {
|
|
449
|
+
display: flex;
|
|
450
|
+
flex-direction: column;
|
|
451
|
+
margin: 6px 4px 0;
|
|
452
|
+
padding-top: 6px;
|
|
453
|
+
border-top: 1px solid #edf1f6;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.footer-action {
|
|
457
|
+
display: flex;
|
|
458
|
+
align-items: center;
|
|
459
|
+
gap: 9px;
|
|
460
|
+
padding: 9px 6px;
|
|
461
|
+
border-radius: 9px;
|
|
462
|
+
color: var(--uniplat-user-menu-primary);
|
|
463
|
+
font-size: 13px;
|
|
464
|
+
font-weight: 600;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
.footer-action.logout {
|
|
468
|
+
color: #5a6673;
|
|
469
|
+
font-weight: 500;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
.footer-action.logout .footer-icon {
|
|
473
|
+
color: #98a4b2;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
.footer-icon {
|
|
477
|
+
width: 16px;
|
|
478
|
+
height: 16px;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
svg {
|
|
482
|
+
display: block;
|
|
483
|
+
width: 100%;
|
|
484
|
+
height: 100%;
|
|
485
|
+
fill: none;
|
|
486
|
+
stroke: currentColor;
|
|
487
|
+
stroke-linecap: round;
|
|
488
|
+
stroke-linejoin: round;
|
|
489
|
+
stroke-width: 2;
|
|
490
|
+
}
|
|
491
|
+
`
|
|
492
|
+
|
|
493
|
+
declare private viewModel: UserMenuViewModel | null
|
|
494
|
+
private onCreateOrganization: (() => void) | null = null
|
|
495
|
+
|
|
496
|
+
constructor() {
|
|
497
|
+
super()
|
|
498
|
+
this.viewModel = null
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
configure(config: UserMenuConfig): void {
|
|
502
|
+
if (!config || !Array.isArray(config.organizations)) {
|
|
503
|
+
throw new TypeError('user-menu requires an organizations array')
|
|
504
|
+
}
|
|
505
|
+
if (
|
|
506
|
+
config.onCreateOrganization !== undefined &&
|
|
507
|
+
typeof config.onCreateOrganization !== 'function'
|
|
508
|
+
) {
|
|
509
|
+
throw new TypeError('user-menu onCreateOrganization must be a function')
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
const displayName = normalizeRequiredText(config.displayName, 'Display name', 80)
|
|
513
|
+
const organizations = normalizeOrganizations(config.organizations)
|
|
514
|
+
const currentOrganizationId = normalizeOptionalText(config.currentOrganizationId, 128)
|
|
515
|
+
const avatarText =
|
|
516
|
+
normalizeOptionalText(config.avatarText, 4) || Array.from(displayName.trim())[0] || '用'
|
|
517
|
+
|
|
518
|
+
this.viewModel = {
|
|
519
|
+
displayName,
|
|
520
|
+
maskedMobile: normalizeMaskedMobile(config.maskedMobile),
|
|
521
|
+
avatarText,
|
|
522
|
+
avatarUrl: normalizeSafeImageUrl(config.avatarUrl, 'user-menu avatar URL'),
|
|
523
|
+
organizations,
|
|
524
|
+
currentOrganizationId,
|
|
525
|
+
organizationCount: normalizeOrganizationCount(config.organizationCount, organizations.length),
|
|
526
|
+
accountSettingsLabel:
|
|
527
|
+
normalizeOptionalText(config.accountSettingsLabel, 40) || DEFAULT_LABELS.accountSettings,
|
|
528
|
+
employeeInfoLabel:
|
|
529
|
+
normalizeOptionalText(config.employeeInfoLabel, 40) || DEFAULT_LABELS.employeeInfo,
|
|
530
|
+
organizationSectionLabel:
|
|
531
|
+
normalizeOptionalText(config.organizationSectionLabel, 40) ||
|
|
532
|
+
DEFAULT_LABELS.organizationSection,
|
|
533
|
+
createOrganizationLabel:
|
|
534
|
+
normalizeOptionalText(config.createOrganizationLabel, 40) ||
|
|
535
|
+
DEFAULT_LABELS.createOrganization,
|
|
536
|
+
logoutLabel: normalizeOptionalText(config.logoutLabel, 40) || DEFAULT_LABELS.logout,
|
|
537
|
+
}
|
|
538
|
+
this.onCreateOrganization = config.onCreateOrganization ?? null
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
override render() {
|
|
542
|
+
const viewModel = this.viewModel
|
|
543
|
+
if (!viewModel) return nothing
|
|
544
|
+
|
|
545
|
+
return html`
|
|
546
|
+
<section class="panel" part="panel" aria-label="账户与组织">
|
|
547
|
+
<header class="profile" part="profile">
|
|
548
|
+
<span class="avatar" part="avatar" aria-hidden="true">
|
|
549
|
+
<span class="avatar-fallback">${viewModel.avatarText}</span>
|
|
550
|
+
${
|
|
551
|
+
viewModel.avatarUrl
|
|
552
|
+
? html`<img
|
|
553
|
+
class="avatar-image"
|
|
554
|
+
part="avatar-image"
|
|
555
|
+
src=${viewModel.avatarUrl}
|
|
556
|
+
alt=""
|
|
557
|
+
referrerpolicy="no-referrer"
|
|
558
|
+
@load=${this.showLoadedAvatar}
|
|
559
|
+
@error=${this.hideBrokenAvatar}
|
|
560
|
+
/>`
|
|
561
|
+
: nothing
|
|
562
|
+
}
|
|
563
|
+
</span>
|
|
564
|
+
<span class="profile-copy">
|
|
565
|
+
<span class="profile-name" part="profile-name">${viewModel.displayName}</span>
|
|
566
|
+
${
|
|
567
|
+
viewModel.maskedMobile
|
|
568
|
+
? html`<span class="profile-mobile" part="profile-mobile"
|
|
569
|
+
>${viewModel.maskedMobile}</span
|
|
570
|
+
>`
|
|
571
|
+
: nothing
|
|
572
|
+
}
|
|
573
|
+
</span>
|
|
574
|
+
</header>
|
|
575
|
+
|
|
576
|
+
<div class="menu-actions" part="menu-actions">
|
|
577
|
+
<button
|
|
578
|
+
class="menu-action account-settings-action"
|
|
579
|
+
part="account-settings-action"
|
|
580
|
+
type="button"
|
|
581
|
+
@click=${this.requestAccountSettings}
|
|
582
|
+
>
|
|
583
|
+
<span class="menu-action-icon" aria-hidden="true">${prototypeIcon('user-cog')}</span>
|
|
584
|
+
<span class="action-copy">
|
|
585
|
+
<span class="menu-action-title">${viewModel.accountSettingsLabel}</span>
|
|
586
|
+
</span>
|
|
587
|
+
<span class="menu-action-chevron" aria-hidden="true"
|
|
588
|
+
>${prototypeIcon('chevron-right')}</span
|
|
589
|
+
>
|
|
590
|
+
</button>
|
|
591
|
+
<button
|
|
592
|
+
class="menu-action employee-info-action"
|
|
593
|
+
part="employee-info-action"
|
|
594
|
+
type="button"
|
|
595
|
+
@click=${this.requestEmployeeInfo}
|
|
596
|
+
>
|
|
597
|
+
<span class="menu-action-icon" aria-hidden="true">${prototypeIcon('id-badge-2')}</span>
|
|
598
|
+
<span class="action-copy">
|
|
599
|
+
<span class="menu-action-title">${viewModel.employeeInfoLabel}</span>
|
|
600
|
+
${this.renderEmployeeInfoSubtitle(viewModel)}
|
|
601
|
+
</span>
|
|
602
|
+
<span class="menu-action-chevron" aria-hidden="true"
|
|
603
|
+
>${prototypeIcon('chevron-right')}</span
|
|
604
|
+
>
|
|
605
|
+
</button>
|
|
606
|
+
</div>
|
|
607
|
+
|
|
608
|
+
<div class="section-title" part="section-title">
|
|
609
|
+
${viewModel.organizationSectionLabel}(${viewModel.organizationCount})
|
|
610
|
+
</div>
|
|
611
|
+
<div class="organization-list" part="organization-list">
|
|
612
|
+
${
|
|
613
|
+
viewModel.organizations.length
|
|
614
|
+
? viewModel.organizations.map((organization) =>
|
|
615
|
+
this.renderOrganization(organization, viewModel.currentOrganizationId),
|
|
616
|
+
)
|
|
617
|
+
: html`<div class="empty" part="empty">暂无可切换的企业或团队</div>`
|
|
618
|
+
}
|
|
619
|
+
</div>
|
|
620
|
+
|
|
621
|
+
<div class="footer create-footer" part="footer create-footer">
|
|
622
|
+
<button
|
|
623
|
+
class="footer-action create-action"
|
|
624
|
+
part="create-action"
|
|
625
|
+
type="button"
|
|
626
|
+
@click=${this.requestOrganizationCreation}
|
|
627
|
+
>
|
|
628
|
+
<span class="footer-icon" aria-hidden="true">${prototypeIcon('plus')}</span>
|
|
629
|
+
<span>${viewModel.createOrganizationLabel}</span>
|
|
630
|
+
</button>
|
|
631
|
+
</div>
|
|
632
|
+
<div class="footer logout-footer" part="logout-footer">
|
|
633
|
+
<button
|
|
634
|
+
class="footer-action logout"
|
|
635
|
+
part="logout-action"
|
|
636
|
+
type="button"
|
|
637
|
+
@click=${this.requestLogout}
|
|
638
|
+
>
|
|
639
|
+
<span class="footer-icon" aria-hidden="true">${prototypeIcon('logout')}</span>
|
|
640
|
+
<span>${viewModel.logoutLabel}</span>
|
|
641
|
+
</button>
|
|
642
|
+
</div>
|
|
643
|
+
</section>
|
|
644
|
+
`
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
private renderOrganization(
|
|
648
|
+
organization: ResolvedUserMenuOrganization,
|
|
649
|
+
currentOrganizationId: string,
|
|
650
|
+
) {
|
|
651
|
+
const selected = organization.id === currentOrganizationId
|
|
652
|
+
const typeLabel = organization.type === 'company' ? '企业' : '团队'
|
|
653
|
+
const meta = organizationMeta(organization)
|
|
654
|
+
return html`
|
|
655
|
+
<button
|
|
656
|
+
class=${`organization${selected ? ' selected' : ''}`}
|
|
657
|
+
part="organization"
|
|
658
|
+
type="button"
|
|
659
|
+
aria-pressed=${String(selected)}
|
|
660
|
+
data-organization-id=${organization.id}
|
|
661
|
+
@click=${() => this.selectOrganization(organization.id)}
|
|
662
|
+
>
|
|
663
|
+
<span
|
|
664
|
+
class=${`organization-icon ${organization.type}`}
|
|
665
|
+
part="organization-icon"
|
|
666
|
+
aria-hidden="true"
|
|
667
|
+
>${prototypeIcon(organization.type === 'company' ? 'building' : 'users')}</span
|
|
668
|
+
>
|
|
669
|
+
<span class="organization-copy">
|
|
670
|
+
<span class="organization-heading">
|
|
671
|
+
<span class="organization-name" part="organization-name">${organization.name}</span>
|
|
672
|
+
<span class=${`organization-type ${organization.type}`} part="organization-type"
|
|
673
|
+
>${typeLabel}</span
|
|
674
|
+
>
|
|
675
|
+
${this.renderVerificationStatus(organization)}
|
|
676
|
+
</span>
|
|
677
|
+
${
|
|
678
|
+
meta
|
|
679
|
+
? html`<span
|
|
680
|
+
class=${`organization-meta${organizationNeedsAttention(organization) ? ' attention' : ''}`}
|
|
681
|
+
part="organization-meta"
|
|
682
|
+
>${meta}</span
|
|
683
|
+
>`
|
|
684
|
+
: nothing
|
|
685
|
+
}
|
|
686
|
+
</span>
|
|
687
|
+
<span class="check" part="organization-check" aria-hidden="true"
|
|
688
|
+
>${prototypeIcon('circle-check-filled')}</span
|
|
689
|
+
>
|
|
690
|
+
</button>
|
|
691
|
+
`
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
private renderVerificationStatus(organization: ResolvedUserMenuOrganization) {
|
|
695
|
+
if (organization.type === 'team') {
|
|
696
|
+
return html`<span class="verification-status team" part="verification-status"
|
|
697
|
+
>${prototypeIcon('users')}无需认证</span
|
|
698
|
+
>`
|
|
699
|
+
}
|
|
700
|
+
const status = organization.verificationStatus ?? 'unverified'
|
|
701
|
+
return html`<span class=${`verification-status ${status}`} part="verification-status"
|
|
702
|
+
>${prototypeIcon(verificationStatusIcon(status))}${verificationStatusLabel(status)}</span
|
|
703
|
+
>`
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
private readonly selectOrganization = (organizationId: string): void => {
|
|
707
|
+
this.dispatchPublicEvent('organization-selected', { organizationId })
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
private renderEmployeeInfoSubtitle(viewModel: UserMenuViewModel) {
|
|
711
|
+
const organization = viewModel.organizations.find(
|
|
712
|
+
(candidate) => candidate.id === viewModel.currentOrganizationId,
|
|
713
|
+
)
|
|
714
|
+
return organization
|
|
715
|
+
? html`<span class="menu-action-subtitle">在 ${organization.name} 的员工信息</span>`
|
|
716
|
+
: nothing
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
private readonly requestAccountSettings = (): void => {
|
|
720
|
+
this.dispatchPublicEvent('account-settings-requested', {})
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
private readonly requestEmployeeInfo = (): void => {
|
|
724
|
+
this.dispatchPublicEvent('employee-info-requested', {
|
|
725
|
+
organizationId: this.viewModel?.currentOrganizationId || null,
|
|
726
|
+
})
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
private readonly requestOrganizationCreation = (): void => {
|
|
730
|
+
if (this.onCreateOrganization) {
|
|
731
|
+
this.onCreateOrganization()
|
|
732
|
+
return
|
|
733
|
+
}
|
|
734
|
+
this.dispatchPublicEvent('organization-create-requested', {})
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
private readonly requestLogout = (): void => {
|
|
738
|
+
this.dispatchPublicEvent('logout-requested', {})
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
private readonly hideBrokenAvatar = (event: Event): void => {
|
|
742
|
+
if (event.currentTarget instanceof HTMLImageElement) event.currentTarget.hidden = true
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
private readonly showLoadedAvatar = (event: Event): void => {
|
|
746
|
+
if (event.currentTarget instanceof HTMLImageElement) event.currentTarget.hidden = false
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
private dispatchPublicEvent<K extends keyof UserMenuEventMap>(
|
|
750
|
+
type: K,
|
|
751
|
+
detail: UserMenuEventMap[K]['detail'],
|
|
752
|
+
): void {
|
|
753
|
+
this.dispatchEvent(
|
|
754
|
+
new CustomEvent(type, {
|
|
755
|
+
detail,
|
|
756
|
+
bubbles: true,
|
|
757
|
+
composed: true,
|
|
758
|
+
}),
|
|
759
|
+
)
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function normalizeOrganizations(
|
|
764
|
+
organizations: readonly UserMenuOrganization[],
|
|
765
|
+
): readonly ResolvedUserMenuOrganization[] {
|
|
766
|
+
const ids = new Set<string>()
|
|
767
|
+
return Object.freeze(
|
|
768
|
+
organizations.map((organization, index) => {
|
|
769
|
+
if (!organization || typeof organization !== 'object') {
|
|
770
|
+
throw new TypeError(`Organization ${index + 1} is invalid`)
|
|
771
|
+
}
|
|
772
|
+
const id = normalizeRequiredText(organization.id, 'Organization ID', 128)
|
|
773
|
+
if (ids.has(id)) throw new TypeError('Organization IDs must be unique')
|
|
774
|
+
ids.add(id)
|
|
775
|
+
if (organization.type !== 'company' && organization.type !== 'team') {
|
|
776
|
+
throw new TypeError('Organization type must be company or team')
|
|
777
|
+
}
|
|
778
|
+
const memberCount = normalizeMemberCount(organization.memberCount)
|
|
779
|
+
const name = normalizeRequiredText(organization.name, 'Organization name', 120)
|
|
780
|
+
return Object.freeze({
|
|
781
|
+
id,
|
|
782
|
+
name,
|
|
783
|
+
type: organization.type,
|
|
784
|
+
avatarText:
|
|
785
|
+
normalizeOptionalText(organization.avatarText, 4) || Array.from(name)[0] || '组',
|
|
786
|
+
role: normalizeOptionalText(organization.role, 80),
|
|
787
|
+
memberCount,
|
|
788
|
+
verificationStatus:
|
|
789
|
+
organization.type === 'company'
|
|
790
|
+
? normalizeVerificationStatus(organization.verificationStatus)
|
|
791
|
+
: null,
|
|
792
|
+
})
|
|
793
|
+
}),
|
|
794
|
+
)
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function normalizeOrganizationCount(value: unknown, fallback: number): number {
|
|
798
|
+
if (value === undefined) return fallback
|
|
799
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < fallback) {
|
|
800
|
+
throw new TypeError('user-menu organizationCount must include every provided organization')
|
|
801
|
+
}
|
|
802
|
+
return value
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function normalizeVerificationStatus(
|
|
806
|
+
value: unknown,
|
|
807
|
+
): 'unverified' | 'pending' | 'rejected' | 'verified' | 'expired' | null {
|
|
808
|
+
if (value === undefined || value === null) return null
|
|
809
|
+
if (!['unverified', 'pending', 'rejected', 'verified', 'expired'].includes(String(value))) {
|
|
810
|
+
throw new TypeError('Organization verification status is invalid')
|
|
811
|
+
}
|
|
812
|
+
return value as 'unverified' | 'pending' | 'rejected' | 'verified' | 'expired'
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
function verificationStatusLabel(
|
|
816
|
+
status: 'unverified' | 'pending' | 'rejected' | 'verified' | 'expired',
|
|
817
|
+
): string {
|
|
818
|
+
if (status === 'verified') return '已认证'
|
|
819
|
+
if (status === 'pending') return '审核中'
|
|
820
|
+
if (status === 'rejected') return '未通过'
|
|
821
|
+
if (status === 'expired') return '已过期'
|
|
822
|
+
return '未认证'
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
function normalizeMemberCount(value: unknown): number | null {
|
|
826
|
+
if (value === undefined || value === null) return null
|
|
827
|
+
if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
|
|
828
|
+
throw new TypeError('Organization member count must be a non-negative integer')
|
|
829
|
+
}
|
|
830
|
+
return value
|
|
831
|
+
}
|
|
832
|
+
|
|
833
|
+
function normalizeMaskedMobile(value: unknown): string {
|
|
834
|
+
const mobile = normalizeOptionalText(value, 40)
|
|
835
|
+
if (/^1[3-9]\d{9}$/.test(mobile)) {
|
|
836
|
+
throw new TypeError('user-menu only accepts a masked mobile number')
|
|
837
|
+
}
|
|
838
|
+
return mobile
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function normalizeRequiredText(value: unknown, label: string, maxLength: number): string {
|
|
842
|
+
const normalized = normalizeOptionalText(value, maxLength)
|
|
843
|
+
if (!normalized) throw new TypeError(`${label} is required`)
|
|
844
|
+
return normalized
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
function normalizeOptionalText(value: unknown, maxLength: number): string {
|
|
848
|
+
if (value === undefined || value === null) return ''
|
|
849
|
+
if (typeof value !== 'string') throw new TypeError('user-menu text values must be strings')
|
|
850
|
+
const normalized = value.trim()
|
|
851
|
+
if (normalized.length > maxLength) throw new TypeError('user-menu text value is too long')
|
|
852
|
+
return normalized
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function organizationMeta(organization: ResolvedUserMenuOrganization): string {
|
|
856
|
+
const values: string[] = []
|
|
857
|
+
if (organization.role) values.push(organization.role)
|
|
858
|
+
if (organization.memberCount !== null) values.push(`${organization.memberCount} 人`)
|
|
859
|
+
if (organization.type === 'company' && organization.verificationStatus === 'unverified') {
|
|
860
|
+
values.push('认证后可办理业务')
|
|
861
|
+
}
|
|
862
|
+
return values.join(' · ')
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
function organizationNeedsAttention(organization: ResolvedUserMenuOrganization): boolean {
|
|
866
|
+
return (
|
|
867
|
+
organization.type === 'company' &&
|
|
868
|
+
['unverified', 'rejected', 'expired'].includes(organization.verificationStatus ?? '')
|
|
869
|
+
)
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
function verificationStatusIcon(
|
|
873
|
+
status: 'unverified' | 'pending' | 'rejected' | 'verified' | 'expired',
|
|
874
|
+
): PrototypeIconName {
|
|
875
|
+
if (status === 'verified') return 'discount-check-filled'
|
|
876
|
+
if (status === 'pending') return 'clock-hour-4'
|
|
877
|
+
if (status === 'rejected') return 'alert-triangle'
|
|
878
|
+
if (status === 'expired') return 'clock-exclamation'
|
|
879
|
+
return 'alert-circle'
|
|
880
|
+
}
|