@mintplayer/ng-spark-auth 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,1136 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, signal, computed, Injectable, makeEnvironmentProviders, Component } from '@angular/core';
3
+ import { HttpClient, withInterceptors, withXsrfConfiguration } from '@angular/common/http';
4
+ import { switchMap, tap } from 'rxjs';
5
+ import { Router, RouterLink, ActivatedRoute } from '@angular/router';
6
+ import * as i1 from '@angular/forms';
7
+ import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
8
+ import * as i2 from '@mintplayer/ng-bootstrap/form';
9
+ import { BsFormModule } from '@mintplayer/ng-bootstrap/form';
10
+ import * as i3 from '@mintplayer/ng-bootstrap/toggle-button';
11
+ import { BsToggleButtonModule } from '@mintplayer/ng-bootstrap/toggle-button';
12
+
13
+ const SPARK_AUTH_CONFIG = new InjectionToken('SPARK_AUTH_CONFIG');
14
+ const defaultSparkAuthConfig = {
15
+ apiBasePath: '/spark/auth',
16
+ defaultRedirectUrl: '/',
17
+ loginUrl: '/login',
18
+ };
19
+
20
+ const SPARK_AUTH_ROUTE_PATHS = new InjectionToken('SPARK_AUTH_ROUTE_PATHS');
21
+
22
+ class SparkAuthService {
23
+ http = inject(HttpClient);
24
+ config = inject(SPARK_AUTH_CONFIG);
25
+ currentUser = signal(null, ...(ngDevMode ? [{ debugName: "currentUser" }] : []));
26
+ user = this.currentUser.asReadonly();
27
+ isAuthenticated = computed(() => this.currentUser()?.isAuthenticated === true, ...(ngDevMode ? [{ debugName: "isAuthenticated" }] : []));
28
+ constructor() {
29
+ this.checkAuth().subscribe();
30
+ }
31
+ login(email, password) {
32
+ return this.http
33
+ .post(`${this.config.apiBasePath}/login?useCookies=true`, { email, password })
34
+ .pipe(switchMap(() => this.csrfRefresh()), tap(() => { this.checkAuth().subscribe(); }));
35
+ }
36
+ loginTwoFactor(twoFactorCode, twoFactorRecoveryCode) {
37
+ return this.http
38
+ .post(`${this.config.apiBasePath}/login?useCookies=true`, {
39
+ twoFactorCode,
40
+ twoFactorRecoveryCode,
41
+ })
42
+ .pipe(switchMap(() => this.csrfRefresh()), tap(() => { this.checkAuth().subscribe(); }));
43
+ }
44
+ register(email, password) {
45
+ return this.http.post(`${this.config.apiBasePath}/register`, { email, password });
46
+ }
47
+ logout() {
48
+ return this.http
49
+ .post(`${this.config.apiBasePath}/logout`, {})
50
+ .pipe(switchMap(() => this.csrfRefresh()), tap(() => { this.currentUser.set(null); }));
51
+ }
52
+ csrfRefresh() {
53
+ return this.http.post(`${this.config.apiBasePath}/csrf-refresh`, {});
54
+ }
55
+ checkAuth() {
56
+ return this.http.get(`${this.config.apiBasePath}/me`).pipe(tap({
57
+ next: (user) => this.currentUser.set(user),
58
+ error: () => this.currentUser.set(null),
59
+ }));
60
+ }
61
+ forgotPassword(email) {
62
+ return this.http.post(`${this.config.apiBasePath}/forgotPassword`, { email });
63
+ }
64
+ resetPassword(email, resetCode, newPassword) {
65
+ return this.http.post(`${this.config.apiBasePath}/resetPassword`, {
66
+ email,
67
+ resetCode,
68
+ newPassword,
69
+ });
70
+ }
71
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkAuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
72
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkAuthService, providedIn: 'root' });
73
+ }
74
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkAuthService, decorators: [{
75
+ type: Injectable,
76
+ args: [{ providedIn: 'root' }]
77
+ }], ctorParameters: () => [] });
78
+
79
+ const sparkAuthInterceptor = (req, next) => {
80
+ const config = inject(SPARK_AUTH_CONFIG);
81
+ const router = inject(Router);
82
+ return next(req).pipe(tap({
83
+ error: (error) => {
84
+ if (error.status === 401 &&
85
+ !req.url.startsWith(config.apiBasePath)) {
86
+ router.navigate([config.loginUrl], {
87
+ queryParams: { returnUrl: router.url },
88
+ });
89
+ }
90
+ },
91
+ }));
92
+ };
93
+
94
+ const sparkAuthGuard = (route, state) => {
95
+ const authService = inject(SparkAuthService);
96
+ const router = inject(Router);
97
+ const config = inject(SPARK_AUTH_CONFIG);
98
+ if (authService.isAuthenticated()) {
99
+ return true;
100
+ }
101
+ return router.createUrlTree([config.loginUrl], {
102
+ queryParams: { returnUrl: state.url },
103
+ });
104
+ };
105
+
106
+ function provideSparkAuth(config) {
107
+ return makeEnvironmentProviders([
108
+ {
109
+ provide: SPARK_AUTH_CONFIG,
110
+ useValue: { ...defaultSparkAuthConfig, ...config },
111
+ },
112
+ ]);
113
+ }
114
+ function withSparkAuth() {
115
+ return [
116
+ withInterceptors([sparkAuthInterceptor]),
117
+ withXsrfConfiguration({ cookieName: 'XSRF-TOKEN', headerName: 'X-XSRF-TOKEN' }),
118
+ ];
119
+ }
120
+
121
+ function resolveEntry(entry, defaultPath, defaultLoader) {
122
+ if (entry === undefined || typeof entry === 'string') {
123
+ return {
124
+ path: typeof entry === 'string' ? entry : defaultPath,
125
+ loadComponent: defaultLoader,
126
+ };
127
+ }
128
+ return {
129
+ path: entry.path,
130
+ loadComponent: entry.component
131
+ ? () => Promise.resolve(entry.component)
132
+ : defaultLoader,
133
+ };
134
+ }
135
+ function sparkAuthRoutes(config) {
136
+ const login = resolveEntry(config?.login, 'login', () => Promise.resolve().then(function () { return sparkLogin_component; }).then(m => m.SparkLoginComponent));
137
+ const twoFactor = resolveEntry(config?.twoFactor, 'login/two-factor', () => Promise.resolve().then(function () { return sparkTwoFactor_component; }).then(m => m.SparkTwoFactorComponent));
138
+ const register = resolveEntry(config?.register, 'register', () => Promise.resolve().then(function () { return sparkRegister_component; }).then(m => m.SparkRegisterComponent));
139
+ const forgotPassword = resolveEntry(config?.forgotPassword, 'forgot-password', () => Promise.resolve().then(function () { return sparkForgotPassword_component; }).then(m => m.SparkForgotPasswordComponent));
140
+ const resetPassword = resolveEntry(config?.resetPassword, 'reset-password', () => Promise.resolve().then(function () { return sparkResetPassword_component; }).then(m => m.SparkResetPasswordComponent));
141
+ const paths = {
142
+ login: '/' + login.path,
143
+ twoFactor: '/' + twoFactor.path,
144
+ register: '/' + register.path,
145
+ forgotPassword: '/' + forgotPassword.path,
146
+ resetPassword: '/' + resetPassword.path,
147
+ };
148
+ return [
149
+ {
150
+ path: '',
151
+ providers: [
152
+ { provide: SPARK_AUTH_ROUTE_PATHS, useValue: paths },
153
+ ],
154
+ children: [
155
+ { path: login.path, loadComponent: login.loadComponent },
156
+ { path: twoFactor.path, loadComponent: twoFactor.loadComponent },
157
+ { path: register.path, loadComponent: register.loadComponent },
158
+ { path: forgotPassword.path, loadComponent: forgotPassword.loadComponent },
159
+ { path: resetPassword.path, loadComponent: resetPassword.loadComponent },
160
+ ],
161
+ },
162
+ ];
163
+ }
164
+
165
+ class SparkAuthBarComponent {
166
+ authService = inject(SparkAuthService);
167
+ config = inject(SPARK_AUTH_CONFIG);
168
+ router = inject(Router);
169
+ onLogout() {
170
+ this.authService.logout().subscribe(() => {
171
+ this.router.navigateByUrl('/');
172
+ });
173
+ }
174
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkAuthBarComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
175
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: SparkAuthBarComponent, isStandalone: true, selector: "spark-auth-bar", ngImport: i0, template: `
176
+ @if (authService.isAuthenticated()) {
177
+ <span class="me-2">{{ authService.user()?.userName }}</span>
178
+ <button class="btn btn-outline-light btn-sm" (click)="onLogout()">Logout</button>
179
+ } @else {
180
+ <a class="btn btn-outline-light btn-sm" [routerLink]="config.loginUrl">Login</a>
181
+ }
182
+ `, isInline: true, dependencies: [{ kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] });
183
+ }
184
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkAuthBarComponent, decorators: [{
185
+ type: Component,
186
+ args: [{
187
+ selector: 'spark-auth-bar',
188
+ standalone: true,
189
+ imports: [RouterLink],
190
+ template: `
191
+ @if (authService.isAuthenticated()) {
192
+ <span class="me-2">{{ authService.user()?.userName }}</span>
193
+ <button class="btn btn-outline-light btn-sm" (click)="onLogout()">Logout</button>
194
+ } @else {
195
+ <a class="btn btn-outline-light btn-sm" [routerLink]="config.loginUrl">Login</a>
196
+ }
197
+ `,
198
+ }]
199
+ }] });
200
+
201
+ class SparkLoginComponent {
202
+ fb = inject(FormBuilder);
203
+ authService = inject(SparkAuthService);
204
+ router = inject(Router);
205
+ route = inject(ActivatedRoute);
206
+ config = inject(SPARK_AUTH_CONFIG);
207
+ routePaths = inject(SPARK_AUTH_ROUTE_PATHS);
208
+ loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
209
+ errorMessage = signal('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
210
+ form = this.fb.group({
211
+ email: ['', Validators.required],
212
+ password: ['', Validators.required],
213
+ rememberMe: [false],
214
+ });
215
+ onSubmit() {
216
+ if (this.form.invalid)
217
+ return;
218
+ this.loading.set(true);
219
+ this.errorMessage.set('');
220
+ const { email, password } = this.form.value;
221
+ this.authService.login(email, password).subscribe({
222
+ next: () => {
223
+ this.loading.set(false);
224
+ const returnUrl = this.route.snapshot.queryParamMap.get('returnUrl');
225
+ this.router.navigateByUrl(returnUrl || this.config.defaultRedirectUrl);
226
+ },
227
+ error: (err) => {
228
+ this.loading.set(false);
229
+ if (err.status === 401 && err.error?.detail === 'RequiresTwoFactor') {
230
+ const returnUrl = this.route.snapshot.queryParamMap.get('returnUrl');
231
+ this.router.navigate([this.routePaths.twoFactor], {
232
+ queryParams: returnUrl ? { returnUrl } : undefined,
233
+ });
234
+ }
235
+ else {
236
+ this.errorMessage.set('Invalid email or password.');
237
+ }
238
+ },
239
+ });
240
+ }
241
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkLoginComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
242
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: SparkLoginComponent, isStandalone: true, selector: "spark-login", ngImport: i0, template: `
243
+ <div class="d-flex justify-content-center">
244
+ <div class="card" style="width: 100%; max-width: 400px;">
245
+ <div class="card-body">
246
+ <h3 class="card-title text-center mb-4">Login</h3>
247
+
248
+ @if (errorMessage()) {
249
+ <div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
250
+ }
251
+
252
+ <bs-form>
253
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
254
+ <div class="mb-3">
255
+ <label for="email" class="form-label">Email</label>
256
+ <input
257
+ type="text"
258
+ id="email"
259
+ formControlName="email"
260
+ autocomplete="username"
261
+ />
262
+ </div>
263
+
264
+ <div class="mb-3">
265
+ <label for="password" class="form-label">Password</label>
266
+ <input
267
+ type="password"
268
+ id="password"
269
+ formControlName="password"
270
+ autocomplete="current-password"
271
+ />
272
+ </div>
273
+
274
+ <div class="mb-3">
275
+ <bs-toggle-button [type]="'checkbox'" formControlName="rememberMe" [name]="'rememberMe'">Remember me</bs-toggle-button>
276
+ </div>
277
+
278
+ <button
279
+ type="submit"
280
+ class="btn btn-primary w-100"
281
+ [disabled]="loading()"
282
+ >
283
+ @if (loading()) {
284
+ <span class="spinner-border spinner-border-sm me-1" role="status"></span>
285
+ }
286
+ Login
287
+ </button>
288
+ </form>
289
+ </bs-form>
290
+
291
+ <div class="mt-3 text-center">
292
+ <a [routerLink]="routePaths.register">Create an account</a>
293
+ </div>
294
+ <div class="mt-2 text-center">
295
+ <a [routerLink]="routePaths.forgotPassword">Forgot password?</a>
296
+ </div>
297
+ </div>
298
+ </div>
299
+ </div>
300
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: BsFormModule }, { kind: "component", type: i2.BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: i2.BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }, { kind: "ngmodule", type: BsToggleButtonModule }, { kind: "component", type: i3.BsToggleButtonComponent, selector: "bs-toggle-button", inputs: ["type", "isToggled", "name", "value", "group"], outputs: ["isToggledChange"] }, { kind: "directive", type: i3.BsToggleButtonValueAccessor, selector: "bs-toggle-button" }] });
301
+ }
302
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkLoginComponent, decorators: [{
303
+ type: Component,
304
+ args: [{
305
+ selector: 'spark-login',
306
+ standalone: true,
307
+ imports: [ReactiveFormsModule, RouterLink, BsFormModule, BsToggleButtonModule],
308
+ template: `
309
+ <div class="d-flex justify-content-center">
310
+ <div class="card" style="width: 100%; max-width: 400px;">
311
+ <div class="card-body">
312
+ <h3 class="card-title text-center mb-4">Login</h3>
313
+
314
+ @if (errorMessage()) {
315
+ <div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
316
+ }
317
+
318
+ <bs-form>
319
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
320
+ <div class="mb-3">
321
+ <label for="email" class="form-label">Email</label>
322
+ <input
323
+ type="text"
324
+ id="email"
325
+ formControlName="email"
326
+ autocomplete="username"
327
+ />
328
+ </div>
329
+
330
+ <div class="mb-3">
331
+ <label for="password" class="form-label">Password</label>
332
+ <input
333
+ type="password"
334
+ id="password"
335
+ formControlName="password"
336
+ autocomplete="current-password"
337
+ />
338
+ </div>
339
+
340
+ <div class="mb-3">
341
+ <bs-toggle-button [type]="'checkbox'" formControlName="rememberMe" [name]="'rememberMe'">Remember me</bs-toggle-button>
342
+ </div>
343
+
344
+ <button
345
+ type="submit"
346
+ class="btn btn-primary w-100"
347
+ [disabled]="loading()"
348
+ >
349
+ @if (loading()) {
350
+ <span class="spinner-border spinner-border-sm me-1" role="status"></span>
351
+ }
352
+ Login
353
+ </button>
354
+ </form>
355
+ </bs-form>
356
+
357
+ <div class="mt-3 text-center">
358
+ <a [routerLink]="routePaths.register">Create an account</a>
359
+ </div>
360
+ <div class="mt-2 text-center">
361
+ <a [routerLink]="routePaths.forgotPassword">Forgot password?</a>
362
+ </div>
363
+ </div>
364
+ </div>
365
+ </div>
366
+ `,
367
+ }]
368
+ }] });
369
+
370
+ var sparkLogin_component = /*#__PURE__*/Object.freeze({
371
+ __proto__: null,
372
+ SparkLoginComponent: SparkLoginComponent,
373
+ default: SparkLoginComponent
374
+ });
375
+
376
+ class SparkTwoFactorComponent {
377
+ fb = inject(FormBuilder);
378
+ authService = inject(SparkAuthService);
379
+ router = inject(Router);
380
+ route = inject(ActivatedRoute);
381
+ config = inject(SPARK_AUTH_CONFIG);
382
+ routePaths = inject(SPARK_AUTH_ROUTE_PATHS);
383
+ loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
384
+ errorMessage = signal('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
385
+ useRecoveryCode = signal(false, ...(ngDevMode ? [{ debugName: "useRecoveryCode" }] : []));
386
+ form = this.fb.group({
387
+ code: [''],
388
+ recoveryCode: [''],
389
+ });
390
+ toggleRecoveryCode() {
391
+ this.useRecoveryCode.update(v => !v);
392
+ this.errorMessage.set('');
393
+ }
394
+ onSubmit() {
395
+ const isRecovery = this.useRecoveryCode();
396
+ const code = isRecovery ? this.form.value.recoveryCode : this.form.value.code;
397
+ if (!code?.trim()) {
398
+ this.errorMessage.set(isRecovery ? 'Please enter a recovery code.' : 'Please enter the 6-digit code.');
399
+ return;
400
+ }
401
+ this.loading.set(true);
402
+ this.errorMessage.set('');
403
+ const twoFactorCode = isRecovery ? undefined : code;
404
+ const twoFactorRecoveryCode = isRecovery ? code : undefined;
405
+ this.authService.loginTwoFactor(twoFactorCode ?? '', twoFactorRecoveryCode).subscribe({
406
+ next: () => {
407
+ this.loading.set(false);
408
+ const returnUrl = this.route.snapshot.queryParamMap.get('returnUrl');
409
+ this.router.navigateByUrl(returnUrl || this.config.defaultRedirectUrl);
410
+ },
411
+ error: () => {
412
+ this.loading.set(false);
413
+ this.errorMessage.set('Invalid code. Please try again.');
414
+ },
415
+ });
416
+ }
417
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkTwoFactorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
418
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: SparkTwoFactorComponent, isStandalone: true, selector: "spark-two-factor", ngImport: i0, template: `
419
+ <div class="d-flex justify-content-center">
420
+ <div class="card" style="width: 100%; max-width: 400px;">
421
+ <div class="card-body">
422
+ <h3 class="card-title text-center mb-4">Two-Factor Authentication</h3>
423
+
424
+ @if (errorMessage()) {
425
+ <div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
426
+ }
427
+
428
+ <bs-form>
429
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
430
+ @if (!useRecoveryCode()) {
431
+ <div class="mb-3">
432
+ <label for="code" class="form-label">Authentication Code</label>
433
+ <input
434
+ type="text"
435
+ id="code"
436
+ formControlName="code"
437
+ autocomplete="one-time-code"
438
+ maxlength="6"
439
+ placeholder="Enter 6-digit code"
440
+ />
441
+ </div>
442
+ } @else {
443
+ <div class="mb-3">
444
+ <label for="recoveryCode" class="form-label">Recovery Code</label>
445
+ <input
446
+ type="text"
447
+ id="recoveryCode"
448
+ formControlName="recoveryCode"
449
+ autocomplete="off"
450
+ placeholder="Enter recovery code"
451
+ />
452
+ </div>
453
+ }
454
+
455
+ <button
456
+ type="submit"
457
+ class="btn btn-primary w-100"
458
+ [disabled]="loading()"
459
+ >
460
+ @if (loading()) {
461
+ <span class="spinner-border spinner-border-sm me-1" role="status"></span>
462
+ }
463
+ Verify
464
+ </button>
465
+ </form>
466
+ </bs-form>
467
+
468
+ <div class="mt-3 text-center">
469
+ <button class="btn btn-link" (click)="toggleRecoveryCode()">
470
+ @if (useRecoveryCode()) {
471
+ Use authentication code instead
472
+ } @else {
473
+ Use a recovery code instead
474
+ }
475
+ </button>
476
+ </div>
477
+ <div class="mt-2 text-center">
478
+ <a [routerLink]="routePaths.login">Back to login</a>
479
+ </div>
480
+ </div>
481
+ </div>
482
+ </div>
483
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: BsFormModule }, { kind: "component", type: i2.BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: i2.BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }] });
484
+ }
485
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkTwoFactorComponent, decorators: [{
486
+ type: Component,
487
+ args: [{
488
+ selector: 'spark-two-factor',
489
+ standalone: true,
490
+ imports: [ReactiveFormsModule, RouterLink, BsFormModule],
491
+ template: `
492
+ <div class="d-flex justify-content-center">
493
+ <div class="card" style="width: 100%; max-width: 400px;">
494
+ <div class="card-body">
495
+ <h3 class="card-title text-center mb-4">Two-Factor Authentication</h3>
496
+
497
+ @if (errorMessage()) {
498
+ <div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
499
+ }
500
+
501
+ <bs-form>
502
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
503
+ @if (!useRecoveryCode()) {
504
+ <div class="mb-3">
505
+ <label for="code" class="form-label">Authentication Code</label>
506
+ <input
507
+ type="text"
508
+ id="code"
509
+ formControlName="code"
510
+ autocomplete="one-time-code"
511
+ maxlength="6"
512
+ placeholder="Enter 6-digit code"
513
+ />
514
+ </div>
515
+ } @else {
516
+ <div class="mb-3">
517
+ <label for="recoveryCode" class="form-label">Recovery Code</label>
518
+ <input
519
+ type="text"
520
+ id="recoveryCode"
521
+ formControlName="recoveryCode"
522
+ autocomplete="off"
523
+ placeholder="Enter recovery code"
524
+ />
525
+ </div>
526
+ }
527
+
528
+ <button
529
+ type="submit"
530
+ class="btn btn-primary w-100"
531
+ [disabled]="loading()"
532
+ >
533
+ @if (loading()) {
534
+ <span class="spinner-border spinner-border-sm me-1" role="status"></span>
535
+ }
536
+ Verify
537
+ </button>
538
+ </form>
539
+ </bs-form>
540
+
541
+ <div class="mt-3 text-center">
542
+ <button class="btn btn-link" (click)="toggleRecoveryCode()">
543
+ @if (useRecoveryCode()) {
544
+ Use authentication code instead
545
+ } @else {
546
+ Use a recovery code instead
547
+ }
548
+ </button>
549
+ </div>
550
+ <div class="mt-2 text-center">
551
+ <a [routerLink]="routePaths.login">Back to login</a>
552
+ </div>
553
+ </div>
554
+ </div>
555
+ </div>
556
+ `,
557
+ }]
558
+ }] });
559
+
560
+ var sparkTwoFactor_component = /*#__PURE__*/Object.freeze({
561
+ __proto__: null,
562
+ SparkTwoFactorComponent: SparkTwoFactorComponent,
563
+ default: SparkTwoFactorComponent
564
+ });
565
+
566
+ function passwordMatchValidator$1(control) {
567
+ const password = control.get('password');
568
+ const confirmPassword = control.get('confirmPassword');
569
+ if (password && confirmPassword && password.value !== confirmPassword.value) {
570
+ return { passwordMismatch: true };
571
+ }
572
+ return null;
573
+ }
574
+ class SparkRegisterComponent {
575
+ fb = inject(FormBuilder);
576
+ authService = inject(SparkAuthService);
577
+ router = inject(Router);
578
+ routePaths = inject(SPARK_AUTH_ROUTE_PATHS);
579
+ loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
580
+ errorMessage = signal('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
581
+ form = this.fb.group({
582
+ email: ['', [Validators.required, Validators.email]],
583
+ password: ['', Validators.required],
584
+ confirmPassword: ['', Validators.required],
585
+ }, { validators: passwordMatchValidator$1 });
586
+ onSubmit() {
587
+ if (this.form.invalid) {
588
+ this.form.markAllAsTouched();
589
+ return;
590
+ }
591
+ this.loading.set(true);
592
+ this.errorMessage.set('');
593
+ const { email, password } = this.form.value;
594
+ this.authService.register(email, password).subscribe({
595
+ next: () => {
596
+ this.loading.set(false);
597
+ this.router.navigate([this.routePaths.login], {
598
+ queryParams: { registered: 'true' },
599
+ });
600
+ },
601
+ error: (err) => {
602
+ this.loading.set(false);
603
+ if (err.status === 400 && err.error?.errors) {
604
+ const messages = [].concat(...Object.values(err.error.errors));
605
+ this.errorMessage.set(messages.join(' '));
606
+ }
607
+ else if (err.error?.detail) {
608
+ this.errorMessage.set(err.error.detail);
609
+ }
610
+ else {
611
+ this.errorMessage.set('Registration failed. Please try again.');
612
+ }
613
+ },
614
+ });
615
+ }
616
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkRegisterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
617
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: SparkRegisterComponent, isStandalone: true, selector: "spark-register", ngImport: i0, template: `
618
+ <div class="d-flex justify-content-center">
619
+ <div class="card" style="width: 100%; max-width: 400px;">
620
+ <div class="card-body">
621
+ <h3 class="card-title text-center mb-4">Register</h3>
622
+
623
+ @if (errorMessage()) {
624
+ <div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
625
+ }
626
+
627
+ <bs-form>
628
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
629
+ <div class="mb-3">
630
+ <label for="email" class="form-label">Email</label>
631
+ <input
632
+ type="email"
633
+ id="email"
634
+ formControlName="email"
635
+ autocomplete="email"
636
+ />
637
+ @if (form.get('email')?.touched && form.get('email')?.hasError('email')) {
638
+ <div class="text-danger mt-1">Please enter a valid email address.</div>
639
+ }
640
+ </div>
641
+
642
+ <div class="mb-3">
643
+ <label for="password" class="form-label">Password</label>
644
+ <input
645
+ type="password"
646
+ id="password"
647
+ formControlName="password"
648
+ autocomplete="new-password"
649
+ />
650
+ </div>
651
+
652
+ <div class="mb-3">
653
+ <label for="confirmPassword" class="form-label">Confirm Password</label>
654
+ <input
655
+ type="password"
656
+ id="confirmPassword"
657
+ formControlName="confirmPassword"
658
+ autocomplete="new-password"
659
+ />
660
+ @if (form.touched && form.hasError('passwordMismatch')) {
661
+ <div class="text-danger mt-1">Passwords do not match.</div>
662
+ }
663
+ </div>
664
+
665
+ <button
666
+ type="submit"
667
+ class="btn btn-primary w-100"
668
+ [disabled]="loading()"
669
+ >
670
+ @if (loading()) {
671
+ <span class="spinner-border spinner-border-sm me-1" role="status"></span>
672
+ }
673
+ Register
674
+ </button>
675
+ </form>
676
+ </bs-form>
677
+
678
+ <div class="mt-3 text-center">
679
+ <span>Already have an account? </span>
680
+ <a [routerLink]="routePaths.login">Login</a>
681
+ </div>
682
+ </div>
683
+ </div>
684
+ </div>
685
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: BsFormModule }, { kind: "component", type: i2.BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: i2.BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }] });
686
+ }
687
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkRegisterComponent, decorators: [{
688
+ type: Component,
689
+ args: [{
690
+ selector: 'spark-register',
691
+ standalone: true,
692
+ imports: [ReactiveFormsModule, RouterLink, BsFormModule],
693
+ template: `
694
+ <div class="d-flex justify-content-center">
695
+ <div class="card" style="width: 100%; max-width: 400px;">
696
+ <div class="card-body">
697
+ <h3 class="card-title text-center mb-4">Register</h3>
698
+
699
+ @if (errorMessage()) {
700
+ <div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
701
+ }
702
+
703
+ <bs-form>
704
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
705
+ <div class="mb-3">
706
+ <label for="email" class="form-label">Email</label>
707
+ <input
708
+ type="email"
709
+ id="email"
710
+ formControlName="email"
711
+ autocomplete="email"
712
+ />
713
+ @if (form.get('email')?.touched && form.get('email')?.hasError('email')) {
714
+ <div class="text-danger mt-1">Please enter a valid email address.</div>
715
+ }
716
+ </div>
717
+
718
+ <div class="mb-3">
719
+ <label for="password" class="form-label">Password</label>
720
+ <input
721
+ type="password"
722
+ id="password"
723
+ formControlName="password"
724
+ autocomplete="new-password"
725
+ />
726
+ </div>
727
+
728
+ <div class="mb-3">
729
+ <label for="confirmPassword" class="form-label">Confirm Password</label>
730
+ <input
731
+ type="password"
732
+ id="confirmPassword"
733
+ formControlName="confirmPassword"
734
+ autocomplete="new-password"
735
+ />
736
+ @if (form.touched && form.hasError('passwordMismatch')) {
737
+ <div class="text-danger mt-1">Passwords do not match.</div>
738
+ }
739
+ </div>
740
+
741
+ <button
742
+ type="submit"
743
+ class="btn btn-primary w-100"
744
+ [disabled]="loading()"
745
+ >
746
+ @if (loading()) {
747
+ <span class="spinner-border spinner-border-sm me-1" role="status"></span>
748
+ }
749
+ Register
750
+ </button>
751
+ </form>
752
+ </bs-form>
753
+
754
+ <div class="mt-3 text-center">
755
+ <span>Already have an account? </span>
756
+ <a [routerLink]="routePaths.login">Login</a>
757
+ </div>
758
+ </div>
759
+ </div>
760
+ </div>
761
+ `,
762
+ }]
763
+ }] });
764
+
765
+ var sparkRegister_component = /*#__PURE__*/Object.freeze({
766
+ __proto__: null,
767
+ SparkRegisterComponent: SparkRegisterComponent,
768
+ default: SparkRegisterComponent
769
+ });
770
+
771
+ class SparkForgotPasswordComponent {
772
+ fb = inject(FormBuilder);
773
+ authService = inject(SparkAuthService);
774
+ routePaths = inject(SPARK_AUTH_ROUTE_PATHS);
775
+ loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
776
+ errorMessage = signal('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
777
+ successMessage = signal('', ...(ngDevMode ? [{ debugName: "successMessage" }] : []));
778
+ form = this.fb.group({
779
+ email: ['', [Validators.required, Validators.email]],
780
+ });
781
+ onSubmit() {
782
+ if (this.form.invalid) {
783
+ this.form.markAllAsTouched();
784
+ return;
785
+ }
786
+ this.loading.set(true);
787
+ this.errorMessage.set('');
788
+ this.successMessage.set('');
789
+ const { email } = this.form.value;
790
+ this.authService.forgotPassword(email).subscribe({
791
+ next: () => {
792
+ this.loading.set(false);
793
+ this.successMessage.set('If an account with that email exists, we\'ve sent a password reset link.');
794
+ },
795
+ error: () => {
796
+ this.loading.set(false);
797
+ // Don't reveal whether the email exists
798
+ this.successMessage.set('If an account with that email exists, we\'ve sent a password reset link.');
799
+ },
800
+ });
801
+ }
802
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkForgotPasswordComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
803
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: SparkForgotPasswordComponent, isStandalone: true, selector: "spark-forgot-password", ngImport: i0, template: `
804
+ <div class="d-flex justify-content-center">
805
+ <div class="card" style="width: 100%; max-width: 400px;">
806
+ <div class="card-body">
807
+ <h3 class="card-title text-center mb-4">Forgot Password</h3>
808
+
809
+ @if (successMessage()) {
810
+ <div class="alert alert-success" role="alert">{{ successMessage() }}</div>
811
+ }
812
+
813
+ @if (errorMessage()) {
814
+ <div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
815
+ }
816
+
817
+ @if (!successMessage()) {
818
+ <p class="text-muted mb-3">
819
+ Enter your email address and we will send you a link to reset your password.
820
+ </p>
821
+
822
+ <bs-form>
823
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
824
+ <div class="mb-3">
825
+ <label for="email" class="form-label">Email</label>
826
+ <input
827
+ type="email"
828
+ id="email"
829
+ formControlName="email"
830
+ autocomplete="email"
831
+ />
832
+ </div>
833
+
834
+ <button
835
+ type="submit"
836
+ class="btn btn-primary w-100"
837
+ [disabled]="loading()"
838
+ >
839
+ @if (loading()) {
840
+ <span class="spinner-border spinner-border-sm me-1" role="status"></span>
841
+ }
842
+ Send Reset Link
843
+ </button>
844
+ </form>
845
+ </bs-form>
846
+ }
847
+
848
+ <div class="mt-3 text-center">
849
+ <a [routerLink]="routePaths.login">Back to login</a>
850
+ </div>
851
+ </div>
852
+ </div>
853
+ </div>
854
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: BsFormModule }, { kind: "component", type: i2.BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: i2.BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }] });
855
+ }
856
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkForgotPasswordComponent, decorators: [{
857
+ type: Component,
858
+ args: [{
859
+ selector: 'spark-forgot-password',
860
+ standalone: true,
861
+ imports: [ReactiveFormsModule, RouterLink, BsFormModule],
862
+ template: `
863
+ <div class="d-flex justify-content-center">
864
+ <div class="card" style="width: 100%; max-width: 400px;">
865
+ <div class="card-body">
866
+ <h3 class="card-title text-center mb-4">Forgot Password</h3>
867
+
868
+ @if (successMessage()) {
869
+ <div class="alert alert-success" role="alert">{{ successMessage() }}</div>
870
+ }
871
+
872
+ @if (errorMessage()) {
873
+ <div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
874
+ }
875
+
876
+ @if (!successMessage()) {
877
+ <p class="text-muted mb-3">
878
+ Enter your email address and we will send you a link to reset your password.
879
+ </p>
880
+
881
+ <bs-form>
882
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
883
+ <div class="mb-3">
884
+ <label for="email" class="form-label">Email</label>
885
+ <input
886
+ type="email"
887
+ id="email"
888
+ formControlName="email"
889
+ autocomplete="email"
890
+ />
891
+ </div>
892
+
893
+ <button
894
+ type="submit"
895
+ class="btn btn-primary w-100"
896
+ [disabled]="loading()"
897
+ >
898
+ @if (loading()) {
899
+ <span class="spinner-border spinner-border-sm me-1" role="status"></span>
900
+ }
901
+ Send Reset Link
902
+ </button>
903
+ </form>
904
+ </bs-form>
905
+ }
906
+
907
+ <div class="mt-3 text-center">
908
+ <a [routerLink]="routePaths.login">Back to login</a>
909
+ </div>
910
+ </div>
911
+ </div>
912
+ </div>
913
+ `,
914
+ }]
915
+ }] });
916
+
917
+ var sparkForgotPassword_component = /*#__PURE__*/Object.freeze({
918
+ __proto__: null,
919
+ SparkForgotPasswordComponent: SparkForgotPasswordComponent,
920
+ default: SparkForgotPasswordComponent
921
+ });
922
+
923
+ function passwordMatchValidator(control) {
924
+ const password = control.get('newPassword');
925
+ const confirmPassword = control.get('confirmPassword');
926
+ if (password && confirmPassword && password.value !== confirmPassword.value) {
927
+ return { passwordMismatch: true };
928
+ }
929
+ return null;
930
+ }
931
+ class SparkResetPasswordComponent {
932
+ fb = inject(FormBuilder);
933
+ authService = inject(SparkAuthService);
934
+ router = inject(Router);
935
+ route = inject(ActivatedRoute);
936
+ routePaths = inject(SPARK_AUTH_ROUTE_PATHS);
937
+ loading = signal(false, ...(ngDevMode ? [{ debugName: "loading" }] : []));
938
+ errorMessage = signal('', ...(ngDevMode ? [{ debugName: "errorMessage" }] : []));
939
+ successMessage = signal('', ...(ngDevMode ? [{ debugName: "successMessage" }] : []));
940
+ email = '';
941
+ code = '';
942
+ form = this.fb.group({
943
+ newPassword: ['', Validators.required],
944
+ confirmPassword: ['', Validators.required],
945
+ }, { validators: passwordMatchValidator });
946
+ ngOnInit() {
947
+ this.email = this.route.snapshot.queryParamMap.get('email') ?? '';
948
+ this.code = this.route.snapshot.queryParamMap.get('code') ?? '';
949
+ if (!this.email || !this.code) {
950
+ this.errorMessage.set('Invalid password reset link. Please request a new one.');
951
+ }
952
+ }
953
+ onSubmit() {
954
+ if (this.form.invalid) {
955
+ this.form.markAllAsTouched();
956
+ return;
957
+ }
958
+ if (!this.email || !this.code) {
959
+ this.errorMessage.set('Invalid password reset link. Please request a new one.');
960
+ return;
961
+ }
962
+ this.loading.set(true);
963
+ this.errorMessage.set('');
964
+ this.successMessage.set('');
965
+ const { newPassword } = this.form.value;
966
+ this.authService.resetPassword(this.email, this.code, newPassword).subscribe({
967
+ next: () => {
968
+ this.loading.set(false);
969
+ this.successMessage.set('Your password has been reset successfully.');
970
+ },
971
+ error: (err) => {
972
+ this.loading.set(false);
973
+ if (err.error?.detail) {
974
+ this.errorMessage.set(err.error.detail);
975
+ }
976
+ else {
977
+ this.errorMessage.set('Failed to reset password. The link may have expired.');
978
+ }
979
+ },
980
+ });
981
+ }
982
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkResetPasswordComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
983
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.5", type: SparkResetPasswordComponent, isStandalone: true, selector: "spark-reset-password", ngImport: i0, template: `
984
+ <div class="d-flex justify-content-center">
985
+ <div class="card" style="width: 100%; max-width: 400px;">
986
+ <div class="card-body">
987
+ <h3 class="card-title text-center mb-4">Reset Password</h3>
988
+
989
+ @if (successMessage()) {
990
+ <div class="alert alert-success" role="alert">
991
+ {{ successMessage() }}
992
+ <div class="mt-2">
993
+ <a [routerLink]="routePaths.login">Go to login</a>
994
+ </div>
995
+ </div>
996
+ }
997
+
998
+ @if (errorMessage()) {
999
+ <div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
1000
+ }
1001
+
1002
+ @if (!successMessage()) {
1003
+ <bs-form>
1004
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
1005
+ <div class="mb-3">
1006
+ <label for="newPassword" class="form-label">New Password</label>
1007
+ <input
1008
+ type="password"
1009
+ id="newPassword"
1010
+ formControlName="newPassword"
1011
+ autocomplete="new-password"
1012
+ />
1013
+ </div>
1014
+
1015
+ <div class="mb-3">
1016
+ <label for="confirmPassword" class="form-label">Confirm Password</label>
1017
+ <input
1018
+ type="password"
1019
+ id="confirmPassword"
1020
+ formControlName="confirmPassword"
1021
+ autocomplete="new-password"
1022
+ />
1023
+ @if (form.touched && form.hasError('passwordMismatch')) {
1024
+ <div class="text-danger mt-1">Passwords do not match.</div>
1025
+ }
1026
+ </div>
1027
+
1028
+ <button
1029
+ type="submit"
1030
+ class="btn btn-primary w-100"
1031
+ [disabled]="loading()"
1032
+ >
1033
+ @if (loading()) {
1034
+ <span class="spinner-border spinner-border-sm me-1" role="status"></span>
1035
+ }
1036
+ Reset Password
1037
+ </button>
1038
+ </form>
1039
+ </bs-form>
1040
+
1041
+ <div class="mt-3 text-center">
1042
+ <a [routerLink]="routePaths.login">Back to login</a>
1043
+ </div>
1044
+ }
1045
+ </div>
1046
+ </div>
1047
+ </div>
1048
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "directive", type: RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: BsFormModule }, { kind: "component", type: i2.BsFormComponent, selector: "bs-form", inputs: ["action", "method"], outputs: ["submitted"] }, { kind: "directive", type: i2.BsFormControlDirective, selector: "bs-form input:not(.no-form-control), bs-form textarea:not(.no-form-control)" }] });
1049
+ }
1050
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: SparkResetPasswordComponent, decorators: [{
1051
+ type: Component,
1052
+ args: [{
1053
+ selector: 'spark-reset-password',
1054
+ standalone: true,
1055
+ imports: [ReactiveFormsModule, RouterLink, BsFormModule],
1056
+ template: `
1057
+ <div class="d-flex justify-content-center">
1058
+ <div class="card" style="width: 100%; max-width: 400px;">
1059
+ <div class="card-body">
1060
+ <h3 class="card-title text-center mb-4">Reset Password</h3>
1061
+
1062
+ @if (successMessage()) {
1063
+ <div class="alert alert-success" role="alert">
1064
+ {{ successMessage() }}
1065
+ <div class="mt-2">
1066
+ <a [routerLink]="routePaths.login">Go to login</a>
1067
+ </div>
1068
+ </div>
1069
+ }
1070
+
1071
+ @if (errorMessage()) {
1072
+ <div class="alert alert-danger" role="alert">{{ errorMessage() }}</div>
1073
+ }
1074
+
1075
+ @if (!successMessage()) {
1076
+ <bs-form>
1077
+ <form [formGroup]="form" (ngSubmit)="onSubmit()">
1078
+ <div class="mb-3">
1079
+ <label for="newPassword" class="form-label">New Password</label>
1080
+ <input
1081
+ type="password"
1082
+ id="newPassword"
1083
+ formControlName="newPassword"
1084
+ autocomplete="new-password"
1085
+ />
1086
+ </div>
1087
+
1088
+ <div class="mb-3">
1089
+ <label for="confirmPassword" class="form-label">Confirm Password</label>
1090
+ <input
1091
+ type="password"
1092
+ id="confirmPassword"
1093
+ formControlName="confirmPassword"
1094
+ autocomplete="new-password"
1095
+ />
1096
+ @if (form.touched && form.hasError('passwordMismatch')) {
1097
+ <div class="text-danger mt-1">Passwords do not match.</div>
1098
+ }
1099
+ </div>
1100
+
1101
+ <button
1102
+ type="submit"
1103
+ class="btn btn-primary w-100"
1104
+ [disabled]="loading()"
1105
+ >
1106
+ @if (loading()) {
1107
+ <span class="spinner-border spinner-border-sm me-1" role="status"></span>
1108
+ }
1109
+ Reset Password
1110
+ </button>
1111
+ </form>
1112
+ </bs-form>
1113
+
1114
+ <div class="mt-3 text-center">
1115
+ <a [routerLink]="routePaths.login">Back to login</a>
1116
+ </div>
1117
+ }
1118
+ </div>
1119
+ </div>
1120
+ </div>
1121
+ `,
1122
+ }]
1123
+ }] });
1124
+
1125
+ var sparkResetPassword_component = /*#__PURE__*/Object.freeze({
1126
+ __proto__: null,
1127
+ SparkResetPasswordComponent: SparkResetPasswordComponent,
1128
+ default: SparkResetPasswordComponent
1129
+ });
1130
+
1131
+ /**
1132
+ * Generated bundle index. Do not edit.
1133
+ */
1134
+
1135
+ export { SPARK_AUTH_CONFIG, SPARK_AUTH_ROUTE_PATHS, SparkAuthBarComponent, SparkAuthService, SparkForgotPasswordComponent, SparkLoginComponent, SparkRegisterComponent, SparkResetPasswordComponent, SparkTwoFactorComponent, defaultSparkAuthConfig, provideSparkAuth, sparkAuthGuard, sparkAuthInterceptor, sparkAuthRoutes, withSparkAuth };
1136
+ //# sourceMappingURL=mintplayer-ng-spark-auth.mjs.map