@nymphjs/tilmeld-components 1.0.0-alpha.2
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 +10 -0
- package/LICENSE +202 -0
- package/README.md +31 -0
- package/dist/index.js +3 -0
- package/dist/index.js.LICENSE.txt +47 -0
- package/dist/index.js.map +1 -0
- package/jest.config.js +6 -0
- package/package.json +57 -0
- package/src/Account.svelte +317 -0
- package/src/ChangePassword.svelte +176 -0
- package/src/Login.svelte +314 -0
- package/src/Recover.svelte +309 -0
- package/src/global.d.ts +1 -0
- package/src/index.d.ts +6 -0
- package/src/index.ts +6 -0
- package/tsconfig.json +10 -0
- package/webpack.config.js +35 -0
package/src/Login.svelte
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
{#if clientConfig == null || registering || loggingIn}
|
|
2
|
+
<CircularProgress style="height: 32px; width: 32px;" indeterminate />
|
|
3
|
+
{:else}
|
|
4
|
+
<div style="width: {width};">
|
|
5
|
+
{#if successRegisteredMessage}
|
|
6
|
+
<div>
|
|
7
|
+
{successRegisteredMessage}
|
|
8
|
+
</div>
|
|
9
|
+
{:else if successLoginMessage}
|
|
10
|
+
<div>
|
|
11
|
+
{successLoginMessage}
|
|
12
|
+
</div>
|
|
13
|
+
{:else}
|
|
14
|
+
<form on:submit|preventDefault>
|
|
15
|
+
<div>
|
|
16
|
+
<Textfield
|
|
17
|
+
bind:value={username}
|
|
18
|
+
bind:this={usernameElem}
|
|
19
|
+
label={clientConfig.emailUsernames ? 'Email' : 'Username'}
|
|
20
|
+
type={clientConfig.emailUsernames ? 'email' : 'text'}
|
|
21
|
+
style="width: 100%;"
|
|
22
|
+
helperLine$style="width: 100%;"
|
|
23
|
+
invalid={usernameVerified === false}
|
|
24
|
+
input$autocomplete={clientConfig.emailUsernames
|
|
25
|
+
? 'email'
|
|
26
|
+
: 'username'}
|
|
27
|
+
input$name={clientConfig.emailUsernames ? 'email' : 'username'}
|
|
28
|
+
input$autocapitalize="off"
|
|
29
|
+
input$spellcheck="false"
|
|
30
|
+
>
|
|
31
|
+
<HelperText persistent slot="helper">
|
|
32
|
+
{#if !existingUser}
|
|
33
|
+
{usernameVerifiedMessage ?? ''}
|
|
34
|
+
{/if}
|
|
35
|
+
</HelperText>
|
|
36
|
+
</Textfield>
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
<div>
|
|
40
|
+
<Textfield
|
|
41
|
+
bind:value={password}
|
|
42
|
+
label="Password"
|
|
43
|
+
type="password"
|
|
44
|
+
style="width: 100%;"
|
|
45
|
+
input$autocomplete="{clientConfig.allowRegistration && !existingUser
|
|
46
|
+
? 'new'
|
|
47
|
+
: 'current'}-password"
|
|
48
|
+
input$name="password"
|
|
49
|
+
/>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
{#if clientConfig.allowRegistration && !existingUser}
|
|
53
|
+
<div>
|
|
54
|
+
<Textfield
|
|
55
|
+
bind:value={password2}
|
|
56
|
+
label="Re-enter Password"
|
|
57
|
+
type="password"
|
|
58
|
+
style="width: 100%;"
|
|
59
|
+
input$autocomplete="new-password"
|
|
60
|
+
input$name="password2"
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
{#if clientConfig.regFields.indexOf('name') !== -1}
|
|
65
|
+
<div>
|
|
66
|
+
<Textfield
|
|
67
|
+
bind:value={name}
|
|
68
|
+
label="Name"
|
|
69
|
+
type="text"
|
|
70
|
+
style="width: 100%;"
|
|
71
|
+
input$autocomplete="name"
|
|
72
|
+
input$name="name"
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
{/if}
|
|
76
|
+
|
|
77
|
+
{#if !clientConfig.emailUsernames && clientConfig.regFields.indexOf('email') !== -1}
|
|
78
|
+
<div>
|
|
79
|
+
<Textfield
|
|
80
|
+
bind:value={email}
|
|
81
|
+
label="Email"
|
|
82
|
+
type="email"
|
|
83
|
+
style="width: 100%;"
|
|
84
|
+
input$autocomplete="email"
|
|
85
|
+
input$name="email"
|
|
86
|
+
input$autocapitalize="off"
|
|
87
|
+
input$spellcheck="false"
|
|
88
|
+
/>
|
|
89
|
+
</div>
|
|
90
|
+
{/if}
|
|
91
|
+
|
|
92
|
+
{#if clientConfig.regFields.indexOf('phone') !== -1}
|
|
93
|
+
<div>
|
|
94
|
+
<Textfield
|
|
95
|
+
bind:value={phone}
|
|
96
|
+
label="Phone Number"
|
|
97
|
+
type="tel"
|
|
98
|
+
style="width: 100%;"
|
|
99
|
+
input$autocomplete="tel"
|
|
100
|
+
input$name="phone"
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
{/if}
|
|
104
|
+
{/if}
|
|
105
|
+
|
|
106
|
+
{#if failureMessage}
|
|
107
|
+
<div class="tilmeld-login-failure">
|
|
108
|
+
{failureMessage}
|
|
109
|
+
</div>
|
|
110
|
+
{/if}
|
|
111
|
+
|
|
112
|
+
<div class="tilmeld-login-buttons">
|
|
113
|
+
{#if existingUser}
|
|
114
|
+
<Button variant="raised" type="submit" on:click={login}>
|
|
115
|
+
<Label>Log In</Label>
|
|
116
|
+
</Button>
|
|
117
|
+
{:else}
|
|
118
|
+
<Button variant="raised" type="submit" on:click={register}>
|
|
119
|
+
<Label>Create Account</Label>
|
|
120
|
+
</Button>
|
|
121
|
+
{/if}
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
{#if clientConfig.allowRegistration && showExistingUserToggle}
|
|
125
|
+
<div class="tilmeld-login-action">
|
|
126
|
+
{#if existingUser}
|
|
127
|
+
<a
|
|
128
|
+
href="javascript:void(0);"
|
|
129
|
+
on:click={() => (existingUser = false)}
|
|
130
|
+
>
|
|
131
|
+
Create an account.
|
|
132
|
+
</a>
|
|
133
|
+
{:else}
|
|
134
|
+
<a
|
|
135
|
+
href="javascript:void(0);"
|
|
136
|
+
on:click={() => (existingUser = true)}
|
|
137
|
+
>
|
|
138
|
+
Log in to your account.
|
|
139
|
+
</a>
|
|
140
|
+
{/if}
|
|
141
|
+
</div>
|
|
142
|
+
{/if}
|
|
143
|
+
|
|
144
|
+
{#if !hideRecovery && clientConfig.pwRecovery && existingUser}
|
|
145
|
+
<div class="tilmeld-login-action">
|
|
146
|
+
<a href="javascript:void(0);" on:click={() => (recoverOpen = true)}>
|
|
147
|
+
I can't access my account.
|
|
148
|
+
</a>
|
|
149
|
+
</div>
|
|
150
|
+
<Recover bind:open={recoverOpen} />
|
|
151
|
+
{/if}
|
|
152
|
+
</form>
|
|
153
|
+
{/if}
|
|
154
|
+
</div>
|
|
155
|
+
{/if}
|
|
156
|
+
|
|
157
|
+
<script lang="ts">
|
|
158
|
+
import { onMount, createEventDispatcher } from 'svelte';
|
|
159
|
+
import CircularProgress from '@smui/circular-progress';
|
|
160
|
+
import Button, { Label } from '@smui/button';
|
|
161
|
+
import Textfield, { TextfieldComponentDev } from '@smui/textfield';
|
|
162
|
+
import HelperText from '@smui/textfield/helper-text/index';
|
|
163
|
+
import {
|
|
164
|
+
getClientConfig,
|
|
165
|
+
login as loginAction,
|
|
166
|
+
register as registerAction,
|
|
167
|
+
checkUsername as checkUsernameAction,
|
|
168
|
+
ClientConfig,
|
|
169
|
+
} from '@nymphjs/tilmeld-client';
|
|
170
|
+
import Recover from './Recover.svelte';
|
|
171
|
+
|
|
172
|
+
const dispatch = createEventDispatcher();
|
|
173
|
+
|
|
174
|
+
/** Hide the recovery link that only appears if password recovery is on. */
|
|
175
|
+
export let hideRecovery = false;
|
|
176
|
+
/** Give focus to the username box (or email box) when the form is ready. */
|
|
177
|
+
export let autofocus = true;
|
|
178
|
+
/** This determines whether the 'Log In' or 'Sign Up' button is activated and which corresponding form is shown. */
|
|
179
|
+
export let existingUser = true;
|
|
180
|
+
/** Whether to show the 'Log In'/'Sign Up' switcher buttons. */
|
|
181
|
+
export let showExistingUserToggle = true;
|
|
182
|
+
/** The width of the form. */
|
|
183
|
+
export let width = '220px';
|
|
184
|
+
|
|
185
|
+
/** User provided. You can bind to it if you need to. */
|
|
186
|
+
export let username = '';
|
|
187
|
+
/** User provided. You can bind to it if you need to. */
|
|
188
|
+
export let password = '';
|
|
189
|
+
/** User provided. You can bind to it if you need to. */
|
|
190
|
+
export let password2 = '';
|
|
191
|
+
/** User provided. You can bind to it if you need to. */
|
|
192
|
+
export let name = '';
|
|
193
|
+
/** User provided. You can bind to it if you need to. */
|
|
194
|
+
export let email = '';
|
|
195
|
+
/** User provided. You can bind to it if you need to. */
|
|
196
|
+
export let phone = '';
|
|
197
|
+
|
|
198
|
+
let clientConfig: ClientConfig | undefined = undefined;
|
|
199
|
+
let usernameElem: TextfieldComponentDev;
|
|
200
|
+
let successLoginMessage: string | undefined = undefined;
|
|
201
|
+
let successRegisteredMessage: string | undefined = undefined;
|
|
202
|
+
let failureMessage: string | undefined = undefined;
|
|
203
|
+
let usernameTimer: NodeJS.Timeout | undefined = undefined;
|
|
204
|
+
let usernameVerified: boolean | undefined = undefined;
|
|
205
|
+
let usernameVerifiedMessage: string | undefined = undefined;
|
|
206
|
+
let registering = false;
|
|
207
|
+
let loggingIn = false;
|
|
208
|
+
let recoverOpen = false;
|
|
209
|
+
|
|
210
|
+
$: nameFirst = name?.match(/^(.*?)(?: ([^ ]+))?$/)?.[1] ?? '';
|
|
211
|
+
$: nameLast = name?.match(/^(.*?)(?: ([^ ]+))?$/)?.[2] ?? '';
|
|
212
|
+
|
|
213
|
+
let _previousExistingUser = existingUser;
|
|
214
|
+
$: if (existingUser !== _previousExistingUser) {
|
|
215
|
+
failureMessage = '';
|
|
216
|
+
checkUsername(username);
|
|
217
|
+
_previousExistingUser = existingUser;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
$: {
|
|
221
|
+
checkUsername(username);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
onMount(async () => {
|
|
225
|
+
clientConfig = await getClientConfig();
|
|
226
|
+
if (!clientConfig.allowRegistration) {
|
|
227
|
+
existingUser = true;
|
|
228
|
+
}
|
|
229
|
+
if (autofocus && usernameElem) {
|
|
230
|
+
usernameElem.focus();
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
async function login() {
|
|
235
|
+
successLoginMessage = undefined;
|
|
236
|
+
failureMessage = undefined;
|
|
237
|
+
loggingIn = true;
|
|
238
|
+
try {
|
|
239
|
+
const data = await loginAction(username, password);
|
|
240
|
+
successLoginMessage = data.message;
|
|
241
|
+
dispatch('login', { user: data.user });
|
|
242
|
+
} catch (e: any) {
|
|
243
|
+
failureMessage = e?.message;
|
|
244
|
+
}
|
|
245
|
+
loggingIn = false;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function register() {
|
|
249
|
+
successRegisteredMessage = undefined;
|
|
250
|
+
successLoginMessage = undefined;
|
|
251
|
+
failureMessage = undefined;
|
|
252
|
+
registering = true;
|
|
253
|
+
try {
|
|
254
|
+
const data = await registerAction({
|
|
255
|
+
username,
|
|
256
|
+
usernameVerified: !!usernameVerified,
|
|
257
|
+
password,
|
|
258
|
+
password2,
|
|
259
|
+
email,
|
|
260
|
+
nameFirst,
|
|
261
|
+
nameLast,
|
|
262
|
+
phone,
|
|
263
|
+
});
|
|
264
|
+
successRegisteredMessage = data.message;
|
|
265
|
+
dispatch('register', { user: data.user });
|
|
266
|
+
if (data.loggedin) {
|
|
267
|
+
successLoginMessage = data.message;
|
|
268
|
+
dispatch('login', { user: data.user });
|
|
269
|
+
}
|
|
270
|
+
} catch (e: any) {
|
|
271
|
+
failureMessage = e?.message;
|
|
272
|
+
}
|
|
273
|
+
registering = false;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function checkUsername(newValue: string) {
|
|
277
|
+
usernameVerified = undefined;
|
|
278
|
+
usernameVerifiedMessage = undefined;
|
|
279
|
+
if (usernameTimer) {
|
|
280
|
+
clearTimeout(usernameTimer);
|
|
281
|
+
usernameTimer = undefined;
|
|
282
|
+
}
|
|
283
|
+
if (newValue === '' || existingUser) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
usernameTimer = setTimeout(async () => {
|
|
287
|
+
try {
|
|
288
|
+
const data = await checkUsernameAction(newValue);
|
|
289
|
+
usernameVerified = true;
|
|
290
|
+
usernameVerifiedMessage = data.message;
|
|
291
|
+
} catch (e: any) {
|
|
292
|
+
usernameVerified = false;
|
|
293
|
+
usernameVerifiedMessage = e?.message;
|
|
294
|
+
}
|
|
295
|
+
}, 400);
|
|
296
|
+
}
|
|
297
|
+
</script>
|
|
298
|
+
|
|
299
|
+
<style>
|
|
300
|
+
.tilmeld-login-buttons {
|
|
301
|
+
display: flex;
|
|
302
|
+
flex-direction: row;
|
|
303
|
+
justify-content: start;
|
|
304
|
+
align-items: center;
|
|
305
|
+
margin-top: 1em;
|
|
306
|
+
}
|
|
307
|
+
.tilmeld-login-action {
|
|
308
|
+
margin-top: 1em;
|
|
309
|
+
}
|
|
310
|
+
.tilmeld-login-failure {
|
|
311
|
+
margin-top: 1em;
|
|
312
|
+
color: var(--mdc-theme-error, #f00);
|
|
313
|
+
}
|
|
314
|
+
</style>
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
{#if clientConfig != null && clientConfig.pwRecovery}
|
|
2
|
+
<Dialog
|
|
3
|
+
bind:open
|
|
4
|
+
aria-labelledby="tilmeld-recovery-title"
|
|
5
|
+
aria-describedby="tilmeld-recovery-content"
|
|
6
|
+
surface$class="tilmeld-recover-dialog-surface"
|
|
7
|
+
>
|
|
8
|
+
<!-- Title cannot contain leading whitespace due to mdc-typography-baseline-top() -->
|
|
9
|
+
<Title id="tilmeld-recovery-title">Recover Your Account</Title>
|
|
10
|
+
<Content id="tilmeld-recovery-content">
|
|
11
|
+
{#if successRecoveredMessage}
|
|
12
|
+
{successRecoveredMessage}
|
|
13
|
+
{:else}
|
|
14
|
+
{#if !hasSentSecret}
|
|
15
|
+
{#if !clientConfig.emailUsernames}
|
|
16
|
+
<div>
|
|
17
|
+
<FormField style="margin-right: 1em;">
|
|
18
|
+
<Radio bind:group={recoveryType} value="password" />
|
|
19
|
+
<span slot="label">I don't know my password.</span>
|
|
20
|
+
</FormField>
|
|
21
|
+
<FormField style="margin-right: 1em;">
|
|
22
|
+
<Radio bind:group={recoveryType} value="username" />
|
|
23
|
+
<span slot="label">I don't know my username.</span>
|
|
24
|
+
</FormField>
|
|
25
|
+
</div>
|
|
26
|
+
{/if}
|
|
27
|
+
|
|
28
|
+
<div>
|
|
29
|
+
{#if recoveryType === 'password'}
|
|
30
|
+
<p>
|
|
31
|
+
To reset your password, type the {clientConfig.emailUsernames
|
|
32
|
+
? 'email'
|
|
33
|
+
: 'username'}
|
|
34
|
+
you use to sign in below.
|
|
35
|
+
</p>
|
|
36
|
+
{/if}
|
|
37
|
+
{#if recoveryType === 'username'}
|
|
38
|
+
<p>
|
|
39
|
+
To get your username, type your email as you entered it when
|
|
40
|
+
creating your account.
|
|
41
|
+
</p>
|
|
42
|
+
{/if}
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<div>
|
|
46
|
+
<Textfield
|
|
47
|
+
bind:this={accountElem}
|
|
48
|
+
bind:value={account}
|
|
49
|
+
label={clientConfig.emailUsernames || recoveryType === 'username'
|
|
50
|
+
? 'Email Address'
|
|
51
|
+
: 'Username'}
|
|
52
|
+
type={clientConfig.emailUsernames || recoveryType === 'username'
|
|
53
|
+
? 'email'
|
|
54
|
+
: 'text'}
|
|
55
|
+
input$autocomplete={clientConfig.emailUsernames ||
|
|
56
|
+
recoveryType === 'username'
|
|
57
|
+
? 'email'
|
|
58
|
+
: 'username'}
|
|
59
|
+
input$autocapitalize="off"
|
|
60
|
+
input$spellcheck="false"
|
|
61
|
+
/>
|
|
62
|
+
</div>
|
|
63
|
+
|
|
64
|
+
{#if recoveryType === 'password'}
|
|
65
|
+
<div class="tilmeld-recover-action">
|
|
66
|
+
<a
|
|
67
|
+
href="javascript:void(0);"
|
|
68
|
+
on:click={() => (hasSentSecret = 1)}
|
|
69
|
+
>
|
|
70
|
+
Already Got a Code?
|
|
71
|
+
</a>
|
|
72
|
+
</div>
|
|
73
|
+
{/if}
|
|
74
|
+
{:else}
|
|
75
|
+
<div>
|
|
76
|
+
<p>
|
|
77
|
+
A code has been sent to you by email. Enter that code here, and a
|
|
78
|
+
new password for your account.
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
|
|
82
|
+
{#if hasSentSecret === 1}
|
|
83
|
+
<div>
|
|
84
|
+
<Textfield
|
|
85
|
+
bind:this={accountElem}
|
|
86
|
+
bind:value={account}
|
|
87
|
+
label={clientConfig.emailUsernames
|
|
88
|
+
? 'Email Address'
|
|
89
|
+
: 'Username'}
|
|
90
|
+
type={clientConfig.emailUsernames ? 'email' : 'text'}
|
|
91
|
+
input$autocomplete={clientConfig.emailUsernames
|
|
92
|
+
? 'email'
|
|
93
|
+
: 'username'}
|
|
94
|
+
input$autocapitalize="off"
|
|
95
|
+
input$spellcheck="false"
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
{/if}
|
|
99
|
+
|
|
100
|
+
<div>
|
|
101
|
+
<Textfield
|
|
102
|
+
bind:value={secret}
|
|
103
|
+
label="Recovery Code"
|
|
104
|
+
type="text"
|
|
105
|
+
input$autocomplete="one-time-code"
|
|
106
|
+
/>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
<div>
|
|
110
|
+
<Textfield
|
|
111
|
+
bind:value={password}
|
|
112
|
+
label="Password"
|
|
113
|
+
type="password"
|
|
114
|
+
input$autocomplete="new-password"
|
|
115
|
+
/>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<div>
|
|
119
|
+
<Textfield
|
|
120
|
+
bind:value={password2}
|
|
121
|
+
label="Re-enter Password"
|
|
122
|
+
type="password"
|
|
123
|
+
input$autocomplete="new-password"
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div class="tilmeld-recover-action">
|
|
128
|
+
<a
|
|
129
|
+
href="javascript:void(0);"
|
|
130
|
+
on:click={() => (hasSentSecret = false)}
|
|
131
|
+
>
|
|
132
|
+
Need a New Code?
|
|
133
|
+
</a>
|
|
134
|
+
</div>
|
|
135
|
+
{/if}
|
|
136
|
+
|
|
137
|
+
{#if failureMessage}
|
|
138
|
+
<div class="tilmeld-recover-failure">
|
|
139
|
+
{failureMessage}
|
|
140
|
+
</div>
|
|
141
|
+
{/if}
|
|
142
|
+
|
|
143
|
+
{#if recovering}
|
|
144
|
+
<div class="tilmeld-recover-loading">
|
|
145
|
+
<CircularProgress
|
|
146
|
+
style="height: 24px; width: 24px;"
|
|
147
|
+
indeterminate
|
|
148
|
+
/>
|
|
149
|
+
</div>
|
|
150
|
+
{/if}
|
|
151
|
+
{/if}
|
|
152
|
+
</Content>
|
|
153
|
+
<Actions>
|
|
154
|
+
<Button on:click={() => (open = false)} disabled={recovering}>
|
|
155
|
+
<Label>{successRecoveredMessage ? 'Close' : 'Cancel'}</Label>
|
|
156
|
+
</Button>
|
|
157
|
+
{#if !successRecoveredMessage}
|
|
158
|
+
{#if !hasSentSecret}
|
|
159
|
+
<Button
|
|
160
|
+
on:click$preventDefault$stopPropagation={sendRecovery}
|
|
161
|
+
disabled={recovering}
|
|
162
|
+
>
|
|
163
|
+
<Label>Send Recovery</Label>
|
|
164
|
+
</Button>
|
|
165
|
+
{:else}
|
|
166
|
+
<Button
|
|
167
|
+
on:click$preventDefault$stopPropagation={recover}
|
|
168
|
+
disabled={recovering}
|
|
169
|
+
>
|
|
170
|
+
<Label>Reset Password</Label>
|
|
171
|
+
</Button>
|
|
172
|
+
{/if}
|
|
173
|
+
{/if}
|
|
174
|
+
</Actions>
|
|
175
|
+
</Dialog>
|
|
176
|
+
{/if}
|
|
177
|
+
|
|
178
|
+
<script lang="ts">
|
|
179
|
+
import { onMount } from 'svelte';
|
|
180
|
+
import CircularProgress from '@smui/circular-progress';
|
|
181
|
+
import Dialog, { Title, Content, Actions } from '@smui/dialog';
|
|
182
|
+
import Textfield, { TextfieldComponentDev } from '@smui/textfield';
|
|
183
|
+
import Button, { Label } from '@smui/button';
|
|
184
|
+
import FormField from '@smui/form-field';
|
|
185
|
+
import Radio from '@smui/radio';
|
|
186
|
+
import { ClientConfig, User } from '@nymphjs/tilmeld-client';
|
|
187
|
+
|
|
188
|
+
export let open = false;
|
|
189
|
+
// Give focus to the account box when the form is ready.
|
|
190
|
+
export let autofocus = true;
|
|
191
|
+
export let recoveryType: 'username' | 'password' = 'password';
|
|
192
|
+
|
|
193
|
+
/** User provided. You can bind to it if you need to. */
|
|
194
|
+
export let account = '';
|
|
195
|
+
/** User provided. You can bind to it if you need to. */
|
|
196
|
+
export let secret = '';
|
|
197
|
+
/** User provided. You can bind to it if you need to. */
|
|
198
|
+
export let password = '';
|
|
199
|
+
/** User provided. You can bind to it if you need to. */
|
|
200
|
+
export let password2 = '';
|
|
201
|
+
|
|
202
|
+
let clientConfig: ClientConfig | undefined = undefined;
|
|
203
|
+
let recovering = false;
|
|
204
|
+
let hasSentSecret: number | boolean = false;
|
|
205
|
+
let accountElem: TextfieldComponentDev;
|
|
206
|
+
let failureMessage: string | undefined = undefined;
|
|
207
|
+
let successRecoveredMessage: string | undefined = undefined;
|
|
208
|
+
|
|
209
|
+
$: {
|
|
210
|
+
if (open && autofocus && accountElem) {
|
|
211
|
+
accountElem.focus();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
onMount(async () => {
|
|
216
|
+
clientConfig = await User.getClientConfig();
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
async function sendRecovery() {
|
|
220
|
+
if (account === '') {
|
|
221
|
+
failureMessage =
|
|
222
|
+
'You need to enter ' +
|
|
223
|
+
(clientConfig?.emailUsernames || recoveryType === 'username'
|
|
224
|
+
? 'an email address'
|
|
225
|
+
: 'a username') +
|
|
226
|
+
'.';
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
failureMessage = undefined;
|
|
231
|
+
recovering = true;
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
const data = await User.sendRecovery({
|
|
235
|
+
recoveryType,
|
|
236
|
+
account,
|
|
237
|
+
});
|
|
238
|
+
if (!data.result) {
|
|
239
|
+
failureMessage = data.message;
|
|
240
|
+
} else {
|
|
241
|
+
if (recoveryType === 'username') {
|
|
242
|
+
successRecoveredMessage = data.message;
|
|
243
|
+
} else if (recoveryType === 'password') {
|
|
244
|
+
hasSentSecret = true;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
} catch (e: any) {
|
|
248
|
+
failureMessage = e?.message;
|
|
249
|
+
}
|
|
250
|
+
recovering = false;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function recover() {
|
|
254
|
+
if (account === '') {
|
|
255
|
+
failureMessage =
|
|
256
|
+
'You need to enter ' +
|
|
257
|
+
(clientConfig?.emailUsernames || recoveryType === 'username'
|
|
258
|
+
? 'an email address'
|
|
259
|
+
: 'a username') +
|
|
260
|
+
'.';
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (password !== password2) {
|
|
264
|
+
failureMessage = 'Your passwords do not match.';
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (password === '') {
|
|
268
|
+
failureMessage = 'You need to enter a password.';
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
failureMessage = undefined;
|
|
273
|
+
recovering = true;
|
|
274
|
+
try {
|
|
275
|
+
const data = await User.recover({
|
|
276
|
+
username: account,
|
|
277
|
+
secret,
|
|
278
|
+
password,
|
|
279
|
+
});
|
|
280
|
+
if (!data.result) {
|
|
281
|
+
failureMessage = data.message;
|
|
282
|
+
} else {
|
|
283
|
+
successRecoveredMessage = data.message;
|
|
284
|
+
}
|
|
285
|
+
} catch (e: any) {
|
|
286
|
+
failureMessage = e?.message;
|
|
287
|
+
}
|
|
288
|
+
recovering = false;
|
|
289
|
+
}
|
|
290
|
+
</script>
|
|
291
|
+
|
|
292
|
+
<style>
|
|
293
|
+
:global(.mdc-dialog .mdc-dialog__surface.tilmeld-recover-dialog-surface) {
|
|
294
|
+
width: 540px;
|
|
295
|
+
max-width: calc(100vw - 32px);
|
|
296
|
+
}
|
|
297
|
+
.tilmeld-recover-action {
|
|
298
|
+
margin-top: 1em;
|
|
299
|
+
}
|
|
300
|
+
.tilmeld-recover-failure {
|
|
301
|
+
margin-top: 1em;
|
|
302
|
+
color: var(--mdc-theme-error, #f00);
|
|
303
|
+
}
|
|
304
|
+
.tilmeld-recover-loading {
|
|
305
|
+
display: flex;
|
|
306
|
+
justify-content: center;
|
|
307
|
+
align-items: center;
|
|
308
|
+
}
|
|
309
|
+
</style>
|
package/src/global.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
/// <reference types="svelte" />
|
package/src/index.d.ts
ADDED
package/src/index.ts
ADDED
package/tsconfig.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const sveltePreprocess = require('svelte-preprocess');
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
mode: 'production',
|
|
6
|
+
devtool: 'source-map',
|
|
7
|
+
entry: {
|
|
8
|
+
TilmeldComponents: './src/index.ts',
|
|
9
|
+
},
|
|
10
|
+
output: {
|
|
11
|
+
path: path.resolve(__dirname, 'dist'),
|
|
12
|
+
filename: 'index.js',
|
|
13
|
+
},
|
|
14
|
+
resolve: {
|
|
15
|
+
extensions: ['.mjs', '.js', '.ts', '.svelte'],
|
|
16
|
+
mainFields: ['svelte', 'browser', 'module', 'main'],
|
|
17
|
+
},
|
|
18
|
+
module: {
|
|
19
|
+
rules: [
|
|
20
|
+
{
|
|
21
|
+
test: /\.svelte$/,
|
|
22
|
+
use: {
|
|
23
|
+
loader: 'svelte-loader',
|
|
24
|
+
options: {
|
|
25
|
+
preprocess: sveltePreprocess(),
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
test: /\.ts$/,
|
|
31
|
+
loader: 'ts-loader',
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
},
|
|
35
|
+
};
|