@nymphjs/tilmeld-components 1.0.0-beta.13 → 1.0.0-beta.131
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/CHANGELOG.md +494 -0
- package/README.md +6 -4
- package/dist/Account.svelte +516 -0
- package/dist/ChangePassword.svelte +309 -0
- package/dist/Login.svelte +603 -0
- package/dist/Recover.svelte +463 -0
- package/dist/RevokeTokens.svelte +236 -0
- package/dist/TwoFactor.svelte +544 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -0
- package/{lib → dist}/index.js.map +1 -1
- package/jest.config.js +32 -0
- package/package.json +54 -43
- package/src/Account.svelte +280 -113
- package/src/ChangePassword.svelte +147 -51
- package/src/Login.svelte +328 -115
- package/src/Recover.svelte +180 -78
- package/src/RevokeTokens.svelte +236 -0
- package/src/TwoFactor.svelte +544 -0
- package/src/index.d.ts +3 -1
- package/src/index.ts +3 -1
- package/tsconfig.json +8 -4
- package/typedoc.json +4 -0
- package/dist/index.cjs +0 -3
- package/dist/index.cjs.LICENSE.txt +0 -45
- package/dist/index.cjs.map +0 -1
- package/jest.config.cjs +0 -6
- package/lib/Account.svelte +0 -333
- package/lib/Account.svelte.map +0 -1
- package/lib/ChangePassword.svelte +0 -188
- package/lib/ChangePassword.svelte.map +0 -1
- package/lib/Login.svelte +0 -353
- package/lib/Login.svelte.map +0 -1
- package/lib/Recover.svelte +0 -339
- package/lib/Recover.svelte.map +0 -1
- package/lib/index.d.ts +0 -5
- package/lib/index.js +0 -6
- package/webpack.config.cjs +0 -35
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
<svelte:options runes />
|
|
2
|
+
|
|
3
|
+
{#if $user != null}
|
|
4
|
+
<Dialog
|
|
5
|
+
{use}
|
|
6
|
+
bind:open
|
|
7
|
+
aria-labelledby="tilmeld-password-title"
|
|
8
|
+
aria-describedby="tilmeld-password-content"
|
|
9
|
+
surface$class="tilmeld-password-dialog-surface"
|
|
10
|
+
{...exclude(restProps, [
|
|
11
|
+
'password$',
|
|
12
|
+
'newPassword$',
|
|
13
|
+
'newPassword2$',
|
|
14
|
+
'revokeTokens$',
|
|
15
|
+
'closeButton$',
|
|
16
|
+
'saveButton$',
|
|
17
|
+
'progress$',
|
|
18
|
+
])}
|
|
19
|
+
>
|
|
20
|
+
<!-- Title cannot contain leading whitespace due to mdc-typography-baseline-top() -->
|
|
21
|
+
<Title id="tilmeld-password-title">{title}</Title>
|
|
22
|
+
<Content id="tilmeld-password-content">
|
|
23
|
+
<div>
|
|
24
|
+
<Textfield
|
|
25
|
+
bind:value={currentPassword}
|
|
26
|
+
label="Current Password"
|
|
27
|
+
type="password"
|
|
28
|
+
style="width: 100%;"
|
|
29
|
+
input$autocomplete="current-password"
|
|
30
|
+
{...prefixFilter(restProps, 'password$')}
|
|
31
|
+
/>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div>
|
|
35
|
+
<Textfield
|
|
36
|
+
bind:value={newPassword}
|
|
37
|
+
label="New Password"
|
|
38
|
+
type="password"
|
|
39
|
+
style="width: 100%;"
|
|
40
|
+
input$autocomplete="new-password"
|
|
41
|
+
{...prefixFilter(restProps, 'newPassword$')}
|
|
42
|
+
/>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div>
|
|
46
|
+
<Textfield
|
|
47
|
+
bind:value={newPassword2}
|
|
48
|
+
label="Re-enter New Password"
|
|
49
|
+
type="password"
|
|
50
|
+
style="width: 100%;"
|
|
51
|
+
input$autocomplete="new-password"
|
|
52
|
+
{...prefixFilter(restProps, 'newPassword2$')}
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
|
|
56
|
+
<div style="margin: 1em 0;">
|
|
57
|
+
<FormField>
|
|
58
|
+
<Checkbox
|
|
59
|
+
bind:checked={revokeCurrentTokens}
|
|
60
|
+
{...prefixFilter(restProps, 'revokeTokens$')}
|
|
61
|
+
/>
|
|
62
|
+
{#snippet label()}
|
|
63
|
+
Log out of all other sessions.
|
|
64
|
+
<span
|
|
65
|
+
style="display: block; font-size: 0.7em; height: 0; overflow: visible;"
|
|
66
|
+
>
|
|
67
|
+
Use this if you think you've been hacked.
|
|
68
|
+
</span>
|
|
69
|
+
{/snippet}
|
|
70
|
+
</FormField>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
{@render additional?.()}
|
|
74
|
+
|
|
75
|
+
{#if failureMessage}
|
|
76
|
+
<div class="tilmeld-password-failure">
|
|
77
|
+
{failureMessage}
|
|
78
|
+
</div>
|
|
79
|
+
{/if}
|
|
80
|
+
|
|
81
|
+
{#if loading}
|
|
82
|
+
<div class="tilmeld-password-loading">
|
|
83
|
+
<CircularProgress
|
|
84
|
+
style="height: 24px; width: 24px;"
|
|
85
|
+
indeterminate
|
|
86
|
+
{...prefixFilter(restProps, 'progress$')}
|
|
87
|
+
/>
|
|
88
|
+
</div>
|
|
89
|
+
{/if}
|
|
90
|
+
</Content>
|
|
91
|
+
<Actions>
|
|
92
|
+
<Button disabled={loading} {...prefixFilter(restProps, 'closeButton$')}>
|
|
93
|
+
<Label>Close</Label>
|
|
94
|
+
</Button>
|
|
95
|
+
<Button
|
|
96
|
+
onclick={preventDefault(stopPropagation(changePassword))}
|
|
97
|
+
disabled={loading}
|
|
98
|
+
{...prefixFilter(restProps, 'saveButton$')}
|
|
99
|
+
>
|
|
100
|
+
<Label>Change Password</Label>
|
|
101
|
+
</Button>
|
|
102
|
+
</Actions>
|
|
103
|
+
</Dialog>
|
|
104
|
+
{/if}
|
|
105
|
+
|
|
106
|
+
<script lang="ts">
|
|
107
|
+
import type { ComponentProps, Snippet } from 'svelte';
|
|
108
|
+
import { onMount, onDestroy } from 'svelte';
|
|
109
|
+
import type { Writable } from 'svelte/store';
|
|
110
|
+
import { writable } from 'svelte/store';
|
|
111
|
+
import CircularProgress from '@smui/circular-progress';
|
|
112
|
+
import Dialog, { Title, Content, Actions } from '@smui/dialog';
|
|
113
|
+
import Textfield from '@smui/textfield';
|
|
114
|
+
import Checkbox from '@smui/checkbox';
|
|
115
|
+
import FormField from '@smui/form-field';
|
|
116
|
+
import Button, { Label } from '@smui/button';
|
|
117
|
+
import type { ActionArray } from '@smui/common/internal';
|
|
118
|
+
import { exclude, prefixFilter } from '@smui/common/internal';
|
|
119
|
+
import { preventDefault, stopPropagation } from '@smui/common/events';
|
|
120
|
+
import type { CurrentUserData } from '@nymphjs/tilmeld-client';
|
|
121
|
+
import type { User as UserClass } from '@nymphjs/tilmeld-client';
|
|
122
|
+
|
|
123
|
+
type OwnProps = {
|
|
124
|
+
/**
|
|
125
|
+
* An array of Action or [Action, ActionProps] to be applied to the element.
|
|
126
|
+
*/
|
|
127
|
+
use?: ActionArray;
|
|
128
|
+
/**
|
|
129
|
+
* Whether the dialog is open.
|
|
130
|
+
*/
|
|
131
|
+
open?: boolean;
|
|
132
|
+
/**
|
|
133
|
+
* The title of the dialog.
|
|
134
|
+
*/
|
|
135
|
+
title?: string;
|
|
136
|
+
/**
|
|
137
|
+
* The User class from Nymph.
|
|
138
|
+
*/
|
|
139
|
+
User: typeof UserClass;
|
|
140
|
+
/**
|
|
141
|
+
* A writable store of the current user.
|
|
142
|
+
*
|
|
143
|
+
* It will be retrieved from the server if not provided.
|
|
144
|
+
*/
|
|
145
|
+
user?: Writable<(UserClass & CurrentUserData) | null | undefined>;
|
|
146
|
+
/**
|
|
147
|
+
* User provided. You can bind to it if you need to.
|
|
148
|
+
*/
|
|
149
|
+
currentPassword?: string;
|
|
150
|
+
/**
|
|
151
|
+
* User provided. You can bind to it if you need to.
|
|
152
|
+
*/
|
|
153
|
+
newPassword?: string;
|
|
154
|
+
/**
|
|
155
|
+
* User provided. You can bind to it if you need to.
|
|
156
|
+
*/
|
|
157
|
+
newPassword2?: string;
|
|
158
|
+
/**
|
|
159
|
+
* User provided. You can bind to it if you need to.
|
|
160
|
+
*/
|
|
161
|
+
revokeCurrentTokens?: boolean;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* A spot for additional content.
|
|
165
|
+
*/
|
|
166
|
+
additional?: Snippet;
|
|
167
|
+
};
|
|
168
|
+
let {
|
|
169
|
+
use = [],
|
|
170
|
+
open = $bindable(false),
|
|
171
|
+
title = 'Change Your Password',
|
|
172
|
+
User,
|
|
173
|
+
user = $bindable(writable(false as unknown as undefined)),
|
|
174
|
+
currentPassword = $bindable(''),
|
|
175
|
+
newPassword = $bindable(''),
|
|
176
|
+
newPassword2 = $bindable(''),
|
|
177
|
+
revokeCurrentTokens = $bindable(false),
|
|
178
|
+
additional,
|
|
179
|
+
...restProps
|
|
180
|
+
}: OwnProps & {
|
|
181
|
+
[
|
|
182
|
+
k in keyof ComponentProps<typeof Textfield> as `password\$${k}`
|
|
183
|
+
]?: ComponentProps<typeof Textfield>[k];
|
|
184
|
+
} & {
|
|
185
|
+
[
|
|
186
|
+
k in keyof ComponentProps<typeof Textfield> as `newPassword\$${k}`
|
|
187
|
+
]?: ComponentProps<typeof Textfield>[k];
|
|
188
|
+
} & {
|
|
189
|
+
[
|
|
190
|
+
k in keyof ComponentProps<typeof Textfield> as `newPassword2\$${k}`
|
|
191
|
+
]?: ComponentProps<typeof Textfield>[k];
|
|
192
|
+
} & {
|
|
193
|
+
[
|
|
194
|
+
k in keyof ComponentProps<typeof Checkbox> as `revokeTokens\$${k}`
|
|
195
|
+
]?: ComponentProps<typeof Checkbox>[k];
|
|
196
|
+
} & {
|
|
197
|
+
[
|
|
198
|
+
k in keyof ComponentProps<typeof CircularProgress> as `progress\$${k}`
|
|
199
|
+
]?: ComponentProps<typeof CircularProgress>[k];
|
|
200
|
+
} & {
|
|
201
|
+
[
|
|
202
|
+
k in keyof ComponentProps<
|
|
203
|
+
typeof Button<undefined, 'button'>
|
|
204
|
+
> as `closeButton\$${k}`
|
|
205
|
+
]?: ComponentProps<typeof Button<undefined, 'button'>>[k];
|
|
206
|
+
} & {
|
|
207
|
+
[
|
|
208
|
+
k in keyof ComponentProps<
|
|
209
|
+
typeof Button<undefined, 'button'>
|
|
210
|
+
> as `saveButton\$${k}`
|
|
211
|
+
]?: ComponentProps<typeof Button<undefined, 'button'>>[k];
|
|
212
|
+
} = $props();
|
|
213
|
+
|
|
214
|
+
let loading = $state(false);
|
|
215
|
+
let failureMessage: string | undefined = $state();
|
|
216
|
+
|
|
217
|
+
$effect(() => {
|
|
218
|
+
if (!open) {
|
|
219
|
+
loading = false;
|
|
220
|
+
failureMessage = undefined;
|
|
221
|
+
currentPassword = '';
|
|
222
|
+
newPassword = '';
|
|
223
|
+
newPassword2 = '';
|
|
224
|
+
revokeCurrentTokens = false;
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
const onLogin = (currentUser: UserClass & CurrentUserData) => {
|
|
229
|
+
$user = currentUser;
|
|
230
|
+
};
|
|
231
|
+
const onLogout = () => {
|
|
232
|
+
$user = null;
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
onMount(async () => {
|
|
236
|
+
User.on('login', onLogin);
|
|
237
|
+
User.on('logout', onLogout);
|
|
238
|
+
if ($user === (false as unknown as undefined)) {
|
|
239
|
+
$user = undefined;
|
|
240
|
+
$user = await User.current();
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
onDestroy(() => {
|
|
245
|
+
User.off('login', onLogin);
|
|
246
|
+
User.off('logout', onLogout);
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
async function changePassword() {
|
|
250
|
+
if (currentPassword === '') {
|
|
251
|
+
failureMessage = 'You need to enter your current password';
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (newPassword !== newPassword2) {
|
|
255
|
+
failureMessage = 'Your passwords do not match.';
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
if (newPassword === '') {
|
|
259
|
+
failureMessage = 'You need to enter a new password';
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
failureMessage = undefined;
|
|
264
|
+
loading = true;
|
|
265
|
+
|
|
266
|
+
// Get the current user again, in case their data has changed.
|
|
267
|
+
$user = await User.current();
|
|
268
|
+
|
|
269
|
+
if ($user == null) {
|
|
270
|
+
failureMessage = 'You must be logged in.';
|
|
271
|
+
loading = false;
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
// Change the user's password.
|
|
277
|
+
const data = await $user.$changePassword({
|
|
278
|
+
currentPassword,
|
|
279
|
+
newPassword,
|
|
280
|
+
revokeCurrentTokens,
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (!data.result) {
|
|
284
|
+
failureMessage = data.message;
|
|
285
|
+
} else {
|
|
286
|
+
open = false;
|
|
287
|
+
}
|
|
288
|
+
} catch (e: any) {
|
|
289
|
+
failureMessage = e?.message;
|
|
290
|
+
}
|
|
291
|
+
loading = false;
|
|
292
|
+
}
|
|
293
|
+
</script>
|
|
294
|
+
|
|
295
|
+
<style>
|
|
296
|
+
:global(.mdc-dialog .mdc-dialog__surface.tilmeld-password-dialog-surface) {
|
|
297
|
+
width: 360px;
|
|
298
|
+
max-width: calc(100vw - 32px);
|
|
299
|
+
}
|
|
300
|
+
.tilmeld-password-failure {
|
|
301
|
+
margin-top: 1em;
|
|
302
|
+
color: var(--mdc-theme-error, #f00);
|
|
303
|
+
}
|
|
304
|
+
.tilmeld-password-loading {
|
|
305
|
+
display: flex;
|
|
306
|
+
justify-content: center;
|
|
307
|
+
align-items: center;
|
|
308
|
+
}
|
|
309
|
+
</style>
|