@flusys/ng-auth 1.0.0-beta → 1.1.0-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,12 @@
1
1
  import * as i1 from '@angular/common/http';
2
2
  import { HttpClient } from '@angular/common/http';
3
3
  import * as i0 from '@angular/core';
4
- import { inject, Injectable, signal, computed, effect, PLATFORM_ID, Component, DestroyRef, input, output } from '@angular/core';
5
- import { APP_CONFIG, getServiceUrl, isCompanyFeatureEnabled } from '@flusys/ng-core';
6
- import * as i2$1 from '@angular/router';
4
+ import { inject, Injectable, signal, computed, effect, PLATFORM_ID, Component, DestroyRef, input, ChangeDetectionStrategy, output, makeStateKey, TransferState } from '@angular/core';
5
+ import { APP_CONFIG, getServiceUrl, isCompanyFeatureEnabled, isFeatureEnabled, getPermissionMode } from '@flusys/ng-core';
6
+ import * as i2 from '@angular/router';
7
7
  import { Router, RouterOutlet, RouterLink, RouterLinkActive, ActivatedRoute } from '@angular/router';
8
8
  import { of, timeout, switchMap, map, tap, catchError, shareReplay, finalize, BehaviorSubject, throwError, filter, take, firstValueFrom } from 'rxjs';
9
- import { ApiResourceService, CookieService, USER_PROVIDER, COMPANY_API_PROVIDER, USER_PERMISSION_PROVIDER, AUTH_STATE_PROVIDER, AngularModule, PrimeModule } from '@flusys/ng-shared';
9
+ import { ApiResourceService, CookieService, USER_PROVIDER, COMPANY_API_PROVIDER, USER_PERMISSION_PROVIDER, AUTH_STATE_PROVIDER, AngularModule, PrimeModule, FileUrlService, PROFILE_UPLOAD_PROVIDER, PROFILE_PERMISSION_PROVIDER, FileUploaderComponent } from '@flusys/ng-shared';
10
10
  import { map as map$1, catchError as catchError$1, tap as tap$1 } from 'rxjs/operators';
11
11
  import { isPlatformBrowser, CommonModule } from '@angular/common';
12
12
  import * as i2$2 from 'primeng/api';
@@ -14,24 +14,31 @@ import { MessageService, ConfirmationService } from 'primeng/api';
14
14
  import { LAYOUT_AUTH_STATE, LAYOUT_AUTH_API, AppFloatingConfigurator } from '@flusys/ng-layout';
15
15
  import { form, required, FormField, email, minLength } from '@angular/forms/signals';
16
16
  import * as i1$1 from '@angular/forms';
17
- import * as i2 from 'primeng/inputtext';
18
17
  import * as i3 from 'primeng/button';
19
- import * as i4 from 'primeng/card';
20
- import * as i1$2 from 'primeng/tag';
18
+ import * as i3$1 from 'primeng/card';
19
+ import * as i4 from 'primeng/inputtext';
20
+ import * as i4$2 from 'primeng/tag';
21
21
  import { TagModule } from 'primeng/tag';
22
- import * as i3$1 from 'primeng/tooltip';
23
- import * as i4$1 from 'primeng/table';
24
- import * as i7 from 'primeng/select';
22
+ import * as i4$1 from 'primeng/select';
23
+ import * as i3$2 from 'primeng/table';
24
+ import * as i5 from 'primeng/tooltip';
25
25
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
26
- import * as i5 from 'primeng/dialog';
26
+ import * as i2$1 from 'primeng/avatar';
27
+ import * as i5$1 from 'primeng/password';
28
+ import * as i6 from 'primeng/skeleton';
29
+ import * as i5$2 from 'primeng/dialog';
27
30
  import { DialogModule } from 'primeng/dialog';
28
- import * as i6 from 'primeng/progressspinner';
31
+ import * as i6$1 from 'primeng/progressspinner';
29
32
  import { ProgressSpinnerModule } from 'primeng/progressspinner';
30
- import * as i4$2 from 'primeng/checkbox';
33
+ import * as i4$3 from 'primeng/checkbox';
31
34
 
32
- // =============================================================================
33
35
  // AUTH INTERFACES - Matching NestJS Auth Package DTOs
34
- // =============================================================================
36
+ /**
37
+ * Type guard to check if response requires email verification
38
+ */
39
+ function isEmailVerificationRequired(response) {
40
+ return 'requiresEmailVerification' in response && response.requiresEmailVerification === true;
41
+ }
35
42
  /**
36
43
  * Type guard to check if response requires company selection
37
44
  */
@@ -42,7 +49,7 @@ function isCompanySelectionResponse(response) {
42
49
  * Type guard to check if response is a successful login
43
50
  */
44
51
  function isLoginResponse(response) {
45
- return 'accessToken' in response && !('requiresSelection' in response);
52
+ return 'accessToken' in response && !('requiresSelection' in response) && !('requiresEmailVerification' in response);
46
53
  }
47
54
 
48
55
  // =============================================================================
@@ -148,6 +155,22 @@ class AuthApiService {
148
155
  newPassword,
149
156
  });
150
157
  }
158
+ /**
159
+ * Resend verification email (unauthenticated)
160
+ */
161
+ resendVerification(email) {
162
+ return this.http.post(`${this.baseUrl}/resend-verification`, { email });
163
+ }
164
+ /**
165
+ * Verify email with token
166
+ * Returns IMessageResponse for existing user verification
167
+ * Returns IRegistrationResponse for pending registration completion
168
+ */
169
+ verifyEmail(token) {
170
+ return this.http.post(`${this.baseUrl}/verify-email`, {
171
+ token,
172
+ });
173
+ }
151
174
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AuthApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
152
175
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: AuthApiService, providedIn: 'root' });
153
176
  }
@@ -764,6 +787,10 @@ const AUTH_ENDPOINTS = {
764
787
  REGISTER: '/auth/register',
765
788
  SELECT: '/auth/select',
766
789
  SWITCH_COMPANY: '/auth/switch-company',
790
+ VERIFY_EMAIL: '/auth/verify-email',
791
+ FORGOT_PASSWORD: '/auth/forgot-password',
792
+ RESET_PASSWORD: '/auth/reset-password',
793
+ RESEND_VERIFICATION: '/auth/resend-verification',
767
794
  };
768
795
  /**
769
796
  * Check if a URL is an auth-related endpoint
@@ -905,22 +932,18 @@ const companyFeatureGuard = () => {
905
932
  return true;
906
933
  };
907
934
 
935
+ /** Endpoints that need withCredentials for cookie handling */
936
+ const COOKIE_ENDPOINTS = [
937
+ AUTH_ENDPOINTS.LOGIN,
938
+ AUTH_ENDPOINTS.LOGOUT,
939
+ AUTH_ENDPOINTS.REFRESH,
940
+ AUTH_ENDPOINTS.REGISTER,
941
+ AUTH_ENDPOINTS.SELECT,
942
+ AUTH_ENDPOINTS.SWITCH_COMPANY,
943
+ AUTH_ENDPOINTS.VERIFY_EMAIL,
944
+ ];
908
945
  /**
909
- * Auth Interceptor
910
- * Adds Authorization header and tenant header to HTTP requests.
911
- *
912
- * - Adds Bearer token for authenticated requests (including /auth/me)
913
- * - Adds tenant header for multi-tenant mode
914
- * - Adds withCredentials for auth endpoints (cookies)
915
- *
916
- * @example
917
- * ```typescript
918
- * // In app.config.ts
919
- * provideHttpClient(
920
- * withFetch(),
921
- * withInterceptors([authInterceptor, tokenRefreshInterceptor])
922
- * )
923
- * ```
946
+ * Auth Interceptor - Adds auth headers and cookie credentials to requests
924
947
  */
925
948
  function authInterceptor(req, next) {
926
949
  const authState = inject(AuthStateService);
@@ -930,33 +953,21 @@ function authInterceptor(req, next) {
930
953
  // Add tenant header if multi-tenant mode
931
954
  if (appConfig.multiTenant.enabled) {
932
955
  const tenantId = authState.tenantId();
933
- const tenantHeader = appConfig.multiTenant.tenantHeader;
934
956
  if (tenantId) {
935
- headers[tenantHeader] = tenantId;
957
+ headers[appConfig.multiTenant.tenantHeader] = tenantId;
936
958
  }
937
959
  }
938
- // Add Bearer token for authenticated endpoints (including /auth/me)
960
+ // Add Bearer token if available
939
961
  const token = authState.accessToken();
940
962
  if (token) {
941
963
  headers['Authorization'] = `Bearer ${token}`;
942
964
  }
943
- // Check if this is an auth endpoint that needs withCredentials (for cookies)
944
- const needsCredentials = isEndpoint(req.url, AUTH_ENDPOINTS.REFRESH) ||
945
- isEndpoint(req.url, AUTH_ENDPOINTS.LOGOUT) ||
946
- isEndpoint(req.url, AUTH_ENDPOINTS.LOGIN) ||
947
- isEndpoint(req.url, AUTH_ENDPOINTS.REGISTER) ||
948
- isEndpoint(req.url, AUTH_ENDPOINTS.SELECT) ||
949
- isEndpoint(req.url, AUTH_ENDPOINTS.SWITCH_COMPANY);
965
+ // Check if this endpoint needs withCredentials for cookies
966
+ const needsCredentials = COOKIE_ENDPOINTS.some((ep) => isEndpoint(req.url, ep));
950
967
  if (needsCredentials) {
951
- // Auth endpoints use cookies
952
- return next(req.clone({
953
- withCredentials: true,
954
- setHeaders: {
955
- cookie: cookieService.get(),
956
- },
957
- }));
968
+ headers['cookie'] = cookieService.get();
969
+ return next(req.clone({ withCredentials: true, setHeaders: headers }));
958
970
  }
959
- // Other requests just need headers (Bearer token + tenant)
960
971
  if (Object.keys(headers).length > 0) {
961
972
  return next(req.clone({ setHeaders: headers }));
962
973
  }
@@ -1072,6 +1083,20 @@ const AUTH_ROUTES = [
1072
1083
  loadComponent: () => Promise.resolve().then(function () { return registerPage_component; }).then((m) => m.RegisterPageComponent),
1073
1084
  canActivate: [guestGuard],
1074
1085
  },
1086
+ {
1087
+ path: 'forgot-password',
1088
+ loadComponent: () => Promise.resolve().then(function () { return forgotPasswordPage_component; }).then((m) => m.ForgotPasswordPageComponent),
1089
+ canActivate: [guestGuard],
1090
+ },
1091
+ {
1092
+ path: 'reset-password',
1093
+ loadComponent: () => Promise.resolve().then(function () { return resetPasswordPage_component; }).then((m) => m.ResetPasswordPageComponent),
1094
+ canActivate: [guestGuard],
1095
+ },
1096
+ {
1097
+ path: 'verify-email',
1098
+ loadComponent: () => Promise.resolve().then(function () { return verifyEmailPage_component; }).then((m) => m.VerifyEmailPageComponent),
1099
+ },
1075
1100
  {
1076
1101
  path: '',
1077
1102
  redirectTo: 'login',
@@ -1157,6 +1182,17 @@ const ADMINISTRATION_ROUTES = [
1157
1182
  ],
1158
1183
  },
1159
1184
  ];
1185
+ /**
1186
+ * Profile Routes Configuration
1187
+ * Protected route for user profile management
1188
+ */
1189
+ const PROFILE_ROUTES = [
1190
+ {
1191
+ path: '',
1192
+ canActivate: [authGuard],
1193
+ loadComponent: () => Promise.resolve().then(function () { return profilePage_component; }).then((m) => m.ProfilePageComponent),
1194
+ },
1195
+ ];
1160
1196
 
1161
1197
  /**
1162
1198
  * Auth Layout State Adapter
@@ -1915,7 +1951,7 @@ class BranchFormComponent {
1915
1951
  </div>
1916
1952
  </form>
1917
1953
  </p-card>
1918
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "directive", type: i2.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i4.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }] });
1954
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i3$1.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }] });
1919
1955
  }
1920
1956
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: BranchFormComponent, decorators: [{
1921
1957
  type: Component,
@@ -2232,7 +2268,7 @@ class BranchListComponent {
2232
2268
  </ng-template>
2233
2269
  </p-table>
2234
2270
  </p-card>
2235
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i1$2.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i3$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: i4$1.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "component", type: i4.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "component", type: i7.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: TagModule }] });
2271
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i3$1.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "component", type: i4$1.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i3$2.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "component", type: i4$2.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: i5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: TagModule }] });
2236
2272
  }
2237
2273
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: BranchListComponent, decorators: [{
2238
2274
  type: Component,
@@ -2599,7 +2635,7 @@ class CompanyFormComponent {
2599
2635
  </div>
2600
2636
  </form>
2601
2637
  </p-card>
2602
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "directive", type: i2.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i4.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }] });
2638
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i3$1.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }] });
2603
2639
  }
2604
2640
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CompanyFormComponent, decorators: [{
2605
2641
  type: Component,
@@ -2876,7 +2912,7 @@ class CompanyListComponent {
2876
2912
  </ng-template>
2877
2913
  </p-table>
2878
2914
  </p-card>
2879
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i1$2.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i3$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: i4$1.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "component", type: i4.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: TagModule }] });
2915
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i3$1.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "component", type: i3$2.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "component", type: i4$2.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: i5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: TagModule }] });
2880
2916
  }
2881
2917
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: CompanyListComponent, decorators: [{
2882
2918
  type: Component,
@@ -2974,6 +3010,223 @@ var companyList_component = /*#__PURE__*/Object.freeze({
2974
3010
  CompanyListComponent: CompanyListComponent
2975
3011
  });
2976
3012
 
3013
+ class ForgotPasswordPageComponent {
3014
+ destroyRef = inject(DestroyRef);
3015
+ messageService = inject(MessageService);
3016
+ authApi = inject(AuthApiService);
3017
+ isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
3018
+ emailSent = signal(false, ...(ngDevMode ? [{ debugName: "emailSent" }] : []));
3019
+ submittedEmail = signal('', ...(ngDevMode ? [{ debugName: "submittedEmail" }] : []));
3020
+ formModel = signal({ email: '' }, ...(ngDevMode ? [{ debugName: "formModel" }] : []));
3021
+ forgotForm = form(this.formModel, (f) => {
3022
+ required(f.email, { message: 'Email is required' });
3023
+ email(f.email, { message: 'Please enter a valid email address' });
3024
+ });
3025
+ isFormValid = computed(() => this.forgotForm().valid(), ...(ngDevMode ? [{ debugName: "isFormValid" }] : []));
3026
+ onSubmit() {
3027
+ if (!this.isFormValid()) {
3028
+ this.messageService.add({
3029
+ severity: 'error',
3030
+ summary: 'Validation Error',
3031
+ detail: 'Please enter a valid email address.',
3032
+ });
3033
+ return;
3034
+ }
3035
+ this.isLoading.set(true);
3036
+ const email = this.formModel().email;
3037
+ this.authApi
3038
+ .forgotPassword(email)
3039
+ .pipe(takeUntilDestroyed(this.destroyRef))
3040
+ .subscribe({
3041
+ next: () => {
3042
+ this.isLoading.set(false);
3043
+ this.submittedEmail.set(email);
3044
+ this.emailSent.set(true);
3045
+ },
3046
+ error: () => {
3047
+ this.isLoading.set(false);
3048
+ this.submittedEmail.set(email);
3049
+ this.emailSent.set(true);
3050
+ },
3051
+ });
3052
+ }
3053
+ resetForm() {
3054
+ this.emailSent.set(false);
3055
+ this.submittedEmail.set('');
3056
+ this.formModel.set({ email: '' });
3057
+ }
3058
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ForgotPasswordPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3059
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: ForgotPasswordPageComponent, isStandalone: true, selector: "lib-forgot-password-page", ngImport: i0, template: `
3060
+ <app-floating-configurator />
3061
+ <div class="bg-surface-ground flex items-center justify-center min-h-screen">
3062
+ <div class="flex flex-col items-center justify-center w-full max-w-[32rem] px-4">
3063
+ <div
3064
+ class="w-full"
3065
+ style="border-radius: var(--p-content-border-radius); padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)">
3066
+ <div
3067
+ class="w-full py-16 px-12"
3068
+ style="border-radius: var(--p-content-border-radius); background: var(--p-content-background);">
3069
+
3070
+ @if (!emailSent()) {
3071
+ <div class="text-center mb-8">
3072
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
3073
+ Forgot Password
3074
+ </div>
3075
+ <span class="text-muted-color font-medium">
3076
+ Enter your email address and we'll send you a link to reset your password.
3077
+ </span>
3078
+ </div>
3079
+
3080
+ <form (ngSubmit)="onSubmit()">
3081
+ <label for="email" class="block text-lg font-medium mb-2" style="color: var(--p-content-color);">
3082
+ Email
3083
+ </label>
3084
+ <input
3085
+ pInputText
3086
+ id="email"
3087
+ type="email"
3088
+ placeholder="Enter your email address"
3089
+ class="w-full md:w-[25rem] mb-6"
3090
+ [formField]="forgotForm.email"
3091
+ autocomplete="email" />
3092
+
3093
+ <p-button
3094
+ label="Send Reset Link"
3095
+ styleClass="w-full mb-4"
3096
+ type="submit"
3097
+ [loading]="isLoading()"
3098
+ [disabled]="!isFormValid() || isLoading()" />
3099
+
3100
+ <div class="flex items-center justify-center gap-2">
3101
+ <span class="font-medium">Remember your password?</span>
3102
+ <a routerLink="/auth/login" class="font-semibold text-primary cursor-pointer">
3103
+ Sign In
3104
+ </a>
3105
+ </div>
3106
+ </form>
3107
+ } @else {
3108
+ <div class="text-center">
3109
+ <div class="flex justify-center mb-6">
3110
+ <i class="pi pi-envelope text-6xl text-primary"></i>
3111
+ </div>
3112
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
3113
+ Check Your Email
3114
+ </div>
3115
+ <p class="text-muted-color font-medium mb-6">
3116
+ We've sent a password reset link to <strong>{{ submittedEmail() }}</strong>.
3117
+ Please check your inbox and follow the instructions.
3118
+ </p>
3119
+ <p class="text-sm text-muted-color mb-6">
3120
+ Didn't receive the email? Check your spam folder or
3121
+ <span
3122
+ class="text-primary cursor-pointer font-semibold"
3123
+ (click)="resetForm()">
3124
+ try again
3125
+ </span>.
3126
+ </p>
3127
+ <a routerLink="/auth/login" class="font-semibold text-primary cursor-pointer">
3128
+ Back to Sign In
3129
+ </a>
3130
+ </div>
3131
+ }
3132
+ </div>
3133
+ </div>
3134
+ </div>
3135
+ </div>
3136
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }, { kind: "component", type: AppFloatingConfigurator, selector: "app-floating-configurator" }] });
3137
+ }
3138
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ForgotPasswordPageComponent, decorators: [{
3139
+ type: Component,
3140
+ args: [{
3141
+ selector: 'lib-forgot-password-page',
3142
+ standalone: true,
3143
+ imports: [AngularModule, PrimeModule, FormField, AppFloatingConfigurator],
3144
+ template: `
3145
+ <app-floating-configurator />
3146
+ <div class="bg-surface-ground flex items-center justify-center min-h-screen">
3147
+ <div class="flex flex-col items-center justify-center w-full max-w-[32rem] px-4">
3148
+ <div
3149
+ class="w-full"
3150
+ style="border-radius: var(--p-content-border-radius); padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)">
3151
+ <div
3152
+ class="w-full py-16 px-12"
3153
+ style="border-radius: var(--p-content-border-radius); background: var(--p-content-background);">
3154
+
3155
+ @if (!emailSent()) {
3156
+ <div class="text-center mb-8">
3157
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
3158
+ Forgot Password
3159
+ </div>
3160
+ <span class="text-muted-color font-medium">
3161
+ Enter your email address and we'll send you a link to reset your password.
3162
+ </span>
3163
+ </div>
3164
+
3165
+ <form (ngSubmit)="onSubmit()">
3166
+ <label for="email" class="block text-lg font-medium mb-2" style="color: var(--p-content-color);">
3167
+ Email
3168
+ </label>
3169
+ <input
3170
+ pInputText
3171
+ id="email"
3172
+ type="email"
3173
+ placeholder="Enter your email address"
3174
+ class="w-full md:w-[25rem] mb-6"
3175
+ [formField]="forgotForm.email"
3176
+ autocomplete="email" />
3177
+
3178
+ <p-button
3179
+ label="Send Reset Link"
3180
+ styleClass="w-full mb-4"
3181
+ type="submit"
3182
+ [loading]="isLoading()"
3183
+ [disabled]="!isFormValid() || isLoading()" />
3184
+
3185
+ <div class="flex items-center justify-center gap-2">
3186
+ <span class="font-medium">Remember your password?</span>
3187
+ <a routerLink="/auth/login" class="font-semibold text-primary cursor-pointer">
3188
+ Sign In
3189
+ </a>
3190
+ </div>
3191
+ </form>
3192
+ } @else {
3193
+ <div class="text-center">
3194
+ <div class="flex justify-center mb-6">
3195
+ <i class="pi pi-envelope text-6xl text-primary"></i>
3196
+ </div>
3197
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
3198
+ Check Your Email
3199
+ </div>
3200
+ <p class="text-muted-color font-medium mb-6">
3201
+ We've sent a password reset link to <strong>{{ submittedEmail() }}</strong>.
3202
+ Please check your inbox and follow the instructions.
3203
+ </p>
3204
+ <p class="text-sm text-muted-color mb-6">
3205
+ Didn't receive the email? Check your spam folder or
3206
+ <span
3207
+ class="text-primary cursor-pointer font-semibold"
3208
+ (click)="resetForm()">
3209
+ try again
3210
+ </span>.
3211
+ </p>
3212
+ <a routerLink="/auth/login" class="font-semibold text-primary cursor-pointer">
3213
+ Back to Sign In
3214
+ </a>
3215
+ </div>
3216
+ }
3217
+ </div>
3218
+ </div>
3219
+ </div>
3220
+ </div>
3221
+ `,
3222
+ }]
3223
+ }] });
3224
+
3225
+ var forgotPasswordPage_component = /*#__PURE__*/Object.freeze({
3226
+ __proto__: null,
3227
+ ForgotPasswordPageComponent: ForgotPasswordPageComponent
3228
+ });
3229
+
2977
3230
  /**
2978
3231
  * Login Page Component
2979
3232
  * Multi-step authentication flow:
@@ -2986,12 +3239,10 @@ class LoginPageComponent {
2986
3239
  messageService = inject(MessageService);
2987
3240
  authApi = inject(AuthApiService);
2988
3241
  authState = inject(AuthStateService);
2989
- /** Return URL after successful login */
2990
3242
  returnUrl = input('', ...(ngDevMode ? [{ debugName: "returnUrl" }] : []));
2991
- /** Current step in login flow */
2992
3243
  currentStep = signal('credentials', ...(ngDevMode ? [{ debugName: "currentStep" }] : []));
2993
- /** Loading state */
2994
3244
  isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
3245
+ unverifiedEmail = signal('', ...(ngDevMode ? [{ debugName: "unverifiedEmail" }] : []));
2995
3246
  // ============================================
2996
3247
  // Login Form (Signal Forms)
2997
3248
  // ============================================
@@ -3080,23 +3331,24 @@ class LoginPageComponent {
3080
3331
  };
3081
3332
  this.authApi
3082
3333
  .login(loginData)
3083
- .pipe(take(1), takeUntilDestroyed(this.destroyRef))
3334
+ .pipe(takeUntilDestroyed(this.destroyRef))
3084
3335
  .subscribe({
3085
3336
  next: (response) => {
3086
3337
  this.isLoading.set(false);
3087
- // Check if company selection is required
3338
+ if (isEmailVerificationRequired(response)) {
3339
+ this.unverifiedEmail.set(response.email);
3340
+ this.currentStep.set('email-verification');
3341
+ return;
3342
+ }
3088
3343
  if (isCompanySelectionResponse(response)) {
3089
- // Move to step 2 (company selection)
3090
3344
  this.sessionId = response.sessionId;
3091
3345
  this.companies.set(response.companies);
3092
3346
  this.userName.set(response.user.name ?? response.user.email);
3093
- // Store selection state for potential page refresh
3094
3347
  this.authState.setSelectionState(response);
3095
3348
  this.currentStep.set('company-selection');
3096
3349
  this.autoSelectIfSingle();
3097
3350
  }
3098
3351
  else {
3099
- // Direct login success
3100
3352
  this.authState.setLoginState(response);
3101
3353
  this.messageService.add({
3102
3354
  severity: 'success',
@@ -3162,7 +3414,7 @@ class LoginPageComponent {
3162
3414
  };
3163
3415
  this.authApi
3164
3416
  .select(selectData)
3165
- .pipe(take(1), takeUntilDestroyed(this.destroyRef))
3417
+ .pipe(takeUntilDestroyed(this.destroyRef))
3166
3418
  .subscribe({
3167
3419
  next: (response) => {
3168
3420
  this.isLoading.set(false);
@@ -3186,9 +3438,9 @@ class LoginPageComponent {
3186
3438
  });
3187
3439
  }
3188
3440
  goBackToCredentials() {
3189
- // Reset selection state
3190
3441
  this.sessionId = null;
3191
3442
  this.companies.set([]);
3443
+ this.unverifiedEmail.set('');
3192
3444
  this.selectionFormModel.set({
3193
3445
  companyId: '',
3194
3446
  branchId: '',
@@ -3197,6 +3449,33 @@ class LoginPageComponent {
3197
3449
  this.authState.resetState();
3198
3450
  this.currentStep.set('credentials');
3199
3451
  }
3452
+ onResendVerification() {
3453
+ const email = this.unverifiedEmail();
3454
+ if (!email)
3455
+ return;
3456
+ this.isLoading.set(true);
3457
+ this.authApi
3458
+ .resendVerification(email)
3459
+ .pipe(takeUntilDestroyed(this.destroyRef))
3460
+ .subscribe({
3461
+ next: (response) => {
3462
+ this.isLoading.set(false);
3463
+ this.messageService.add({
3464
+ severity: 'success',
3465
+ summary: 'Email Sent',
3466
+ detail: response.message || 'Verification email has been sent.',
3467
+ });
3468
+ },
3469
+ error: (error) => {
3470
+ this.isLoading.set(false);
3471
+ this.messageService.add({
3472
+ severity: 'error',
3473
+ summary: 'Failed to Send',
3474
+ detail: error.error?.message || 'Unable to send verification email.',
3475
+ });
3476
+ },
3477
+ });
3478
+ }
3200
3479
  // ============================================
3201
3480
  // Helpers
3202
3481
  // ============================================
@@ -3332,6 +3611,33 @@ class LoginPageComponent {
3332
3611
  </form>
3333
3612
  }
3334
3613
 
3614
+ <!-- Email Verification Required -->
3615
+ @if (currentStep() === 'email-verification') {
3616
+ <div class="text-center">
3617
+ <div class="flex justify-center mb-6">
3618
+ <i class="pi pi-envelope text-6xl text-orange-500"></i>
3619
+ </div>
3620
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
3621
+ Email Not Verified
3622
+ </div>
3623
+ <p class="text-muted-color font-medium mb-6">
3624
+ Please verify your email address <strong>{{ unverifiedEmail() }}</strong> before logging in.
3625
+ </p>
3626
+ <p-button
3627
+ label="Resend Verification Email"
3628
+ styleClass="w-full mb-4"
3629
+ [loading]="isLoading()"
3630
+ (onClick)="onResendVerification()" />
3631
+ <div class="mt-4">
3632
+ <span
3633
+ class="font-semibold text-primary cursor-pointer"
3634
+ (click)="goBackToCredentials()">
3635
+ Back to Sign In
3636
+ </span>
3637
+ </div>
3638
+ </div>
3639
+ }
3640
+
3335
3641
  <!-- Step 2: Company Selection -->
3336
3642
  @if (currentStep() === 'company-selection') {
3337
3643
  <div class="text-center mb-8">
@@ -3401,7 +3707,7 @@ class LoginPageComponent {
3401
3707
  </div>
3402
3708
  </div>
3403
3709
  </div>
3404
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "directive", type: i2.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }, { kind: "component", type: AppFloatingConfigurator, selector: "app-floating-configurator" }] });
3710
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }, { kind: "component", type: AppFloatingConfigurator, selector: "app-floating-configurator" }] });
3405
3711
  }
3406
3712
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: LoginPageComponent, decorators: [{
3407
3713
  type: Component,
@@ -3509,6 +3815,33 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
3509
3815
  </form>
3510
3816
  }
3511
3817
 
3818
+ <!-- Email Verification Required -->
3819
+ @if (currentStep() === 'email-verification') {
3820
+ <div class="text-center">
3821
+ <div class="flex justify-center mb-6">
3822
+ <i class="pi pi-envelope text-6xl text-orange-500"></i>
3823
+ </div>
3824
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
3825
+ Email Not Verified
3826
+ </div>
3827
+ <p class="text-muted-color font-medium mb-6">
3828
+ Please verify your email address <strong>{{ unverifiedEmail() }}</strong> before logging in.
3829
+ </p>
3830
+ <p-button
3831
+ label="Resend Verification Email"
3832
+ styleClass="w-full mb-4"
3833
+ [loading]="isLoading()"
3834
+ (onClick)="onResendVerification()" />
3835
+ <div class="mt-4">
3836
+ <span
3837
+ class="font-semibold text-primary cursor-pointer"
3838
+ (click)="goBackToCredentials()">
3839
+ Back to Sign In
3840
+ </span>
3841
+ </div>
3842
+ </div>
3843
+ }
3844
+
3512
3845
  <!-- Step 2: Company Selection -->
3513
3846
  @if (currentStep() === 'company-selection') {
3514
3847
  <div class="text-center mb-8">
@@ -3588,109 +3921,922 @@ var loginPage_component = /*#__PURE__*/Object.freeze({
3588
3921
  });
3589
3922
 
3590
3923
  /**
3591
- * Registration Page Component
3592
- * Handles user registration with optional company creation
3593
- * Adapts UI based on company feature configuration
3924
+ * Profile Page Component
3925
+ *
3926
+ * Allows users to view and update their profile information.
3927
+ * Features:
3928
+ * - Update name, phone, profile picture (if storage enabled)
3929
+ * - View company/branch info (read-only, if company feature enabled)
3930
+ * - View roles/actions (read-only, if IAM enabled via optional injection)
3931
+ *
3932
+ * Package Independence:
3933
+ * - Uses PROFILE_UPLOAD_PROVIDER (optional) for storage functionality
3934
+ * - Uses PROFILE_PERMISSION_PROVIDER (optional) for IAM functionality
3935
+ * - No direct imports from ng-storage or ng-iam
3594
3936
  */
3595
- class RegisterPageComponent {
3937
+ class ProfilePageComponent {
3596
3938
  destroyRef = inject(DestroyRef);
3597
3939
  appConfig = inject(APP_CONFIG);
3598
3940
  messageService = inject(MessageService);
3599
- authApi = inject(AuthApiService);
3600
3941
  authState = inject(AuthStateService);
3601
- route = inject(ActivatedRoute);
3602
- /** Return URL after successful registration */
3603
- returnUrl = input('', ...(ngDevMode ? [{ debugName: "returnUrl" }] : []));
3604
- /** Loading state */
3942
+ userService = inject(UserApiService);
3943
+ authApiService = inject(AuthApiService);
3944
+ fileUrlService = inject(FileUrlService);
3945
+ // Optional injection - null if storage not configured
3946
+ uploadProvider = inject(PROFILE_UPLOAD_PROVIDER, {
3947
+ optional: true,
3948
+ });
3949
+ // Optional injection - null if IAM not configured
3950
+ permissionProvider = inject(PROFILE_PERMISSION_PROVIDER, {
3951
+ optional: true,
3952
+ });
3953
+ // State
3605
3954
  isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
3606
- /** Whether company feature is enabled */
3607
- companyFeatureEnabled = computed(() => isCompanyFeatureEnabled(this.appConfig), ...(ngDevMode ? [{ debugName: "companyFeatureEnabled" }] : []));
3608
- // ============================================
3609
- // User Form (Signal Forms)
3610
- // ============================================
3611
- /** User form model */
3612
- userFormModel = signal({
3955
+ isLoadingPermissions = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingPermissions" }] : []));
3956
+ _profilePictureUrl = signal(null, ...(ngDevMode ? [{ debugName: "_profilePictureUrl" }] : []));
3957
+ profilePictureUrl = this._profilePictureUrl.asReadonly();
3958
+ // Permission data
3959
+ _userRoles = signal([], ...(ngDevMode ? [{ debugName: "_userRoles" }] : []));
3960
+ userRoles = this._userRoles.asReadonly();
3961
+ _userActions = signal([], ...(ngDevMode ? [{ debugName: "_userActions" }] : []));
3962
+ userActions = this._userActions.asReadonly();
3963
+ // Computed from auth state
3964
+ userEmail = computed(() => this.authState.user()?.email ?? '', ...(ngDevMode ? [{ debugName: "userEmail" }] : []));
3965
+ userName = computed(() => this.authState.user()?.name ?? 'User', ...(ngDevMode ? [{ debugName: "userName" }] : []));
3966
+ userInitials = computed(() => {
3967
+ const name = this.authState.user()?.name ?? '';
3968
+ return name
3969
+ .split(' ')
3970
+ .map((n) => n[0])
3971
+ .join('')
3972
+ .toUpperCase()
3973
+ .slice(0, 2);
3974
+ }, ...(ngDevMode ? [{ debugName: "userInitials" }] : []));
3975
+ companyName = computed(() => this.authState.companyName(), ...(ngDevMode ? [{ debugName: "companyName" }] : []));
3976
+ branchName = computed(() => this.authState.branchName(), ...(ngDevMode ? [{ debugName: "branchName" }] : []));
3977
+ // Feature flags - Storage feature check
3978
+ showUploadSection = computed(() => isFeatureEnabled(this.appConfig, 'storage') &&
3979
+ this.uploadProvider !== null, ...(ngDevMode ? [{ debugName: "showUploadSection" }] : []));
3980
+ showCompanySection = computed(() => isCompanyFeatureEnabled(this.appConfig), ...(ngDevMode ? [{ debugName: "showCompanySection" }] : []));
3981
+ showPermissionsSection = computed(() => isFeatureEnabled(this.appConfig, 'iam') &&
3982
+ this.permissionProvider !== null, ...(ngDevMode ? [{ debugName: "showPermissionsSection" }] : []));
3983
+ permissionMode = computed(() => getPermissionMode(this.appConfig), ...(ngDevMode ? [{ debugName: "permissionMode" }] : []));
3984
+ showRoles = computed(() => {
3985
+ const mode = this.permissionMode();
3986
+ return mode === 'rbac' || mode === 'full';
3987
+ }, ...(ngDevMode ? [{ debugName: "showRoles" }] : []));
3988
+ showActions = computed(() => {
3989
+ const mode = this.permissionMode();
3990
+ return mode === 'direct' || mode === 'full';
3991
+ }, ...(ngDevMode ? [{ debugName: "showActions" }] : []));
3992
+ // Show bottom row only if at least one section is visible
3993
+ showBottomRow = computed(() => this.showCompanySection() || this.showPermissionsSection(), ...(ngDevMode ? [{ debugName: "showBottomRow" }] : []));
3994
+ // Form
3995
+ formModel = signal({
3613
3996
  name: '',
3614
- email: '',
3615
- password: '',
3616
- confirmPassword: '',
3617
3997
  phone: '',
3618
- }, ...(ngDevMode ? [{ debugName: "userFormModel" }] : []));
3619
- /** User form with validation schema */
3620
- userForm = form(this.userFormModel, (f) => {
3998
+ }, ...(ngDevMode ? [{ debugName: "formModel" }] : []));
3999
+ formTree = form(this.formModel, (f) => {
3621
4000
  required(f.name, { message: 'Name is required' });
3622
- minLength(f.name, 2, { message: 'Name must be at least 2 characters' });
3623
- required(f.email, { message: 'Email is required' });
3624
- email(f.email, { message: 'Please enter a valid email address' });
3625
- required(f.password, { message: 'Password is required' });
3626
- minLength(f.password, 6, { message: 'Password must be at least 6 characters' });
3627
- required(f.confirmPassword, { message: 'Please confirm your password' });
3628
- });
3629
- // ============================================
3630
- // Company Form (Signal Forms)
3631
- // ============================================
3632
- /** Company form model */
3633
- companyFormModel = signal({
3634
- createNewCompany: false,
3635
- companySlug: '',
3636
- branchSlug: '',
3637
- newCompanyName: '',
3638
- newCompanyPhone: '',
3639
- newCompanyAddress: '',
3640
- }, ...(ngDevMode ? [{ debugName: "companyFormModel" }] : []));
3641
- /** Company form with conditional validation schema */
3642
- companyForm = form(this.companyFormModel, (f) => {
3643
- // Validation is conditional based on createNewCompany flag
3644
- // We'll handle this in isFormValid() computed
3645
4001
  });
3646
- // ============================================
3647
- // Lifecycle
3648
- // ============================================
3649
- ngOnInit() {
3650
- // Read companySlug from query params and prefill the form
3651
- const companySlug = this.route.snapshot.queryParamMap.get('companySlug');
3652
- if (companySlug) {
3653
- this.companyFormModel.update((model) => ({
3654
- ...model,
3655
- companySlug,
3656
- }));
4002
+ isFormValid = computed(() => {
4003
+ const model = this.formModel();
4004
+ return model.name.trim().length > 0;
4005
+ }, ...(ngDevMode ? [{ debugName: "isFormValid" }] : []));
4006
+ // Password form
4007
+ passwordModel = signal({
4008
+ currentPassword: '',
4009
+ newPassword: '',
4010
+ confirmPassword: '',
4011
+ }, ...(ngDevMode ? [{ debugName: "passwordModel" }] : []));
4012
+ isPasswordLoading = signal(false, ...(ngDevMode ? [{ debugName: "isPasswordLoading" }] : []));
4013
+ isPasswordFormValid = computed(() => {
4014
+ const model = this.passwordModel();
4015
+ return (model.currentPassword.length > 0 &&
4016
+ model.newPassword.length >= 6 &&
4017
+ model.newPassword === model.confirmPassword);
4018
+ }, ...(ngDevMode ? [{ debugName: "isPasswordFormValid" }] : []));
4019
+ passwordsMatch = computed(() => {
4020
+ const model = this.passwordModel();
4021
+ return model.confirmPassword.length === 0 || model.newPassword === model.confirmPassword;
4022
+ }, ...(ngDevMode ? [{ debugName: "passwordsMatch" }] : []));
4023
+ // Upload function for FileUploader - uses provider if available
4024
+ uploadFileFn = (file, options) => {
4025
+ if (!this.uploadProvider) {
4026
+ throw new Error('Upload provider not available');
4027
+ }
4028
+ return this.uploadProvider
4029
+ .uploadProfilePicture(file, {
4030
+ folderPath: options?.folderPath ?? 'profile-pictures',
4031
+ compress: options?.compress ?? true,
4032
+ maxWidth: options?.maxWidth ?? 400,
4033
+ maxHeight: options?.maxHeight ?? 400,
4034
+ })
4035
+ .pipe(map((response) => ({
4036
+ success: response.success,
4037
+ message: response.message,
4038
+ data: response.data
4039
+ ? this.mapUploadResultToUploadedFile(response.data)
4040
+ : undefined,
4041
+ })));
4042
+ };
4043
+ /** Map upload result to IUploadedFile format */
4044
+ mapUploadResultToUploadedFile(result) {
4045
+ return {
4046
+ id: result.id, // UUID for storing in profilePictureId
4047
+ name: result.name,
4048
+ key: result.key,
4049
+ size: result.size,
4050
+ contentType: result.contentType,
4051
+ };
4052
+ }
4053
+ ngOnInit() {
4054
+ this.loadUserData();
4055
+ if (this.showPermissionsSection()) {
4056
+ this.loadPermissions();
3657
4057
  }
3658
4058
  }
3659
- // ============================================
3660
- // Form Validation
3661
- // ============================================
3662
- /** Check if passwords match */
4059
+ loadUserData() {
4060
+ const user = this.authState.user();
4061
+ if (user) {
4062
+ this.formModel.set({
4063
+ name: user.name ?? '',
4064
+ phone: user.phone ?? '',
4065
+ });
4066
+ // Load profile picture URL (only if storage is enabled)
4067
+ if (user.profilePictureId && isFeatureEnabled(this.appConfig, 'storage')) {
4068
+ this.fileUrlService
4069
+ .fetchSingleFileUrl(user.profilePictureId)
4070
+ .pipe(takeUntilDestroyed(this.destroyRef))
4071
+ .subscribe((file) => {
4072
+ this._profilePictureUrl.set(file?.url ?? null);
4073
+ });
4074
+ }
4075
+ }
4076
+ }
4077
+ loadPermissions() {
4078
+ const user = this.authState.user();
4079
+ const branch = this.authState.branch();
4080
+ if (!user || !this.permissionProvider)
4081
+ return;
4082
+ const loadRoles = this.showRoles();
4083
+ const loadActions = this.showActions();
4084
+ if (!loadRoles && !loadActions)
4085
+ return;
4086
+ this.isLoadingPermissions.set(true);
4087
+ let rolesLoaded = !loadRoles;
4088
+ let actionsLoaded = !loadActions;
4089
+ const checkComplete = () => {
4090
+ if (rolesLoaded && actionsLoaded) {
4091
+ this.isLoadingPermissions.set(false);
4092
+ }
4093
+ };
4094
+ // Load roles (if applicable)
4095
+ if (loadRoles) {
4096
+ this.permissionProvider
4097
+ .getUserRoles(user.id, branch?.id)
4098
+ .pipe(takeUntilDestroyed(this.destroyRef))
4099
+ .subscribe({
4100
+ next: (response) => {
4101
+ this._userRoles.set(response.data ?? []);
4102
+ rolesLoaded = true;
4103
+ checkComplete();
4104
+ },
4105
+ error: () => {
4106
+ this._userRoles.set([]);
4107
+ rolesLoaded = true;
4108
+ checkComplete();
4109
+ },
4110
+ });
4111
+ }
4112
+ // Load actions (if applicable)
4113
+ if (loadActions) {
4114
+ this.permissionProvider
4115
+ .getUserActions(user.id, branch?.id)
4116
+ .pipe(takeUntilDestroyed(this.destroyRef))
4117
+ .subscribe({
4118
+ next: (response) => {
4119
+ this._userActions.set(response.data ?? []);
4120
+ actionsLoaded = true;
4121
+ checkComplete();
4122
+ },
4123
+ error: () => {
4124
+ this._userActions.set([]);
4125
+ actionsLoaded = true;
4126
+ checkComplete();
4127
+ },
4128
+ });
4129
+ }
4130
+ }
4131
+ onProfilePictureUploaded(file) {
4132
+ // Update user with new profile picture ID (file.id is the FileManager UUID)
4133
+ const user = this.authState.user();
4134
+ if (!user || !file.id)
4135
+ return;
4136
+ const fileManagerId = file.id;
4137
+ this.userService
4138
+ .updateProfile({ id: user.id, profilePictureId: fileManagerId })
4139
+ .pipe(takeUntilDestroyed(this.destroyRef))
4140
+ .subscribe({
4141
+ next: (response) => {
4142
+ if (response.success && response.data) {
4143
+ // Update auth state with new profile picture ID
4144
+ this.authState.user.set({
4145
+ ...user,
4146
+ profilePictureId: fileManagerId,
4147
+ });
4148
+ // Fetch new profile picture URL (force refresh to get fresh URL)
4149
+ this.fileUrlService
4150
+ .fetchSingleFileUrl(fileManagerId, true)
4151
+ .pipe(takeUntilDestroyed(this.destroyRef))
4152
+ .subscribe((fileInfo) => {
4153
+ this._profilePictureUrl.set(fileInfo?.url ?? null);
4154
+ });
4155
+ this.messageService.add({
4156
+ severity: 'success',
4157
+ summary: 'Success',
4158
+ detail: 'Profile picture updated',
4159
+ });
4160
+ }
4161
+ },
4162
+ error: () => {
4163
+ this.messageService.add({
4164
+ severity: 'error',
4165
+ summary: 'Error',
4166
+ detail: 'Failed to update profile picture',
4167
+ });
4168
+ },
4169
+ });
4170
+ }
4171
+ onSubmit() {
4172
+ if (!this.isFormValid())
4173
+ return;
4174
+ const user = this.authState.user();
4175
+ if (!user)
4176
+ return;
4177
+ this.isLoading.set(true);
4178
+ const model = this.formModel();
4179
+ const updateData = {
4180
+ id: user.id,
4181
+ name: model.name,
4182
+ phone: model.phone || undefined,
4183
+ };
4184
+ this.userService
4185
+ .updateProfile(updateData)
4186
+ .pipe(takeUntilDestroyed(this.destroyRef))
4187
+ .subscribe({
4188
+ next: (response) => {
4189
+ if (response.success && response.data) {
4190
+ // Update auth state with new user data (name and phone)
4191
+ this.authState.user.set({
4192
+ ...user,
4193
+ name: response.data.name,
4194
+ phone: response.data.phone ?? null,
4195
+ });
4196
+ this.messageService.add({
4197
+ severity: 'success',
4198
+ summary: 'Success',
4199
+ detail: 'Profile updated successfully',
4200
+ });
4201
+ }
4202
+ this.isLoading.set(false);
4203
+ },
4204
+ error: (error) => {
4205
+ const err = error;
4206
+ this.messageService.add({
4207
+ severity: 'error',
4208
+ summary: 'Error',
4209
+ detail: err.error?.message || 'Failed to update profile',
4210
+ });
4211
+ this.isLoading.set(false);
4212
+ },
4213
+ });
4214
+ }
4215
+ updatePasswordField(field, value) {
4216
+ this.passwordModel.update((model) => ({ ...model, [field]: value }));
4217
+ }
4218
+ onChangePassword() {
4219
+ if (!this.isPasswordFormValid())
4220
+ return;
4221
+ this.isPasswordLoading.set(true);
4222
+ const model = this.passwordModel();
4223
+ this.authApiService
4224
+ .changePassword({
4225
+ currentPassword: model.currentPassword,
4226
+ newPassword: model.newPassword,
4227
+ })
4228
+ .pipe(takeUntilDestroyed(this.destroyRef))
4229
+ .subscribe({
4230
+ next: () => {
4231
+ this.messageService.add({
4232
+ severity: 'success',
4233
+ summary: 'Success',
4234
+ detail: 'Password changed successfully',
4235
+ });
4236
+ // Reset password form
4237
+ this.passwordModel.set({
4238
+ currentPassword: '',
4239
+ newPassword: '',
4240
+ confirmPassword: '',
4241
+ });
4242
+ this.isPasswordLoading.set(false);
4243
+ },
4244
+ error: (error) => {
4245
+ const err = error;
4246
+ this.messageService.add({
4247
+ severity: 'error',
4248
+ summary: 'Error',
4249
+ detail: err.error?.message || 'Failed to change password',
4250
+ });
4251
+ this.isPasswordLoading.set(false);
4252
+ },
4253
+ });
4254
+ }
4255
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ProfilePageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
4256
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: ProfilePageComponent, isStandalone: true, selector: "lib-profile-page", ngImport: i0, template: `
4257
+ <!-- Page Header -->
4258
+ <div class="mb-4 md:mb-6">
4259
+ <h1 class="text-2xl font-bold text-color mb-2">My Profile</h1>
4260
+ <p class="text-muted-color">Manage your account settings and preferences.</p>
4261
+ </div>
4262
+
4263
+ <!-- Profile Form Card (full width) -->
4264
+ <div class="card mb-2 md:mb-4">
4265
+ <div class="flex flex-col md:flex-row gap-4 md:gap-6">
4266
+ <!-- Avatar Section -->
4267
+ <div class="flex flex-col items-center gap-4 md:w-72 lg:w-80 md:border-r md:border-surface-200 md:pr-6">
4268
+ @if (profilePictureUrl()) {
4269
+ <p-avatar
4270
+ [image]="profilePictureUrl()!"
4271
+ size="xlarge"
4272
+ shape="circle"
4273
+ styleClass="w-28 h-28" />
4274
+ } @else {
4275
+ <p-avatar
4276
+ [label]="userInitials()"
4277
+ size="xlarge"
4278
+ shape="circle"
4279
+ styleClass="w-28 h-28 text-3xl bg-primary text-primary-contrast" />
4280
+ }
4281
+ <div class="text-center">
4282
+ <div class="font-semibold text-color">{{ userName() }}</div>
4283
+ <div class="text-muted-color text-sm">{{ userEmail() }}</div>
4284
+ </div>
4285
+ <!-- Upload section - only show if storage is enabled and provider is available -->
4286
+ @if (showUploadSection()) {
4287
+ <lib-file-uploader
4288
+ [uploadFile]="uploadFileFn"
4289
+ [acceptTypes]="['image/*']"
4290
+ [multiple]="false"
4291
+ [maxSizeMb]="5"
4292
+ [showPreview]="false"
4293
+ (fileUploaded)="onProfilePictureUploaded($event)"
4294
+ class="w-full" />
4295
+ } @else {
4296
+ <div class="w-full p-3 bg-surface-100 rounded-lg text-center">
4297
+ <i class="pi pi-info-circle text-muted-color mr-2"></i>
4298
+ <span class="text-sm text-muted-color">Storage feature not enabled</span>
4299
+ </div>
4300
+ }
4301
+ </div>
4302
+
4303
+ <!-- Form Section -->
4304
+ <div class="flex-1">
4305
+ <form (ngSubmit)="onSubmit()" class="flex flex-col gap-4">
4306
+ <!-- Email (read-only) -->
4307
+ <div class="flex flex-col gap-2">
4308
+ <label class="font-medium text-color">Email</label>
4309
+ <input
4310
+ pInputText
4311
+ [value]="userEmail()"
4312
+ disabled
4313
+ class="bg-surface-50" />
4314
+ </div>
4315
+
4316
+ <!-- Name -->
4317
+ <div class="flex flex-col gap-2">
4318
+ <label for="name" class="font-medium text-color">Name *</label>
4319
+ <input
4320
+ pInputText
4321
+ id="name"
4322
+ [formField]="formTree.name"
4323
+ placeholder="Your full name" />
4324
+ </div>
4325
+
4326
+ <!-- Phone -->
4327
+ <div class="flex flex-col gap-2">
4328
+ <label for="phone" class="font-medium text-color">Phone</label>
4329
+ <input
4330
+ pInputText
4331
+ id="phone"
4332
+ [formField]="formTree.phone"
4333
+ placeholder="+1 234 567 8900" />
4334
+ </div>
4335
+
4336
+ <!-- Submit -->
4337
+ <div class="flex justify-end pt-4">
4338
+ <p-button
4339
+ label="Save Changes"
4340
+ icon="pi pi-check"
4341
+ type="submit"
4342
+ [loading]="isLoading()"
4343
+ [disabled]="!isFormValid() || isLoading()" />
4344
+ </div>
4345
+ </form>
4346
+ </div>
4347
+ </div>
4348
+ </div>
4349
+
4350
+ <!-- Security Card -->
4351
+ <div class="card mb-2 md:mb-4">
4352
+ <div class="flex items-center gap-2 mb-4">
4353
+ <i class="pi pi-lock text-primary"></i>
4354
+ <span class="font-semibold text-color">Security</span>
4355
+ </div>
4356
+ <form (ngSubmit)="onChangePassword()" class="flex flex-col gap-4">
4357
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
4358
+ <!-- Current Password -->
4359
+ <div class="flex flex-col gap-2">
4360
+ <label for="currentPassword" class="font-medium text-color">Current Password *</label>
4361
+ <p-password
4362
+ id="currentPassword"
4363
+ name="currentPassword"
4364
+ [ngModel]="passwordModel().currentPassword"
4365
+ (ngModelChange)="updatePasswordField('currentPassword', $event)"
4366
+ [feedback]="false"
4367
+ [toggleMask]="true"
4368
+ placeholder="Enter current password"
4369
+ styleClass="w-full"
4370
+ inputStyleClass="w-full" />
4371
+ </div>
4372
+
4373
+ <!-- New Password -->
4374
+ <div class="flex flex-col gap-2">
4375
+ <label for="newPassword" class="font-medium text-color">New Password *</label>
4376
+ <p-password
4377
+ id="newPassword"
4378
+ name="newPassword"
4379
+ [ngModel]="passwordModel().newPassword"
4380
+ (ngModelChange)="updatePasswordField('newPassword', $event)"
4381
+ [feedback]="true"
4382
+ [toggleMask]="true"
4383
+ placeholder="Min 6 characters"
4384
+ styleClass="w-full"
4385
+ inputStyleClass="w-full" />
4386
+ </div>
4387
+
4388
+ <!-- Confirm Password -->
4389
+ <div class="flex flex-col gap-2">
4390
+ <label for="confirmPassword" class="font-medium text-color">Confirm Password *</label>
4391
+ <p-password
4392
+ id="confirmPassword"
4393
+ name="confirmPassword"
4394
+ [ngModel]="passwordModel().confirmPassword"
4395
+ (ngModelChange)="updatePasswordField('confirmPassword', $event)"
4396
+ [feedback]="false"
4397
+ [toggleMask]="true"
4398
+ placeholder="Confirm new password"
4399
+ styleClass="w-full"
4400
+ inputStyleClass="w-full" />
4401
+ @if (!passwordsMatch()) {
4402
+ <small class="text-red-500">Passwords do not match</small>
4403
+ }
4404
+ </div>
4405
+ </div>
4406
+
4407
+ <!-- Submit -->
4408
+ <div class="flex justify-end">
4409
+ <p-button
4410
+ label="Change Password"
4411
+ icon="pi pi-key"
4412
+ type="submit"
4413
+ [loading]="isPasswordLoading()"
4414
+ [disabled]="!isPasswordFormValid() || isPasswordLoading()" />
4415
+ </div>
4416
+ </form>
4417
+ </div>
4418
+
4419
+ <!-- Company & Permissions Row (50/50 on desktop, stacked on mobile) -->
4420
+ @if (showBottomRow()) {
4421
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-2 md:gap-4 flex-1">
4422
+ <!-- Company/Branch Info -->
4423
+ @if (showCompanySection()) {
4424
+ <div class="card h-48 md:h-56 flex flex-col">
4425
+ <div class="flex items-center gap-2 mb-4">
4426
+ <i class="pi pi-building text-primary"></i>
4427
+ <span class="font-semibold text-color">Company & Branch</span>
4428
+ </div>
4429
+ <div class="flex flex-col gap-4 flex-1 overflow-auto">
4430
+ <div class="flex items-center gap-3">
4431
+ <div class="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0">
4432
+ <i class="pi pi-briefcase text-primary"></i>
4433
+ </div>
4434
+ <div>
4435
+ <div class="text-sm text-muted-color">Company</div>
4436
+ <div class="font-medium text-color">{{ companyName() || 'Not assigned' }}</div>
4437
+ </div>
4438
+ </div>
4439
+ <div class="flex items-center gap-3">
4440
+ <div class="w-10 h-10 rounded-lg bg-green-500/10 flex items-center justify-center flex-shrink-0">
4441
+ <i class="pi pi-map-marker text-green-500"></i>
4442
+ </div>
4443
+ <div>
4444
+ <div class="text-sm text-muted-color">Branch</div>
4445
+ <div class="font-medium text-color">{{ branchName() || 'Not assigned' }}</div>
4446
+ </div>
4447
+ </div>
4448
+ </div>
4449
+ </div>
4450
+ }
4451
+
4452
+ <!-- Permissions -->
4453
+ @if (showPermissionsSection()) {
4454
+ <div class="card h-48 md:h-56 flex flex-col">
4455
+ <div class="flex items-center justify-between mb-4">
4456
+ <div class="flex items-center gap-2">
4457
+ <i class="pi pi-shield text-primary"></i>
4458
+ <span class="font-semibold text-color">My Permissions</span>
4459
+ </div>
4460
+ <p-tag value="Read-only" severity="secondary" />
4461
+ </div>
4462
+ <div class="flex flex-col gap-4 flex-1 overflow-auto">
4463
+ <!-- Roles -->
4464
+ @if (showRoles()) {
4465
+ <div>
4466
+ <div class="text-sm text-muted-color mb-2 flex items-center gap-2">
4467
+ <i class="pi pi-users"></i> Roles
4468
+ </div>
4469
+ @if (isLoadingPermissions()) {
4470
+ <p-skeleton height="1.5rem" styleClass="mb-2" />
4471
+ } @else if (userRoles().length === 0) {
4472
+ <span class="text-muted-color text-sm">No roles assigned</span>
4473
+ } @else {
4474
+ <div class="flex flex-wrap gap-2">
4475
+ @for (role of userRoles(); track role.id) {
4476
+ <p-tag [value]="role.name" severity="info" />
4477
+ }
4478
+ </div>
4479
+ }
4480
+ </div>
4481
+ }
4482
+
4483
+ <!-- Actions -->
4484
+ @if (showActions()) {
4485
+ <div>
4486
+ <div class="text-sm text-muted-color mb-2 flex items-center gap-2">
4487
+ <i class="pi pi-bolt"></i> Direct Actions
4488
+ </div>
4489
+ @if (isLoadingPermissions()) {
4490
+ <p-skeleton height="1.5rem" styleClass="mb-2" />
4491
+ } @else if (userActions().length === 0) {
4492
+ <span class="text-muted-color text-sm">No actions assigned</span>
4493
+ } @else {
4494
+ <div class="flex flex-wrap gap-2">
4495
+ @for (action of userActions(); track action.id) {
4496
+ <p-tag [value]="action.name" severity="success" />
4497
+ }
4498
+ </div>
4499
+ }
4500
+ </div>
4501
+ }
4502
+ </div>
4503
+ </div>
4504
+ }
4505
+ </div>
4506
+ }
4507
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i2$1.Avatar, selector: "p-avatar", inputs: ["label", "icon", "image", "size", "shape", "styleClass", "ariaLabel", "ariaLabelledBy"], outputs: ["onImageError"] }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i5$1.Password, selector: "p-password", inputs: ["ariaLabel", "ariaLabelledBy", "label", "promptLabel", "mediumRegex", "strongRegex", "weakLabel", "mediumLabel", "maxLength", "strongLabel", "inputId", "feedback", "toggleMask", "inputStyleClass", "styleClass", "inputStyle", "showTransitionOptions", "hideTransitionOptions", "autocomplete", "placeholder", "showClear", "autofocus", "tabindex", "appendTo", "motionOptions", "overlayOptions"], outputs: ["onFocus", "onBlur", "onClear"] }, { kind: "component", type: i6.Skeleton, selector: "p-skeleton", inputs: ["styleClass", "shape", "animation", "borderRadius", "size", "width", "height"] }, { kind: "component", type: i4$2.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }, { kind: "component", type: FileUploaderComponent, selector: "lib-file-uploader", inputs: ["uploadFile", "acceptTypes", "multiple", "maxFiles", "maxSizeMb", "uploadOptions", "disabled", "showPreview", "autoUpload"], outputs: ["fileUploaded", "filesUploaded", "onError", "fileSelected"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
4508
+ }
4509
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ProfilePageComponent, decorators: [{
4510
+ type: Component,
4511
+ args: [{
4512
+ selector: 'lib-profile-page',
4513
+ standalone: true,
4514
+ imports: [AngularModule, PrimeModule, FormField, FileUploaderComponent],
4515
+ template: `
4516
+ <!-- Page Header -->
4517
+ <div class="mb-4 md:mb-6">
4518
+ <h1 class="text-2xl font-bold text-color mb-2">My Profile</h1>
4519
+ <p class="text-muted-color">Manage your account settings and preferences.</p>
4520
+ </div>
4521
+
4522
+ <!-- Profile Form Card (full width) -->
4523
+ <div class="card mb-2 md:mb-4">
4524
+ <div class="flex flex-col md:flex-row gap-4 md:gap-6">
4525
+ <!-- Avatar Section -->
4526
+ <div class="flex flex-col items-center gap-4 md:w-72 lg:w-80 md:border-r md:border-surface-200 md:pr-6">
4527
+ @if (profilePictureUrl()) {
4528
+ <p-avatar
4529
+ [image]="profilePictureUrl()!"
4530
+ size="xlarge"
4531
+ shape="circle"
4532
+ styleClass="w-28 h-28" />
4533
+ } @else {
4534
+ <p-avatar
4535
+ [label]="userInitials()"
4536
+ size="xlarge"
4537
+ shape="circle"
4538
+ styleClass="w-28 h-28 text-3xl bg-primary text-primary-contrast" />
4539
+ }
4540
+ <div class="text-center">
4541
+ <div class="font-semibold text-color">{{ userName() }}</div>
4542
+ <div class="text-muted-color text-sm">{{ userEmail() }}</div>
4543
+ </div>
4544
+ <!-- Upload section - only show if storage is enabled and provider is available -->
4545
+ @if (showUploadSection()) {
4546
+ <lib-file-uploader
4547
+ [uploadFile]="uploadFileFn"
4548
+ [acceptTypes]="['image/*']"
4549
+ [multiple]="false"
4550
+ [maxSizeMb]="5"
4551
+ [showPreview]="false"
4552
+ (fileUploaded)="onProfilePictureUploaded($event)"
4553
+ class="w-full" />
4554
+ } @else {
4555
+ <div class="w-full p-3 bg-surface-100 rounded-lg text-center">
4556
+ <i class="pi pi-info-circle text-muted-color mr-2"></i>
4557
+ <span class="text-sm text-muted-color">Storage feature not enabled</span>
4558
+ </div>
4559
+ }
4560
+ </div>
4561
+
4562
+ <!-- Form Section -->
4563
+ <div class="flex-1">
4564
+ <form (ngSubmit)="onSubmit()" class="flex flex-col gap-4">
4565
+ <!-- Email (read-only) -->
4566
+ <div class="flex flex-col gap-2">
4567
+ <label class="font-medium text-color">Email</label>
4568
+ <input
4569
+ pInputText
4570
+ [value]="userEmail()"
4571
+ disabled
4572
+ class="bg-surface-50" />
4573
+ </div>
4574
+
4575
+ <!-- Name -->
4576
+ <div class="flex flex-col gap-2">
4577
+ <label for="name" class="font-medium text-color">Name *</label>
4578
+ <input
4579
+ pInputText
4580
+ id="name"
4581
+ [formField]="formTree.name"
4582
+ placeholder="Your full name" />
4583
+ </div>
4584
+
4585
+ <!-- Phone -->
4586
+ <div class="flex flex-col gap-2">
4587
+ <label for="phone" class="font-medium text-color">Phone</label>
4588
+ <input
4589
+ pInputText
4590
+ id="phone"
4591
+ [formField]="formTree.phone"
4592
+ placeholder="+1 234 567 8900" />
4593
+ </div>
4594
+
4595
+ <!-- Submit -->
4596
+ <div class="flex justify-end pt-4">
4597
+ <p-button
4598
+ label="Save Changes"
4599
+ icon="pi pi-check"
4600
+ type="submit"
4601
+ [loading]="isLoading()"
4602
+ [disabled]="!isFormValid() || isLoading()" />
4603
+ </div>
4604
+ </form>
4605
+ </div>
4606
+ </div>
4607
+ </div>
4608
+
4609
+ <!-- Security Card -->
4610
+ <div class="card mb-2 md:mb-4">
4611
+ <div class="flex items-center gap-2 mb-4">
4612
+ <i class="pi pi-lock text-primary"></i>
4613
+ <span class="font-semibold text-color">Security</span>
4614
+ </div>
4615
+ <form (ngSubmit)="onChangePassword()" class="flex flex-col gap-4">
4616
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4">
4617
+ <!-- Current Password -->
4618
+ <div class="flex flex-col gap-2">
4619
+ <label for="currentPassword" class="font-medium text-color">Current Password *</label>
4620
+ <p-password
4621
+ id="currentPassword"
4622
+ name="currentPassword"
4623
+ [ngModel]="passwordModel().currentPassword"
4624
+ (ngModelChange)="updatePasswordField('currentPassword', $event)"
4625
+ [feedback]="false"
4626
+ [toggleMask]="true"
4627
+ placeholder="Enter current password"
4628
+ styleClass="w-full"
4629
+ inputStyleClass="w-full" />
4630
+ </div>
4631
+
4632
+ <!-- New Password -->
4633
+ <div class="flex flex-col gap-2">
4634
+ <label for="newPassword" class="font-medium text-color">New Password *</label>
4635
+ <p-password
4636
+ id="newPassword"
4637
+ name="newPassword"
4638
+ [ngModel]="passwordModel().newPassword"
4639
+ (ngModelChange)="updatePasswordField('newPassword', $event)"
4640
+ [feedback]="true"
4641
+ [toggleMask]="true"
4642
+ placeholder="Min 6 characters"
4643
+ styleClass="w-full"
4644
+ inputStyleClass="w-full" />
4645
+ </div>
4646
+
4647
+ <!-- Confirm Password -->
4648
+ <div class="flex flex-col gap-2">
4649
+ <label for="confirmPassword" class="font-medium text-color">Confirm Password *</label>
4650
+ <p-password
4651
+ id="confirmPassword"
4652
+ name="confirmPassword"
4653
+ [ngModel]="passwordModel().confirmPassword"
4654
+ (ngModelChange)="updatePasswordField('confirmPassword', $event)"
4655
+ [feedback]="false"
4656
+ [toggleMask]="true"
4657
+ placeholder="Confirm new password"
4658
+ styleClass="w-full"
4659
+ inputStyleClass="w-full" />
4660
+ @if (!passwordsMatch()) {
4661
+ <small class="text-red-500">Passwords do not match</small>
4662
+ }
4663
+ </div>
4664
+ </div>
4665
+
4666
+ <!-- Submit -->
4667
+ <div class="flex justify-end">
4668
+ <p-button
4669
+ label="Change Password"
4670
+ icon="pi pi-key"
4671
+ type="submit"
4672
+ [loading]="isPasswordLoading()"
4673
+ [disabled]="!isPasswordFormValid() || isPasswordLoading()" />
4674
+ </div>
4675
+ </form>
4676
+ </div>
4677
+
4678
+ <!-- Company & Permissions Row (50/50 on desktop, stacked on mobile) -->
4679
+ @if (showBottomRow()) {
4680
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-2 md:gap-4 flex-1">
4681
+ <!-- Company/Branch Info -->
4682
+ @if (showCompanySection()) {
4683
+ <div class="card h-48 md:h-56 flex flex-col">
4684
+ <div class="flex items-center gap-2 mb-4">
4685
+ <i class="pi pi-building text-primary"></i>
4686
+ <span class="font-semibold text-color">Company & Branch</span>
4687
+ </div>
4688
+ <div class="flex flex-col gap-4 flex-1 overflow-auto">
4689
+ <div class="flex items-center gap-3">
4690
+ <div class="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center flex-shrink-0">
4691
+ <i class="pi pi-briefcase text-primary"></i>
4692
+ </div>
4693
+ <div>
4694
+ <div class="text-sm text-muted-color">Company</div>
4695
+ <div class="font-medium text-color">{{ companyName() || 'Not assigned' }}</div>
4696
+ </div>
4697
+ </div>
4698
+ <div class="flex items-center gap-3">
4699
+ <div class="w-10 h-10 rounded-lg bg-green-500/10 flex items-center justify-center flex-shrink-0">
4700
+ <i class="pi pi-map-marker text-green-500"></i>
4701
+ </div>
4702
+ <div>
4703
+ <div class="text-sm text-muted-color">Branch</div>
4704
+ <div class="font-medium text-color">{{ branchName() || 'Not assigned' }}</div>
4705
+ </div>
4706
+ </div>
4707
+ </div>
4708
+ </div>
4709
+ }
4710
+
4711
+ <!-- Permissions -->
4712
+ @if (showPermissionsSection()) {
4713
+ <div class="card h-48 md:h-56 flex flex-col">
4714
+ <div class="flex items-center justify-between mb-4">
4715
+ <div class="flex items-center gap-2">
4716
+ <i class="pi pi-shield text-primary"></i>
4717
+ <span class="font-semibold text-color">My Permissions</span>
4718
+ </div>
4719
+ <p-tag value="Read-only" severity="secondary" />
4720
+ </div>
4721
+ <div class="flex flex-col gap-4 flex-1 overflow-auto">
4722
+ <!-- Roles -->
4723
+ @if (showRoles()) {
4724
+ <div>
4725
+ <div class="text-sm text-muted-color mb-2 flex items-center gap-2">
4726
+ <i class="pi pi-users"></i> Roles
4727
+ </div>
4728
+ @if (isLoadingPermissions()) {
4729
+ <p-skeleton height="1.5rem" styleClass="mb-2" />
4730
+ } @else if (userRoles().length === 0) {
4731
+ <span class="text-muted-color text-sm">No roles assigned</span>
4732
+ } @else {
4733
+ <div class="flex flex-wrap gap-2">
4734
+ @for (role of userRoles(); track role.id) {
4735
+ <p-tag [value]="role.name" severity="info" />
4736
+ }
4737
+ </div>
4738
+ }
4739
+ </div>
4740
+ }
4741
+
4742
+ <!-- Actions -->
4743
+ @if (showActions()) {
4744
+ <div>
4745
+ <div class="text-sm text-muted-color mb-2 flex items-center gap-2">
4746
+ <i class="pi pi-bolt"></i> Direct Actions
4747
+ </div>
4748
+ @if (isLoadingPermissions()) {
4749
+ <p-skeleton height="1.5rem" styleClass="mb-2" />
4750
+ } @else if (userActions().length === 0) {
4751
+ <span class="text-muted-color text-sm">No actions assigned</span>
4752
+ } @else {
4753
+ <div class="flex flex-wrap gap-2">
4754
+ @for (action of userActions(); track action.id) {
4755
+ <p-tag [value]="action.name" severity="success" />
4756
+ }
4757
+ </div>
4758
+ }
4759
+ </div>
4760
+ }
4761
+ </div>
4762
+ </div>
4763
+ }
4764
+ </div>
4765
+ }
4766
+ `,
4767
+ changeDetection: ChangeDetectionStrategy.OnPush,
4768
+ }]
4769
+ }] });
4770
+
4771
+ var profilePage_component = /*#__PURE__*/Object.freeze({
4772
+ __proto__: null,
4773
+ ProfilePageComponent: ProfilePageComponent
4774
+ });
4775
+
4776
+ class RegisterPageComponent {
4777
+ destroyRef = inject(DestroyRef);
4778
+ appConfig = inject(APP_CONFIG);
4779
+ messageService = inject(MessageService);
4780
+ authApi = inject(AuthApiService);
4781
+ authState = inject(AuthStateService);
4782
+ route = inject(ActivatedRoute);
4783
+ router = inject(Router);
4784
+ returnUrl = input('', ...(ngDevMode ? [{ debugName: "returnUrl" }] : []));
4785
+ isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
4786
+ companyFeatureEnabled = computed(() => isCompanyFeatureEnabled(this.appConfig), ...(ngDevMode ? [{ debugName: "companyFeatureEnabled" }] : []));
4787
+ userFormModel = signal({
4788
+ name: '',
4789
+ email: '',
4790
+ password: '',
4791
+ confirmPassword: '',
4792
+ phone: '',
4793
+ }, ...(ngDevMode ? [{ debugName: "userFormModel" }] : []));
4794
+ userForm = form(this.userFormModel, (f) => {
4795
+ required(f.name, { message: 'Name is required' });
4796
+ minLength(f.name, 2, { message: 'Name must be at least 2 characters' });
4797
+ required(f.email, { message: 'Email is required' });
4798
+ email(f.email, { message: 'Please enter a valid email address' });
4799
+ required(f.password, { message: 'Password is required' });
4800
+ minLength(f.password, 6, { message: 'Password must be at least 6 characters' });
4801
+ required(f.confirmPassword, { message: 'Please confirm your password' });
4802
+ });
4803
+ companyFormModel = signal({
4804
+ createNewCompany: false,
4805
+ companySlug: '',
4806
+ branchSlug: '',
4807
+ newCompanyName: '',
4808
+ newCompanyPhone: '',
4809
+ newCompanyAddress: '',
4810
+ }, ...(ngDevMode ? [{ debugName: "companyFormModel" }] : []));
4811
+ companyForm = form(this.companyFormModel, (f) => { });
3663
4812
  passwordsMatch = computed(() => {
3664
4813
  const model = this.userFormModel();
3665
4814
  return model.password === model.confirmPassword;
3666
4815
  }, ...(ngDevMode ? [{ debugName: "passwordsMatch" }] : []));
3667
- /** Check if form is valid */
3668
4816
  isFormValid = computed(() => {
3669
4817
  const userModel = this.userFormModel();
3670
- // User validation
3671
4818
  const userValid = userModel.name.trim().length >= 2 &&
3672
4819
  userModel.email.includes('@') &&
3673
4820
  userModel.password.length >= 6 &&
3674
4821
  userModel.confirmPassword.length > 0 &&
3675
4822
  this.passwordsMatch();
3676
- if (!isCompanyFeatureEnabled(this.appConfig)) {
4823
+ if (!isCompanyFeatureEnabled(this.appConfig))
3677
4824
  return userValid;
3678
- }
3679
4825
  const companyModel = this.companyFormModel();
3680
- // Company validation
3681
4826
  if (companyModel.createNewCompany) {
3682
4827
  return userValid && companyModel.newCompanyName.trim().length > 0;
3683
4828
  }
3684
4829
  return userValid;
3685
4830
  }, ...(ngDevMode ? [{ debugName: "isFormValid" }] : []));
3686
- // ============================================
3687
- // Event Handlers
3688
- // ============================================
4831
+ ngOnInit() {
4832
+ const companySlug = this.route.snapshot.queryParamMap.get('companySlug');
4833
+ if (companySlug) {
4834
+ this.companyFormModel.update((model) => ({ ...model, companySlug }));
4835
+ }
4836
+ }
3689
4837
  onCreateCompanyToggle() {
3690
4838
  const currentValue = this.companyFormModel().createNewCompany;
3691
- // Reset fields when toggling
3692
4839
  if (!currentValue) {
3693
- // Toggling to create new company
3694
4840
  this.companyFormModel.update((model) => ({
3695
4841
  ...model,
3696
4842
  createNewCompany: true,
@@ -3699,7 +4845,6 @@ class RegisterPageComponent {
3699
4845
  }));
3700
4846
  }
3701
4847
  else {
3702
- // Toggling to join existing company
3703
4848
  this.companyFormModel.update((model) => ({
3704
4849
  ...model,
3705
4850
  createNewCompany: false,
@@ -3727,7 +4872,6 @@ class RegisterPageComponent {
3727
4872
  password: userModel.password,
3728
4873
  phone: userModel.phone || undefined,
3729
4874
  };
3730
- // Add company fields if company feature enabled
3731
4875
  if (isCompanyFeatureEnabled(this.appConfig)) {
3732
4876
  if (companyModel.createNewCompany) {
3733
4877
  registrationData.newCompanyName =
@@ -3744,13 +4888,21 @@ class RegisterPageComponent {
3744
4888
  }
3745
4889
  this.authApi
3746
4890
  .register(registrationData)
3747
- .pipe(take(1), takeUntilDestroyed(this.destroyRef))
4891
+ .pipe(takeUntilDestroyed(this.destroyRef))
3748
4892
  .subscribe({
3749
4893
  next: (response) => {
3750
4894
  this.isLoading.set(false);
3751
- // Backend returns RegistrationResponse directly (not wrapped)
3752
- if (response && response.accessToken) {
3753
- // Set auth state with registration response
4895
+ if (response.requiresEmailVerification) {
4896
+ this.messageService.add({
4897
+ severity: 'success',
4898
+ summary: 'Account Created',
4899
+ detail: 'Please check your email to verify your account.',
4900
+ life: 5000,
4901
+ });
4902
+ this.router.navigate(['/auth/login']);
4903
+ return;
4904
+ }
4905
+ if (response.accessToken) {
3754
4906
  this.authState.setLoginState({
3755
4907
  accessToken: response.accessToken,
3756
4908
  refreshToken: response.refreshToken,
@@ -3766,21 +4918,13 @@ class RegisterPageComponent {
3766
4918
  });
3767
4919
  this.authState.navigateAfterLogin(this.returnUrl());
3768
4920
  }
3769
- else {
3770
- this.messageService.add({
3771
- severity: 'error',
3772
- summary: 'Registration Failed',
3773
- detail: 'Unable to create account. Please try again.',
3774
- });
3775
- }
3776
4921
  },
3777
4922
  error: (error) => {
3778
4923
  this.isLoading.set(false);
3779
4924
  this.messageService.add({
3780
4925
  severity: 'error',
3781
4926
  summary: 'Registration Failed',
3782
- detail: error.error?.message ||
3783
- 'Unable to create account. Please try again.',
4927
+ detail: error.error?.message || 'Unable to create account. Please try again.',
3784
4928
  });
3785
4929
  },
3786
4930
  });
@@ -3812,7 +4956,6 @@ class RegisterPageComponent {
3812
4956
  </div>
3813
4957
 
3814
4958
  <form (ngSubmit)="onSubmit()">
3815
- <!-- User Information -->
3816
4959
  <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
3817
4960
  <div>
3818
4961
  <label for="name" class="block font-medium mb-2"
@@ -3895,7 +5038,6 @@ class RegisterPageComponent {
3895
5038
  </div>
3896
5039
  </div>
3897
5040
 
3898
- <!-- Company Feature Section -->
3899
5041
  @if (companyFeatureEnabled()) {
3900
5042
  <div
3901
5043
  class="border-t pt-6 mt-6"
@@ -3923,9 +5065,7 @@ class RegisterPageComponent {
3923
5065
  </label>
3924
5066
  </div>
3925
5067
 
3926
- <!-- Fixed height container to prevent height changes -->
3927
5068
  @if (!companyFormModel().createNewCompany) {
3928
- <!-- Join Existing Company -->
3929
5069
  <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
3930
5070
  <div>
3931
5071
  <label for="companySlug" class="block font-medium mb-2"
@@ -3957,7 +5097,6 @@ class RegisterPageComponent {
3957
5097
  </div>
3958
5098
  </div>
3959
5099
  } @else {
3960
- <!-- Create New Company -->
3961
5100
  <div class="mb-4">
3962
5101
  <label for="newCompanyName" class="block font-medium mb-2"
3963
5102
  >Company Name *</label
@@ -4004,7 +5143,6 @@ class RegisterPageComponent {
4004
5143
  </div>
4005
5144
  }
4006
5145
 
4007
- <!-- Submit -->
4008
5146
  <p-button
4009
5147
  label="Create Account"
4010
5148
  styleClass="w-full mt-4"
@@ -4027,7 +5165,7 @@ class RegisterPageComponent {
4027
5165
  </div>
4028
5166
  </div>
4029
5167
  </div>
4030
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "directive", type: i2.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }, { kind: "component", type: AppFloatingConfigurator, selector: "app-floating-configurator" }] });
5168
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }, { kind: "component", type: AppFloatingConfigurator, selector: "app-floating-configurator" }] });
4031
5169
  }
4032
5170
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: RegisterPageComponent, decorators: [{
4033
5171
  type: Component,
@@ -4061,7 +5199,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
4061
5199
  </div>
4062
5200
 
4063
5201
  <form (ngSubmit)="onSubmit()">
4064
- <!-- User Information -->
4065
5202
  <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
4066
5203
  <div>
4067
5204
  <label for="name" class="block font-medium mb-2"
@@ -4144,7 +5281,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
4144
5281
  </div>
4145
5282
  </div>
4146
5283
 
4147
- <!-- Company Feature Section -->
4148
5284
  @if (companyFeatureEnabled()) {
4149
5285
  <div
4150
5286
  class="border-t pt-6 mt-6"
@@ -4172,9 +5308,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
4172
5308
  </label>
4173
5309
  </div>
4174
5310
 
4175
- <!-- Fixed height container to prevent height changes -->
4176
5311
  @if (!companyFormModel().createNewCompany) {
4177
- <!-- Join Existing Company -->
4178
5312
  <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
4179
5313
  <div>
4180
5314
  <label for="companySlug" class="block font-medium mb-2"
@@ -4192,97 +5326,402 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImpor
4192
5326
  >
4193
5327
  </div>
4194
5328
 
4195
- <div>
4196
- <label for="branchSlug" class="block font-medium mb-2"
4197
- >Branch Code</label
4198
- >
4199
- <input
4200
- pInputText
4201
- id="branchSlug"
4202
- [formField]="companyForm.branchSlug"
4203
- placeholder="Branch code (optional)"
4204
- class="w-full"
4205
- />
5329
+ <div>
5330
+ <label for="branchSlug" class="block font-medium mb-2"
5331
+ >Branch Code</label
5332
+ >
5333
+ <input
5334
+ pInputText
5335
+ id="branchSlug"
5336
+ [formField]="companyForm.branchSlug"
5337
+ placeholder="Branch code (optional)"
5338
+ class="w-full"
5339
+ />
5340
+ </div>
5341
+ </div>
5342
+ } @else {
5343
+ <div class="mb-4">
5344
+ <label for="newCompanyName" class="block font-medium mb-2"
5345
+ >Company Name *</label
5346
+ >
5347
+ <input
5348
+ pInputText
5349
+ id="newCompanyName"
5350
+ [formField]="companyForm.newCompanyName"
5351
+ placeholder="Your company name"
5352
+ class="w-full"
5353
+ />
5354
+ </div>
5355
+
5356
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
5357
+ <div>
5358
+ <label for="newCompanyPhone" class="block font-medium mb-2"
5359
+ >Company Phone</label
5360
+ >
5361
+ <input
5362
+ pInputText
5363
+ id="newCompanyPhone"
5364
+ [formField]="companyForm.newCompanyPhone"
5365
+ placeholder="Company phone"
5366
+ class="w-full"
5367
+ />
5368
+ </div>
5369
+
5370
+ <div>
5371
+ <label
5372
+ for="newCompanyAddress"
5373
+ class="block font-medium mb-2"
5374
+ >Address</label
5375
+ >
5376
+ <input
5377
+ pInputText
5378
+ id="newCompanyAddress"
5379
+ [formField]="companyForm.newCompanyAddress"
5380
+ placeholder="Company address"
5381
+ class="w-full"
5382
+ />
5383
+ </div>
5384
+ </div>
5385
+ }
5386
+ </div>
5387
+ }
5388
+
5389
+ <p-button
5390
+ label="Create Account"
5391
+ styleClass="w-full mt-4"
5392
+ type="submit"
5393
+ [loading]="isLoading()"
5394
+ [disabled]="!isFormValid() || isLoading()"
5395
+ />
5396
+
5397
+ <div class="flex items-center justify-center mt-4 gap-2">
5398
+ <span class="font-medium">Already have an account?</span>
5399
+ <a
5400
+ routerLink="/auth/login"
5401
+ class="font-semibold text-primary cursor-pointer"
5402
+ >
5403
+ Sign In
5404
+ </a>
5405
+ </div>
5406
+ </form>
5407
+ </div>
5408
+ </div>
5409
+ </div>
5410
+ </div>
5411
+ `,
5412
+ }]
5413
+ }], propDecorators: { returnUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "returnUrl", required: false }] }] } });
5414
+
5415
+ var registerPage_component = /*#__PURE__*/Object.freeze({
5416
+ __proto__: null,
5417
+ RegisterPageComponent: RegisterPageComponent
5418
+ });
5419
+
5420
+ class ResetPasswordPageComponent {
5421
+ destroyRef = inject(DestroyRef);
5422
+ route = inject(ActivatedRoute);
5423
+ messageService = inject(MessageService);
5424
+ authApi = inject(AuthApiService);
5425
+ token = '';
5426
+ isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
5427
+ pageState = signal('form', ...(ngDevMode ? [{ debugName: "pageState" }] : []));
5428
+ formModel = signal({ newPassword: '', confirmPassword: '' }, ...(ngDevMode ? [{ debugName: "formModel" }] : []));
5429
+ resetForm = form(this.formModel, (f) => {
5430
+ required(f.newPassword, { message: 'New password is required' });
5431
+ minLength(f.newPassword, 6, { message: 'Password must be at least 6 characters' });
5432
+ required(f.confirmPassword, { message: 'Please confirm your password' });
5433
+ });
5434
+ passwordMismatch = computed(() => {
5435
+ const model = this.formModel();
5436
+ return model.confirmPassword.length > 0 && model.newPassword !== model.confirmPassword;
5437
+ }, ...(ngDevMode ? [{ debugName: "passwordMismatch" }] : []));
5438
+ canSubmit = computed(() => this.resetForm().valid() && !this.passwordMismatch(), ...(ngDevMode ? [{ debugName: "canSubmit" }] : []));
5439
+ ngOnInit() {
5440
+ this.route.queryParams.pipe(take(1)).subscribe((params) => {
5441
+ this.token = params['token'] || '';
5442
+ if (!this.token) {
5443
+ this.pageState.set('invalid-token');
5444
+ }
5445
+ });
5446
+ }
5447
+ onSubmit() {
5448
+ if (!this.canSubmit()) {
5449
+ this.messageService.add({
5450
+ severity: 'error',
5451
+ summary: 'Validation Error',
5452
+ detail: 'Please fill in all fields correctly.',
5453
+ });
5454
+ return;
5455
+ }
5456
+ this.isLoading.set(true);
5457
+ const newPassword = this.formModel().newPassword;
5458
+ this.authApi
5459
+ .resetPassword(this.token, newPassword)
5460
+ .pipe(takeUntilDestroyed(this.destroyRef))
5461
+ .subscribe({
5462
+ next: () => {
5463
+ this.isLoading.set(false);
5464
+ this.messageService.add({
5465
+ severity: 'success',
5466
+ summary: 'Success',
5467
+ detail: 'Your password has been reset successfully.',
5468
+ });
5469
+ this.pageState.set('success');
5470
+ },
5471
+ error: (error) => {
5472
+ this.isLoading.set(false);
5473
+ const message = error.error?.message || 'Failed to reset password';
5474
+ if (message.toLowerCase().includes('expired') || message.toLowerCase().includes('invalid')) {
5475
+ this.pageState.set('invalid-token');
5476
+ }
5477
+ else {
5478
+ this.messageService.add({
5479
+ severity: 'error',
5480
+ summary: 'Error',
5481
+ detail: message,
5482
+ });
5483
+ }
5484
+ },
5485
+ });
5486
+ }
5487
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ResetPasswordPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
5488
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: ResetPasswordPageComponent, isStandalone: true, selector: "lib-reset-password-page", ngImport: i0, template: `
5489
+ <app-floating-configurator />
5490
+ <div class="bg-surface-ground flex items-center justify-center min-h-screen">
5491
+ <div class="flex flex-col items-center justify-center w-full max-w-[32rem] px-4">
5492
+ <div
5493
+ class="w-full"
5494
+ style="border-radius: var(--p-content-border-radius); padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)">
5495
+ <div
5496
+ class="w-full py-16 px-12"
5497
+ style="border-radius: var(--p-content-border-radius); background: var(--p-content-background);">
5498
+
5499
+ @switch (pageState()) {
5500
+ @case ('form') {
5501
+ <div class="text-center mb-8">
5502
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
5503
+ Reset Password
5504
+ </div>
5505
+ <span class="text-muted-color font-medium">
5506
+ Enter your new password below.
5507
+ </span>
5508
+ </div>
5509
+
5510
+ <form (ngSubmit)="onSubmit()">
5511
+ <label for="newPassword" class="block text-lg font-medium mb-2" style="color: var(--p-content-color);">
5512
+ New Password
5513
+ </label>
5514
+ <input
5515
+ pInputText
5516
+ id="newPassword"
5517
+ type="password"
5518
+ placeholder="Enter new password"
5519
+ class="w-full md:w-[25rem] mb-4"
5520
+ [formField]="resetForm.newPassword"
5521
+ autocomplete="new-password" />
5522
+
5523
+ <label for="confirmPassword" class="block text-lg font-medium mb-2" style="color: var(--p-content-color);">
5524
+ Confirm Password
5525
+ </label>
5526
+ <input
5527
+ pInputText
5528
+ id="confirmPassword"
5529
+ type="password"
5530
+ placeholder="Confirm new password"
5531
+ class="w-full md:w-[25rem] mb-2"
5532
+ [formField]="resetForm.confirmPassword"
5533
+ autocomplete="new-password" />
5534
+
5535
+ @if (passwordMismatch()) {
5536
+ <small class="text-red-500 block mb-4">Passwords do not match</small>
5537
+ }
5538
+
5539
+ <p-button
5540
+ label="Reset Password"
5541
+ styleClass="w-full mt-4 mb-4"
5542
+ type="submit"
5543
+ [loading]="isLoading()"
5544
+ [disabled]="!canSubmit() || isLoading()" />
5545
+
5546
+ <div class="flex items-center justify-center gap-2">
5547
+ <a routerLink="/auth/login" class="font-semibold text-primary cursor-pointer">
5548
+ Back to Sign In
5549
+ </a>
5550
+ </div>
5551
+ </form>
5552
+ }
5553
+
5554
+ @case ('success') {
5555
+ <div class="text-center">
5556
+ <div class="flex justify-center mb-6">
5557
+ <i class="pi pi-check-circle text-6xl text-green-500"></i>
5558
+ </div>
5559
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
5560
+ Password Reset Successful
5561
+ </div>
5562
+ <p class="text-muted-color font-medium mb-6">
5563
+ Your password has been reset successfully.
5564
+ You can now sign in with your new password.
5565
+ </p>
5566
+ <a routerLink="/auth/login">
5567
+ <p-button label="Sign In" styleClass="w-full" />
5568
+ </a>
5569
+ </div>
5570
+ }
5571
+
5572
+ @case ('invalid-token') {
5573
+ <div class="text-center">
5574
+ <div class="flex justify-center mb-6">
5575
+ <i class="pi pi-times-circle text-6xl text-red-500"></i>
5576
+ </div>
5577
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
5578
+ Invalid or Expired Link
5579
+ </div>
5580
+ <p class="text-muted-color font-medium mb-6">
5581
+ This password reset link is invalid or has expired.
5582
+ Please request a new one.
5583
+ </p>
5584
+ <a routerLink="/auth/forgot-password">
5585
+ <p-button label="Request New Link" styleClass="w-full mb-4" />
5586
+ </a>
5587
+ <div class="mt-4">
5588
+ <a routerLink="/auth/login" class="font-semibold text-primary cursor-pointer">
5589
+ Back to Sign In
5590
+ </a>
5591
+ </div>
5592
+ </div>
5593
+ }
5594
+ }
5595
+ </div>
5596
+ </div>
5597
+ </div>
5598
+ </div>
5599
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }, { kind: "component", type: AppFloatingConfigurator, selector: "app-floating-configurator" }] });
5600
+ }
5601
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: ResetPasswordPageComponent, decorators: [{
5602
+ type: Component,
5603
+ args: [{
5604
+ selector: 'lib-reset-password-page',
5605
+ standalone: true,
5606
+ imports: [AngularModule, PrimeModule, FormField, AppFloatingConfigurator],
5607
+ template: `
5608
+ <app-floating-configurator />
5609
+ <div class="bg-surface-ground flex items-center justify-center min-h-screen">
5610
+ <div class="flex flex-col items-center justify-center w-full max-w-[32rem] px-4">
5611
+ <div
5612
+ class="w-full"
5613
+ style="border-radius: var(--p-content-border-radius); padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)">
5614
+ <div
5615
+ class="w-full py-16 px-12"
5616
+ style="border-radius: var(--p-content-border-radius); background: var(--p-content-background);">
5617
+
5618
+ @switch (pageState()) {
5619
+ @case ('form') {
5620
+ <div class="text-center mb-8">
5621
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
5622
+ Reset Password
4206
5623
  </div>
5624
+ <span class="text-muted-color font-medium">
5625
+ Enter your new password below.
5626
+ </span>
4207
5627
  </div>
4208
- } @else {
4209
- <!-- Create New Company -->
4210
- <div class="mb-4">
4211
- <label for="newCompanyName" class="block font-medium mb-2"
4212
- >Company Name *</label
4213
- >
5628
+
5629
+ <form (ngSubmit)="onSubmit()">
5630
+ <label for="newPassword" class="block text-lg font-medium mb-2" style="color: var(--p-content-color);">
5631
+ New Password
5632
+ </label>
4214
5633
  <input
4215
5634
  pInputText
4216
- id="newCompanyName"
4217
- [formField]="companyForm.newCompanyName"
4218
- placeholder="Your company name"
4219
- class="w-full"
4220
- />
4221
- </div>
5635
+ id="newPassword"
5636
+ type="password"
5637
+ placeholder="Enter new password"
5638
+ class="w-full md:w-[25rem] mb-4"
5639
+ [formField]="resetForm.newPassword"
5640
+ autocomplete="new-password" />
4222
5641
 
4223
- <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
4224
- <div>
4225
- <label for="newCompanyPhone" class="block font-medium mb-2"
4226
- >Company Phone</label
4227
- >
4228
- <input
4229
- pInputText
4230
- id="newCompanyPhone"
4231
- [formField]="companyForm.newCompanyPhone"
4232
- placeholder="Company phone"
4233
- class="w-full"
4234
- />
5642
+ <label for="confirmPassword" class="block text-lg font-medium mb-2" style="color: var(--p-content-color);">
5643
+ Confirm Password
5644
+ </label>
5645
+ <input
5646
+ pInputText
5647
+ id="confirmPassword"
5648
+ type="password"
5649
+ placeholder="Confirm new password"
5650
+ class="w-full md:w-[25rem] mb-2"
5651
+ [formField]="resetForm.confirmPassword"
5652
+ autocomplete="new-password" />
5653
+
5654
+ @if (passwordMismatch()) {
5655
+ <small class="text-red-500 block mb-4">Passwords do not match</small>
5656
+ }
5657
+
5658
+ <p-button
5659
+ label="Reset Password"
5660
+ styleClass="w-full mt-4 mb-4"
5661
+ type="submit"
5662
+ [loading]="isLoading()"
5663
+ [disabled]="!canSubmit() || isLoading()" />
5664
+
5665
+ <div class="flex items-center justify-center gap-2">
5666
+ <a routerLink="/auth/login" class="font-semibold text-primary cursor-pointer">
5667
+ Back to Sign In
5668
+ </a>
4235
5669
  </div>
5670
+ </form>
5671
+ }
4236
5672
 
4237
- <div>
4238
- <label
4239
- for="newCompanyAddress"
4240
- class="block font-medium mb-2"
4241
- >Address</label
4242
- >
4243
- <input
4244
- pInputText
4245
- id="newCompanyAddress"
4246
- [formField]="companyForm.newCompanyAddress"
4247
- placeholder="Company address"
4248
- class="w-full"
4249
- />
5673
+ @case ('success') {
5674
+ <div class="text-center">
5675
+ <div class="flex justify-center mb-6">
5676
+ <i class="pi pi-check-circle text-6xl text-green-500"></i>
4250
5677
  </div>
5678
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
5679
+ Password Reset Successful
5680
+ </div>
5681
+ <p class="text-muted-color font-medium mb-6">
5682
+ Your password has been reset successfully.
5683
+ You can now sign in with your new password.
5684
+ </p>
5685
+ <a routerLink="/auth/login">
5686
+ <p-button label="Sign In" styleClass="w-full" />
5687
+ </a>
4251
5688
  </div>
4252
- }
4253
- </div>
4254
5689
  }
4255
5690
 
4256
- <!-- Submit -->
4257
- <p-button
4258
- label="Create Account"
4259
- styleClass="w-full mt-4"
4260
- type="submit"
4261
- [loading]="isLoading()"
4262
- [disabled]="!isFormValid() || isLoading()"
4263
- />
4264
-
4265
- <div class="flex items-center justify-center mt-4 gap-2">
4266
- <span class="font-medium">Already have an account?</span>
4267
- <a
4268
- routerLink="/auth/login"
4269
- class="font-semibold text-primary cursor-pointer"
4270
- >
4271
- Sign In
4272
- </a>
4273
- </div>
4274
- </form>
5691
+ @case ('invalid-token') {
5692
+ <div class="text-center">
5693
+ <div class="flex justify-center mb-6">
5694
+ <i class="pi pi-times-circle text-6xl text-red-500"></i>
5695
+ </div>
5696
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
5697
+ Invalid or Expired Link
5698
+ </div>
5699
+ <p class="text-muted-color font-medium mb-6">
5700
+ This password reset link is invalid or has expired.
5701
+ Please request a new one.
5702
+ </p>
5703
+ <a routerLink="/auth/forgot-password">
5704
+ <p-button label="Request New Link" styleClass="w-full mb-4" />
5705
+ </a>
5706
+ <div class="mt-4">
5707
+ <a routerLink="/auth/login" class="font-semibold text-primary cursor-pointer">
5708
+ Back to Sign In
5709
+ </a>
5710
+ </div>
5711
+ </div>
5712
+ }
5713
+ }
4275
5714
  </div>
4276
5715
  </div>
4277
5716
  </div>
4278
5717
  </div>
4279
5718
  `,
4280
5719
  }]
4281
- }], propDecorators: { returnUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "returnUrl", required: false }] }] } });
5720
+ }] });
4282
5721
 
4283
- var registerPage_component = /*#__PURE__*/Object.freeze({
5722
+ var resetPasswordPage_component = /*#__PURE__*/Object.freeze({
4284
5723
  __proto__: null,
4285
- RegisterPageComponent: RegisterPageComponent
5724
+ ResetPasswordPageComponent: ResetPasswordPageComponent
4286
5725
  });
4287
5726
 
4288
5727
  /**
@@ -4596,7 +6035,7 @@ class UserFormComponent {
4596
6035
  </div>
4597
6036
  </form>
4598
6037
  </p-card>
4599
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "directive", type: i2.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i4.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }] });
6038
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i3$1.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }] });
4600
6039
  }
4601
6040
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserFormComponent, decorators: [{
4602
6041
  type: Component,
@@ -4889,7 +6328,7 @@ class UserCompanyPermissionDialogComponent {
4889
6328
  [disabled]="isSaving()" />
4890
6329
  </ng-template>
4891
6330
  </p-dialog>
4892
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "directive", type: i2$2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i4$2.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "component", type: i5.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i6.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }] });
6331
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "directive", type: i2$2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i4$3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "component", type: i5$2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i6$1.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }] });
4893
6332
  }
4894
6333
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserCompanyPermissionDialogComponent, decorators: [{
4895
6334
  type: Component,
@@ -5194,7 +6633,7 @@ class UserBranchPermissionDialogComponent {
5194
6633
  [disabled]="isSaving()" />
5195
6634
  </ng-template>
5196
6635
  </p-dialog>
5197
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "directive", type: i2$2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i4$2.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "component", type: i7.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "component", type: i5.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i6.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }] });
6636
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "directive", type: i2$2.PrimeTemplate, selector: "[pTemplate]", inputs: ["type", "pTemplate"] }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i4$3.Checkbox, selector: "p-checkbox, p-checkBox, p-check-box", inputs: ["hostName", "value", "binary", "ariaLabelledBy", "ariaLabel", "tabindex", "inputId", "inputStyle", "styleClass", "inputClass", "indeterminate", "formControl", "checkboxIcon", "readonly", "autofocus", "trueValue", "falseValue", "variant", "size"], outputs: ["onChange", "onFocus", "onBlur"] }, { kind: "component", type: i5$2.Dialog, selector: "p-dialog", inputs: ["hostName", "header", "draggable", "resizable", "contentStyle", "contentStyleClass", "modal", "closeOnEscape", "dismissableMask", "rtl", "closable", "breakpoints", "styleClass", "maskStyleClass", "maskStyle", "showHeader", "blockScroll", "autoZIndex", "baseZIndex", "minX", "minY", "focusOnShow", "maximizable", "keepInViewport", "focusTrap", "transitionOptions", "maskMotionOptions", "motionOptions", "closeIcon", "closeAriaLabel", "closeTabindex", "minimizeIcon", "maximizeIcon", "closeButtonProps", "maximizeButtonProps", "visible", "style", "position", "role", "appendTo", "content", "contentTemplate", "footerTemplate", "closeIconTemplate", "maximizeIconTemplate", "minimizeIconTemplate", "headlessTemplate"], outputs: ["onShow", "onHide", "visibleChange", "onResizeInit", "onResizeEnd", "onDragEnd", "onMaximize"] }, { kind: "component", type: i4$1.Select, selector: "p-select", inputs: ["id", "scrollHeight", "filter", "panelStyle", "styleClass", "panelStyleClass", "readonly", "editable", "tabindex", "placeholder", "loadingIcon", "filterPlaceholder", "filterLocale", "inputId", "dataKey", "filterBy", "filterFields", "autofocus", "resetFilterOnHide", "checkmark", "dropdownIcon", "loading", "optionLabel", "optionValue", "optionDisabled", "optionGroupLabel", "optionGroupChildren", "group", "showClear", "emptyFilterMessage", "emptyMessage", "lazy", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "overlayOptions", "ariaFilterLabel", "ariaLabel", "ariaLabelledBy", "filterMatchMode", "tooltip", "tooltipPosition", "tooltipPositionStyle", "tooltipStyleClass", "focusOnHover", "selectOnFocus", "autoOptionFocus", "autofocusFilter", "filterValue", "options", "appendTo", "motionOptions"], outputs: ["onChange", "onFilter", "onFocus", "onBlur", "onClick", "onShow", "onHide", "onClear", "onLazyLoad"] }, { kind: "ngmodule", type: DialogModule }, { kind: "ngmodule", type: ProgressSpinnerModule }, { kind: "component", type: i6$1.ProgressSpinner, selector: "p-progressSpinner, p-progress-spinner, p-progressspinner", inputs: ["styleClass", "strokeWidth", "fill", "animationDuration", "ariaLabel"] }] });
5198
6637
  }
5199
6638
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserBranchPermissionDialogComponent, decorators: [{
5200
6639
  type: Component,
@@ -5460,7 +6899,7 @@ class UserListComponent {
5460
6899
  (closed)="showBranchDialog.set(false)"
5461
6900
  (permissionsChanged)="loadUsers()" />
5462
6901
  }
5463
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i1$2.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i3$1.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "component", type: i4$1.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "component", type: i4.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: UserCompanyPermissionDialogComponent, selector: "lib-user-company-permission-dialog", inputs: ["visible", "user"], outputs: ["closed", "permissionsChanged"] }, { kind: "component", type: UserBranchPermissionDialogComponent, selector: "lib-user-branch-permission-dialog", inputs: ["visible", "user"], outputs: ["closed", "permissionsChanged"] }] });
6902
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "component", type: i3$1.Card, selector: "p-card", inputs: ["header", "subheader", "style", "styleClass"] }, { kind: "component", type: i3$2.Table, selector: "p-table", inputs: ["frozenColumns", "frozenValue", "styleClass", "tableStyle", "tableStyleClass", "paginator", "pageLinks", "rowsPerPageOptions", "alwaysShowPaginator", "paginatorPosition", "paginatorStyleClass", "paginatorDropdownAppendTo", "paginatorDropdownScrollHeight", "currentPageReportTemplate", "showCurrentPageReport", "showJumpToPageDropdown", "showJumpToPageInput", "showFirstLastIcon", "showPageLinks", "defaultSortOrder", "sortMode", "resetPageOnSort", "selectionMode", "selectionPageOnly", "contextMenuSelection", "contextMenuSelectionMode", "dataKey", "metaKeySelection", "rowSelectable", "rowTrackBy", "lazy", "lazyLoadOnInit", "compareSelectionBy", "csvSeparator", "exportFilename", "filters", "globalFilterFields", "filterDelay", "filterLocale", "expandedRowKeys", "editingRowKeys", "rowExpandMode", "scrollable", "rowGroupMode", "scrollHeight", "virtualScroll", "virtualScrollItemSize", "virtualScrollOptions", "virtualScrollDelay", "frozenWidth", "contextMenu", "resizableColumns", "columnResizeMode", "reorderableColumns", "loading", "loadingIcon", "showLoader", "rowHover", "customSort", "showInitialSortBadge", "exportFunction", "exportHeader", "stateKey", "stateStorage", "editMode", "groupRowsBy", "size", "showGridlines", "stripedRows", "groupRowsByOrder", "responsiveLayout", "breakpoint", "paginatorLocale", "value", "columns", "first", "rows", "totalRecords", "sortField", "sortOrder", "multiSortMeta", "selection", "selectAll"], outputs: ["contextMenuSelectionChange", "selectAllChange", "selectionChange", "onRowSelect", "onRowUnselect", "onPage", "onSort", "onFilter", "onLazyLoad", "onRowExpand", "onRowCollapse", "onContextMenuSelect", "onColResize", "onColReorder", "onRowReorder", "onEditInit", "onEditComplete", "onEditCancel", "onHeaderCheckboxToggle", "sortFunction", "firstChange", "rowsChange", "onStateSave", "onStateRestore"] }, { kind: "component", type: i4$2.Tag, selector: "p-tag", inputs: ["styleClass", "severity", "value", "icon", "rounded"] }, { kind: "directive", type: i5.Tooltip, selector: "[pTooltip]", inputs: ["tooltipPosition", "tooltipEvent", "positionStyle", "tooltipStyleClass", "tooltipZIndex", "escape", "showDelay", "hideDelay", "life", "positionTop", "positionLeft", "autoHide", "fitContent", "hideOnEscape", "showOnEllipsis", "pTooltip", "tooltipDisabled", "tooltipOptions", "appendTo", "ptTooltip", "pTooltipPT", "pTooltipUnstyled"] }, { kind: "ngmodule", type: TagModule }, { kind: "component", type: UserCompanyPermissionDialogComponent, selector: "lib-user-company-permission-dialog", inputs: ["visible", "user"], outputs: ["closed", "permissionsChanged"] }, { kind: "component", type: UserBranchPermissionDialogComponent, selector: "lib-user-branch-permission-dialog", inputs: ["visible", "user"], outputs: ["closed", "permissionsChanged"] }] });
5464
6903
  }
5465
6904
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: UserListComponent, decorators: [{
5466
6905
  type: Component,
@@ -5587,6 +7026,375 @@ var userList_component = /*#__PURE__*/Object.freeze({
5587
7026
  UserListComponent: UserListComponent
5588
7027
  });
5589
7028
 
7029
+ const VERIFY_EMAIL_KEY = makeStateKey('verify-email-result');
7030
+ class VerifyEmailPageComponent {
7031
+ platformId = inject(PLATFORM_ID);
7032
+ transferState = inject(TransferState);
7033
+ destroyRef = inject(DestroyRef);
7034
+ route = inject(ActivatedRoute);
7035
+ messageService = inject(MessageService);
7036
+ authApi = inject(AuthApiService);
7037
+ isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
7038
+ pageState = signal('verifying', ...(ngDevMode ? [{ debugName: "pageState" }] : []));
7039
+ resendSuccess = signal(false, ...(ngDevMode ? [{ debugName: "resendSuccess" }] : []));
7040
+ submittedEmail = signal('', ...(ngDevMode ? [{ debugName: "submittedEmail" }] : []));
7041
+ resendFormModel = signal({ email: '' }, ...(ngDevMode ? [{ debugName: "resendFormModel" }] : []));
7042
+ resendForm = form(this.resendFormModel, (f) => {
7043
+ required(f.email, { message: 'Email is required' });
7044
+ email(f.email, { message: 'Please enter a valid email address' });
7045
+ });
7046
+ isResendFormValid = computed(() => this.resendForm().valid(), ...(ngDevMode ? [{ debugName: "isResendFormValid" }] : []));
7047
+ ngOnInit() {
7048
+ this.route.queryParams.pipe(take(1)).subscribe((params) => {
7049
+ const token = params['token'] || '';
7050
+ if (!token) {
7051
+ this.pageState.set('resend');
7052
+ return;
7053
+ }
7054
+ // Check if we already have result from SSR (avoid duplicate API call)
7055
+ const cachedResult = this.transferState.get(VERIFY_EMAIL_KEY, null);
7056
+ if (cachedResult) {
7057
+ this.transferState.remove(VERIFY_EMAIL_KEY);
7058
+ this.handleVerificationResult(cachedResult === 'success');
7059
+ return;
7060
+ }
7061
+ this.verifyEmail(token);
7062
+ });
7063
+ }
7064
+ verifyEmail(token) {
7065
+ this.authApi
7066
+ .verifyEmail(token)
7067
+ .pipe(takeUntilDestroyed(this.destroyRef))
7068
+ .subscribe({
7069
+ next: () => {
7070
+ // Store result for browser hydration (SSR only)
7071
+ if (!isPlatformBrowser(this.platformId)) {
7072
+ this.transferState.set(VERIFY_EMAIL_KEY, 'success');
7073
+ }
7074
+ this.handleVerificationResult(true);
7075
+ },
7076
+ error: () => {
7077
+ if (!isPlatformBrowser(this.platformId)) {
7078
+ this.transferState.set(VERIFY_EMAIL_KEY, 'error');
7079
+ }
7080
+ this.handleVerificationResult(false);
7081
+ },
7082
+ });
7083
+ }
7084
+ handleVerificationResult(success) {
7085
+ if (success) {
7086
+ this.pageState.set('success');
7087
+ if (isPlatformBrowser(this.platformId)) {
7088
+ this.messageService.add({
7089
+ severity: 'success',
7090
+ summary: 'Success',
7091
+ detail: 'Your email has been verified successfully.',
7092
+ });
7093
+ }
7094
+ }
7095
+ else {
7096
+ this.pageState.set('invalid-token');
7097
+ }
7098
+ }
7099
+ showResendForm() {
7100
+ this.pageState.set('resend');
7101
+ this.resendSuccess.set(false);
7102
+ }
7103
+ onResend() {
7104
+ if (!this.isResendFormValid()) {
7105
+ this.messageService.add({
7106
+ severity: 'error',
7107
+ summary: 'Validation Error',
7108
+ detail: 'Please enter a valid email address.',
7109
+ });
7110
+ return;
7111
+ }
7112
+ this.isLoading.set(true);
7113
+ const email = this.resendFormModel().email;
7114
+ this.authApi
7115
+ .resendVerification(email)
7116
+ .pipe(takeUntilDestroyed(this.destroyRef))
7117
+ .subscribe({
7118
+ next: () => this.handleResendComplete(email),
7119
+ error: () => this.handleResendComplete(email),
7120
+ });
7121
+ }
7122
+ handleResendComplete(email) {
7123
+ this.isLoading.set(false);
7124
+ this.messageService.add({
7125
+ severity: 'success',
7126
+ summary: 'Email Sent',
7127
+ detail: 'If the email exists, a verification link has been sent.',
7128
+ });
7129
+ this.submittedEmail.set(email);
7130
+ this.resendSuccess.set(true);
7131
+ }
7132
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: VerifyEmailPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7133
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.1.3", type: VerifyEmailPageComponent, isStandalone: true, selector: "lib-verify-email-page", ngImport: i0, template: `
7134
+ <app-floating-configurator />
7135
+ <div class="bg-surface-ground flex items-center justify-center min-h-screen">
7136
+ <div class="flex flex-col items-center justify-center w-full max-w-[32rem] px-4">
7137
+ <div
7138
+ class="w-full"
7139
+ style="border-radius: var(--p-content-border-radius); padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)">
7140
+ <div
7141
+ class="w-full py-16 px-12"
7142
+ style="border-radius: var(--p-content-border-radius); background: var(--p-content-background);">
7143
+
7144
+ @switch (pageState()) {
7145
+ @case ('verifying') {
7146
+ <div class="text-center">
7147
+ <div class="flex justify-center mb-6">
7148
+ <i class="pi pi-spin pi-spinner text-8xl text-primary"></i>
7149
+ </div>
7150
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
7151
+ Verifying Email
7152
+ </div>
7153
+ <p class="text-muted-color font-medium">
7154
+ Please wait while we verify your email address...
7155
+ </p>
7156
+ </div>
7157
+ }
7158
+
7159
+ @case ('success') {
7160
+ <div class="text-center">
7161
+ <div class="flex justify-center mb-6">
7162
+ <i class="pi pi-check-circle text-8xl text-green-500"></i>
7163
+ </div>
7164
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
7165
+ Email Verified
7166
+ </div>
7167
+ <p class="text-muted-color font-medium mb-6">
7168
+ Your email address has been verified successfully.
7169
+ You can now access all features of your account.
7170
+ </p>
7171
+ <a routerLink="/auth/login">
7172
+ <p-button label="Sign In" styleClass="w-full" />
7173
+ </a>
7174
+ </div>
7175
+ }
7176
+
7177
+ @case ('invalid-token') {
7178
+ <div class="text-center">
7179
+ <div class="flex justify-center mb-6">
7180
+ <i class="pi pi-times-circle text-8xl text-red-500"></i>
7181
+ </div>
7182
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
7183
+ Verification Failed
7184
+ </div>
7185
+ <p class="text-muted-color font-medium mb-6">
7186
+ This verification link is invalid or has expired.
7187
+ </p>
7188
+ <p-button
7189
+ label="Resend Verification Email"
7190
+ styleClass="w-full mb-4"
7191
+ (onClick)="showResendForm()" />
7192
+ <div class="mt-4">
7193
+ <a routerLink="/auth/login" class="font-semibold text-primary cursor-pointer">
7194
+ Back to Sign In
7195
+ </a>
7196
+ </div>
7197
+ </div>
7198
+ }
7199
+
7200
+ @case ('resend') {
7201
+ <div class="text-center mb-8">
7202
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
7203
+ Resend Verification
7204
+ </div>
7205
+ <span class="text-muted-color font-medium">
7206
+ Enter your email to receive a new verification link.
7207
+ </span>
7208
+ </div>
7209
+
7210
+ @if (!resendSuccess()) {
7211
+ <form (ngSubmit)="onResend()">
7212
+ <label for="email" class="block text-lg font-medium mb-2" style="color: var(--p-content-color);">
7213
+ Email
7214
+ </label>
7215
+ <input
7216
+ pInputText
7217
+ id="email"
7218
+ type="email"
7219
+ placeholder="Enter your email address"
7220
+ class="w-full md:w-[25rem] mb-6"
7221
+ [formField]="resendForm.email"
7222
+ autocomplete="email" />
7223
+
7224
+ <p-button
7225
+ label="Resend Verification"
7226
+ styleClass="w-full mb-4"
7227
+ type="submit"
7228
+ [loading]="isLoading()"
7229
+ [disabled]="!isResendFormValid() || isLoading()" />
7230
+
7231
+ <div class="flex items-center justify-center gap-2">
7232
+ <a routerLink="/auth/login" class="font-semibold text-primary cursor-pointer">
7233
+ Back to Sign In
7234
+ </a>
7235
+ </div>
7236
+ </form>
7237
+ } @else {
7238
+ <div class="text-center">
7239
+ <div class="flex justify-center mb-6">
7240
+ <i class="pi pi-envelope text-8xl text-primary"></i>
7241
+ </div>
7242
+ <p class="text-muted-color font-medium mb-6">
7243
+ We've sent a new verification link to <strong>{{ submittedEmail() }}</strong>.
7244
+ Please check your inbox.
7245
+ </p>
7246
+ <a routerLink="/auth/login" class="font-semibold text-primary cursor-pointer">
7247
+ Back to Sign In
7248
+ </a>
7249
+ </div>
7250
+ }
7251
+ }
7252
+ }
7253
+ </div>
7254
+ </div>
7255
+ </div>
7256
+ </div>
7257
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1$1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1$1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1$1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i2.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: PrimeModule }, { kind: "component", type: i3.Button, selector: "p-button", inputs: ["hostName", "type", "badge", "disabled", "raised", "rounded", "text", "plain", "outlined", "link", "tabindex", "size", "variant", "style", "styleClass", "badgeClass", "badgeSeverity", "ariaLabel", "autofocus", "iconPos", "icon", "label", "loading", "loadingIcon", "severity", "buttonProps", "fluid"], outputs: ["onClick", "onFocus", "onBlur"] }, { kind: "directive", type: i4.InputText, selector: "[pInputText]", inputs: ["hostName", "ptInputText", "pInputTextPT", "pInputTextUnstyled", "pSize", "variant", "fluid", "invalid"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"] }, { kind: "component", type: AppFloatingConfigurator, selector: "app-floating-configurator" }] });
7258
+ }
7259
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.3", ngImport: i0, type: VerifyEmailPageComponent, decorators: [{
7260
+ type: Component,
7261
+ args: [{
7262
+ selector: 'lib-verify-email-page',
7263
+ standalone: true,
7264
+ imports: [AngularModule, PrimeModule, FormField, AppFloatingConfigurator],
7265
+ template: `
7266
+ <app-floating-configurator />
7267
+ <div class="bg-surface-ground flex items-center justify-center min-h-screen">
7268
+ <div class="flex flex-col items-center justify-center w-full max-w-[32rem] px-4">
7269
+ <div
7270
+ class="w-full"
7271
+ style="border-radius: var(--p-content-border-radius); padding: 0.3rem; background: linear-gradient(180deg, var(--primary-color) 10%, rgba(33, 150, 243, 0) 30%)">
7272
+ <div
7273
+ class="w-full py-16 px-12"
7274
+ style="border-radius: var(--p-content-border-radius); background: var(--p-content-background);">
7275
+
7276
+ @switch (pageState()) {
7277
+ @case ('verifying') {
7278
+ <div class="text-center">
7279
+ <div class="flex justify-center mb-6">
7280
+ <i class="pi pi-spin pi-spinner text-8xl text-primary"></i>
7281
+ </div>
7282
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
7283
+ Verifying Email
7284
+ </div>
7285
+ <p class="text-muted-color font-medium">
7286
+ Please wait while we verify your email address...
7287
+ </p>
7288
+ </div>
7289
+ }
7290
+
7291
+ @case ('success') {
7292
+ <div class="text-center">
7293
+ <div class="flex justify-center mb-6">
7294
+ <i class="pi pi-check-circle text-8xl text-green-500"></i>
7295
+ </div>
7296
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
7297
+ Email Verified
7298
+ </div>
7299
+ <p class="text-muted-color font-medium mb-6">
7300
+ Your email address has been verified successfully.
7301
+ You can now access all features of your account.
7302
+ </p>
7303
+ <a routerLink="/auth/login">
7304
+ <p-button label="Sign In" styleClass="w-full" />
7305
+ </a>
7306
+ </div>
7307
+ }
7308
+
7309
+ @case ('invalid-token') {
7310
+ <div class="text-center">
7311
+ <div class="flex justify-center mb-6">
7312
+ <i class="pi pi-times-circle text-8xl text-red-500"></i>
7313
+ </div>
7314
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
7315
+ Verification Failed
7316
+ </div>
7317
+ <p class="text-muted-color font-medium mb-6">
7318
+ This verification link is invalid or has expired.
7319
+ </p>
7320
+ <p-button
7321
+ label="Resend Verification Email"
7322
+ styleClass="w-full mb-4"
7323
+ (onClick)="showResendForm()" />
7324
+ <div class="mt-4">
7325
+ <a routerLink="/auth/login" class="font-semibold text-primary cursor-pointer">
7326
+ Back to Sign In
7327
+ </a>
7328
+ </div>
7329
+ </div>
7330
+ }
7331
+
7332
+ @case ('resend') {
7333
+ <div class="text-center mb-8">
7334
+ <div class="text-3xl font-medium mb-4" style="color: var(--p-content-color);">
7335
+ Resend Verification
7336
+ </div>
7337
+ <span class="text-muted-color font-medium">
7338
+ Enter your email to receive a new verification link.
7339
+ </span>
7340
+ </div>
7341
+
7342
+ @if (!resendSuccess()) {
7343
+ <form (ngSubmit)="onResend()">
7344
+ <label for="email" class="block text-lg font-medium mb-2" style="color: var(--p-content-color);">
7345
+ Email
7346
+ </label>
7347
+ <input
7348
+ pInputText
7349
+ id="email"
7350
+ type="email"
7351
+ placeholder="Enter your email address"
7352
+ class="w-full md:w-[25rem] mb-6"
7353
+ [formField]="resendForm.email"
7354
+ autocomplete="email" />
7355
+
7356
+ <p-button
7357
+ label="Resend Verification"
7358
+ styleClass="w-full mb-4"
7359
+ type="submit"
7360
+ [loading]="isLoading()"
7361
+ [disabled]="!isResendFormValid() || isLoading()" />
7362
+
7363
+ <div class="flex items-center justify-center gap-2">
7364
+ <a routerLink="/auth/login" class="font-semibold text-primary cursor-pointer">
7365
+ Back to Sign In
7366
+ </a>
7367
+ </div>
7368
+ </form>
7369
+ } @else {
7370
+ <div class="text-center">
7371
+ <div class="flex justify-center mb-6">
7372
+ <i class="pi pi-envelope text-8xl text-primary"></i>
7373
+ </div>
7374
+ <p class="text-muted-color font-medium mb-6">
7375
+ We've sent a new verification link to <strong>{{ submittedEmail() }}</strong>.
7376
+ Please check your inbox.
7377
+ </p>
7378
+ <a routerLink="/auth/login" class="font-semibold text-primary cursor-pointer">
7379
+ Back to Sign In
7380
+ </a>
7381
+ </div>
7382
+ }
7383
+ }
7384
+ }
7385
+ </div>
7386
+ </div>
7387
+ </div>
7388
+ </div>
7389
+ `,
7390
+ }]
7391
+ }] });
7392
+
7393
+ var verifyEmailPage_component = /*#__PURE__*/Object.freeze({
7394
+ __proto__: null,
7395
+ VerifyEmailPageComponent: VerifyEmailPageComponent
7396
+ });
7397
+
5590
7398
  // =============================================================================
5591
7399
  // @flusys/ng-auth - Authentication Package
5592
7400
  // =============================================================================
@@ -5596,5 +7404,5 @@ var userList_component = /*#__PURE__*/Object.freeze({
5596
7404
  * Generated bundle index. Do not edit.
5597
7405
  */
5598
7406
 
5599
- export { ADMINISTRATION_ROUTES, AUTH_ENDPOINTS, AUTH_ROUTES, AdministrationPageComponent, AuthApiService, AuthCompanyApiProviderAdapter, AuthInitService, AuthLayoutApiAdapter, AuthLayoutStateAdapter, AuthStateService, AuthUserPermissionProviderAdapter, AuthUserProviderAdapter, BranchApiService, BranchFormComponent, BranchListComponent, CompanyApiService, CompanyFormComponent, CompanyListComponent, LoginPageComponent, RegisterPageComponent, TokenRefreshStateService, UserApiService, UserBranchPermissionDialogComponent, UserCompanyPermissionDialogComponent, UserFormComponent, UserListComponent, UserPermissionApiService, authGuard, authInterceptor, companyFeatureGuard, guestGuard, isAuthEndpoint, isCompanySelectionResponse, isEndpoint, isLoginResponse, provideAuthLayoutIntegration, provideAuthProviders, tokenRefreshInterceptor };
7407
+ export { ADMINISTRATION_ROUTES, AUTH_ENDPOINTS, AUTH_ROUTES, AdministrationPageComponent, AuthApiService, AuthCompanyApiProviderAdapter, AuthInitService, AuthLayoutApiAdapter, AuthLayoutStateAdapter, AuthStateService, AuthUserPermissionProviderAdapter, AuthUserProviderAdapter, BranchApiService, BranchFormComponent, BranchListComponent, CompanyApiService, CompanyFormComponent, CompanyListComponent, ForgotPasswordPageComponent, LoginPageComponent, PROFILE_ROUTES, ProfilePageComponent, RegisterPageComponent, ResetPasswordPageComponent, TokenRefreshStateService, UserApiService, UserBranchPermissionDialogComponent, UserCompanyPermissionDialogComponent, UserFormComponent, UserListComponent, UserPermissionApiService, VerifyEmailPageComponent, authGuard, authInterceptor, companyFeatureGuard, guestGuard, isAuthEndpoint, isCompanySelectionResponse, isEmailVerificationRequired, isEndpoint, isLoginResponse, provideAuthLayoutIntegration, provideAuthProviders, tokenRefreshInterceptor };
5600
7408
  //# sourceMappingURL=flusys-ng-auth.mjs.map