@humandialog/forms.svelte 1.7.18 → 1.7.20

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,492 @@
1
+ <script>
2
+ import {afterUpdate, onMount, tick} from 'svelte'
3
+ import Dialog from './dialog.svelte'
4
+ import {clearActiveItem, isDeviceSmallerThan, startEditing, editable, isValidEmail, randomString} from './utils.js'
5
+ import Icon from './components/icon.svelte'
6
+ import {FaTimes} from 'svelte-icons/fa'
7
+ import {i18n} from './i18n'
8
+ import { reef, session } from '@humandialog/auth.svelte';
9
+ import { onErrorShowAlert } from './stores';
10
+ import EditableParagraph from './components/prose.editable.p.svelte'
11
+ import EditableSpan from './components/prose.editable.span.svelte'
12
+
13
+ export let nameAttrib = "Name";
14
+ export let emailAttrib = "login";
15
+ export let refAttrib = "$ref";
16
+ export let hrefAttrib = ''
17
+
18
+ export let onNewUserAdded = undefined
19
+
20
+ //$: initData()
21
+
22
+ async function initData(...args)
23
+ {
24
+ await reloadData()
25
+ }
26
+
27
+ let authAccessKinds = []
28
+ let appRoles = []
29
+
30
+ let invitingUser = null
31
+ let invitingIdentifier = ''
32
+ let groupName = ''
33
+ let subject = ''
34
+ let content = ''
35
+
36
+ let emailIsInvalid = false
37
+ let email = ''
38
+ let name = ''
39
+ let selectedAccessKey = 0
40
+ let selectedAppRole = ''
41
+ let silently = false
42
+ let accepted = false
43
+
44
+ let showAccessRoles = false
45
+
46
+ let inviteUserIdempotencyToken = ''
47
+
48
+
49
+ let rootDialog;
50
+ export function show(parameters)
51
+ {
52
+ authAccessKinds = parameters.authAccessKinds
53
+ selectedAccessKey = authAccessKinds[0].key
54
+
55
+ appRoles = parameters.appRoles
56
+ selectedAppRole = appRoles[0].name
57
+ showAccessRoles = appRoles.length > 0
58
+
59
+ emailIsInvalid = false
60
+ email = parameters.email ?? ''
61
+ name = parameters.name ?? ''
62
+ silently = parameters.silently ?? false
63
+ accepted = parameters.accepted ?? false
64
+
65
+ inviteUserIdempotencyToken = randomString(8);
66
+
67
+ //elementLink = paramElement
68
+ initData().then((res) => rootDialog.show())
69
+
70
+ }
71
+
72
+ async function reloadData()
73
+ {
74
+ const tInfo = $session.tenants.find((t) => t.id == $session.tid)
75
+ if(tInfo)
76
+ groupName = tInfo.name
77
+
78
+ // ================================
79
+
80
+ let userFields = ''
81
+ if(nameAttrib)
82
+ userFields += (userFields ? ',' : '') + nameAttrib
83
+
84
+ if(emailAttrib)
85
+ userFields += (userFields ? ',' : '') + emailAttrib
86
+
87
+ if(refAttrib)
88
+ userFields += (userFields ? ',' : '') + refAttrib
89
+
90
+ if(hrefAttrib)
91
+ userFields += (userFields ? ',' : '') + hrefAttrib
92
+
93
+ const userResponse = reef.get(`user?fields=${userFields}`, onErrorShowAlert)
94
+ const appInfoResponse = reef.getAppInstanceInfo(onErrorShowAlert)
95
+
96
+ const requestResults = await Promise.all([userResponse, appInfoResponse])
97
+
98
+ invitingUser = requestResults[0].User
99
+ const appInfo = requestResults[1]
100
+
101
+ // ================================
102
+
103
+ subject = i18n({en: 'Invitation to', es: 'Invitación a', pl: 'Zaproszenie do'})
104
+ subject += ' ' + groupName
105
+
106
+ // ================================
107
+
108
+ let invitingName
109
+ if(invitingUser[nameAttrib])
110
+ {
111
+ invitingIdentifier = invitingUser[nameAttrib]
112
+ invitingName = invitingIdentifier
113
+ }
114
+ else
115
+ {
116
+ invitingIdentifier = invitingUser[emailAttrib]
117
+ invitingName = i18n({
118
+ en: `A user ${invitingIdentifier}`,
119
+ es: `Un usuario ${invitingIdentifier}`,
120
+ pl: `Użytkownik ${invitingIdentifier}`
121
+ })
122
+ }
123
+
124
+ let appName = ''
125
+ if(appInfo && appInfo.app && appInfo.app.name)
126
+ {
127
+ appName = i18n({
128
+ en: ` at ${appInfo.app.name}`,
129
+ es: ` en ${appInfo.app.name}`,
130
+ pl: ` w serwisie ${appInfo.app.name}`
131
+ })
132
+ }
133
+
134
+ content = i18n({
135
+ en: `Hello!
136
+ ${invitingName} has invited you to join ${groupName} group${appName}.
137
+ Please follow the link below to accept the invitation:`,
138
+
139
+ es: `¡Hola!
140
+ ${invitingName} te ha invitado a unirte al grupo ${groupName}${appName}.
141
+ Sigue el enlace siguiente para aceptar la invitación:`,
142
+
143
+ pl: `Witaj!
144
+ ${invitingName} zaprosił(a) Cię do grupy ${groupName}${appName}.
145
+ Aby zaakceptować zaproszenie, kliknij poniższy link:`
146
+ })
147
+ }
148
+
149
+ let rootElement;
150
+ let prevHeight = 0
151
+ let closeButtonPos = ''
152
+ afterUpdate( () =>
153
+ {
154
+ if(rootElement)
155
+ {
156
+ const myRect = rootElement.getBoundingClientRect()
157
+ if(myRect.height != prevHeight)
158
+ prevHeight = myRect.height
159
+
160
+ //closeButtonPos = `top: calc(${myRect.top}px - 2.25rem); left: calc(${myRect.right}px - 1rem)`
161
+ if(isDeviceSmallerThan('sm'))
162
+ closeButtonPos = `top: calc(${myRect.top}px - 0.25rem); left: calc(${myRect.right}px - 1.25rem)`
163
+ else
164
+ closeButtonPos = `top: -0.25rem; left: calc(${myRect.width}px - 1.25rem)`
165
+ }
166
+ })
167
+
168
+
169
+ function clearSelection(e)
170
+ {
171
+ clearActiveItem('handy')
172
+ }
173
+
174
+ onMount( () => {
175
+ // clear selection when shown
176
+ clearActiveItem('handy')
177
+ })
178
+
179
+ function closeDialog(e)
180
+ {
181
+ rootDialog.hide()
182
+ }
183
+
184
+ let emailElement
185
+ let nameElement
186
+ let subjectElement
187
+ let contentElement
188
+
189
+ async function invite(e)
190
+ {
191
+ let isValid = true
192
+ isValid = emailElement.validate() && isValid
193
+ isValid = nameElement.validate() && isValid
194
+ isValid = subjectElement.validate() && isValid
195
+ isValid = contentElement.validate() && isValid
196
+
197
+ if(!isValid)
198
+ {
199
+ console.log('invalid input')
200
+ }
201
+ else
202
+ {
203
+ email = emailElement.getValue().trim()
204
+ name = nameElement.getValue().trim()
205
+ subject = subjectElement.getValue().trim()
206
+ content = contentElement.getValue().trim()
207
+
208
+ try {
209
+ const res = await reef.fetch('/json/anyv/sys/invite_user', {
210
+ method: 'POST',
211
+ body: JSON.stringify({
212
+ email: email,
213
+ auth_group: selectedAccessKey,
214
+ //files_group: new_user.files_group,
215
+ role: selectedAppRole,
216
+ client_id: $session.configuration.client_id,
217
+ redirect_uri: `${window.location.origin}/#/auth/cb`,
218
+ state: `${window.location.origin}/#/auth/signin`,
219
+ idempotency_token: inviteUserIdempotencyToken,
220
+ silently: silently,
221
+ accepted: accepted,
222
+ subject: subject,
223
+ content: content,
224
+ set:
225
+ {
226
+ [nameAttrib]: name,
227
+ [emailAttrib]: email
228
+ }
229
+ })
230
+ })
231
+
232
+ if(res.ok)
233
+ {
234
+ const result = await res.json();
235
+ let created_user = result.User;
236
+
237
+ if(onNewUserAdded)
238
+ onNewUserAdded(created_user)
239
+ }
240
+ else
241
+ {
242
+ const err_msg = await res.text();
243
+ onErrorShowAlert(err_msg);
244
+ }
245
+ }
246
+ catch(err)
247
+ {
248
+ onErrorShowAlert(err);
249
+ }
250
+
251
+
252
+ rootDialog.hide()
253
+ }
254
+ }
255
+
256
+ async function copyInvitation(e)
257
+ {
258
+ let isValid = true
259
+ isValid = emailElement.validate() && isValid
260
+ isValid = nameElement.validate() && isValid
261
+ isValid = subjectElement.validate() && isValid
262
+ isValid = contentElement.validate() && isValid
263
+
264
+ if(!isValid)
265
+ {
266
+ console.log('invalid input')
267
+ }
268
+ else
269
+ {
270
+ email = emailElement.getValue().trim()
271
+ name = nameElement.getValue().trim()
272
+ subject = subjectElement.getValue().trim()
273
+ content = contentElement.getValue().trim()
274
+
275
+ try {
276
+ const res = await reef.fetch('/json/anyv/sys/invite_user', {
277
+ method: 'POST',
278
+ body: JSON.stringify({
279
+ email: email,
280
+ auth_group: selectedAccessKey,
281
+ //files_group: new_user.files_group,
282
+ role: selectedAppRole,
283
+ client_id: $session.configuration.client_id,
284
+ redirect_uri: `${window.location.origin}/#/auth/cb`,
285
+ state: `${window.location.origin}/#/auth/signin`,
286
+ idempotency_token: inviteUserIdempotencyToken,
287
+ silently: true,
288
+ accepted: accepted,
289
+ set:
290
+ {
291
+ [nameAttrib]: name,
292
+ [emailAttrib]: email
293
+ }
294
+ })
295
+ })
296
+
297
+ if(res.ok)
298
+ {
299
+ const result = await res.json();
300
+ let created_user = result.User;
301
+
302
+ if(onNewUserAdded)
303
+ onNewUserAdded(created_user)
304
+
305
+
306
+ let params = `username=${email}`
307
+ params += `&client_id=${$session.configuration.client_id}`
308
+ params += `&redirect_uri=${encodeURIComponent(window.location.origin+'/#/auth/cb')}`
309
+ params += `&state=${encodeURIComponent(window.location.origin+'/#/auth/signin')}`
310
+ params += `&tenant=${$session.tid}`
311
+ params += `&scope=${$session.appId}`
312
+
313
+
314
+ const res2 = await reef.fetch(`/auth/regenerate_invitation_link?${params}`)
315
+
316
+ if(res2.ok)
317
+ {
318
+ const result = await res2.json();
319
+ console.log(result.invitation_link)
320
+
321
+ let wholeMessage = `${subject}\n\n${content}\n${result.invitation_link}\n`
322
+ navigator.clipboard.writeText(wholeMessage)
323
+ }
324
+ else
325
+ {
326
+ const err_msg = await res2.text();
327
+ onErrorShowAlert(err_msg);
328
+ }
329
+ }
330
+ else
331
+ {
332
+ const err_msg = await res.text();
333
+ onErrorShowAlert(err_msg);
334
+ }
335
+ }
336
+ catch(err)
337
+ {
338
+ onErrorShowAlert(err);
339
+ }
340
+
341
+
342
+ rootDialog.hide()
343
+ }
344
+ }
345
+
346
+ </script>
347
+
348
+ <!-- svelte-ignore a11y-click-events-have-key-events -->
349
+ <!-- svelte-ignore a11y-no-noninteractive-interactions -->
350
+ <!-- svelte-ignore a11y-no-noninteractive-tabindex-->
351
+
352
+ <Dialog bind:this={rootDialog}>
353
+ <menu bind:this={rootElement} on:click={clearSelection}
354
+ class="w-full sm:min-w-[20rem] max-h-80 sm:max-h-none overflow-y-auto sm:overflow-y-hidden overflow-x-hidden overscroll-contain" >
355
+
356
+ {#if closeButtonPos}
357
+ <button class=" text-stone-800 dark:text-stone-400
358
+ fixed sm:relative
359
+ w-6 h-6 flex items-center justify-center
360
+ focus:outline-none font-medium text-sm text-center"
361
+ style={closeButtonPos}
362
+ on:click={ closeDialog }> <!-- rounded-full text-stone-500 bg-stone-200/70 hover:bg-stone-200 dark:text-stone-500 dark:bg-stone-700/80 dark:hover:bg-stone-700 focus:ring-4 focus:ring-blue-300 dark:focus:ring-blue-800 -->
363
+ <Icon component={FaTimes} s="md"/>
364
+ </button>
365
+ {/if}
366
+
367
+ <article class="w-full prose prose-base prose-zinc dark:prose-invert mb-20 sm:my-5">
368
+ <h3>{i18n({ en: 'Invitation', es: 'Invitación', pl: 'Zaproszenie'})}</h3>
369
+
370
+ <h4>E-mail</h4>
371
+ <EditableParagraph bind:this={emailElement}
372
+ val={email}
373
+ validation={isValidEmail}
374
+ tooltip="E-mail"
375
+ placeholder={i18n({
376
+ en: 'Enter the email address',
377
+ es: 'Introduce la dirección de correo electrónico',
378
+ pl: 'Wprowadź adres e-mail zapraszanej osoby'
379
+ })}
380
+ />
381
+
382
+ <h4>{i18n({en: 'Name', es: 'Nombre', pl: 'Imię'})}</h4>
383
+ <EditableParagraph bind:this={nameElement}
384
+ val={name}
385
+ placeholder={i18n({
386
+ en: 'Enter the name of the person you are inviting',
387
+ es: 'Introduce el nombre de la persona invitada',
388
+ pl: 'Wprowadź imię zapraszanej osoby'
389
+ })}
390
+ />
391
+
392
+ <h4>{i18n({en: 'Inviting', es: 'Invitante', pl: 'Zapraszający'})}</h4>
393
+ <p>{invitingIdentifier}</p>
394
+
395
+ <h4>{i18n({en: 'Invitation subject', es: 'Asunto de la invitación', pl: 'Temat zaproszenia'})}</h4>
396
+ <EditableParagraph bind:this={subjectElement}
397
+ val={subject}
398
+ required
399
+ tooltip={i18n({
400
+ en: 'Click to edit',
401
+ es: 'Haga clic para editar',
402
+ pl: 'Kliknij, aby edytować'
403
+ })}
404
+ />
405
+
406
+
407
+ <h4>{i18n({en: 'Invitation content', es: 'Contenido de la invitación', pl: 'Treść zaproszenia'})}</h4>
408
+ <p>
409
+ <EditableSpan bind:this={contentElement}
410
+ class="text-wrap break-words whitespace-pre-wrap"
411
+ val={content}
412
+ required
413
+ multiline
414
+ tooltip={i18n({
415
+ en: 'Click to edit',
416
+ es: 'Haga clic para editar',
417
+ pl: 'Kliknij, aby edytować'
418
+ })}/>
419
+
420
+ <span class="uppercase text-sm text-zinc-600 dark:text-zinc-400">
421
+ <br>
422
+ &lt;{i18n({en: 'Unique invitation link', es: 'Enlace único de invitación', pl: 'Unikalny link zapraszający'})}&gt;
423
+ </span>
424
+ </p>
425
+
426
+ <h4>{i18n({en: 'Permissions management', es: 'Gestión de permisos', pl: 'Zarządzanie uprawnieniami'})}</h4>
427
+ <p class="flex flex-row">
428
+ {#each authAccessKinds as kind}
429
+ {@const isActive = selectedAccessKey == kind.key}
430
+ <button class="text-sm px-2 mx-1
431
+ text-stone-700 dark:text-stone-300 dark:hover:text-white
432
+ hover:bg-stone-200 dark:hover:bg-stone-700
433
+ border border-stone-300 focus:outline-none dark:border-stone-600
434
+ flex items-center rounded"
435
+ class:bg-stone-200={isActive}
436
+ class:dark:bg-stone-700={isActive}
437
+ class:dark:text-white={isActive}
438
+ disabled={isActive}
439
+ on:click={(e)=> selectedAccessKey=kind.key}>
440
+ {kind.name}
441
+ </button>
442
+ {/each}
443
+
444
+ </p>
445
+
446
+
447
+ {#if showAccessRoles}
448
+ <h4>{i18n({en: 'Role in the application', es: 'Papel en la aplicación', pl: 'Rola w aplikacji'})}</h4>
449
+ <p class="flex flex-row">
450
+ {#each appRoles as role}
451
+ {@const isActive = selectedAppRole == role.name}
452
+ <button class="text-sm px-2 mx-1
453
+ text-stone-700 dark:text-stone-300 dark:hover:text-white
454
+ hover:bg-stone-200 dark:hover:bg-stone-700
455
+ border border-stone-300 focus:outline-none dark:border-stone-600
456
+ flex items-center rounded"
457
+ class:bg-stone-200={isActive}
458
+ class:dark:bg-stone-700={isActive}
459
+ class:dark:text-white={isActive}
460
+ disabled={isActive}
461
+ on:click={(e)=> selectedAppRole=role.name}>
462
+ {role.summary}
463
+ </button>
464
+ {/each}
465
+ </p>
466
+ {/if}
467
+
468
+ <hr/>
469
+ <section class="flex flex-row gap-1">
470
+ <button class=" ms-auto
471
+ py-2.5 px-4
472
+ text-stone-700 dark:text-stone-300 dark:hover:text-white
473
+ hover:bg-stone-200 dark:hover:bg-stone-700
474
+ border border-stone-300 focus:outline-none dark:border-stone-600
475
+ flex items-center rounded"
476
+ on:click={invite}
477
+ >
478
+ {i18n({ en: 'Invite', es: 'Invitar', pl: 'Zaproś'})}
479
+ </button>
480
+ <button class=" py-2.5 px-4
481
+ text-stone-700 dark:text-stone-300 dark:hover:text-white
482
+ hover:bg-stone-200 dark:hover:bg-stone-700
483
+ border border-stone-300 focus:outline-none dark:border-stone-600
484
+ flex items-center rounded"
485
+ on:click={copyInvitation}
486
+ >
487
+ {i18n({ en: 'Copy invitation', es: 'Copiar invitación', pl: 'Skopiuj zaproszenie'})}
488
+ </button>
489
+ </section>
490
+ </article>
491
+ </menu>
492
+ </Dialog>
@@ -0,0 +1,34 @@
1
+ /** @typedef {typeof __propDef.props} TenantProps */
2
+ /** @typedef {typeof __propDef.events} TenantEvents */
3
+ /** @typedef {typeof __propDef.slots} TenantSlots */
4
+ export default class Tenant extends SvelteComponentTyped<{
5
+ show?: ((parameters: any) => void) | undefined;
6
+ nameAttrib?: string | undefined;
7
+ emailAttrib?: string | undefined;
8
+ refAttrib?: string | undefined;
9
+ hrefAttrib?: string | undefined;
10
+ onNewUserAdded?: any;
11
+ }, {
12
+ [evt: string]: CustomEvent<any>;
13
+ }, {}> {
14
+ get show(): (parameters: any) => void;
15
+ }
16
+ export type TenantProps = typeof __propDef.props;
17
+ export type TenantEvents = typeof __propDef.events;
18
+ export type TenantSlots = typeof __propDef.slots;
19
+ import { SvelteComponentTyped } from "svelte";
20
+ declare const __propDef: {
21
+ props: {
22
+ show?: ((parameters: any) => void) | undefined;
23
+ nameAttrib?: string | undefined;
24
+ emailAttrib?: string | undefined;
25
+ refAttrib?: string | undefined;
26
+ hrefAttrib?: string | undefined;
27
+ onNewUserAdded?: any;
28
+ };
29
+ events: {
30
+ [evt: string]: CustomEvent<any>;
31
+ };
32
+ slots: {};
33
+ };
34
+ export {};