@flusys/ng-auth 9.1.0 → 9.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.
Files changed (37) hide show
  1. package/README.md +2 -0
  2. package/fesm2022/flusys-ng-auth-administration-page.component-DmN4KqiR.mjs +146 -0
  3. package/fesm2022/flusys-ng-auth-administration-page.component-DmN4KqiR.mjs.map +1 -0
  4. package/fesm2022/flusys-ng-auth-branch-form.component-Df_sMC2G.mjs +743 -0
  5. package/fesm2022/flusys-ng-auth-branch-form.component-Df_sMC2G.mjs.map +1 -0
  6. package/fesm2022/flusys-ng-auth-branch-list.component-CWat6QWN.mjs +775 -0
  7. package/fesm2022/flusys-ng-auth-branch-list.component-CWat6QWN.mjs.map +1 -0
  8. package/fesm2022/flusys-ng-auth-company-form.component-BvZ4Pn8j.mjs +198 -0
  9. package/fesm2022/flusys-ng-auth-company-form.component-BvZ4Pn8j.mjs.map +1 -0
  10. package/fesm2022/flusys-ng-auth-company-list.component-Corh9AIh.mjs +241 -0
  11. package/fesm2022/flusys-ng-auth-company-list.component-Corh9AIh.mjs.map +1 -0
  12. package/fesm2022/flusys-ng-auth-extension-order.util-2XWSLqii.mjs +6 -0
  13. package/fesm2022/flusys-ng-auth-extension-order.util-2XWSLqii.mjs.map +1 -0
  14. package/fesm2022/flusys-ng-auth-forgot-password-page.component-BRTVZPX6.mjs +384 -0
  15. package/fesm2022/flusys-ng-auth-forgot-password-page.component-BRTVZPX6.mjs.map +1 -0
  16. package/fesm2022/flusys-ng-auth-login-page.component-DQl52Qau.mjs +1074 -0
  17. package/fesm2022/flusys-ng-auth-login-page.component-DQl52Qau.mjs.map +1 -0
  18. package/fesm2022/flusys-ng-auth-profile-page.component-b-8rPEXQ.mjs +1792 -0
  19. package/fesm2022/flusys-ng-auth-profile-page.component-b-8rPEXQ.mjs.map +1 -0
  20. package/fesm2022/flusys-ng-auth-register-page.component-D99y1jiA.mjs +1513 -0
  21. package/fesm2022/flusys-ng-auth-register-page.component-D99y1jiA.mjs.map +1 -0
  22. package/fesm2022/flusys-ng-auth-reset-password-page.component-C0tN_AW1.mjs +538 -0
  23. package/fesm2022/flusys-ng-auth-reset-password-page.component-C0tN_AW1.mjs.map +1 -0
  24. package/fesm2022/flusys-ng-auth-social-callback-page.component-gTD7_LJY.mjs +217 -0
  25. package/fesm2022/flusys-ng-auth-social-callback-page.component-gTD7_LJY.mjs.map +1 -0
  26. package/fesm2022/flusys-ng-auth-social-config-page.component-DpGmcb9n.mjs +541 -0
  27. package/fesm2022/flusys-ng-auth-social-config-page.component-DpGmcb9n.mjs.map +1 -0
  28. package/fesm2022/flusys-ng-auth-user-form.component-CM9UYc0q.mjs +1171 -0
  29. package/fesm2022/flusys-ng-auth-user-form.component-CM9UYc0q.mjs.map +1 -0
  30. package/fesm2022/flusys-ng-auth-user-list.component-Bew7gz-H.mjs +1046 -0
  31. package/fesm2022/flusys-ng-auth-user-list.component-Bew7gz-H.mjs.map +1 -0
  32. package/fesm2022/flusys-ng-auth-verify-email-page.component-DB9iKvt3.mjs +545 -0
  33. package/fesm2022/flusys-ng-auth-verify-email-page.component-DB9iKvt3.mjs.map +1 -0
  34. package/fesm2022/flusys-ng-auth.mjs +3030 -10735
  35. package/fesm2022/flusys-ng-auth.mjs.map +1 -1
  36. package/package.json +5 -5
  37. package/types/flusys-ng-auth.d.ts +978 -1558
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flusys-ng-auth-user-list.component-Bew7gz-H.mjs","names":[],"sources":["../../../projects/ng-auth/pages/users/user-list.component.ts"],"sourcesContent":["import { Component, computed, inject, signal } from '@angular/core';\nimport { NgTemplateOutlet } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { Router } from '@angular/router';\nimport { APP_CONFIG, isCompanyFeatureEnabled } from '@flusys/ng-core';\nimport {\n CardComponent,\n ConfirmationService,\n DialogComponent,\n FButtonDirective,\n IconFieldComponent,\n InputIconComponent,\n InputTextDirective,\n MessageService,\n TableComponent,\n TagComponent,\n TooltipDirective,\n UiIconComponent,\n} from '@flusys/ng-ui';\nimport {\n HasPermissionDirective,\n TranslatePipe,\n TranslateService,\n USER_PERMISSIONS,\n} from '@flusys/ng-shared';\nimport { UserBranchPermissionDialogComponent } from '../../components/user-branch-permission-dialog.component';\nimport { UserCompanyPermissionDialogComponent } from '../../components/user-company-permission-dialog.component';\nimport { IUser } from '../../interfaces/user.interface';\nimport {\n AUTH_USER_DETAIL_CONFIG,\n AUTH_USER_LIST_EXTENSION,\n IUserListExtraAction,\n IUserListExtraColumn,\n} from '../../providers/auth-extension.providers';\nimport { sortByOrder } from '../../providers/extension-order.util';\nimport { UserApiService } from '../../services/user-api.service';\n\n@Component({\n selector: 'lib-user-list',\n imports: [\n NgTemplateOutlet,\n FormsModule,\n CardComponent,\n DialogComponent,\n FButtonDirective,\n IconFieldComponent,\n InputIconComponent,\n InputTextDirective,\n TableComponent,\n TagComponent,\n TooltipDirective,\n UiIconComponent,\n HasPermissionDirective,\n TranslatePipe,\n UserCompanyPermissionDialogComponent,\n UserBranchPermissionDialogComponent,\n ],\n template: `\n <f-card>\n <ng-template #header>\n <div\n class=\"flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 p-4\"\n >\n <h2 class=\"text-xl font-semibold m-0\">\n {{ 'administrative.user.title' | translate }}\n </h2>\n <div class=\"flex items-center gap-3 w-full sm:w-auto\">\n <f-iconfield class=\"w-full sm:w-64\">\n <f-inputicon icon=\"search\" />\n <input\n fInputText\n type=\"text\"\n [ngModel]=\"searchTerm()\"\n (ngModelChange)=\"onSearchChange($event)\"\n [placeholder]=\"'shared.search' | translate\"\n />\n </f-iconfield>\n <button\n fButton\n *hasPermission=\"USER_PERMISSIONS.CREATE\"\n (click)=\"onCreate()\"\n >\n <ui-icon pButtonIcon name=\"plus\"></ui-icon>\n <span pButtonLabel>{{ 'administrative.user.add' | translate }}</span>\n </button>\n </div>\n </div>\n </ng-template>\n\n <div class=\"overflow-x-auto\">\n <f-table\n [value]=\"users()\"\n [loading]=\"isLoading()\"\n [paginator]=\"total() > 0\"\n [rows]=\"10\"\n [rowsPerPageOptions]=\"[10, 25, 50]\"\n [totalRecords]=\"total()\"\n [lazy]=\"true\"\n (onLazyLoad)=\"onLazyLoad($event)\"\n [scrollable]=\"true\"\n >\n <ng-template #header>\n <tr>\n <th>{{ 'administrative.user.name' | translate }}</th>\n <th>{{ 'administrative.email.label' | translate }}</th>\n <th>{{ 'administrative.phone.label' | translate }}</th>\n <th>{{ 'administrative.user.email.verified' | translate }}</th>\n <th>{{ 'shared.status' | translate }}</th>\n @for (col of extraColumns(); track col.key) {\n <th\n [class]=\"col.styleClass ?? ''\"\n [style.width]=\"col.width ?? 'auto'\"\n >\n {{ col.headerKey | translate: null : col.header }}\n </th>\n }\n <th class=\"w-[180px]\">{{ 'shared.actions' | translate }}</th>\n </tr>\n </ng-template>\n <ng-template #body let-user>\n <tr>\n <td>{{ user.name }}</td>\n <td>{{ user.email }}</td>\n <td>{{ user.phone || '-' }}</td>\n <td>\n <f-tag\n [value]=\"\n user.emailVerified\n ? ('shared.verified' | translate)\n : ('shared.unverified' | translate)\n \"\n [severity]=\"user.emailVerified ? 'success' : 'warn'\"\n />\n </td>\n <td>\n <f-tag\n [value]=\"\n user.isActive\n ? ('shared.active' | translate)\n : ('shared.inactive' | translate)\n \"\n [severity]=\"user.isActive ? 'success' : 'danger'\"\n />\n </td>\n @for (col of extraColumns(); track col.key) {\n <td [class]=\"col.styleClass ?? ''\">\n @if (col.template) {\n <ng-container\n [ngTemplateOutlet]=\"col.template\"\n [ngTemplateOutletContext]=\"{ $implicit: user }\"\n />\n } @else if (col.field) {\n {{ getFieldValue(user, col.field) ?? '-' }}\n }\n </td>\n }\n <td>\n <div class=\"flex gap-1 flex-wrap\">\n @if (hasUserDetailView()) {\n <button\n fButton\n [rounded]=\"true\"\n [text]=\"true\"\n severity=\"info\"\n [fTooltip]=\"'shared.view.details' | translate\"\n (click)=\"onViewDetails(user)\"\n >\n <ui-icon pButtonIcon name=\"eye\"></ui-icon>\n </button>\n }\n <button\n fButton\n *hasPermission=\"USER_PERMISSIONS.UPDATE\"\n [rounded]=\"true\"\n [text]=\"true\"\n severity=\"info\"\n [fTooltip]=\"'shared.edit' | translate\"\n (click)=\"onEdit(user)\"\n >\n <ui-icon pButtonIcon name=\"pencil\"></ui-icon>\n </button>\n @if (companyFeatureEnabled()) {\n <button\n fButton\n *hasPermission=\"USER_PERMISSIONS.UPDATE\"\n [rounded]=\"true\"\n [text]=\"true\"\n severity=\"secondary\"\n [fTooltip]=\"\n 'administrative.permission.company.permissions'\n | translate\n \"\n (click)=\"onManageCompanyPermissions(user)\"\n >\n <ui-icon pButtonIcon name=\"building\"></ui-icon>\n </button>\n <button\n fButton\n *hasPermission=\"USER_PERMISSIONS.UPDATE\"\n [rounded]=\"true\"\n [text]=\"true\"\n severity=\"secondary\"\n [fTooltip]=\"\n 'administrative.permission.branch.permissions'\n | translate\n \"\n (click)=\"onManageBranchPermissions(user)\"\n >\n <ui-icon pButtonIcon name=\"sitemap\"></ui-icon>\n </button>\n }\n @for (action of extraActions(); track action.key) {\n @if (isActionVisible(action, user)) {\n <button\n fButton\n [rounded]=\"true\"\n [text]=\"true\"\n [severity]=\"action.severity ?? 'secondary'\"\n [fTooltip]=\"\n action.labelKey | translate: null : action.label\n \"\n [disabled]=\"isActionDisabled(action, user)\"\n (click)=\"onExtraAction(action, user)\"\n >\n <ui-icon pButtonIcon [name]=\"action.icon\"></ui-icon>\n </button>\n }\n }\n <button\n fButton\n *hasPermission=\"USER_PERMISSIONS.DELETE\"\n [rounded]=\"true\"\n [text]=\"true\"\n severity=\"danger\"\n [fTooltip]=\"'shared.delete' | translate\"\n (click)=\"onDelete(user)\"\n >\n <ui-icon pButtonIcon name=\"trash\"></ui-icon>\n </button>\n </div>\n </td>\n </tr>\n </ng-template>\n <ng-template #emptymessage>\n <tr>\n <td [attr.colspan]=\"tableColspan()\" class=\"text-center py-8\">\n {{ 'administrative.user.no.users' | translate }}\n </td>\n </tr>\n </ng-template>\n </f-table>\n </div>\n </f-card>\n\n @if (companyFeatureEnabled()) {\n <lib-user-company-permission-dialog\n [visible]=\"showCompanyDialog()\"\n [user]=\"selectedUser()\"\n (closed)=\"showCompanyDialog.set(false)\"\n (permissionsChanged)=\"loadUsers()\"\n />\n\n <lib-user-branch-permission-dialog\n [visible]=\"showBranchDialog()\"\n [user]=\"selectedUser()\"\n (closed)=\"showBranchDialog.set(false)\"\n (permissionsChanged)=\"loadUsers()\"\n />\n }\n\n @if (hasUserDetailView() && showUserDetailDialog()) {\n <f-dialog\n [header]=\"'administrative.user.details' | translate\"\n [visible]=\"showUserDetailDialog()\"\n [modal]=\"true\"\n [style]=\"{ width: userDetailConfig?.width ?? '600px' }\"\n [draggable]=\"false\"\n [resizable]=\"false\"\n (onHide)=\"closeUserDetailDialog()\"\n >\n @if (selectedUser(); as user) {\n <div class=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\n <div class=\"flex flex-col gap-2\">\n <label class=\"font-medium text-muted-color\">{{\n 'administrative.user.name' | translate\n }}</label>\n <span class=\"text-color\">{{ user.name }}</span>\n </div>\n <div class=\"flex flex-col gap-2\">\n <label class=\"font-medium text-muted-color\">{{\n 'administrative.email.label' | translate\n }}</label>\n <span class=\"text-color\">{{ user.email }}</span>\n </div>\n <div class=\"flex flex-col gap-2\">\n <label class=\"font-medium text-muted-color\">{{\n 'administrative.phone.label' | translate\n }}</label>\n <span class=\"text-color\">{{ user.phone || '-' }}</span>\n </div>\n <div class=\"flex flex-col gap-2\">\n <label class=\"font-medium text-muted-color\">{{\n 'shared.status' | translate\n }}</label>\n <f-tag\n [value]=\"\n user.isActive\n ? ('shared.active' | translate)\n : ('shared.inactive' | translate)\n \"\n [severity]=\"user.isActive ? 'success' : 'danger'\"\n />\n </div>\n <div class=\"flex flex-col gap-2\">\n <label class=\"font-medium text-muted-color\">{{\n 'administrative.user.email.verified' | translate\n }}</label>\n <f-tag\n [value]=\"\n user.emailVerified\n ? ('shared.verified' | translate)\n : ('shared.unverified' | translate)\n \"\n [severity]=\"user.emailVerified ? 'success' : 'warn'\"\n />\n </div>\n @for (col of extraColumns(); track col.key) {\n <div class=\"flex flex-col gap-2\">\n <label class=\"font-medium text-muted-color\">{{\n col.headerKey | translate: null : col.header\n }}</label>\n <span class=\"text-color\">{{\n getFieldValue(user, col.field ?? col.key) ?? '-'\n }}</span>\n </div>\n }\n </div>\n <div class=\"flex justify-end gap-2 mt-6\">\n @if (userDetailConfig?.allowEdit) {\n <button\n fButton\n (click)=\"onEdit(user); closeUserDetailDialog()\"\n >\n <ui-icon pButtonIcon name=\"pencil\"></ui-icon>\n <span pButtonLabel>{{ 'shared.edit' | translate }}</span>\n </button>\n }\n <button\n fButton\n severity=\"secondary\"\n [outlined]=\"true\"\n (click)=\"closeUserDetailDialog()\"\n >\n <span pButtonLabel>{{ 'shared.close' | translate }}</span>\n </button>\n </div>\n }\n </f-dialog>\n }\n `,\n})\nexport class UserListComponent {\n readonly USER_PERMISSIONS = USER_PERMISSIONS;\n\n private readonly router = inject(Router);\n private readonly appConfig = inject(APP_CONFIG);\n private readonly messageService = inject(MessageService);\n private readonly confirmationService = inject(ConfirmationService);\n private readonly userService = inject(UserApiService);\n\n private readonly userListExtension = inject(AUTH_USER_LIST_EXTENSION, {\n optional: true,\n });\n readonly userDetailConfig = inject(AUTH_USER_DETAIL_CONFIG, {\n optional: true,\n });\n private readonly translateAdapter = inject(TranslateService, {\n optional: true,\n });\n\n private translate(\n key: string,\n vars?: Record<string, string | number>,\n ): string {\n return this.translateAdapter?.translate(key, vars) ?? key;\n }\n\n readonly users = this.userService.data;\n readonly isLoading = this.userService.isLoading;\n readonly total = this.userService.total;\n readonly companyFeatureEnabled = computed(() =>\n isCompanyFeatureEnabled(this.appConfig),\n );\n\n readonly extraColumns = computed<IUserListExtraColumn[]>(() => {\n if (!this.userListExtension) return [];\n return sortByOrder(this.userListExtension.getExtraColumns());\n });\n\n readonly extraActions = computed<IUserListExtraAction[]>(() => {\n if (!this.userListExtension) return [];\n return sortByOrder(this.userListExtension.getExtraActions());\n });\n\n readonly hasUserDetailView = computed(() => !!this.userDetailConfig);\n readonly tableColspan = computed(() => 6 + this.extraColumns().length);\n\n readonly showCompanyDialog = signal(false);\n readonly showBranchDialog = signal(false);\n readonly showUserDetailDialog = signal(false);\n readonly selectedUser = signal<IUser | null>(null);\n\n private readonly _searchTerm = signal('');\n readonly searchTerm = this._searchTerm.asReadonly();\n\n getFieldValue(user: IUser, field: string): unknown {\n const parts = field.split('.');\n let value: unknown = user;\n for (const part of parts) {\n if (value === null || value === undefined) return undefined;\n value = (value as Record<string, unknown>)[part];\n }\n return value;\n }\n\n isActionVisible(action: IUserListExtraAction, user: IUser): boolean {\n return action.isVisible ? action.isVisible(user) : true;\n }\n\n isActionDisabled(action: IUserListExtraAction, user: IUser): boolean {\n return action.isDisabled ? action.isDisabled(user) : false;\n }\n\n onExtraAction(action: IUserListExtraAction, user: IUser): void {\n action.onClick(user);\n }\n\n onViewDetails(user: IUser): void {\n if (!this.userDetailConfig) return;\n\n if (this.userDetailConfig.viewMode === 'page') {\n this.router.navigate(['/administration/users', user.id]);\n } else {\n this.selectedUser.set(user);\n this.showUserDetailDialog.set(true);\n }\n }\n\n closeUserDetailDialog(): void {\n this.showUserDetailDialog.set(false);\n this.selectedUser.set(null);\n }\n\n loadUsers(page = 0, pageSize = 10): void {\n this.userService.fetchList(this._searchTerm(), {\n pagination: { currentPage: page, pageSize },\n });\n }\n\n onLazyLoad(event: { first?: number | null; rows?: number | null }): void {\n const first = event.first ?? 0;\n const rows = event.rows ?? 10;\n const page = Math.floor(first / rows);\n this.loadUsers(page, rows);\n }\n\n onSearchChange(value: string): void {\n this._searchTerm.set(value);\n this.loadUsers(0);\n }\n\n onCreate(): void {\n this.router.navigate(['/administration/users/new']);\n }\n\n onEdit(user: IUser): void {\n this.router.navigate(['/administration/users', user.id]);\n }\n\n onManageCompanyPermissions(user: IUser): void {\n this.openPermissionDialog(user, this.showCompanyDialog);\n }\n\n onManageBranchPermissions(user: IUser): void {\n this.openPermissionDialog(user, this.showBranchDialog);\n }\n\n private openPermissionDialog(\n user: IUser,\n dialogSignal: typeof this.showCompanyDialog,\n ): void {\n this.selectedUser.set(user);\n dialogSignal.set(true);\n }\n\n onDelete(user: IUser): void {\n this.confirmationService.confirm({\n message: this.translate('shared.confirm.delete.item', {\n name: user.name,\n }),\n header: this.translate('administrative.user.delete.title'),\n icon: 'exclamation-triangle',\n acceptButtonStyleClass: 'p-button-danger',\n accept: async () => {\n try {\n const response = await this.userService.delete({\n id: user.id,\n type: 'delete',\n });\n this.messageService.add({\n severity: 'success',\n summary: this.translate('shared.success'),\n detail: response.messageKey\n ? this.translate(\n response.messageKey,\n response.messageVariables as Record<string, string | number>,\n )\n : response.message,\n });\n this.userService.reload();\n } catch {}\n },\n });\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,545 @@
1
+ import { AuthApiService } from "./flusys-ng-auth.mjs";
2
+ import * as i0 from "@angular/core";
3
+ import { Component, DestroyRef, PLATFORM_ID, TransferState, computed, effect, inject, makeStateKey, signal } from "@angular/core";
4
+ import { ActivatedRoute, RouterLink } from "@angular/router";
5
+ import { TranslatePipe, TranslateService } from "@flusys/ng-shared";
6
+ import { isPlatformBrowser } from "@angular/common";
7
+ import { FButtonDirective, InputTextDirective, MessageService, UiIconComponent } from "@flusys/ng-ui";
8
+ import { AppFloatingConfigurator } from "@flusys/ng-layout";
9
+ import { takeUntilDestroyed, toSignal } from "@angular/core/rxjs-interop";
10
+ import { FormField, email, form, required } from "@angular/forms/signals";
11
+ const VERIFY_EMAIL_KEY = makeStateKey("verify-email-result");
12
+ var VerifyEmailPageComponent = class VerifyEmailPageComponent {
13
+ platformId = inject(PLATFORM_ID);
14
+ transferState = inject(TransferState);
15
+ destroyRef = inject(DestroyRef);
16
+ route = inject(ActivatedRoute);
17
+ messageService = inject(MessageService);
18
+ authApi = inject(AuthApiService);
19
+ translateAdapter = inject(TranslateService, { optional: true });
20
+ translate(key, vars) {
21
+ return this.translateAdapter?.translate(key, vars) ?? key;
22
+ }
23
+ queryParams = toSignal(this.route.queryParams);
24
+ verificationTriggered = false;
25
+ isLoading = signal(false, ...ngDevMode ? [{ debugName: "isLoading" }] : /* istanbul ignore next */ []);
26
+ pageState = signal("verifying", ...ngDevMode ? [{ debugName: "pageState" }] : /* istanbul ignore next */ []);
27
+ resendSuccess = signal(false, ...ngDevMode ? [{ debugName: "resendSuccess" }] : /* istanbul ignore next */ []);
28
+ submittedEmail = signal("", ...ngDevMode ? [{ debugName: "submittedEmail" }] : /* istanbul ignore next */ []);
29
+ resendFormModel = signal({ email: "" }, ...ngDevMode ? [{ debugName: "resendFormModel" }] : /* istanbul ignore next */ []);
30
+ resendForm = form(this.resendFormModel, (f) => {
31
+ required(f.email, { message: this.translate("auth.validation.email.required") });
32
+ email(f.email, { message: this.translate("shared.invalid.email") });
33
+ });
34
+ isResendFormValid = computed(() => this.resendForm().valid(), ...ngDevMode ? [{ debugName: "isResendFormValid" }] : /* istanbul ignore next */ []);
35
+ constructor() {
36
+ effect(() => {
37
+ const params = this.queryParams();
38
+ if (!params || this.verificationTriggered) return;
39
+ this.verificationTriggered = true;
40
+ const token = params["token"] || "";
41
+ if (!token) {
42
+ this.pageState.set("resend");
43
+ return;
44
+ }
45
+ const cachedResult = this.transferState.get(VERIFY_EMAIL_KEY, null);
46
+ if (cachedResult) {
47
+ this.transferState.remove(VERIFY_EMAIL_KEY);
48
+ this.handleVerificationResult(cachedResult === "success");
49
+ return;
50
+ }
51
+ this.verifyEmail(token);
52
+ });
53
+ }
54
+ verifyEmail(token) {
55
+ this.authApi.verifyEmail(token).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
56
+ next: () => {
57
+ if (!isPlatformBrowser(this.platformId)) this.transferState.set(VERIFY_EMAIL_KEY, "success");
58
+ this.handleVerificationResult(true);
59
+ },
60
+ error: () => {
61
+ if (!isPlatformBrowser(this.platformId)) this.transferState.set(VERIFY_EMAIL_KEY, "error");
62
+ this.handleVerificationResult(false);
63
+ }
64
+ });
65
+ }
66
+ handleVerificationResult(success) {
67
+ if (success) {
68
+ this.pageState.set("success");
69
+ if (isPlatformBrowser(this.platformId)) this.messageService.add({
70
+ severity: "success",
71
+ summary: this.translate("shared.success"),
72
+ detail: this.translate("auth.verify.email.success")
73
+ });
74
+ } else this.pageState.set("invalid-token");
75
+ }
76
+ showResendForm() {
77
+ this.pageState.set("resend");
78
+ this.resendSuccess.set(false);
79
+ }
80
+ onResend(event) {
81
+ event.preventDefault();
82
+ if (!this.isResendFormValid()) {
83
+ this.messageService.add({
84
+ severity: "error",
85
+ summary: this.translate("shared.validation.error"),
86
+ detail: this.translate("shared.invalid.email")
87
+ });
88
+ return;
89
+ }
90
+ this.isLoading.set(true);
91
+ const email = this.resendFormModel().email;
92
+ this.authApi.resendVerification(email).pipe(takeUntilDestroyed(this.destroyRef)).subscribe({
93
+ next: (response) => this.handleResendComplete(email, response.messageKey),
94
+ error: () => {
95
+ this.isLoading.set(false);
96
+ }
97
+ });
98
+ }
99
+ handleResendComplete(email, messageKey) {
100
+ this.isLoading.set(false);
101
+ this.messageService.add({
102
+ severity: "success",
103
+ summary: this.translate("shared.success"),
104
+ detail: this.translate(messageKey ?? "auth.verify.email.sent")
105
+ });
106
+ this.submittedEmail.set(email);
107
+ this.resendSuccess.set(true);
108
+ }
109
+ static ɵfac = i0.ɵɵngDeclareFactory({
110
+ minVersion: "12.0.0",
111
+ version: "22.2.1",
112
+ ngImport: i0,
113
+ type: VerifyEmailPageComponent,
114
+ deps: [],
115
+ target: i0.ɵɵFactoryTarget.Component
116
+ });
117
+ static ɵcmp = i0.ɵɵngDeclareComponent({
118
+ minVersion: "17.0.0",
119
+ version: "22.2.1",
120
+ type: VerifyEmailPageComponent,
121
+ isStandalone: true,
122
+ selector: "lib-verify-email-page",
123
+ ngImport: i0,
124
+ template: `
125
+ <app-floating-configurator />
126
+ <div
127
+ class="bg-surface-ground flex items-center justify-center min-h-screen"
128
+ >
129
+ <div
130
+ class="flex flex-col items-center justify-center w-full max-w-[32rem] px-4"
131
+ >
132
+ <div
133
+ class="w-full"
134
+ style="border-radius: var(--f-content-border-radius); padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)"
135
+ >
136
+ <div
137
+ class="w-full py-8 px-4 sm:py-12 sm:px-8 md:py-16 md:px-12"
138
+ style="border-radius: var(--f-content-border-radius); background: var(--f-content-background)"
139
+ >
140
+ @switch (pageState()) {
141
+ @case ('verifying') {
142
+ <div class="text-center">
143
+ <div class="flex justify-center mb-6">
144
+ <ui-icon name="loader-circle" spin class="text-8xl text-primary"></ui-icon>
145
+ </div>
146
+ <div class="text-3xl font-medium mb-4 text-color">
147
+ {{ 'auth.verify.email.verifying' | translate }}
148
+ </div>
149
+ <p class="text-muted-color font-medium">
150
+ {{ 'auth.verify.email.please.wait' | translate }}
151
+ </p>
152
+ </div>
153
+ }
154
+
155
+ @case ('success') {
156
+ <div class="text-center">
157
+ <div class="flex justify-center mb-6">
158
+ <ui-icon name="check-circle" class="text-8xl text-success"></ui-icon>
159
+ </div>
160
+ <div class="text-3xl font-medium mb-4 text-color">
161
+ {{ 'auth.verify.email.success' | translate }}
162
+ </div>
163
+ <p class="text-muted-color font-medium mb-6">
164
+ {{ 'auth.verify.email.success.message' | translate }}
165
+ </p>
166
+ <a routerLink="/auth/login">
167
+ <button
168
+ fButton
169
+ class="w-full"
170
+ >
171
+ <span pButtonLabel>{{ 'auth.login.sign.in' | translate }}</span>
172
+ </button>
173
+ </a>
174
+ </div>
175
+ }
176
+
177
+ @case ('invalid-token') {
178
+ <div class="text-center">
179
+ <div class="flex justify-center mb-6">
180
+ <ui-icon name="times-circle" class="text-8xl text-danger"></ui-icon>
181
+ </div>
182
+ <div class="text-3xl font-medium mb-4 text-color">
183
+ {{ 'auth.verify.email.failed' | translate }}
184
+ </div>
185
+ <p class="text-muted-color font-medium mb-6">
186
+ {{ 'auth.verify.email.invalid.link' | translate }}
187
+ </p>
188
+ <button
189
+ fButton
190
+ class="w-full mb-4"
191
+ (click)="showResendForm()"
192
+ >
193
+ <span pButtonLabel>{{ 'auth.verify.email.resend.email' | translate }}</span>
194
+ </button>
195
+ <div class="mt-4">
196
+ <a
197
+ routerLink="/auth/login"
198
+ class="font-semibold text-primary cursor-pointer"
199
+ >
200
+ {{ 'auth.login.back.to.sign.in' | translate }}
201
+ </a>
202
+ </div>
203
+ </div>
204
+ }
205
+
206
+ @case ('resend') {
207
+ <div class="text-center mb-8">
208
+ <div class="text-3xl font-medium mb-4 text-color">
209
+ {{ 'auth.verify.email.resend.title' | translate }}
210
+ </div>
211
+ <span class="text-muted-color font-medium">
212
+ {{ 'auth.verify.email.resend.subtitle' | translate }}
213
+ </span>
214
+ </div>
215
+
216
+ @if (!resendSuccess()) {
217
+ <form (submit)="onResend($event)">
218
+ <label
219
+ for="email"
220
+ class="block text-lg font-medium mb-2 text-color"
221
+ >
222
+ {{ 'auth.email.label' | translate }}
223
+ </label>
224
+ <input
225
+ fInputText
226
+ id="email"
227
+ type="email"
228
+ [placeholder]="'auth.email.placeholder' | translate"
229
+ class="w-full mb-6"
230
+ [formField]="resendForm.email"
231
+ autocomplete="email"
232
+ />
233
+
234
+ <button
235
+ fButton
236
+ class="w-full mb-4"
237
+ type="submit"
238
+ [disabled]="!isResendFormValid() || isLoading()"
239
+ >
240
+ @if (isLoading()) {
241
+ <ui-icon pButtonIcon name="spinner" [spin]="true"></ui-icon>
242
+ }
243
+ <span pButtonLabel>{{ 'auth.verify.email.resend' | translate }}</span>
244
+ </button>
245
+
246
+ <div class="flex items-center justify-center gap-2">
247
+ <a
248
+ routerLink="/auth/login"
249
+ class="font-semibold text-primary cursor-pointer"
250
+ >
251
+ {{ 'auth.login.back.to.sign.in' | translate }}
252
+ </a>
253
+ </div>
254
+ </form>
255
+ } @else {
256
+ <div class="text-center">
257
+ <div class="flex justify-center mb-6">
258
+ <ui-icon name="envelope" class="text-8xl text-primary"></ui-icon>
259
+ </div>
260
+ <p class="text-muted-color font-medium mb-6">
261
+ {{ 'auth.verify.email.sent' | translate }}
262
+ </p>
263
+ <a
264
+ routerLink="/auth/login"
265
+ class="font-semibold text-primary cursor-pointer"
266
+ >
267
+ {{ 'auth.login.back.to.sign.in' | translate }}
268
+ </a>
269
+ </div>
270
+ }
271
+ }
272
+ }
273
+ </div>
274
+ </div>
275
+ </div>
276
+ </div>
277
+ `,
278
+ isInline: true,
279
+ dependencies: [
280
+ {
281
+ kind: "directive",
282
+ type: RouterLink,
283
+ selector: "[routerLink]",
284
+ inputs: [
285
+ "target",
286
+ "queryParams",
287
+ "fragment",
288
+ "queryParamsHandling",
289
+ "state",
290
+ "info",
291
+ "relativeTo",
292
+ "preserveFragment",
293
+ "skipLocationChange",
294
+ "replaceUrl",
295
+ "browserUrl",
296
+ "routerLink"
297
+ ]
298
+ },
299
+ {
300
+ kind: "directive",
301
+ type: FButtonDirective,
302
+ selector: "[fButton]",
303
+ inputs: [
304
+ "severity",
305
+ "text",
306
+ "outlined",
307
+ "link",
308
+ "variant",
309
+ "rounded",
310
+ "raised",
311
+ "fluid",
312
+ "disabled",
313
+ "loading",
314
+ "loadingIcon",
315
+ "size",
316
+ "label",
317
+ "icon",
318
+ "iconPos",
319
+ "ariaLabel",
320
+ "plain"
321
+ ]
322
+ },
323
+ {
324
+ kind: "directive",
325
+ type: InputTextDirective,
326
+ selector: "[fInputText]",
327
+ inputs: [
328
+ "invalid",
329
+ "touched",
330
+ "variant",
331
+ "size",
332
+ "fluid"
333
+ ]
334
+ },
335
+ {
336
+ kind: "component",
337
+ type: UiIconComponent,
338
+ selector: "ui-icon",
339
+ inputs: [
340
+ "name",
341
+ "size",
342
+ "spin",
343
+ "fill",
344
+ "severity"
345
+ ]
346
+ },
347
+ {
348
+ kind: "directive",
349
+ type: FormField,
350
+ selector: "[formField]",
351
+ inputs: ["formField"],
352
+ exportAs: ["formField"]
353
+ },
354
+ {
355
+ kind: "component",
356
+ type: AppFloatingConfigurator,
357
+ selector: "app-floating-configurator"
358
+ },
359
+ {
360
+ kind: "pipe",
361
+ type: TranslatePipe,
362
+ name: "translate"
363
+ }
364
+ ]
365
+ });
366
+ };
367
+ i0.ɵɵngDeclareClassMetadata({
368
+ minVersion: "12.0.0",
369
+ version: "22.2.1",
370
+ ngImport: i0,
371
+ type: VerifyEmailPageComponent,
372
+ decorators: [{
373
+ type: Component,
374
+ args: [{
375
+ selector: "lib-verify-email-page",
376
+ imports: [
377
+ RouterLink,
378
+ FButtonDirective,
379
+ InputTextDirective,
380
+ UiIconComponent,
381
+ FormField,
382
+ AppFloatingConfigurator,
383
+ TranslatePipe
384
+ ],
385
+ template: `
386
+ <app-floating-configurator />
387
+ <div
388
+ class="bg-surface-ground flex items-center justify-center min-h-screen"
389
+ >
390
+ <div
391
+ class="flex flex-col items-center justify-center w-full max-w-[32rem] px-4"
392
+ >
393
+ <div
394
+ class="w-full"
395
+ style="border-radius: var(--f-content-border-radius); padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)"
396
+ >
397
+ <div
398
+ class="w-full py-8 px-4 sm:py-12 sm:px-8 md:py-16 md:px-12"
399
+ style="border-radius: var(--f-content-border-radius); background: var(--f-content-background)"
400
+ >
401
+ @switch (pageState()) {
402
+ @case ('verifying') {
403
+ <div class="text-center">
404
+ <div class="flex justify-center mb-6">
405
+ <ui-icon name="loader-circle" spin class="text-8xl text-primary"></ui-icon>
406
+ </div>
407
+ <div class="text-3xl font-medium mb-4 text-color">
408
+ {{ 'auth.verify.email.verifying' | translate }}
409
+ </div>
410
+ <p class="text-muted-color font-medium">
411
+ {{ 'auth.verify.email.please.wait' | translate }}
412
+ </p>
413
+ </div>
414
+ }
415
+
416
+ @case ('success') {
417
+ <div class="text-center">
418
+ <div class="flex justify-center mb-6">
419
+ <ui-icon name="check-circle" class="text-8xl text-success"></ui-icon>
420
+ </div>
421
+ <div class="text-3xl font-medium mb-4 text-color">
422
+ {{ 'auth.verify.email.success' | translate }}
423
+ </div>
424
+ <p class="text-muted-color font-medium mb-6">
425
+ {{ 'auth.verify.email.success.message' | translate }}
426
+ </p>
427
+ <a routerLink="/auth/login">
428
+ <button
429
+ fButton
430
+ class="w-full"
431
+ >
432
+ <span pButtonLabel>{{ 'auth.login.sign.in' | translate }}</span>
433
+ </button>
434
+ </a>
435
+ </div>
436
+ }
437
+
438
+ @case ('invalid-token') {
439
+ <div class="text-center">
440
+ <div class="flex justify-center mb-6">
441
+ <ui-icon name="times-circle" class="text-8xl text-danger"></ui-icon>
442
+ </div>
443
+ <div class="text-3xl font-medium mb-4 text-color">
444
+ {{ 'auth.verify.email.failed' | translate }}
445
+ </div>
446
+ <p class="text-muted-color font-medium mb-6">
447
+ {{ 'auth.verify.email.invalid.link' | translate }}
448
+ </p>
449
+ <button
450
+ fButton
451
+ class="w-full mb-4"
452
+ (click)="showResendForm()"
453
+ >
454
+ <span pButtonLabel>{{ 'auth.verify.email.resend.email' | translate }}</span>
455
+ </button>
456
+ <div class="mt-4">
457
+ <a
458
+ routerLink="/auth/login"
459
+ class="font-semibold text-primary cursor-pointer"
460
+ >
461
+ {{ 'auth.login.back.to.sign.in' | translate }}
462
+ </a>
463
+ </div>
464
+ </div>
465
+ }
466
+
467
+ @case ('resend') {
468
+ <div class="text-center mb-8">
469
+ <div class="text-3xl font-medium mb-4 text-color">
470
+ {{ 'auth.verify.email.resend.title' | translate }}
471
+ </div>
472
+ <span class="text-muted-color font-medium">
473
+ {{ 'auth.verify.email.resend.subtitle' | translate }}
474
+ </span>
475
+ </div>
476
+
477
+ @if (!resendSuccess()) {
478
+ <form (submit)="onResend($event)">
479
+ <label
480
+ for="email"
481
+ class="block text-lg font-medium mb-2 text-color"
482
+ >
483
+ {{ 'auth.email.label' | translate }}
484
+ </label>
485
+ <input
486
+ fInputText
487
+ id="email"
488
+ type="email"
489
+ [placeholder]="'auth.email.placeholder' | translate"
490
+ class="w-full mb-6"
491
+ [formField]="resendForm.email"
492
+ autocomplete="email"
493
+ />
494
+
495
+ <button
496
+ fButton
497
+ class="w-full mb-4"
498
+ type="submit"
499
+ [disabled]="!isResendFormValid() || isLoading()"
500
+ >
501
+ @if (isLoading()) {
502
+ <ui-icon pButtonIcon name="spinner" [spin]="true"></ui-icon>
503
+ }
504
+ <span pButtonLabel>{{ 'auth.verify.email.resend' | translate }}</span>
505
+ </button>
506
+
507
+ <div class="flex items-center justify-center gap-2">
508
+ <a
509
+ routerLink="/auth/login"
510
+ class="font-semibold text-primary cursor-pointer"
511
+ >
512
+ {{ 'auth.login.back.to.sign.in' | translate }}
513
+ </a>
514
+ </div>
515
+ </form>
516
+ } @else {
517
+ <div class="text-center">
518
+ <div class="flex justify-center mb-6">
519
+ <ui-icon name="envelope" class="text-8xl text-primary"></ui-icon>
520
+ </div>
521
+ <p class="text-muted-color font-medium mb-6">
522
+ {{ 'auth.verify.email.sent' | translate }}
523
+ </p>
524
+ <a
525
+ routerLink="/auth/login"
526
+ class="font-semibold text-primary cursor-pointer"
527
+ >
528
+ {{ 'auth.login.back.to.sign.in' | translate }}
529
+ </a>
530
+ </div>
531
+ }
532
+ }
533
+ }
534
+ </div>
535
+ </div>
536
+ </div>
537
+ </div>
538
+ `
539
+ }]
540
+ }],
541
+ ctorParameters: () => []
542
+ });
543
+ export { VerifyEmailPageComponent };
544
+
545
+ //# sourceMappingURL=flusys-ng-auth-verify-email-page.component-DB9iKvt3.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"flusys-ng-auth-verify-email-page.component-DB9iKvt3.mjs","names":[],"sources":["../../../projects/ng-auth/pages/verify-email/verify-email-page.component.ts"],"sourcesContent":["import { isPlatformBrowser } from '@angular/common';\nimport {\n Component,\n computed,\n DestroyRef,\n effect,\n inject,\n makeStateKey,\n PLATFORM_ID,\n signal,\n TransferState,\n} from '@angular/core';\nimport { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';\nimport {\n email as emailValidator,\n form,\n FormField,\n required,\n} from '@angular/forms/signals';\nimport { ActivatedRoute, RouterLink } from '@angular/router';\nimport { AppFloatingConfigurator } from '@flusys/ng-layout';\nimport { FButtonDirective, InputTextDirective, MessageService, UiIconComponent } from '@flusys/ng-ui';\nimport {\n TranslatePipe,\n TranslateService,\n} from '@flusys/ng-shared';\nimport { AuthApiService } from '../../services/auth-api.service';\n\nconst VERIFY_EMAIL_KEY = makeStateKey<'success' | 'error'>(\n 'verify-email-result',\n);\n\ninterface IResendFormModel {\n email: string;\n}\n\ntype PageState = 'verifying' | 'success' | 'invalid-token' | 'resend';\n\n@Component({\n selector: 'lib-verify-email-page',\n imports: [\n RouterLink,\n FButtonDirective,\n InputTextDirective,\n UiIconComponent,\n FormField,\n AppFloatingConfigurator,\n TranslatePipe,\n ],\n template: `\n <app-floating-configurator />\n <div\n class=\"bg-surface-ground flex items-center justify-center min-h-screen\"\n >\n <div\n class=\"flex flex-col items-center justify-center w-full max-w-[32rem] px-4\"\n >\n <div\n class=\"w-full\"\n style=\"border-radius: var(--f-content-border-radius); padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)\"\n >\n <div\n class=\"w-full py-8 px-4 sm:py-12 sm:px-8 md:py-16 md:px-12\"\n style=\"border-radius: var(--f-content-border-radius); background: var(--f-content-background)\"\n >\n @switch (pageState()) {\n @case ('verifying') {\n <div class=\"text-center\">\n <div class=\"flex justify-center mb-6\">\n <ui-icon name=\"loader-circle\" spin class=\"text-8xl text-primary\"></ui-icon>\n </div>\n <div class=\"text-3xl font-medium mb-4 text-color\">\n {{ 'auth.verify.email.verifying' | translate }}\n </div>\n <p class=\"text-muted-color font-medium\">\n {{ 'auth.verify.email.please.wait' | translate }}\n </p>\n </div>\n }\n\n @case ('success') {\n <div class=\"text-center\">\n <div class=\"flex justify-center mb-6\">\n <ui-icon name=\"check-circle\" class=\"text-8xl text-success\"></ui-icon>\n </div>\n <div class=\"text-3xl font-medium mb-4 text-color\">\n {{ 'auth.verify.email.success' | translate }}\n </div>\n <p class=\"text-muted-color font-medium mb-6\">\n {{ 'auth.verify.email.success.message' | translate }}\n </p>\n <a routerLink=\"/auth/login\">\n <button\n fButton\n class=\"w-full\"\n >\n <span pButtonLabel>{{ 'auth.login.sign.in' | translate }}</span>\n </button>\n </a>\n </div>\n }\n\n @case ('invalid-token') {\n <div class=\"text-center\">\n <div class=\"flex justify-center mb-6\">\n <ui-icon name=\"times-circle\" class=\"text-8xl text-danger\"></ui-icon>\n </div>\n <div class=\"text-3xl font-medium mb-4 text-color\">\n {{ 'auth.verify.email.failed' | translate }}\n </div>\n <p class=\"text-muted-color font-medium mb-6\">\n {{ 'auth.verify.email.invalid.link' | translate }}\n </p>\n <button\n fButton\n class=\"w-full mb-4\"\n (click)=\"showResendForm()\"\n >\n <span pButtonLabel>{{ 'auth.verify.email.resend.email' | translate }}</span>\n </button>\n <div class=\"mt-4\">\n <a\n routerLink=\"/auth/login\"\n class=\"font-semibold text-primary cursor-pointer\"\n >\n {{ 'auth.login.back.to.sign.in' | translate }}\n </a>\n </div>\n </div>\n }\n\n @case ('resend') {\n <div class=\"text-center mb-8\">\n <div class=\"text-3xl font-medium mb-4 text-color\">\n {{ 'auth.verify.email.resend.title' | translate }}\n </div>\n <span class=\"text-muted-color font-medium\">\n {{ 'auth.verify.email.resend.subtitle' | translate }}\n </span>\n </div>\n\n @if (!resendSuccess()) {\n <form (submit)=\"onResend($event)\">\n <label\n for=\"email\"\n class=\"block text-lg font-medium mb-2 text-color\"\n >\n {{ 'auth.email.label' | translate }}\n </label>\n <input\n fInputText\n id=\"email\"\n type=\"email\"\n [placeholder]=\"'auth.email.placeholder' | translate\"\n class=\"w-full mb-6\"\n [formField]=\"resendForm.email\"\n autocomplete=\"email\"\n />\n\n <button\n fButton\n class=\"w-full mb-4\"\n type=\"submit\"\n [disabled]=\"!isResendFormValid() || isLoading()\"\n >\n @if (isLoading()) {\n <ui-icon pButtonIcon name=\"spinner\" [spin]=\"true\"></ui-icon>\n }\n <span pButtonLabel>{{ 'auth.verify.email.resend' | translate }}</span>\n </button>\n\n <div class=\"flex items-center justify-center gap-2\">\n <a\n routerLink=\"/auth/login\"\n class=\"font-semibold text-primary cursor-pointer\"\n >\n {{ 'auth.login.back.to.sign.in' | translate }}\n </a>\n </div>\n </form>\n } @else {\n <div class=\"text-center\">\n <div class=\"flex justify-center mb-6\">\n <ui-icon name=\"envelope\" class=\"text-8xl text-primary\"></ui-icon>\n </div>\n <p class=\"text-muted-color font-medium mb-6\">\n {{ 'auth.verify.email.sent' | translate }}\n </p>\n <a\n routerLink=\"/auth/login\"\n class=\"font-semibold text-primary cursor-pointer\"\n >\n {{ 'auth.login.back.to.sign.in' | translate }}\n </a>\n </div>\n }\n }\n }\n </div>\n </div>\n </div>\n </div>\n `,\n})\nexport class VerifyEmailPageComponent {\n private readonly platformId = inject(PLATFORM_ID);\n private readonly transferState = inject(TransferState);\n private readonly destroyRef = inject(DestroyRef);\n private readonly route = inject(ActivatedRoute);\n private readonly messageService = inject(MessageService);\n private readonly authApi = inject(AuthApiService);\n private readonly translateAdapter = inject(TranslateService, {\n optional: true,\n });\n\n private translate(\n key: string,\n vars?: Record<string, string | number>,\n ): string {\n return this.translateAdapter?.translate(key, vars) ?? key;\n }\n\n private readonly queryParams = toSignal(this.route.queryParams);\n private verificationTriggered = false;\n\n readonly isLoading = signal(false);\n readonly pageState = signal<PageState>('verifying');\n readonly resendSuccess = signal(false);\n readonly submittedEmail = signal('');\n readonly resendFormModel = signal<IResendFormModel>({ email: '' });\n readonly resendForm = form(this.resendFormModel, (f) => {\n required(f.email, {\n message: this.translate('auth.validation.email.required'),\n });\n emailValidator(f.email, {\n message: this.translate('shared.invalid.email'),\n });\n });\n readonly isResendFormValid = computed(() => this.resendForm().valid());\n\n constructor() {\n effect(() => {\n const params = this.queryParams();\n if (!params || this.verificationTriggered) return;\n\n this.verificationTriggered = true;\n const token = params['token'] || '';\n if (!token) {\n this.pageState.set('resend');\n return;\n }\n\n // Check if we already have result from SSR (avoid duplicate API call)\n const cachedResult = this.transferState.get(VERIFY_EMAIL_KEY, null);\n if (cachedResult) {\n this.transferState.remove(VERIFY_EMAIL_KEY);\n this.handleVerificationResult(cachedResult === 'success');\n return;\n }\n\n this.verifyEmail(token);\n });\n }\n\n private verifyEmail(token: string): void {\n this.authApi\n .verifyEmail(token)\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe({\n next: () => {\n // Store result for browser hydration (SSR only)\n if (!isPlatformBrowser(this.platformId)) {\n this.transferState.set(VERIFY_EMAIL_KEY, 'success');\n }\n this.handleVerificationResult(true);\n },\n error: () => {\n if (!isPlatformBrowser(this.platformId)) {\n this.transferState.set(VERIFY_EMAIL_KEY, 'error');\n }\n this.handleVerificationResult(false);\n },\n });\n }\n\n private handleVerificationResult(success: boolean): void {\n if (success) {\n this.pageState.set('success');\n if (isPlatformBrowser(this.platformId)) {\n this.messageService.add({\n severity: 'success',\n summary: this.translate('shared.success'),\n detail: this.translate('auth.verify.email.success'),\n });\n }\n } else {\n this.pageState.set('invalid-token');\n }\n }\n\n showResendForm(): void {\n this.pageState.set('resend');\n this.resendSuccess.set(false);\n }\n\n onResend(event: Event): void {\n event.preventDefault();\n\n if (!this.isResendFormValid()) {\n this.messageService.add({\n severity: 'error',\n summary: this.translate('shared.validation.error'),\n detail: this.translate('shared.invalid.email'),\n });\n return;\n }\n\n this.isLoading.set(true);\n const email = this.resendFormModel().email;\n\n // Always show success (security: don't reveal if email exists)\n this.authApi\n .resendVerification(email)\n .pipe(takeUntilDestroyed(this.destroyRef))\n .subscribe({\n next: (response) =>\n this.handleResendComplete(email, response.messageKey),\n error: () => {\n //handle by interceptor\n this.isLoading.set(false);\n },\n });\n }\n\n private handleResendComplete(email: string, messageKey?: string): void {\n this.isLoading.set(false);\n this.messageService.add({\n severity: 'success',\n summary: this.translate('shared.success'),\n detail: this.translate(messageKey ?? 'auth.verify.email.sent'),\n });\n this.submittedEmail.set(email);\n this.resendSuccess.set(true);\n }\n}\n"],"mappings":"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"}