@mintplayer/ng-spark 22.30.0 → 22.32.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.
Files changed (84) hide show
  1. package/README.md +2 -2
  2. package/auth/README.md +216 -0
  3. package/fesm2022/mintplayer-ng-spark-auth-account.mjs +2023 -0
  4. package/fesm2022/mintplayer-ng-spark-auth-account.mjs.map +1 -0
  5. package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs +96 -0
  6. package/fesm2022/mintplayer-ng-spark-auth-auth-bar.mjs.map +1 -0
  7. package/fesm2022/mintplayer-ng-spark-auth-confirm-email.mjs +200 -0
  8. package/fesm2022/mintplayer-ng-spark-auth-confirm-email.mjs.map +1 -0
  9. package/fesm2022/mintplayer-ng-spark-auth-core.mjs +452 -0
  10. package/fesm2022/mintplayer-ng-spark-auth-core.mjs.map +1 -0
  11. package/fesm2022/mintplayer-ng-spark-auth-external-login.mjs +201 -0
  12. package/fesm2022/mintplayer-ng-spark-auth-external-login.mjs.map +1 -0
  13. package/fesm2022/mintplayer-ng-spark-auth-forgot-password.mjs +196 -0
  14. package/fesm2022/mintplayer-ng-spark-auth-forgot-password.mjs.map +1 -0
  15. package/fesm2022/mintplayer-ng-spark-auth-guards.mjs +16 -0
  16. package/fesm2022/mintplayer-ng-spark-auth-guards.mjs.map +1 -0
  17. package/fesm2022/mintplayer-ng-spark-auth-interceptors.mjs +18 -0
  18. package/fesm2022/mintplayer-ng-spark-auth-interceptors.mjs.map +1 -0
  19. package/fesm2022/mintplayer-ng-spark-auth-login.mjs +282 -0
  20. package/fesm2022/mintplayer-ng-spark-auth-login.mjs.map +1 -0
  21. package/fesm2022/mintplayer-ng-spark-auth-models.mjs +114 -0
  22. package/fesm2022/mintplayer-ng-spark-auth-models.mjs.map +1 -0
  23. package/fesm2022/mintplayer-ng-spark-auth-pipes.mjs +43 -0
  24. package/fesm2022/mintplayer-ng-spark-auth-pipes.mjs.map +1 -0
  25. package/fesm2022/mintplayer-ng-spark-auth-register.mjs +216 -0
  26. package/fesm2022/mintplayer-ng-spark-auth-register.mjs.map +1 -0
  27. package/fesm2022/mintplayer-ng-spark-auth-reset-password.mjs +223 -0
  28. package/fesm2022/mintplayer-ng-spark-auth-reset-password.mjs.map +1 -0
  29. package/fesm2022/mintplayer-ng-spark-auth-routes.mjs +194 -0
  30. package/fesm2022/mintplayer-ng-spark-auth-routes.mjs.map +1 -0
  31. package/fesm2022/mintplayer-ng-spark-auth-sign-in.mjs +210 -0
  32. package/fesm2022/mintplayer-ng-spark-auth-sign-in.mjs.map +1 -0
  33. package/fesm2022/mintplayer-ng-spark-auth-two-factor.mjs +218 -0
  34. package/fesm2022/mintplayer-ng-spark-auth-two-factor.mjs.map +1 -0
  35. package/fesm2022/mintplayer-ng-spark-auth.mjs +48 -0
  36. package/fesm2022/mintplayer-ng-spark-auth.mjs.map +1 -0
  37. package/fesm2022/mintplayer-ng-spark-client-operations.mjs +294 -2
  38. package/fesm2022/mintplayer-ng-spark-client-operations.mjs.map +1 -1
  39. package/fesm2022/mintplayer-ng-spark-identity-provider-connected-applications.mjs +389 -0
  40. package/fesm2022/mintplayer-ng-spark-identity-provider-connected-applications.mjs.map +1 -0
  41. package/fesm2022/mintplayer-ng-spark-identity-provider-core.mjs +204 -0
  42. package/fesm2022/mintplayer-ng-spark-identity-provider-core.mjs.map +1 -0
  43. package/fesm2022/mintplayer-ng-spark-identity-provider-developers.mjs +486 -0
  44. package/fesm2022/mintplayer-ng-spark-identity-provider-developers.mjs.map +1 -0
  45. package/fesm2022/mintplayer-ng-spark-identity-provider-management.mjs +350 -0
  46. package/fesm2022/mintplayer-ng-spark-identity-provider-management.mjs.map +1 -0
  47. package/fesm2022/mintplayer-ng-spark-identity-provider.mjs +66 -0
  48. package/fesm2022/mintplayer-ng-spark-identity-provider.mjs.map +1 -0
  49. package/fesm2022/mintplayer-ng-spark-po-detail.mjs +9 -4
  50. package/fesm2022/mintplayer-ng-spark-po-detail.mjs.map +1 -1
  51. package/fesm2022/mintplayer-ng-spark-pwa.mjs +162 -0
  52. package/fesm2022/mintplayer-ng-spark-pwa.mjs.map +1 -0
  53. package/fesm2022/mintplayer-ng-spark-services.mjs +11 -1
  54. package/fesm2022/mintplayer-ng-spark-services.mjs.map +1 -1
  55. package/fesm2022/mintplayer-ng-spark-shell.mjs.map +1 -1
  56. package/fesm2022/mintplayer-ng-spark.mjs.map +1 -1
  57. package/package.json +99 -1
  58. package/types/mintplayer-ng-spark-auth-account.d.ts +255 -0
  59. package/types/mintplayer-ng-spark-auth-auth-bar.d.ts +22 -0
  60. package/types/mintplayer-ng-spark-auth-confirm-email.d.ts +21 -0
  61. package/types/mintplayer-ng-spark-auth-core.d.ts +197 -0
  62. package/types/mintplayer-ng-spark-auth-external-login.d.ts +132 -0
  63. package/types/mintplayer-ng-spark-auth-forgot-password.d.ts +18 -0
  64. package/types/mintplayer-ng-spark-auth-guards.d.ts +15 -0
  65. package/types/mintplayer-ng-spark-auth-interceptors.d.ts +2 -0
  66. package/types/mintplayer-ng-spark-auth-login.d.ts +46 -0
  67. package/types/mintplayer-ng-spark-auth-models.d.ts +489 -0
  68. package/types/mintplayer-ng-spark-auth-pipes.d.ts +8 -0
  69. package/types/mintplayer-ng-spark-auth-register.d.ts +21 -0
  70. package/types/mintplayer-ng-spark-auth-reset-password.d.ts +25 -0
  71. package/types/mintplayer-ng-spark-auth-routes.d.ts +154 -0
  72. package/types/mintplayer-ng-spark-auth-sign-in.d.ts +84 -0
  73. package/types/mintplayer-ng-spark-auth-two-factor.d.ts +23 -0
  74. package/types/mintplayer-ng-spark-auth.d.ts +24 -0
  75. package/types/mintplayer-ng-spark-client-operations.d.ts +78 -3
  76. package/types/mintplayer-ng-spark-identity-provider-connected-applications.d.ts +35 -0
  77. package/types/mintplayer-ng-spark-identity-provider-core.d.ts +144 -0
  78. package/types/mintplayer-ng-spark-identity-provider-developers.d.ts +55 -0
  79. package/types/mintplayer-ng-spark-identity-provider-management.d.ts +33 -0
  80. package/types/mintplayer-ng-spark-identity-provider.d.ts +64 -0
  81. package/types/mintplayer-ng-spark-pwa.d.ts +89 -0
  82. package/types/mintplayer-ng-spark-services.d.ts +10 -0
  83. package/types/mintplayer-ng-spark-shell.d.ts +2 -2
  84. package/types/mintplayer-ng-spark.d.ts +4 -4
@@ -0,0 +1,2023 @@
1
+ import * as i0 from "@angular/core";
2
+ import { ChangeDetectionStrategy, Component, computed, inject, signal } from "@angular/core";
3
+ import { Router, RouterLink } from "@angular/router";
4
+ import { BsCardComponent, BsCardHeaderComponent } from "@mintplayer/ng-bootstrap/card";
5
+ import { SparkAuthService, SparkAuthTranslationService } from "@mintplayer/ng-spark/auth/core";
6
+ import { currentLanguage } from "@mintplayer/ng-spark/models";
7
+ import { SPARK_ACCOUNT_OVERVIEW_LINKS, SPARK_ACCOUNT_PROFILE_FIELDS, SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, SPARK_EXTERNAL_PROVIDERS, passkeysSupported } from "@mintplayer/ng-spark/auth/models";
8
+ import { TranslateKeyPipe } from "@mintplayer/ng-spark/auth/pipes";
9
+ import { HttpClient } from "@angular/common/http";
10
+ import * as i1 from "@angular/forms";
11
+ import { FormControl, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms";
12
+ import { firstValueFrom } from "rxjs";
13
+ import { Color } from "@mintplayer/ng-bootstrap";
14
+ import { BsAlertComponent } from "@mintplayer/ng-bootstrap/alert";
15
+ import { BsCheckboxComponent } from "@mintplayer/ng-bootstrap/checkbox";
16
+ import { BsFormComponent, BsFormControlDirective } from "@mintplayer/ng-bootstrap/form";
17
+ import { BsInputGroupComponent } from "@mintplayer/ng-bootstrap/input-group";
18
+ import { BsSelectComponent, BsSelectOption, BsSelectValueAccessor } from "@mintplayer/ng-bootstrap/select";
19
+ import { BsSpinnerComponent } from "@mintplayer/ng-bootstrap/spinner";
20
+ function accountMessages(result) {
21
+ if (result.success) return [];
22
+ if (result.errors) {
23
+ const messages = Object.values(result.errors).flat().filter((m) => !!m);
24
+ if (messages.length) return messages;
25
+ }
26
+ if (result.error) return [`auth.accountError.${result.error}`];
27
+ if (result.status === 404) return ["auth.accountUnavailable"];
28
+ return ["auth.accountFailed"];
29
+ }
30
+ function fieldMessages(result, field) {
31
+ if (!result?.errors) return [];
32
+ const key = Object.keys(result.errors).find((k) => k.toLowerCase() === field.toLowerCase());
33
+ return key ? result.errors[key] : [];
34
+ }
35
+ const passwordSignIn = (c) => c.localCredentials !== "Disabled";
36
+ const PAGES = [
37
+ {
38
+ key: "profile",
39
+ label: "auth.profileTitle"
40
+ },
41
+ {
42
+ key: "changePassword",
43
+ label: "auth.changePasswordTitle",
44
+ server: passwordSignIn
45
+ },
46
+ {
47
+ key: "twoFactorSetup",
48
+ label: "auth.twoFactorSetupTitle",
49
+ server: (c) => passwordSignIn(c) && c.twoFactor === true
50
+ },
51
+ {
52
+ key: "externalLogins",
53
+ label: "auth.externalLoginsTitle",
54
+ server: (c) => c.externalLogins === true
55
+ },
56
+ {
57
+ key: "passkeys",
58
+ label: "auth.passkeysTitle",
59
+ server: (c) => c.passkeys === true && passkeysSupported()
60
+ },
61
+ {
62
+ key: "personalData",
63
+ label: "auth.personalDataTitle"
64
+ }
65
+ ];
66
+ var SparkAccountOverviewComponent = class SparkAccountOverviewComponent {
67
+ auth = inject(SparkAuthService);
68
+ paths = inject(SPARK_AUTH_ROUTE_PATHS, { optional: true }) ?? {};
69
+ capabilities = signal(null, ...ngDevMode ? [{ debugName: "capabilities" }] : /* istanbul ignore next */ []);
70
+ pages = computed(() => {
71
+ const capabilities = this.capabilities();
72
+ return PAGES.filter((page) => !!this.paths[page.key]).filter((page) => !page.server || capabilities !== null && page.server(capabilities)).map((page) => ({
73
+ ...page,
74
+ path: this.paths[page.key]
75
+ }));
76
+ }, ...ngDevMode ? [{ debugName: "pages" }] : /* istanbul ignore next */ []);
77
+ accountLinks = inject(SPARK_ACCOUNT_OVERVIEW_LINKS, { optional: true }) ?? [];
78
+ extraLinks = computed(() => {
79
+ const lang = currentLanguage();
80
+ return this.accountLinks.map((link) => ({
81
+ key: link.key,
82
+ path: link.path,
83
+ label: link.label[lang] ?? link.label["en"] ?? Object.values(link.label)[0] ?? link.key
84
+ }));
85
+ }, ...ngDevMode ? [{ debugName: "extraLinks" }] : /* istanbul ignore next */ []);
86
+ constructor() {
87
+ this.auth.capabilities().then((capabilities) => this.capabilities.set(capabilities)).catch(() => {});
88
+ }
89
+ static ɵfac = i0.ɵɵngDeclareFactory({
90
+ minVersion: "12.0.0",
91
+ version: "22.2.0",
92
+ ngImport: i0,
93
+ type: SparkAccountOverviewComponent,
94
+ deps: [],
95
+ target: i0.ɵɵFactoryTarget.Component
96
+ });
97
+ static ɵcmp = i0.ɵɵngDeclareComponent({
98
+ minVersion: "17.0.0",
99
+ version: "22.2.0",
100
+ type: SparkAccountOverviewComponent,
101
+ isStandalone: true,
102
+ selector: "spark-account-overview",
103
+ ngImport: i0,
104
+ template: `
105
+ <div class="d-flex justify-content-center">
106
+ <bs-card style="width: 100%; max-width: 640px;">
107
+ <bs-card-header>
108
+ <h3 class="mb-0">{{ 'auth.accountTitle' | t }}</h3>
109
+ @if (auth.user(); as user) {
110
+ <small class="spark-account-user text-muted text-break">{{ user.userName }}</small>
111
+ @if (user.email && user.email !== user.userName) {
112
+ <small class="spark-account-email text-muted text-break d-block">{{ user.email }}</small>
113
+ }
114
+ }
115
+ </bs-card-header>
116
+ <div class="p-4">
117
+ <ul class="list-unstyled mb-0">
118
+ @for (page of pages(); track page.key) {
119
+ <li class="py-1"><a class="spark-account-link" [routerLink]="page.path">{{ page.label | t }}</a></li>
120
+ }
121
+ @for (link of extraLinks(); track link.key) {
122
+ <li class="py-1"><a class="spark-account-link" [routerLink]="link.path">{{ link.label }}</a></li>
123
+ }
124
+ </ul>
125
+ </div>
126
+ </bs-card>
127
+ </div>
128
+ `,
129
+ isInline: true,
130
+ dependencies: [
131
+ {
132
+ kind: "directive",
133
+ type: RouterLink,
134
+ selector: "[routerLink]",
135
+ inputs: [
136
+ "target",
137
+ "queryParams",
138
+ "fragment",
139
+ "queryParamsHandling",
140
+ "state",
141
+ "info",
142
+ "relativeTo",
143
+ "preserveFragment",
144
+ "skipLocationChange",
145
+ "replaceUrl",
146
+ "browserUrl",
147
+ "routerLink"
148
+ ]
149
+ },
150
+ {
151
+ kind: "component",
152
+ type: BsCardComponent,
153
+ selector: "bs-card",
154
+ inputs: ["color", "outline"]
155
+ },
156
+ {
157
+ kind: "component",
158
+ type: BsCardHeaderComponent,
159
+ selector: "bs-card-header",
160
+ inputs: ["color", "navStyle"]
161
+ },
162
+ {
163
+ kind: "pipe",
164
+ type: TranslateKeyPipe,
165
+ name: "t"
166
+ }
167
+ ],
168
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
169
+ });
170
+ };
171
+ i0.ɵɵngDeclareClassMetadata({
172
+ minVersion: "12.0.0",
173
+ version: "22.2.0",
174
+ ngImport: i0,
175
+ type: SparkAccountOverviewComponent,
176
+ decorators: [{
177
+ type: Component,
178
+ args: [{
179
+ selector: "spark-account-overview",
180
+ standalone: true,
181
+ changeDetection: ChangeDetectionStrategy.OnPush,
182
+ imports: [
183
+ RouterLink,
184
+ BsCardComponent,
185
+ BsCardHeaderComponent,
186
+ TranslateKeyPipe
187
+ ],
188
+ template: `
189
+ <div class="d-flex justify-content-center">
190
+ <bs-card style="width: 100%; max-width: 640px;">
191
+ <bs-card-header>
192
+ <h3 class="mb-0">{{ 'auth.accountTitle' | t }}</h3>
193
+ @if (auth.user(); as user) {
194
+ <small class="spark-account-user text-muted text-break">{{ user.userName }}</small>
195
+ @if (user.email && user.email !== user.userName) {
196
+ <small class="spark-account-email text-muted text-break d-block">{{ user.email }}</small>
197
+ }
198
+ }
199
+ </bs-card-header>
200
+ <div class="p-4">
201
+ <ul class="list-unstyled mb-0">
202
+ @for (page of pages(); track page.key) {
203
+ <li class="py-1"><a class="spark-account-link" [routerLink]="page.path">{{ page.label | t }}</a></li>
204
+ }
205
+ @for (link of extraLinks(); track link.key) {
206
+ <li class="py-1"><a class="spark-account-link" [routerLink]="link.path">{{ link.label }}</a></li>
207
+ }
208
+ </ul>
209
+ </div>
210
+ </bs-card>
211
+ </div>
212
+ `
213
+ }]
214
+ }],
215
+ ctorParameters: () => []
216
+ });
217
+ var SparkAccountProfileComponent = class SparkAccountProfileComponent {
218
+ auth = inject(SparkAuthService);
219
+ http = inject(HttpClient);
220
+ colors = Color;
221
+ profileFields = [...inject(SPARK_ACCOUNT_PROFILE_FIELDS, { optional: true }) ?? []].map((field, index) => ({
222
+ field,
223
+ index
224
+ })).sort((a, b) => (a.field.order ?? Infinity) - (b.field.order ?? Infinity) || a.index - b.index).map((x) => x.field);
225
+ form = new FormGroup({
226
+ userName: new FormControl("", { nonNullable: true }),
227
+ preferredCulture: new FormControl("", { nonNullable: true }),
228
+ fields: new FormGroup({})
229
+ });
230
+ emailForm = new FormGroup({ newEmail: new FormControl("", { nonNullable: true }) });
231
+ loading = signal(true, ...ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []);
232
+ busy = signal(false, ...ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []);
233
+ saved = signal(false, ...ngDevMode ? [{ debugName: "saved" }] : /* istanbul ignore next */ []);
234
+ messages = signal([], ...ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []);
235
+ lastResult = signal(null, ...ngDevMode ? [{ debugName: "lastResult" }] : /* istanbul ignore next */ []);
236
+ email = signal(null, ...ngDevMode ? [{ debugName: "email" }] : /* istanbul ignore next */ []);
237
+ emailConfirmed = signal(null, ...ngDevMode ? [{ debugName: "emailConfirmed" }] : /* istanbul ignore next */ []);
238
+ emailSent = signal(false, ...ngDevMode ? [{ debugName: "emailSent" }] : /* istanbul ignore next */ []);
239
+ emailMessages = signal([], ...ngDevMode ? [{ debugName: "emailMessages" }] : /* istanbul ignore next */ []);
240
+ emailChangeAllowed = signal(false, ...ngDevMode ? [{ debugName: "emailChangeAllowed" }] : /* istanbul ignore next */ []);
241
+ languages = signal([], ...ngDevMode ? [{ debugName: "languages" }] : /* istanbul ignore next */ []);
242
+ storedCulture = signal(null, ...ngDevMode ? [{ debugName: "storedCulture" }] : /* istanbul ignore next */ []);
243
+ cultureOptions = computed(() => {
244
+ const options = this.languages();
245
+ const stored = this.storedCulture();
246
+ return stored && !options.some((o) => o.value.toLowerCase() === stored.toLowerCase()) ? [...options, {
247
+ value: stored,
248
+ label: stored
249
+ }] : options;
250
+ }, ...ngDevMode ? [{ debugName: "cultureOptions" }] : /* istanbul ignore next */ []);
251
+ userNameErrors = computed(() => {
252
+ const result = this.lastResult();
253
+ if (!result?.errors) return [];
254
+ return Object.entries(result.errors).filter(([key]) => key.toLowerCase().includes("username")).flatMap(([, messages]) => messages);
255
+ }, ...ngDevMode ? [{ debugName: "userNameErrors" }] : /* istanbul ignore next */ []);
256
+ constructor() {
257
+ for (const field of this.profileFields) this.form.controls.fields.addControl(field.name, new FormControl(field.type === "checkbox" ? false : null));
258
+ this.load();
259
+ }
260
+ isInlineError(key) {
261
+ const lowered = key.toLowerCase();
262
+ return lowered === "preferredculture" || lowered.includes("username") || this.profileFields.some((f) => f.name.toLowerCase() === lowered);
263
+ }
264
+ fieldErrors(name) {
265
+ return fieldMessages(this.lastResult(), name);
266
+ }
267
+ async load() {
268
+ this.loading.set(true);
269
+ const [profile, info] = await Promise.all([
270
+ this.auth.profile(),
271
+ this.auth.accountInfo(),
272
+ this.loadLanguages(),
273
+ this.auth.capabilities().then((capabilities) => this.emailChangeAllowed.set(capabilities.emailChange === true)).catch(() => {})
274
+ ]);
275
+ if (profile.success && profile.value) this.apply(profile.value);
276
+ else this.messages.set(accountMessages(profile));
277
+ if (info.success && info.value) {
278
+ this.email.set(info.value.email);
279
+ this.emailConfirmed.set(info.value.isEmailConfirmed);
280
+ } else if (profile.value) this.email.set(profile.value.email);
281
+ this.loading.set(false);
282
+ }
283
+ async loadLanguages() {
284
+ try {
285
+ const culture = await firstValueFrom(this.http.get("/spark/culture"));
286
+ const lang = globalThis.__sparkCurrentLanguage?.() ?? "en";
287
+ this.languages.set(Object.entries(culture.languages ?? {}).map(([value, label]) => ({
288
+ value,
289
+ label: label?.[lang] ?? label?.["en"] ?? Object.values(label ?? {})[0] ?? value
290
+ })));
291
+ } catch {}
292
+ }
293
+ apply(profile) {
294
+ this.storedCulture.set(profile.preferredCulture);
295
+ this.form.controls.userName.setValue(profile.userName ?? "");
296
+ this.form.controls.preferredCulture.setValue(profile.preferredCulture ?? "");
297
+ for (const field of this.profileFields) {
298
+ const key = Object.keys(profile.fields ?? {}).find((k) => k.toLowerCase() === field.name.toLowerCase());
299
+ const value = key ? profile.fields[key] : null;
300
+ this.form.controls.fields.controls[field.name]?.setValue(field.type === "checkbox" ? value === true : value ?? null);
301
+ }
302
+ }
303
+ async save() {
304
+ this.saved.set(false);
305
+ this.busy.set(true);
306
+ this.messages.set([]);
307
+ this.lastResult.set(null);
308
+ try {
309
+ const value = this.form.getRawValue();
310
+ const result = await this.auth.updateProfile({
311
+ userName: value.userName.trim() || null,
312
+ preferredCulture: value.preferredCulture || null,
313
+ ...this.profileFields.length ? { fields: { ...value.fields } } : {}
314
+ });
315
+ this.lastResult.set(result);
316
+ if (result.success && result.value) {
317
+ this.apply(result.value);
318
+ this.saved.set(true);
319
+ } else this.messages.set(result.errors ? Object.entries(result.errors).filter(([key]) => !this.isInlineError(key)).flatMap(([, messages]) => messages) : accountMessages(result));
320
+ } finally {
321
+ this.busy.set(false);
322
+ }
323
+ }
324
+ async changeEmail() {
325
+ const newEmail = this.emailForm.controls.newEmail.value.trim();
326
+ this.emailSent.set(false);
327
+ this.emailMessages.set([]);
328
+ if (!newEmail) return;
329
+ this.busy.set(true);
330
+ try {
331
+ const result = await this.auth.changeEmail(newEmail);
332
+ if (result.success) {
333
+ this.emailSent.set(true);
334
+ this.emailForm.reset();
335
+ } else this.emailMessages.set(accountMessages(result));
336
+ } finally {
337
+ this.busy.set(false);
338
+ }
339
+ }
340
+ static ɵfac = i0.ɵɵngDeclareFactory({
341
+ minVersion: "12.0.0",
342
+ version: "22.2.0",
343
+ ngImport: i0,
344
+ type: SparkAccountProfileComponent,
345
+ deps: [],
346
+ target: i0.ɵɵFactoryTarget.Component
347
+ });
348
+ static ɵcmp = i0.ɵɵngDeclareComponent({
349
+ minVersion: "17.0.0",
350
+ version: "22.2.0",
351
+ type: SparkAccountProfileComponent,
352
+ isStandalone: true,
353
+ selector: "spark-account-profile",
354
+ ngImport: i0,
355
+ template: `
356
+ <div class="d-flex justify-content-center">
357
+ <bs-card style="width: 100%; max-width: 640px;">
358
+ <bs-card-header><h3 class="mb-0">{{ 'auth.profileTitle' | t }}</h3></bs-card-header>
359
+ <div class="p-4">
360
+ @if (loading()) {
361
+ <div class="text-center"><bs-spinner /></div>
362
+ } @else {
363
+ @for (message of messages(); track message) {
364
+ <bs-alert [type]="colors.danger" class="mb-3 d-block spark-account-error">{{ message | t }}</bs-alert>
365
+ }
366
+ @if (saved()) {
367
+ <bs-alert [type]="colors.success" class="mb-3 d-block spark-account-saved">{{ 'auth.profileSaved' | t }}</bs-alert>
368
+ }
369
+
370
+ <bs-form>
371
+ <form [formGroup]="form" (ngSubmit)="save()">
372
+ <div class="mb-3">
373
+ <label for="userName" class="form-label">{{ 'auth.userName' | t }}</label>
374
+ <input type="text" id="userName" formControlName="userName" autocomplete="username" />
375
+ @for (message of userNameErrors(); track message) {
376
+ <div class="text-danger small">{{ message | t }}</div>
377
+ }
378
+ </div>
379
+
380
+ <div class="mb-3">
381
+ <label for="preferredCulture" class="form-label">{{ 'auth.preferredCulture' | t }}</label>
382
+ <bs-select id="preferredCulture" formControlName="preferredCulture" class="w-100">
383
+ <option [ngValue]="''">{{ 'auth.preferredCultureDefault' | t }}</option>
384
+ @for (culture of cultureOptions(); track culture.value) {
385
+ <option [ngValue]="culture.value">{{ culture.label }}</option>
386
+ }
387
+ </bs-select>
388
+ <small class="text-muted">{{ 'auth.preferredCultureHint' | t }}</small>
389
+ @for (message of fieldErrors('PreferredCulture'); track message) {
390
+ <div class="text-danger small">{{ message | t }}</div>
391
+ }
392
+ </div>
393
+
394
+ <div formGroupName="fields">
395
+ @for (field of profileFields; track field.name) {
396
+ <div class="mb-3 spark-profile-field">
397
+ @if (field.type === 'checkbox') {
398
+ <bs-checkbox [type]="'checkbox'" [formControlName]="field.name" [name]="field.name">{{ field.label | t }}</bs-checkbox>
399
+ } @else {
400
+ <label [for]="'field-' + field.name" class="form-label">{{ field.label | t }}</label>
401
+ @switch (field.type) {
402
+ @case ('textarea') {
403
+ <textarea [id]="'field-' + field.name" [formControlName]="field.name" rows="3"
404
+ [attr.maxlength]="field.maxLength ?? null" [required]="!!field.required"></textarea>
405
+ }
406
+ @case ('select') {
407
+ <bs-select [id]="'field-' + field.name" [formControlName]="field.name" class="w-100">
408
+ @if (!field.required) {
409
+ <option [ngValue]="null"></option>
410
+ }
411
+ @for (option of field.options ?? []; track option.value) {
412
+ <option [ngValue]="option.value">{{ option.label | t }}</option>
413
+ }
414
+ </bs-select>
415
+ }
416
+ @default {
417
+ <input [type]="field.type ?? 'text'" [id]="'field-' + field.name" [formControlName]="field.name"
418
+ [attr.maxlength]="field.maxLength ?? null" [required]="!!field.required" />
419
+ }
420
+ }
421
+ }
422
+ @if (field.hint) {
423
+ <small class="text-muted">{{ field.hint | t }}</small>
424
+ }
425
+ @for (message of fieldErrors(field.name); track message) {
426
+ <div class="text-danger small">{{ message | t }}</div>
427
+ }
428
+ </div>
429
+ }
430
+ </div>
431
+
432
+ <button type="submit" class="btn btn-primary" [disabled]="busy()">
433
+ @if (busy()) { <bs-spinner class="me-1" /> }
434
+ {{ 'auth.saveProfile' | t }}
435
+ </button>
436
+ </form>
437
+ </bs-form>
438
+
439
+ <hr class="my-4" />
440
+
441
+ <h5>{{ 'auth.email' | t }}</h5>
442
+ <p class="spark-current-email">
443
+ {{ email() }}
444
+ @if (emailConfirmed() === false) {
445
+ <span class="text-warning ms-2">{{ 'auth.emailNotConfirmed' | t }}</span>
446
+ }
447
+ </p>
448
+ @if (emailMessages().length) {
449
+ @for (message of emailMessages(); track message) {
450
+ <bs-alert [type]="colors.danger" class="mb-3 d-block">{{ message | t }}</bs-alert>
451
+ }
452
+ }
453
+ @if (emailSent()) {
454
+ <bs-alert [type]="colors.info" class="mb-3 d-block spark-email-sent">{{ 'auth.emailChangeSent' | t }}</bs-alert>
455
+ }
456
+ <!-- Opt-in on the server (SparkEmailChange.Enabled); hidden until it says so. -->
457
+ @if (emailChangeAllowed()) {
458
+ <bs-form>
459
+ <form [formGroup]="emailForm" (ngSubmit)="changeEmail()">
460
+ <label for="newEmail" class="form-label">{{ 'auth.newEmail' | t }}</label>
461
+ <bs-input-group>
462
+ <input type="email" id="newEmail" formControlName="newEmail" autocomplete="email" />
463
+ <button type="submit" class="btn btn-outline-primary" [disabled]="busy()">{{ 'auth.changeEmail' | t }}</button>
464
+ </bs-input-group>
465
+ </form>
466
+ </bs-form>
467
+ }
468
+ }
469
+ </div>
470
+ </bs-card>
471
+ </div>
472
+ `,
473
+ isInline: true,
474
+ dependencies: [
475
+ {
476
+ kind: "ngmodule",
477
+ type: ReactiveFormsModule
478
+ },
479
+ {
480
+ kind: "directive",
481
+ type: i1.ɵNgNoValidate,
482
+ selector: "form:not([ngNoForm]):not([ngNativeValidate])"
483
+ },
484
+ {
485
+ kind: "directive",
486
+ type: i1.NgSelectOption,
487
+ selector: "option",
488
+ inputs: ["ngValue", "value"]
489
+ },
490
+ {
491
+ kind: "directive",
492
+ type: i1.ɵNgSelectMultipleOption,
493
+ selector: "option",
494
+ inputs: ["ngValue", "value"]
495
+ },
496
+ {
497
+ kind: "directive",
498
+ type: i1.DefaultValueAccessor,
499
+ selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]"
500
+ },
501
+ {
502
+ kind: "directive",
503
+ type: i1.NgControlStatus,
504
+ selector: "[formControlName],[ngModel],[formControl]"
505
+ },
506
+ {
507
+ kind: "directive",
508
+ type: i1.NgControlStatusGroup,
509
+ selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]"
510
+ },
511
+ {
512
+ kind: "directive",
513
+ type: i1.RequiredValidator,
514
+ selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",
515
+ inputs: ["required"]
516
+ },
517
+ {
518
+ kind: "directive",
519
+ type: i1.FormGroupDirective,
520
+ selector: "[formGroup]",
521
+ inputs: ["formGroup"],
522
+ outputs: ["ngSubmit"],
523
+ exportAs: ["ngForm"]
524
+ },
525
+ {
526
+ kind: "directive",
527
+ type: i1.FormControlName,
528
+ selector: "[formControlName]",
529
+ inputs: [
530
+ "formControlName",
531
+ "disabled",
532
+ "ngModel"
533
+ ],
534
+ outputs: ["ngModelChange"]
535
+ },
536
+ {
537
+ kind: "directive",
538
+ type: i1.FormGroupName,
539
+ selector: "[formGroupName]",
540
+ inputs: ["formGroupName"]
541
+ },
542
+ {
543
+ kind: "component",
544
+ type: BsAlertComponent,
545
+ selector: "bs-alert",
546
+ inputs: [
547
+ "type",
548
+ "announce",
549
+ "isVisible"
550
+ ],
551
+ outputs: ["isVisibleChange", "afterOpenedOrClosed"]
552
+ },
553
+ {
554
+ kind: "component",
555
+ type: BsCardComponent,
556
+ selector: "bs-card",
557
+ inputs: ["color", "outline"]
558
+ },
559
+ {
560
+ kind: "component",
561
+ type: BsCardHeaderComponent,
562
+ selector: "bs-card-header",
563
+ inputs: ["color", "navStyle"]
564
+ },
565
+ {
566
+ kind: "component",
567
+ type: BsCheckboxComponent,
568
+ selector: "bs-checkbox",
569
+ inputs: [
570
+ "type",
571
+ "isToggled",
572
+ "indeterminate",
573
+ "name",
574
+ "value",
575
+ "group"
576
+ ],
577
+ outputs: ["isToggledChange", "indeterminateChange"]
578
+ },
579
+ {
580
+ kind: "component",
581
+ type: BsFormComponent,
582
+ selector: "bs-form",
583
+ inputs: ["action", "method"],
584
+ outputs: ["submitted"]
585
+ },
586
+ {
587
+ kind: "directive",
588
+ type: BsFormControlDirective,
589
+ selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)"
590
+ },
591
+ {
592
+ kind: "component",
593
+ type: BsInputGroupComponent,
594
+ selector: "bs-input-group",
595
+ inputs: ["size"]
596
+ },
597
+ {
598
+ kind: "component",
599
+ type: BsSelectComponent,
600
+ selector: "bs-select",
601
+ inputs: [
602
+ "identifier",
603
+ "size",
604
+ "multiple",
605
+ "numberVisible",
606
+ "disabled"
607
+ ]
608
+ },
609
+ {
610
+ kind: "directive",
611
+ type: BsSelectValueAccessor,
612
+ selector: "bs-select",
613
+ inputs: ["compareWith"]
614
+ },
615
+ {
616
+ kind: "directive",
617
+ type: BsSelectOption,
618
+ selector: "option",
619
+ inputs: ["ngValue", "value"]
620
+ },
621
+ {
622
+ kind: "component",
623
+ type: BsSpinnerComponent,
624
+ selector: "bs-spinner",
625
+ inputs: ["type", "color"]
626
+ },
627
+ {
628
+ kind: "pipe",
629
+ type: TranslateKeyPipe,
630
+ name: "t"
631
+ }
632
+ ],
633
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
634
+ });
635
+ };
636
+ i0.ɵɵngDeclareClassMetadata({
637
+ minVersion: "12.0.0",
638
+ version: "22.2.0",
639
+ ngImport: i0,
640
+ type: SparkAccountProfileComponent,
641
+ decorators: [{
642
+ type: Component,
643
+ args: [{
644
+ selector: "spark-account-profile",
645
+ standalone: true,
646
+ changeDetection: ChangeDetectionStrategy.OnPush,
647
+ imports: [
648
+ ReactiveFormsModule,
649
+ BsAlertComponent,
650
+ BsCardComponent,
651
+ BsCardHeaderComponent,
652
+ BsCheckboxComponent,
653
+ BsFormComponent,
654
+ BsFormControlDirective,
655
+ BsInputGroupComponent,
656
+ BsSelectComponent,
657
+ BsSelectValueAccessor,
658
+ BsSelectOption,
659
+ BsSpinnerComponent,
660
+ TranslateKeyPipe
661
+ ],
662
+ template: `
663
+ <div class="d-flex justify-content-center">
664
+ <bs-card style="width: 100%; max-width: 640px;">
665
+ <bs-card-header><h3 class="mb-0">{{ 'auth.profileTitle' | t }}</h3></bs-card-header>
666
+ <div class="p-4">
667
+ @if (loading()) {
668
+ <div class="text-center"><bs-spinner /></div>
669
+ } @else {
670
+ @for (message of messages(); track message) {
671
+ <bs-alert [type]="colors.danger" class="mb-3 d-block spark-account-error">{{ message | t }}</bs-alert>
672
+ }
673
+ @if (saved()) {
674
+ <bs-alert [type]="colors.success" class="mb-3 d-block spark-account-saved">{{ 'auth.profileSaved' | t }}</bs-alert>
675
+ }
676
+
677
+ <bs-form>
678
+ <form [formGroup]="form" (ngSubmit)="save()">
679
+ <div class="mb-3">
680
+ <label for="userName" class="form-label">{{ 'auth.userName' | t }}</label>
681
+ <input type="text" id="userName" formControlName="userName" autocomplete="username" />
682
+ @for (message of userNameErrors(); track message) {
683
+ <div class="text-danger small">{{ message | t }}</div>
684
+ }
685
+ </div>
686
+
687
+ <div class="mb-3">
688
+ <label for="preferredCulture" class="form-label">{{ 'auth.preferredCulture' | t }}</label>
689
+ <bs-select id="preferredCulture" formControlName="preferredCulture" class="w-100">
690
+ <option [ngValue]="''">{{ 'auth.preferredCultureDefault' | t }}</option>
691
+ @for (culture of cultureOptions(); track culture.value) {
692
+ <option [ngValue]="culture.value">{{ culture.label }}</option>
693
+ }
694
+ </bs-select>
695
+ <small class="text-muted">{{ 'auth.preferredCultureHint' | t }}</small>
696
+ @for (message of fieldErrors('PreferredCulture'); track message) {
697
+ <div class="text-danger small">{{ message | t }}</div>
698
+ }
699
+ </div>
700
+
701
+ <div formGroupName="fields">
702
+ @for (field of profileFields; track field.name) {
703
+ <div class="mb-3 spark-profile-field">
704
+ @if (field.type === 'checkbox') {
705
+ <bs-checkbox [type]="'checkbox'" [formControlName]="field.name" [name]="field.name">{{ field.label | t }}</bs-checkbox>
706
+ } @else {
707
+ <label [for]="'field-' + field.name" class="form-label">{{ field.label | t }}</label>
708
+ @switch (field.type) {
709
+ @case ('textarea') {
710
+ <textarea [id]="'field-' + field.name" [formControlName]="field.name" rows="3"
711
+ [attr.maxlength]="field.maxLength ?? null" [required]="!!field.required"></textarea>
712
+ }
713
+ @case ('select') {
714
+ <bs-select [id]="'field-' + field.name" [formControlName]="field.name" class="w-100">
715
+ @if (!field.required) {
716
+ <option [ngValue]="null"></option>
717
+ }
718
+ @for (option of field.options ?? []; track option.value) {
719
+ <option [ngValue]="option.value">{{ option.label | t }}</option>
720
+ }
721
+ </bs-select>
722
+ }
723
+ @default {
724
+ <input [type]="field.type ?? 'text'" [id]="'field-' + field.name" [formControlName]="field.name"
725
+ [attr.maxlength]="field.maxLength ?? null" [required]="!!field.required" />
726
+ }
727
+ }
728
+ }
729
+ @if (field.hint) {
730
+ <small class="text-muted">{{ field.hint | t }}</small>
731
+ }
732
+ @for (message of fieldErrors(field.name); track message) {
733
+ <div class="text-danger small">{{ message | t }}</div>
734
+ }
735
+ </div>
736
+ }
737
+ </div>
738
+
739
+ <button type="submit" class="btn btn-primary" [disabled]="busy()">
740
+ @if (busy()) { <bs-spinner class="me-1" /> }
741
+ {{ 'auth.saveProfile' | t }}
742
+ </button>
743
+ </form>
744
+ </bs-form>
745
+
746
+ <hr class="my-4" />
747
+
748
+ <h5>{{ 'auth.email' | t }}</h5>
749
+ <p class="spark-current-email">
750
+ {{ email() }}
751
+ @if (emailConfirmed() === false) {
752
+ <span class="text-warning ms-2">{{ 'auth.emailNotConfirmed' | t }}</span>
753
+ }
754
+ </p>
755
+ @if (emailMessages().length) {
756
+ @for (message of emailMessages(); track message) {
757
+ <bs-alert [type]="colors.danger" class="mb-3 d-block">{{ message | t }}</bs-alert>
758
+ }
759
+ }
760
+ @if (emailSent()) {
761
+ <bs-alert [type]="colors.info" class="mb-3 d-block spark-email-sent">{{ 'auth.emailChangeSent' | t }}</bs-alert>
762
+ }
763
+ <!-- Opt-in on the server (SparkEmailChange.Enabled); hidden until it says so. -->
764
+ @if (emailChangeAllowed()) {
765
+ <bs-form>
766
+ <form [formGroup]="emailForm" (ngSubmit)="changeEmail()">
767
+ <label for="newEmail" class="form-label">{{ 'auth.newEmail' | t }}</label>
768
+ <bs-input-group>
769
+ <input type="email" id="newEmail" formControlName="newEmail" autocomplete="email" />
770
+ <button type="submit" class="btn btn-outline-primary" [disabled]="busy()">{{ 'auth.changeEmail' | t }}</button>
771
+ </bs-input-group>
772
+ </form>
773
+ </bs-form>
774
+ }
775
+ }
776
+ </div>
777
+ </bs-card>
778
+ </div>
779
+ `
780
+ }]
781
+ }],
782
+ ctorParameters: () => []
783
+ });
784
+ var SparkChangePasswordComponent = class SparkChangePasswordComponent {
785
+ auth = inject(SparkAuthService);
786
+ colors = Color;
787
+ form = new FormGroup({
788
+ currentPassword: new FormControl("", { nonNullable: true }),
789
+ newPassword: new FormControl("", {
790
+ nonNullable: true,
791
+ validators: [Validators.required]
792
+ }),
793
+ confirmPassword: new FormControl("", {
794
+ nonNullable: true,
795
+ validators: [Validators.required]
796
+ })
797
+ });
798
+ busy = signal(false, ...ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []);
799
+ saved = signal(false, ...ngDevMode ? [{ debugName: "saved" }] : /* istanbul ignore next */ []);
800
+ messages = signal([], ...ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []);
801
+ async submit() {
802
+ this.saved.set(false);
803
+ const { currentPassword, newPassword, confirmPassword } = this.form.getRawValue();
804
+ if (!newPassword) {
805
+ this.messages.set(["auth.newPasswordRequired"]);
806
+ return;
807
+ }
808
+ if (newPassword !== confirmPassword) {
809
+ this.messages.set(["auth.passwordMismatch"]);
810
+ return;
811
+ }
812
+ this.busy.set(true);
813
+ this.messages.set([]);
814
+ try {
815
+ const result = await this.auth.setPassword(newPassword, currentPassword || null);
816
+ if (result.success) {
817
+ this.saved.set(true);
818
+ this.form.reset();
819
+ } else this.messages.set(accountMessages(result));
820
+ } finally {
821
+ this.busy.set(false);
822
+ }
823
+ }
824
+ static ɵfac = i0.ɵɵngDeclareFactory({
825
+ minVersion: "12.0.0",
826
+ version: "22.2.0",
827
+ ngImport: i0,
828
+ type: SparkChangePasswordComponent,
829
+ deps: [],
830
+ target: i0.ɵɵFactoryTarget.Component
831
+ });
832
+ static ɵcmp = i0.ɵɵngDeclareComponent({
833
+ minVersion: "17.0.0",
834
+ version: "22.2.0",
835
+ type: SparkChangePasswordComponent,
836
+ isStandalone: true,
837
+ selector: "spark-change-password",
838
+ ngImport: i0,
839
+ template: `
840
+ <div class="d-flex justify-content-center">
841
+ <bs-card style="width: 100%; max-width: 640px;">
842
+ <bs-card-header><h3 class="mb-0">{{ 'auth.changePasswordTitle' | t }}</h3></bs-card-header>
843
+ <div class="p-4">
844
+ @for (message of messages(); track message) {
845
+ <bs-alert [type]="colors.danger" class="mb-3 d-block spark-account-error">{{ message | t }}</bs-alert>
846
+ }
847
+ @if (saved()) {
848
+ <bs-alert [type]="colors.success" class="mb-3 d-block spark-account-saved">{{ 'auth.passwordChanged' | t }}</bs-alert>
849
+ }
850
+ <bs-form>
851
+ <form [formGroup]="form" (ngSubmit)="submit()">
852
+ <div class="mb-3">
853
+ <label for="currentPassword" class="form-label">{{ 'auth.currentPassword' | t }}</label>
854
+ <input type="password" id="currentPassword" formControlName="currentPassword" autocomplete="current-password" />
855
+ <small class="text-muted">{{ 'auth.currentPasswordHint' | t }}</small>
856
+ </div>
857
+ <div class="mb-3">
858
+ <label for="newPassword" class="form-label">{{ 'auth.newPassword' | t }}</label>
859
+ <input type="password" id="newPassword" formControlName="newPassword" autocomplete="new-password" />
860
+ </div>
861
+ <div class="mb-3">
862
+ <label for="confirmPassword" class="form-label">{{ 'auth.confirmPassword' | t }}</label>
863
+ <input type="password" id="confirmPassword" formControlName="confirmPassword" autocomplete="new-password" />
864
+ </div>
865
+ <button type="submit" class="btn btn-primary" [disabled]="busy()">
866
+ @if (busy()) { <bs-spinner class="me-1" /> }
867
+ {{ 'auth.savePassword' | t }}
868
+ </button>
869
+ </form>
870
+ </bs-form>
871
+ </div>
872
+ </bs-card>
873
+ </div>
874
+ `,
875
+ isInline: true,
876
+ dependencies: [
877
+ {
878
+ kind: "ngmodule",
879
+ type: ReactiveFormsModule
880
+ },
881
+ {
882
+ kind: "directive",
883
+ type: i1.ɵNgNoValidate,
884
+ selector: "form:not([ngNoForm]):not([ngNativeValidate])"
885
+ },
886
+ {
887
+ kind: "directive",
888
+ type: i1.DefaultValueAccessor,
889
+ selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]"
890
+ },
891
+ {
892
+ kind: "directive",
893
+ type: i1.NgControlStatus,
894
+ selector: "[formControlName],[ngModel],[formControl]"
895
+ },
896
+ {
897
+ kind: "directive",
898
+ type: i1.NgControlStatusGroup,
899
+ selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]"
900
+ },
901
+ {
902
+ kind: "directive",
903
+ type: i1.FormGroupDirective,
904
+ selector: "[formGroup]",
905
+ inputs: ["formGroup"],
906
+ outputs: ["ngSubmit"],
907
+ exportAs: ["ngForm"]
908
+ },
909
+ {
910
+ kind: "directive",
911
+ type: i1.FormControlName,
912
+ selector: "[formControlName]",
913
+ inputs: [
914
+ "formControlName",
915
+ "disabled",
916
+ "ngModel"
917
+ ],
918
+ outputs: ["ngModelChange"]
919
+ },
920
+ {
921
+ kind: "component",
922
+ type: BsAlertComponent,
923
+ selector: "bs-alert",
924
+ inputs: [
925
+ "type",
926
+ "announce",
927
+ "isVisible"
928
+ ],
929
+ outputs: ["isVisibleChange", "afterOpenedOrClosed"]
930
+ },
931
+ {
932
+ kind: "component",
933
+ type: BsCardComponent,
934
+ selector: "bs-card",
935
+ inputs: ["color", "outline"]
936
+ },
937
+ {
938
+ kind: "component",
939
+ type: BsCardHeaderComponent,
940
+ selector: "bs-card-header",
941
+ inputs: ["color", "navStyle"]
942
+ },
943
+ {
944
+ kind: "component",
945
+ type: BsFormComponent,
946
+ selector: "bs-form",
947
+ inputs: ["action", "method"],
948
+ outputs: ["submitted"]
949
+ },
950
+ {
951
+ kind: "directive",
952
+ type: BsFormControlDirective,
953
+ selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)"
954
+ },
955
+ {
956
+ kind: "component",
957
+ type: BsSpinnerComponent,
958
+ selector: "bs-spinner",
959
+ inputs: ["type", "color"]
960
+ },
961
+ {
962
+ kind: "pipe",
963
+ type: TranslateKeyPipe,
964
+ name: "t"
965
+ }
966
+ ],
967
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
968
+ });
969
+ };
970
+ i0.ɵɵngDeclareClassMetadata({
971
+ minVersion: "12.0.0",
972
+ version: "22.2.0",
973
+ ngImport: i0,
974
+ type: SparkChangePasswordComponent,
975
+ decorators: [{
976
+ type: Component,
977
+ args: [{
978
+ selector: "spark-change-password",
979
+ standalone: true,
980
+ changeDetection: ChangeDetectionStrategy.OnPush,
981
+ imports: [
982
+ ReactiveFormsModule,
983
+ BsAlertComponent,
984
+ BsCardComponent,
985
+ BsCardHeaderComponent,
986
+ BsFormComponent,
987
+ BsFormControlDirective,
988
+ BsSpinnerComponent,
989
+ TranslateKeyPipe
990
+ ],
991
+ template: `
992
+ <div class="d-flex justify-content-center">
993
+ <bs-card style="width: 100%; max-width: 640px;">
994
+ <bs-card-header><h3 class="mb-0">{{ 'auth.changePasswordTitle' | t }}</h3></bs-card-header>
995
+ <div class="p-4">
996
+ @for (message of messages(); track message) {
997
+ <bs-alert [type]="colors.danger" class="mb-3 d-block spark-account-error">{{ message | t }}</bs-alert>
998
+ }
999
+ @if (saved()) {
1000
+ <bs-alert [type]="colors.success" class="mb-3 d-block spark-account-saved">{{ 'auth.passwordChanged' | t }}</bs-alert>
1001
+ }
1002
+ <bs-form>
1003
+ <form [formGroup]="form" (ngSubmit)="submit()">
1004
+ <div class="mb-3">
1005
+ <label for="currentPassword" class="form-label">{{ 'auth.currentPassword' | t }}</label>
1006
+ <input type="password" id="currentPassword" formControlName="currentPassword" autocomplete="current-password" />
1007
+ <small class="text-muted">{{ 'auth.currentPasswordHint' | t }}</small>
1008
+ </div>
1009
+ <div class="mb-3">
1010
+ <label for="newPassword" class="form-label">{{ 'auth.newPassword' | t }}</label>
1011
+ <input type="password" id="newPassword" formControlName="newPassword" autocomplete="new-password" />
1012
+ </div>
1013
+ <div class="mb-3">
1014
+ <label for="confirmPassword" class="form-label">{{ 'auth.confirmPassword' | t }}</label>
1015
+ <input type="password" id="confirmPassword" formControlName="confirmPassword" autocomplete="new-password" />
1016
+ </div>
1017
+ <button type="submit" class="btn btn-primary" [disabled]="busy()">
1018
+ @if (busy()) { <bs-spinner class="me-1" /> }
1019
+ {{ 'auth.savePassword' | t }}
1020
+ </button>
1021
+ </form>
1022
+ </bs-form>
1023
+ </div>
1024
+ </bs-card>
1025
+ </div>
1026
+ `
1027
+ }]
1028
+ }]
1029
+ });
1030
+ var SparkTwoFactorSetupComponent = class SparkTwoFactorSetupComponent {
1031
+ auth = inject(SparkAuthService);
1032
+ translation = inject(SparkAuthTranslationService);
1033
+ colors = Color;
1034
+ form = new FormGroup({ code: new FormControl("", { nonNullable: true }) });
1035
+ loading = signal(true, ...ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []);
1036
+ busy = signal(false, ...ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []);
1037
+ messages = signal([], ...ngDevMode ? [{ debugName: "messages" }] : /* istanbul ignore next */ []);
1038
+ state = signal(null, ...ngDevMode ? [{ debugName: "state" }] : /* istanbul ignore next */ []);
1039
+ authenticator = signal(null, ...ngDevMode ? [{ debugName: "authenticator" }] : /* istanbul ignore next */ []);
1040
+ recoveryCodes = signal([], ...ngDevMode ? [{ debugName: "recoveryCodes" }] : /* istanbul ignore next */ []);
1041
+ qrDataUrl = computed(() => {
1042
+ const svg = this.authenticator()?.qrCodeSvg;
1043
+ return svg ? "data:image/svg+xml;charset=utf-8," + encodeURIComponent(svg) : null;
1044
+ }, ...ngDevMode ? [{ debugName: "qrDataUrl" }] : /* istanbul ignore next */ []);
1045
+ bypassForm = new FormGroup({ code: new FormControl("", { nonNullable: true }) });
1046
+ bypassCapability = signal(false, ...ngDevMode ? [{ debugName: "bypassCapability" }] : /* istanbul ignore next */ []);
1047
+ bypass = signal(null, ...ngDevMode ? [{ debugName: "bypass" }] : /* istanbul ignore next */ []);
1048
+ bypassPending = signal(false, ...ngDevMode ? [{ debugName: "bypassPending" }] : /* istanbul ignore next */ []);
1049
+ bypassOffered = computed(() => this.bypassCapability() && this.bypass() !== null && this.state()?.isTwoFactorEnabled === true, ...ngDevMode ? [{ debugName: "bypassOffered" }] : /* istanbul ignore next */ []);
1050
+ bypassChecked = computed(() => this.bypass() === true || this.bypassPending(), ...ngDevMode ? [{ debugName: "bypassChecked" }] : /* istanbul ignore next */ []);
1051
+ constructor() {
1052
+ this.refresh();
1053
+ Promise.resolve().then(() => this.auth.capabilities()).then((capabilities) => {
1054
+ this.bypassCapability.set(capabilities?.externalLoginTwoFactorBypass === true);
1055
+ if (this.bypassCapability()) return this.loadBypass();
1056
+ }).catch(() => {});
1057
+ }
1058
+ async loadBypass() {
1059
+ const result = await this.auth.externalLoginTwoFactor();
1060
+ this.bypass.set(result.success && result.value ? result.value.bypass : null);
1061
+ }
1062
+ async toggleBypass(on) {
1063
+ if (on) {
1064
+ if (this.bypass() !== true) this.bypassPending.set(true);
1065
+ return;
1066
+ }
1067
+ if (this.bypassPending()) {
1068
+ this.bypassPending.set(false);
1069
+ this.bypassForm.reset();
1070
+ return;
1071
+ }
1072
+ if (this.bypass() !== true) return;
1073
+ await this.saveBypass(false);
1074
+ }
1075
+ async confirmBypass() {
1076
+ const code = this.bypassForm.controls.code.value.replace(/\s|-/g, "");
1077
+ if (!code) {
1078
+ this.messages.set(["auth.enterCodeError"]);
1079
+ return;
1080
+ }
1081
+ await this.saveBypass(true, code);
1082
+ }
1083
+ async saveBypass(bypass, code) {
1084
+ this.busy.set(true);
1085
+ this.messages.set([]);
1086
+ try {
1087
+ const result = await this.auth.setExternalLoginTwoFactor(bypass, code);
1088
+ if (result.success && result.value) {
1089
+ this.bypass.set(result.value.bypass);
1090
+ this.bypassPending.set(false);
1091
+ this.bypassForm.reset();
1092
+ } else this.messages.set(result.status === 400 && !result.errors ? ["auth.invalidCode"] : accountMessages(result));
1093
+ } finally {
1094
+ this.busy.set(false);
1095
+ }
1096
+ }
1097
+ formatKey(key) {
1098
+ return key.replace(/(.{4})/g, "$1 ").trim();
1099
+ }
1100
+ async refresh() {
1101
+ this.loading.set(true);
1102
+ const state = await this.auth.twoFactor();
1103
+ await this.applyState(state);
1104
+ this.loading.set(false);
1105
+ }
1106
+ async enable() {
1107
+ const code = this.form.controls.code.value.replace(/\s|-/g, "");
1108
+ if (!code) {
1109
+ this.messages.set(["auth.enterCodeError"]);
1110
+ return;
1111
+ }
1112
+ await this.run({
1113
+ enable: true,
1114
+ twoFactorCode: code
1115
+ });
1116
+ if (this.state()?.isTwoFactorEnabled) this.form.reset();
1117
+ }
1118
+ async disable() {
1119
+ if (!confirm(this.translation.t("auth.confirmDisableTwoFactor"))) return;
1120
+ await this.run({ enable: false });
1121
+ }
1122
+ async regenerateRecoveryCodes() {
1123
+ await this.run({ resetRecoveryCodes: true });
1124
+ }
1125
+ async forgetMachine() {
1126
+ await this.run({ forgetMachine: true });
1127
+ const state = this.state();
1128
+ if (state?.isMachineRemembered && this.messages().length === 0) this.state.set({
1129
+ ...state,
1130
+ isMachineRemembered: false
1131
+ });
1132
+ }
1133
+ async resetKey() {
1134
+ if (!confirm(this.translation.t("auth.confirmResetAuthenticator"))) return;
1135
+ await this.run({ resetSharedKey: true });
1136
+ }
1137
+ async run(request) {
1138
+ this.busy.set(true);
1139
+ this.messages.set([]);
1140
+ try {
1141
+ await this.applyState(await this.auth.twoFactor(request));
1142
+ } finally {
1143
+ this.busy.set(false);
1144
+ }
1145
+ }
1146
+ async applyState(result) {
1147
+ if (!result.success || !result.value) {
1148
+ this.messages.set(result.status === 400 && !result.errors ? ["auth.invalidCode"] : accountMessages(result));
1149
+ return;
1150
+ }
1151
+ const wasEnabled = this.state()?.isTwoFactorEnabled;
1152
+ this.state.set(result.value);
1153
+ this.recoveryCodes.set(result.value.recoveryCodes ?? []);
1154
+ if (this.bypassCapability() && wasEnabled !== void 0 && wasEnabled !== result.value.isTwoFactorEnabled) {
1155
+ this.bypassPending.set(false);
1156
+ this.loadBypass();
1157
+ }
1158
+ if (result.value.isTwoFactorEnabled) {
1159
+ this.authenticator.set(null);
1160
+ return;
1161
+ }
1162
+ const uri = await this.auth.authenticatorUri();
1163
+ this.authenticator.set(uri.success && uri.value ? uri.value : null);
1164
+ if (!uri.success) this.messages.set(accountMessages(uri));
1165
+ }
1166
+ static ɵfac = i0.ɵɵngDeclareFactory({
1167
+ minVersion: "12.0.0",
1168
+ version: "22.2.0",
1169
+ ngImport: i0,
1170
+ type: SparkTwoFactorSetupComponent,
1171
+ deps: [],
1172
+ target: i0.ɵɵFactoryTarget.Component
1173
+ });
1174
+ static ɵcmp = i0.ɵɵngDeclareComponent({
1175
+ minVersion: "17.0.0",
1176
+ version: "22.2.0",
1177
+ type: SparkTwoFactorSetupComponent,
1178
+ isStandalone: true,
1179
+ selector: "spark-two-factor-setup",
1180
+ ngImport: i0,
1181
+ template: `
1182
+ <div class="d-flex justify-content-center">
1183
+ <bs-card style="width: 100%; max-width: 640px;">
1184
+ <bs-card-header><h3 class="mb-0">{{ 'auth.twoFactorSetupTitle' | t }}</h3></bs-card-header>
1185
+ <div class="p-4">
1186
+ @for (message of messages(); track message) {
1187
+ <bs-alert [type]="colors.danger" class="mb-3 d-block spark-account-error">{{ message | t }}</bs-alert>
1188
+ }
1189
+ @if (loading()) {
1190
+ <div class="text-center"><bs-spinner /></div>
1191
+ } @else if (state(); as s) {
1192
+ @if (recoveryCodes().length) {
1193
+ <bs-alert [type]="colors.warning" class="mb-3 d-block spark-recovery-codes">
1194
+ <p class="mb-2">{{ 'auth.recoveryCodesShownOnce' | t }}</p>
1195
+ <div class="d-flex flex-wrap gap-3 font-monospace">
1196
+ @for (code of recoveryCodes(); track code) { <span>{{ code }}</span> }
1197
+ </div>
1198
+ </bs-alert>
1199
+ }
1200
+
1201
+ @if (s.isTwoFactorEnabled) {
1202
+ <p class="spark-2fa-enabled">{{ 'auth.twoFactorEnabled' | t }}</p>
1203
+ <p class="text-muted">{{ 'auth.recoveryCodesLeft' | t }}: {{ s.recoveryCodesLeft }}</p>
1204
+ <div class="d-flex flex-wrap gap-2">
1205
+ <button type="button" class="btn btn-outline-primary spark-2fa-new-codes" [disabled]="busy()" (click)="regenerateRecoveryCodes()">
1206
+ {{ 'auth.newRecoveryCodes' | t }}
1207
+ </button>
1208
+ @if (s.isMachineRemembered) {
1209
+ <button type="button" class="btn btn-outline-secondary" [disabled]="busy()" (click)="forgetMachine()">
1210
+ {{ 'auth.forgetBrowser' | t }}
1211
+ </button>
1212
+ }
1213
+ <button type="button" class="btn btn-outline-warning" [disabled]="busy()" (click)="resetKey()">
1214
+ {{ 'auth.resetAuthenticator' | t }}
1215
+ </button>
1216
+ <button type="button" class="btn btn-outline-danger spark-2fa-disable" [disabled]="busy()" (click)="disable()">
1217
+ {{ 'auth.disableTwoFactor' | t }}
1218
+ </button>
1219
+ </div>
1220
+
1221
+ @if (bypassOffered()) {
1222
+ <hr class="my-4" />
1223
+ <div class="spark-2fa-external-bypass">
1224
+ <bs-form>
1225
+ <bs-checkbox [type]="'checkbox'" [name]="'externalTwoFactorBypass'" [isToggled]="bypassChecked()"
1226
+ (isToggledChange)="toggleBypass($event === true)">
1227
+ {{ 'auth.externalTwoFactorBypassLabel' | t }}
1228
+ </bs-checkbox>
1229
+ </bs-form>
1230
+ <small class="text-muted d-block">{{ 'auth.externalTwoFactorBypassHint' | t }}</small>
1231
+ @if (bypassPending()) {
1232
+ <bs-form>
1233
+ <form [formGroup]="bypassForm" (ngSubmit)="confirmBypass()" class="d-flex gap-2 align-items-end mt-2">
1234
+ <div class="flex-grow-1">
1235
+ <label for="bypassCode" class="form-label">{{ 'auth.code' | t }}</label>
1236
+ <input type="text" id="bypassCode" formControlName="code" inputmode="numeric" autocomplete="one-time-code" maxlength="8" />
1237
+ </div>
1238
+ <button type="submit" class="btn btn-primary spark-2fa-bypass-confirm" [disabled]="busy()">{{ 'auth.verify' | t }}</button>
1239
+ <button type="button" class="btn btn-outline-secondary spark-2fa-bypass-cancel" [disabled]="busy()" (click)="toggleBypass(false)">{{ 'common.cancel' | t }}</button>
1240
+ </form>
1241
+ </bs-form>
1242
+ }
1243
+ </div>
1244
+ }
1245
+ } @else {
1246
+ <p>{{ 'auth.twoFactorSetupIntro' | t }}</p>
1247
+ @if (qrDataUrl(); as qr) {
1248
+ <div class="text-center my-3">
1249
+ <img class="spark-2fa-qr" [src]="qr" width="200" height="200" [attr.alt]="'auth.qrCodeAlt' | t" />
1250
+ </div>
1251
+ }
1252
+ @if (authenticator(); as a) {
1253
+ <p class="text-muted small mb-1">{{ 'auth.manualKey' | t }}</p>
1254
+ <p class="font-monospace spark-2fa-key">{{ formatKey(a.sharedKey) }}</p>
1255
+ }
1256
+ <bs-form>
1257
+ <form [formGroup]="form" (ngSubmit)="enable()" class="d-flex gap-2 align-items-end">
1258
+ <div class="flex-grow-1">
1259
+ <label for="twoFactorCode" class="form-label">{{ 'auth.code' | t }}</label>
1260
+ <input type="text" id="twoFactorCode" formControlName="code" inputmode="numeric" autocomplete="one-time-code" maxlength="8" />
1261
+ </div>
1262
+ <button type="submit" class="btn btn-primary spark-2fa-enable" [disabled]="busy()">{{ 'auth.enableTwoFactor' | t }}</button>
1263
+ </form>
1264
+ </bs-form>
1265
+ }
1266
+ }
1267
+ </div>
1268
+ </bs-card>
1269
+ </div>
1270
+ `,
1271
+ isInline: true,
1272
+ dependencies: [
1273
+ {
1274
+ kind: "ngmodule",
1275
+ type: ReactiveFormsModule
1276
+ },
1277
+ {
1278
+ kind: "directive",
1279
+ type: i1.ɵNgNoValidate,
1280
+ selector: "form:not([ngNoForm]):not([ngNativeValidate])"
1281
+ },
1282
+ {
1283
+ kind: "directive",
1284
+ type: i1.DefaultValueAccessor,
1285
+ selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]"
1286
+ },
1287
+ {
1288
+ kind: "directive",
1289
+ type: i1.NgControlStatus,
1290
+ selector: "[formControlName],[ngModel],[formControl]"
1291
+ },
1292
+ {
1293
+ kind: "directive",
1294
+ type: i1.NgControlStatusGroup,
1295
+ selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]"
1296
+ },
1297
+ {
1298
+ kind: "directive",
1299
+ type: i1.MaxLengthValidator,
1300
+ selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]",
1301
+ inputs: ["maxlength"]
1302
+ },
1303
+ {
1304
+ kind: "directive",
1305
+ type: i1.FormGroupDirective,
1306
+ selector: "[formGroup]",
1307
+ inputs: ["formGroup"],
1308
+ outputs: ["ngSubmit"],
1309
+ exportAs: ["ngForm"]
1310
+ },
1311
+ {
1312
+ kind: "directive",
1313
+ type: i1.FormControlName,
1314
+ selector: "[formControlName]",
1315
+ inputs: [
1316
+ "formControlName",
1317
+ "disabled",
1318
+ "ngModel"
1319
+ ],
1320
+ outputs: ["ngModelChange"]
1321
+ },
1322
+ {
1323
+ kind: "component",
1324
+ type: BsAlertComponent,
1325
+ selector: "bs-alert",
1326
+ inputs: [
1327
+ "type",
1328
+ "announce",
1329
+ "isVisible"
1330
+ ],
1331
+ outputs: ["isVisibleChange", "afterOpenedOrClosed"]
1332
+ },
1333
+ {
1334
+ kind: "component",
1335
+ type: BsCardComponent,
1336
+ selector: "bs-card",
1337
+ inputs: ["color", "outline"]
1338
+ },
1339
+ {
1340
+ kind: "component",
1341
+ type: BsCardHeaderComponent,
1342
+ selector: "bs-card-header",
1343
+ inputs: ["color", "navStyle"]
1344
+ },
1345
+ {
1346
+ kind: "component",
1347
+ type: BsCheckboxComponent,
1348
+ selector: "bs-checkbox",
1349
+ inputs: [
1350
+ "type",
1351
+ "isToggled",
1352
+ "indeterminate",
1353
+ "name",
1354
+ "value",
1355
+ "group"
1356
+ ],
1357
+ outputs: ["isToggledChange", "indeterminateChange"]
1358
+ },
1359
+ {
1360
+ kind: "component",
1361
+ type: BsFormComponent,
1362
+ selector: "bs-form",
1363
+ inputs: ["action", "method"],
1364
+ outputs: ["submitted"]
1365
+ },
1366
+ {
1367
+ kind: "directive",
1368
+ type: BsFormControlDirective,
1369
+ selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)"
1370
+ },
1371
+ {
1372
+ kind: "component",
1373
+ type: BsSpinnerComponent,
1374
+ selector: "bs-spinner",
1375
+ inputs: ["type", "color"]
1376
+ },
1377
+ {
1378
+ kind: "pipe",
1379
+ type: TranslateKeyPipe,
1380
+ name: "t"
1381
+ }
1382
+ ],
1383
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1384
+ });
1385
+ };
1386
+ i0.ɵɵngDeclareClassMetadata({
1387
+ minVersion: "12.0.0",
1388
+ version: "22.2.0",
1389
+ ngImport: i0,
1390
+ type: SparkTwoFactorSetupComponent,
1391
+ decorators: [{
1392
+ type: Component,
1393
+ args: [{
1394
+ selector: "spark-two-factor-setup",
1395
+ standalone: true,
1396
+ changeDetection: ChangeDetectionStrategy.OnPush,
1397
+ imports: [
1398
+ ReactiveFormsModule,
1399
+ BsAlertComponent,
1400
+ BsCardComponent,
1401
+ BsCardHeaderComponent,
1402
+ BsCheckboxComponent,
1403
+ BsFormComponent,
1404
+ BsFormControlDirective,
1405
+ BsSpinnerComponent,
1406
+ TranslateKeyPipe
1407
+ ],
1408
+ template: `
1409
+ <div class="d-flex justify-content-center">
1410
+ <bs-card style="width: 100%; max-width: 640px;">
1411
+ <bs-card-header><h3 class="mb-0">{{ 'auth.twoFactorSetupTitle' | t }}</h3></bs-card-header>
1412
+ <div class="p-4">
1413
+ @for (message of messages(); track message) {
1414
+ <bs-alert [type]="colors.danger" class="mb-3 d-block spark-account-error">{{ message | t }}</bs-alert>
1415
+ }
1416
+ @if (loading()) {
1417
+ <div class="text-center"><bs-spinner /></div>
1418
+ } @else if (state(); as s) {
1419
+ @if (recoveryCodes().length) {
1420
+ <bs-alert [type]="colors.warning" class="mb-3 d-block spark-recovery-codes">
1421
+ <p class="mb-2">{{ 'auth.recoveryCodesShownOnce' | t }}</p>
1422
+ <div class="d-flex flex-wrap gap-3 font-monospace">
1423
+ @for (code of recoveryCodes(); track code) { <span>{{ code }}</span> }
1424
+ </div>
1425
+ </bs-alert>
1426
+ }
1427
+
1428
+ @if (s.isTwoFactorEnabled) {
1429
+ <p class="spark-2fa-enabled">{{ 'auth.twoFactorEnabled' | t }}</p>
1430
+ <p class="text-muted">{{ 'auth.recoveryCodesLeft' | t }}: {{ s.recoveryCodesLeft }}</p>
1431
+ <div class="d-flex flex-wrap gap-2">
1432
+ <button type="button" class="btn btn-outline-primary spark-2fa-new-codes" [disabled]="busy()" (click)="regenerateRecoveryCodes()">
1433
+ {{ 'auth.newRecoveryCodes' | t }}
1434
+ </button>
1435
+ @if (s.isMachineRemembered) {
1436
+ <button type="button" class="btn btn-outline-secondary" [disabled]="busy()" (click)="forgetMachine()">
1437
+ {{ 'auth.forgetBrowser' | t }}
1438
+ </button>
1439
+ }
1440
+ <button type="button" class="btn btn-outline-warning" [disabled]="busy()" (click)="resetKey()">
1441
+ {{ 'auth.resetAuthenticator' | t }}
1442
+ </button>
1443
+ <button type="button" class="btn btn-outline-danger spark-2fa-disable" [disabled]="busy()" (click)="disable()">
1444
+ {{ 'auth.disableTwoFactor' | t }}
1445
+ </button>
1446
+ </div>
1447
+
1448
+ @if (bypassOffered()) {
1449
+ <hr class="my-4" />
1450
+ <div class="spark-2fa-external-bypass">
1451
+ <bs-form>
1452
+ <bs-checkbox [type]="'checkbox'" [name]="'externalTwoFactorBypass'" [isToggled]="bypassChecked()"
1453
+ (isToggledChange)="toggleBypass($event === true)">
1454
+ {{ 'auth.externalTwoFactorBypassLabel' | t }}
1455
+ </bs-checkbox>
1456
+ </bs-form>
1457
+ <small class="text-muted d-block">{{ 'auth.externalTwoFactorBypassHint' | t }}</small>
1458
+ @if (bypassPending()) {
1459
+ <bs-form>
1460
+ <form [formGroup]="bypassForm" (ngSubmit)="confirmBypass()" class="d-flex gap-2 align-items-end mt-2">
1461
+ <div class="flex-grow-1">
1462
+ <label for="bypassCode" class="form-label">{{ 'auth.code' | t }}</label>
1463
+ <input type="text" id="bypassCode" formControlName="code" inputmode="numeric" autocomplete="one-time-code" maxlength="8" />
1464
+ </div>
1465
+ <button type="submit" class="btn btn-primary spark-2fa-bypass-confirm" [disabled]="busy()">{{ 'auth.verify' | t }}</button>
1466
+ <button type="button" class="btn btn-outline-secondary spark-2fa-bypass-cancel" [disabled]="busy()" (click)="toggleBypass(false)">{{ 'common.cancel' | t }}</button>
1467
+ </form>
1468
+ </bs-form>
1469
+ }
1470
+ </div>
1471
+ }
1472
+ } @else {
1473
+ <p>{{ 'auth.twoFactorSetupIntro' | t }}</p>
1474
+ @if (qrDataUrl(); as qr) {
1475
+ <div class="text-center my-3">
1476
+ <img class="spark-2fa-qr" [src]="qr" width="200" height="200" [attr.alt]="'auth.qrCodeAlt' | t" />
1477
+ </div>
1478
+ }
1479
+ @if (authenticator(); as a) {
1480
+ <p class="text-muted small mb-1">{{ 'auth.manualKey' | t }}</p>
1481
+ <p class="font-monospace spark-2fa-key">{{ formatKey(a.sharedKey) }}</p>
1482
+ }
1483
+ <bs-form>
1484
+ <form [formGroup]="form" (ngSubmit)="enable()" class="d-flex gap-2 align-items-end">
1485
+ <div class="flex-grow-1">
1486
+ <label for="twoFactorCode" class="form-label">{{ 'auth.code' | t }}</label>
1487
+ <input type="text" id="twoFactorCode" formControlName="code" inputmode="numeric" autocomplete="one-time-code" maxlength="8" />
1488
+ </div>
1489
+ <button type="submit" class="btn btn-primary spark-2fa-enable" [disabled]="busy()">{{ 'auth.enableTwoFactor' | t }}</button>
1490
+ </form>
1491
+ </bs-form>
1492
+ }
1493
+ }
1494
+ </div>
1495
+ </bs-card>
1496
+ </div>
1497
+ `
1498
+ }]
1499
+ }],
1500
+ ctorParameters: () => []
1501
+ });
1502
+ var SparkExternalLoginsComponent = class SparkExternalLoginsComponent {
1503
+ auth = inject(SparkAuthService);
1504
+ router = inject(Router);
1505
+ presentations = inject(SPARK_EXTERNAL_PROVIDERS, { optional: true }) ?? [];
1506
+ colors = Color;
1507
+ loading = signal(true, ...ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []);
1508
+ busy = signal(false, ...ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []);
1509
+ errorKey = signal("", ...ngDevMode ? [{ debugName: "errorKey" }] : /* istanbul ignore next */ []);
1510
+ logins = signal(null, ...ngDevMode ? [{ debugName: "logins" }] : /* istanbul ignore next */ []);
1511
+ linkPending = computed(() => this.auth.externalLoginPending(), ...ngDevMode ? [{ debugName: "linkPending" }] : /* istanbul ignore next */ []);
1512
+ errorIsNotice = computed(() => this.errorKey() === "auth.externalLoginError.link_confirmation_sent", ...ngDevMode ? [{ debugName: "errorIsNotice" }] : /* istanbul ignore next */ []);
1513
+ constructor() {
1514
+ const returned = this.auth.takeExternalLoginResult();
1515
+ if (returned) this.showLinkError(returned);
1516
+ this.refresh();
1517
+ }
1518
+ showLinkError(error) {
1519
+ if (error === "popup_closed") return;
1520
+ this.errorKey.set(`auth.externalLoginError.${error ?? "link_failed"}`);
1521
+ }
1522
+ presentation(scheme) {
1523
+ return this.presentations.find((p) => p.scheme.toLowerCase() === scheme.toLowerCase());
1524
+ }
1525
+ iconFor(scheme) {
1526
+ return this.presentation(scheme)?.iconClass;
1527
+ }
1528
+ nameFor(scheme, serverName) {
1529
+ return this.presentation(scheme)?.displayName ?? serverName ?? scheme;
1530
+ }
1531
+ async refresh() {
1532
+ this.loading.set(true);
1533
+ try {
1534
+ this.logins.set(await this.auth.externalLogins());
1535
+ } catch {
1536
+ this.errorKey.set("auth.externalLoginsUnavailable");
1537
+ } finally {
1538
+ this.loading.set(false);
1539
+ }
1540
+ }
1541
+ async link(provider) {
1542
+ this.errorKey.set("");
1543
+ const result = await this.auth.linkProvider(provider, { returnUrl: this.router.url });
1544
+ if (!result.success) this.showLinkError(result.error);
1545
+ await this.refresh();
1546
+ }
1547
+ async unlink(provider, providerKey) {
1548
+ this.errorKey.set("");
1549
+ this.busy.set(true);
1550
+ try {
1551
+ const result = await this.auth.unlinkProvider(provider, providerKey);
1552
+ if (!result.success) this.errorKey.set(`auth.unlinkError.${result.error ?? "unlink_failed"}`);
1553
+ await this.refresh();
1554
+ } finally {
1555
+ this.busy.set(false);
1556
+ }
1557
+ }
1558
+ static ɵfac = i0.ɵɵngDeclareFactory({
1559
+ minVersion: "12.0.0",
1560
+ version: "22.2.0",
1561
+ ngImport: i0,
1562
+ type: SparkExternalLoginsComponent,
1563
+ deps: [],
1564
+ target: i0.ɵɵFactoryTarget.Component
1565
+ });
1566
+ static ɵcmp = i0.ɵɵngDeclareComponent({
1567
+ minVersion: "17.0.0",
1568
+ version: "22.2.0",
1569
+ type: SparkExternalLoginsComponent,
1570
+ isStandalone: true,
1571
+ selector: "spark-external-logins",
1572
+ ngImport: i0,
1573
+ template: `
1574
+ <div class="d-flex justify-content-center">
1575
+ <bs-card style="width: 100%; max-width: 640px;">
1576
+ <bs-card-header><h3 class="mb-0">{{ 'auth.externalLoginsTitle' | t }}</h3></bs-card-header>
1577
+ <div class="p-4">
1578
+ @if (errorKey()) {
1579
+ <bs-alert [type]="errorIsNotice() ? colors.info : colors.danger" class="mb-3 d-block spark-account-error">{{ errorKey() | t }}</bs-alert>
1580
+ }
1581
+ @if (loading()) {
1582
+ <div class="text-center"><bs-spinner /></div>
1583
+ } @else if (logins(); as l) {
1584
+ @for (login of l.linked; track login.provider + login.providerKey) {
1585
+ <div class="d-flex align-items-center justify-content-between border-bottom py-2 spark-linked-login">
1586
+ <span>
1587
+ @if (iconFor(login.provider); as icon) { <i [class]="icon" class="me-2"></i> }
1588
+ {{ nameFor(login.provider, login.displayName) }}
1589
+ </span>
1590
+ <button type="button" class="btn btn-outline-danger btn-sm" [disabled]="busy() || !login.canUnlink"
1591
+ [attr.title]="login.canUnlink ? null : ('auth.lastCredentialHint' | t)"
1592
+ (click)="unlink(login.provider, login.providerKey)">
1593
+ {{ 'auth.removeLogin' | t }}
1594
+ </button>
1595
+ </div>
1596
+ } @empty {
1597
+ <p class="text-muted">{{ 'auth.noLinkedLogins' | t }}</p>
1598
+ }
1599
+
1600
+ @if (l.available.length) {
1601
+ <h5 class="mt-4">{{ 'auth.addLogin' | t }}</h5>
1602
+ <div class="d-flex flex-wrap gap-2">
1603
+ @for (provider of l.available; track provider.provider) {
1604
+ <button type="button" class="btn btn-outline-primary spark-available-login" [disabled]="busy() || linkPending()" (click)="link(provider.provider)">
1605
+ @if (iconFor(provider.provider); as icon) { <i [class]="icon" class="me-2"></i> }
1606
+ {{ nameFor(provider.provider, provider.displayName) }}
1607
+ </button>
1608
+ }
1609
+ </div>
1610
+ @if (linkPending()) {
1611
+ <div class="text-center mt-3 spark-link-pending"><bs-spinner /></div>
1612
+ }
1613
+ }
1614
+ }
1615
+ </div>
1616
+ </bs-card>
1617
+ </div>
1618
+ `,
1619
+ isInline: true,
1620
+ dependencies: [
1621
+ {
1622
+ kind: "component",
1623
+ type: BsAlertComponent,
1624
+ selector: "bs-alert",
1625
+ inputs: [
1626
+ "type",
1627
+ "announce",
1628
+ "isVisible"
1629
+ ],
1630
+ outputs: ["isVisibleChange", "afterOpenedOrClosed"]
1631
+ },
1632
+ {
1633
+ kind: "component",
1634
+ type: BsCardComponent,
1635
+ selector: "bs-card",
1636
+ inputs: ["color", "outline"]
1637
+ },
1638
+ {
1639
+ kind: "component",
1640
+ type: BsCardHeaderComponent,
1641
+ selector: "bs-card-header",
1642
+ inputs: ["color", "navStyle"]
1643
+ },
1644
+ {
1645
+ kind: "component",
1646
+ type: BsSpinnerComponent,
1647
+ selector: "bs-spinner",
1648
+ inputs: ["type", "color"]
1649
+ },
1650
+ {
1651
+ kind: "pipe",
1652
+ type: TranslateKeyPipe,
1653
+ name: "t"
1654
+ }
1655
+ ],
1656
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1657
+ });
1658
+ };
1659
+ i0.ɵɵngDeclareClassMetadata({
1660
+ minVersion: "12.0.0",
1661
+ version: "22.2.0",
1662
+ ngImport: i0,
1663
+ type: SparkExternalLoginsComponent,
1664
+ decorators: [{
1665
+ type: Component,
1666
+ args: [{
1667
+ selector: "spark-external-logins",
1668
+ standalone: true,
1669
+ changeDetection: ChangeDetectionStrategy.OnPush,
1670
+ imports: [
1671
+ BsAlertComponent,
1672
+ BsCardComponent,
1673
+ BsCardHeaderComponent,
1674
+ BsSpinnerComponent,
1675
+ TranslateKeyPipe
1676
+ ],
1677
+ template: `
1678
+ <div class="d-flex justify-content-center">
1679
+ <bs-card style="width: 100%; max-width: 640px;">
1680
+ <bs-card-header><h3 class="mb-0">{{ 'auth.externalLoginsTitle' | t }}</h3></bs-card-header>
1681
+ <div class="p-4">
1682
+ @if (errorKey()) {
1683
+ <bs-alert [type]="errorIsNotice() ? colors.info : colors.danger" class="mb-3 d-block spark-account-error">{{ errorKey() | t }}</bs-alert>
1684
+ }
1685
+ @if (loading()) {
1686
+ <div class="text-center"><bs-spinner /></div>
1687
+ } @else if (logins(); as l) {
1688
+ @for (login of l.linked; track login.provider + login.providerKey) {
1689
+ <div class="d-flex align-items-center justify-content-between border-bottom py-2 spark-linked-login">
1690
+ <span>
1691
+ @if (iconFor(login.provider); as icon) { <i [class]="icon" class="me-2"></i> }
1692
+ {{ nameFor(login.provider, login.displayName) }}
1693
+ </span>
1694
+ <button type="button" class="btn btn-outline-danger btn-sm" [disabled]="busy() || !login.canUnlink"
1695
+ [attr.title]="login.canUnlink ? null : ('auth.lastCredentialHint' | t)"
1696
+ (click)="unlink(login.provider, login.providerKey)">
1697
+ {{ 'auth.removeLogin' | t }}
1698
+ </button>
1699
+ </div>
1700
+ } @empty {
1701
+ <p class="text-muted">{{ 'auth.noLinkedLogins' | t }}</p>
1702
+ }
1703
+
1704
+ @if (l.available.length) {
1705
+ <h5 class="mt-4">{{ 'auth.addLogin' | t }}</h5>
1706
+ <div class="d-flex flex-wrap gap-2">
1707
+ @for (provider of l.available; track provider.provider) {
1708
+ <button type="button" class="btn btn-outline-primary spark-available-login" [disabled]="busy() || linkPending()" (click)="link(provider.provider)">
1709
+ @if (iconFor(provider.provider); as icon) { <i [class]="icon" class="me-2"></i> }
1710
+ {{ nameFor(provider.provider, provider.displayName) }}
1711
+ </button>
1712
+ }
1713
+ </div>
1714
+ @if (linkPending()) {
1715
+ <div class="text-center mt-3 spark-link-pending"><bs-spinner /></div>
1716
+ }
1717
+ }
1718
+ }
1719
+ </div>
1720
+ </bs-card>
1721
+ </div>
1722
+ `
1723
+ }]
1724
+ }],
1725
+ ctorParameters: () => []
1726
+ });
1727
+ var SparkPersonalDataComponent = class SparkPersonalDataComponent {
1728
+ auth = inject(SparkAuthService);
1729
+ router = inject(Router);
1730
+ config = inject(SPARK_AUTH_CONFIG);
1731
+ colors = Color;
1732
+ form = new FormGroup({
1733
+ password: new FormControl("", { nonNullable: true }),
1734
+ confirm: new FormControl(false, { nonNullable: true })
1735
+ });
1736
+ busy = signal(false, ...ngDevMode ? [{ debugName: "busy" }] : /* istanbul ignore next */ []);
1737
+ downloadMessages = signal([], ...ngDevMode ? [{ debugName: "downloadMessages" }] : /* istanbul ignore next */ []);
1738
+ deleteMessages = signal([], ...ngDevMode ? [{ debugName: "deleteMessages" }] : /* istanbul ignore next */ []);
1739
+ hasPassword = signal(null, ...ngDevMode ? [{ debugName: "hasPassword" }] : /* istanbul ignore next */ []);
1740
+ async ngOnInit() {
1741
+ try {
1742
+ const result = await this.auth.personalData();
1743
+ const flag = result.success ? result.value?.account?.hasPassword : void 0;
1744
+ this.hasPassword.set(typeof flag === "boolean" ? flag : null);
1745
+ } catch {
1746
+ this.hasPassword.set(null);
1747
+ }
1748
+ }
1749
+ async download() {
1750
+ this.busy.set(true);
1751
+ this.downloadMessages.set([]);
1752
+ try {
1753
+ const result = await this.auth.personalData();
1754
+ if (!result.success) {
1755
+ this.downloadMessages.set(accountMessages(result));
1756
+ return;
1757
+ }
1758
+ const blob = new Blob([JSON.stringify(result.value, null, 2)], { type: "application/json" });
1759
+ const url = URL.createObjectURL(blob);
1760
+ const anchor = document.createElement("a");
1761
+ anchor.href = url;
1762
+ anchor.download = "personal-data.json";
1763
+ anchor.click();
1764
+ URL.revokeObjectURL(url);
1765
+ } finally {
1766
+ this.busy.set(false);
1767
+ }
1768
+ }
1769
+ async deleteAccount() {
1770
+ if (!this.form.controls.confirm.value) return;
1771
+ this.busy.set(true);
1772
+ this.deleteMessages.set([]);
1773
+ try {
1774
+ const result = await this.auth.deleteAccount(this.form.controls.password.value || null);
1775
+ if (result.success) {
1776
+ await this.router.navigateByUrl(this.config.defaultRedirectUrl);
1777
+ return;
1778
+ }
1779
+ this.deleteMessages.set(accountMessages(result));
1780
+ } finally {
1781
+ this.busy.set(false);
1782
+ }
1783
+ }
1784
+ static ɵfac = i0.ɵɵngDeclareFactory({
1785
+ minVersion: "12.0.0",
1786
+ version: "22.2.0",
1787
+ ngImport: i0,
1788
+ type: SparkPersonalDataComponent,
1789
+ deps: [],
1790
+ target: i0.ɵɵFactoryTarget.Component
1791
+ });
1792
+ static ɵcmp = i0.ɵɵngDeclareComponent({
1793
+ minVersion: "17.0.0",
1794
+ version: "22.2.0",
1795
+ type: SparkPersonalDataComponent,
1796
+ isStandalone: true,
1797
+ selector: "spark-personal-data",
1798
+ ngImport: i0,
1799
+ template: `
1800
+ <div class="d-flex justify-content-center">
1801
+ <bs-card style="width: 100%; max-width: 640px;">
1802
+ <bs-card-header><h3 class="mb-0">{{ 'auth.personalDataTitle' | t }}</h3></bs-card-header>
1803
+ <div class="p-4">
1804
+ <p>{{ 'auth.personalDataDescription' | t }}</p>
1805
+ @for (message of downloadMessages(); track message) {
1806
+ <bs-alert [type]="colors.danger" class="mb-3 d-block">{{ message | t }}</bs-alert>
1807
+ }
1808
+ <button type="button" class="btn btn-outline-primary spark-download-data" [disabled]="busy()" (click)="download()">
1809
+ {{ 'auth.downloadPersonalData' | t }}
1810
+ </button>
1811
+
1812
+ <hr class="my-4" />
1813
+
1814
+ <h5 class="text-danger">{{ 'auth.deleteAccountTitle' | t }}</h5>
1815
+ <p>{{ 'auth.deleteAccountDescription' | t }}</p>
1816
+ @for (message of deleteMessages(); track message) {
1817
+ <bs-alert [type]="colors.danger" class="mb-3 d-block spark-delete-error">{{ message | t }}</bs-alert>
1818
+ }
1819
+ <bs-form>
1820
+ <form [formGroup]="form" (ngSubmit)="deleteAccount()">
1821
+ @if (hasPassword() !== false) {
1822
+ <div class="mb-3">
1823
+ <label for="deletePassword" class="form-label">{{ 'auth.password' | t }}</label>
1824
+ <input type="password" id="deletePassword" formControlName="password" autocomplete="current-password" />
1825
+ <small class="text-muted">{{ 'auth.deletePasswordHint' | t }}</small>
1826
+ </div>
1827
+ } @else {
1828
+ <p class="text-muted spark-delete-recent-sign-in">{{ 'auth.deleteRecentSignIn' | t }}</p>
1829
+ }
1830
+ <div class="mb-3">
1831
+ <bs-checkbox [type]="'checkbox'" formControlName="confirm" [name]="'confirmDelete'">{{ 'auth.deleteAccountConfirm' | t }}</bs-checkbox>
1832
+ </div>
1833
+ <button type="submit" class="btn btn-danger spark-delete-account" [disabled]="busy() || !form.controls.confirm.value">
1834
+ @if (busy()) { <bs-spinner class="me-1" /> }
1835
+ {{ 'auth.deleteAccount' | t }}
1836
+ </button>
1837
+ </form>
1838
+ </bs-form>
1839
+ </div>
1840
+ </bs-card>
1841
+ </div>
1842
+ `,
1843
+ isInline: true,
1844
+ dependencies: [
1845
+ {
1846
+ kind: "ngmodule",
1847
+ type: ReactiveFormsModule
1848
+ },
1849
+ {
1850
+ kind: "directive",
1851
+ type: i1.ɵNgNoValidate,
1852
+ selector: "form:not([ngNoForm]):not([ngNativeValidate])"
1853
+ },
1854
+ {
1855
+ kind: "directive",
1856
+ type: i1.DefaultValueAccessor,
1857
+ selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]"
1858
+ },
1859
+ {
1860
+ kind: "directive",
1861
+ type: i1.NgControlStatus,
1862
+ selector: "[formControlName],[ngModel],[formControl]"
1863
+ },
1864
+ {
1865
+ kind: "directive",
1866
+ type: i1.NgControlStatusGroup,
1867
+ selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]"
1868
+ },
1869
+ {
1870
+ kind: "directive",
1871
+ type: i1.FormGroupDirective,
1872
+ selector: "[formGroup]",
1873
+ inputs: ["formGroup"],
1874
+ outputs: ["ngSubmit"],
1875
+ exportAs: ["ngForm"]
1876
+ },
1877
+ {
1878
+ kind: "directive",
1879
+ type: i1.FormControlName,
1880
+ selector: "[formControlName]",
1881
+ inputs: [
1882
+ "formControlName",
1883
+ "disabled",
1884
+ "ngModel"
1885
+ ],
1886
+ outputs: ["ngModelChange"]
1887
+ },
1888
+ {
1889
+ kind: "component",
1890
+ type: BsAlertComponent,
1891
+ selector: "bs-alert",
1892
+ inputs: [
1893
+ "type",
1894
+ "announce",
1895
+ "isVisible"
1896
+ ],
1897
+ outputs: ["isVisibleChange", "afterOpenedOrClosed"]
1898
+ },
1899
+ {
1900
+ kind: "component",
1901
+ type: BsCardComponent,
1902
+ selector: "bs-card",
1903
+ inputs: ["color", "outline"]
1904
+ },
1905
+ {
1906
+ kind: "component",
1907
+ type: BsCardHeaderComponent,
1908
+ selector: "bs-card-header",
1909
+ inputs: ["color", "navStyle"]
1910
+ },
1911
+ {
1912
+ kind: "component",
1913
+ type: BsCheckboxComponent,
1914
+ selector: "bs-checkbox",
1915
+ inputs: [
1916
+ "type",
1917
+ "isToggled",
1918
+ "indeterminate",
1919
+ "name",
1920
+ "value",
1921
+ "group"
1922
+ ],
1923
+ outputs: ["isToggledChange", "indeterminateChange"]
1924
+ },
1925
+ {
1926
+ kind: "component",
1927
+ type: BsFormComponent,
1928
+ selector: "bs-form",
1929
+ inputs: ["action", "method"],
1930
+ outputs: ["submitted"]
1931
+ },
1932
+ {
1933
+ kind: "directive",
1934
+ type: BsFormControlDirective,
1935
+ selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)"
1936
+ },
1937
+ {
1938
+ kind: "component",
1939
+ type: BsSpinnerComponent,
1940
+ selector: "bs-spinner",
1941
+ inputs: ["type", "color"]
1942
+ },
1943
+ {
1944
+ kind: "pipe",
1945
+ type: TranslateKeyPipe,
1946
+ name: "t"
1947
+ }
1948
+ ],
1949
+ changeDetection: i0.ChangeDetectionStrategy.OnPush
1950
+ });
1951
+ };
1952
+ i0.ɵɵngDeclareClassMetadata({
1953
+ minVersion: "12.0.0",
1954
+ version: "22.2.0",
1955
+ ngImport: i0,
1956
+ type: SparkPersonalDataComponent,
1957
+ decorators: [{
1958
+ type: Component,
1959
+ args: [{
1960
+ selector: "spark-personal-data",
1961
+ standalone: true,
1962
+ changeDetection: ChangeDetectionStrategy.OnPush,
1963
+ imports: [
1964
+ ReactiveFormsModule,
1965
+ BsAlertComponent,
1966
+ BsCardComponent,
1967
+ BsCardHeaderComponent,
1968
+ BsCheckboxComponent,
1969
+ BsFormComponent,
1970
+ BsFormControlDirective,
1971
+ BsSpinnerComponent,
1972
+ TranslateKeyPipe
1973
+ ],
1974
+ template: `
1975
+ <div class="d-flex justify-content-center">
1976
+ <bs-card style="width: 100%; max-width: 640px;">
1977
+ <bs-card-header><h3 class="mb-0">{{ 'auth.personalDataTitle' | t }}</h3></bs-card-header>
1978
+ <div class="p-4">
1979
+ <p>{{ 'auth.personalDataDescription' | t }}</p>
1980
+ @for (message of downloadMessages(); track message) {
1981
+ <bs-alert [type]="colors.danger" class="mb-3 d-block">{{ message | t }}</bs-alert>
1982
+ }
1983
+ <button type="button" class="btn btn-outline-primary spark-download-data" [disabled]="busy()" (click)="download()">
1984
+ {{ 'auth.downloadPersonalData' | t }}
1985
+ </button>
1986
+
1987
+ <hr class="my-4" />
1988
+
1989
+ <h5 class="text-danger">{{ 'auth.deleteAccountTitle' | t }}</h5>
1990
+ <p>{{ 'auth.deleteAccountDescription' | t }}</p>
1991
+ @for (message of deleteMessages(); track message) {
1992
+ <bs-alert [type]="colors.danger" class="mb-3 d-block spark-delete-error">{{ message | t }}</bs-alert>
1993
+ }
1994
+ <bs-form>
1995
+ <form [formGroup]="form" (ngSubmit)="deleteAccount()">
1996
+ @if (hasPassword() !== false) {
1997
+ <div class="mb-3">
1998
+ <label for="deletePassword" class="form-label">{{ 'auth.password' | t }}</label>
1999
+ <input type="password" id="deletePassword" formControlName="password" autocomplete="current-password" />
2000
+ <small class="text-muted">{{ 'auth.deletePasswordHint' | t }}</small>
2001
+ </div>
2002
+ } @else {
2003
+ <p class="text-muted spark-delete-recent-sign-in">{{ 'auth.deleteRecentSignIn' | t }}</p>
2004
+ }
2005
+ <div class="mb-3">
2006
+ <bs-checkbox [type]="'checkbox'" formControlName="confirm" [name]="'confirmDelete'">{{ 'auth.deleteAccountConfirm' | t }}</bs-checkbox>
2007
+ </div>
2008
+ <button type="submit" class="btn btn-danger spark-delete-account" [disabled]="busy() || !form.controls.confirm.value">
2009
+ @if (busy()) { <bs-spinner class="me-1" /> }
2010
+ {{ 'auth.deleteAccount' | t }}
2011
+ </button>
2012
+ </form>
2013
+ </bs-form>
2014
+ </div>
2015
+ </bs-card>
2016
+ </div>
2017
+ `
2018
+ }]
2019
+ }]
2020
+ });
2021
+ export { SparkAccountOverviewComponent, SparkAccountProfileComponent, SparkChangePasswordComponent, SparkExternalLoginsComponent, SparkPersonalDataComponent, SparkTwoFactorSetupComponent, accountMessages, fieldMessages };
2022
+
2023
+ //# sourceMappingURL=mintplayer-ng-spark-auth-account.mjs.map