@flusys/ng-auth 7.0.1 → 7.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,11 @@
1
1
  import { HttpClient } from '@angular/common/http';
2
2
  import * as i0 from '@angular/core';
3
- import { inject, Injectable, signal, computed, effect, PLATFORM_ID, InjectionToken, Component, untracked, viewChild, DestroyRef, input, Injector, output, resource, makeStateKey, TransferState } from '@angular/core';
3
+ import { inject, Injectable, signal, computed, effect, PLATFORM_ID, InjectionToken, Component, untracked, viewChild, DestroyRef, DOCUMENT, input, Injector, output, resource, makeStateKey, TransferState } from '@angular/core';
4
4
  import { APP_CONFIG, getServiceUrl, isCompanyFeatureEnabled, isServiceEnabled, isSignUpEnabled, getPermissionMode, getDefaultCompanySlug, getDefaultBranchSlug } from '@flusys/ng-core';
5
5
  import { of, timeout, switchMap, map, tap, catchError, shareReplay, finalize, BehaviorSubject, throwError, filter, take, from, forkJoin, firstValueFrom } from 'rxjs';
6
6
  import * as i2 from '@angular/router';
7
7
  import { Router, RouterOutlet, ActivatedRoute } from '@angular/router';
8
- import { PlatformService, TranslateService, PermissionValidatorService, ApiResourceService, CookieService, USER_PERMISSIONS, COMPANY_PERMISSIONS, BRANCH_PERMISSIONS, resolveTranslationModule, SHARED_MESSAGES, permissionGuard, USER_PROVIDER, COMPANY_API_PROVIDER, USER_PERMISSION_PROVIDER, AUTH_STATE_PROVIDER, RouterTabsComponent, TranslatePipe, AngularModule, HasPermissionDirective, BaseFormComponent, BaseListComponent, BaseListTemplateDirective, FileUrlService, FILE_PROVIDER, PROFILE_PERMISSION_PROVIDER, FileUploaderComponent } from '@flusys/ng-shared';
8
+ import { PlatformService, TranslateService, PermissionValidatorService, ApiResourceService, CookieService, USER_PERMISSIONS, COMPANY_PERMISSIONS, BRANCH_PERMISSIONS, SOCIAL_AUTH_CONFIG_PERMISSIONS, resolveTranslationModule, SHARED_MESSAGES, permissionGuard, USER_PROVIDER, COMPANY_API_PROVIDER, USER_PERMISSION_PROVIDER, AUTH_STATE_PROVIDER, RouterTabsComponent, TranslatePipe, AngularModule, HasPermissionDirective, BaseFormComponent, BaseListComponent, BaseListTemplateDirective, FileUrlService, FILE_PROVIDER, PROFILE_PERMISSION_PROVIDER, FileUploaderComponent } from '@flusys/ng-shared';
9
9
  import { map as map$1, catchError as catchError$1, tap as tap$1 } from 'rxjs/operators';
10
10
  import * as i2$1 from '@angular/common';
11
11
  import { isPlatformBrowser } from '@angular/common';
@@ -16,6 +16,20 @@ import { toSignal, takeUntilDestroyed } from '@angular/core/rxjs-interop';
16
16
  import { form, required, FormField, email, minLength } from '@angular/forms/signals';
17
17
  import * as i1 from '@angular/forms';
18
18
 
19
+ var SocialProviderEnum;
20
+ (function (SocialProviderEnum) {
21
+ SocialProviderEnum["GOOGLE"] = "google";
22
+ SocialProviderEnum["FACEBOOK"] = "facebook";
23
+ SocialProviderEnum["LINKEDIN"] = "linkedin";
24
+ SocialProviderEnum["MICROSOFT"] = "microsoft";
25
+ })(SocialProviderEnum || (SocialProviderEnum = {}));
26
+ const SOCIAL_PROVIDERS = [
27
+ SocialProviderEnum.GOOGLE,
28
+ SocialProviderEnum.FACEBOOK,
29
+ SocialProviderEnum.LINKEDIN,
30
+ SocialProviderEnum.MICROSOFT,
31
+ ];
32
+
19
33
  // ─── Type guards ───────────────────────────────────────────────────────────────
20
34
  function isEmailVerificationRequired(response) {
21
35
  return !!response.data && 'requiresEmailVerification' in response.data && response.data.requiresEmailVerification === true;
@@ -326,6 +340,10 @@ class AuthInitService {
326
340
  this._initialized.set(true);
327
341
  return of(true);
328
342
  }
343
+ if (this.authState.requiresCompanySelection()) {
344
+ this._initialized.set(true);
345
+ return of(false);
346
+ }
329
347
  if (this._initialized()) {
330
348
  return of(this.authState.isAuthenticated());
331
349
  }
@@ -419,6 +437,90 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
419
437
  args: [{ providedIn: 'root' }]
420
438
  }], ctorParameters: () => [] });
421
439
 
440
+ /**
441
+ * Authentication API Endpoints
442
+ *
443
+ * Centralized endpoint definitions to prevent inconsistencies across interceptors
444
+ * and services. All auth-related endpoints should be referenced from here.
445
+ */
446
+ const AUTH_ENDPOINTS = {
447
+ LOGIN: '/auth/login',
448
+ LOGOUT: '/auth/logout',
449
+ REFRESH: '/auth/refresh',
450
+ REGISTER: '/auth/register',
451
+ ME: '/auth/me',
452
+ SELECT: '/auth/select',
453
+ SWITCH_COMPANY: '/auth/switch-company',
454
+ CHANGE_PASSWORD: '/auth/change-password',
455
+ VERIFY_EMAIL: '/auth/verify-email',
456
+ FORGOT_PASSWORD: '/auth/forgot-password',
457
+ RESET_PASSWORD: '/auth/reset-password',
458
+ RESEND_VERIFICATION: '/auth/resend-verification',
459
+ SOCIAL_CALLBACK: '/auth/social/callback',
460
+ };
461
+ /** Check if a URL matches a specific endpoint */
462
+ function isEndpoint(url, endpoint) {
463
+ return url.includes(endpoint);
464
+ }
465
+
466
+ /** Round-trips the pre-login returnUrl through the OAuth redirect, which cannot carry it itself. */
467
+ const SOCIAL_RETURN_URL_KEY = 'flusys_social_return_url';
468
+ class SocialAuthApiService {
469
+ appConfig = inject(APP_CONFIG);
470
+ http = inject(HttpClient);
471
+ platform = inject(PlatformService);
472
+ baseUrl = getServiceUrl(this.appConfig, 'auth');
473
+ get redirectUri() {
474
+ if (this.platform.isServer)
475
+ return '';
476
+ return `${window.location.origin}${AUTH_ENDPOINTS.SOCIAL_CALLBACK}`;
477
+ }
478
+ /** Called before redirecting to the provider, so the callback page can resume where the user left off. */
479
+ saveReturnUrl(returnUrl) {
480
+ if (this.platform.isServer)
481
+ return;
482
+ if (returnUrl)
483
+ sessionStorage.setItem(SOCIAL_RETURN_URL_KEY, returnUrl);
484
+ else
485
+ sessionStorage.removeItem(SOCIAL_RETURN_URL_KEY);
486
+ }
487
+ /** Reads and clears the returnUrl saved by {@link saveReturnUrl}. */
488
+ consumeReturnUrl() {
489
+ if (this.platform.isServer)
490
+ return '';
491
+ const returnUrl = sessionStorage.getItem(SOCIAL_RETURN_URL_KEY) ?? '';
492
+ sessionStorage.removeItem(SOCIAL_RETURN_URL_KEY);
493
+ return returnUrl;
494
+ }
495
+ getProviders() {
496
+ return this.http.post(`${this.baseUrl}/social/providers`, { redirectUri: this.redirectUri });
497
+ }
498
+ authorize(provider) {
499
+ return this.http.post(`${this.baseUrl}/social/authorize`, { provider, redirectUri: this.redirectUri });
500
+ }
501
+ callback(request) {
502
+ return this.http.post(`${this.baseUrl}/social/callback`, request);
503
+ }
504
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SocialAuthApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
505
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SocialAuthApiService, providedIn: 'root' });
506
+ }
507
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SocialAuthApiService, decorators: [{
508
+ type: Injectable,
509
+ args: [{ providedIn: 'root' }]
510
+ }] });
511
+
512
+ class SocialAuthConfigApiService extends ApiResourceService {
513
+ constructor() {
514
+ super('social-config', 'auth');
515
+ }
516
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SocialAuthConfigApiService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
517
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SocialAuthConfigApiService, providedIn: 'root' });
518
+ }
519
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SocialAuthConfigApiService, decorators: [{
520
+ type: Injectable,
521
+ args: [{ providedIn: 'root' }]
522
+ }], ctorParameters: () => [] });
523
+
422
524
  /**
423
525
  * Token Refresh State Service
424
526
  * Manages token refresh state for SSR-safe request queuing.
@@ -518,31 +620,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
518
620
  args: [{ providedIn: 'root' }]
519
621
  }] });
520
622
 
521
- /**
522
- * Authentication API Endpoints
523
- *
524
- * Centralized endpoint definitions to prevent inconsistencies across interceptors
525
- * and services. All auth-related endpoints should be referenced from here.
526
- */
527
- const AUTH_ENDPOINTS = {
528
- LOGIN: '/auth/login',
529
- LOGOUT: '/auth/logout',
530
- REFRESH: '/auth/refresh',
531
- REGISTER: '/auth/register',
532
- ME: '/auth/me',
533
- SELECT: '/auth/select',
534
- SWITCH_COMPANY: '/auth/switch-company',
535
- CHANGE_PASSWORD: '/auth/change-password',
536
- VERIFY_EMAIL: '/auth/verify-email',
537
- FORGOT_PASSWORD: '/auth/forgot-password',
538
- RESET_PASSWORD: '/auth/reset-password',
539
- RESEND_VERIFICATION: '/auth/resend-verification',
540
- };
541
- /** Check if a URL matches a specific endpoint */
542
- function isEndpoint(url, endpoint) {
543
- return url.includes(endpoint);
544
- }
545
-
546
623
  function isValidReturnUrl(url) {
547
624
  if (typeof url !== 'string' || !url)
548
625
  return false;
@@ -618,6 +695,7 @@ const COOKIE_ENDPOINTS = [
618
695
  AUTH_ENDPOINTS.SELECT,
619
696
  AUTH_ENDPOINTS.SWITCH_COMPANY,
620
697
  AUTH_ENDPOINTS.VERIFY_EMAIL,
698
+ AUTH_ENDPOINTS.SOCIAL_CALLBACK,
621
699
  ];
622
700
  /**
623
701
  * Auth Interceptor - Adds auth headers and cookie credentials to requests
@@ -666,11 +744,13 @@ function tokenRefreshInterceptor(req, next) {
666
744
  const isLoginRequest = isEndpoint(req.url, AUTH_ENDPOINTS.LOGIN);
667
745
  const isSelectRequest = isEndpoint(req.url, AUTH_ENDPOINTS.SELECT);
668
746
  const isRegisterRequest = isEndpoint(req.url, AUTH_ENDPOINTS.REGISTER);
747
+ const isSocialCallbackRequest = isEndpoint(req.url, AUTH_ENDPOINTS.SOCIAL_CALLBACK);
669
748
  // Don't intercept auth-related endpoints
670
749
  if (isRefreshRequest ||
671
750
  isLoginRequest ||
672
751
  isSelectRequest ||
673
- isRegisterRequest) {
752
+ isRegisterRequest ||
753
+ isSocialCallbackRequest) {
674
754
  return next(req);
675
755
  }
676
756
  // PROACTIVE REFRESH: Check if token is expired or expiring soon
@@ -1005,6 +1085,47 @@ const AUTH_MESSAGES = {
1005
1085
  'administrative.user.no.users': 'No users found',
1006
1086
  'administrative.user.details': 'User Details',
1007
1087
  'administrative.user.delete.title': 'Delete User',
1088
+ // Social sign-in API keys
1089
+ 'auth.social.providers.success': 'Social providers retrieved successfully',
1090
+ 'auth.social.authorize.success': 'Authorization URL generated successfully',
1091
+ 'auth.social.login.success': 'Signed in successfully',
1092
+ 'auth.social.provider.disabled': 'Social sign-in with {{provider}} is not enabled',
1093
+ 'auth.social.provider.not.configured': 'Social sign-in with {{provider}} is missing its client credentials',
1094
+ 'auth.social.state.invalid': 'Social sign-in session is invalid or has expired',
1095
+ 'auth.social.redirect.uri.mismatch': 'Redirect URI does not match the one the sign-in started with',
1096
+ 'auth.social.exchange.failed': 'Social sign-in with {{provider}} failed',
1097
+ 'auth.social.profile.incomplete': '{{provider}} did not return an account identifier',
1098
+ 'auth.social.account.not.linked': 'No account is linked to this {{provider}} profile',
1099
+ 'auth.social.company.required': 'No account is linked to this {{provider}} profile. Please sign up manually and join or create a company first, then social sign-in will work.',
1100
+ 'auth.social.email.required': '{{provider}} did not share an email address',
1101
+ 'auth.social.email.not.verified': '{{provider}} has not verified this email address',
1102
+ // components/social-login-buttons.component.ts
1103
+ 'auth.social.continue.with': 'or continue with',
1104
+ 'auth.social.provider.google': 'Google',
1105
+ 'auth.social.provider.facebook': 'Facebook',
1106
+ 'auth.social.provider.linkedin': 'LinkedIn',
1107
+ 'auth.social.provider.microsoft': 'Microsoft',
1108
+ // pages/social-callback/social-callback-page.component.ts
1109
+ 'auth.social.signing.in': 'Signing you in...',
1110
+ 'auth.social.failed': 'Social sign-in failed',
1111
+ // pages/social-config/social-config-page.component.ts (API keys)
1112
+ 'social.config.provider.already.configured': 'Social provider "{{provider}}" is already configured',
1113
+ 'social.config.credentials.required': 'Client ID and client secret are required for {{provider}}',
1114
+ 'social.config.title': 'Social Config',
1115
+ 'social.config.subtitle': 'Enable the providers users may sign in with, and hold their credentials',
1116
+ 'social.config.redirect.uri': 'Redirect URI',
1117
+ 'social.config.client.id': 'Client ID',
1118
+ 'social.config.client.secret': 'Client Secret',
1119
+ 'social.config.client.secret.unchanged': 'Unchanged - leave blank to keep the saved secret',
1120
+ 'social.config.tenant.id': 'Directory (tenant) ID',
1121
+ 'social.config.tenant.id.example': 'common',
1122
+ 'social.config.scopes': 'Scopes',
1123
+ 'social.config.scopes.placeholder': 'Leave blank to use the provider defaults',
1124
+ 'social.config.save.success': 'Social sign-in settings saved',
1125
+ 'social.config.provider.hint.google': 'Google Cloud console, OAuth 2.0 client ID',
1126
+ 'social.config.provider.hint.facebook': 'Meta for Developers, Facebook Login app',
1127
+ 'social.config.provider.hint.linkedin': 'LinkedIn developers, Sign In with LinkedIn',
1128
+ 'social.config.provider.hint.microsoft': 'Microsoft Entra ID, app registration',
1008
1129
  };
1009
1130
 
1010
1131
  /**
@@ -1027,6 +1148,9 @@ function resolveAdministrationDefaultTab() {
1027
1148
  permissionValidator.hasPermission(BRANCH_PERMISSIONS.READ)) {
1028
1149
  return 'branch';
1029
1150
  }
1151
+ if (permissionValidator.hasPermission(SOCIAL_AUTH_CONFIG_PERMISSIONS.READ)) {
1152
+ return 'social-config';
1153
+ }
1030
1154
  return 'users';
1031
1155
  }
1032
1156
  /**
@@ -1065,6 +1189,10 @@ const AUTH_ROUTES = [
1065
1189
  loadComponent: () => Promise.resolve().then(function () { return resetPasswordPage_component; }).then((m) => m.ResetPasswordPageComponent),
1066
1190
  canActivate: [emailFeatureGuard, guestGuard],
1067
1191
  },
1192
+ {
1193
+ path: 'social/callback',
1194
+ loadComponent: () => Promise.resolve().then(function () { return socialCallbackPage_component; }).then((m) => m.SocialCallbackPageComponent),
1195
+ },
1068
1196
  {
1069
1197
  path: 'verify-email',
1070
1198
  loadComponent: () => Promise.resolve().then(function () { return verifyEmailPage_component; }).then((m) => m.VerifyEmailPageComponent),
@@ -1158,6 +1286,12 @@ const ADMINISTRATION_ROUTES = [
1158
1286
  },
1159
1287
  ],
1160
1288
  },
1289
+ // Social sign-in provider credentials tab
1290
+ {
1291
+ path: 'social-config',
1292
+ canActivate: [permissionGuard(SOCIAL_AUTH_CONFIG_PERMISSIONS.READ)],
1293
+ loadComponent: () => Promise.resolve().then(function () { return socialConfigPage_component; }).then((m) => m.SocialConfigPageComponent),
1294
+ },
1161
1295
  // Default redirect to the first tab the user has permission to see
1162
1296
  {
1163
1297
  path: '',
@@ -1513,6 +1647,7 @@ function validateEmail(email, config) {
1513
1647
  * - Users (always visible) - includes permission management via dialogs
1514
1648
  * - Companies (only if company feature enabled)
1515
1649
  * - Branches (only if company feature enabled)
1650
+ * - Social Config (social sign-in provider credentials)
1516
1651
  */
1517
1652
  class AdministrationPageComponent {
1518
1653
  appConfig = inject(APP_CONFIG);
@@ -1543,6 +1678,13 @@ class AdministrationPageComponent {
1543
1678
  permission: BRANCH_PERMISSIONS.READ,
1544
1679
  requiresCompanyFeature: true,
1545
1680
  },
1681
+ {
1682
+ id: 'social-config',
1683
+ labelKey: 'social.config.title',
1684
+ icon: 'key',
1685
+ route: 'social-config',
1686
+ permission: SOCIAL_AUTH_CONFIG_PERMISSIONS.READ,
1687
+ },
1546
1688
  ];
1547
1689
  /** Check if company feature is enabled */
1548
1690
  companyFeatureEnabled = computed(() => isCompanyFeatureEnabled(this.appConfig), /* @ts-ignore */
@@ -1922,6 +2064,7 @@ class BranchFormComponent {
1922
2064
  <div class="flex justify-end gap-4 md:col-span-2 pt-4">
1923
2065
  <button
1924
2066
  fButton
2067
+ type="button"
1925
2068
  severity="secondary"
1926
2069
  [outlined]="true"
1927
2070
  (click)="navigateToList()"
@@ -2064,6 +2207,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
2064
2207
  <div class="flex justify-end gap-4 md:col-span-2 pt-4">
2065
2208
  <button
2066
2209
  fButton
2210
+ type="button"
2067
2211
  severity="secondary"
2068
2212
  [outlined]="true"
2069
2213
  (click)="navigateToList()"
@@ -3004,6 +3148,240 @@ var forgotPasswordPage_component = /*#__PURE__*/Object.freeze({
3004
3148
  ForgotPasswordPageComponent: ForgotPasswordPageComponent
3005
3149
  });
3006
3150
 
3151
+ class SocialLoginButtonsComponent {
3152
+ destroyRef = inject(DestroyRef);
3153
+ socialApi = inject(SocialAuthApiService);
3154
+ messageService = inject(MessageService);
3155
+ platform = inject(PlatformService);
3156
+ document = inject(DOCUMENT);
3157
+ translateAdapter = inject(TranslateService, {
3158
+ optional: true,
3159
+ });
3160
+ returnUrl = input('', /* @ts-ignore */
3161
+ ...(ngDevMode ? [{ debugName: "returnUrl" }] : /* istanbul ignore next */ []));
3162
+ providerEnum = SocialProviderEnum;
3163
+ _providers = signal([], /* @ts-ignore */
3164
+ ...(ngDevMode ? [{ debugName: "_providers" }] : /* istanbul ignore next */ []));
3165
+ providers = this._providers.asReadonly();
3166
+ _pendingProvider = signal(null, /* @ts-ignore */
3167
+ ...(ngDevMode ? [{ debugName: "_pendingProvider" }] : /* istanbul ignore next */ []));
3168
+ pendingProvider = this._pendingProvider.asReadonly();
3169
+ showLabels = computed(() => this.providers().length <= 2, /* @ts-ignore */
3170
+ ...(ngDevMode ? [{ debugName: "showLabels" }] : /* istanbul ignore next */ []));
3171
+ layoutClass = computed(() => {
3172
+ switch (this.providers().length) {
3173
+ case 1:
3174
+ return 'grid grid-cols-1';
3175
+ case 2:
3176
+ return 'grid grid-cols-2 gap-3';
3177
+ default:
3178
+ return 'flex flex-wrap justify-center gap-3';
3179
+ }
3180
+ }, /* @ts-ignore */
3181
+ ...(ngDevMode ? [{ debugName: "layoutClass" }] : /* istanbul ignore next */ []));
3182
+ constructor() {
3183
+ if (this.platform.isServer)
3184
+ return;
3185
+ this.socialApi
3186
+ .getProviders()
3187
+ .pipe(takeUntilDestroyed(this.destroyRef))
3188
+ .subscribe({
3189
+ next: (response) => this._providers.set(response.data ?? []),
3190
+ error: () => this._providers.set([]),
3191
+ });
3192
+ }
3193
+ onSelect(provider) {
3194
+ this._pendingProvider.set(provider);
3195
+ this.socialApi.saveReturnUrl(this.returnUrl());
3196
+ this.socialApi
3197
+ .authorize(provider)
3198
+ .pipe(takeUntilDestroyed(this.destroyRef))
3199
+ .subscribe({
3200
+ next: (response) => {
3201
+ const url = response.data?.authorizationUrl;
3202
+ if (!url) {
3203
+ this._pendingProvider.set(null);
3204
+ return;
3205
+ }
3206
+ this.redirectTo(url);
3207
+ },
3208
+ error: (error) => {
3209
+ this._pendingProvider.set(null);
3210
+ const messageKey = error?.error?.messageKey;
3211
+ this.messageService.add({
3212
+ severity: 'error',
3213
+ summary: this.translate('auth.login.title'),
3214
+ detail: messageKey
3215
+ ? this.translate(messageKey, { provider })
3216
+ : error?.error?.message,
3217
+ });
3218
+ },
3219
+ });
3220
+ }
3221
+ redirectTo(url) {
3222
+ this.document.location.assign(url);
3223
+ }
3224
+ translate(key, vars) {
3225
+ return this.translateAdapter?.translate(key, vars) ?? key;
3226
+ }
3227
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SocialLoginButtonsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3228
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: SocialLoginButtonsComponent, isStandalone: true, selector: "lib-social-login-buttons", inputs: { returnUrl: { classPropertyName: "returnUrl", publicName: "returnUrl", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: `
3229
+ @if (providers().length) {
3230
+ <div class="mt-6">
3231
+ <div class="flex items-center gap-3 mb-4">
3232
+ <span class="flex-1 h-px bg-surface-200 dark:bg-surface-700"></span>
3233
+ <span class="text-muted-color text-sm">{{
3234
+ 'auth.social.continue.with' | translate
3235
+ }}</span>
3236
+ <span class="flex-1 h-px bg-surface-200 dark:bg-surface-700"></span>
3237
+ </div>
3238
+
3239
+ <div [class]="layoutClass()">
3240
+ @for (info of providers(); track info.provider) {
3241
+ <button
3242
+ fButton
3243
+ type="button"
3244
+ severity="secondary"
3245
+ [outlined]="true"
3246
+ [class]="showLabels() ? 'w-full' : 'shrink-0'"
3247
+ [attr.aria-label]="
3248
+ showLabels()
3249
+ ? null
3250
+ : ('auth.social.provider.' + info.provider | translate)
3251
+ "
3252
+ [attr.title]="
3253
+ showLabels()
3254
+ ? null
3255
+ : ('auth.social.provider.' + info.provider | translate)
3256
+ "
3257
+ [disabled]="pendingProvider() !== null && pendingProvider() !== info.provider"
3258
+ [loading]="pendingProvider() === info.provider"
3259
+ (click)="onSelect(info.provider)"
3260
+ >
3261
+ @if (pendingProvider() !== info.provider) {
3262
+ @switch (info.provider) {
3263
+ @case (providerEnum.GOOGLE) {
3264
+ <svg class="shrink-0" width="18" height="18" viewBox="0 0 48 48" aria-hidden="true">
3265
+ <path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z" />
3266
+ <path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 3-2.26 5.54-4.78 7.25l7.73 6c4.51-4.18 7.09-10.36 7.09-17.72z" />
3267
+ <path fill="#FBBC05" d="M10.53 28.59A14.5 14.5 0 0 1 9.77 24c0-1.6.29-3.14.76-4.59l-7.98-6.19A23.94 23.94 0 0 0 0 24c0 3.88.93 7.54 2.56 10.78l7.97-6.19z" />
3268
+ <path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z" />
3269
+ </svg>
3270
+ }
3271
+ @case (providerEnum.FACEBOOK) {
3272
+ <svg class="shrink-0" width="18" height="18" viewBox="0 0 24 24" aria-hidden="true">
3273
+ <path fill="#1877F2" d="M24 12.07C24 5.4 18.63 0 12 0S0 5.4 0 12.07C0 18.1 4.39 23.1 10.13 24v-8.44H7.08v-3.49h3.05V9.41c0-3.02 1.79-4.69 4.53-4.69 1.31 0 2.69.24 2.69.24v2.96h-1.51c-1.49 0-1.96.93-1.96 1.88v2.25h3.33l-.53 3.49h-2.8V24C19.61 23.1 24 18.1 24 12.07z" />
3274
+ </svg>
3275
+ }
3276
+ @case (providerEnum.LINKEDIN) {
3277
+ <svg class="shrink-0" width="18" height="18" viewBox="0 0 24 24" aria-hidden="true">
3278
+ <path fill="#0A66C2" d="M20.45 20.45h-3.56v-5.57c0-1.33-.03-3.04-1.85-3.04-1.86 0-2.14 1.45-2.14 2.94v5.67H9.35V9h3.41v1.56h.05a3.74 3.74 0 0 1 3.37-1.85c3.6 0 4.27 2.37 4.27 5.46v6.28zM5.34 7.43a2.06 2.06 0 1 1 0-4.13 2.06 2.06 0 0 1 0 4.13zM7.12 20.45H3.55V9h3.57v11.45zM22.22 0H1.77C.79 0 0 .78 0 1.73v20.54C0 23.22.79 24 1.77 24h20.45c.98 0 1.78-.78 1.78-1.73V1.73C24 .78 23.2 0 22.22 0z" />
3279
+ </svg>
3280
+ }
3281
+ @case (providerEnum.MICROSOFT) {
3282
+ <svg class="shrink-0" width="18" height="18" viewBox="0 0 23 23" aria-hidden="true">
3283
+ <path fill="#F25022" d="M0 0h11v11H0z" />
3284
+ <path fill="#7FBA00" d="M12 0h11v11H12z" />
3285
+ <path fill="#00A4EF" d="M0 12h11v11H0z" />
3286
+ <path fill="#FFB900" d="M12 12h11v11H12z" />
3287
+ </svg>
3288
+ }
3289
+ }
3290
+ }
3291
+ @if (showLabels()) {
3292
+ <span>{{
3293
+ 'auth.social.provider.' + info.provider | translate
3294
+ }}</span>
3295
+ }
3296
+ </button>
3297
+ }
3298
+ </div>
3299
+ </div>
3300
+ }
3301
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1$1.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
3302
+ }
3303
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SocialLoginButtonsComponent, decorators: [{
3304
+ type: Component,
3305
+ args: [{
3306
+ selector: 'lib-social-login-buttons',
3307
+ imports: [UiModule, TranslatePipe],
3308
+ template: `
3309
+ @if (providers().length) {
3310
+ <div class="mt-6">
3311
+ <div class="flex items-center gap-3 mb-4">
3312
+ <span class="flex-1 h-px bg-surface-200 dark:bg-surface-700"></span>
3313
+ <span class="text-muted-color text-sm">{{
3314
+ 'auth.social.continue.with' | translate
3315
+ }}</span>
3316
+ <span class="flex-1 h-px bg-surface-200 dark:bg-surface-700"></span>
3317
+ </div>
3318
+
3319
+ <div [class]="layoutClass()">
3320
+ @for (info of providers(); track info.provider) {
3321
+ <button
3322
+ fButton
3323
+ type="button"
3324
+ severity="secondary"
3325
+ [outlined]="true"
3326
+ [class]="showLabels() ? 'w-full' : 'shrink-0'"
3327
+ [attr.aria-label]="
3328
+ showLabels()
3329
+ ? null
3330
+ : ('auth.social.provider.' + info.provider | translate)
3331
+ "
3332
+ [attr.title]="
3333
+ showLabels()
3334
+ ? null
3335
+ : ('auth.social.provider.' + info.provider | translate)
3336
+ "
3337
+ [disabled]="pendingProvider() !== null && pendingProvider() !== info.provider"
3338
+ [loading]="pendingProvider() === info.provider"
3339
+ (click)="onSelect(info.provider)"
3340
+ >
3341
+ @if (pendingProvider() !== info.provider) {
3342
+ @switch (info.provider) {
3343
+ @case (providerEnum.GOOGLE) {
3344
+ <svg class="shrink-0" width="18" height="18" viewBox="0 0 48 48" aria-hidden="true">
3345
+ <path fill="#EA4335" d="M24 9.5c3.54 0 6.71 1.22 9.21 3.6l6.85-6.85C35.9 2.38 30.47 0 24 0 14.62 0 6.51 5.38 2.56 13.22l7.98 6.19C12.43 13.72 17.74 9.5 24 9.5z" />
3346
+ <path fill="#4285F4" d="M46.98 24.55c0-1.57-.15-3.09-.38-4.55H24v9.02h12.94c-.58 3-2.26 5.54-4.78 7.25l7.73 6c4.51-4.18 7.09-10.36 7.09-17.72z" />
3347
+ <path fill="#FBBC05" d="M10.53 28.59A14.5 14.5 0 0 1 9.77 24c0-1.6.29-3.14.76-4.59l-7.98-6.19A23.94 23.94 0 0 0 0 24c0 3.88.93 7.54 2.56 10.78l7.97-6.19z" />
3348
+ <path fill="#34A853" d="M24 48c6.48 0 11.93-2.13 15.89-5.81l-7.73-6c-2.15 1.45-4.92 2.3-8.16 2.3-6.26 0-11.57-4.22-13.47-9.91l-7.98 6.19C6.51 42.62 14.62 48 24 48z" />
3349
+ </svg>
3350
+ }
3351
+ @case (providerEnum.FACEBOOK) {
3352
+ <svg class="shrink-0" width="18" height="18" viewBox="0 0 24 24" aria-hidden="true">
3353
+ <path fill="#1877F2" d="M24 12.07C24 5.4 18.63 0 12 0S0 5.4 0 12.07C0 18.1 4.39 23.1 10.13 24v-8.44H7.08v-3.49h3.05V9.41c0-3.02 1.79-4.69 4.53-4.69 1.31 0 2.69.24 2.69.24v2.96h-1.51c-1.49 0-1.96.93-1.96 1.88v2.25h3.33l-.53 3.49h-2.8V24C19.61 23.1 24 18.1 24 12.07z" />
3354
+ </svg>
3355
+ }
3356
+ @case (providerEnum.LINKEDIN) {
3357
+ <svg class="shrink-0" width="18" height="18" viewBox="0 0 24 24" aria-hidden="true">
3358
+ <path fill="#0A66C2" d="M20.45 20.45h-3.56v-5.57c0-1.33-.03-3.04-1.85-3.04-1.86 0-2.14 1.45-2.14 2.94v5.67H9.35V9h3.41v1.56h.05a3.74 3.74 0 0 1 3.37-1.85c3.6 0 4.27 2.37 4.27 5.46v6.28zM5.34 7.43a2.06 2.06 0 1 1 0-4.13 2.06 2.06 0 0 1 0 4.13zM7.12 20.45H3.55V9h3.57v11.45zM22.22 0H1.77C.79 0 0 .78 0 1.73v20.54C0 23.22.79 24 1.77 24h20.45c.98 0 1.78-.78 1.78-1.73V1.73C24 .78 23.2 0 22.22 0z" />
3359
+ </svg>
3360
+ }
3361
+ @case (providerEnum.MICROSOFT) {
3362
+ <svg class="shrink-0" width="18" height="18" viewBox="0 0 23 23" aria-hidden="true">
3363
+ <path fill="#F25022" d="M0 0h11v11H0z" />
3364
+ <path fill="#7FBA00" d="M12 0h11v11H12z" />
3365
+ <path fill="#00A4EF" d="M0 12h11v11H0z" />
3366
+ <path fill="#FFB900" d="M12 12h11v11H12z" />
3367
+ </svg>
3368
+ }
3369
+ }
3370
+ }
3371
+ @if (showLabels()) {
3372
+ <span>{{
3373
+ 'auth.social.provider.' + info.provider | translate
3374
+ }}</span>
3375
+ }
3376
+ </button>
3377
+ }
3378
+ </div>
3379
+ </div>
3380
+ }
3381
+ `,
3382
+ }]
3383
+ }], ctorParameters: () => [], propDecorators: { returnUrl: [{ type: i0.Input, args: [{ isSignal: true, alias: "returnUrl", required: false }] }] } });
3384
+
3007
3385
  class LoginPageComponent {
3008
3386
  destroyRef = inject(DestroyRef);
3009
3387
  appConfig = inject(APP_CONFIG);
@@ -3029,6 +3407,8 @@ class LoginPageComponent {
3029
3407
  ...(ngDevMode ? [{ debugName: "unverifiedEmail" }] : /* istanbul ignore next */ []));
3030
3408
  savedUserName = signal(this.authState.getSavedUserName(), /* @ts-ignore */
3031
3409
  ...(ngDevMode ? [{ debugName: "savedUserName" }] : /* istanbul ignore next */ []));
3410
+ welcomeBackText = computed(() => this.translate('auth.login.welcome.back', { name: this.savedUserName() }), /* @ts-ignore */
3411
+ ...(ngDevMode ? [{ debugName: "welcomeBackText" }] : /* istanbul ignore next */ []));
3032
3412
  loginFormModel = signal({
3033
3413
  email: '',
3034
3414
  password: '',
@@ -3379,12 +3759,12 @@ class LoginPageComponent {
3379
3759
  <!-- Step 1: Credentials -->
3380
3760
  @if (currentStep() === 'credentials') {
3381
3761
  <div class="text-center mb-8">
3382
- <div class="text-3xl font-medium mb-4 text-color">
3762
+ <div
3763
+ class="text-3xl font-medium mb-4 text-color truncate"
3764
+ [title]="savedUserName() ? welcomeBackText() : null"
3765
+ >
3383
3766
  @if (savedUserName()) {
3384
- {{
3385
- 'auth.login.welcome.back'
3386
- | translate: { name: savedUserName()! }
3387
- }}
3767
+ {{ welcomeBackText() }}
3388
3768
  } @else {
3389
3769
  {{ 'auth.login.welcome' | translate }}
3390
3770
  }
@@ -3465,6 +3845,8 @@ class LoginPageComponent {
3465
3845
  <span pButtonLabel>{{ 'auth.login.sign.in' | translate }}</span>
3466
3846
  </button>
3467
3847
 
3848
+ <lib-social-login-buttons [returnUrl]="returnUrl()" />
3849
+
3468
3850
  @if (signUpEnabled()) {
3469
3851
  <div class="flex items-center justify-center mt-4 gap-2">
3470
3852
  <span class="font-medium">{{
@@ -3596,7 +3978,7 @@ class LoginPageComponent {
3596
3978
  </div>
3597
3979
  </div>
3598
3980
  </div>
3599
- `, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1$1.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "component", type: i1$1.CheckboxComponent, selector: "f-checkbox", inputs: ["label", "binary", "disabled", "readonly", "inputId", "invalid", "indeterminate", "variant", "size", "touched", "styleClass", "style", "checked"], outputs: ["onChange", "onBlur", "touch", "checkedChange"] }, { kind: "directive", type: i1$1.InputTextDirective, selector: "[fInputText]", inputs: ["invalid", "touched", "variant", "size", "fluid"] }, { kind: "component", type: i1$1.PasswordComponent, selector: "f-password", inputs: ["toggleMask", "feedback", "disabled", "placeholder", "inputId", "invalid", "touched", "inputStyleClass", "styleClass", "style", "ariaDescribedby", "autocomplete", "variant", "size", "fluid", "appendTo", "promptLabel", "weakLabel", "mediumLabel", "strongLabel", "mediumRegex", "strongRegex"], outputs: ["onBlur", "onFocus"] }, { kind: "component", type: i1$1.SelectComponent, selector: "f-select", inputs: ["options", "optionLabel", "optionValue", "placeholder", "disabled", "filter", "filterBy", "filterMatchMode", "filterLocale", "filterPlaceholder", "resetFilterOnHide", "autofocusFilter", "emptyMessage", "emptyFilterMessage", "showClear", "loading", "loadingIcon", "dropdownIcon", "clearIcon", "inputId", "invalid", "touched", "styleClass", "style", "appendTo", "editable", "readonly", "autofocus", "checkmark", "group", "optionGroupLabel", "optionGroupChildren", "virtualScroll", "virtualScrollItemSize", "lazy"], outputs: ["onFilter", "onChange", "onShow", "onHide", "onBlur", "onFocus", "onClear", "onLazyLoad"] }, { kind: "component", type: i1$1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.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", "browserUrl", "routerLink"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"], exportAs: ["formField"] }, { kind: "component", type: AppFloatingConfigurator, selector: "app-floating-configurator" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
3981
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1$1.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "component", type: i1$1.CheckboxComponent, selector: "f-checkbox", inputs: ["label", "binary", "disabled", "readonly", "inputId", "invalid", "indeterminate", "variant", "size", "touched", "styleClass", "style", "checked"], outputs: ["onChange", "onBlur", "touch", "checkedChange"] }, { kind: "directive", type: i1$1.InputTextDirective, selector: "[fInputText]", inputs: ["invalid", "touched", "variant", "size", "fluid"] }, { kind: "component", type: i1$1.PasswordComponent, selector: "f-password", inputs: ["toggleMask", "feedback", "disabled", "placeholder", "inputId", "invalid", "touched", "inputStyleClass", "styleClass", "style", "ariaDescribedby", "autocomplete", "variant", "size", "fluid", "appendTo", "promptLabel", "weakLabel", "mediumLabel", "strongLabel", "mediumRegex", "strongRegex"], outputs: ["onBlur", "onFocus"] }, { kind: "component", type: i1$1.SelectComponent, selector: "f-select", inputs: ["options", "optionLabel", "optionValue", "placeholder", "disabled", "filter", "filterBy", "filterMatchMode", "filterLocale", "filterPlaceholder", "resetFilterOnHide", "autofocusFilter", "emptyMessage", "emptyFilterMessage", "showClear", "loading", "loadingIcon", "dropdownIcon", "clearIcon", "inputId", "invalid", "touched", "styleClass", "style", "appendTo", "editable", "readonly", "autofocus", "checkmark", "group", "optionGroupLabel", "optionGroupChildren", "virtualScroll", "virtualScrollItemSize", "lazy"], outputs: ["onFilter", "onChange", "onShow", "onHide", "onBlur", "onFocus", "onClear", "onLazyLoad"] }, { kind: "component", type: i1$1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.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", "browserUrl", "routerLink"] }, { kind: "directive", type: FormField, selector: "[formField]", inputs: ["formField"], exportAs: ["formField"] }, { kind: "component", type: AppFloatingConfigurator, selector: "app-floating-configurator" }, { kind: "component", type: SocialLoginButtonsComponent, selector: "lib-social-login-buttons", inputs: ["returnUrl"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
3600
3982
  }
3601
3983
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: LoginPageComponent, decorators: [{
3602
3984
  type: Component,
@@ -3605,6 +3987,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
3605
3987
  imports: [UiModule, AngularModule,
3606
3988
  FormField,
3607
3989
  AppFloatingConfigurator,
3990
+ SocialLoginButtonsComponent,
3608
3991
  TranslatePipe,],
3609
3992
  template: `
3610
3993
  <app-floating-configurator />
@@ -3658,12 +4041,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
3658
4041
  <!-- Step 1: Credentials -->
3659
4042
  @if (currentStep() === 'credentials') {
3660
4043
  <div class="text-center mb-8">
3661
- <div class="text-3xl font-medium mb-4 text-color">
4044
+ <div
4045
+ class="text-3xl font-medium mb-4 text-color truncate"
4046
+ [title]="savedUserName() ? welcomeBackText() : null"
4047
+ >
3662
4048
  @if (savedUserName()) {
3663
- {{
3664
- 'auth.login.welcome.back'
3665
- | translate: { name: savedUserName()! }
3666
- }}
4049
+ {{ welcomeBackText() }}
3667
4050
  } @else {
3668
4051
  {{ 'auth.login.welcome' | translate }}
3669
4052
  }
@@ -3744,6 +4127,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
3744
4127
  <span pButtonLabel>{{ 'auth.login.sign.in' | translate }}</span>
3745
4128
  </button>
3746
4129
 
4130
+ <lib-social-login-buttons [returnUrl]="returnUrl()" />
4131
+
3747
4132
  @if (signUpEnabled()) {
3748
4133
  <div class="flex items-center justify-center mt-4 gap-2">
3749
4134
  <span class="font-medium">{{
@@ -7125,6 +7510,542 @@ var resetPasswordPage_component = /*#__PURE__*/Object.freeze({
7125
7510
  ResetPasswordPageComponent: ResetPasswordPageComponent
7126
7511
  });
7127
7512
 
7513
+ class SocialCallbackPageComponent {
7514
+ destroyRef = inject(DestroyRef);
7515
+ route = inject(ActivatedRoute);
7516
+ router = inject(Router);
7517
+ platform = inject(PlatformService);
7518
+ socialApi = inject(SocialAuthApiService);
7519
+ authState = inject(AuthStateService);
7520
+ messageService = inject(MessageService);
7521
+ translateAdapter = inject(TranslateService, {
7522
+ optional: true,
7523
+ });
7524
+ _error = signal(null, /* @ts-ignore */
7525
+ ...(ngDevMode ? [{ debugName: "_error" }] : /* istanbul ignore next */ []));
7526
+ errorDetail = computed(() => {
7527
+ const error = this._error();
7528
+ if (!error)
7529
+ return null;
7530
+ return 'key' in error ? this.translate(error.key, error.vars) : error.raw;
7531
+ }, /* @ts-ignore */
7532
+ ...(ngDevMode ? [{ debugName: "errorDetail" }] : /* istanbul ignore next */ []));
7533
+ constructor() {
7534
+ if (this.platform.isServer)
7535
+ return;
7536
+ this.exchangeCode();
7537
+ }
7538
+ backToLogin() {
7539
+ this.router.navigate(['/auth/login']);
7540
+ }
7541
+ exchangeCode() {
7542
+ const params = this.route.snapshot.queryParamMap;
7543
+ const providerError = params.get('error_description') ?? params.get('error');
7544
+ if (providerError) {
7545
+ this._error.set({ raw: providerError });
7546
+ return;
7547
+ }
7548
+ const code = params.get('code');
7549
+ const state = params.get('state');
7550
+ if (!code || !state) {
7551
+ this._error.set({ key: 'auth.social.state.invalid' });
7552
+ return;
7553
+ }
7554
+ this.socialApi
7555
+ .callback({ code, state, redirectUri: this.socialApi.redirectUri })
7556
+ .pipe(takeUntilDestroyed(this.destroyRef))
7557
+ .subscribe({
7558
+ next: (response) => {
7559
+ if (!response.data) {
7560
+ this._error.set({ key: 'auth.social.failed' });
7561
+ return;
7562
+ }
7563
+ if (isCompanySelectionResponse(response)) {
7564
+ this.authState.setSelectionState(response.data);
7565
+ this.router.navigate(['/auth/login']);
7566
+ return;
7567
+ }
7568
+ if (isLoginResponse(response)) {
7569
+ this.authState.setLoginState(response.data);
7570
+ this.messageService.add({
7571
+ severity: 'success',
7572
+ summary: this.translate('shared.success'),
7573
+ detail: response.messageKey
7574
+ ? this.translate(response.messageKey)
7575
+ : (response.message ?? this.translate('auth.login.success')),
7576
+ });
7577
+ this.authState.navigateAfterLogin(this.returnUrl());
7578
+ }
7579
+ },
7580
+ error: (error) => {
7581
+ const messageKey = error?.error?.messageKey;
7582
+ if (messageKey) {
7583
+ this._error.set({ key: messageKey, vars: error?.error?.messageVariables });
7584
+ }
7585
+ else if (error?.error?.message) {
7586
+ this._error.set({ raw: error.error.message });
7587
+ }
7588
+ else {
7589
+ this._error.set({ key: 'auth.social.failed' });
7590
+ }
7591
+ },
7592
+ });
7593
+ }
7594
+ returnUrl() {
7595
+ return this.socialApi.consumeReturnUrl();
7596
+ }
7597
+ translate(key, vars) {
7598
+ return this.translateAdapter?.translate(key, vars) ?? key;
7599
+ }
7600
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SocialCallbackPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7601
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: SocialCallbackPageComponent, isStandalone: true, selector: "lib-social-callback-page", ngImport: i0, template: `
7602
+ <app-floating-configurator />
7603
+ <div class="bg-surface-ground flex items-center justify-center min-h-screen">
7604
+ <div class="flex flex-col items-center gap-4 text-center px-4">
7605
+ @if (errorDetail()) {
7606
+ <ui-icon name="exclamation-circle" class="text-6xl text-danger"></ui-icon>
7607
+ <div class="text-2xl font-medium text-color">
7608
+ {{ 'auth.social.failed' | translate }}
7609
+ </div>
7610
+ <p class="text-muted-color font-medium">{{ errorDetail() }}</p>
7611
+ <button fButton severity="secondary" [outlined]="true" (click)="backToLogin()">
7612
+ <span>{{ 'auth.login.back.to.sign.in' | translate }}</span>
7613
+ </button>
7614
+ } @else {
7615
+ <ui-icon name="spinner" [spin]="true" class="text-5xl text-primary"></ui-icon>
7616
+ <div class="text-2xl font-medium text-color">
7617
+ {{ 'auth.social.signing.in' | translate }}
7618
+ </div>
7619
+ }
7620
+ </div>
7621
+ </div>
7622
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1$1.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "component", type: i1$1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "component", type: AppFloatingConfigurator, selector: "app-floating-configurator" }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
7623
+ }
7624
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SocialCallbackPageComponent, decorators: [{
7625
+ type: Component,
7626
+ args: [{
7627
+ selector: 'lib-social-callback-page',
7628
+ imports: [UiModule, TranslatePipe, AppFloatingConfigurator],
7629
+ template: `
7630
+ <app-floating-configurator />
7631
+ <div class="bg-surface-ground flex items-center justify-center min-h-screen">
7632
+ <div class="flex flex-col items-center gap-4 text-center px-4">
7633
+ @if (errorDetail()) {
7634
+ <ui-icon name="exclamation-circle" class="text-6xl text-danger"></ui-icon>
7635
+ <div class="text-2xl font-medium text-color">
7636
+ {{ 'auth.social.failed' | translate }}
7637
+ </div>
7638
+ <p class="text-muted-color font-medium">{{ errorDetail() }}</p>
7639
+ <button fButton severity="secondary" [outlined]="true" (click)="backToLogin()">
7640
+ <span>{{ 'auth.login.back.to.sign.in' | translate }}</span>
7641
+ </button>
7642
+ } @else {
7643
+ <ui-icon name="spinner" [spin]="true" class="text-5xl text-primary"></ui-icon>
7644
+ <div class="text-2xl font-medium text-color">
7645
+ {{ 'auth.social.signing.in' | translate }}
7646
+ </div>
7647
+ }
7648
+ </div>
7649
+ </div>
7650
+ `,
7651
+ }]
7652
+ }], ctorParameters: () => [] });
7653
+
7654
+ var socialCallbackPage_component = /*#__PURE__*/Object.freeze({
7655
+ __proto__: null,
7656
+ SocialCallbackPageComponent: SocialCallbackPageComponent
7657
+ });
7658
+
7659
+ const TENANT_PROVIDER = SocialProviderEnum.MICROSOFT;
7660
+ class SocialConfigPageComponent {
7661
+ configService = inject(SocialAuthConfigApiService);
7662
+ socialApi = inject(SocialAuthApiService);
7663
+ messageService = inject(MessageService);
7664
+ translateAdapter = inject(TranslateService, {
7665
+ optional: true,
7666
+ });
7667
+ tenantProvider = TENANT_PROVIDER;
7668
+ _forms = signal(SOCIAL_PROVIDERS.map(emptyForm), /* @ts-ignore */
7669
+ ...(ngDevMode ? [{ debugName: "_forms" }] : /* istanbul ignore next */ []));
7670
+ forms = this._forms.asReadonly();
7671
+ _isLoading = signal(true, /* @ts-ignore */
7672
+ ...(ngDevMode ? [{ debugName: "_isLoading" }] : /* istanbul ignore next */ []));
7673
+ isLoading = this._isLoading.asReadonly();
7674
+ _isSaving = signal(false, /* @ts-ignore */
7675
+ ...(ngDevMode ? [{ debugName: "_isSaving" }] : /* istanbul ignore next */ []));
7676
+ isSaving = this._isSaving.asReadonly();
7677
+ redirectUri = this.socialApi.redirectUri;
7678
+ constructor() {
7679
+ void this.loadConfigs();
7680
+ }
7681
+ updateField(provider, field, value) {
7682
+ this._forms.update((forms) => forms.map((form) => form.provider === provider ? { ...form, [field]: value } : form));
7683
+ }
7684
+ async onSave() {
7685
+ const invalid = this._forms().find((form) => form.isEnabled &&
7686
+ (!form.clientId.trim() || (!form.hasClientSecret && !form.clientSecret.trim())));
7687
+ if (invalid) {
7688
+ this.messageService.add({
7689
+ severity: 'warn',
7690
+ summary: this.translate('shared.validation'),
7691
+ detail: this.translate('social.config.credentials.required', {
7692
+ provider: this.translate(`auth.social.provider.${invalid.provider}`),
7693
+ }),
7694
+ });
7695
+ return;
7696
+ }
7697
+ this._isSaving.set(true);
7698
+ try {
7699
+ const pending = this._forms().filter((form) => form.id || form.isEnabled);
7700
+ await this.configService.bulkUpsert(pending.filter((form) => !form.id).map(toCreateDto), pending.filter((form) => form.id).map(toUpdateDto));
7701
+ this.messageService.add({
7702
+ severity: 'success',
7703
+ summary: this.translate('shared.success'),
7704
+ detail: this.translate('social.config.save.success'),
7705
+ });
7706
+ await this.loadConfigs();
7707
+ }
7708
+ catch {
7709
+ // Error toast handled by global interceptor
7710
+ }
7711
+ finally {
7712
+ this._isSaving.set(false);
7713
+ }
7714
+ }
7715
+ async loadConfigs() {
7716
+ this._isLoading.set(true);
7717
+ try {
7718
+ const response = await this.configService.getAll({
7719
+ pagination: { currentPage: 0, pageSize: SOCIAL_PROVIDERS.length },
7720
+ filter: {},
7721
+ select: [],
7722
+ sort: {},
7723
+ });
7724
+ this._forms.set(mergeConfigs(response.data ?? []));
7725
+ }
7726
+ catch {
7727
+ // Error toast handled by global interceptor
7728
+ }
7729
+ finally {
7730
+ this._isLoading.set(false);
7731
+ }
7732
+ }
7733
+ translate(key, vars) {
7734
+ return this.translateAdapter?.translate(key, vars) ?? key;
7735
+ }
7736
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SocialConfigPageComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
7737
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "22.0.6", type: SocialConfigPageComponent, isStandalone: true, selector: "lib-social-config-page", providers: [MessageService], ngImport: i0, template: `
7738
+ <div class="card">
7739
+ <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 mb-4">
7740
+ <div>
7741
+ <h3 class="text-lg sm:text-xl font-semibold m-0">
7742
+ {{ 'social.config.title' | translate }}
7743
+ </h3>
7744
+ <p class="text-sm text-muted-color mt-1">
7745
+ {{ 'social.config.subtitle' | translate }}
7746
+ </p>
7747
+ </div>
7748
+ <button fButton (click)="onSave()" [disabled]="isSaving()" [loading]="isSaving()">
7749
+ @if (!isSaving()) {
7750
+ <ui-icon pButtonIcon name="save"></ui-icon>
7751
+ }
7752
+ <span pButtonLabel>{{ 'shared.save' | translate }}</span>
7753
+ </button>
7754
+ </div>
7755
+
7756
+ <p class="text-sm text-muted-color mb-4">
7757
+ {{ 'social.config.redirect.uri' | translate }}:
7758
+ <code class="text-color">{{ redirectUri }}</code>
7759
+ </p>
7760
+
7761
+ @if (isLoading()) {
7762
+ <div class="flex justify-center py-8">
7763
+ <ui-icon name="spinner" [spin]="true" class="text-3xl text-primary"></ui-icon>
7764
+ </div>
7765
+ } @else {
7766
+ <div class="flex flex-col gap-4">
7767
+ @for (form of forms(); track form.provider) {
7768
+ <div class="border border-surface rounded p-4">
7769
+ <div class="flex items-center justify-between gap-3">
7770
+ <div>
7771
+ <div class="font-semibold">
7772
+ {{ 'auth.social.provider.' + form.provider | translate }}
7773
+ </div>
7774
+ <div class="text-sm text-muted-color">
7775
+ {{ 'social.config.provider.hint.' + form.provider | translate }}
7776
+ </div>
7777
+ </div>
7778
+ <f-toggleswitch
7779
+ [ngModel]="form.isEnabled"
7780
+ (ngModelChange)="updateField(form.provider, 'isEnabled', $event)"
7781
+ />
7782
+ </div>
7783
+
7784
+ @if (form.isEnabled) {
7785
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4 pt-4 border-t border-surface">
7786
+ <div class="field">
7787
+ <label [for]="form.provider + '-client-id'" class="block font-medium mb-2">
7788
+ {{ 'social.config.client.id' | translate }} *
7789
+ </label>
7790
+ <input
7791
+ fInputText
7792
+ [id]="form.provider + '-client-id'"
7793
+ class="w-full"
7794
+ [ngModel]="form.clientId"
7795
+ (ngModelChange)="updateField(form.provider, 'clientId', $event)"
7796
+ />
7797
+ </div>
7798
+
7799
+ <div class="field">
7800
+ <label [for]="form.provider + '-client-secret'" class="block font-medium mb-2">
7801
+ {{ 'social.config.client.secret' | translate }}
7802
+ @if (!form.hasClientSecret) {
7803
+ *
7804
+ }
7805
+ </label>
7806
+ <f-password
7807
+ [inputId]="form.provider + '-client-secret'"
7808
+ styleClass="w-full"
7809
+ [placeholder]="
7810
+ form.hasClientSecret
7811
+ ? ('social.config.client.secret.unchanged' | translate)
7812
+ : ''
7813
+ "
7814
+ [ngModel]="form.clientSecret"
7815
+ (ngModelChange)="updateField(form.provider, 'clientSecret', $event)"
7816
+ />
7817
+ </div>
7818
+
7819
+ @if (form.provider === tenantProvider) {
7820
+ <div class="field">
7821
+ <label [for]="form.provider + '-tenant'" class="block font-medium mb-2">
7822
+ {{ 'social.config.tenant.id' | translate }}
7823
+ </label>
7824
+ <input
7825
+ fInputText
7826
+ [id]="form.provider + '-tenant'"
7827
+ class="w-full"
7828
+ [placeholder]="'social.config.tenant.id.example' | translate"
7829
+ [ngModel]="form.tenantId"
7830
+ (ngModelChange)="updateField(form.provider, 'tenantId', $event)"
7831
+ />
7832
+ </div>
7833
+ }
7834
+
7835
+ <div class="field">
7836
+ <label [for]="form.provider + '-scopes'" class="block font-medium mb-2">
7837
+ {{ 'social.config.scopes' | translate }}
7838
+ </label>
7839
+ <input
7840
+ fInputText
7841
+ [id]="form.provider + '-scopes'"
7842
+ class="w-full"
7843
+ [placeholder]="'social.config.scopes.placeholder' | translate"
7844
+ [ngModel]="form.scopes"
7845
+ (ngModelChange)="updateField(form.provider, 'scopes', $event)"
7846
+ />
7847
+ </div>
7848
+
7849
+ </div>
7850
+ }
7851
+ </div>
7852
+ }
7853
+ </div>
7854
+ }
7855
+ </div>
7856
+ `, isInline: true, dependencies: [{ kind: "ngmodule", type: UiModule }, { kind: "directive", type: i1$1.FButtonDirective, selector: "[fButton]", inputs: ["severity", "text", "outlined", "link", "variant", "rounded", "raised", "fluid", "disabled", "loading", "loadingIcon", "size", "label", "icon", "iconPos", "ariaLabel", "plain"] }, { kind: "directive", type: i1$1.InputTextDirective, selector: "[fInputText]", inputs: ["invalid", "touched", "variant", "size", "fluid"] }, { kind: "component", type: i1$1.PasswordComponent, selector: "f-password", inputs: ["toggleMask", "feedback", "disabled", "placeholder", "inputId", "invalid", "touched", "inputStyleClass", "styleClass", "style", "ariaDescribedby", "autocomplete", "variant", "size", "fluid", "appendTo", "promptLabel", "weakLabel", "mediumLabel", "strongLabel", "mediumRegex", "strongRegex"], outputs: ["onBlur", "onFocus"] }, { kind: "component", type: i1$1.ToggleSwitchComponent, selector: "f-toggleswitch", inputs: ["disabled", "readonly", "invalid", "ariaLabel", "trueValue", "falseValue", "inputId", "size", "tabindex", "styleClass", "style"] }, { kind: "component", type: i1$1.UiIconComponent, selector: "ui-icon", inputs: ["name", "size", "spin", "fill", "severity"] }, { kind: "ngmodule", type: AngularModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox]):not([ngNoCva])[formControlName],textarea:not([ngNoCva])[formControlName],input:not([type=checkbox]):not([ngNoCva])[formControl],textarea:not([ngNoCva])[formControl],input:not([type=checkbox]):not([ngNoCva])[ngModel],textarea:not([ngNoCva])[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
7857
+ }
7858
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImport: i0, type: SocialConfigPageComponent, decorators: [{
7859
+ type: Component,
7860
+ args: [{
7861
+ selector: 'lib-social-config-page',
7862
+ imports: [UiModule, AngularModule, TranslatePipe],
7863
+ providers: [MessageService],
7864
+ template: `
7865
+ <div class="card">
7866
+ <div class="flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3 mb-4">
7867
+ <div>
7868
+ <h3 class="text-lg sm:text-xl font-semibold m-0">
7869
+ {{ 'social.config.title' | translate }}
7870
+ </h3>
7871
+ <p class="text-sm text-muted-color mt-1">
7872
+ {{ 'social.config.subtitle' | translate }}
7873
+ </p>
7874
+ </div>
7875
+ <button fButton (click)="onSave()" [disabled]="isSaving()" [loading]="isSaving()">
7876
+ @if (!isSaving()) {
7877
+ <ui-icon pButtonIcon name="save"></ui-icon>
7878
+ }
7879
+ <span pButtonLabel>{{ 'shared.save' | translate }}</span>
7880
+ </button>
7881
+ </div>
7882
+
7883
+ <p class="text-sm text-muted-color mb-4">
7884
+ {{ 'social.config.redirect.uri' | translate }}:
7885
+ <code class="text-color">{{ redirectUri }}</code>
7886
+ </p>
7887
+
7888
+ @if (isLoading()) {
7889
+ <div class="flex justify-center py-8">
7890
+ <ui-icon name="spinner" [spin]="true" class="text-3xl text-primary"></ui-icon>
7891
+ </div>
7892
+ } @else {
7893
+ <div class="flex flex-col gap-4">
7894
+ @for (form of forms(); track form.provider) {
7895
+ <div class="border border-surface rounded p-4">
7896
+ <div class="flex items-center justify-between gap-3">
7897
+ <div>
7898
+ <div class="font-semibold">
7899
+ {{ 'auth.social.provider.' + form.provider | translate }}
7900
+ </div>
7901
+ <div class="text-sm text-muted-color">
7902
+ {{ 'social.config.provider.hint.' + form.provider | translate }}
7903
+ </div>
7904
+ </div>
7905
+ <f-toggleswitch
7906
+ [ngModel]="form.isEnabled"
7907
+ (ngModelChange)="updateField(form.provider, 'isEnabled', $event)"
7908
+ />
7909
+ </div>
7910
+
7911
+ @if (form.isEnabled) {
7912
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4 pt-4 border-t border-surface">
7913
+ <div class="field">
7914
+ <label [for]="form.provider + '-client-id'" class="block font-medium mb-2">
7915
+ {{ 'social.config.client.id' | translate }} *
7916
+ </label>
7917
+ <input
7918
+ fInputText
7919
+ [id]="form.provider + '-client-id'"
7920
+ class="w-full"
7921
+ [ngModel]="form.clientId"
7922
+ (ngModelChange)="updateField(form.provider, 'clientId', $event)"
7923
+ />
7924
+ </div>
7925
+
7926
+ <div class="field">
7927
+ <label [for]="form.provider + '-client-secret'" class="block font-medium mb-2">
7928
+ {{ 'social.config.client.secret' | translate }}
7929
+ @if (!form.hasClientSecret) {
7930
+ *
7931
+ }
7932
+ </label>
7933
+ <f-password
7934
+ [inputId]="form.provider + '-client-secret'"
7935
+ styleClass="w-full"
7936
+ [placeholder]="
7937
+ form.hasClientSecret
7938
+ ? ('social.config.client.secret.unchanged' | translate)
7939
+ : ''
7940
+ "
7941
+ [ngModel]="form.clientSecret"
7942
+ (ngModelChange)="updateField(form.provider, 'clientSecret', $event)"
7943
+ />
7944
+ </div>
7945
+
7946
+ @if (form.provider === tenantProvider) {
7947
+ <div class="field">
7948
+ <label [for]="form.provider + '-tenant'" class="block font-medium mb-2">
7949
+ {{ 'social.config.tenant.id' | translate }}
7950
+ </label>
7951
+ <input
7952
+ fInputText
7953
+ [id]="form.provider + '-tenant'"
7954
+ class="w-full"
7955
+ [placeholder]="'social.config.tenant.id.example' | translate"
7956
+ [ngModel]="form.tenantId"
7957
+ (ngModelChange)="updateField(form.provider, 'tenantId', $event)"
7958
+ />
7959
+ </div>
7960
+ }
7961
+
7962
+ <div class="field">
7963
+ <label [for]="form.provider + '-scopes'" class="block font-medium mb-2">
7964
+ {{ 'social.config.scopes' | translate }}
7965
+ </label>
7966
+ <input
7967
+ fInputText
7968
+ [id]="form.provider + '-scopes'"
7969
+ class="w-full"
7970
+ [placeholder]="'social.config.scopes.placeholder' | translate"
7971
+ [ngModel]="form.scopes"
7972
+ (ngModelChange)="updateField(form.provider, 'scopes', $event)"
7973
+ />
7974
+ </div>
7975
+
7976
+ </div>
7977
+ }
7978
+ </div>
7979
+ }
7980
+ </div>
7981
+ }
7982
+ </div>
7983
+ `,
7984
+ }]
7985
+ }], ctorParameters: () => [] });
7986
+ function emptyForm(provider) {
7987
+ return {
7988
+ provider,
7989
+ id: null,
7990
+ isEnabled: false,
7991
+ clientId: '',
7992
+ clientSecret: '',
7993
+ hasClientSecret: false,
7994
+ tenantId: '',
7995
+ scopes: '',
7996
+ };
7997
+ }
7998
+ function mergeConfigs(configs) {
7999
+ return SOCIAL_PROVIDERS.map((provider) => {
8000
+ const config = configs.find((entry) => entry.provider === provider);
8001
+ if (!config)
8002
+ return emptyForm(provider);
8003
+ return {
8004
+ provider,
8005
+ id: config.id,
8006
+ isEnabled: config.isEnabled,
8007
+ clientId: config.clientId ?? '',
8008
+ clientSecret: '',
8009
+ hasClientSecret: config.hasClientSecret,
8010
+ tenantId: config.extraConfig?.['tenantId'] ?? '',
8011
+ scopes: (config.scopes ?? []).join(', '),
8012
+ };
8013
+ });
8014
+ }
8015
+ /** clientSecret is omitted here; callers decide whether to include a newly typed value. */
8016
+ function toBaseDto(form) {
8017
+ const scopes = form.scopes
8018
+ .split(',')
8019
+ .map((scope) => scope.trim())
8020
+ .filter(Boolean);
8021
+ return {
8022
+ provider: form.provider,
8023
+ isEnabled: form.isEnabled,
8024
+ clientId: form.clientId.trim(),
8025
+ ...(scopes.length ? { scopes } : {}),
8026
+ ...(form.provider === TENANT_PROVIDER && form.tenantId.trim()
8027
+ ? { extraConfig: { tenantId: form.tenantId.trim() } }
8028
+ : {}),
8029
+ };
8030
+ }
8031
+ function toCreateDto(form) {
8032
+ return { ...toBaseDto(form), clientSecret: form.clientSecret.trim() };
8033
+ }
8034
+ function toUpdateDto(form) {
8035
+ const trimmedSecret = form.clientSecret.trim();
8036
+ return {
8037
+ id: form.id,
8038
+ ...toBaseDto(form),
8039
+ // Blank means "keep the previously stored secret" - never overwrite it with an empty value.
8040
+ ...(trimmedSecret ? { clientSecret: trimmedSecret } : {}),
8041
+ };
8042
+ }
8043
+
8044
+ var socialConfigPage_component = /*#__PURE__*/Object.freeze({
8045
+ __proto__: null,
8046
+ SocialConfigPageComponent: SocialConfigPageComponent
8047
+ });
8048
+
7128
8049
  class UserFormComponent {
7129
8050
  destroyRef = inject(DestroyRef);
7130
8051
  router = inject(Router);
@@ -7706,6 +8627,7 @@ class UserFormComponent {
7706
8627
  <div class="flex justify-end gap-4 md:col-span-2 pt-4">
7707
8628
  <button
7708
8629
  fButton
8630
+ type="button"
7709
8631
  severity="secondary"
7710
8632
  [outlined]="true"
7711
8633
  (click)="onCancel()"
@@ -8007,6 +8929,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "22.0.6", ngImpor
8007
8929
  <div class="flex justify-end gap-4 md:col-span-2 pt-4">
8008
8930
  <button
8009
8931
  fButton
8932
+ type="button"
8010
8933
  severity="secondary"
8011
8934
  [outlined]="true"
8012
8935
  (click)="onCancel()"
@@ -9780,11 +10703,11 @@ var verifyEmailPage_component = /*#__PURE__*/Object.freeze({
9780
10703
  // =============================================================================
9781
10704
  // @flusys/ng-auth - Authentication Package
9782
10705
  // =============================================================================
9783
- // Interfaces (matching NestJS auth API)
10706
+ // Enums
9784
10707
 
9785
10708
  /**
9786
10709
  * Generated bundle index. Do not edit.
9787
10710
  */
9788
10711
 
9789
- export { ADMINISTRATION_ROUTES, AUTH_ENDPOINTS, AUTH_MESSAGES, AUTH_PROFILE_EXTENSION, AUTH_REGISTRATION_EXTENSION, AUTH_ROUTES, AUTH_USER_DETAIL_CONFIG, AUTH_USER_FORM_EXTENSION, AUTH_USER_LIST_EXTENSION, AUTH_VALIDATION_CONFIG, AdministrationPageComponent, AuthApiService, AuthCompanyApiProviderAdapter, AuthInitService, AuthLayoutApiAdapter, AuthLayoutStateAdapter, AuthStateProviderAdapter, AuthStateService, AuthUserPermissionProviderAdapter, AuthUserProviderAdapter, BranchApiService, BranchFormComponent, BranchListComponent, CompanyApiService, CompanyFormComponent, CompanyListComponent, ForgotPasswordPageComponent, LoginPageComponent, PROFILE_FIELD_CONTEXT, PROFILE_ROUTES, ProfilePageComponent, REGISTRATION_FIELD_CONTEXT, RegisterPageComponent, ResetPasswordPageComponent, TokenRefreshStateService, USER_FORM_FIELD_CONTEXT, UserApiService, UserBranchPermissionDialogComponent, UserCompanyPermissionDialogComponent, UserFormComponent, UserListComponent, UserPermissionApiService, VerifyEmailPageComponent, authGuard, authInterceptor, companyFeatureGuard, emailFeatureGuard, guestGuard, isCompanySelectionResponse, isEmailVerificationRequired, isEndpoint, isLoginResponse, provideAuthLayoutIntegration, provideAuthProviders, signUpFeatureGuard, tokenRefreshInterceptor, validateEmail, validatePassword };
10712
+ export { ADMINISTRATION_ROUTES, AUTH_ENDPOINTS, AUTH_MESSAGES, AUTH_PROFILE_EXTENSION, AUTH_REGISTRATION_EXTENSION, AUTH_ROUTES, AUTH_USER_DETAIL_CONFIG, AUTH_USER_FORM_EXTENSION, AUTH_USER_LIST_EXTENSION, AUTH_VALIDATION_CONFIG, AdministrationPageComponent, AuthApiService, AuthCompanyApiProviderAdapter, AuthInitService, AuthLayoutApiAdapter, AuthLayoutStateAdapter, AuthStateProviderAdapter, AuthStateService, AuthUserPermissionProviderAdapter, AuthUserProviderAdapter, BranchApiService, BranchFormComponent, BranchListComponent, CompanyApiService, CompanyFormComponent, CompanyListComponent, ForgotPasswordPageComponent, LoginPageComponent, PROFILE_FIELD_CONTEXT, PROFILE_ROUTES, ProfilePageComponent, REGISTRATION_FIELD_CONTEXT, RegisterPageComponent, ResetPasswordPageComponent, SOCIAL_PROVIDERS, SOCIAL_RETURN_URL_KEY, SocialAuthApiService, SocialAuthConfigApiService, SocialCallbackPageComponent, SocialConfigPageComponent, SocialLoginButtonsComponent, SocialProviderEnum, TokenRefreshStateService, USER_FORM_FIELD_CONTEXT, UserApiService, UserBranchPermissionDialogComponent, UserCompanyPermissionDialogComponent, UserFormComponent, UserListComponent, UserPermissionApiService, VerifyEmailPageComponent, authGuard, authInterceptor, companyFeatureGuard, emailFeatureGuard, guestGuard, isCompanySelectionResponse, isEmailVerificationRequired, isEndpoint, isLoginResponse, provideAuthLayoutIntegration, provideAuthProviders, signUpFeatureGuard, tokenRefreshInterceptor, validateEmail, validatePassword };
9790
10713
  //# sourceMappingURL=flusys-ng-auth.mjs.map