newportsite 1.1.3
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/newportsite-1.1.3.tgz +0 -0
- package/ng-package.json +7 -0
- package/obfuscate.js +70 -0
- package/package.json +15 -0
- package/src/lib/app.component.ts +47 -0
- package/src/lib/app.routing.ts +38 -0
- package/src/lib/auth/alert.component.html +5 -0
- package/src/lib/auth/alert.component.ts +24 -0
- package/src/lib/auth/auth.component.html +1 -0
- package/src/lib/auth/auth.component.ts +10 -0
- package/src/lib/auth/auth.routes.ts +16 -0
- package/src/lib/auth/index.ts +4 -0
- package/src/lib/auth/login.component.html +87 -0
- package/src/lib/auth/login.component.ts +158 -0
- package/src/lib/auth/models/index.ts +1 -0
- package/src/lib/auth/models/user.ts +25 -0
- package/src/lib/auth/register.component.html +157 -0
- package/src/lib/auth/register.component.ts +219 -0
- package/src/lib/auth/services/alert.service.ts +47 -0
- package/src/lib/auth/services/auth.service.ts +28 -0
- package/src/lib/auth/services/index.ts +3 -0
- package/src/lib/auth/services/user.service.spec.ts +112 -0
- package/src/lib/auth/services/user.service.ts +47 -0
- package/src/lib/common/card.component.html +72 -0
- package/src/lib/common/card.component.ts +102 -0
- package/src/lib/common/commands.component.html +8 -0
- package/src/lib/common/commands.component.ts +42 -0
- package/src/lib/common/context.component.html +9 -0
- package/src/lib/common/context.component.ts +38 -0
- package/src/lib/common/grid.component.html +20 -0
- package/src/lib/common/grid.component.ts +747 -0
- package/src/lib/common/index.ts +9 -0
- package/src/lib/common/loader.component.html +5 -0
- package/src/lib/common/loader.component.ts +27 -0
- package/src/lib/common/lookup.component.html +29 -0
- package/src/lib/common/lookup.component.ts +115 -0
- package/src/lib/common/messagebox.component.html +39 -0
- package/src/lib/common/messagebox.component.ts +74 -0
- package/src/lib/common/theme-toggle.component.ts +139 -0
- package/src/lib/config.ts +62 -0
- package/src/lib/containers/default-layout/default-layout.component.html +191 -0
- package/src/lib/containers/default-layout/default-layout.component.ts +158 -0
- package/src/lib/containers/default-layout/index.ts +1 -0
- package/src/lib/containers/index.ts +1 -0
- package/src/lib/directives/component.draggable.ts +80 -0
- package/src/lib/directives/index.ts +2 -0
- package/src/lib/directives/input.directive.spec.ts +158 -0
- package/src/lib/directives/input.directive.ts +210 -0
- package/src/lib/home/dashboard/dashboard.component.html +38 -0
- package/src/lib/home/dashboard/dashboard.component.ts +50 -0
- package/src/lib/home/dashboard/index.ts +1 -0
- package/src/lib/home/index.component.html +1 -0
- package/src/lib/home/index.component.ts +10 -0
- package/src/lib/home/index.routes.ts +29 -0
- package/src/lib/home/index.ts +1 -0
- package/src/lib/home/info/index.ts +1 -0
- package/src/lib/home/info/info.component.css +476 -0
- package/src/lib/home/info/info.component.html +174 -0
- package/src/lib/home/info/info.component.ts +287 -0
- package/src/lib/home/model/article.component.html +10 -0
- package/src/lib/home/model/article.component.ts +50 -0
- package/src/lib/home/model/barchart.component.html +8 -0
- package/src/lib/home/model/barchart.component.ts +59 -0
- package/src/lib/home/model/index.ts +7 -0
- package/src/lib/home/model/itemdetail.component.html +25 -0
- package/src/lib/home/model/itemdetail.component.ts +93 -0
- package/src/lib/home/model/itemtab.component.html +25 -0
- package/src/lib/home/model/itemtab.component.ts +105 -0
- package/src/lib/home/model/model.component.html +121 -0
- package/src/lib/home/model/model.component.ts +510 -0
- package/src/lib/home/model/modeltoolbar.component.html +111 -0
- package/src/lib/home/model/modeltoolbar.component.ts +157 -0
- package/src/lib/home/model/navigation.component.html +86 -0
- package/src/lib/home/model/navigation.component.ts +247 -0
- package/src/lib/home/model/services/index.ts +1 -0
- package/src/lib/home/model/services/model.service.spec.ts +423 -0
- package/src/lib/home/model/services/model.service.ts +319 -0
- package/src/lib/home/modelsearch/index.ts +1 -0
- package/src/lib/home/modelsearch/modelsearch.component.html +124 -0
- package/src/lib/home/modelsearch/modelsearch.component.ts +453 -0
- package/src/lib/interfaces/data.interface.ts +131 -0
- package/src/lib/interfaces/index.ts +2 -0
- package/src/lib/interfaces/item.interface.ts +438 -0
- package/src/lib/players/lookup/lookup.directive.ts +6 -0
- package/src/lib/players/lookup/lookup.item.component.ts +37 -0
- package/src/lib/players/lookup/lookup.item.ts +9 -0
- package/src/lib/players/lookup/lookup.player.component.ts +59 -0
- package/src/lib/players/lookup/lookup.selector.component.ts +41 -0
- package/src/lib/players/model/model.directive.ts +6 -0
- package/src/lib/players/model/model.item.component.spec.ts +311 -0
- package/src/lib/players/model/model.item.component.ts +3457 -0
- package/src/lib/players/model/model.item.ts +9 -0
- package/src/lib/players/model/model.player.component.ts +109 -0
- package/src/lib/players/model/model.selector.component.ts +59 -0
- package/src/lib/scheduler/scheduler.component.html +13 -0
- package/src/lib/scheduler/scheduler.component.scss +6 -0
- package/src/lib/scheduler/scheduler.component.ts +296 -0
- package/src/lib/scheduler/scheduler.routes.ts +15 -0
- package/src/lib/scheduler/schedulerdialog.component.html +72 -0
- package/src/lib/scheduler/schedulerdialog.component.ts +208 -0
- package/src/lib/scheduler/services/scheduler.service.ts +133 -0
- package/src/lib/services/auth-state.service.ts +129 -0
- package/src/lib/services/auth.interceptor.spec.ts +144 -0
- package/src/lib/services/auth.interceptor.ts +44 -0
- package/src/lib/services/cache.service.spec.ts +143 -0
- package/src/lib/services/cache.service.ts +71 -0
- package/src/lib/services/global-error-handler.spec.ts +39 -0
- package/src/lib/services/global-error-handler.ts +28 -0
- package/src/lib/services/global.service.spec.ts +801 -0
- package/src/lib/services/global.service.ts +724 -0
- package/src/lib/services/message.service.ts +556 -0
- package/src/lib/services/theme.service.ts +96 -0
- package/src/lib/template/authtemplate.component.html +6 -0
- package/src/lib/template/authtemplate.component.ts +13 -0
- package/src/lib/template/basetemplate.component.html +7 -0
- package/src/lib/template/basetemplate.component.ts +13 -0
- package/src/lib/template/index.ts +3 -0
- package/src/lib/template/modeltemplate.component.html +7 -0
- package/src/lib/template/modeltemplate.component.ts +21 -0
- package/src/lib/utils/piva.spec.ts +56 -0
- package/src/lib/utils/piva.ts +29 -0
- package/src/lib/validators/email.validator.spec.ts +57 -0
- package/src/lib/validators/email.validator.ts +17 -0
- package/src/lib/validators/equalPasswords.validator.spec.ts +54 -0
- package/src/lib/validators/equalPasswords.validator.ts +17 -0
- package/src/lib/validators/index.ts +2 -0
- package/src/lib/version.ts +1 -0
- package/src/public-api.ts +64 -0
- package/src/typings.d.ts +2 -0
- package/tsconfig.lib.json +18 -0
- package/tsconfig.lib.prod.json +9 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
<!-- register.component -->
|
|
2
|
+
<authtemplate>
|
|
3
|
+
<div class="auth-card">
|
|
4
|
+
<div class="auth-brand">
|
|
5
|
+
<div class="auth-logo">
|
|
6
|
+
<i class="bi bi-person-plus"></i>
|
|
7
|
+
</div>
|
|
8
|
+
<h1 class="auth-app-name">Newport</h1>
|
|
9
|
+
<p class="auth-subtitle">Crea il tuo account</p>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<form
|
|
13
|
+
[formGroup]="form"
|
|
14
|
+
(ngSubmit)="onSubmit(form.value)"
|
|
15
|
+
role="form"
|
|
16
|
+
aria-label="Modulo di registrazione"
|
|
17
|
+
autocomplete="off">
|
|
18
|
+
<!-- Login -->
|
|
19
|
+
<div class="auth-field">
|
|
20
|
+
<span class="auth-field-icon" aria-hidden="true"><i class="bi bi-person"></i></span>
|
|
21
|
+
<label for="login" class="visually-hidden">{{ msg?.get('app.login') }}</label>
|
|
22
|
+
<input
|
|
23
|
+
nvg
|
|
24
|
+
[attr.data-state]="0"
|
|
25
|
+
tabindex="1"
|
|
26
|
+
[formControl]="login"
|
|
27
|
+
type="text"
|
|
28
|
+
class="form-control auth-input"
|
|
29
|
+
id="login"
|
|
30
|
+
placeholder="{{ msg?.get('app.login') }}"
|
|
31
|
+
[attr.aria-label]="msg?.get('app.login')"
|
|
32
|
+
aria-required="true"
|
|
33
|
+
autocomplete="off" />
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<!-- Password + Generate -->
|
|
37
|
+
<div class="auth-field">
|
|
38
|
+
<span class="auth-field-icon" aria-hidden="true"><i class="bi bi-lock"></i></span>
|
|
39
|
+
<label for="password" class="visually-hidden">{{ msg?.get('app.password') }}</label>
|
|
40
|
+
<input
|
|
41
|
+
nvg
|
|
42
|
+
[attr.data-state]="0"
|
|
43
|
+
tabindex="2"
|
|
44
|
+
[formControl]="password"
|
|
45
|
+
[type]="showPassword ? 'text' : 'password'"
|
|
46
|
+
class="form-control auth-input auth-input-padded2"
|
|
47
|
+
id="password"
|
|
48
|
+
placeholder="{{ msg?.get('app.password') }}"
|
|
49
|
+
[attr.aria-label]="msg?.get('app.password')"
|
|
50
|
+
aria-required="true"
|
|
51
|
+
aria-describedby="password-strength"
|
|
52
|
+
autocomplete="off" />
|
|
53
|
+
<div class="auth-toggle-group">
|
|
54
|
+
<button
|
|
55
|
+
type="button"
|
|
56
|
+
class="auth-toggle-btn"
|
|
57
|
+
tabindex="-1"
|
|
58
|
+
[attr.aria-pressed]="showPassword"
|
|
59
|
+
[attr.aria-label]="showPassword ? 'Nascondi password' : 'Mostra password'"
|
|
60
|
+
(click)="showPassword = !showPassword"
|
|
61
|
+
[title]="showPassword ? 'Nascondi password' : 'Mostra password'">
|
|
62
|
+
<i
|
|
63
|
+
class="bi"
|
|
64
|
+
aria-hidden="true"
|
|
65
|
+
[class.bi-eye]="!showPassword"
|
|
66
|
+
[class.bi-eye-slash]="showPassword"></i>
|
|
67
|
+
</button>
|
|
68
|
+
<button
|
|
69
|
+
type="button"
|
|
70
|
+
class="auth-toggle-btn btn-generate"
|
|
71
|
+
tabindex="-1"
|
|
72
|
+
aria-label="Genera password sicura"
|
|
73
|
+
(click)="generatePassword()"
|
|
74
|
+
title="Genera password sicura">
|
|
75
|
+
<i class="bi bi-shield-lock" aria-hidden="true"></i>
|
|
76
|
+
</button>
|
|
77
|
+
</div>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
<!-- Password strength indicator -->
|
|
81
|
+
@if (password.value) {
|
|
82
|
+
<div
|
|
83
|
+
id="password-strength"
|
|
84
|
+
class="strength-meter"
|
|
85
|
+
role="status"
|
|
86
|
+
aria-live="polite"
|
|
87
|
+
[attr.aria-label]="'Sicurezza password: ' + passwordStrength.label">
|
|
88
|
+
<div class="strength-bar" role="presentation">
|
|
89
|
+
<div
|
|
90
|
+
class="strength-fill"
|
|
91
|
+
[style.width.%]="passwordStrength.score * 25"
|
|
92
|
+
[class.strength-weak]="passwordStrength.score === 1"
|
|
93
|
+
[class.strength-fair]="passwordStrength.score === 2"
|
|
94
|
+
[class.strength-good]="passwordStrength.score === 3"
|
|
95
|
+
[class.strength-strong]="passwordStrength.score === 4"></div>
|
|
96
|
+
</div>
|
|
97
|
+
<span
|
|
98
|
+
class="strength-label"
|
|
99
|
+
[class]="'strength-text-' + passwordStrength.score">
|
|
100
|
+
{{ passwordStrength.label }}
|
|
101
|
+
</span>
|
|
102
|
+
</div>
|
|
103
|
+
@if (passwordStrength.suggestions.length) {
|
|
104
|
+
<ul class="strength-suggestions" aria-label="Suggerimenti per migliorare la password">
|
|
105
|
+
@for (tip of passwordStrength.suggestions; track tip) {
|
|
106
|
+
<li>{{ tip }}</li>
|
|
107
|
+
}
|
|
108
|
+
</ul>
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
<!-- Confirm password -->
|
|
113
|
+
<div class="auth-field">
|
|
114
|
+
<span class="auth-field-icon" aria-hidden="true"><i class="bi bi-lock-fill"></i></span>
|
|
115
|
+
<label for="confirmpassword" class="visually-hidden">{{ msg?.get('app.repeatepassword') }}</label>
|
|
116
|
+
<input
|
|
117
|
+
nvg
|
|
118
|
+
[attr.data-state]="0"
|
|
119
|
+
tabindex="3"
|
|
120
|
+
[formControl]="confirmpassword"
|
|
121
|
+
[type]="showPassword ? 'text' : 'password'"
|
|
122
|
+
class="form-control auth-input"
|
|
123
|
+
id="confirmpassword"
|
|
124
|
+
placeholder="{{ msg?.get('app.repeatepassword') }}"
|
|
125
|
+
[attr.aria-label]="msg?.get('app.repeatepassword')"
|
|
126
|
+
aria-required="true"
|
|
127
|
+
[attr.aria-invalid]="showPasswordError || null" />
|
|
128
|
+
</div>
|
|
129
|
+
@if (showPasswordError) {
|
|
130
|
+
<div class="auth-error-msg" role="alert" aria-live="assertive">
|
|
131
|
+
{{ msg?.get('app.passworddoesntmatch') }}
|
|
132
|
+
</div>
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
<button
|
|
136
|
+
type="submit"
|
|
137
|
+
nvg
|
|
138
|
+
[attr.data-state]="0"
|
|
139
|
+
tabindex="4"
|
|
140
|
+
[disabled]="isSubmitDisabled"
|
|
141
|
+
[attr.aria-disabled]="isSubmitDisabled"
|
|
142
|
+
class="btn btn-primary btn-auth-submit">
|
|
143
|
+
{{ msg?.get('app.confirm') }}
|
|
144
|
+
</button>
|
|
145
|
+
|
|
146
|
+
<div class="auth-alt-action">
|
|
147
|
+
<span>Hai già un account?</span>
|
|
148
|
+
<a (click)="navigate('/auth/login')" class="auth-alt-link">{{
|
|
149
|
+
msg?.get('app.authtitle')
|
|
150
|
+
}}</a>
|
|
151
|
+
</div>
|
|
152
|
+
</form>
|
|
153
|
+
|
|
154
|
+
<alert></alert>
|
|
155
|
+
</div>
|
|
156
|
+
</authtemplate>
|
|
157
|
+
<!-- fine register.component -->
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Component,
|
|
3
|
+
AfterViewInit,
|
|
4
|
+
ChangeDetectionStrategy,
|
|
5
|
+
DestroyRef,
|
|
6
|
+
inject,
|
|
7
|
+
} from '@angular/core';
|
|
8
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
9
|
+
import {
|
|
10
|
+
FormGroup,
|
|
11
|
+
AbstractControl,
|
|
12
|
+
FormBuilder,
|
|
13
|
+
Validators,
|
|
14
|
+
ReactiveFormsModule,
|
|
15
|
+
} from '@angular/forms';
|
|
16
|
+
import { EqualPasswordsValidator } from '../validators/index';
|
|
17
|
+
|
|
18
|
+
import { GlobalService } from '../services/global.service';
|
|
19
|
+
|
|
20
|
+
import { AppMessageService } from '../services/message.service';
|
|
21
|
+
|
|
22
|
+
import { AlertService, UserService } from './services/index';
|
|
23
|
+
import { User } from '../auth/models/user';
|
|
24
|
+
import { AuthTemplateComponent } from '../template/authtemplate.component';
|
|
25
|
+
import { InputDirective } from '../directives/input.directive';
|
|
26
|
+
|
|
27
|
+
import { AlertComponent } from './alert.component';
|
|
28
|
+
|
|
29
|
+
interface PasswordStrength {
|
|
30
|
+
score: number;
|
|
31
|
+
label: string;
|
|
32
|
+
suggestions: string[];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@Component({
|
|
36
|
+
templateUrl: 'register.component.html',
|
|
37
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
38
|
+
providers: [AlertService, UserService],
|
|
39
|
+
imports: [
|
|
40
|
+
AuthTemplateComponent,
|
|
41
|
+
ReactiveFormsModule,
|
|
42
|
+
InputDirective,
|
|
43
|
+
AlertComponent,
|
|
44
|
+
],
|
|
45
|
+
})
|
|
46
|
+
export class RegisterComponent implements AfterViewInit {
|
|
47
|
+
fb = inject(FormBuilder);
|
|
48
|
+
usv = inject(UserService);
|
|
49
|
+
als = inject(AlertService);
|
|
50
|
+
gsv = inject(GlobalService);
|
|
51
|
+
msg = inject(AppMessageService);
|
|
52
|
+
|
|
53
|
+
public form!: FormGroup;
|
|
54
|
+
public login!: AbstractControl;
|
|
55
|
+
public name!: AbstractControl;
|
|
56
|
+
public email!: AbstractControl;
|
|
57
|
+
public password!: AbstractControl;
|
|
58
|
+
public confirmpassword!: AbstractControl;
|
|
59
|
+
public passwords!: FormGroup;
|
|
60
|
+
|
|
61
|
+
public submitted: boolean = false;
|
|
62
|
+
public loading = false;
|
|
63
|
+
public showPassword = false;
|
|
64
|
+
public passwordStrength: PasswordStrength = {
|
|
65
|
+
score: 0,
|
|
66
|
+
label: '',
|
|
67
|
+
suggestions: [],
|
|
68
|
+
};
|
|
69
|
+
private destroyRef = inject(DestroyRef);
|
|
70
|
+
|
|
71
|
+
constructor() {
|
|
72
|
+
const fb = this.fb;
|
|
73
|
+
|
|
74
|
+
this.form = fb.group({
|
|
75
|
+
login: ['', Validators.compose([Validators.required])],
|
|
76
|
+
passwords: fb.group(
|
|
77
|
+
{
|
|
78
|
+
password: [
|
|
79
|
+
'',
|
|
80
|
+
Validators.compose([Validators.required, Validators.minLength(8)]),
|
|
81
|
+
],
|
|
82
|
+
confirmpassword: ['', Validators.compose([Validators.required])],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
validator: EqualPasswordsValidator.validate(
|
|
86
|
+
'password',
|
|
87
|
+
'confirmpassword'
|
|
88
|
+
),
|
|
89
|
+
}
|
|
90
|
+
),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
this.login = this.form.controls['login'];
|
|
94
|
+
this.passwords = this.form.controls['passwords'] as FormGroup;
|
|
95
|
+
this.password = this.passwords.controls['password'];
|
|
96
|
+
this.confirmpassword = this.passwords.controls['confirmpassword'];
|
|
97
|
+
|
|
98
|
+
this.password.valueChanges
|
|
99
|
+
.pipe(takeUntilDestroyed(this.destroyRef))
|
|
100
|
+
.subscribe((value: string) => {
|
|
101
|
+
this.passwordStrength = this.evaluateStrength(value);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get showPasswordError() {
|
|
106
|
+
return !this.confirmpassword.valid && this.confirmpassword.touched;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get isSubmitDisabled() {
|
|
110
|
+
return this.loading || !this.form.valid;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Lifecycle: focuses the login field on first render. */
|
|
114
|
+
public ngAfterViewInit(): void {
|
|
115
|
+
const loginElement = document.getElementById('login');
|
|
116
|
+
loginElement?.focus();
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Generates a cryptographically random 16-char password satisfying all complexity rules. */
|
|
120
|
+
public generatePassword(): void {
|
|
121
|
+
const upper = 'ABCDEFGHJKLMNPQRSTUVWXYZ';
|
|
122
|
+
const lower = 'abcdefghjkmnpqrstuvwxyz';
|
|
123
|
+
const digits = '23456789';
|
|
124
|
+
const symbols = '!@#$%&*?';
|
|
125
|
+
const all = upper + lower + digits + symbols;
|
|
126
|
+
|
|
127
|
+
const array = new Uint32Array(16);
|
|
128
|
+
crypto.getRandomValues(array);
|
|
129
|
+
|
|
130
|
+
// Guarantee at least one from each category
|
|
131
|
+
const required = [
|
|
132
|
+
upper[array[0] % upper.length],
|
|
133
|
+
lower[array[1] % lower.length],
|
|
134
|
+
digits[array[2] % digits.length],
|
|
135
|
+
symbols[array[3] % symbols.length],
|
|
136
|
+
];
|
|
137
|
+
|
|
138
|
+
const rest: string[] = [];
|
|
139
|
+
for (let i = 4; i < 16; i++) {
|
|
140
|
+
rest.push(all[array[i] % all.length]);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Shuffle using Fisher-Yates with crypto random
|
|
144
|
+
const combined = [...required, ...rest];
|
|
145
|
+
const shuffleArray = new Uint32Array(combined.length);
|
|
146
|
+
crypto.getRandomValues(shuffleArray);
|
|
147
|
+
for (let i = combined.length - 1; i > 0; i--) {
|
|
148
|
+
const j = shuffleArray[i] % (i + 1);
|
|
149
|
+
[combined[i], combined[j]] = [combined[j], combined[i]];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const generated = combined.join('');
|
|
153
|
+
this.password.setValue(generated);
|
|
154
|
+
this.confirmpassword.setValue(generated);
|
|
155
|
+
this.showPassword = true;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Scores password strength (0–4) and returns a label with improvement suggestions. */
|
|
159
|
+
public evaluateStrength(value: string): PasswordStrength {
|
|
160
|
+
if (!value) {
|
|
161
|
+
return { score: 0, label: '', suggestions: [] };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
let score = 0;
|
|
165
|
+
const suggestions: string[] = [];
|
|
166
|
+
|
|
167
|
+
if (value.length >= 8) score++;
|
|
168
|
+
else suggestions.push('Almeno 8 caratteri');
|
|
169
|
+
|
|
170
|
+
if (value.length >= 12) score++;
|
|
171
|
+
else if (value.length >= 8)
|
|
172
|
+
suggestions.push('12+ caratteri per maggiore sicurezza');
|
|
173
|
+
|
|
174
|
+
if (/[a-z]/.test(value) && /[A-Z]/.test(value)) score++;
|
|
175
|
+
else suggestions.push('Maiuscole e minuscole');
|
|
176
|
+
|
|
177
|
+
if (/[0-9]/.test(value) && /[^a-zA-Z0-9]/.test(value)) score++;
|
|
178
|
+
else {
|
|
179
|
+
if (!/[0-9]/.test(value)) suggestions.push('Aggiungi numeri');
|
|
180
|
+
if (!/[^a-zA-Z0-9]/.test(value))
|
|
181
|
+
suggestions.push('Aggiungi simboli (!@#$%&*)');
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const labels = ['', 'Debole', 'Discreta', 'Buona', 'Forte'];
|
|
185
|
+
return { score, label: labels[score] || '', suggestions };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Submits the registration form: creates the user via UserService and redirects to login on success. */
|
|
189
|
+
public onSubmit(usr: any): void {
|
|
190
|
+
this.submitted = true;
|
|
191
|
+
if (this.form.valid) {
|
|
192
|
+
this.loading = true;
|
|
193
|
+
const user = new User();
|
|
194
|
+
user.login = usr.login;
|
|
195
|
+
user.name = usr.name === undefined ? '' : usr.name;
|
|
196
|
+
user.email = usr.email === undefined ? '' : usr.email;
|
|
197
|
+
user.password = usr.passwords.password;
|
|
198
|
+
user.token = '';
|
|
199
|
+
this.gsv.setCurrentUser(user);
|
|
200
|
+
this.usv.post(user).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
|
|
201
|
+
next: () => {
|
|
202
|
+
this.gsv.logOff();
|
|
203
|
+
this.als.success('Registration successful', true);
|
|
204
|
+
this.gsv.navigate('/auth/login');
|
|
205
|
+
},
|
|
206
|
+
error: () => {
|
|
207
|
+
this.als.error(this.msg.get('app.registerfailed'));
|
|
208
|
+
this.loading = false;
|
|
209
|
+
this.gsv.setCurrentUser(null);
|
|
210
|
+
},
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/** Delegates client-side navigation to GlobalService. */
|
|
216
|
+
public navigate(url: string) {
|
|
217
|
+
this.gsv.navigate(url);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Injectable, DestroyRef, inject } from '@angular/core';
|
|
2
|
+
import { Router, NavigationStart } from '@angular/router';
|
|
3
|
+
import { Observable, Subject } from 'rxjs';
|
|
4
|
+
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
5
|
+
import { filter } from 'rxjs/operators';
|
|
6
|
+
|
|
7
|
+
@Injectable()
|
|
8
|
+
export class AlertService {
|
|
9
|
+
private router = inject(Router);
|
|
10
|
+
private destroyRef = inject(DestroyRef);
|
|
11
|
+
|
|
12
|
+
private subject = new Subject<{ type: string; text: string } | null>();
|
|
13
|
+
private keepAfterNavigationChange = false;
|
|
14
|
+
|
|
15
|
+
constructor() {
|
|
16
|
+
// clear alert message on route change
|
|
17
|
+
this.router.events
|
|
18
|
+
.pipe(
|
|
19
|
+
filter(event => event instanceof NavigationStart),
|
|
20
|
+
takeUntilDestroyed(this.destroyRef)
|
|
21
|
+
)
|
|
22
|
+
.subscribe(() => {
|
|
23
|
+
if (this.keepAfterNavigationChange) {
|
|
24
|
+
this.keepAfterNavigationChange = false;
|
|
25
|
+
} else {
|
|
26
|
+
this.subject.next(null);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Emits a success alert message; optionally persists it across the next navigation. */
|
|
32
|
+
public success(message: string, keepAfterNavigationChange = true) {
|
|
33
|
+
this.keepAfterNavigationChange = keepAfterNavigationChange;
|
|
34
|
+
this.subject.next({ type: 'success', text: message });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Emits an error alert message; optionally persists it across the next navigation. */
|
|
38
|
+
public error(message: string, keepAfterNavigationChange = true) {
|
|
39
|
+
this.keepAfterNavigationChange = keepAfterNavigationChange;
|
|
40
|
+
this.subject.next({ type: 'error', text: message });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Returns an Observable that emits alert messages (or null to clear). */
|
|
44
|
+
public getMessage(): Observable<any> {
|
|
45
|
+
return this.subject.asObservable();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { inject } from '@angular/core';
|
|
2
|
+
import {
|
|
3
|
+
Router,
|
|
4
|
+
CanActivateFn,
|
|
5
|
+
ActivatedRouteSnapshot,
|
|
6
|
+
RouterStateSnapshot,
|
|
7
|
+
} from '@angular/router';
|
|
8
|
+
import { GlobalService } from '../../services/global.service';
|
|
9
|
+
|
|
10
|
+
export const authGuard: CanActivateFn = (
|
|
11
|
+
_route: ActivatedRouteSnapshot,
|
|
12
|
+
state: RouterStateSnapshot
|
|
13
|
+
): boolean => {
|
|
14
|
+
const gsv = inject(GlobalService);
|
|
15
|
+
const router = inject(Router);
|
|
16
|
+
|
|
17
|
+
if (gsv.isLogged()) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
gsv.setActiveLink('/auth/login');
|
|
21
|
+
router.navigate(['/auth/login'], {
|
|
22
|
+
queryParams: { returnUrl: state.url },
|
|
23
|
+
});
|
|
24
|
+
return false;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** @deprecated Use authGuard functional guard instead */
|
|
28
|
+
export const AuthGuard = authGuard;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { TestBed } from '@angular/core/testing';
|
|
2
|
+
import { provideHttpClient } from '@angular/common/http';
|
|
3
|
+
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
|
|
4
|
+
import { Router } from '@angular/router';
|
|
5
|
+
import { Location } from '@angular/common';
|
|
6
|
+
import { UserService } from './user.service';
|
|
7
|
+
import { GlobalService } from '../../services/global.service';
|
|
8
|
+
import { User } from '../models/index';
|
|
9
|
+
import { NEWPORT_CONFIG } from '../../config';
|
|
10
|
+
|
|
11
|
+
const TEST_CONFIG = { apiUrl: 'http://test/', logicFactory: () => {}, projectsInfoLoader: () => Promise.resolve({}) };
|
|
12
|
+
|
|
13
|
+
describe('UserService', () => {
|
|
14
|
+
let svc: UserService;
|
|
15
|
+
let httpMock: HttpTestingController;
|
|
16
|
+
let gsv: GlobalService;
|
|
17
|
+
|
|
18
|
+
const routerStub = { navigateByUrl: jest.fn().mockResolvedValue(true) };
|
|
19
|
+
const locationStub = { normalize: (url: string) => url };
|
|
20
|
+
|
|
21
|
+
const expectedUrl = 'http://test/users';
|
|
22
|
+
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
localStorage.clear();
|
|
25
|
+
jest.clearAllMocks();
|
|
26
|
+
TestBed.configureTestingModule({
|
|
27
|
+
providers: [
|
|
28
|
+
UserService,
|
|
29
|
+
GlobalService,
|
|
30
|
+
{ provide: Router, useValue: routerStub },
|
|
31
|
+
{ provide: Location, useValue: locationStub },
|
|
32
|
+
{ provide: NEWPORT_CONFIG, useValue: TEST_CONFIG },
|
|
33
|
+
provideHttpClient(),
|
|
34
|
+
provideHttpClientTesting(),
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
svc = TestBed.inject(UserService);
|
|
38
|
+
httpMock = TestBed.inject(HttpTestingController);
|
|
39
|
+
gsv = TestBed.inject(GlobalService);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
afterEach(() => httpMock.verify());
|
|
43
|
+
|
|
44
|
+
describe('put() — login', () => {
|
|
45
|
+
it('sends a PUT request to the users endpoint', () => {
|
|
46
|
+
const credentials = Object.assign(new User(), { login: 'mario', password: 'secret' });
|
|
47
|
+
const mockResponse: Partial<User> = { token: 'jwt-token', name: 'Mario' };
|
|
48
|
+
|
|
49
|
+
svc.put(credentials).subscribe(res => {
|
|
50
|
+
expect(res.token).toBe('jwt-token');
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const req = httpMock.expectOne(expectedUrl);
|
|
54
|
+
expect(req.request.method).toBe('PUT');
|
|
55
|
+
req.flush(mockResponse);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('includes the Authorization header from GlobalService.jwt()', () => {
|
|
59
|
+
const credentials = new User();
|
|
60
|
+
svc.put(credentials).subscribe();
|
|
61
|
+
|
|
62
|
+
const req = httpMock.expectOne(expectedUrl);
|
|
63
|
+
expect(req.request.headers.get('Content-Type')).toBe('application/json');
|
|
64
|
+
req.flush({});
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
describe('post() — register', () => {
|
|
69
|
+
it('sends a POST request to the users endpoint', () => {
|
|
70
|
+
const newUser = Object.assign(new User(), { login: 'mario', password: 'secret' });
|
|
71
|
+
const mockResponse: Partial<User> = { token: 'new-token', name: 'Mario' };
|
|
72
|
+
|
|
73
|
+
svc.post(newUser).subscribe(res => {
|
|
74
|
+
expect(res.token).toBe('new-token');
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const req = httpMock.expectOne(expectedUrl);
|
|
78
|
+
expect(req.request.method).toBe('POST');
|
|
79
|
+
req.flush(mockResponse);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe('error handling', () => {
|
|
84
|
+
it('maps a server 400 error to a typed Error observable', done => {
|
|
85
|
+
// post() has no retry, so exactly one request is made
|
|
86
|
+
svc.post(new User()).subscribe({
|
|
87
|
+
error: (err: Error) => {
|
|
88
|
+
expect(err).toBeInstanceOf(Error);
|
|
89
|
+
expect(err.message).toContain('400');
|
|
90
|
+
done();
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
httpMock.expectOne(expectedUrl).flush(
|
|
95
|
+
{ message: 'Bad Request' },
|
|
96
|
+
{ status: 400, statusText: 'Bad Request' }
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('maps a network error to a typed Error observable', done => {
|
|
101
|
+
svc.post(new User()).subscribe({
|
|
102
|
+
error: (err: Error) => {
|
|
103
|
+
expect(err).toBeInstanceOf(Error);
|
|
104
|
+
expect(err.message).toBeTruthy();
|
|
105
|
+
done();
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
httpMock.expectOne(expectedUrl).error(new ErrorEvent('error', { message: 'Network failure' }));
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Injectable, inject } from '@angular/core';
|
|
2
|
+
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
|
|
3
|
+
import { Observable, throwError } from 'rxjs';
|
|
4
|
+
import { GlobalService } from '../../services/global.service';
|
|
5
|
+
|
|
6
|
+
import { User } from '../models/index';
|
|
7
|
+
|
|
8
|
+
import { catchError, retry } from 'rxjs/operators';
|
|
9
|
+
|
|
10
|
+
@Injectable()
|
|
11
|
+
export class UserService {
|
|
12
|
+
private http = inject(HttpClient);
|
|
13
|
+
private globalService = inject(GlobalService);
|
|
14
|
+
|
|
15
|
+
private usersUrl: string = this.globalService.restService() + 'users';
|
|
16
|
+
|
|
17
|
+
public put(user: User): Observable<User> {
|
|
18
|
+
return this.http
|
|
19
|
+
.put<User>(this.usersUrl, user, this.globalService.jwt())
|
|
20
|
+
.pipe(retry(3), catchError(this.handleError));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public post(user: User): Observable<User> {
|
|
24
|
+
return this.http
|
|
25
|
+
.post<User>(this.usersUrl, user, this.globalService.jwt())
|
|
26
|
+
.pipe(catchError(this.handleError));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private handleError(error: HttpErrorResponse): Observable<never> {
|
|
30
|
+
let errorMessage = 'An error occurred';
|
|
31
|
+
|
|
32
|
+
if (error.error instanceof ErrorEvent) {
|
|
33
|
+
// Client-side or network error
|
|
34
|
+
errorMessage = `Client Error: ${error.error.message}`;
|
|
35
|
+
console.error('Client-side error:', error.error.message);
|
|
36
|
+
} else {
|
|
37
|
+
// Backend error
|
|
38
|
+
errorMessage = `Server Error (${error.status}): ${error.message}`;
|
|
39
|
+
console.error(
|
|
40
|
+
`Backend returned code ${error.status}, body:`,
|
|
41
|
+
error.error
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return throwError(() => new Error(errorMessage));
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<div [@flipState]="flip()" class="card">
|
|
2
|
+
<div #front [hidden]="flip() === 'back'" class="flipfront">
|
|
3
|
+
<div>
|
|
4
|
+
<div class="card-body">
|
|
5
|
+
<div class="card-title fs-18px">
|
|
6
|
+
<span class="align-self-start" [ngClass]="stateClass()"> </span>
|
|
7
|
+
<span
|
|
8
|
+
class="bg-transparent truncate"
|
|
9
|
+
title="{{ msg?.getProjectDescription('foldertitle') }}">
|
|
10
|
+
{{ msg?.getProjectDescription('foldertitle') }}
|
|
11
|
+
</span>
|
|
12
|
+
</div>
|
|
13
|
+
@for (front of item().frontitems; track front) {
|
|
14
|
+
<div>
|
|
15
|
+
<div>{{ front.name_id }}</div>
|
|
16
|
+
<div id="{{ front.id }}">{{ front.value_id }}</div>
|
|
17
|
+
</div>
|
|
18
|
+
}
|
|
19
|
+
</div>
|
|
20
|
+
<div class="nopaddingtae">
|
|
21
|
+
<div class="p-1">
|
|
22
|
+
<div
|
|
23
|
+
title="{{ msg?.get('app.modify') }}"
|
|
24
|
+
class="btn bi bi-box-arrow-right"
|
|
25
|
+
(click)="activateitem(item().key_id)"></div>
|
|
26
|
+
<div
|
|
27
|
+
title="{{ msg?.get('app.minus') }}"
|
|
28
|
+
class="btn bi bi-patch-minus"
|
|
29
|
+
(click)="deleteitem(item().key_id)"></div>
|
|
30
|
+
<div
|
|
31
|
+
[hidden]="printHidden"
|
|
32
|
+
title="{{ msg?.get('app.print') }}"
|
|
33
|
+
class="btn bi bi-printer"
|
|
34
|
+
(click)="printitem(item().key_id)"></div>
|
|
35
|
+
<div
|
|
36
|
+
[hidden]="summaryHidden"
|
|
37
|
+
title="{{ msg?.get('app.back') }}"
|
|
38
|
+
class="btn bi bi-arrow-clockwise"
|
|
39
|
+
(click)="toggleFlip(item().key_id)"></div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
<div [hidden]="flip() === 'front'" class="flipback">
|
|
45
|
+
<div class="card">
|
|
46
|
+
<div class="card-body">
|
|
47
|
+
<h5 class="card-title">
|
|
48
|
+
<span class="align-self-start" [ngClass]="stateClass()"> </span>
|
|
49
|
+
<span
|
|
50
|
+
class="bg-transparent truncate"
|
|
51
|
+
title="{{ msg?.getProjectDescription('foldertitle') }}">
|
|
52
|
+
{{ msg?.getProjectDescription('foldertitle') }}
|
|
53
|
+
</span>
|
|
54
|
+
</h5>
|
|
55
|
+
@for (back of item().backitems; track back) {
|
|
56
|
+
<div>
|
|
57
|
+
<div id="{{ back.id }}">{{ back.name_id }}</div>
|
|
58
|
+
<div>{{ back.value_id }}</div>
|
|
59
|
+
</div>
|
|
60
|
+
}
|
|
61
|
+
</div>
|
|
62
|
+
<div class="nopaddingtae">
|
|
63
|
+
<div class="p-1">
|
|
64
|
+
<div
|
|
65
|
+
title="{{ msg?.get('app.front') }}"
|
|
66
|
+
class="btn bi bi-arrow-counterclockwise"
|
|
67
|
+
(click)="toggleFlip(item().key_id)"></div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|