@acontplus/ng-auth 1.1.0 → 1.1.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.
- package/README.md +66 -47
- package/fesm2022/acontplus-ng-auth.mjs +43 -60
- package/fesm2022/acontplus-ng-auth.mjs.map +1 -1
- package/index.d.ts +30 -43
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# @acontplus/ng-auth
|
|
2
2
|
|
|
3
|
-
Acontplus Angular Authentication Module providing comprehensive authentication
|
|
3
|
+
Acontplus Angular Authentication Module providing comprehensive authentication
|
|
4
|
+
and authorization features for Angular applications.
|
|
4
5
|
|
|
5
6
|
## Installation
|
|
6
7
|
|
|
@@ -12,26 +13,36 @@ npm install @acontplus/ng-auth
|
|
|
12
13
|
|
|
13
14
|
- **Auth Guard**: Route protection with automatic redirect to login page
|
|
14
15
|
- **Auth Token Service**: JWT token management and authentication state handling
|
|
15
|
-
- **URL Redirection Strategy**: Automatically redirects users back to their
|
|
16
|
-
|
|
16
|
+
- **URL Redirection Strategy**: Automatically redirects users back to their
|
|
17
|
+
intended destination after login
|
|
18
|
+
- **Session Expiry Handling**: Manages session expiry during HTTP requests with
|
|
19
|
+
automatic redirection
|
|
17
20
|
- **CSRF Protection**: Built-in CSRF token management for secure API requests
|
|
18
|
-
- **Token Repository**: Secure token storage and retrieval with local storage
|
|
19
|
-
|
|
21
|
+
- **Token Repository**: Secure token storage and retrieval with local storage
|
|
22
|
+
support
|
|
23
|
+
- **Authentication Use Cases**: Login, register, refresh token, and logout
|
|
24
|
+
functionality
|
|
20
25
|
- **Domain Models**: User domain models and value objects
|
|
21
|
-
- **Clean Architecture**: Organized in domain, application, data, and
|
|
22
|
-
|
|
26
|
+
- **Clean Architecture**: Organized in domain, application, data, and
|
|
27
|
+
presentation layers
|
|
28
|
+
- **Angular Integration**: Seamless integration with Angular Router and HTTP
|
|
29
|
+
client
|
|
23
30
|
- **Type Safety**: Full TypeScript support with comprehensive type definitions
|
|
24
31
|
|
|
25
32
|
## URL Redirection Strategy
|
|
26
33
|
|
|
27
|
-
The module includes a comprehensive URL redirection strategy that ensures users
|
|
34
|
+
The module includes a comprehensive URL redirection strategy that ensures users
|
|
35
|
+
are returned to their intended destination after authentication, even when
|
|
36
|
+
sessions expire.
|
|
28
37
|
|
|
29
38
|
### Components
|
|
30
39
|
|
|
31
40
|
1. **UrlRedirectService** - Manages URL storage and redirection logic
|
|
32
41
|
2. **Enhanced Auth Guard** - Captures URLs before redirecting to login
|
|
33
|
-
3. **Enhanced Login Use Case** - Redirects to stored URLs after successful
|
|
34
|
-
|
|
42
|
+
3. **Enhanced Login Use Case** - Redirects to stored URLs after successful
|
|
43
|
+
authentication
|
|
44
|
+
4. **Auth Redirect Interceptor** - Handles session expiry during HTTP requests
|
|
45
|
+
(optional)
|
|
35
46
|
|
|
36
47
|
### Basic Setup
|
|
37
48
|
|
|
@@ -43,8 +54,8 @@ const routes: Routes = [
|
|
|
43
54
|
{
|
|
44
55
|
path: 'dashboard',
|
|
45
56
|
component: DashboardComponent,
|
|
46
|
-
canActivate: [authGuard] // Automatically captures URL if not authenticated
|
|
47
|
-
}
|
|
57
|
+
canActivate: [authGuard], // Automatically captures URL if not authenticated
|
|
58
|
+
},
|
|
48
59
|
];
|
|
49
60
|
```
|
|
50
61
|
|
|
@@ -59,7 +70,7 @@ export const appConfig: ApplicationConfig = {
|
|
|
59
70
|
provideHttpClient(
|
|
60
71
|
withInterceptors([
|
|
61
72
|
authRedirectInterceptor, // Handles session expiry during API calls
|
|
62
|
-
])
|
|
73
|
+
]),
|
|
63
74
|
),
|
|
64
75
|
],
|
|
65
76
|
};
|
|
@@ -68,11 +79,13 @@ export const appConfig: ApplicationConfig = {
|
|
|
68
79
|
### How It Works
|
|
69
80
|
|
|
70
81
|
**Scenario 1 - Route Protection:**
|
|
82
|
+
|
|
71
83
|
```
|
|
72
84
|
User → /dashboard → Auth Guard → Store URL → Login → Success → Redirect to /dashboard
|
|
73
85
|
```
|
|
74
86
|
|
|
75
87
|
**Scenario 2 - Session Expiry (with interceptor):**
|
|
88
|
+
|
|
76
89
|
```
|
|
77
90
|
User on /reports → API Call → 401 Error → Store URL → Login → Success → Redirect to /reports
|
|
78
91
|
```
|
|
@@ -137,21 +150,19 @@ export class AuthComponent {
|
|
|
137
150
|
|
|
138
151
|
## Login UI Component
|
|
139
152
|
|
|
140
|
-
The library includes a comprehensive login UI component with Material Design
|
|
153
|
+
The library includes a comprehensive login UI component with Material Design
|
|
154
|
+
styling.
|
|
141
155
|
|
|
142
156
|
### Basic Usage
|
|
143
157
|
|
|
144
158
|
```html
|
|
145
|
-
<acp-login
|
|
146
|
-
title="Welcome Back"
|
|
147
|
-
[showRegisterButton]="true">
|
|
148
|
-
</acp-login>
|
|
159
|
+
<acp-login title="Welcome Back" [showRegisterButton]="true"> </acp-login>
|
|
149
160
|
```
|
|
150
161
|
|
|
151
162
|
### Component Features
|
|
152
163
|
|
|
153
164
|
- **Dual Mode Support**: Toggle between login and signup modes
|
|
154
|
-
- **Material Design**: Built with Angular Material components
|
|
165
|
+
- **Material Design**: Built with Angular Material components
|
|
155
166
|
- **Responsive Layout**: Works on desktop and mobile devices
|
|
156
167
|
- **Theme Integration**: Automatically inherits your app's theme colors
|
|
157
168
|
- **Validation**: Built-in form validation with error messaging
|
|
@@ -162,14 +173,15 @@ The library includes a comprehensive login UI component with Material Design sty
|
|
|
162
173
|
### Core Services
|
|
163
174
|
|
|
164
175
|
- **AuthTokenService**: JWT token management and authentication state
|
|
165
|
-
- **UrlRedirectService**: URL storage and redirection management
|
|
176
|
+
- **UrlRedirectService**: URL storage and redirection management
|
|
166
177
|
- **CsrfService**: CSRF token management for secure API requests
|
|
167
178
|
- **TokenRepository**: Secure token storage and retrieval
|
|
168
179
|
|
|
169
180
|
### Guards and Interceptors
|
|
170
181
|
|
|
171
182
|
- **authGuard**: Route protection with automatic URL capture
|
|
172
|
-
- **authRedirectInterceptor**: Session expiry handling during HTTP requests
|
|
183
|
+
- **authRedirectInterceptor**: Session expiry handling during HTTP requests
|
|
184
|
+
(optional)
|
|
173
185
|
|
|
174
186
|
### Use Cases
|
|
175
187
|
|
|
@@ -180,7 +192,8 @@ The library includes a comprehensive login UI component with Material Design sty
|
|
|
180
192
|
|
|
181
193
|
## Architecture
|
|
182
194
|
|
|
183
|
-
This library follows Clean Architecture principles with clear separation of
|
|
195
|
+
This library follows Clean Architecture principles with clear separation of
|
|
196
|
+
concerns organized in domain, application, data, and presentation layers.
|
|
184
197
|
|
|
185
198
|
### Authentication Service
|
|
186
199
|
|
|
@@ -212,7 +225,7 @@ export class LoginComponent {
|
|
|
212
225
|
|
|
213
226
|
async login(credentials: LoginCredentials) {
|
|
214
227
|
const csrfToken = await this.csrfService.getCsrfToken();
|
|
215
|
-
|
|
228
|
+
|
|
216
229
|
// Include CSRF token in your login request
|
|
217
230
|
const response = await this.http.post('/api/login', {
|
|
218
231
|
...credentials,
|
|
@@ -257,11 +270,9 @@ import { NgModule } from '@angular/core';
|
|
|
257
270
|
import { authProviders } from '@acontplus/ng-auth';
|
|
258
271
|
|
|
259
272
|
@NgModule({
|
|
260
|
-
providers: [
|
|
261
|
-
...authProviders
|
|
262
|
-
]
|
|
273
|
+
providers: [...authProviders],
|
|
263
274
|
})
|
|
264
|
-
export class AppModule {
|
|
275
|
+
export class AppModule {}
|
|
265
276
|
```
|
|
266
277
|
|
|
267
278
|
## Running unit tests
|
|
@@ -270,7 +281,8 @@ Run `nx test ng-auth` to execute the unit tests.
|
|
|
270
281
|
|
|
271
282
|
## Login Component
|
|
272
283
|
|
|
273
|
-
The `LoginComponent` provides a flexible, themeable authentication UI component
|
|
284
|
+
The `LoginComponent` provides a flexible, themeable authentication UI component
|
|
285
|
+
with support for custom fields and dynamic content.
|
|
274
286
|
|
|
275
287
|
### Basic Usage
|
|
276
288
|
|
|
@@ -279,12 +291,9 @@ import { LoginComponent } from '@acontplus/ng-auth/ui/login';
|
|
|
279
291
|
|
|
280
292
|
@Component({
|
|
281
293
|
template: `
|
|
282
|
-
<acp-login
|
|
283
|
-
title="Welcome Back"
|
|
284
|
-
[showRegisterButton]="true">
|
|
285
|
-
</acp-login>
|
|
294
|
+
<acp-login title="Welcome Back" [showRegisterButton]="true"> </acp-login>
|
|
286
295
|
`,
|
|
287
|
-
imports: [LoginComponent]
|
|
296
|
+
imports: [LoginComponent],
|
|
288
297
|
})
|
|
289
298
|
export class AuthPageComponent {}
|
|
290
299
|
```
|
|
@@ -318,7 +327,8 @@ export class AuthPageComponent {
|
|
|
318
327
|
```html
|
|
319
328
|
<acp-login
|
|
320
329
|
[additionalSigninControls]="signinExtras"
|
|
321
|
-
[additionalSignupControls]="signupExtras"
|
|
330
|
+
[additionalSignupControls]="signupExtras"
|
|
331
|
+
>
|
|
322
332
|
</acp-login>
|
|
323
333
|
```
|
|
324
334
|
|
|
@@ -334,9 +344,7 @@ Use content projection slots for custom field templates:
|
|
|
334
344
|
<mat-label>Company</mat-label>
|
|
335
345
|
<mat-select formControlName="companyId">
|
|
336
346
|
@for (company of companies; track company.id) {
|
|
337
|
-
<mat-option [value]="company.id">
|
|
338
|
-
{{ company.name }}
|
|
339
|
-
</mat-option>
|
|
347
|
+
<mat-option [value]="company.id"> {{ company.name }} </mat-option>
|
|
340
348
|
}
|
|
341
349
|
</mat-select>
|
|
342
350
|
</mat-form-field>
|
|
@@ -348,9 +356,7 @@ Use content projection slots for custom field templates:
|
|
|
348
356
|
<mat-label>Company</mat-label>
|
|
349
357
|
<mat-select formControlName="companyId" required>
|
|
350
358
|
@for (company of companies; track company.id) {
|
|
351
|
-
<mat-option [value]="company.id">
|
|
352
|
-
{{ company.name }}
|
|
353
|
-
</mat-option>
|
|
359
|
+
<mat-option [value]="company.id"> {{ company.name }} </mat-option>
|
|
354
360
|
}
|
|
355
361
|
</mat-select>
|
|
356
362
|
</mat-form-field>
|
|
@@ -366,7 +372,13 @@ Use content projection slots for custom field templates:
|
|
|
366
372
|
|
|
367
373
|
<mat-form-field class="w-100">
|
|
368
374
|
<mat-label>Validation PIN</mat-label>
|
|
369
|
-
<input
|
|
375
|
+
<input
|
|
376
|
+
matInput
|
|
377
|
+
type="text"
|
|
378
|
+
placeholder="Enter PIN"
|
|
379
|
+
formControlName="validationPin"
|
|
380
|
+
required
|
|
381
|
+
/>
|
|
370
382
|
</mat-form-field>
|
|
371
383
|
</div>
|
|
372
384
|
</acp-login>
|
|
@@ -393,13 +405,17 @@ Customize the footer with dynamic content:
|
|
|
393
405
|
|
|
394
406
|
### Theme Color Inheritance
|
|
395
407
|
|
|
396
|
-
The component uses CSS custom properties to inherit colors from the parent app's
|
|
408
|
+
The component uses CSS custom properties to inherit colors from the parent app's
|
|
409
|
+
theme:
|
|
397
410
|
|
|
398
411
|
- Background gradient: Uses `--mdc-theme-primary` and `--mdc-theme-secondary`
|
|
399
412
|
- Title color: Uses `--mdc-theme-on-surface`
|
|
400
|
-
- Error alerts: Uses `--mdc-theme-error`, `--mdc-theme-error-container`,
|
|
413
|
+
- Error alerts: Uses `--mdc-theme-error`, `--mdc-theme-error-container`,
|
|
414
|
+
`--mdc-theme-on-error-container`
|
|
415
|
+
|
|
416
|
+
Fallback colors use Angular Material's Material Design 3 (M3) neutral color
|
|
417
|
+
tokens:
|
|
401
418
|
|
|
402
|
-
Fallback colors use Angular Material's Material Design 3 (M3) neutral color tokens:
|
|
403
419
|
- Primary: #5c5f5c (M3 neutral primary)
|
|
404
420
|
- Secondary: #79747e (M3 neutral secondary)
|
|
405
421
|
- On surface: #1c1b1f (M3 on-surface)
|
|
@@ -423,7 +439,10 @@ To customize colors, define these CSS variables in your app's global styles:
|
|
|
423
439
|
### Component Inputs
|
|
424
440
|
|
|
425
441
|
- `title: string` - The title displayed in the card header (default: 'Login')
|
|
426
|
-
- `showRegisterButton: boolean` - Whether to show the register button (default:
|
|
427
|
-
|
|
428
|
-
- `
|
|
442
|
+
- `showRegisterButton: boolean` - Whether to show the register button (default:
|
|
443
|
+
true)
|
|
444
|
+
- `additionalSigninControls: Record<string, AbstractControl>` - Additional
|
|
445
|
+
controls for signin form
|
|
446
|
+
- `additionalSignupControls: Record<string, AbstractControl>` - Additional
|
|
447
|
+
controls for signup form
|
|
429
448
|
- `footerContent: TemplateRef<any> | null` - Custom footer template
|
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import { UserRepository, BaseUseCase } from '@acontplus/ng-infrastructure';
|
|
1
|
+
import { UserRepository, BaseUseCase, LoggingService } from '@acontplus/ng-infrastructure';
|
|
2
2
|
export { TOKEN_PROVIDER } from '@acontplus/ng-infrastructure';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { inject, PLATFORM_ID, Injectable, signal, input, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
4
|
+
import { inject, PLATFORM_ID, Injectable, NgZone, signal, input, computed, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
5
5
|
import { Router } from '@angular/router';
|
|
6
6
|
import * as i1 from '@angular/common';
|
|
7
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
10
|
import { catchError, switchMap } from 'rxjs/operators';
|
|
11
|
-
import { throwError, from,
|
|
11
|
+
import { throwError, from, of, tap, catchError as catchError$1 } from 'rxjs';
|
|
12
12
|
import { HttpClient } from '@angular/common/http';
|
|
13
|
-
import { AuthTokens } from '@acontplus/core';
|
|
14
13
|
import * as i2 from '@angular/forms';
|
|
15
14
|
import { FormBuilder, Validators, ReactiveFormsModule } from '@angular/forms';
|
|
16
15
|
import { MatCard, MatCardHeader, MatCardTitle, MatCardContent, MatCardFooter } from '@angular/material/card';
|
|
@@ -112,7 +111,7 @@ class TokenRepository {
|
|
|
112
111
|
try {
|
|
113
112
|
return jwtDecode(token);
|
|
114
113
|
}
|
|
115
|
-
catch
|
|
114
|
+
catch {
|
|
116
115
|
return null;
|
|
117
116
|
}
|
|
118
117
|
}
|
|
@@ -139,23 +138,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
|
|
|
139
138
|
}] });
|
|
140
139
|
|
|
141
140
|
class AuthTokenService {
|
|
142
|
-
tokenRepository;
|
|
143
|
-
constructor(tokenRepository) {
|
|
144
|
-
this.tokenRepository = tokenRepository;
|
|
145
|
-
}
|
|
141
|
+
tokenRepository = inject(TokenRepository);
|
|
146
142
|
getToken() {
|
|
147
143
|
return this.tokenRepository.getAccessToken();
|
|
148
144
|
}
|
|
149
145
|
isAuthenticated() {
|
|
150
146
|
return this.tokenRepository.isAuthenticated();
|
|
151
147
|
}
|
|
152
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthTokenService, deps: [
|
|
148
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthTokenService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
153
149
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthTokenService, providedIn: 'root' });
|
|
154
150
|
}
|
|
155
151
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: AuthTokenService, decorators: [{
|
|
156
152
|
type: Injectable,
|
|
157
153
|
args: [{ providedIn: 'root' }]
|
|
158
|
-
}]
|
|
154
|
+
}] });
|
|
159
155
|
|
|
160
156
|
/**
|
|
161
157
|
* Service to manage URL redirection after authentication
|
|
@@ -164,7 +160,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
|
|
|
164
160
|
*/
|
|
165
161
|
class UrlRedirectService {
|
|
166
162
|
REDIRECT_URL_KEY = 'acp_redirect_url';
|
|
167
|
-
EXCLUDED_ROUTES = [
|
|
163
|
+
EXCLUDED_ROUTES = [
|
|
164
|
+
'/login',
|
|
165
|
+
'/auth',
|
|
166
|
+
'/register',
|
|
167
|
+
'/forgot-password',
|
|
168
|
+
'/reset-password',
|
|
169
|
+
];
|
|
168
170
|
router = inject(Router);
|
|
169
171
|
platformId = inject(PLATFORM_ID);
|
|
170
172
|
document = inject(DOCUMENT);
|
|
@@ -311,31 +313,7 @@ const authRedirectInterceptor = (req, next) => {
|
|
|
311
313
|
}));
|
|
312
314
|
};
|
|
313
315
|
|
|
314
|
-
|
|
315
|
-
id;
|
|
316
|
-
email;
|
|
317
|
-
displayName;
|
|
318
|
-
_refreshToken;
|
|
319
|
-
constructor(id, email, displayName, _refreshToken) {
|
|
320
|
-
this.id = id;
|
|
321
|
-
this.email = email;
|
|
322
|
-
this.displayName = displayName;
|
|
323
|
-
this._refreshToken = _refreshToken;
|
|
324
|
-
}
|
|
325
|
-
get refreshToken() {
|
|
326
|
-
return this._refreshToken;
|
|
327
|
-
}
|
|
328
|
-
updateRefreshToken(token) {
|
|
329
|
-
this._refreshToken = token;
|
|
330
|
-
}
|
|
331
|
-
clearRefreshToken() {
|
|
332
|
-
this._refreshToken = undefined;
|
|
333
|
-
}
|
|
334
|
-
static create(email, displayName, _password) {
|
|
335
|
-
const id = Date.now(); // Generate a numeric ID
|
|
336
|
-
return new User(id, email, displayName);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
316
|
+
// src/lib/domain/models/auth.ts
|
|
339
317
|
|
|
340
318
|
// src/lib/domain/models/index.ts
|
|
341
319
|
|
|
@@ -405,26 +383,22 @@ class AuthHttpRepository extends AuthRepository {
|
|
|
405
383
|
})));
|
|
406
384
|
}
|
|
407
385
|
register(request) {
|
|
408
|
-
return from(this.csrfService.getCsrfToken()).pipe(switchMap(csrfToken => this.http
|
|
409
|
-
.post(`${this.URL}register`, request, {
|
|
386
|
+
return from(this.csrfService.getCsrfToken()).pipe(switchMap(csrfToken => this.http.post(`${this.URL}register`, request, {
|
|
410
387
|
headers: {
|
|
411
388
|
'Device-Info': getDeviceInfo(),
|
|
412
389
|
'X-CSRF-Token': csrfToken,
|
|
413
390
|
},
|
|
414
391
|
withCredentials: true,
|
|
415
|
-
})
|
|
416
|
-
.pipe(map(response => new AuthTokens(response.token, response.refreshToken)))));
|
|
392
|
+
})));
|
|
417
393
|
}
|
|
418
394
|
refreshToken(request) {
|
|
419
|
-
return from(this.csrfService.getCsrfToken()).pipe(switchMap(csrfToken => this.http
|
|
420
|
-
.post(`${this.URL}refresh`, request, {
|
|
395
|
+
return from(this.csrfService.getCsrfToken()).pipe(switchMap(csrfToken => this.http.post(`${this.URL}refresh`, request, {
|
|
421
396
|
headers: {
|
|
422
397
|
'Device-Info': getDeviceInfo(),
|
|
423
398
|
'X-CSRF-Token': csrfToken,
|
|
424
399
|
},
|
|
425
400
|
withCredentials: true,
|
|
426
|
-
})
|
|
427
|
-
.pipe(map(response => new AuthTokens(response.token, response.refreshToken)))));
|
|
401
|
+
})));
|
|
428
402
|
}
|
|
429
403
|
logout(email, refreshToken) {
|
|
430
404
|
return from(this.csrfService.getCsrfToken()).pipe(switchMap(csrfToken => this.http.post(`${this.URL}logout`, { email, refreshToken: refreshToken || undefined }, {
|
|
@@ -452,6 +426,7 @@ class AuthStore {
|
|
|
452
426
|
tokenRepository = inject(TokenRepository);
|
|
453
427
|
userRepository = inject(UserRepository);
|
|
454
428
|
router = inject(Router);
|
|
429
|
+
ngZone = inject(NgZone);
|
|
455
430
|
// Authentication state signals
|
|
456
431
|
_isAuthenticated = signal(false, ...(ngDevMode ? [{ debugName: "_isAuthenticated" }] : []));
|
|
457
432
|
_isLoading = signal(false, ...(ngDevMode ? [{ debugName: "_isLoading" }] : []));
|
|
@@ -505,12 +480,16 @@ class AuthStore {
|
|
|
505
480
|
if (this.refreshTokenTimeout) {
|
|
506
481
|
clearTimeout(this.refreshTokenTimeout);
|
|
507
482
|
}
|
|
508
|
-
this.
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
483
|
+
this.ngZone.runOutsideAngular(() => {
|
|
484
|
+
this.refreshTokenTimeout = window.setTimeout(() => {
|
|
485
|
+
this.ngZone.run(() => {
|
|
486
|
+
// Check if refresh is still needed before executing
|
|
487
|
+
if (this.tokenRepository.needsRefresh()) {
|
|
488
|
+
this.refreshToken().subscribe();
|
|
489
|
+
}
|
|
490
|
+
});
|
|
491
|
+
}, refreshTime);
|
|
492
|
+
});
|
|
514
493
|
}
|
|
515
494
|
catch {
|
|
516
495
|
// Silent fail - token might be invalid
|
|
@@ -555,7 +534,7 @@ class AuthStore {
|
|
|
555
534
|
},
|
|
556
535
|
error: () => {
|
|
557
536
|
this.refreshInProgress$ = undefined;
|
|
558
|
-
}
|
|
537
|
+
},
|
|
559
538
|
}));
|
|
560
539
|
return this.refreshInProgress$;
|
|
561
540
|
}
|
|
@@ -584,7 +563,7 @@ class AuthStore {
|
|
|
584
563
|
error: () => {
|
|
585
564
|
// Server logout failed, still clear client-side data for security
|
|
586
565
|
this.performClientLogout();
|
|
587
|
-
}
|
|
566
|
+
},
|
|
588
567
|
});
|
|
589
568
|
}
|
|
590
569
|
else {
|
|
@@ -744,7 +723,9 @@ class LogoutUseCase extends BaseUseCase {
|
|
|
744
723
|
const userData = this.userRepository.getCurrentUser();
|
|
745
724
|
const refreshToken = this.tokenRepository.getRefreshToken();
|
|
746
725
|
if (userData?.email && refreshToken && refreshToken.length > 0) {
|
|
747
|
-
return this.authRepository
|
|
726
|
+
return this.authRepository
|
|
727
|
+
.logout(userData.email, refreshToken)
|
|
728
|
+
.pipe(tap(() => this.cleanup()));
|
|
748
729
|
}
|
|
749
730
|
this.cleanup();
|
|
750
731
|
return of(void 0);
|
|
@@ -785,8 +766,10 @@ class LoginComponent {
|
|
|
785
766
|
// Computed signal to check if footer content exists
|
|
786
767
|
hasFooterContent = computed(() => this.footerContent() !== null, ...(ngDevMode ? [{ debugName: "hasFooterContent" }] : []));
|
|
787
768
|
fb = inject(FormBuilder);
|
|
769
|
+
authStore = inject(AuthStore);
|
|
788
770
|
loginUseCase = inject(LoginUseCase);
|
|
789
771
|
registerUseCase = inject(RegisterUseCase);
|
|
772
|
+
loggingService = inject(LoggingService);
|
|
790
773
|
// Angular 20+ signals
|
|
791
774
|
isLoginMode = signal(true, ...(ngDevMode ? [{ debugName: "isLoginMode" }] : []));
|
|
792
775
|
isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
|
|
@@ -832,7 +815,7 @@ class LoginComponent {
|
|
|
832
815
|
const loginRequest = {
|
|
833
816
|
...this.signinForm.value,
|
|
834
817
|
// If showRememberMe is false, default rememberMe to false
|
|
835
|
-
rememberMe: this.showRememberMe() ? (this.signinForm.value.rememberMe ?? false) : false
|
|
818
|
+
rememberMe: this.showRememberMe() ? (this.signinForm.value.rememberMe ?? false) : false,
|
|
836
819
|
};
|
|
837
820
|
this.loginUseCase.execute(loginRequest).subscribe({
|
|
838
821
|
next: () => {
|
|
@@ -841,7 +824,7 @@ class LoginComponent {
|
|
|
841
824
|
error: error => {
|
|
842
825
|
this.isLoading.set(false);
|
|
843
826
|
this.errorMessage.set('Error al iniciar sesión. Verifique sus credenciales.');
|
|
844
|
-
|
|
827
|
+
this.loggingService.error('Login failed', { error });
|
|
845
828
|
},
|
|
846
829
|
});
|
|
847
830
|
}
|
|
@@ -859,13 +842,13 @@ class LoginComponent {
|
|
|
859
842
|
error: error => {
|
|
860
843
|
this.isLoading.set(false);
|
|
861
844
|
this.errorMessage.set('Error al registrar usuario. Intente nuevamente.');
|
|
862
|
-
|
|
845
|
+
this.loggingService.error('Register error', { error });
|
|
863
846
|
},
|
|
864
847
|
});
|
|
865
848
|
}
|
|
866
849
|
}
|
|
867
850
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LoginComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
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 });
|
|
851
|
+
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\n matInput\n type=\"password\"\n placeholder=\"Ingrese su contrase\u00F1a\"\n formControlName=\"password\"\n autocomplete=\"current-password\"\n />\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\"> Recordarme </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\n mat-raised-button\n color=\"primary\"\n [disabled]=\"!signinForm.valid || isLoading()\"\n type=\"submit\"\n class=\"w-100\"\n >\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\n matInput\n type=\"text\"\n placeholder=\"Ingrese su nombre\"\n formControlName=\"displayName\"\n />\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\n matInput\n type=\"password\"\n placeholder=\"Ingrese su contrase\u00F1a\"\n formControlName=\"password\"\n autocomplete=\"new-password\"\n />\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\n mat-raised-button\n color=\"primary\"\n [disabled]=\"!signupForm.valid || isLoading()\"\n type=\"submit\"\n class=\"w-100\"\n >\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()\">\n \u00BFYa tienes cuenta? Inicia sesi\u00F3n\n </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 });
|
|
869
852
|
}
|
|
870
853
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImport: i0, type: LoginComponent, decorators: [{
|
|
871
854
|
type: Component,
|
|
@@ -884,7 +867,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.2", ngImpor
|
|
|
884
867
|
MatCardFooter,
|
|
885
868
|
MatAnchor,
|
|
886
869
|
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
|
|
870
|
+
], 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\n matInput\n type=\"password\"\n placeholder=\"Ingrese su contrase\u00F1a\"\n formControlName=\"password\"\n autocomplete=\"current-password\"\n />\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\"> Recordarme </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\n mat-raised-button\n color=\"primary\"\n [disabled]=\"!signinForm.valid || isLoading()\"\n type=\"submit\"\n class=\"w-100\"\n >\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\n matInput\n type=\"text\"\n placeholder=\"Ingrese su nombre\"\n formControlName=\"displayName\"\n />\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\n matInput\n type=\"password\"\n placeholder=\"Ingrese su contrase\u00F1a\"\n formControlName=\"password\"\n autocomplete=\"new-password\"\n />\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\n mat-raised-button\n color=\"primary\"\n [disabled]=\"!signupForm.valid || isLoading()\"\n type=\"submit\"\n class=\"w-100\"\n >\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()\">\n \u00BFYa tienes cuenta? Inicia sesi\u00F3n\n </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"] }]
|
|
888
871
|
}], ctorParameters: () => [] });
|
|
889
872
|
|
|
890
873
|
// src/lib/presentation/components/index.ts
|
|
@@ -904,5 +887,5 @@ const authProviders = [
|
|
|
904
887
|
* Generated bundle index. Do not edit.
|
|
905
888
|
*/
|
|
906
889
|
|
|
907
|
-
export { AuthHttpRepository, AuthRepository, AuthStore, AuthTokenService, LoginComponent, LoginUseCase, LogoutUseCase, RefreshTokenUseCase, RegisterUseCase, TokenRepository, UrlRedirectService,
|
|
890
|
+
export { AuthHttpRepository, AuthRepository, AuthStore, AuthTokenService, LoginComponent, LoginUseCase, LogoutUseCase, RefreshTokenUseCase, RegisterUseCase, TokenRepository, UrlRedirectService, authGuard, authProviders, authRedirectInterceptor };
|
|
908
891
|
//# sourceMappingURL=acontplus-ng-auth.mjs.map
|