@nymphjs/tilmeld-setup 1.0.0-beta.11 → 1.0.0-beta.111

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.
@@ -1,902 +0,0 @@
1
- <div style="display: flex; align-items: center; padding: 12px;">
2
- <IconButton title="Back" on:click={() => dispatch('leave')}>
3
- <Icon component={Svg} viewBox="0 0 24 24">
4
- <path fill="currentColor" d={mdiArrowLeft} />
5
- </Icon>
6
- </IconButton>
7
- <h2 style="margin: 0px 12px 0px;" class="mdc-typography--headline5">
8
- Editing {entity.guid
9
- ? entity.$is(user)
10
- ? 'Yourself'
11
- : entity.name
12
- : 'New User'}
13
- </h2>
14
- </div>
15
- {#if entity != null}
16
- {#if clientConfig == null || user == null}
17
- <section style="padding-top: 0;">
18
- <div style="display: flex; justify-content: center; align-items: center;">
19
- <CircularProgress style="height: 45px; width: 45px;" indeterminate />
20
- </div>
21
- </section>
22
- {:else}
23
- <TabBar
24
- tabs={['General', 'Groups', 'Abilities', 'Security']}
25
- let:tab
26
- bind:active={activeTab}
27
- >
28
- <Tab {tab}>
29
- <Label>{tab}</Label>
30
- </Tab>
31
- </TabBar>
32
-
33
- <section>
34
- {#if activeTab === 'General'}
35
- <LayoutGrid style="padding: 0;">
36
- <LayoutCell span={4}>
37
- <div class="mdc-typography--headline6">GUID</div>
38
- <code>{entity.guid}</code>
39
- </LayoutCell>
40
- <LayoutCell span={4}>
41
- <FormField>
42
- <Checkbox bind:checked={entity.enabled} />
43
- <span slot="label">Enabled (Able to log in)</span>
44
- </FormField>
45
- </LayoutCell>
46
- <LayoutCell span={4} style="text-align: end;">
47
- <a href="https://en.gravatar.com/" target="_blank" rel="noreferrer">
48
- <img src={avatar} alt="Avatar" title="Avatar by Gravatar" />
49
- </a>
50
- </LayoutCell>
51
- {#if !clientConfig.emailUsernames}
52
- <LayoutCell span={6}>
53
- <Textfield
54
- bind:value={entity.username}
55
- label="Username"
56
- type="text"
57
- style="width: 100%;"
58
- helperLine$style="width: 100%;"
59
- invalid={usernameVerified === false}
60
- input$autocomplete="off"
61
- input$autocapitalize="off"
62
- input$spellcheck="false"
63
- >
64
- <HelperText persistent slot="helper">
65
- {usernameVerifiedMessage ?? ''}
66
- </HelperText>
67
- </Textfield>
68
- </LayoutCell>
69
- {/if}
70
- <LayoutCell span={clientConfig.emailUsernames ? 12 : 6}>
71
- <Textfield
72
- bind:value={entity.email}
73
- label="Email"
74
- type="email"
75
- style="width: 100%;"
76
- helperLine$style="width: 100%;"
77
- invalid={emailVerified === false}
78
- input$autocomplete="off"
79
- input$autocapitalize="off"
80
- input$spellcheck="false"
81
- >
82
- <HelperText persistent slot="helper">
83
- {emailVerifiedMessage ?? ''}
84
- </HelperText>
85
- </Textfield>
86
- </LayoutCell>
87
- <LayoutCell span={4}>
88
- <Textfield
89
- bind:value={entity.nameFirst}
90
- label="First Name"
91
- type="text"
92
- style="width: 100%;"
93
- input$autocomplete="off"
94
- />
95
- </LayoutCell>
96
- <LayoutCell span={4}>
97
- <Textfield
98
- bind:value={entity.nameMiddle}
99
- label="Middle Name"
100
- type="text"
101
- style="width: 100%;"
102
- input$autocomplete="off"
103
- />
104
- </LayoutCell>
105
- <LayoutCell span={4}>
106
- <Textfield
107
- bind:value={entity.nameLast}
108
- label="Last Name"
109
- type="text"
110
- style="width: 100%;"
111
- input$autocomplete="off"
112
- />
113
- </LayoutCell>
114
- <LayoutCell span={8}>
115
- <Textfield
116
- bind:value={entity.avatar}
117
- label="Avatar"
118
- type="text"
119
- style="width: 100%;"
120
- input$autocomplete="off"
121
- />
122
- </LayoutCell>
123
- <LayoutCell span={4}>
124
- <Textfield
125
- bind:value={entity.phone}
126
- label="Phone"
127
- type="tel"
128
- style="width: 100%;"
129
- input$autocomplete="off"
130
- />
131
- </LayoutCell>
132
- <LayoutCell span={6}>
133
- <Textfield
134
- bind:value={entity.passwordTemp}
135
- label={`${entity.guid ? 'Update ' : ''}Password`}
136
- type="password"
137
- style="width: 100%;"
138
- input$autocomplete="off"
139
- />
140
- </LayoutCell>
141
- <LayoutCell span={6}>
142
- <Textfield
143
- bind:value={passwordVerify}
144
- label="Repeat Password"
145
- type="password"
146
- style="width: 100%;"
147
- invalid={passwordVerified === false}
148
- input$autocomplete="off"
149
- on:blur={doVerifyPassword}
150
- />
151
- </LayoutCell>
152
- </LayoutGrid>
153
- {/if}
154
-
155
- {#if activeTab === 'Groups'}
156
- <h5 style="margin-top: 0;">Primary Group</h5>
157
-
158
- <Paper
159
- style="display: flex; justify-content: space-between; align-items: center;"
160
- >
161
- {#if !entity.group}
162
- No primary group
163
- {:else}
164
- <span
165
- >{entity.group.name + ' (' + entity.group.groupname + ')'}</span
166
- >
167
-
168
- <IconButton
169
- on:click={() => {
170
- delete entity.group;
171
- entity = entity;
172
- }}
173
- >
174
- <Icon component={Svg} viewBox="0 0 24 24">
175
- <path fill="currentColor" d={mdiMinus} />
176
- </Icon>
177
- </IconButton>
178
- {/if}
179
- </Paper>
180
-
181
- <h6>Change Primary Group</h6>
182
-
183
- <div class="solo-search-container solo-container">
184
- <Paper class="solo-paper" elevation={1}>
185
- <Icon class="solo-icon" component={Svg} viewBox="0 0 24 24">
186
- <path fill="currentColor" d={mdiMagnify} />
187
- </Icon>
188
- <Input
189
- bind:value={primaryGroupSearch}
190
- on:keydown={primaryGroupSearchKeyDown}
191
- placeholder="Primary Group Search"
192
- class="solo-input"
193
- />
194
- </Paper>
195
- <IconButton
196
- on:click={searchPrimaryGroups}
197
- disabled={primaryGroupSearch === ''}
198
- class="solo-fab"
199
- title="Search"
200
- >
201
- <Icon component={Svg} viewBox="0 0 24 24">
202
- <path fill="currentColor" d={mdiArrowRight} />
203
- </Icon>
204
- </IconButton>
205
- </div>
206
-
207
- {#if primaryGroupsSearching}
208
- <div
209
- style="display: flex; justify-content: center; align-items: center;"
210
- >
211
- <CircularProgress
212
- style="height: 32px; width: 32px;"
213
- indeterminate
214
- />
215
- </div>
216
- {:else if primaryGroups != null}
217
- <DataTable table$aria-label="Primary group list" style="width: 100%;">
218
- <Head>
219
- <Row>
220
- {#if !clientConfig.emailUsernames}
221
- <Cell>Groupname</Cell>
222
- {/if}
223
- <Cell>Name</Cell>
224
- <Cell>Email</Cell>
225
- <Cell>Enabled</Cell>
226
- </Row>
227
- </Head>
228
- <Body>
229
- {#each primaryGroups as curEntity (curEntity.guid)}
230
- <Row
231
- on:click={() => (entity.group = curEntity)}
232
- style="cursor: pointer;"
233
- >
234
- {#if !clientConfig.emailUsernames}
235
- <Cell>{curEntity.groupname}</Cell>
236
- {/if}
237
- <Cell>{curEntity.name}</Cell>
238
- <Cell>{curEntity.email}</Cell>
239
- <Cell>{curEntity.enabled ? 'Yes' : 'No'}</Cell>
240
- </Row>
241
- {/each}
242
- </Body>
243
- </DataTable>
244
- {/if}
245
-
246
- <h5>Secondary Groups</h5>
247
-
248
- <DataTable
249
- table$aria-label="Current secondary groups"
250
- style="width: 100%;"
251
- >
252
- <Head>
253
- <Row>
254
- {#if !clientConfig.emailUsernames}
255
- <Cell>Groupname</Cell>
256
- {/if}
257
- <Cell>Name</Cell>
258
- <Cell>Email</Cell>
259
- <Cell>Enabled</Cell>
260
- <Cell>Remove</Cell>
261
- </Row>
262
- </Head>
263
- <Body>
264
- {#if entity.groups}
265
- {#each entity.groups as curEntity, index (curEntity.guid)}
266
- <Row>
267
- {#if !clientConfig.emailUsernames}
268
- <Cell>{curEntity.groupname}</Cell>
269
- {/if}
270
- <Cell>{curEntity.name}</Cell>
271
- <Cell>{curEntity.email}</Cell>
272
- <Cell>{curEntity.enabled ? 'Yes' : 'No'}</Cell>
273
- <Cell>
274
- <IconButton
275
- on:click={() => {
276
- entity.groups?.splice(index, 1);
277
- entity = entity;
278
- }}
279
- >
280
- <Icon component={Svg} viewBox="0 0 24 24">
281
- <path fill="currentColor" d={mdiMinus} />
282
- </Icon>
283
- </IconButton>
284
- </Cell>
285
- </Row>
286
- {:else}
287
- <Row>
288
- <Cell colspan={clientConfig.emailUsernames ? 4 : 5}>
289
- No secondary groups
290
- </Cell>
291
- </Row>
292
- {/each}
293
- {/if}
294
- </Body>
295
- </DataTable>
296
-
297
- <h6>Add Secondary Groups</h6>
298
-
299
- <div class="solo-search-container solo-container">
300
- <Paper class="solo-paper" elevation={1}>
301
- <Icon class="solo-icon" component={Svg} viewBox="0 0 24 24">
302
- <path fill="currentColor" d={mdiMagnify} />
303
- </Icon>
304
- <Input
305
- bind:value={secondaryGroupSearch}
306
- on:keydown={secondaryGroupSearchKeyDown}
307
- placeholder="Secondary Group Search"
308
- class="solo-input"
309
- />
310
- </Paper>
311
- <IconButton
312
- on:click={searchSecondaryGroups}
313
- disabled={secondaryGroupSearch === ''}
314
- class="solo-fab"
315
- title="Search"
316
- >
317
- <Icon component={Svg} viewBox="0 0 24 24">
318
- <path fill="currentColor" d={mdiArrowRight} />
319
- </Icon>
320
- </IconButton>
321
- </div>
322
-
323
- {#if secondaryGroupsSearching}
324
- <div
325
- style="display: flex; justify-content: center; align-items: center;"
326
- >
327
- <CircularProgress
328
- style="height: 32px; width: 32px;"
329
- indeterminate
330
- />
331
- </div>
332
- {:else if secondaryGroups != null}
333
- <DataTable
334
- table$aria-label="Secondary group list"
335
- style="width: 100%;"
336
- >
337
- <Head>
338
- <Row>
339
- {#if !clientConfig.emailUsernames}
340
- <Cell>Groupname</Cell>
341
- {/if}
342
- <Cell>Name</Cell>
343
- <Cell>Email</Cell>
344
- <Cell>Enabled</Cell>
345
- </Row>
346
- </Head>
347
- <Body>
348
- {#each secondaryGroups as curEntity, index (curEntity.guid)}
349
- <Row
350
- on:click={() => {
351
- entity.groups?.push(curEntity);
352
- secondaryGroups?.splice(index, 1);
353
- entity = entity;
354
- }}
355
- style="cursor: pointer;"
356
- >
357
- {#if !clientConfig.emailUsernames}
358
- <Cell>{curEntity.groupname}</Cell>
359
- {/if}
360
- <Cell>{curEntity.name}</Cell>
361
- <Cell>{curEntity.email}</Cell>
362
- <Cell>{curEntity.enabled ? 'Yes' : 'No'}</Cell>
363
- </Row>
364
- {/each}
365
- </Body>
366
- </DataTable>
367
- {/if}
368
- {/if}
369
-
370
- {#if activeTab === 'Abilities'}
371
- <h5 style="margin-top: 0;">Abilities</h5>
372
-
373
- <List nonInteractive>
374
- {#if entity.abilities}
375
- {#each entity.abilities as ability, index (ability)}
376
- <Item>
377
- <Text>
378
- {ability}
379
- </Text>
380
- <Meta>
381
- <IconButton
382
- on:click={() => {
383
- entity.abilities?.splice(index, 1);
384
- entity = entity;
385
- }}
386
- >
387
- <Icon component={Svg} viewBox="0 0 24 24">
388
- <path fill="currentColor" d={mdiMinus} />
389
- </Icon>
390
- </IconButton>
391
- </Meta>
392
- </Item>
393
- {:else}
394
- <Item>
395
- <Text>No abilities</Text>
396
- </Item>
397
- {/each}
398
- {/if}
399
- </List>
400
-
401
- <h6>Add Ability</h6>
402
-
403
- <div style="display: flex; align-items: center; flex-wrap: wrap;">
404
- <Textfield
405
- bind:value={ability}
406
- label="Ability"
407
- type="text"
408
- style="width: 250px; max-width: 100%;"
409
- on:keydown={abilityKeyDown}
410
- />
411
- <IconButton on:click={addAbility}>
412
- <Icon component={Svg} viewBox="0 0 24 24">
413
- <path fill="currentColor" d={mdiPlus} />
414
- </Icon>
415
- </IconButton>
416
- <Button
417
- on:click={addTilmeldAdminAbility}
418
- title="Tilmeld Admins have the ability to modify, create, and delete users and groups, and grant and revoke abilities."
419
- >
420
- <Label>Tilmeld Admin</Label>
421
- </Button>
422
- {#if sysAdmin}
423
- <Button
424
- on:click={addSystemAdminAbility}
425
- title="System Admins have all abilities. Gatekeeper checks always return true."
426
- >
427
- <Label>System Admin</Label>
428
- </Button>
429
- {/if}
430
- </div>
431
-
432
- <h6>Inherit Abilities</h6>
433
-
434
- <div>
435
- <FormField>
436
- <Checkbox bind:checked={entity.inheritAbilities} />
437
- <span slot="label"
438
- >Additionally, inherit the abilities of the group(s) this user
439
- belongs to.</span
440
- >
441
- </FormField>
442
- </div>
443
- {/if}
444
-
445
- {#if activeTab === 'Security'}
446
- <LayoutGrid style="padding: 0;">
447
- <LayoutCell span={12}>
448
- The email verification secret is the code emailed to the user to
449
- verify their address when they first sign up.
450
- </LayoutCell>
451
- <LayoutCell span={12}>
452
- <Textfield
453
- bind:value={entity.secret}
454
- label="Email Verification Secret"
455
- type="text"
456
- style="width: 100%;"
457
- input$autocomplete="off"
458
- />
459
- </LayoutCell>
460
- <LayoutCell span={12}>
461
- The account recovery secret is the code emailed to the user to allow
462
- them to change their password and recover their account. The date is
463
- used to determine if the code has expired.
464
- </LayoutCell>
465
- <LayoutCell span={6}>
466
- <Textfield
467
- bind:value={entity.recoverSecret}
468
- label="Account Recovery Secret"
469
- type="text"
470
- style="width: 100%;"
471
- input$autocomplete="off"
472
- />
473
- </LayoutCell>
474
- <LayoutCell span={6}>
475
- <Textfield
476
- bind:value={entity.recoverSecretDate}
477
- label="Account Recovery Date (Timestamp)"
478
- type="number"
479
- style="width: 100%;"
480
- input$autocomplete="off"
481
- />
482
- </LayoutCell>
483
- <LayoutCell span={12}>
484
- An email change uses all of the following properties. The email
485
- change date is used to rate limit email changes and to allow the
486
- user to cancel the change within the rate limit time. The new secret
487
- is emailed to the new address, and when the user clicks the link,
488
- that email address is set for their account. The cancel secret is
489
- emailed to the old address and will reset the user's email to the
490
- cancel address if the link is clicked in time.
491
- </LayoutCell>
492
- <LayoutCell span={12}>
493
- <Textfield
494
- bind:value={entity.emailChangeDate}
495
- label="Email Change Date (Timestamp)"
496
- type="number"
497
- style="width: 100%;"
498
- input$autocomplete="off"
499
- />
500
- </LayoutCell>
501
- <LayoutCell span={6}>
502
- <Textfield
503
- bind:value={entity.newEmailSecret}
504
- label="New Email Verification Secret"
505
- type="text"
506
- style="width: 100%;"
507
- input$autocomplete="off"
508
- />
509
- </LayoutCell>
510
- <LayoutCell span={6}>
511
- <Textfield
512
- bind:value={entity.newEmailAddress}
513
- label="New Email Address"
514
- type="email"
515
- style="width: 100%;"
516
- input$autocomplete="off"
517
- />
518
- </LayoutCell>
519
- <LayoutCell span={6}>
520
- <Textfield
521
- bind:value={entity.cancelEmailSecret}
522
- label="Cancel Email Verification Secret"
523
- type="text"
524
- style="width: 100%;"
525
- input$autocomplete="off"
526
- />
527
- </LayoutCell>
528
- <LayoutCell span={6}>
529
- <Textfield
530
- bind:value={entity.cancelEmailAddress}
531
- label="Cancel Email Address"
532
- type="email"
533
- style="width: 100%;"
534
- input$autocomplete="off"
535
- />
536
- </LayoutCell>
537
- </LayoutGrid>
538
- {/if}
539
-
540
- {#if failureMessage}
541
- <div class="tilmeld-failure">
542
- {failureMessage}
543
- </div>
544
- {/if}
545
-
546
- <div style="margin-top: 36px;">
547
- <Button variant="raised" on:click={saveEntity} disabled={saving}>
548
- <Label>Save User</Label>
549
- </Button>
550
- {#if entity.guid}
551
- <Button on:click={deleteEntity} disabled={saving}>
552
- <Label>Delete</Label>
553
- </Button>
554
- {/if}
555
- {#if success}
556
- <span>Successfully saved!</span>
557
- {/if}
558
- </div>
559
- </section>
560
- {/if}
561
- {/if}
562
-
563
- <script lang="ts">
564
- import { createEventDispatcher, onMount } from 'svelte';
565
- import type {
566
- AdminGroupData,
567
- AdminUserData,
568
- ClientConfig,
569
- CurrentUserData,
570
- } from '@nymphjs/tilmeld-client';
571
- import type {
572
- Group as GroupClass,
573
- User as UserClass,
574
- } from '@nymphjs/tilmeld-client';
575
- import queryParser from '@nymphjs/query-parser';
576
- import {
577
- mdiArrowLeft,
578
- mdiArrowRight,
579
- mdiMagnify,
580
- mdiMinus,
581
- mdiPlus,
582
- } from '@mdi/js';
583
- import CircularProgress from '@smui/circular-progress';
584
- import Tab from '@smui/tab';
585
- import TabBar from '@smui/tab-bar';
586
- import LayoutGrid, { Cell as LayoutCell } from '@smui/layout-grid';
587
- import FormField from '@smui/form-field';
588
- import Checkbox from '@smui/checkbox';
589
- import List, { Item, Text, Meta } from '@smui/list';
590
- import Paper from '@smui/paper';
591
- import DataTable, { Head, Body, Row, Cell } from '@smui/data-table';
592
- import Textfield, { Input } from '@smui/textfield';
593
- import HelperText from '@smui/textfield/helper-text';
594
- import IconButton from '@smui/icon-button';
595
- import Button from '@smui/button';
596
- import { Icon, Label, Svg } from '@smui/common';
597
-
598
- import { User, Group } from './nymph';
599
-
600
- const dispatch = createEventDispatcher();
601
-
602
- export let entity: UserClass & AdminUserData;
603
-
604
- let clientConfig: ClientConfig | undefined = undefined;
605
- let user: (UserClass & CurrentUserData) | undefined = undefined;
606
- let sysAdmin = false;
607
- let activeTab: 'General' | 'Groups' | 'Abilities' | 'Security' = 'General';
608
- let primaryGroupSearch = '';
609
- let secondaryGroupSearch = '';
610
- let ability = '';
611
- let avatar = 'https://secure.gravatar.com/avatar/?d=mm&s=40';
612
- let failureMessage: string | undefined = undefined;
613
- let passwordVerify = '';
614
- let passwordVerified: boolean | undefined = undefined;
615
- let usernameTimer: NodeJS.Timeout | undefined = undefined;
616
- let usernameVerified: boolean | undefined = undefined;
617
- let usernameVerifiedMessage: string | undefined = undefined;
618
- let emailTimer: NodeJS.Timeout | undefined = undefined;
619
- let emailVerified: boolean | undefined = undefined;
620
- let emailVerifiedMessage: string | undefined = undefined;
621
- let saving = false;
622
- let success: boolean | undefined = undefined;
623
-
624
- onMount(async () => {
625
- user = (await User.current()) ?? undefined;
626
- sysAdmin = (await user?.$gatekeeper('system/admin')) ?? false;
627
- });
628
- onMount(async () => {
629
- clientConfig = await User.getClientConfig();
630
- });
631
-
632
- readyEntity();
633
- function readyEntity() {
634
- // Make sure all fields are defined.
635
- if (entity.enabled == null) {
636
- entity.enabled = false;
637
- }
638
- if (entity.username == null) {
639
- entity.username = '';
640
- }
641
- if (entity.email == null) {
642
- entity.email = '';
643
- }
644
- if (entity.nameFirst == null) {
645
- entity.nameFirst = '';
646
- }
647
- if (entity.nameMiddle == null) {
648
- entity.nameMiddle = '';
649
- }
650
- if (entity.nameLast == null) {
651
- entity.nameLast = '';
652
- }
653
- if (entity.avatar == null) {
654
- entity.avatar = '';
655
- }
656
- if (entity.phone == null) {
657
- entity.phone = '';
658
- }
659
- if (entity.passwordTemp == null) {
660
- entity.passwordTemp = '';
661
- }
662
- if (entity.inheritAbilities == null) {
663
- entity.inheritAbilities = false;
664
- }
665
- if (entity.secret == null) {
666
- entity.secret = '';
667
- }
668
- if (entity.emailChangeDate == null) {
669
- entity.emailChangeDate = 0;
670
- }
671
- if (entity.newEmailSecret == null) {
672
- entity.newEmailSecret = '';
673
- }
674
- if (entity.newEmailAddress == null) {
675
- entity.newEmailAddress = '';
676
- }
677
- if (entity.cancelEmailSecret == null) {
678
- entity.cancelEmailSecret = '';
679
- }
680
- if (entity.cancelEmailAddress == null) {
681
- entity.cancelEmailAddress = '';
682
- }
683
- if (entity.recoverSecret == null) {
684
- entity.recoverSecret = '';
685
- }
686
- if (entity.recoverSecretDate == null) {
687
- entity.recoverSecretDate = 0;
688
- }
689
- entity.$getAvatar().then((value) => {
690
- avatar = value;
691
- });
692
- entity.$readyAll(1).then(() => {
693
- entity = entity;
694
- });
695
- }
696
-
697
- let primaryGroupsSearching = false;
698
- let primaryGroups: (GroupClass & AdminGroupData)[] | undefined = undefined;
699
- async function searchPrimaryGroups() {
700
- primaryGroupsSearching = true;
701
- failureMessage = undefined;
702
- if (primaryGroupSearch.trim() == '') {
703
- return;
704
- }
705
- try {
706
- const [options, ...selectors] = queryParser({
707
- query: primaryGroupSearch,
708
- entityClass: Group,
709
- defaultFields: ['groupname', 'name', 'email'],
710
- qrefMap: {
711
- User: {
712
- class: User,
713
- defaultFields: ['username', 'name', 'email'],
714
- },
715
- Group: {
716
- class: Group,
717
- defaultFields: ['groupname', 'name', 'email'],
718
- },
719
- },
720
- });
721
- primaryGroups = (await Group.getPrimaryGroups(options, selectors)).filter(
722
- (group) => {
723
- return !group.$is(entity.group);
724
- }
725
- );
726
- } catch (e: any) {
727
- failureMessage = e?.message;
728
- }
729
- primaryGroupsSearching = false;
730
- }
731
- function primaryGroupSearchKeyDown(event: CustomEvent | KeyboardEvent) {
732
- event = event as KeyboardEvent;
733
- if (event.key === 'Enter') searchPrimaryGroups();
734
- }
735
-
736
- let secondaryGroupsSearching = false;
737
- let secondaryGroups: (GroupClass & AdminGroupData)[] | undefined = undefined;
738
- async function searchSecondaryGroups() {
739
- secondaryGroupsSearching = true;
740
- failureMessage = undefined;
741
- if (secondaryGroupSearch.trim() == '') {
742
- return;
743
- }
744
- try {
745
- const [options, ...selectors] = queryParser({
746
- query: secondaryGroupSearch,
747
- entityClass: Group,
748
- defaultFields: ['groupname', 'name', 'email'],
749
- qrefMap: {
750
- User: {
751
- class: User,
752
- defaultFields: ['username', 'name', 'email'],
753
- },
754
- Group: {
755
- class: Group,
756
- defaultFields: ['groupname', 'name', 'email'],
757
- },
758
- },
759
- });
760
- secondaryGroups = (
761
- await Group.getSecondaryGroups(options, selectors)
762
- ).filter((group) => {
763
- return !group.$inArray(entity.groups ?? []);
764
- });
765
- } catch (e: any) {
766
- failureMessage = e?.message;
767
- }
768
- secondaryGroupsSearching = false;
769
- }
770
- function secondaryGroupSearchKeyDown(event: CustomEvent | KeyboardEvent) {
771
- event = event as KeyboardEvent;
772
- if (event.key === 'Enter') searchSecondaryGroups();
773
- }
774
-
775
- let oldUsername = entity.username;
776
- $: if (entity.username !== oldUsername) {
777
- if (usernameTimer) {
778
- clearTimeout(usernameTimer);
779
- }
780
- usernameTimer = setTimeout(async () => {
781
- if (entity.username === '') {
782
- usernameVerified = undefined;
783
- usernameVerifiedMessage = undefined;
784
- return;
785
- }
786
- try {
787
- const data = await entity.$checkUsername();
788
- usernameVerified = data.result;
789
- usernameVerifiedMessage = data.message;
790
- } catch (e: any) {
791
- usernameVerified = false;
792
- usernameVerifiedMessage = e?.message;
793
- }
794
- }, 400);
795
- oldUsername = entity.username;
796
- }
797
-
798
- let oldEmail = entity.email;
799
- $: if (entity.email !== oldEmail) {
800
- if (emailTimer) {
801
- clearTimeout(emailTimer);
802
- }
803
- emailTimer = setTimeout(async () => {
804
- if (entity.email === '') {
805
- emailVerified = undefined;
806
- emailVerifiedMessage = undefined;
807
- return;
808
- }
809
- try {
810
- const data = await entity.$checkEmail();
811
- emailVerified = data.result;
812
- emailVerifiedMessage = data.message;
813
- } catch (e: any) {
814
- emailVerified = false;
815
- emailVerifiedMessage = e?.message;
816
- }
817
- }, 400);
818
- oldEmail = entity.email;
819
- }
820
-
821
- function doVerifyPassword() {
822
- if (
823
- (entity.passwordTemp == null || entity.passwordTemp === '') &&
824
- passwordVerify === ''
825
- ) {
826
- passwordVerified = undefined;
827
- }
828
- passwordVerified = entity.passwordTemp === passwordVerify;
829
- }
830
-
831
- function addAbility() {
832
- if (ability === '') {
833
- return;
834
- }
835
- entity.abilities?.push(ability);
836
- ability = '';
837
- entity = entity;
838
- }
839
- function abilityKeyDown(event: CustomEvent | KeyboardEvent) {
840
- event = event as KeyboardEvent;
841
- if (event.key === 'Enter') addAbility();
842
- }
843
-
844
- function addTilmeldAdminAbility() {
845
- if (entity.abilities?.indexOf('tilmeld/admin') === -1) {
846
- entity.abilities?.push('tilmeld/admin');
847
- entity = entity;
848
- }
849
- }
850
-
851
- function addSystemAdminAbility() {
852
- if (entity.abilities?.indexOf('system/admin') === -1) {
853
- entity.abilities?.push('system/admin');
854
- entity = entity;
855
- }
856
- }
857
-
858
- async function saveEntity() {
859
- if (
860
- (entity.passwordTemp != null || entity.passwordTemp !== '') &&
861
- entity.passwordTemp !== passwordVerify
862
- ) {
863
- failureMessage = "Passwords don't match!";
864
- return;
865
- }
866
-
867
- saving = true;
868
- failureMessage = undefined;
869
- try {
870
- if (await entity.$save()) {
871
- success = true;
872
- setTimeout(() => {
873
- success = undefined;
874
- }, 1000);
875
- readyEntity();
876
- } else {
877
- failureMessage = 'Error saving user.';
878
- }
879
- } catch (e: any) {
880
- console.log('error:', e);
881
- failureMessage = e?.message;
882
- }
883
- saving = false;
884
- }
885
-
886
- async function deleteEntity() {
887
- failureMessage = undefined;
888
- if (confirm('Are you sure you want to delete this?')) {
889
- saving = true;
890
- try {
891
- if (await entity.$delete()) {
892
- dispatch('leave');
893
- } else {
894
- failureMessage = 'An error occurred.';
895
- }
896
- } catch (e: any) {
897
- failureMessage = e?.message;
898
- }
899
- saving = false;
900
- }
901
- }
902
- </script>