@design.estate/dees-catalog 3.97.1 → 3.98.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/dist_bundle/bundle.js +4606 -3404
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/00group-button/dees-button/dees-button.d.ts +35 -0
- package/dist_ts_web/elements/00group-button/dees-button/dees-button.js +128 -5
- package/dist_ts_web/elements/00group-feedback/dees-spinner/dees-spinner.js +12 -3
- package/dist_ts_web/elements/00group-form/dees-form-submit/dees-form-submit.d.ts +9 -0
- package/dist_ts_web/elements/00group-form/dees-form-submit/dees-form-submit.js +25 -2
- package/dist_ts_web/elements/00group-input/dees-input-text/dees-input-text.d.ts +15 -0
- package/dist_ts_web/elements/00group-input/dees-input-text/dees-input-text.js +36 -2
- package/dist_ts_web/elements/00group-simple/dees-simple-login/dees-simple-login.d.ts +246 -0
- package/dist_ts_web/elements/00group-simple/dees-simple-login/dees-simple-login.demo.js +439 -21
- package/dist_ts_web/elements/00group-simple/dees-simple-login/dees-simple-login.js +857 -23
- package/package.json +3 -3
- package/readme.hints.md +35 -0
- package/readme.md +80 -11
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/00group-button/dees-button/dees-button.ts +128 -2
- package/ts_web/elements/00group-feedback/dees-spinner/dees-spinner.ts +11 -2
- package/ts_web/elements/00group-form/dees-form-submit/dees-form-submit.ts +18 -0
- package/ts_web/elements/00group-input/dees-input-text/dees-input-text.ts +29 -0
- package/ts_web/elements/00group-simple/dees-simple-login/dees-simple-login.demo.ts +418 -19
- package/ts_web/elements/00group-simple/dees-simple-login/dees-simple-login.ts +969 -20
|
@@ -1,4 +1,420 @@
|
|
|
1
|
-
import { html } from '@design.estate/dees-element';
|
|
1
|
+
import { html, css, cssManager, customElement, state, DeesElement } from '@design.estate/dees-element';
|
|
2
|
+
import { themeDefaultStyles } from '../../00theme.js';
|
|
3
|
+
import type {
|
|
4
|
+
DeesSimpleLogin,
|
|
5
|
+
IDeesLoginPasskeyContext,
|
|
6
|
+
IDeesLoginProvider,
|
|
7
|
+
IDeesLoginProviderContext,
|
|
8
|
+
TDeesLoginMethod,
|
|
9
|
+
} from './dees-simple-login.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The catalog carries no third-party brand assets, so provider icons are always supplied by
|
|
13
|
+
* the consumer. These are neutral Lucide stand-ins for what a real deployment would pass.
|
|
14
|
+
*/
|
|
15
|
+
const demoProviders: IDeesLoginProvider[] = [
|
|
16
|
+
{ id: 'idp-global', label: 'idp.global', icon: 'lucide:shieldCheck' },
|
|
17
|
+
{ id: 'workspace', label: 'Workspace SSO', icon: 'lucide:building2' },
|
|
18
|
+
{ id: 'email-link', label: 'Email link', icon: 'lucide:mail' },
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
const manyProviders: IDeesLoginProvider[] = [
|
|
22
|
+
...demoProviders,
|
|
23
|
+
{ id: 'security-key', label: 'Security key', icon: 'lucide:usb' },
|
|
24
|
+
{ id: 'smartcard', label: 'Smartcard', icon: 'lucide:idCard' },
|
|
25
|
+
{ id: 'legacy-ldap', label: 'LDAP', icon: 'lucide:network', disabled: true },
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
const delay = (msArg: number) => new Promise<void>((resolve) => window.setTimeout(resolve, msArg));
|
|
29
|
+
|
|
30
|
+
type TDemoVariant =
|
|
31
|
+
| 'legacy'
|
|
32
|
+
| 'full'
|
|
33
|
+
| 'passkeyOnly'
|
|
34
|
+
| 'enrollment'
|
|
35
|
+
| 'providerOnly'
|
|
36
|
+
| 'manyProviders'
|
|
37
|
+
| 'eventMode';
|
|
38
|
+
|
|
39
|
+
interface IDemoVariantConfig {
|
|
40
|
+
key: TDemoVariant;
|
|
41
|
+
title: string;
|
|
42
|
+
description: string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const variants: IDemoVariantConfig[] = [
|
|
46
|
+
{
|
|
47
|
+
key: 'legacy',
|
|
48
|
+
title: 'Default (password only)',
|
|
49
|
+
description:
|
|
50
|
+
'No new properties set. This is the pre-existing component: username, password, the ' +
|
|
51
|
+
'legacy `login` event and nothing else.',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
key: 'full',
|
|
55
|
+
title: 'Passkey + providers + password',
|
|
56
|
+
description:
|
|
57
|
+
'Handler mode. Every method has its own busy and error state — the passkey handler ' +
|
|
58
|
+
'below always rejects, and the password form stays untouched by it.',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
key: 'passkeyOnly',
|
|
62
|
+
title: 'Passkey only',
|
|
63
|
+
description: '`.passkey=${true} .password=${false}` — no credential fallback at all.',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
key: 'enrollment',
|
|
67
|
+
title: 'Passkey enrollment + sign in',
|
|
68
|
+
description:
|
|
69
|
+
"`.passkeyIntents=${['authenticate', 'register']}` renders both ceremonies. " +
|
|
70
|
+
'This is what Onebox hand-rolls today.',
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
key: 'providerOnly',
|
|
74
|
+
title: 'Providers only',
|
|
75
|
+
description: 'A pure OIDC surface. The provider handler resolves after a short delay.',
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
key: 'manyProviders',
|
|
79
|
+
title: 'Six providers',
|
|
80
|
+
description:
|
|
81
|
+
'Four or more providers pair up into two columns instead of growing the card. The ' +
|
|
82
|
+
'last one is `disabled`.',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: 'eventMode',
|
|
86
|
+
title: 'Event mode (no handlers)',
|
|
87
|
+
description:
|
|
88
|
+
'No handler properties. The component dispatches `passkey-login` / `provider-login` ' +
|
|
89
|
+
'and the demo drives busy and error state through `setBusy()` and `reportError()`.',
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
@customElement('dees-simple-login-demo')
|
|
94
|
+
class DeesSimpleLoginDemo extends DeesElement {
|
|
95
|
+
@state()
|
|
96
|
+
accessor variant: TDemoVariant = 'full';
|
|
97
|
+
|
|
98
|
+
@state()
|
|
99
|
+
accessor log: string[] = [];
|
|
100
|
+
|
|
101
|
+
public static styles = [
|
|
102
|
+
themeDefaultStyles,
|
|
103
|
+
cssManager.defaultStyles,
|
|
104
|
+
css`
|
|
105
|
+
:host {
|
|
106
|
+
display: block;
|
|
107
|
+
position: absolute;
|
|
108
|
+
inset: 0;
|
|
109
|
+
font-family: var(--dees-font-family);
|
|
110
|
+
color: var(--dees-color-text-primary);
|
|
111
|
+
background: var(--dees-color-bg-canvas);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
.layout {
|
|
115
|
+
display: grid;
|
|
116
|
+
grid-template-columns: 320px 1fr;
|
|
117
|
+
height: 100%;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.sidebar {
|
|
121
|
+
display: flex;
|
|
122
|
+
flex-direction: column;
|
|
123
|
+
gap: var(--dees-spacing-md);
|
|
124
|
+
padding: var(--dees-spacing-xl);
|
|
125
|
+
overflow-y: auto;
|
|
126
|
+
border-right: 1px solid var(--dees-color-border-default);
|
|
127
|
+
background: var(--dees-color-bg-secondary);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.sidebarTitle {
|
|
131
|
+
font-size: var(--dees-font-control-size);
|
|
132
|
+
font-weight: 600;
|
|
133
|
+
color: var(--dees-color-text-muted);
|
|
134
|
+
text-transform: uppercase;
|
|
135
|
+
letter-spacing: 0.06em;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.variant {
|
|
139
|
+
padding: var(--dees-spacing-md);
|
|
140
|
+
border: 1px solid var(--dees-color-border-subtle);
|
|
141
|
+
border-radius: var(--dees-radius-lg);
|
|
142
|
+
corner-shape: var(--dees-corner-shape);
|
|
143
|
+
background: var(--dees-color-bg-primary);
|
|
144
|
+
cursor: pointer;
|
|
145
|
+
transition: border-color var(--dees-transition-fast) var(--dees-ease-standard);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.variant:hover {
|
|
149
|
+
border-color: var(--dees-color-border-strong);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.variant.selected {
|
|
153
|
+
border-color: var(--dees-color-accent-primary);
|
|
154
|
+
box-shadow: 0 0 0 1px var(--dees-color-accent-primary);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.variantTitle {
|
|
158
|
+
font-size: var(--dees-font-control-size);
|
|
159
|
+
font-weight: 600;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.variantDescription {
|
|
163
|
+
margin-top: var(--dees-spacing-xs);
|
|
164
|
+
font-size: var(--dees-font-control-size-sm);
|
|
165
|
+
line-height: 1.45;
|
|
166
|
+
color: var(--dees-color-text-muted);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.logTitle {
|
|
170
|
+
margin-top: var(--dees-spacing-lg);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.logEntries {
|
|
174
|
+
display: flex;
|
|
175
|
+
flex-direction: column;
|
|
176
|
+
gap: var(--dees-spacing-xs);
|
|
177
|
+
font-family: var(--dees-font-family-mono);
|
|
178
|
+
font-size: var(--dees-font-control-size-sm);
|
|
179
|
+
color: var(--dees-color-text-secondary);
|
|
180
|
+
user-select: text;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.stage {
|
|
184
|
+
position: relative;
|
|
185
|
+
overflow: hidden;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.authenticated {
|
|
189
|
+
display: flex;
|
|
190
|
+
flex-direction: column;
|
|
191
|
+
gap: var(--dees-spacing-md);
|
|
192
|
+
align-items: center;
|
|
193
|
+
justify-content: center;
|
|
194
|
+
height: 100%;
|
|
195
|
+
text-align: center;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.authenticated h1 {
|
|
199
|
+
margin: 0;
|
|
200
|
+
font-size: var(--dees-font-title2-size);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.authenticated p {
|
|
204
|
+
margin: 0;
|
|
205
|
+
color: var(--dees-color-text-muted);
|
|
206
|
+
}
|
|
207
|
+
`,
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
private get loginElement(): DeesSimpleLogin | null {
|
|
211
|
+
return this.shadowRoot?.querySelector('dees-simple-login') || null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private note(messageArg: string): void {
|
|
215
|
+
this.log = [messageArg, ...this.log].slice(0, 12);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
public render() {
|
|
219
|
+
return html`
|
|
220
|
+
<div class="layout">
|
|
221
|
+
<div class="sidebar">
|
|
222
|
+
<div class="sidebarTitle">Variants</div>
|
|
223
|
+
${variants.map(
|
|
224
|
+
(variantArg) => html`
|
|
225
|
+
<div
|
|
226
|
+
class="variant ${this.variant === variantArg.key ? 'selected' : ''}"
|
|
227
|
+
@click=${() => {
|
|
228
|
+
this.variant = variantArg.key;
|
|
229
|
+
this.log = [];
|
|
230
|
+
}}
|
|
231
|
+
>
|
|
232
|
+
<div class="variantTitle">${variantArg.title}</div>
|
|
233
|
+
<div class="variantDescription">${variantArg.description}</div>
|
|
234
|
+
</div>
|
|
235
|
+
`,
|
|
236
|
+
)}
|
|
237
|
+
<div class="sidebarTitle logTitle">Event log</div>
|
|
238
|
+
<div class="logEntries">
|
|
239
|
+
${this.log.length === 0
|
|
240
|
+
? html`<div>— nothing yet —</div>`
|
|
241
|
+
: this.log.map((entry) => html`<div>${entry}</div>`)}
|
|
242
|
+
</div>
|
|
243
|
+
</div>
|
|
244
|
+
<div class="stage">${this.renderVariant()}</div>
|
|
245
|
+
</div>
|
|
246
|
+
`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private renderVariant() {
|
|
250
|
+
const shared = html`
|
|
251
|
+
<div class="authenticated">
|
|
252
|
+
<h1>Signed in</h1>
|
|
253
|
+
<p>This is the slotted content that appears after login.</p>
|
|
254
|
+
</div>
|
|
255
|
+
`;
|
|
256
|
+
|
|
257
|
+
if (this.variant === 'legacy') {
|
|
258
|
+
return html`
|
|
259
|
+
<dees-simple-login
|
|
260
|
+
name="My Application"
|
|
261
|
+
@login=${(eventArg: CustomEvent) => {
|
|
262
|
+
const data = eventArg.detail?.data;
|
|
263
|
+
this.note(`login: ${data?.username} / ${'*'.repeat(data?.password?.length || 0)}`);
|
|
264
|
+
void this.loginElement?.switchToSlottedContent();
|
|
265
|
+
}}
|
|
266
|
+
>
|
|
267
|
+
${shared}
|
|
268
|
+
</dees-simple-login>
|
|
269
|
+
`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (this.variant === 'full') {
|
|
273
|
+
return html`
|
|
274
|
+
<dees-simple-login
|
|
275
|
+
name="idp.global"
|
|
276
|
+
.passkey=${true}
|
|
277
|
+
.passkeyAvailable=${true}
|
|
278
|
+
.providers=${demoProviders}
|
|
279
|
+
.labels=${{ subheading: 'Choose how you want to continue' }}
|
|
280
|
+
.passkeyLoginHandler=${async (contextArg: IDeesLoginPasskeyContext) => {
|
|
281
|
+
this.note(`passkey handler (${contextArg.mediation})`);
|
|
282
|
+
await delay(900);
|
|
283
|
+
throw new Error('No passkey found for this account.');
|
|
284
|
+
}}
|
|
285
|
+
.providerLoginHandler=${async (contextArg: IDeesLoginProviderContext) => {
|
|
286
|
+
this.note(`provider handler: ${contextArg.providerId}`);
|
|
287
|
+
await delay(900);
|
|
288
|
+
void this.loginElement?.switchToSlottedContent();
|
|
289
|
+
}}
|
|
290
|
+
.passwordLoginHandler=${async () => {
|
|
291
|
+
this.note('password handler');
|
|
292
|
+
await delay(900);
|
|
293
|
+
void this.loginElement?.switchToSlottedContent();
|
|
294
|
+
}}
|
|
295
|
+
@login=${(eventArg: CustomEvent) => {
|
|
296
|
+
this.note(`login event: ${eventArg.detail?.data?.username}`);
|
|
297
|
+
}}
|
|
298
|
+
>
|
|
299
|
+
${shared}
|
|
300
|
+
</dees-simple-login>
|
|
301
|
+
`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (this.variant === 'passkeyOnly') {
|
|
305
|
+
return html`
|
|
306
|
+
<dees-simple-login
|
|
307
|
+
name="Onebox"
|
|
308
|
+
.passkey=${true}
|
|
309
|
+
.passkeyAvailable=${true}
|
|
310
|
+
.password=${false}
|
|
311
|
+
.labels=${{ subheading: 'Use a passkey registered for your Onebox account' }}
|
|
312
|
+
.passkeyLoginHandler=${async () => {
|
|
313
|
+
this.note('passkey sign in');
|
|
314
|
+
await delay(900);
|
|
315
|
+
void this.loginElement?.switchToSlottedContent();
|
|
316
|
+
}}
|
|
317
|
+
>
|
|
318
|
+
${shared}
|
|
319
|
+
</dees-simple-login>
|
|
320
|
+
`;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (this.variant === 'enrollment') {
|
|
324
|
+
return html`
|
|
325
|
+
<dees-simple-login
|
|
326
|
+
name="Onebox"
|
|
327
|
+
.passkey=${true}
|
|
328
|
+
.passkeyAvailable=${true}
|
|
329
|
+
.password=${false}
|
|
330
|
+
.passkeyIntents=${['authenticate', 'register']}
|
|
331
|
+
.labels=${{ subheading: 'Sign in, or enroll this device' }}
|
|
332
|
+
.passkeyLoginHandler=${async () => {
|
|
333
|
+
this.note('passkey authenticate');
|
|
334
|
+
await delay(900);
|
|
335
|
+
throw new Error('This device has no passkey for Onebox yet.');
|
|
336
|
+
}}
|
|
337
|
+
.passkeyRegisterHandler=${async () => {
|
|
338
|
+
this.note('passkey register');
|
|
339
|
+
await delay(900);
|
|
340
|
+
void this.loginElement?.switchToSlottedContent();
|
|
341
|
+
}}
|
|
342
|
+
>
|
|
343
|
+
${shared}
|
|
344
|
+
</dees-simple-login>
|
|
345
|
+
`;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (this.variant === 'providerOnly') {
|
|
349
|
+
return html`
|
|
350
|
+
<dees-simple-login
|
|
351
|
+
name="login.idp.global"
|
|
352
|
+
.password=${false}
|
|
353
|
+
.providers=${demoProviders}
|
|
354
|
+
.labels=${{ subheading: 'Continue with one of your identity providers' }}
|
|
355
|
+
.providerLoginHandler=${async (contextArg: IDeesLoginProviderContext) => {
|
|
356
|
+
this.note(`provider handler: ${contextArg.providerId}`);
|
|
357
|
+
await delay(900);
|
|
358
|
+
void this.loginElement?.switchToSlottedContent();
|
|
359
|
+
}}
|
|
360
|
+
>
|
|
361
|
+
${shared}
|
|
362
|
+
</dees-simple-login>
|
|
363
|
+
`;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (this.variant === 'manyProviders') {
|
|
367
|
+
return html`
|
|
368
|
+
<dees-simple-login
|
|
369
|
+
name="login.idp.global"
|
|
370
|
+
.providers=${manyProviders}
|
|
371
|
+
.methodOrder=${['provider', 'password'] as TDeesLoginMethod[]}
|
|
372
|
+
.providerLoginHandler=${async (contextArg: IDeesLoginProviderContext) => {
|
|
373
|
+
this.note(`provider handler: ${contextArg.providerId}`);
|
|
374
|
+
await delay(600);
|
|
375
|
+
throw new Error('Discovery endpoint unreachable.');
|
|
376
|
+
}}
|
|
377
|
+
@login=${() => this.note('login event')}
|
|
378
|
+
>
|
|
379
|
+
${shared}
|
|
380
|
+
</dees-simple-login>
|
|
381
|
+
`;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// eventMode
|
|
385
|
+
return html`
|
|
386
|
+
<dees-simple-login
|
|
387
|
+
name="Cloudly"
|
|
388
|
+
.passkey=${true}
|
|
389
|
+
.passkeyAvailable=${true}
|
|
390
|
+
.providers=${demoProviders}
|
|
391
|
+
@passkey-login=${async (eventArg: CustomEvent) => {
|
|
392
|
+
this.note(`passkey-login event (username: ${eventArg.detail.username || '—'})`);
|
|
393
|
+
const element = this.loginElement;
|
|
394
|
+
element?.setBusy({ method: 'passkey' }, true);
|
|
395
|
+
await delay(900);
|
|
396
|
+
element?.setBusy({ method: 'passkey' }, false);
|
|
397
|
+
element?.reportError({ method: 'passkey' }, 'Consumer-reported: ceremony declined.');
|
|
398
|
+
}}
|
|
399
|
+
@provider-login=${async (eventArg: CustomEvent) => {
|
|
400
|
+
this.note(`provider-login event: ${eventArg.detail.providerId}`);
|
|
401
|
+
const element = this.loginElement;
|
|
402
|
+
const target = { method: 'provider' as const, providerId: eventArg.detail.providerId };
|
|
403
|
+
element?.setBusy(target, true);
|
|
404
|
+
await delay(900);
|
|
405
|
+
element?.setBusy(target, false);
|
|
406
|
+
element?.reportError(target, 'Consumer-reported: redirect blocked.');
|
|
407
|
+
}}
|
|
408
|
+
@login=${(eventArg: CustomEvent) => {
|
|
409
|
+
this.note(`login event: ${eventArg.detail?.data?.username}`);
|
|
410
|
+
this.loginElement?.reportError('password', 'Consumer-reported: invalid credentials.');
|
|
411
|
+
}}
|
|
412
|
+
>
|
|
413
|
+
${shared}
|
|
414
|
+
</dees-simple-login>
|
|
415
|
+
`;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
2
418
|
|
|
3
419
|
export const demoFunc = () => html`
|
|
4
420
|
<style>
|
|
@@ -15,23 +431,6 @@ export const demoFunc = () => html`
|
|
|
15
431
|
}
|
|
16
432
|
</style>
|
|
17
433
|
<div class="demo-container">
|
|
18
|
-
<dees-simple-login
|
|
19
|
-
name="My Application"
|
|
20
|
-
@login=${(e: CustomEvent) => {
|
|
21
|
-
console.log('Login event received:', e.detail);
|
|
22
|
-
const loginData = e.detail?.data || e.detail;
|
|
23
|
-
if (loginData?.username && loginData?.password) {
|
|
24
|
-
alert(`Login attempted with:\nUsername: ${loginData.username}\nPassword: ${loginData.password}`);
|
|
25
|
-
// Here you would typically validate credentials and show the slotted content
|
|
26
|
-
} else {
|
|
27
|
-
console.error('Invalid login data structure:', e.detail);
|
|
28
|
-
}
|
|
29
|
-
}}
|
|
30
|
-
>
|
|
31
|
-
<div style="padding: 40px; text-align: center;">
|
|
32
|
-
<h1>Welcome!</h1>
|
|
33
|
-
<p>This is the slotted content that appears after login.</p>
|
|
34
|
-
</div>
|
|
35
|
-
</dees-simple-login>
|
|
434
|
+
<dees-simple-login-demo></dees-simple-login-demo>
|
|
36
435
|
</div>
|
|
37
436
|
`;
|