@acontplus/ng-auth 1.0.2 → 1.1.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.
@@ -1,15 +1,15 @@
1
- import { UserRepository } from '@acontplus/ng-infrastructure';
1
+ import { UserRepository, BaseUseCase } from '@acontplus/ng-infrastructure';
2
2
  export { TOKEN_PROVIDER } from '@acontplus/ng-infrastructure';
3
3
  import * as i0 from '@angular/core';
4
4
  import { inject, PLATFORM_ID, Injectable, signal, input, computed, ChangeDetectionStrategy, Component } from '@angular/core';
5
5
  import { Router } from '@angular/router';
6
6
  import * as i1 from '@angular/common';
7
- import { isPlatformBrowser, CommonModule } from '@angular/common';
7
+ import { isPlatformBrowser, DOCUMENT, CommonModule } from '@angular/common';
8
8
  import { jwtDecode } from 'jwt-decode';
9
9
  import { ENVIRONMENT, AUTH_API } from '@acontplus/ng-config';
10
+ import { catchError, switchMap } from 'rxjs/operators';
11
+ import { throwError, from, map, of, tap, catchError as catchError$1 } from 'rxjs';
10
12
  import { HttpClient } from '@angular/common/http';
11
- import { from, map, of, tap, catchError, throwError } from 'rxjs';
12
- import { switchMap } from 'rxjs/operators';
13
13
  import { AuthTokens } from '@acontplus/core';
14
14
  import * as i2 from '@angular/forms';
15
15
  import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
@@ -18,6 +18,7 @@ import { MatLabel, MatFormField } from '@angular/material/form-field';
18
18
  import { MatInput } from '@angular/material/input';
19
19
  import { MatIcon } from '@angular/material/icon';
20
20
  import { MatButton, MatAnchor } from '@angular/material/button';
21
+ import { MatCheckbox } from '@angular/material/checkbox';
21
22
 
22
23
  class TokenRepository {
23
24
  environment = inject(ENVIRONMENT);
@@ -36,10 +37,11 @@ class TokenRepository {
36
37
  sessionStorage.getItem(this.environment.tokenKey));
37
38
  }
38
39
  getRefreshToken() {
39
- // Refresh tokens are now stored in HttpOnly cookies by the server
40
- // We can't read them directly from client-side code for security
41
- // They are automatically sent with requests when withCredentials: true is used
42
- return null;
40
+ if (!isPlatformBrowser(this.platformId)) {
41
+ return null;
42
+ }
43
+ return (localStorage.getItem(this.environment.refreshTokenKey) ||
44
+ sessionStorage.getItem(this.environment.refreshTokenKey));
43
45
  }
44
46
  setToken(token, rememberMe = false) {
45
47
  if (!isPlatformBrowser(this.platformId)) {
@@ -52,10 +54,16 @@ class TokenRepository {
52
54
  sessionStorage.setItem(this.environment.tokenKey, token);
53
55
  }
54
56
  }
55
- setRefreshToken(_refreshToken, _rememberMe = false) {
56
- // Refresh tokens are now set by the server as HttpOnly cookies
57
- // Client-side code cannot set HttpOnly cookies for security reasons
58
- // They are set in response to successful login/register requests
57
+ setRefreshToken(refreshToken, rememberMe = false) {
58
+ if (!isPlatformBrowser(this.platformId)) {
59
+ return;
60
+ }
61
+ if (rememberMe) {
62
+ localStorage.setItem(this.environment.refreshTokenKey, refreshToken);
63
+ }
64
+ else {
65
+ sessionStorage.setItem(this.environment.refreshTokenKey, refreshToken);
66
+ }
59
67
  }
60
68
  clearTokens() {
61
69
  if (!isPlatformBrowser(this.platformId)) {
@@ -108,6 +116,18 @@ class TokenRepository {
108
116
  return null;
109
117
  }
110
118
  }
119
+ /**
120
+ * Determines if tokens are stored persistently (localStorage) vs session (sessionStorage)
121
+ */
122
+ isRememberMeEnabled() {
123
+ if (!isPlatformBrowser(this.platformId)) {
124
+ return false;
125
+ }
126
+ // Check if tokens exist in localStorage (persistent storage)
127
+ const tokenInLocalStorage = localStorage.getItem(this.environment.tokenKey);
128
+ const refreshTokenInLocalStorage = localStorage.getItem(this.environment.refreshTokenKey);
129
+ return !!(tokenInLocalStorage || refreshTokenInLocalStorage);
130
+ }
111
131
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: TokenRepository, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
112
132
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: TokenRepository, providedIn: 'root' });
113
133
  }
@@ -137,59 +157,160 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
137
157
  args: [{ providedIn: 'root' }]
138
158
  }], ctorParameters: () => [{ type: TokenRepository }] });
139
159
 
140
- const authGuard = (_route, _state) => {
141
- const authService = inject(AuthTokenService);
142
- const router = inject(Router);
143
- const environment = inject(ENVIRONMENT);
144
- if (authService.isAuthenticated()) {
145
- return true;
160
+ /**
161
+ * Service to manage URL redirection after authentication
162
+ * Stores the intended URL when session is lost and redirects to it after successful login
163
+ * SSR-compatible by checking platform before accessing sessionStorage
164
+ */
165
+ class UrlRedirectService {
166
+ REDIRECT_URL_KEY = 'acp_redirect_url';
167
+ EXCLUDED_ROUTES = ['/login', '/register', '/forgot-password', '/reset-password'];
168
+ router = inject(Router);
169
+ platformId = inject(PLATFORM_ID);
170
+ document = inject(DOCUMENT);
171
+ /**
172
+ * Stores the current URL for later redirection
173
+ * @param url - The URL to store (defaults to current URL)
174
+ */
175
+ storeIntendedUrl(url) {
176
+ // Only store in browser environment
177
+ if (!this.isBrowser()) {
178
+ return;
179
+ }
180
+ const urlToStore = url || this.router.url;
181
+ // Don't store authentication-related routes
182
+ if (this.isExcludedRoute(urlToStore)) {
183
+ return;
184
+ }
185
+ // Don't store URLs with query parameters that might contain sensitive data
186
+ const urlWithoutParams = urlToStore.split('?')[0];
187
+ this.getSessionStorage()?.setItem(this.REDIRECT_URL_KEY, urlWithoutParams);
146
188
  }
147
- // Redirect to login page (configurable via environment)
148
- router.navigate([environment.loginRoute]);
149
- return false;
150
- };
151
-
152
- // src/lib/services/csrf.service.ts
153
- class CsrfService {
154
- http = inject(HttpClient);
155
- environment = inject(ENVIRONMENT);
156
- csrfToken = null;
157
189
  /**
158
- * Get CSRF token, fetching it if not available
190
+ * Gets the stored intended URL
191
+ * @returns The stored URL or null if none exists
159
192
  */
160
- async getCsrfToken() {
161
- if (this.csrfToken) {
162
- return this.csrfToken;
193
+ getIntendedUrl() {
194
+ if (!this.isBrowser()) {
195
+ return null;
163
196
  }
164
- try {
165
- const response = await this.http
166
- .get(`${this.environment.apiBaseUrl}/csrf-token`)
167
- .toPromise();
168
- this.csrfToken = response?.csrfToken || null;
169
- return this.csrfToken || '';
197
+ return this.getSessionStorage()?.getItem(this.REDIRECT_URL_KEY) || null;
198
+ }
199
+ /**
200
+ * Redirects to the stored URL and clears it from storage
201
+ * @param defaultRoute - The default route to navigate to if no URL is stored
202
+ */
203
+ redirectToIntendedUrl(defaultRoute = '/') {
204
+ const intendedUrl = this.getIntendedUrl();
205
+ if (intendedUrl && !this.isExcludedRoute(intendedUrl)) {
206
+ this.clearIntendedUrl();
207
+ this.router.navigateByUrl(intendedUrl);
170
208
  }
171
- catch {
172
- // If CSRF endpoint fails, return empty token
173
- // Server should handle missing CSRF tokens appropriately
174
- return '';
209
+ else {
210
+ this.router.navigate([defaultRoute]);
175
211
  }
176
212
  }
177
213
  /**
178
- * Clear stored CSRF token (useful on logout)
214
+ * Clears the stored intended URL
179
215
  */
180
- clearCsrfToken() {
181
- this.csrfToken = null;
216
+ clearIntendedUrl() {
217
+ if (!this.isBrowser()) {
218
+ return;
219
+ }
220
+ this.getSessionStorage()?.removeItem(this.REDIRECT_URL_KEY);
182
221
  }
183
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
184
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, providedIn: 'root' });
222
+ /**
223
+ * Checks if a URL should be excluded from redirection
224
+ * @param url - The URL to check
225
+ * @returns True if the URL should be excluded
226
+ */
227
+ isExcludedRoute(url) {
228
+ return this.EXCLUDED_ROUTES.some(route => url.includes(route));
229
+ }
230
+ /**
231
+ * Stores the current URL if it's not an excluded route
232
+ * Useful for guards and interceptors
233
+ */
234
+ storeCurrentUrlIfAllowed() {
235
+ const currentUrl = this.router.url;
236
+ if (!this.isExcludedRoute(currentUrl)) {
237
+ this.storeIntendedUrl(currentUrl);
238
+ }
239
+ }
240
+ /**
241
+ * Checks if we're running in a browser environment
242
+ * @returns True if running in browser, false if SSR
243
+ */
244
+ isBrowser() {
245
+ return isPlatformBrowser(this.platformId);
246
+ }
247
+ /**
248
+ * Safely gets sessionStorage reference
249
+ * @returns sessionStorage object or null if not available
250
+ */
251
+ getSessionStorage() {
252
+ if (!this.isBrowser()) {
253
+ return null;
254
+ }
255
+ try {
256
+ return this.document.defaultView?.sessionStorage || null;
257
+ }
258
+ catch {
259
+ // Handle cases where sessionStorage might be disabled
260
+ return null;
261
+ }
262
+ }
263
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: UrlRedirectService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
264
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: UrlRedirectService, providedIn: 'root' });
185
265
  }
186
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, decorators: [{
266
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: UrlRedirectService, decorators: [{
187
267
  type: Injectable,
188
268
  args: [{
189
269
  providedIn: 'root',
190
270
  }]
191
271
  }] });
192
272
 
273
+ const authGuard = (_route, state) => {
274
+ const authService = inject(AuthTokenService);
275
+ const router = inject(Router);
276
+ const urlRedirectService = inject(UrlRedirectService);
277
+ const environment = inject(ENVIRONMENT);
278
+ if (authService.isAuthenticated()) {
279
+ return true;
280
+ }
281
+ // Store the current URL for redirection after login
282
+ urlRedirectService.storeIntendedUrl(state.url);
283
+ // Redirect to login page (configurable via environment)
284
+ router.navigate([environment.loginRoute]);
285
+ return false;
286
+ };
287
+
288
+ /**
289
+ * Interceptor that handles authentication errors and manages URL redirection
290
+ * Captures the current URL when a 401 error occurs and redirects to login
291
+ */
292
+ const authRedirectInterceptor = (req, next) => {
293
+ const router = inject(Router);
294
+ const urlRedirectService = inject(UrlRedirectService);
295
+ const authTokenService = inject(AuthTokenService);
296
+ const environment = inject(ENVIRONMENT);
297
+ return next(req).pipe(catchError((error) => {
298
+ // Handle 401 Unauthorized errors
299
+ if (error.status === 401) {
300
+ // Only store and redirect if user was previously authenticated
301
+ // This prevents redirect loops and handles session expiry scenarios
302
+ if (authTokenService.isAuthenticated()) {
303
+ // Store the current URL for redirection after re-authentication
304
+ urlRedirectService.storeCurrentUrlIfAllowed();
305
+ // Navigate to login page
306
+ router.navigate([environment.loginRoute]);
307
+ }
308
+ }
309
+ // Re-throw the error so other error handlers can process it
310
+ return throwError(() => error);
311
+ }));
312
+ };
313
+
193
314
  class User {
194
315
  id;
195
316
  email;
@@ -225,6 +346,47 @@ class AuthRepository {
225
346
 
226
347
  // src/lib/domain/index.ts
227
348
 
349
+ // src/lib/services/csrf.service.ts
350
+ class CsrfService {
351
+ http = inject(HttpClient);
352
+ environment = inject(ENVIRONMENT);
353
+ csrfToken = null;
354
+ /**
355
+ * Get CSRF token, fetching it if not available
356
+ */
357
+ async getCsrfToken() {
358
+ if (this.csrfToken) {
359
+ return this.csrfToken;
360
+ }
361
+ try {
362
+ const response = await this.http
363
+ .get(`${this.environment.apiBaseUrl}/csrf-token`)
364
+ .toPromise();
365
+ this.csrfToken = response?.csrfToken || null;
366
+ return this.csrfToken || '';
367
+ }
368
+ catch {
369
+ // If CSRF endpoint fails, return empty token
370
+ // Server should handle missing CSRF tokens appropriately
371
+ return '';
372
+ }
373
+ }
374
+ /**
375
+ * Clear stored CSRF token (useful on logout)
376
+ */
377
+ clearCsrfToken() {
378
+ this.csrfToken = null;
379
+ }
380
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
381
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, providedIn: 'root' });
382
+ }
383
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: CsrfService, decorators: [{
384
+ type: Injectable,
385
+ args: [{
386
+ providedIn: 'root',
387
+ }]
388
+ }] });
389
+
228
390
  // src/lib/data/repositories/auth-http.repository.ts
229
391
  function getDeviceInfo() {
230
392
  return `${navigator.platform ?? 'Unknown'} - ${navigator.userAgent}`;
@@ -251,16 +413,7 @@ class AuthHttpRepository extends AuthRepository {
251
413
  },
252
414
  withCredentials: true,
253
415
  })
254
- .pipe(map(response => {
255
- // Convert string ID to number - if response.id is a string, hash it to get a number
256
- const numericId = response.id !== ''
257
- ? Math.abs(response.id.split('').reduce((a, b) => {
258
- a = (a << 5) - a + b.charCodeAt(0);
259
- return a & a;
260
- }, 0))
261
- : Date.now();
262
- return new User(numericId, request.email, request.displayName);
263
- }))));
416
+ .pipe(map(response => new AuthTokens(response.token, response.refreshToken)))));
264
417
  }
265
418
  refreshToken(request) {
266
419
  return from(this.csrfService.getCsrfToken()).pipe(switchMap(csrfToken => this.http
@@ -393,7 +546,7 @@ class AuthStore {
393
546
  })
394
547
  .pipe(tap(tokens => {
395
548
  this.setAuthenticated(tokens);
396
- }), catchError(() => {
549
+ }), catchError$1(() => {
397
550
  this.logout();
398
551
  return of(null);
399
552
  }), tap({
@@ -409,8 +562,8 @@ class AuthStore {
409
562
  /**
410
563
  * Set authentication state after successful login
411
564
  */
412
- setAuthenticated(tokens) {
413
- this.tokenRepository.saveTokens(tokens);
565
+ setAuthenticated(tokens, rememberMe = false) {
566
+ this.tokenRepository.saveTokens(tokens, rememberMe);
414
567
  this._isAuthenticated.set(true);
415
568
  const userData = this.userRepository.getCurrentUser();
416
569
  this._user.set(userData);
@@ -497,19 +650,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
497
650
  }], ctorParameters: () => [] });
498
651
 
499
652
  // src/lib/application/use-cases/login.use-case.ts
500
- class LoginUseCase {
653
+ class LoginUseCase extends BaseUseCase {
501
654
  authRepository = inject(AuthRepository);
502
655
  authStore = inject(AuthStore);
503
656
  router = inject(Router);
657
+ urlRedirectService = inject(UrlRedirectService);
504
658
  execute(request) {
505
659
  return this.authRepository.login(request).pipe(tap(tokens => {
506
- // Set authentication state
507
- this.authStore.setAuthenticated(tokens);
508
- // Navigate to main page
509
- this.router.navigate(['/']);
660
+ // Set authentication state with rememberMe preference
661
+ this.authStore.setAuthenticated(tokens, request.rememberMe ?? false);
662
+ // Redirect to intended URL or default route
663
+ this.urlRedirectService.redirectToIntendedUrl('/');
510
664
  }));
511
665
  }
512
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LoginUseCase, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
666
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LoginUseCase, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
513
667
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LoginUseCase, providedIn: 'root' });
514
668
  }
515
669
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LoginUseCase, decorators: [{
@@ -520,15 +674,19 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
520
674
  }] });
521
675
 
522
676
  // src/lib/application/use-cases/register.use-case.ts
523
- class RegisterUseCase {
677
+ class RegisterUseCase extends BaseUseCase {
524
678
  authRepository = inject(AuthRepository);
679
+ authStore = inject(AuthStore);
525
680
  router = inject(Router);
526
681
  execute(request) {
527
- return this.authRepository.register(request).pipe(tap(() => {
528
- this.router.navigate(['/', 'auth']);
682
+ return this.authRepository.register(request).pipe(tap(tokens => {
683
+ // Set authentication state
684
+ this.authStore.setAuthenticated(tokens);
685
+ // Navigate to main page
686
+ this.router.navigate(['/']);
529
687
  }));
530
688
  }
531
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RegisterUseCase, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
689
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RegisterUseCase, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
532
690
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RegisterUseCase, providedIn: 'root' });
533
691
  }
534
692
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RegisterUseCase, decorators: [{
@@ -539,7 +697,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
539
697
  }] });
540
698
 
541
699
  // src/lib/application/use-cases/refresh-token.use-case.ts
542
- class RefreshTokenUseCase {
700
+ class RefreshTokenUseCase extends BaseUseCase {
543
701
  authRepository = inject(AuthRepository);
544
702
  userRepository = inject(UserRepository);
545
703
  tokenRepository = inject(TokenRepository);
@@ -557,14 +715,16 @@ class RefreshTokenUseCase {
557
715
  refreshToken,
558
716
  })
559
717
  .pipe(tap(tokens => {
718
+ // Preserve the rememberMe preference from the current token storage
719
+ const rememberMe = this.tokenRepository.isRememberMeEnabled();
560
720
  // Update authentication state
561
- this.authStore.setAuthenticated(tokens);
562
- }), catchError(error => {
721
+ this.authStore.setAuthenticated(tokens, rememberMe);
722
+ }), catchError$1(error => {
563
723
  // Don't logout here, let the interceptor handle it
564
724
  return throwError(() => error);
565
725
  }));
566
726
  }
567
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RefreshTokenUseCase, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
727
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RefreshTokenUseCase, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
568
728
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RefreshTokenUseCase, providedIn: 'root' });
569
729
  }
570
730
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: RefreshTokenUseCase, decorators: [{
@@ -575,7 +735,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
575
735
  }] });
576
736
 
577
737
  // src/lib/application/use-cases/logout.use-case.ts
578
- class LogoutUseCase {
738
+ class LogoutUseCase extends BaseUseCase {
579
739
  authRepository = inject(AuthRepository);
580
740
  userRepository = inject(UserRepository);
581
741
  tokenRepository = inject(TokenRepository);
@@ -593,7 +753,7 @@ class LogoutUseCase {
593
753
  // Use auth store for centralized logout
594
754
  this.authStore.logout();
595
755
  }
596
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LogoutUseCase, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
756
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LogoutUseCase, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
597
757
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LogoutUseCase, providedIn: 'root' });
598
758
  }
599
759
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LogoutUseCase, decorators: [{
@@ -613,6 +773,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
613
773
  class LoginComponent {
614
774
  title = input('Login', ...(ngDevMode ? [{ debugName: "title" }] : []));
615
775
  showRegisterButton = input(true, ...(ngDevMode ? [{ debugName: "showRegisterButton" }] : []));
776
+ showRememberMe = input(true, ...(ngDevMode ? [{ debugName: "showRememberMe" }] : []));
616
777
  // Additional form controls that can be passed from parent components
617
778
  additionalSigninControls = input({}, ...(ngDevMode ? [{ debugName: "additionalSigninControls" }] : []));
618
779
  additionalSignupControls = input({}, ...(ngDevMode ? [{ debugName: "additionalSignupControls" }] : []));
@@ -636,6 +797,7 @@ class LoginComponent {
636
797
  this.signinForm = this.fb.group({
637
798
  email: ['', [Validators.required, Validators.email]],
638
799
  password: ['', Validators.required],
800
+ rememberMe: [false], // Default to false (unchecked)
639
801
  });
640
802
  this.signupForm = this.fb.group({
641
803
  displayName: ['', Validators.required],
@@ -644,6 +806,11 @@ class LoginComponent {
644
806
  });
645
807
  }
646
808
  ngOnInit() {
809
+ // Handle rememberMe control based on showRememberMe input
810
+ if (!this.showRememberMe()) {
811
+ // Remove rememberMe control if not needed
812
+ this.signinForm.removeControl('rememberMe');
813
+ }
647
814
  // Add additional controls to signin form
648
815
  Object.entries(this.additionalSigninControls()).forEach(([key, control]) => {
649
816
  this.signinForm.addControl(key, control);
@@ -661,7 +828,13 @@ class LoginComponent {
661
828
  if (this.signinForm.valid) {
662
829
  this.isLoading.set(true);
663
830
  this.errorMessage.set(null);
664
- this.loginUseCase.execute(this.signinForm.value).subscribe({
831
+ // Prepare login request with rememberMe handling
832
+ const loginRequest = {
833
+ ...this.signinForm.value,
834
+ // If showRememberMe is false, default rememberMe to false
835
+ rememberMe: this.showRememberMe() ? (this.signinForm.value.rememberMe ?? false) : false
836
+ };
837
+ this.loginUseCase.execute(loginRequest).subscribe({
665
838
  next: () => {
666
839
  this.isLoading.set(false);
667
840
  },
@@ -692,7 +865,7 @@ class LoginComponent {
692
865
  }
693
866
  }
694
867
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LoginComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
695
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.2", type: LoginComponent, isStandalone: true, selector: "acp-login", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, showRegisterButton: { classPropertyName: "showRegisterButton", publicName: "showRegisterButton", isSignal: true, isRequired: false, transformFunction: null }, additionalSigninControls: { classPropertyName: "additionalSigninControls", publicName: "additionalSigninControls", isSignal: true, isRequired: false, transformFunction: null }, additionalSignupControls: { classPropertyName: "additionalSignupControls", publicName: "additionalSignupControls", isSignal: true, isRequired: false, transformFunction: null }, additionalSigninFields: { classPropertyName: "additionalSigninFields", publicName: "additionalSigninFields", isSignal: true, isRequired: false, transformFunction: null }, additionalSignupFields: { classPropertyName: "additionalSignupFields", publicName: "additionalSignupFields", isSignal: true, isRequired: false, transformFunction: null }, footerContent: { classPropertyName: "footerContent", publicName: "footerContent", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<section id=\"wrapper\" class=\"d-flex justify-content-center align-items-center\">\n <mat-card class=\"mat-elevation-z8 p-4 rounded\">\n <mat-card-header>\n <mat-card-title class=\"text-center\">{{ title() }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n @if (isLoginMode()) {\n <form [formGroup]=\"signinForm\" (ngSubmit)=\"signIn()\" class=\"d-flex flex-column gap-3\">\n <mat-form-field class=\"w-100\">\n <mat-label>Usuario</mat-label>\n <input matInput type=\"text\" placeholder=\"Ingrese su usuario\" formControlName=\"email\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Contrase\u00F1a</mat-label>\n <input matInput type=\"password\" placeholder=\"Ingrese su contrase\u00F1a\" formControlName=\"password\" />\n </mat-form-field>\n\n <!-- Additional signin fields -->\n <ng-container *ngTemplateOutlet=\"additionalSigninFields()\"></ng-container>\n\n <div class=\"d-flex justify-content-center mt-3\">\n <button mat-raised-button color=\"primary\" [disabled]=\"!signinForm.valid || isLoading()\" type=\"submit\"\n class=\"w-100\">\n @if (isLoading()) {\n Ingresando...\n } @else {\n <ng-container>Ingresar <mat-icon>login</mat-icon></ng-container>\n }\n </button>\n </div>\n\n <div class=\"text-center mt-2\">\n @if (showRegisterButton()) {\n <button mat-button type=\"button\" (click)=\"switchMode()\">\n \u00BFNo tienes cuenta? Reg\u00EDstrate\n </button>\n }\n </div>\n </form>\n } @else {\n <form [formGroup]=\"signupForm\" (ngSubmit)=\"registerUser()\" class=\"d-flex flex-column gap-3\">\n <mat-form-field class=\"w-100\">\n <mat-label>Nombre</mat-label>\n <input matInput type=\"text\" placeholder=\"Ingrese su nombre\" formControlName=\"displayName\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Email</mat-label>\n <input matInput type=\"email\" placeholder=\"Ingrese su email\" formControlName=\"email\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Contrase\u00F1a</mat-label>\n <input matInput type=\"password\" placeholder=\"Ingrese su contrase\u00F1a\" formControlName=\"password\" />\n </mat-form-field>\n\n <!-- Additional signup fields -->\n <ng-container *ngTemplateOutlet=\"additionalSignupFields()\"></ng-container>\n\n <div class=\"d-flex justify-content-center mt-3\">\n <button mat-raised-button color=\"primary\" [disabled]=\"!signupForm.valid || isLoading()\" type=\"submit\"\n class=\"w-100\">\n @if (isLoading()) {\n Registrando...\n } @else {\n <ng-container>Registrarse <mat-icon>person_add</mat-icon></ng-container>\n }\n </button>\n </div>\n\n <div class=\"text-center mt-2\">\n <button mat-button type=\"button\" (click)=\"switchMode()\">\u00BFYa tienes cuenta? Inicia sesi\u00F3n</button>\n </div>\n </form>\n }\n @if (errorMessage()) {\n <div class=\"alert alert-danger mt-3\" role=\"alert\">\n {{ errorMessage() }}\n </div>\n }\n </mat-card-content>\n @if (hasFooterContent()) {\n <mat-card-footer>\n <ng-container *ngTemplateOutlet=\"footerContent()\"></ng-container>\n </mat-card-footer>\n }\n </mat-card>\n</section>\n", styles: ["#wrapper{min-height:100vh;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,var(--mdc-theme-primary, #5c5f5c) 0%,var(--mdc-theme-secondary, #79747e) 100%);padding:20px}mat-card{width:100%;max-width:400px;border-radius:12px;box-shadow:0 8px 32px #0000001a}mat-card-header{text-align:center;margin-bottom:20px}mat-card-title{font-size:24px;font-weight:600;color:var(--mdc-theme-on-surface, #1c1b1f)}mat-form-field{width:100%}.w-100{width:100%}.d-flex{display:flex}.flex-column{flex-direction:column}.gap-3{gap:12px}.justify-content-center{justify-content:center}.align-items-center{align-items:center}.mt-3{margin-top:12px}.mt-2{margin-top:8px}.text-center{text-align:center}.p-4{padding:16px}.rounded{border-radius:12px}.alert-danger{background-color:var(--mdc-theme-error-container, #ffdad6);border-color:var(--mdc-theme-error, #ba1a1a);color:var(--mdc-theme-on-error-container, #410002);padding:12px;border-radius:4px;border:1px solid transparent}.row{display:flex;flex-wrap:wrap;margin:0 -15px}.col-xs-12,.col-sm-12,.col-md-12{flex:0 0 100%;max-width:100%;padding:0 15px}.m-t-10{margin-top:10px}.social{display:flex;justify-content:center}mat-button{border-radius:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.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: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "component", type: MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "directive", type: MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "directive", type: MatCardContent, selector: "mat-card-content" }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: MatCardFooter, selector: "mat-card-footer" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
868
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.2", type: LoginComponent, isStandalone: true, selector: "acp-login", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, showRegisterButton: { classPropertyName: "showRegisterButton", publicName: "showRegisterButton", isSignal: true, isRequired: false, transformFunction: null }, showRememberMe: { classPropertyName: "showRememberMe", publicName: "showRememberMe", isSignal: true, isRequired: false, transformFunction: null }, additionalSigninControls: { classPropertyName: "additionalSigninControls", publicName: "additionalSigninControls", isSignal: true, isRequired: false, transformFunction: null }, additionalSignupControls: { classPropertyName: "additionalSignupControls", publicName: "additionalSignupControls", isSignal: true, isRequired: false, transformFunction: null }, additionalSigninFields: { classPropertyName: "additionalSigninFields", publicName: "additionalSigninFields", isSignal: true, isRequired: false, transformFunction: null }, additionalSignupFields: { classPropertyName: "additionalSignupFields", publicName: "additionalSignupFields", isSignal: true, isRequired: false, transformFunction: null }, footerContent: { classPropertyName: "footerContent", publicName: "footerContent", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<section id=\"wrapper\" class=\"d-flex justify-content-center align-items-center\">\n <mat-card class=\"mat-elevation-z8 p-4 rounded\">\n <mat-card-header>\n <mat-card-title class=\"text-center\">{{ title() }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n @if (isLoginMode()) {\n <form [formGroup]=\"signinForm\" (ngSubmit)=\"signIn()\" class=\"d-flex flex-column gap-3\">\n <mat-form-field class=\"w-100\">\n <mat-label>Usuario</mat-label>\n <input matInput type=\"text\" placeholder=\"Ingrese su usuario\" formControlName=\"email\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Contrase\u00F1a</mat-label>\n <input matInput type=\"password\" placeholder=\"Ingrese su contrase\u00F1a\" formControlName=\"password\" />\n </mat-form-field>\n\n <!-- Remember Me checkbox - conditional -->\n @if (showRememberMe()) {\n <div class=\"d-flex align-items-center mt-2\">\n <mat-checkbox formControlName=\"rememberMe\">\n Recordarme\n </mat-checkbox>\n </div>\n }\n\n <!-- Additional signin fields -->\n <ng-container *ngTemplateOutlet=\"additionalSigninFields()\"></ng-container>\n\n <div class=\"d-flex justify-content-center mt-3\">\n <button mat-raised-button color=\"primary\" [disabled]=\"!signinForm.valid || isLoading()\" type=\"submit\"\n class=\"w-100\">\n @if (isLoading()) {\n Ingresando...\n } @else {\n <ng-container>Ingresar <mat-icon>login</mat-icon></ng-container>\n }\n </button>\n </div>\n\n <div class=\"text-center mt-2\">\n @if (showRegisterButton()) {\n <button mat-button type=\"button\" (click)=\"switchMode()\">\n \u00BFNo tienes cuenta? Reg\u00EDstrate\n </button>\n }\n </div>\n </form>\n } @else {\n <form [formGroup]=\"signupForm\" (ngSubmit)=\"registerUser()\" class=\"d-flex flex-column gap-3\">\n <mat-form-field class=\"w-100\">\n <mat-label>Nombre</mat-label>\n <input matInput type=\"text\" placeholder=\"Ingrese su nombre\" formControlName=\"displayName\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Email</mat-label>\n <input matInput type=\"email\" placeholder=\"Ingrese su email\" formControlName=\"email\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Contrase\u00F1a</mat-label>\n <input matInput type=\"password\" placeholder=\"Ingrese su contrase\u00F1a\" formControlName=\"password\" />\n </mat-form-field>\n\n <!-- Additional signup fields -->\n <ng-container *ngTemplateOutlet=\"additionalSignupFields()\"></ng-container>\n\n <div class=\"d-flex justify-content-center mt-3\">\n <button mat-raised-button color=\"primary\" [disabled]=\"!signupForm.valid || isLoading()\" type=\"submit\"\n class=\"w-100\">\n @if (isLoading()) {\n Registrando...\n } @else {\n <ng-container>Registrarse <mat-icon>person_add</mat-icon></ng-container>\n }\n </button>\n </div>\n\n <div class=\"text-center mt-2\">\n <button mat-button type=\"button\" (click)=\"switchMode()\">\u00BFYa tienes cuenta? Inicia sesi\u00F3n</button>\n </div>\n </form>\n }\n @if (errorMessage()) {\n <div class=\"alert alert-danger mt-3\" role=\"alert\">\n {{ errorMessage() }}\n </div>\n }\n </mat-card-content>\n @if (hasFooterContent()) {\n <mat-card-footer>\n <ng-container *ngTemplateOutlet=\"footerContent()\"></ng-container>\n </mat-card-footer>\n }\n </mat-card>\n</section>\n", styles: ["#wrapper{min-height:100vh;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,var(--mdc-theme-primary, #5c5f5c) 0%,var(--mdc-theme-secondary, #79747e) 100%);padding:20px}mat-card{width:100%;max-width:400px;border-radius:12px;box-shadow:0 8px 32px #0000001a}mat-card-header{text-align:center;margin-bottom:20px}mat-card-title{font-size:24px;font-weight:600;color:var(--mdc-theme-on-surface, #1c1b1f)}mat-form-field{width:100%}.w-100{width:100%}.d-flex{display:flex}.flex-column{flex-direction:column}.gap-3{gap:12px}.justify-content-center{justify-content:center}.align-items-center{align-items:center}.mt-3{margin-top:12px}.mt-2{margin-top:8px}.text-center{text-align:center}.p-4{padding:16px}.rounded{border-radius:12px}.alert-danger{background-color:var(--mdc-theme-error-container, #ffdad6);border-color:var(--mdc-theme-error, #ba1a1a);color:var(--mdc-theme-on-error-container, #410002);padding:12px;border-radius:4px;border:1px solid transparent}.row{display:flex;flex-wrap:wrap;margin:0 -15px}.col-xs-12,.col-sm-12,.col-md-12{flex:0 0 100%;max-width:100%;padding:0 15px}.m-t-10{margin-top:10px}.social{display:flex;justify-content:center}mat-button{border-radius:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.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: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "component", type: MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "component", type: MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: MatLabel, selector: "mat-label" }, { kind: "directive", type: MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "directive", type: MatCardContent, selector: "mat-card-content" }, { kind: "component", type: MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "component", type: MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "directive", type: MatCardFooter, selector: "mat-card-footer" }, { kind: "component", type: MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
696
869
  }
697
870
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LoginComponent, decorators: [{
698
871
  type: Component,
@@ -710,7 +883,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
710
883
  MatButton,
711
884
  MatCardFooter,
712
885
  MatAnchor,
713
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section id=\"wrapper\" class=\"d-flex justify-content-center align-items-center\">\n <mat-card class=\"mat-elevation-z8 p-4 rounded\">\n <mat-card-header>\n <mat-card-title class=\"text-center\">{{ title() }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n @if (isLoginMode()) {\n <form [formGroup]=\"signinForm\" (ngSubmit)=\"signIn()\" class=\"d-flex flex-column gap-3\">\n <mat-form-field class=\"w-100\">\n <mat-label>Usuario</mat-label>\n <input matInput type=\"text\" placeholder=\"Ingrese su usuario\" formControlName=\"email\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Contrase\u00F1a</mat-label>\n <input matInput type=\"password\" placeholder=\"Ingrese su contrase\u00F1a\" formControlName=\"password\" />\n </mat-form-field>\n\n <!-- Additional signin fields -->\n <ng-container *ngTemplateOutlet=\"additionalSigninFields()\"></ng-container>\n\n <div class=\"d-flex justify-content-center mt-3\">\n <button mat-raised-button color=\"primary\" [disabled]=\"!signinForm.valid || isLoading()\" type=\"submit\"\n class=\"w-100\">\n @if (isLoading()) {\n Ingresando...\n } @else {\n <ng-container>Ingresar <mat-icon>login</mat-icon></ng-container>\n }\n </button>\n </div>\n\n <div class=\"text-center mt-2\">\n @if (showRegisterButton()) {\n <button mat-button type=\"button\" (click)=\"switchMode()\">\n \u00BFNo tienes cuenta? Reg\u00EDstrate\n </button>\n }\n </div>\n </form>\n } @else {\n <form [formGroup]=\"signupForm\" (ngSubmit)=\"registerUser()\" class=\"d-flex flex-column gap-3\">\n <mat-form-field class=\"w-100\">\n <mat-label>Nombre</mat-label>\n <input matInput type=\"text\" placeholder=\"Ingrese su nombre\" formControlName=\"displayName\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Email</mat-label>\n <input matInput type=\"email\" placeholder=\"Ingrese su email\" formControlName=\"email\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Contrase\u00F1a</mat-label>\n <input matInput type=\"password\" placeholder=\"Ingrese su contrase\u00F1a\" formControlName=\"password\" />\n </mat-form-field>\n\n <!-- Additional signup fields -->\n <ng-container *ngTemplateOutlet=\"additionalSignupFields()\"></ng-container>\n\n <div class=\"d-flex justify-content-center mt-3\">\n <button mat-raised-button color=\"primary\" [disabled]=\"!signupForm.valid || isLoading()\" type=\"submit\"\n class=\"w-100\">\n @if (isLoading()) {\n Registrando...\n } @else {\n <ng-container>Registrarse <mat-icon>person_add</mat-icon></ng-container>\n }\n </button>\n </div>\n\n <div class=\"text-center mt-2\">\n <button mat-button type=\"button\" (click)=\"switchMode()\">\u00BFYa tienes cuenta? Inicia sesi\u00F3n</button>\n </div>\n </form>\n }\n @if (errorMessage()) {\n <div class=\"alert alert-danger mt-3\" role=\"alert\">\n {{ errorMessage() }}\n </div>\n }\n </mat-card-content>\n @if (hasFooterContent()) {\n <mat-card-footer>\n <ng-container *ngTemplateOutlet=\"footerContent()\"></ng-container>\n </mat-card-footer>\n }\n </mat-card>\n</section>\n", styles: ["#wrapper{min-height:100vh;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,var(--mdc-theme-primary, #5c5f5c) 0%,var(--mdc-theme-secondary, #79747e) 100%);padding:20px}mat-card{width:100%;max-width:400px;border-radius:12px;box-shadow:0 8px 32px #0000001a}mat-card-header{text-align:center;margin-bottom:20px}mat-card-title{font-size:24px;font-weight:600;color:var(--mdc-theme-on-surface, #1c1b1f)}mat-form-field{width:100%}.w-100{width:100%}.d-flex{display:flex}.flex-column{flex-direction:column}.gap-3{gap:12px}.justify-content-center{justify-content:center}.align-items-center{align-items:center}.mt-3{margin-top:12px}.mt-2{margin-top:8px}.text-center{text-align:center}.p-4{padding:16px}.rounded{border-radius:12px}.alert-danger{background-color:var(--mdc-theme-error-container, #ffdad6);border-color:var(--mdc-theme-error, #ba1a1a);color:var(--mdc-theme-on-error-container, #410002);padding:12px;border-radius:4px;border:1px solid transparent}.row{display:flex;flex-wrap:wrap;margin:0 -15px}.col-xs-12,.col-sm-12,.col-md-12{flex:0 0 100%;max-width:100%;padding:0 15px}.m-t-10{margin-top:10px}.social{display:flex;justify-content:center}mat-button{border-radius:8px}\n"] }]
886
+ MatCheckbox,
887
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section id=\"wrapper\" class=\"d-flex justify-content-center align-items-center\">\n <mat-card class=\"mat-elevation-z8 p-4 rounded\">\n <mat-card-header>\n <mat-card-title class=\"text-center\">{{ title() }}</mat-card-title>\n </mat-card-header>\n <mat-card-content>\n @if (isLoginMode()) {\n <form [formGroup]=\"signinForm\" (ngSubmit)=\"signIn()\" class=\"d-flex flex-column gap-3\">\n <mat-form-field class=\"w-100\">\n <mat-label>Usuario</mat-label>\n <input matInput type=\"text\" placeholder=\"Ingrese su usuario\" formControlName=\"email\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Contrase\u00F1a</mat-label>\n <input matInput type=\"password\" placeholder=\"Ingrese su contrase\u00F1a\" formControlName=\"password\" />\n </mat-form-field>\n\n <!-- Remember Me checkbox - conditional -->\n @if (showRememberMe()) {\n <div class=\"d-flex align-items-center mt-2\">\n <mat-checkbox formControlName=\"rememberMe\">\n Recordarme\n </mat-checkbox>\n </div>\n }\n\n <!-- Additional signin fields -->\n <ng-container *ngTemplateOutlet=\"additionalSigninFields()\"></ng-container>\n\n <div class=\"d-flex justify-content-center mt-3\">\n <button mat-raised-button color=\"primary\" [disabled]=\"!signinForm.valid || isLoading()\" type=\"submit\"\n class=\"w-100\">\n @if (isLoading()) {\n Ingresando...\n } @else {\n <ng-container>Ingresar <mat-icon>login</mat-icon></ng-container>\n }\n </button>\n </div>\n\n <div class=\"text-center mt-2\">\n @if (showRegisterButton()) {\n <button mat-button type=\"button\" (click)=\"switchMode()\">\n \u00BFNo tienes cuenta? Reg\u00EDstrate\n </button>\n }\n </div>\n </form>\n } @else {\n <form [formGroup]=\"signupForm\" (ngSubmit)=\"registerUser()\" class=\"d-flex flex-column gap-3\">\n <mat-form-field class=\"w-100\">\n <mat-label>Nombre</mat-label>\n <input matInput type=\"text\" placeholder=\"Ingrese su nombre\" formControlName=\"displayName\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Email</mat-label>\n <input matInput type=\"email\" placeholder=\"Ingrese su email\" formControlName=\"email\" />\n </mat-form-field>\n\n <mat-form-field class=\"w-100\">\n <mat-label>Contrase\u00F1a</mat-label>\n <input matInput type=\"password\" placeholder=\"Ingrese su contrase\u00F1a\" formControlName=\"password\" />\n </mat-form-field>\n\n <!-- Additional signup fields -->\n <ng-container *ngTemplateOutlet=\"additionalSignupFields()\"></ng-container>\n\n <div class=\"d-flex justify-content-center mt-3\">\n <button mat-raised-button color=\"primary\" [disabled]=\"!signupForm.valid || isLoading()\" type=\"submit\"\n class=\"w-100\">\n @if (isLoading()) {\n Registrando...\n } @else {\n <ng-container>Registrarse <mat-icon>person_add</mat-icon></ng-container>\n }\n </button>\n </div>\n\n <div class=\"text-center mt-2\">\n <button mat-button type=\"button\" (click)=\"switchMode()\">\u00BFYa tienes cuenta? Inicia sesi\u00F3n</button>\n </div>\n </form>\n }\n @if (errorMessage()) {\n <div class=\"alert alert-danger mt-3\" role=\"alert\">\n {{ errorMessage() }}\n </div>\n }\n </mat-card-content>\n @if (hasFooterContent()) {\n <mat-card-footer>\n <ng-container *ngTemplateOutlet=\"footerContent()\"></ng-container>\n </mat-card-footer>\n }\n </mat-card>\n</section>\n", styles: ["#wrapper{min-height:100vh;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,var(--mdc-theme-primary, #5c5f5c) 0%,var(--mdc-theme-secondary, #79747e) 100%);padding:20px}mat-card{width:100%;max-width:400px;border-radius:12px;box-shadow:0 8px 32px #0000001a}mat-card-header{text-align:center;margin-bottom:20px}mat-card-title{font-size:24px;font-weight:600;color:var(--mdc-theme-on-surface, #1c1b1f)}mat-form-field{width:100%}.w-100{width:100%}.d-flex{display:flex}.flex-column{flex-direction:column}.gap-3{gap:12px}.justify-content-center{justify-content:center}.align-items-center{align-items:center}.mt-3{margin-top:12px}.mt-2{margin-top:8px}.text-center{text-align:center}.p-4{padding:16px}.rounded{border-radius:12px}.alert-danger{background-color:var(--mdc-theme-error-container, #ffdad6);border-color:var(--mdc-theme-error, #ba1a1a);color:var(--mdc-theme-on-error-container, #410002);padding:12px;border-radius:4px;border:1px solid transparent}.row{display:flex;flex-wrap:wrap;margin:0 -15px}.col-xs-12,.col-sm-12,.col-md-12{flex:0 0 100%;max-width:100%;padding:0 15px}.m-t-10{margin-top:10px}.social{display:flex;justify-content:center}mat-button{border-radius:8px}\n"] }]
714
888
  }], ctorParameters: () => [] });
715
889
 
716
890
  // src/lib/presentation/components/index.ts
@@ -730,5 +904,5 @@ const authProviders = [
730
904
  * Generated bundle index. Do not edit.
731
905
  */
732
906
 
733
- export { AuthHttpRepository, AuthRepository, AuthStore, AuthTokenService, CsrfService, LoginComponent, LoginUseCase, LogoutUseCase, RefreshTokenUseCase, RegisterUseCase, TokenRepository, User, authGuard, authProviders };
907
+ export { AuthHttpRepository, AuthRepository, AuthStore, AuthTokenService, LoginComponent, LoginUseCase, LogoutUseCase, RefreshTokenUseCase, RegisterUseCase, TokenRepository, UrlRedirectService, User, authGuard, authProviders, authRedirectInterceptor };
734
908
  //# sourceMappingURL=acontplus-ng-auth.mjs.map