@progalaxyelabs/ngx-stonescriptphp-client 1.3.1 → 1.4.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,6 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, Inject, NgModule, Input, Component, EventEmitter, Output, Optional } from '@angular/core';
2
+ import { Injectable, Inject, NgModule, EventEmitter, Output, Input, Component, Optional } from '@angular/core';
3
3
  import { BehaviorSubject } from 'rxjs';
4
+ import * as i2$1 from '@angular/common';
4
5
  import { CommonModule } from '@angular/common';
5
6
  import * as i2 from '@angular/forms';
6
7
  import { FormsModule } from '@angular/forms';
@@ -194,6 +195,11 @@ class MyEnvironmentModel {
194
195
  csrfTokenCookieName: 'csrf_token',
195
196
  csrfHeaderName: 'X-CSRF-Token'
196
197
  };
198
+ /**
199
+ * Branding configuration for auth components
200
+ * Allows platforms to customize login/register pages without creating wrappers
201
+ */
202
+ branding;
197
203
  }
198
204
 
199
205
  /**
@@ -1251,29 +1257,59 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
1251
1257
  }]
1252
1258
  }] });
1253
1259
 
1254
- class LoginDialogComponent {
1260
+ class TenantLoginComponent {
1255
1261
  auth;
1256
- /**
1257
- * REQUIRED: Which authentication providers to show in this dialog
1258
- * @example ['google', 'linkedin', 'emailPassword']
1259
- */
1260
- providers = [];
1262
+ // Component Configuration
1263
+ title = 'Sign In';
1264
+ providers = ['google'];
1265
+ showTenantSelector = true;
1266
+ autoSelectSingleTenant = true;
1267
+ prefillEmail; // Email to prefill (for account linking flow)
1268
+ allowTenantCreation = true;
1269
+ // Tenant Selector Labels
1270
+ tenantSelectorTitle = 'Select Organization';
1271
+ tenantSelectorDescription = 'Choose which organization you want to access:';
1272
+ continueButtonText = 'Continue';
1273
+ // Link Labels
1274
+ registerLinkText = "Don't have an account?";
1275
+ registerLinkAction = 'Sign up';
1276
+ createTenantLinkText = "Don't see your organization?";
1277
+ createTenantLinkAction = 'Create New Organization';
1278
+ // Outputs
1279
+ tenantSelected = new EventEmitter();
1280
+ createTenant = new EventEmitter();
1281
+ // Form Fields
1261
1282
  email = '';
1262
1283
  password = '';
1284
+ // State
1263
1285
  error = '';
1264
1286
  loading = false;
1287
+ showPassword = false;
1288
+ useOAuth = true;
1265
1289
  oauthProviders = [];
1290
+ // Tenant Selection State
1291
+ showingTenantSelector = false;
1292
+ memberships = [];
1293
+ selectedTenantId = null;
1294
+ userName = '';
1266
1295
  constructor(auth) {
1267
1296
  this.auth = auth;
1268
1297
  }
1269
1298
  ngOnInit() {
1270
1299
  if (!this.providers || this.providers.length === 0) {
1271
- this.error = 'Configuration Error: No authentication providers specified. Please pass providers to LoginDialogComponent.';
1272
- throw new Error('LoginDialogComponent requires providers input. Example: dialogRef.componentInstance.providers = [\'google\', \'emailPassword\']');
1300
+ this.error = 'Configuration Error: No authentication providers specified.';
1301
+ throw new Error('TenantLoginComponent requires providers input.');
1302
+ }
1303
+ this.oauthProviders = this.providers.filter(p => p !== 'emailPassword');
1304
+ // If only emailPassword is available, use it by default
1305
+ if (this.oauthProviders.length === 0 && this.isProviderEnabled('emailPassword')) {
1306
+ this.useOAuth = false;
1307
+ }
1308
+ // Prefill email if provided (for account linking flow)
1309
+ if (this.prefillEmail) {
1310
+ this.email = this.prefillEmail;
1311
+ this.useOAuth = false; // Switch to email/password form
1273
1312
  }
1274
- // Get OAuth providers (excluding emailPassword)
1275
- this.oauthProviders = this.providers
1276
- .filter(p => p !== 'emailPassword');
1277
1313
  }
1278
1314
  isProviderEnabled(provider) {
1279
1315
  return this.providers.includes(provider);
@@ -1290,9 +1326,13 @@ class LoginDialogComponent {
1290
1326
  return labels[provider];
1291
1327
  }
1292
1328
  getProviderIcon(provider) {
1293
- // Platforms can customize icons via CSS classes: .btn-google, .btn-linkedin, etc.
1294
1329
  return undefined;
1295
1330
  }
1331
+ toggleAuthMethod(event) {
1332
+ event.preventDefault();
1333
+ this.useOAuth = !this.useOAuth;
1334
+ this.error = '';
1335
+ }
1296
1336
  async onEmailLogin() {
1297
1337
  if (!this.email || !this.password) {
1298
1338
  this.error = 'Please enter email and password';
@@ -1304,11 +1344,13 @@ class LoginDialogComponent {
1304
1344
  const result = await this.auth.loginWithEmail(this.email, this.password);
1305
1345
  if (!result.success) {
1306
1346
  this.error = result.message || 'Login failed';
1347
+ return;
1307
1348
  }
1308
- // On success, parent component/dialog should close automatically via user$ subscription
1349
+ // Authentication successful, now handle tenant selection
1350
+ await this.handlePostAuthFlow();
1309
1351
  }
1310
1352
  catch (err) {
1311
- this.error = 'An unexpected error occurred';
1353
+ this.error = err.message || 'An unexpected error occurred';
1312
1354
  }
1313
1355
  finally {
1314
1356
  this.loading = false;
@@ -1321,194 +1363,499 @@ class LoginDialogComponent {
1321
1363
  const result = await this.auth.loginWithProvider(provider);
1322
1364
  if (!result.success) {
1323
1365
  this.error = result.message || 'OAuth login failed';
1366
+ return;
1324
1367
  }
1325
- // On success, parent component/dialog should close automatically via user$ subscription
1368
+ // Authentication successful, now handle tenant selection
1369
+ await this.handlePostAuthFlow();
1326
1370
  }
1327
1371
  catch (err) {
1328
- this.error = 'An unexpected error occurred';
1372
+ this.error = err.message || 'An unexpected error occurred';
1329
1373
  }
1330
1374
  finally {
1331
1375
  this.loading = false;
1332
1376
  }
1333
1377
  }
1334
- onRegisterClick(event) {
1335
- event.preventDefault();
1336
- // Platforms can override this or listen for a custom event
1337
- // For now, just emit a console message
1338
- console.log('Register clicked - platform should handle navigation');
1339
- }
1340
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: LoginDialogComponent, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Component });
1341
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: LoginDialogComponent, isStandalone: true, selector: "lib-login-dialog", inputs: { providers: "providers" }, ngImport: i0, template: `
1342
- <div class="login-dialog">
1343
- <h2 class="login-title">Sign In</h2>
1344
-
1345
- <!-- Email/Password Form (if enabled) -->
1346
- @if (isProviderEnabled('emailPassword')) {
1347
- <form (ngSubmit)="onEmailLogin()" class="email-form">
1348
- <div class="form-group">
1349
- <input
1350
- [(ngModel)]="email"
1351
- name="email"
1352
- placeholder="Email"
1353
- type="email"
1354
- required
1355
- class="form-control">
1356
- </div>
1357
- <div class="form-group">
1358
- <input
1359
- [(ngModel)]="password"
1360
- name="password"
1361
- placeholder="Password"
1362
- type="password"
1363
- required
1364
- class="form-control">
1365
- </div>
1366
- <button
1367
- type="submit"
1368
- [disabled]="loading"
1369
- class="btn btn-primary btn-block">
1370
- {{ loading ? 'Signing in...' : getProviderLabel('emailPassword') }}
1371
- </button>
1372
- </form>
1378
+ async handlePostAuthFlow() {
1379
+ if (!this.showTenantSelector) {
1380
+ // Tenant selection is disabled, emit event immediately
1381
+ this.tenantSelected.emit({
1382
+ tenantId: '',
1383
+ tenantSlug: '',
1384
+ role: ''
1385
+ });
1386
+ return;
1387
+ }
1388
+ // Fetch user's tenant memberships
1389
+ this.loading = true;
1390
+ try {
1391
+ const result = await this.auth.getTenantMemberships();
1392
+ if (!result.memberships || result.memberships.length === 0) {
1393
+ // User has no tenants, prompt to create one
1394
+ this.error = 'You are not a member of any organization. Please create one.';
1395
+ if (this.allowTenantCreation) {
1396
+ setTimeout(() => this.createTenant.emit(), 2000);
1397
+ }
1398
+ return;
1373
1399
  }
1374
-
1375
- <!-- Divider if both email and OAuth are present -->
1376
- @if (isProviderEnabled('emailPassword') && oauthProviders.length > 0) {
1377
- <div class="divider">
1378
- <span>OR</span>
1379
- </div>
1400
+ this.memberships = result.memberships;
1401
+ // Get user name if available
1402
+ const currentUser = this.auth.getCurrentUser();
1403
+ if (currentUser) {
1404
+ this.userName = currentUser.display_name || currentUser.email;
1380
1405
  }
1381
-
1382
- <!-- OAuth Providers -->
1383
- @if (oauthProviders.length > 0) {
1384
- <div class="oauth-buttons">
1385
- @for (provider of oauthProviders; track provider) {
1386
- <button
1387
- (click)="onOAuthLogin(provider)"
1388
- [disabled]="loading"
1389
- class="btn btn-oauth btn-{{ provider }}">
1390
- @if (getProviderIcon(provider)) {
1391
- <span class="oauth-icon">
1392
- {{ getProviderIcon(provider) }}
1393
- </span>
1394
- }
1395
- {{ getProviderLabel(provider) }}
1396
- </button>
1397
- }
1398
- </div>
1406
+ // Auto-select if user has only one tenant
1407
+ if (this.memberships.length === 1 && this.autoSelectSingleTenant) {
1408
+ await this.selectAndContinue(this.memberships[0]);
1399
1409
  }
1400
-
1401
- <!-- Error Message -->
1402
- @if (error) {
1403
- <div class="error-message">
1404
- {{ error }}
1405
- </div>
1410
+ else {
1411
+ // Show tenant selector
1412
+ this.showingTenantSelector = true;
1406
1413
  }
1407
-
1408
- <!-- Loading State -->
1409
- @if (loading) {
1410
- <div class="loading-overlay">
1411
- <div class="spinner"></div>
1412
- </div>
1414
+ }
1415
+ catch (err) {
1416
+ this.error = err.message || 'Failed to load organizations';
1417
+ }
1418
+ finally {
1419
+ this.loading = false;
1420
+ }
1421
+ }
1422
+ selectTenantItem(tenantId) {
1423
+ this.selectedTenantId = tenantId;
1424
+ }
1425
+ async onContinueWithTenant() {
1426
+ if (!this.selectedTenantId) {
1427
+ this.error = 'Please select an organization';
1428
+ return;
1429
+ }
1430
+ const membership = this.memberships.find(m => m.tenant_id === this.selectedTenantId);
1431
+ if (!membership) {
1432
+ this.error = 'Selected organization not found';
1433
+ return;
1434
+ }
1435
+ await this.selectAndContinue(membership);
1436
+ }
1437
+ async selectAndContinue(membership) {
1438
+ this.loading = true;
1439
+ this.error = '';
1440
+ try {
1441
+ const result = await this.auth.selectTenant(membership.tenant_id);
1442
+ if (!result.success) {
1443
+ this.error = result.message || 'Failed to select organization';
1444
+ return;
1413
1445
  }
1446
+ // Emit tenant selected event
1447
+ this.tenantSelected.emit({
1448
+ tenantId: membership.tenant_id,
1449
+ tenantSlug: membership.slug,
1450
+ role: membership.role
1451
+ });
1452
+ }
1453
+ catch (err) {
1454
+ this.error = err.message || 'An unexpected error occurred';
1455
+ }
1456
+ finally {
1457
+ this.loading = false;
1458
+ }
1459
+ }
1460
+ formatRole(role) {
1461
+ return role.charAt(0).toUpperCase() + role.slice(1);
1462
+ }
1463
+ formatLastAccessed(dateStr) {
1464
+ try {
1465
+ const date = new Date(dateStr);
1466
+ const now = new Date();
1467
+ const diffMs = now.getTime() - date.getTime();
1468
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
1469
+ if (diffDays === 0)
1470
+ return 'today';
1471
+ if (diffDays === 1)
1472
+ return 'yesterday';
1473
+ if (diffDays < 7)
1474
+ return `${diffDays} days ago`;
1475
+ if (diffDays < 30)
1476
+ return `${Math.floor(diffDays / 7)} weeks ago`;
1477
+ return `${Math.floor(diffDays / 30)} months ago`;
1478
+ }
1479
+ catch {
1480
+ return dateStr;
1481
+ }
1482
+ }
1483
+ onCreateTenantClick(event) {
1484
+ event.preventDefault();
1485
+ this.createTenant.emit();
1486
+ }
1487
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantLoginComponent, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Component });
1488
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: TenantLoginComponent, isStandalone: true, selector: "lib-tenant-login", inputs: { title: "title", providers: "providers", showTenantSelector: "showTenantSelector", autoSelectSingleTenant: "autoSelectSingleTenant", prefillEmail: "prefillEmail", allowTenantCreation: "allowTenantCreation", tenantSelectorTitle: "tenantSelectorTitle", tenantSelectorDescription: "tenantSelectorDescription", continueButtonText: "continueButtonText", registerLinkText: "registerLinkText", registerLinkAction: "registerLinkAction", createTenantLinkText: "createTenantLinkText", createTenantLinkAction: "createTenantLinkAction" }, outputs: { tenantSelected: "tenantSelected", createTenant: "createTenant" }, ngImport: i0, template: `
1489
+ <div class="tenant-login-dialog">
1490
+ @if (!showingTenantSelector) {
1491
+ <!-- Step 1: Authentication -->
1492
+ <h2 class="login-title">{{ title }}</h2>
1414
1493
 
1415
- <!-- Register Link -->
1416
- <div class="register-link">
1417
- Don't have an account?
1418
- <a href="#" (click)="onRegisterClick($event)">Sign up</a>
1419
- </div>
1420
- </div>
1421
- `, isInline: true, styles: [".login-dialog{padding:24px;max-width:400px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.email-form,.form-group{margin-bottom:16px}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
1422
- }
1423
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: LoginDialogComponent, decorators: [{
1424
- type: Component,
1425
- args: [{ selector: 'lib-login-dialog', standalone: true, imports: [CommonModule, FormsModule], template: `
1426
- <div class="login-dialog">
1427
- <h2 class="login-title">Sign In</h2>
1494
+ <!-- Email/Password Form (if enabled) -->
1495
+ @if (isProviderEnabled('emailPassword') && !useOAuth) {
1496
+ <form (ngSubmit)="onEmailLogin()" class="email-form">
1497
+ <div class="form-group">
1498
+ <input
1499
+ [(ngModel)]="email"
1500
+ name="email"
1501
+ placeholder="Email"
1502
+ type="email"
1503
+ required
1504
+ class="form-control">
1505
+ </div>
1506
+ <div class="form-group password-group">
1507
+ <input
1508
+ [(ngModel)]="password"
1509
+ name="password"
1510
+ placeholder="Password"
1511
+ [type]="showPassword ? 'text' : 'password'"
1512
+ required
1513
+ class="form-control password-input">
1514
+ <button
1515
+ type="button"
1516
+ class="password-toggle"
1517
+ (click)="showPassword = !showPassword"
1518
+ [attr.aria-label]="showPassword ? 'Hide password' : 'Show password'">
1519
+ {{ showPassword ? '👁️' : '👁️‍🗨️' }}
1520
+ </button>
1521
+ </div>
1522
+ <button
1523
+ type="submit"
1524
+ [disabled]="loading"
1525
+ class="btn btn-primary btn-block">
1526
+ {{ loading ? 'Signing in...' : 'Sign in with Email' }}
1527
+ </button>
1528
+ </form>
1428
1529
 
1429
- <!-- Email/Password Form (if enabled) -->
1430
- @if (isProviderEnabled('emailPassword')) {
1431
- <form (ngSubmit)="onEmailLogin()" class="email-form">
1432
- <div class="form-group">
1433
- <input
1434
- [(ngModel)]="email"
1435
- name="email"
1436
- placeholder="Email"
1437
- type="email"
1438
- required
1439
- class="form-control">
1530
+ <!-- Divider -->
1531
+ @if (oauthProviders.length > 0) {
1532
+ <div class="divider">
1533
+ <span>OR</span>
1534
+ </div>
1535
+ }
1536
+ }
1537
+
1538
+ <!-- OAuth Providers -->
1539
+ @if (oauthProviders.length > 0 && (useOAuth || !isProviderEnabled('emailPassword'))) {
1540
+ <div class="oauth-buttons">
1541
+ @for (provider of oauthProviders; track provider) {
1542
+ <button
1543
+ type="button"
1544
+ (click)="onOAuthLogin(provider)"
1545
+ [disabled]="loading"
1546
+ class="btn btn-oauth btn-{{ provider }}">
1547
+ @if (getProviderIcon(provider)) {
1548
+ <span class="oauth-icon">
1549
+ {{ getProviderIcon(provider) }}
1550
+ </span>
1551
+ }
1552
+ {{ getProviderLabel(provider) }}
1553
+ </button>
1554
+ }
1440
1555
  </div>
1441
- <div class="form-group">
1442
- <input
1443
- [(ngModel)]="password"
1444
- name="password"
1445
- placeholder="Password"
1446
- type="password"
1447
- required
1448
- class="form-control">
1556
+
1557
+ <!-- Switch to Email/Password -->
1558
+ @if (isProviderEnabled('emailPassword') && oauthProviders.length > 0) {
1559
+ <div class="switch-method">
1560
+ <a href="#" (click)="toggleAuthMethod($event)">
1561
+ {{ useOAuth ? 'Use email/password instead' : 'Use OAuth instead' }}
1562
+ </a>
1563
+ </div>
1564
+ }
1565
+ }
1566
+
1567
+ <!-- Error Message -->
1568
+ @if (error) {
1569
+ <div class="error-message">
1570
+ {{ error }}
1449
1571
  </div>
1450
- <button
1451
- type="submit"
1452
- [disabled]="loading"
1453
- class="btn btn-primary btn-block">
1454
- {{ loading ? 'Signing in...' : getProviderLabel('emailPassword') }}
1455
- </button>
1456
- </form>
1572
+ }
1573
+
1574
+ <!-- Register Link -->
1575
+ @if (allowTenantCreation) {
1576
+ <div class="register-link">
1577
+ {{ registerLinkText }}
1578
+ <a href="#" (click)="onCreateTenantClick($event)">{{ registerLinkAction }}</a>
1579
+ </div>
1580
+ }
1581
+ } @else {
1582
+ <!-- Step 2: Tenant Selection -->
1583
+ <h2 class="login-title">{{ tenantSelectorTitle }}</h2>
1584
+
1585
+ @if (userName) {
1586
+ <div class="welcome-message">
1587
+ Welcome back, <strong>{{ userName }}</strong>!
1588
+ </div>
1589
+ }
1590
+
1591
+ <p class="selector-description">{{ tenantSelectorDescription }}</p>
1592
+
1593
+ <div class="tenant-list">
1594
+ @for (membership of memberships; track membership.tenant_id) {
1595
+ <div
1596
+ class="tenant-item"
1597
+ [class.selected]="selectedTenantId === membership.tenant_id"
1598
+ (click)="selectTenantItem(membership.tenant_id)">
1599
+ <div class="tenant-radio">
1600
+ <input
1601
+ type="radio"
1602
+ [checked]="selectedTenantId === membership.tenant_id"
1603
+ [name]="'tenant-' + membership.tenant_id"
1604
+ [id]="'tenant-' + membership.tenant_id">
1605
+ </div>
1606
+ <div class="tenant-info">
1607
+ <div class="tenant-name">{{ membership.name }}</div>
1608
+ <div class="tenant-meta">
1609
+ <span class="tenant-role">{{ formatRole(membership.role) }}</span>
1610
+ @if (membership.last_accessed) {
1611
+ <span class="tenant-separator">·</span>
1612
+ <span class="tenant-last-accessed">
1613
+ Last accessed {{ formatLastAccessed(membership.last_accessed) }}
1614
+ </span>
1615
+ }
1616
+ </div>
1617
+ </div>
1618
+ </div>
1619
+ }
1620
+ </div>
1621
+
1622
+ <button
1623
+ type="button"
1624
+ (click)="onContinueWithTenant()"
1625
+ [disabled]="!selectedTenantId || loading"
1626
+ class="btn btn-primary btn-block">
1627
+ {{ loading ? 'Loading...' : continueButtonText }}
1628
+ </button>
1629
+
1630
+ <!-- Error Message -->
1631
+ @if (error) {
1632
+ <div class="error-message">
1633
+ {{ error }}
1634
+ </div>
1635
+ }
1636
+
1637
+ <!-- Create New Tenant Link -->
1638
+ @if (allowTenantCreation) {
1639
+ <div class="create-tenant-link">
1640
+ {{ createTenantLinkText }}
1641
+ <a href="#" (click)="onCreateTenantClick($event)">{{ createTenantLinkAction }}</a>
1642
+ </div>
1643
+ }
1457
1644
  }
1458
1645
 
1459
- <!-- Divider if both email and OAuth are present -->
1460
- @if (isProviderEnabled('emailPassword') && oauthProviders.length > 0) {
1461
- <div class="divider">
1462
- <span>OR</span>
1646
+ <!-- Loading Overlay -->
1647
+ @if (loading) {
1648
+ <div class="loading-overlay">
1649
+ <div class="spinner"></div>
1463
1650
  </div>
1464
1651
  }
1652
+ </div>
1653
+ `, isInline: true, styles: [".tenant-login-dialog{padding:24px;max-width:450px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.welcome-message{margin-bottom:16px;padding:12px;background:#e8f5e9;border-radius:4px;text-align:center;font-size:14px;color:#2e7d32}.selector-description{margin-bottom:20px;font-size:14px;color:#666;text-align:center}.email-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.tenant-list{margin-bottom:20px;display:flex;flex-direction:column;gap:12px}.tenant-item{display:flex;align-items:flex-start;gap:12px;padding:16px;border:2px solid #e0e0e0;border-radius:6px;cursor:pointer;transition:all .2s}.tenant-item:hover{border-color:#4285f4;background:#f8f9ff}.tenant-item.selected{border-color:#4285f4;background:#e8f0fe}.tenant-radio{flex-shrink:0;padding-top:2px}.tenant-radio input[type=radio]{width:18px;height:18px;cursor:pointer}.tenant-info{flex:1}.tenant-name{font-size:16px;font-weight:500;color:#333;margin-bottom:4px}.tenant-meta{font-size:13px;color:#666}.tenant-role{font-weight:500;color:#4285f4}.tenant-separator{margin:0 6px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}.create-tenant-link{margin-top:16px;padding-top:16px;border-top:1px solid #e0e0e0;text-align:center;font-size:14px;color:#666}.create-tenant-link a{color:#4285f4;text-decoration:none;font-weight:500}.create-tenant-link a:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
1654
+ }
1655
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantLoginComponent, decorators: [{
1656
+ type: Component,
1657
+ args: [{ selector: 'lib-tenant-login', standalone: true, imports: [CommonModule, FormsModule], template: `
1658
+ <div class="tenant-login-dialog">
1659
+ @if (!showingTenantSelector) {
1660
+ <!-- Step 1: Authentication -->
1661
+ <h2 class="login-title">{{ title }}</h2>
1465
1662
 
1466
- <!-- OAuth Providers -->
1467
- @if (oauthProviders.length > 0) {
1468
- <div class="oauth-buttons">
1469
- @for (provider of oauthProviders; track provider) {
1663
+ <!-- Email/Password Form (if enabled) -->
1664
+ @if (isProviderEnabled('emailPassword') && !useOAuth) {
1665
+ <form (ngSubmit)="onEmailLogin()" class="email-form">
1666
+ <div class="form-group">
1667
+ <input
1668
+ [(ngModel)]="email"
1669
+ name="email"
1670
+ placeholder="Email"
1671
+ type="email"
1672
+ required
1673
+ class="form-control">
1674
+ </div>
1675
+ <div class="form-group password-group">
1676
+ <input
1677
+ [(ngModel)]="password"
1678
+ name="password"
1679
+ placeholder="Password"
1680
+ [type]="showPassword ? 'text' : 'password'"
1681
+ required
1682
+ class="form-control password-input">
1683
+ <button
1684
+ type="button"
1685
+ class="password-toggle"
1686
+ (click)="showPassword = !showPassword"
1687
+ [attr.aria-label]="showPassword ? 'Hide password' : 'Show password'">
1688
+ {{ showPassword ? '👁️' : '👁️‍🗨️' }}
1689
+ </button>
1690
+ </div>
1470
1691
  <button
1471
- (click)="onOAuthLogin(provider)"
1692
+ type="submit"
1472
1693
  [disabled]="loading"
1473
- class="btn btn-oauth btn-{{ provider }}">
1474
- @if (getProviderIcon(provider)) {
1475
- <span class="oauth-icon">
1476
- {{ getProviderIcon(provider) }}
1477
- </span>
1478
- }
1479
- {{ getProviderLabel(provider) }}
1694
+ class="btn btn-primary btn-block">
1695
+ {{ loading ? 'Signing in...' : 'Sign in with Email' }}
1480
1696
  </button>
1697
+ </form>
1698
+
1699
+ <!-- Divider -->
1700
+ @if (oauthProviders.length > 0) {
1701
+ <div class="divider">
1702
+ <span>OR</span>
1703
+ </div>
1481
1704
  }
1482
- </div>
1483
- }
1705
+ }
1484
1706
 
1485
- <!-- Error Message -->
1486
- @if (error) {
1487
- <div class="error-message">
1488
- {{ error }}
1707
+ <!-- OAuth Providers -->
1708
+ @if (oauthProviders.length > 0 && (useOAuth || !isProviderEnabled('emailPassword'))) {
1709
+ <div class="oauth-buttons">
1710
+ @for (provider of oauthProviders; track provider) {
1711
+ <button
1712
+ type="button"
1713
+ (click)="onOAuthLogin(provider)"
1714
+ [disabled]="loading"
1715
+ class="btn btn-oauth btn-{{ provider }}">
1716
+ @if (getProviderIcon(provider)) {
1717
+ <span class="oauth-icon">
1718
+ {{ getProviderIcon(provider) }}
1719
+ </span>
1720
+ }
1721
+ {{ getProviderLabel(provider) }}
1722
+ </button>
1723
+ }
1724
+ </div>
1725
+
1726
+ <!-- Switch to Email/Password -->
1727
+ @if (isProviderEnabled('emailPassword') && oauthProviders.length > 0) {
1728
+ <div class="switch-method">
1729
+ <a href="#" (click)="toggleAuthMethod($event)">
1730
+ {{ useOAuth ? 'Use email/password instead' : 'Use OAuth instead' }}
1731
+ </a>
1732
+ </div>
1733
+ }
1734
+ }
1735
+
1736
+ <!-- Error Message -->
1737
+ @if (error) {
1738
+ <div class="error-message">
1739
+ {{ error }}
1740
+ </div>
1741
+ }
1742
+
1743
+ <!-- Register Link -->
1744
+ @if (allowTenantCreation) {
1745
+ <div class="register-link">
1746
+ {{ registerLinkText }}
1747
+ <a href="#" (click)="onCreateTenantClick($event)">{{ registerLinkAction }}</a>
1748
+ </div>
1749
+ }
1750
+ } @else {
1751
+ <!-- Step 2: Tenant Selection -->
1752
+ <h2 class="login-title">{{ tenantSelectorTitle }}</h2>
1753
+
1754
+ @if (userName) {
1755
+ <div class="welcome-message">
1756
+ Welcome back, <strong>{{ userName }}</strong>!
1757
+ </div>
1758
+ }
1759
+
1760
+ <p class="selector-description">{{ tenantSelectorDescription }}</p>
1761
+
1762
+ <div class="tenant-list">
1763
+ @for (membership of memberships; track membership.tenant_id) {
1764
+ <div
1765
+ class="tenant-item"
1766
+ [class.selected]="selectedTenantId === membership.tenant_id"
1767
+ (click)="selectTenantItem(membership.tenant_id)">
1768
+ <div class="tenant-radio">
1769
+ <input
1770
+ type="radio"
1771
+ [checked]="selectedTenantId === membership.tenant_id"
1772
+ [name]="'tenant-' + membership.tenant_id"
1773
+ [id]="'tenant-' + membership.tenant_id">
1774
+ </div>
1775
+ <div class="tenant-info">
1776
+ <div class="tenant-name">{{ membership.name }}</div>
1777
+ <div class="tenant-meta">
1778
+ <span class="tenant-role">{{ formatRole(membership.role) }}</span>
1779
+ @if (membership.last_accessed) {
1780
+ <span class="tenant-separator">·</span>
1781
+ <span class="tenant-last-accessed">
1782
+ Last accessed {{ formatLastAccessed(membership.last_accessed) }}
1783
+ </span>
1784
+ }
1785
+ </div>
1786
+ </div>
1787
+ </div>
1788
+ }
1489
1789
  </div>
1790
+
1791
+ <button
1792
+ type="button"
1793
+ (click)="onContinueWithTenant()"
1794
+ [disabled]="!selectedTenantId || loading"
1795
+ class="btn btn-primary btn-block">
1796
+ {{ loading ? 'Loading...' : continueButtonText }}
1797
+ </button>
1798
+
1799
+ <!-- Error Message -->
1800
+ @if (error) {
1801
+ <div class="error-message">
1802
+ {{ error }}
1803
+ </div>
1804
+ }
1805
+
1806
+ <!-- Create New Tenant Link -->
1807
+ @if (allowTenantCreation) {
1808
+ <div class="create-tenant-link">
1809
+ {{ createTenantLinkText }}
1810
+ <a href="#" (click)="onCreateTenantClick($event)">{{ createTenantLinkAction }}</a>
1811
+ </div>
1812
+ }
1490
1813
  }
1491
1814
 
1492
- <!-- Loading State -->
1815
+ <!-- Loading Overlay -->
1493
1816
  @if (loading) {
1494
1817
  <div class="loading-overlay">
1495
1818
  <div class="spinner"></div>
1496
1819
  </div>
1497
1820
  }
1498
-
1499
- <!-- Register Link -->
1500
- <div class="register-link">
1501
- Don't have an account?
1502
- <a href="#" (click)="onRegisterClick($event)">Sign up</a>
1503
- </div>
1504
1821
  </div>
1505
- `, styles: [".login-dialog{padding:24px;max-width:400px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.email-form,.form-group{margin-bottom:16px}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}\n"] }]
1506
- }], ctorParameters: () => [{ type: AuthService }], propDecorators: { providers: [{
1822
+ `, styles: [".tenant-login-dialog{padding:24px;max-width:450px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.welcome-message{margin-bottom:16px;padding:12px;background:#e8f5e9;border-radius:4px;text-align:center;font-size:14px;color:#2e7d32}.selector-description{margin-bottom:20px;font-size:14px;color:#666;text-align:center}.email-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.tenant-list{margin-bottom:20px;display:flex;flex-direction:column;gap:12px}.tenant-item{display:flex;align-items:flex-start;gap:12px;padding:16px;border:2px solid #e0e0e0;border-radius:6px;cursor:pointer;transition:all .2s}.tenant-item:hover{border-color:#4285f4;background:#f8f9ff}.tenant-item.selected{border-color:#4285f4;background:#e8f0fe}.tenant-radio{flex-shrink:0;padding-top:2px}.tenant-radio input[type=radio]{width:18px;height:18px;cursor:pointer}.tenant-info{flex:1}.tenant-name{font-size:16px;font-weight:500;color:#333;margin-bottom:4px}.tenant-meta{font-size:13px;color:#666}.tenant-role{font-weight:500;color:#4285f4}.tenant-separator{margin:0 6px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}.create-tenant-link{margin-top:16px;padding-top:16px;border-top:1px solid #e0e0e0;text-align:center;font-size:14px;color:#666}.create-tenant-link a{color:#4285f4;text-decoration:none;font-weight:500}.create-tenant-link a:hover{text-decoration:underline}\n"] }]
1823
+ }], ctorParameters: () => [{ type: AuthService }], propDecorators: { title: [{
1824
+ type: Input
1825
+ }], providers: [{
1826
+ type: Input
1827
+ }], showTenantSelector: [{
1828
+ type: Input
1829
+ }], autoSelectSingleTenant: [{
1830
+ type: Input
1831
+ }], prefillEmail: [{
1832
+ type: Input
1833
+ }], allowTenantCreation: [{
1834
+ type: Input
1835
+ }], tenantSelectorTitle: [{
1836
+ type: Input
1837
+ }], tenantSelectorDescription: [{
1838
+ type: Input
1839
+ }], continueButtonText: [{
1840
+ type: Input
1841
+ }], registerLinkText: [{
1842
+ type: Input
1843
+ }], registerLinkAction: [{
1844
+ type: Input
1845
+ }], createTenantLinkText: [{
1846
+ type: Input
1847
+ }], createTenantLinkAction: [{
1507
1848
  type: Input
1849
+ }], tenantSelected: [{
1850
+ type: Output
1851
+ }], createTenant: [{
1852
+ type: Output
1508
1853
  }] } });
1509
1854
 
1510
1855
  class RegisterComponent {
1511
1856
  auth;
1857
+ environment;
1858
+ navigateToLogin = new EventEmitter();
1512
1859
  displayName = '';
1513
1860
  email = '';
1514
1861
  password = '';
@@ -1516,8 +1863,13 @@ class RegisterComponent {
1516
1863
  error = '';
1517
1864
  success = '';
1518
1865
  loading = false;
1519
- constructor(auth) {
1866
+ showAccountLinkPrompt = false;
1867
+ existingEmail = '';
1868
+ showPassword = false;
1869
+ showConfirmPassword = false;
1870
+ constructor(auth, environment) {
1520
1871
  this.auth = auth;
1872
+ this.environment = environment;
1521
1873
  }
1522
1874
  async onRegister() {
1523
1875
  // Reset messages
@@ -1544,14 +1896,40 @@ class RegisterComponent {
1544
1896
  }
1545
1897
  this.loading = true;
1546
1898
  try {
1547
- const result = await this.auth.register(this.email, this.password, this.displayName);
1548
- if (result.success) {
1549
- this.success = result.message || 'Account created successfully!';
1550
- // On success, parent component/dialog should close automatically via user$ subscription
1551
- // or navigate to email verification page
1899
+ // Direct API call to check for email already registered
1900
+ const response = await fetch(`${this.environment.accountsUrl}/api/auth/register`, {
1901
+ method: 'POST',
1902
+ headers: { 'Content-Type': 'application/json' },
1903
+ credentials: 'include',
1904
+ body: JSON.stringify({
1905
+ email: this.email,
1906
+ password: this.password,
1907
+ display_name: this.displayName,
1908
+ platform: this.environment.platformCode
1909
+ })
1910
+ });
1911
+ const data = await response.json();
1912
+ if (response.ok && data.identity_id) {
1913
+ // Registration successful - now login
1914
+ const loginResult = await this.auth.loginWithEmail(this.email, this.password);
1915
+ if (loginResult.success) {
1916
+ this.success = 'Account created successfully!';
1917
+ }
1918
+ else {
1919
+ this.success = 'Account created! Please sign in.';
1920
+ }
1552
1921
  }
1553
1922
  else {
1554
- this.error = result.message || 'Registration failed';
1923
+ // Check if email already registered
1924
+ if (data.error === 'Email already registered' || data.details?.includes('Email already registered')) {
1925
+ this.existingEmail = this.email;
1926
+ this.showAccountLinkPrompt = true;
1927
+ this.error = '';
1928
+ }
1929
+ else {
1930
+ // Other errors
1931
+ this.error = data.error || data.details || 'Registration failed';
1932
+ }
1555
1933
  }
1556
1934
  }
1557
1935
  catch (err) {
@@ -1563,16 +1941,50 @@ class RegisterComponent {
1563
1941
  }
1564
1942
  onLoginClick(event) {
1565
1943
  event.preventDefault();
1566
- // Platforms can override this or listen for a custom event
1567
- // For now, just emit a console message
1568
- console.log('Login clicked - platform should handle navigation');
1569
- }
1570
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RegisterComponent, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Component });
1571
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: RegisterComponent, isStandalone: true, selector: "lib-register", ngImport: i0, template: `
1944
+ this.navigateToLogin.emit('');
1945
+ }
1946
+ linkExistingAccount() {
1947
+ // User confirmed they want to link their existing account
1948
+ this.navigateToLogin.emit(this.existingEmail);
1949
+ }
1950
+ cancelLinking() {
1951
+ // User decided not to link - reset form
1952
+ this.showAccountLinkPrompt = false;
1953
+ this.existingEmail = '';
1954
+ this.email = '';
1955
+ this.password = '';
1956
+ this.confirmPassword = '';
1957
+ this.displayName = '';
1958
+ }
1959
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RegisterComponent, deps: [{ token: AuthService }, { token: MyEnvironmentModel }], target: i0.ɵɵFactoryTarget.Component });
1960
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: RegisterComponent, isStandalone: true, selector: "lib-register", outputs: { navigateToLogin: "navigateToLogin" }, ngImport: i0, template: `
1572
1961
  <div class="register-dialog">
1573
1962
  <h2 class="register-title">Create Account</h2>
1574
1963
 
1575
- <form (ngSubmit)="onRegister()" class="register-form">
1964
+ <!-- Account Link Prompt -->
1965
+ @if (showAccountLinkPrompt) {
1966
+ <div class="account-link-prompt">
1967
+ <div class="prompt-icon">🔗</div>
1968
+ <h3>Account Already Exists</h3>
1969
+ <p>
1970
+ You already have an account with <strong>{{ existingEmail }}</strong>,
1971
+ used on another ProGalaxy E-Labs platform.
1972
+ </p>
1973
+ <p>
1974
+ Would you like to use the same account to access this platform?
1975
+ </p>
1976
+ <div class="prompt-actions">
1977
+ <button type="button" class="btn btn-primary btn-block" (click)="linkExistingAccount()">
1978
+ Yes, Use My Existing Account
1979
+ </button>
1980
+ <button type="button" class="btn btn-secondary btn-block" (click)="cancelLinking()">
1981
+ No, Use Different Email
1982
+ </button>
1983
+ </div>
1984
+ </div>
1985
+ }
1986
+
1987
+ <form *ngIf="!showAccountLinkPrompt" (ngSubmit)="onRegister()" class="register-form">
1576
1988
  <div class="form-group">
1577
1989
  <label for="displayName">Full Name</label>
1578
1990
  <input
@@ -1597,30 +2009,44 @@ class RegisterComponent {
1597
2009
  class="form-control">
1598
2010
  </div>
1599
2011
 
1600
- <div class="form-group">
2012
+ <div class="form-group password-group">
1601
2013
  <label for="password">Password</label>
1602
2014
  <input
1603
2015
  id="password"
1604
2016
  [(ngModel)]="password"
1605
2017
  name="password"
1606
2018
  placeholder="Create a password"
1607
- type="password"
2019
+ [type]="showPassword ? 'text' : 'password'"
1608
2020
  required
1609
2021
  minlength="8"
1610
- class="form-control">
2022
+ class="form-control password-input">
2023
+ <button
2024
+ type="button"
2025
+ class="password-toggle"
2026
+ (click)="showPassword = !showPassword"
2027
+ [attr.aria-label]="showPassword ? 'Hide password' : 'Show password'">
2028
+ {{ showPassword ? '👁️' : '👁️‍🗨️' }}
2029
+ </button>
1611
2030
  <small class="form-hint">At least 8 characters</small>
1612
2031
  </div>
1613
2032
 
1614
- <div class="form-group">
2033
+ <div class="form-group password-group">
1615
2034
  <label for="confirmPassword">Confirm Password</label>
1616
2035
  <input
1617
2036
  id="confirmPassword"
1618
2037
  [(ngModel)]="confirmPassword"
1619
2038
  name="confirmPassword"
1620
2039
  placeholder="Confirm your password"
1621
- type="password"
2040
+ [type]="showConfirmPassword ? 'text' : 'password'"
1622
2041
  required
1623
- class="form-control">
2042
+ class="form-control password-input">
2043
+ <button
2044
+ type="button"
2045
+ class="password-toggle"
2046
+ (click)="showConfirmPassword = !showConfirmPassword"
2047
+ [attr.aria-label]="showConfirmPassword ? 'Hide password' : 'Show password'">
2048
+ {{ showConfirmPassword ? '👁️' : '👁️‍🗨️' }}
2049
+ </button>
1624
2050
  </div>
1625
2051
 
1626
2052
  <button
@@ -1632,7 +2058,7 @@ class RegisterComponent {
1632
2058
  </form>
1633
2059
 
1634
2060
  <!-- Error Message -->
1635
- @if (error) {
2061
+ @if (error && !showAccountLinkPrompt) {
1636
2062
  <div class="error-message">
1637
2063
  {{ error }}
1638
2064
  </div>
@@ -1653,12 +2079,12 @@ class RegisterComponent {
1653
2079
  }
1654
2080
 
1655
2081
  <!-- Login Link -->
1656
- <div class="login-link">
2082
+ <div *ngIf="!showAccountLinkPrompt" class="login-link">
1657
2083
  Already have an account?
1658
2084
  <a href="#" (click)="onLoginClick($event)">Sign in</a>
1659
2085
  </div>
1660
2086
  </div>
1661
- `, isInline: true, styles: [".register-dialog{padding:24px;max-width:400px;position:relative}.register-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.register-form,.form-group{margin-bottom:16px}.form-group label{display:block;margin-bottom:6px;font-size:14px;font-weight:500;color:#333}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.form-control:focus{outline:none;border-color:#4285f4}.form-hint{display:block;margin-top:4px;font-size:12px;color:#666}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.success-message{margin-top:16px;padding:12px;background:#efe;color:#3a3;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.login-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.login-link a{color:#4285f4;text-decoration:none}.login-link a:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
2087
+ `, isInline: true, styles: [".register-dialog{padding:24px;max-width:400px;position:relative}.register-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.register-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-group label{display:block;margin-bottom:6px;font-size:14px;font-weight:500;color:#333}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:38px;background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.form-hint{display:block;margin-top:4px;font-size:12px;color:#666}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.success-message{margin-top:16px;padding:12px;background:#efe;color:#3a3;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.login-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.login-link a{color:#4285f4;text-decoration:none}.login-link a:hover{text-decoration:underline}.account-link-prompt{background:#f8f9fa;border:2px solid #4285f4;border-radius:8px;padding:24px;margin-bottom:16px;text-align:center}.prompt-icon{font-size:48px;margin-bottom:12px}.account-link-prompt h3{margin:0 0 12px;color:#333;font-size:20px;font-weight:500}.account-link-prompt p{margin:8px 0;color:#555;font-size:14px;line-height:1.6}.prompt-actions{margin-top:20px;display:flex;flex-direction:column;gap:10px}.btn-secondary{background:#fff;color:#333;border:1px solid #ddd}.btn-secondary:hover:not(:disabled){background:#f8f9fa;border-color:#ccc}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2$1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
1662
2088
  }
1663
2089
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RegisterComponent, decorators: [{
1664
2090
  type: Component,
@@ -1666,7 +2092,30 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
1666
2092
  <div class="register-dialog">
1667
2093
  <h2 class="register-title">Create Account</h2>
1668
2094
 
1669
- <form (ngSubmit)="onRegister()" class="register-form">
2095
+ <!-- Account Link Prompt -->
2096
+ @if (showAccountLinkPrompt) {
2097
+ <div class="account-link-prompt">
2098
+ <div class="prompt-icon">🔗</div>
2099
+ <h3>Account Already Exists</h3>
2100
+ <p>
2101
+ You already have an account with <strong>{{ existingEmail }}</strong>,
2102
+ used on another ProGalaxy E-Labs platform.
2103
+ </p>
2104
+ <p>
2105
+ Would you like to use the same account to access this platform?
2106
+ </p>
2107
+ <div class="prompt-actions">
2108
+ <button type="button" class="btn btn-primary btn-block" (click)="linkExistingAccount()">
2109
+ Yes, Use My Existing Account
2110
+ </button>
2111
+ <button type="button" class="btn btn-secondary btn-block" (click)="cancelLinking()">
2112
+ No, Use Different Email
2113
+ </button>
2114
+ </div>
2115
+ </div>
2116
+ }
2117
+
2118
+ <form *ngIf="!showAccountLinkPrompt" (ngSubmit)="onRegister()" class="register-form">
1670
2119
  <div class="form-group">
1671
2120
  <label for="displayName">Full Name</label>
1672
2121
  <input
@@ -1691,30 +2140,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
1691
2140
  class="form-control">
1692
2141
  </div>
1693
2142
 
1694
- <div class="form-group">
2143
+ <div class="form-group password-group">
1695
2144
  <label for="password">Password</label>
1696
2145
  <input
1697
2146
  id="password"
1698
2147
  [(ngModel)]="password"
1699
2148
  name="password"
1700
2149
  placeholder="Create a password"
1701
- type="password"
2150
+ [type]="showPassword ? 'text' : 'password'"
1702
2151
  required
1703
2152
  minlength="8"
1704
- class="form-control">
2153
+ class="form-control password-input">
2154
+ <button
2155
+ type="button"
2156
+ class="password-toggle"
2157
+ (click)="showPassword = !showPassword"
2158
+ [attr.aria-label]="showPassword ? 'Hide password' : 'Show password'">
2159
+ {{ showPassword ? '👁️' : '👁️‍🗨️' }}
2160
+ </button>
1705
2161
  <small class="form-hint">At least 8 characters</small>
1706
2162
  </div>
1707
2163
 
1708
- <div class="form-group">
2164
+ <div class="form-group password-group">
1709
2165
  <label for="confirmPassword">Confirm Password</label>
1710
2166
  <input
1711
2167
  id="confirmPassword"
1712
2168
  [(ngModel)]="confirmPassword"
1713
2169
  name="confirmPassword"
1714
2170
  placeholder="Confirm your password"
1715
- type="password"
2171
+ [type]="showConfirmPassword ? 'text' : 'password'"
1716
2172
  required
1717
- class="form-control">
2173
+ class="form-control password-input">
2174
+ <button
2175
+ type="button"
2176
+ class="password-toggle"
2177
+ (click)="showConfirmPassword = !showConfirmPassword"
2178
+ [attr.aria-label]="showConfirmPassword ? 'Hide password' : 'Show password'">
2179
+ {{ showConfirmPassword ? '👁️' : '👁️‍🗨️' }}
2180
+ </button>
1718
2181
  </div>
1719
2182
 
1720
2183
  <button
@@ -1726,7 +2189,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
1726
2189
  </form>
1727
2190
 
1728
2191
  <!-- Error Message -->
1729
- @if (error) {
2192
+ @if (error && !showAccountLinkPrompt) {
1730
2193
  <div class="error-message">
1731
2194
  {{ error }}
1732
2195
  </div>
@@ -1747,60 +2210,157 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
1747
2210
  }
1748
2211
 
1749
2212
  <!-- Login Link -->
1750
- <div class="login-link">
2213
+ <div *ngIf="!showAccountLinkPrompt" class="login-link">
1751
2214
  Already have an account?
1752
2215
  <a href="#" (click)="onLoginClick($event)">Sign in</a>
1753
2216
  </div>
1754
2217
  </div>
1755
- `, styles: [".register-dialog{padding:24px;max-width:400px;position:relative}.register-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.register-form,.form-group{margin-bottom:16px}.form-group label{display:block;margin-bottom:6px;font-size:14px;font-weight:500;color:#333}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.form-control:focus{outline:none;border-color:#4285f4}.form-hint{display:block;margin-top:4px;font-size:12px;color:#666}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.success-message{margin-top:16px;padding:12px;background:#efe;color:#3a3;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.login-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.login-link a{color:#4285f4;text-decoration:none}.login-link a:hover{text-decoration:underline}\n"] }]
1756
- }], ctorParameters: () => [{ type: AuthService }] });
2218
+ `, styles: [".register-dialog{padding:24px;max-width:400px;position:relative}.register-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.register-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-group label{display:block;margin-bottom:6px;font-size:14px;font-weight:500;color:#333}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:38px;background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.form-hint{display:block;margin-top:4px;font-size:12px;color:#666}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.success-message{margin-top:16px;padding:12px;background:#efe;color:#3a3;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.login-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.login-link a{color:#4285f4;text-decoration:none}.login-link a:hover{text-decoration:underline}.account-link-prompt{background:#f8f9fa;border:2px solid #4285f4;border-radius:8px;padding:24px;margin-bottom:16px;text-align:center}.prompt-icon{font-size:48px;margin-bottom:12px}.account-link-prompt h3{margin:0 0 12px;color:#333;font-size:20px;font-weight:500}.account-link-prompt p{margin:8px 0;color:#555;font-size:14px;line-height:1.6}.prompt-actions{margin-top:20px;display:flex;flex-direction:column;gap:10px}.btn-secondary{background:#fff;color:#333;border:1px solid #ddd}.btn-secondary:hover:not(:disabled){background:#f8f9fa;border-color:#ccc}\n"] }]
2219
+ }], ctorParameters: () => [{ type: AuthService }, { type: MyEnvironmentModel, decorators: [{
2220
+ type: Inject,
2221
+ args: [MyEnvironmentModel]
2222
+ }] }], propDecorators: { navigateToLogin: [{
2223
+ type: Output
2224
+ }] } });
1757
2225
 
1758
- class TenantLoginComponent {
2226
+ class AuthPageComponent {
2227
+ environment;
2228
+ providers = ['google', 'emailPassword'];
2229
+ authenticated = new EventEmitter();
2230
+ mode = 'login';
2231
+ appName = '';
2232
+ logo;
2233
+ subtitle;
2234
+ gradientStyle = 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)';
2235
+ constructor(environment) {
2236
+ this.environment = environment;
2237
+ }
2238
+ ngOnInit() {
2239
+ const branding = this.environment.branding;
2240
+ if (branding) {
2241
+ this.appName = branding.appName || 'Sign In';
2242
+ this.logo = branding.logo;
2243
+ this.subtitle = branding.subtitle;
2244
+ if (branding.gradientStart && branding.gradientEnd) {
2245
+ this.gradientStyle = `linear-gradient(135deg, ${branding.gradientStart} 0%, ${branding.gradientEnd} 100%)`;
2246
+ }
2247
+ else if (branding.primaryColor) {
2248
+ const color = branding.primaryColor;
2249
+ this.gradientStyle = `linear-gradient(135deg, ${color} 0%, ${this.adjustColor(color, -20)} 100%)`;
2250
+ }
2251
+ }
2252
+ else {
2253
+ this.appName = 'Sign In';
2254
+ }
2255
+ }
2256
+ onAuthenticated(event) {
2257
+ this.authenticated.emit(event);
2258
+ }
2259
+ /**
2260
+ * Adjust color brightness (simple implementation)
2261
+ * @param color Hex color (e.g., '#667eea')
2262
+ * @param percent Percentage to darken (negative) or lighten (positive)
2263
+ */
2264
+ adjustColor(color, percent) {
2265
+ const num = parseInt(color.replace('#', ''), 16);
2266
+ const amt = Math.round(2.55 * percent);
2267
+ const R = (num >> 16) + amt;
2268
+ const G = (num >> 8 & 0x00FF) + amt;
2269
+ const B = (num & 0x0000FF) + amt;
2270
+ return '#' + (0x1000000 + (R < 255 ? R < 1 ? 0 : R : 255) * 0x10000 +
2271
+ (G < 255 ? G < 1 ? 0 : G : 255) * 0x100 +
2272
+ (B < 255 ? B < 1 ? 0 : B : 255))
2273
+ .toString(16).slice(1);
2274
+ }
2275
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AuthPageComponent, deps: [{ token: MyEnvironmentModel }], target: i0.ɵɵFactoryTarget.Component });
2276
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: AuthPageComponent, isStandalone: true, selector: "lib-auth-page", inputs: { providers: "providers" }, outputs: { authenticated: "authenticated" }, ngImport: i0, template: `
2277
+ <div class="auth-container" [style.background]="gradientStyle">
2278
+ <div class="auth-card">
2279
+ @if (logo) {
2280
+ <img [src]="logo" [alt]="appName + ' logo'" class="logo">
2281
+ }
2282
+ <h1 class="app-name">{{ appName }}</h1>
2283
+ @if (subtitle) {
2284
+ <p class="subtitle">{{ subtitle }}</p>
2285
+ }
2286
+
2287
+ @if (mode === 'login') {
2288
+ <lib-tenant-login
2289
+ [providers]="providers"
2290
+ [allowTenantCreation]="false"
2291
+ (tenantSelected)="onAuthenticated($event)"
2292
+ (createTenant)="mode = 'register'">
2293
+ </lib-tenant-login>
2294
+ } @else {
2295
+ <lib-register
2296
+ (navigateToLogin)="mode = 'login'">
2297
+ </lib-register>
2298
+ }
2299
+ </div>
2300
+ </div>
2301
+ `, isInline: true, styles: [".auth-container{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;background:linear-gradient(135deg,#667eea,#764ba2)}.auth-card{background:#fff;border-radius:12px;box-shadow:0 10px 40px #0000001a;padding:40px;width:100%;max-width:480px}.logo{display:block;max-width:200px;max-height:80px;margin:0 auto 24px}.app-name{margin:0 0 12px;font-size:28px;font-weight:600;text-align:center;color:#1a202c}.subtitle{margin:0 0 32px;font-size:16px;text-align:center;color:#718096}:host ::ng-deep .tenant-login-dialog,:host ::ng-deep .register-dialog{padding:0;max-width:none}:host ::ng-deep .login-title,:host ::ng-deep .register-title{display:none}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: TenantLoginComponent, selector: "lib-tenant-login", inputs: ["title", "providers", "showTenantSelector", "autoSelectSingleTenant", "prefillEmail", "allowTenantCreation", "tenantSelectorTitle", "tenantSelectorDescription", "continueButtonText", "registerLinkText", "registerLinkAction", "createTenantLinkText", "createTenantLinkAction"], outputs: ["tenantSelected", "createTenant"] }, { kind: "component", type: RegisterComponent, selector: "lib-register", outputs: ["navigateToLogin"] }] });
2302
+ }
2303
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AuthPageComponent, decorators: [{
2304
+ type: Component,
2305
+ args: [{ selector: 'lib-auth-page', standalone: true, imports: [CommonModule, TenantLoginComponent, RegisterComponent], template: `
2306
+ <div class="auth-container" [style.background]="gradientStyle">
2307
+ <div class="auth-card">
2308
+ @if (logo) {
2309
+ <img [src]="logo" [alt]="appName + ' logo'" class="logo">
2310
+ }
2311
+ <h1 class="app-name">{{ appName }}</h1>
2312
+ @if (subtitle) {
2313
+ <p class="subtitle">{{ subtitle }}</p>
2314
+ }
2315
+
2316
+ @if (mode === 'login') {
2317
+ <lib-tenant-login
2318
+ [providers]="providers"
2319
+ [allowTenantCreation]="false"
2320
+ (tenantSelected)="onAuthenticated($event)"
2321
+ (createTenant)="mode = 'register'">
2322
+ </lib-tenant-login>
2323
+ } @else {
2324
+ <lib-register
2325
+ (navigateToLogin)="mode = 'login'">
2326
+ </lib-register>
2327
+ }
2328
+ </div>
2329
+ </div>
2330
+ `, styles: [".auth-container{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:20px;background:linear-gradient(135deg,#667eea,#764ba2)}.auth-card{background:#fff;border-radius:12px;box-shadow:0 10px 40px #0000001a;padding:40px;width:100%;max-width:480px}.logo{display:block;max-width:200px;max-height:80px;margin:0 auto 24px}.app-name{margin:0 0 12px;font-size:28px;font-weight:600;text-align:center;color:#1a202c}.subtitle{margin:0 0 32px;font-size:16px;text-align:center;color:#718096}:host ::ng-deep .tenant-login-dialog,:host ::ng-deep .register-dialog{padding:0;max-width:none}:host ::ng-deep .login-title,:host ::ng-deep .register-title{display:none}\n"] }]
2331
+ }], ctorParameters: () => [{ type: MyEnvironmentModel, decorators: [{
2332
+ type: Inject,
2333
+ args: [MyEnvironmentModel]
2334
+ }] }], propDecorators: { providers: [{
2335
+ type: Input
2336
+ }], authenticated: [{
2337
+ type: Output
2338
+ }] } });
2339
+
2340
+ class LoginDialogComponent {
1759
2341
  auth;
1760
- // Component Configuration
1761
- title = 'Sign In';
1762
- providers = ['google'];
1763
- showTenantSelector = true;
1764
- autoSelectSingleTenant = true;
1765
- allowTenantCreation = true;
1766
- // Tenant Selector Labels
1767
- tenantSelectorTitle = 'Select Organization';
1768
- tenantSelectorDescription = 'Choose which organization you want to access:';
1769
- continueButtonText = 'Continue';
1770
- // Link Labels
1771
- registerLinkText = "Don't have an account?";
1772
- registerLinkAction = 'Sign up';
1773
- createTenantLinkText = "Don't see your organization?";
1774
- createTenantLinkAction = 'Create New Organization';
1775
- // Outputs
1776
- tenantSelected = new EventEmitter();
1777
- createTenant = new EventEmitter();
1778
- // Form Fields
2342
+ /**
2343
+ * REQUIRED: Which authentication providers to show in this dialog
2344
+ * @example ['google', 'linkedin', 'emailPassword']
2345
+ */
2346
+ providers = [];
1779
2347
  email = '';
1780
2348
  password = '';
1781
- // State
1782
2349
  error = '';
1783
2350
  loading = false;
1784
- useOAuth = true;
2351
+ showPassword = false;
1785
2352
  oauthProviders = [];
1786
- // Tenant Selection State
1787
- showingTenantSelector = false;
1788
- memberships = [];
1789
- selectedTenantId = null;
1790
- userName = '';
1791
2353
  constructor(auth) {
1792
2354
  this.auth = auth;
1793
2355
  }
1794
2356
  ngOnInit() {
1795
2357
  if (!this.providers || this.providers.length === 0) {
1796
- this.error = 'Configuration Error: No authentication providers specified.';
1797
- throw new Error('TenantLoginComponent requires providers input.');
1798
- }
1799
- this.oauthProviders = this.providers.filter(p => p !== 'emailPassword');
1800
- // If only emailPassword is available, use it by default
1801
- if (this.oauthProviders.length === 0 && this.isProviderEnabled('emailPassword')) {
1802
- this.useOAuth = false;
2358
+ this.error = 'Configuration Error: No authentication providers specified. Please pass providers to LoginDialogComponent.';
2359
+ throw new Error('LoginDialogComponent requires providers input. Example: dialogRef.componentInstance.providers = [\'google\', \'emailPassword\']');
1803
2360
  }
2361
+ // Get OAuth providers (excluding emailPassword)
2362
+ this.oauthProviders = this.providers
2363
+ .filter(p => p !== 'emailPassword');
1804
2364
  }
1805
2365
  isProviderEnabled(provider) {
1806
2366
  return this.providers.includes(provider);
@@ -1817,514 +2377,235 @@ class TenantLoginComponent {
1817
2377
  return labels[provider];
1818
2378
  }
1819
2379
  getProviderIcon(provider) {
1820
- return undefined;
1821
- }
1822
- toggleAuthMethod(event) {
1823
- event.preventDefault();
1824
- this.useOAuth = !this.useOAuth;
1825
- this.error = '';
1826
- }
1827
- async onEmailLogin() {
1828
- if (!this.email || !this.password) {
1829
- this.error = 'Please enter email and password';
1830
- return;
1831
- }
1832
- this.loading = true;
1833
- this.error = '';
1834
- try {
1835
- const result = await this.auth.loginWithEmail(this.email, this.password);
1836
- if (!result.success) {
1837
- this.error = result.message || 'Login failed';
1838
- return;
1839
- }
1840
- // Authentication successful, now handle tenant selection
1841
- await this.handlePostAuthFlow();
1842
- }
1843
- catch (err) {
1844
- this.error = err.message || 'An unexpected error occurred';
1845
- }
1846
- finally {
1847
- this.loading = false;
1848
- }
1849
- }
1850
- async onOAuthLogin(provider) {
1851
- this.loading = true;
1852
- this.error = '';
1853
- try {
1854
- const result = await this.auth.loginWithProvider(provider);
1855
- if (!result.success) {
1856
- this.error = result.message || 'OAuth login failed';
1857
- return;
1858
- }
1859
- // Authentication successful, now handle tenant selection
1860
- await this.handlePostAuthFlow();
1861
- }
1862
- catch (err) {
1863
- this.error = err.message || 'An unexpected error occurred';
1864
- }
1865
- finally {
1866
- this.loading = false;
1867
- }
1868
- }
1869
- async handlePostAuthFlow() {
1870
- if (!this.showTenantSelector) {
1871
- // Tenant selection is disabled, emit event immediately
1872
- this.tenantSelected.emit({
1873
- tenantId: '',
1874
- tenantSlug: '',
1875
- role: ''
1876
- });
1877
- return;
1878
- }
1879
- // Fetch user's tenant memberships
1880
- this.loading = true;
1881
- try {
1882
- const result = await this.auth.getTenantMemberships();
1883
- if (!result.memberships || result.memberships.length === 0) {
1884
- // User has no tenants, prompt to create one
1885
- this.error = 'You are not a member of any organization. Please create one.';
1886
- if (this.allowTenantCreation) {
1887
- setTimeout(() => this.createTenant.emit(), 2000);
1888
- }
1889
- return;
1890
- }
1891
- this.memberships = result.memberships;
1892
- // Get user name if available
1893
- const currentUser = this.auth.getCurrentUser();
1894
- if (currentUser) {
1895
- this.userName = currentUser.display_name || currentUser.email;
1896
- }
1897
- // Auto-select if user has only one tenant
1898
- if (this.memberships.length === 1 && this.autoSelectSingleTenant) {
1899
- await this.selectAndContinue(this.memberships[0]);
1900
- }
1901
- else {
1902
- // Show tenant selector
1903
- this.showingTenantSelector = true;
1904
- }
1905
- }
1906
- catch (err) {
1907
- this.error = err.message || 'Failed to load organizations';
1908
- }
1909
- finally {
1910
- this.loading = false;
1911
- }
1912
- }
1913
- selectTenantItem(tenantId) {
1914
- this.selectedTenantId = tenantId;
1915
- }
1916
- async onContinueWithTenant() {
1917
- if (!this.selectedTenantId) {
1918
- this.error = 'Please select an organization';
1919
- return;
1920
- }
1921
- const membership = this.memberships.find(m => m.tenant_id === this.selectedTenantId);
1922
- if (!membership) {
1923
- this.error = 'Selected organization not found';
2380
+ // Platforms can customize icons via CSS classes: .btn-google, .btn-linkedin, etc.
2381
+ return undefined;
2382
+ }
2383
+ async onEmailLogin() {
2384
+ if (!this.email || !this.password) {
2385
+ this.error = 'Please enter email and password';
1924
2386
  return;
1925
2387
  }
1926
- await this.selectAndContinue(membership);
1927
- }
1928
- async selectAndContinue(membership) {
1929
2388
  this.loading = true;
1930
2389
  this.error = '';
1931
2390
  try {
1932
- const result = await this.auth.selectTenant(membership.tenant_id);
2391
+ const result = await this.auth.loginWithEmail(this.email, this.password);
1933
2392
  if (!result.success) {
1934
- this.error = result.message || 'Failed to select organization';
1935
- return;
2393
+ this.error = result.message || 'Login failed';
1936
2394
  }
1937
- // Emit tenant selected event
1938
- this.tenantSelected.emit({
1939
- tenantId: membership.tenant_id,
1940
- tenantSlug: membership.slug,
1941
- role: membership.role
1942
- });
2395
+ // On success, parent component/dialog should close automatically via user$ subscription
1943
2396
  }
1944
2397
  catch (err) {
1945
- this.error = err.message || 'An unexpected error occurred';
2398
+ this.error = 'An unexpected error occurred';
1946
2399
  }
1947
2400
  finally {
1948
2401
  this.loading = false;
1949
2402
  }
1950
2403
  }
1951
- formatRole(role) {
1952
- return role.charAt(0).toUpperCase() + role.slice(1);
1953
- }
1954
- formatLastAccessed(dateStr) {
2404
+ async onOAuthLogin(provider) {
2405
+ this.loading = true;
2406
+ this.error = '';
1955
2407
  try {
1956
- const date = new Date(dateStr);
1957
- const now = new Date();
1958
- const diffMs = now.getTime() - date.getTime();
1959
- const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
1960
- if (diffDays === 0)
1961
- return 'today';
1962
- if (diffDays === 1)
1963
- return 'yesterday';
1964
- if (diffDays < 7)
1965
- return `${diffDays} days ago`;
1966
- if (diffDays < 30)
1967
- return `${Math.floor(diffDays / 7)} weeks ago`;
1968
- return `${Math.floor(diffDays / 30)} months ago`;
2408
+ const result = await this.auth.loginWithProvider(provider);
2409
+ if (!result.success) {
2410
+ this.error = result.message || 'OAuth login failed';
2411
+ }
2412
+ // On success, parent component/dialog should close automatically via user$ subscription
1969
2413
  }
1970
- catch {
1971
- return dateStr;
2414
+ catch (err) {
2415
+ this.error = 'An unexpected error occurred';
2416
+ }
2417
+ finally {
2418
+ this.loading = false;
1972
2419
  }
1973
2420
  }
1974
- onCreateTenantClick(event) {
2421
+ onRegisterClick(event) {
1975
2422
  event.preventDefault();
1976
- this.createTenant.emit();
2423
+ // Platforms can override this or listen for a custom event
2424
+ // For now, just emit a console message
2425
+ console.log('Register clicked - platform should handle navigation');
1977
2426
  }
1978
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantLoginComponent, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Component });
1979
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: TenantLoginComponent, isStandalone: true, selector: "lib-tenant-login", inputs: { title: "title", providers: "providers", showTenantSelector: "showTenantSelector", autoSelectSingleTenant: "autoSelectSingleTenant", allowTenantCreation: "allowTenantCreation", tenantSelectorTitle: "tenantSelectorTitle", tenantSelectorDescription: "tenantSelectorDescription", continueButtonText: "continueButtonText", registerLinkText: "registerLinkText", registerLinkAction: "registerLinkAction", createTenantLinkText: "createTenantLinkText", createTenantLinkAction: "createTenantLinkAction" }, outputs: { tenantSelected: "tenantSelected", createTenant: "createTenant" }, ngImport: i0, template: `
1980
- <div class="tenant-login-dialog">
1981
- @if (!showingTenantSelector) {
1982
- <!-- Step 1: Authentication -->
1983
- <h2 class="login-title">{{ title }}</h2>
2427
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: LoginDialogComponent, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Component });
2428
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: LoginDialogComponent, isStandalone: true, selector: "lib-login-dialog", inputs: { providers: "providers" }, ngImport: i0, template: `
2429
+ <div class="login-dialog">
2430
+ <h2 class="login-title">Sign In</h2>
1984
2431
 
1985
- <!-- Email/Password Form (if enabled) -->
1986
- @if (isProviderEnabled('emailPassword') && !useOAuth) {
1987
- <form (ngSubmit)="onEmailLogin()" class="email-form">
1988
- <div class="form-group">
1989
- <input
1990
- [(ngModel)]="email"
1991
- name="email"
1992
- placeholder="Email"
1993
- type="email"
1994
- required
1995
- class="form-control">
1996
- </div>
1997
- <div class="form-group">
1998
- <input
1999
- [(ngModel)]="password"
2000
- name="password"
2001
- placeholder="Password"
2002
- type="password"
2003
- required
2004
- class="form-control">
2005
- </div>
2432
+ <!-- Email/Password Form (if enabled) -->
2433
+ @if (isProviderEnabled('emailPassword')) {
2434
+ <form (ngSubmit)="onEmailLogin()" class="email-form">
2435
+ <div class="form-group">
2436
+ <input
2437
+ [(ngModel)]="email"
2438
+ name="email"
2439
+ placeholder="Email"
2440
+ type="email"
2441
+ required
2442
+ class="form-control">
2443
+ </div>
2444
+ <div class="form-group password-group">
2445
+ <input
2446
+ [(ngModel)]="password"
2447
+ name="password"
2448
+ placeholder="Password"
2449
+ [type]="showPassword ? 'text' : 'password'"
2450
+ required
2451
+ class="form-control password-input">
2006
2452
  <button
2007
- type="submit"
2008
- [disabled]="loading"
2009
- class="btn btn-primary btn-block">
2010
- {{ loading ? 'Signing in...' : 'Sign in with Email' }}
2453
+ type="button"
2454
+ class="password-toggle"
2455
+ (click)="showPassword = !showPassword"
2456
+ [attr.aria-label]="showPassword ? 'Hide password' : 'Show password'">
2457
+ {{ showPassword ? '👁️' : '👁️‍🗨️' }}
2011
2458
  </button>
2012
- </form>
2013
-
2014
- <!-- Divider -->
2015
- @if (oauthProviders.length > 0) {
2016
- <div class="divider">
2017
- <span>OR</span>
2018
- </div>
2019
- }
2020
- }
2021
-
2022
- <!-- OAuth Providers -->
2023
- @if (oauthProviders.length > 0 && (useOAuth || !isProviderEnabled('emailPassword'))) {
2024
- <div class="oauth-buttons">
2025
- @for (provider of oauthProviders; track provider) {
2026
- <button
2027
- type="button"
2028
- (click)="onOAuthLogin(provider)"
2029
- [disabled]="loading"
2030
- class="btn btn-oauth btn-{{ provider }}">
2031
- @if (getProviderIcon(provider)) {
2032
- <span class="oauth-icon">
2033
- {{ getProviderIcon(provider) }}
2034
- </span>
2035
- }
2036
- {{ getProviderLabel(provider) }}
2037
- </button>
2038
- }
2039
- </div>
2040
-
2041
- <!-- Switch to Email/Password -->
2042
- @if (isProviderEnabled('emailPassword') && oauthProviders.length > 0) {
2043
- <div class="switch-method">
2044
- <a href="#" (click)="toggleAuthMethod($event)">
2045
- {{ useOAuth ? 'Use email/password instead' : 'Use OAuth instead' }}
2046
- </a>
2047
- </div>
2048
- }
2049
- }
2050
-
2051
- <!-- Error Message -->
2052
- @if (error) {
2053
- <div class="error-message">
2054
- {{ error }}
2055
- </div>
2056
- }
2057
-
2058
- <!-- Register Link -->
2059
- @if (allowTenantCreation) {
2060
- <div class="register-link">
2061
- {{ registerLinkText }}
2062
- <a href="#" (click)="onCreateTenantClick($event)">{{ registerLinkAction }}</a>
2063
- </div>
2064
- }
2065
- } @else {
2066
- <!-- Step 2: Tenant Selection -->
2067
- <h2 class="login-title">{{ tenantSelectorTitle }}</h2>
2068
-
2069
- @if (userName) {
2070
- <div class="welcome-message">
2071
- Welcome back, <strong>{{ userName }}</strong>!
2072
2459
  </div>
2073
- }
2460
+ <button
2461
+ type="submit"
2462
+ [disabled]="loading"
2463
+ class="btn btn-primary btn-block">
2464
+ {{ loading ? 'Signing in...' : getProviderLabel('emailPassword') }}
2465
+ </button>
2466
+ </form>
2467
+ }
2074
2468
 
2075
- <p class="selector-description">{{ tenantSelectorDescription }}</p>
2469
+ <!-- Divider if both email and OAuth are present -->
2470
+ @if (isProviderEnabled('emailPassword') && oauthProviders.length > 0) {
2471
+ <div class="divider">
2472
+ <span>OR</span>
2473
+ </div>
2474
+ }
2076
2475
 
2077
- <div class="tenant-list">
2078
- @for (membership of memberships; track membership.tenant_id) {
2079
- <div
2080
- class="tenant-item"
2081
- [class.selected]="selectedTenantId === membership.tenant_id"
2082
- (click)="selectTenantItem(membership.tenant_id)">
2083
- <div class="tenant-radio">
2084
- <input
2085
- type="radio"
2086
- [checked]="selectedTenantId === membership.tenant_id"
2087
- [name]="'tenant-' + membership.tenant_id"
2088
- [id]="'tenant-' + membership.tenant_id">
2089
- </div>
2090
- <div class="tenant-info">
2091
- <div class="tenant-name">{{ membership.name }}</div>
2092
- <div class="tenant-meta">
2093
- <span class="tenant-role">{{ formatRole(membership.role) }}</span>
2094
- @if (membership.last_accessed) {
2095
- <span class="tenant-separator">·</span>
2096
- <span class="tenant-last-accessed">
2097
- Last accessed {{ formatLastAccessed(membership.last_accessed) }}
2098
- </span>
2099
- }
2100
- </div>
2101
- </div>
2102
- </div>
2476
+ <!-- OAuth Providers -->
2477
+ @if (oauthProviders.length > 0) {
2478
+ <div class="oauth-buttons">
2479
+ @for (provider of oauthProviders; track provider) {
2480
+ <button
2481
+ (click)="onOAuthLogin(provider)"
2482
+ [disabled]="loading"
2483
+ class="btn btn-oauth btn-{{ provider }}">
2484
+ @if (getProviderIcon(provider)) {
2485
+ <span class="oauth-icon">
2486
+ {{ getProviderIcon(provider) }}
2487
+ </span>
2488
+ }
2489
+ {{ getProviderLabel(provider) }}
2490
+ </button>
2103
2491
  }
2104
2492
  </div>
2493
+ }
2105
2494
 
2106
- <button
2107
- type="button"
2108
- (click)="onContinueWithTenant()"
2109
- [disabled]="!selectedTenantId || loading"
2110
- class="btn btn-primary btn-block">
2111
- {{ loading ? 'Loading...' : continueButtonText }}
2112
- </button>
2113
-
2114
- <!-- Error Message -->
2115
- @if (error) {
2116
- <div class="error-message">
2117
- {{ error }}
2118
- </div>
2119
- }
2120
-
2121
- <!-- Create New Tenant Link -->
2122
- @if (allowTenantCreation) {
2123
- <div class="create-tenant-link">
2124
- {{ createTenantLinkText }}
2125
- <a href="#" (click)="onCreateTenantClick($event)">{{ createTenantLinkAction }}</a>
2126
- </div>
2127
- }
2495
+ <!-- Error Message -->
2496
+ @if (error) {
2497
+ <div class="error-message">
2498
+ {{ error }}
2499
+ </div>
2128
2500
  }
2129
2501
 
2130
- <!-- Loading Overlay -->
2502
+ <!-- Loading State -->
2131
2503
  @if (loading) {
2132
2504
  <div class="loading-overlay">
2133
2505
  <div class="spinner"></div>
2134
2506
  </div>
2135
2507
  }
2508
+
2509
+ <!-- Register Link -->
2510
+ <div class="register-link">
2511
+ Don't have an account?
2512
+ <a href="#" (click)="onRegisterClick($event)">Sign up</a>
2513
+ </div>
2136
2514
  </div>
2137
- `, isInline: true, styles: [".tenant-login-dialog{padding:24px;max-width:450px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.welcome-message{margin-bottom:16px;padding:12px;background:#e8f5e9;border-radius:4px;text-align:center;font-size:14px;color:#2e7d32}.selector-description{margin-bottom:20px;font-size:14px;color:#666;text-align:center}.email-form,.form-group{margin-bottom:16px}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.tenant-list{margin-bottom:20px;display:flex;flex-direction:column;gap:12px}.tenant-item{display:flex;align-items:flex-start;gap:12px;padding:16px;border:2px solid #e0e0e0;border-radius:6px;cursor:pointer;transition:all .2s}.tenant-item:hover{border-color:#4285f4;background:#f8f9ff}.tenant-item.selected{border-color:#4285f4;background:#e8f0fe}.tenant-radio{flex-shrink:0;padding-top:2px}.tenant-radio input[type=radio]{width:18px;height:18px;cursor:pointer}.tenant-info{flex:1}.tenant-name{font-size:16px;font-weight:500;color:#333;margin-bottom:4px}.tenant-meta{font-size:13px;color:#666}.tenant-role{font-weight:500;color:#4285f4}.tenant-separator{margin:0 6px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}.create-tenant-link{margin-top:16px;padding-top:16px;border-top:1px solid #e0e0e0;text-align:center;font-size:14px;color:#666}.create-tenant-link a{color:#4285f4;text-decoration:none;font-weight:500}.create-tenant-link a:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
2515
+ `, isInline: true, styles: [".login-dialog{padding:24px;max-width:400px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.email-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
2138
2516
  }
2139
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantLoginComponent, decorators: [{
2517
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: LoginDialogComponent, decorators: [{
2140
2518
  type: Component,
2141
- args: [{ selector: 'lib-tenant-login', standalone: true, imports: [CommonModule, FormsModule], template: `
2142
- <div class="tenant-login-dialog">
2143
- @if (!showingTenantSelector) {
2144
- <!-- Step 1: Authentication -->
2145
- <h2 class="login-title">{{ title }}</h2>
2519
+ args: [{ selector: 'lib-login-dialog', standalone: true, imports: [CommonModule, FormsModule], template: `
2520
+ <div class="login-dialog">
2521
+ <h2 class="login-title">Sign In</h2>
2146
2522
 
2147
- <!-- Email/Password Form (if enabled) -->
2148
- @if (isProviderEnabled('emailPassword') && !useOAuth) {
2149
- <form (ngSubmit)="onEmailLogin()" class="email-form">
2150
- <div class="form-group">
2151
- <input
2152
- [(ngModel)]="email"
2153
- name="email"
2154
- placeholder="Email"
2155
- type="email"
2156
- required
2157
- class="form-control">
2158
- </div>
2159
- <div class="form-group">
2160
- <input
2161
- [(ngModel)]="password"
2162
- name="password"
2163
- placeholder="Password"
2164
- type="password"
2165
- required
2166
- class="form-control">
2167
- </div>
2523
+ <!-- Email/Password Form (if enabled) -->
2524
+ @if (isProviderEnabled('emailPassword')) {
2525
+ <form (ngSubmit)="onEmailLogin()" class="email-form">
2526
+ <div class="form-group">
2527
+ <input
2528
+ [(ngModel)]="email"
2529
+ name="email"
2530
+ placeholder="Email"
2531
+ type="email"
2532
+ required
2533
+ class="form-control">
2534
+ </div>
2535
+ <div class="form-group password-group">
2536
+ <input
2537
+ [(ngModel)]="password"
2538
+ name="password"
2539
+ placeholder="Password"
2540
+ [type]="showPassword ? 'text' : 'password'"
2541
+ required
2542
+ class="form-control password-input">
2168
2543
  <button
2169
- type="submit"
2170
- [disabled]="loading"
2171
- class="btn btn-primary btn-block">
2172
- {{ loading ? 'Signing in...' : 'Sign in with Email' }}
2544
+ type="button"
2545
+ class="password-toggle"
2546
+ (click)="showPassword = !showPassword"
2547
+ [attr.aria-label]="showPassword ? 'Hide password' : 'Show password'">
2548
+ {{ showPassword ? '👁️' : '👁️‍🗨️' }}
2173
2549
  </button>
2174
- </form>
2175
-
2176
- <!-- Divider -->
2177
- @if (oauthProviders.length > 0) {
2178
- <div class="divider">
2179
- <span>OR</span>
2180
- </div>
2181
- }
2182
- }
2183
-
2184
- <!-- OAuth Providers -->
2185
- @if (oauthProviders.length > 0 && (useOAuth || !isProviderEnabled('emailPassword'))) {
2186
- <div class="oauth-buttons">
2187
- @for (provider of oauthProviders; track provider) {
2188
- <button
2189
- type="button"
2190
- (click)="onOAuthLogin(provider)"
2191
- [disabled]="loading"
2192
- class="btn btn-oauth btn-{{ provider }}">
2193
- @if (getProviderIcon(provider)) {
2194
- <span class="oauth-icon">
2195
- {{ getProviderIcon(provider) }}
2196
- </span>
2197
- }
2198
- {{ getProviderLabel(provider) }}
2199
- </button>
2200
- }
2201
- </div>
2202
-
2203
- <!-- Switch to Email/Password -->
2204
- @if (isProviderEnabled('emailPassword') && oauthProviders.length > 0) {
2205
- <div class="switch-method">
2206
- <a href="#" (click)="toggleAuthMethod($event)">
2207
- {{ useOAuth ? 'Use email/password instead' : 'Use OAuth instead' }}
2208
- </a>
2209
- </div>
2210
- }
2211
- }
2212
-
2213
- <!-- Error Message -->
2214
- @if (error) {
2215
- <div class="error-message">
2216
- {{ error }}
2217
- </div>
2218
- }
2219
-
2220
- <!-- Register Link -->
2221
- @if (allowTenantCreation) {
2222
- <div class="register-link">
2223
- {{ registerLinkText }}
2224
- <a href="#" (click)="onCreateTenantClick($event)">{{ registerLinkAction }}</a>
2225
- </div>
2226
- }
2227
- } @else {
2228
- <!-- Step 2: Tenant Selection -->
2229
- <h2 class="login-title">{{ tenantSelectorTitle }}</h2>
2230
-
2231
- @if (userName) {
2232
- <div class="welcome-message">
2233
- Welcome back, <strong>{{ userName }}</strong>!
2234
2550
  </div>
2235
- }
2551
+ <button
2552
+ type="submit"
2553
+ [disabled]="loading"
2554
+ class="btn btn-primary btn-block">
2555
+ {{ loading ? 'Signing in...' : getProviderLabel('emailPassword') }}
2556
+ </button>
2557
+ </form>
2558
+ }
2236
2559
 
2237
- <p class="selector-description">{{ tenantSelectorDescription }}</p>
2560
+ <!-- Divider if both email and OAuth are present -->
2561
+ @if (isProviderEnabled('emailPassword') && oauthProviders.length > 0) {
2562
+ <div class="divider">
2563
+ <span>OR</span>
2564
+ </div>
2565
+ }
2238
2566
 
2239
- <div class="tenant-list">
2240
- @for (membership of memberships; track membership.tenant_id) {
2241
- <div
2242
- class="tenant-item"
2243
- [class.selected]="selectedTenantId === membership.tenant_id"
2244
- (click)="selectTenantItem(membership.tenant_id)">
2245
- <div class="tenant-radio">
2246
- <input
2247
- type="radio"
2248
- [checked]="selectedTenantId === membership.tenant_id"
2249
- [name]="'tenant-' + membership.tenant_id"
2250
- [id]="'tenant-' + membership.tenant_id">
2251
- </div>
2252
- <div class="tenant-info">
2253
- <div class="tenant-name">{{ membership.name }}</div>
2254
- <div class="tenant-meta">
2255
- <span class="tenant-role">{{ formatRole(membership.role) }}</span>
2256
- @if (membership.last_accessed) {
2257
- <span class="tenant-separator">·</span>
2258
- <span class="tenant-last-accessed">
2259
- Last accessed {{ formatLastAccessed(membership.last_accessed) }}
2260
- </span>
2261
- }
2262
- </div>
2263
- </div>
2264
- </div>
2567
+ <!-- OAuth Providers -->
2568
+ @if (oauthProviders.length > 0) {
2569
+ <div class="oauth-buttons">
2570
+ @for (provider of oauthProviders; track provider) {
2571
+ <button
2572
+ (click)="onOAuthLogin(provider)"
2573
+ [disabled]="loading"
2574
+ class="btn btn-oauth btn-{{ provider }}">
2575
+ @if (getProviderIcon(provider)) {
2576
+ <span class="oauth-icon">
2577
+ {{ getProviderIcon(provider) }}
2578
+ </span>
2579
+ }
2580
+ {{ getProviderLabel(provider) }}
2581
+ </button>
2265
2582
  }
2266
2583
  </div>
2584
+ }
2267
2585
 
2268
- <button
2269
- type="button"
2270
- (click)="onContinueWithTenant()"
2271
- [disabled]="!selectedTenantId || loading"
2272
- class="btn btn-primary btn-block">
2273
- {{ loading ? 'Loading...' : continueButtonText }}
2274
- </button>
2275
-
2276
- <!-- Error Message -->
2277
- @if (error) {
2278
- <div class="error-message">
2279
- {{ error }}
2280
- </div>
2281
- }
2282
-
2283
- <!-- Create New Tenant Link -->
2284
- @if (allowTenantCreation) {
2285
- <div class="create-tenant-link">
2286
- {{ createTenantLinkText }}
2287
- <a href="#" (click)="onCreateTenantClick($event)">{{ createTenantLinkAction }}</a>
2288
- </div>
2289
- }
2586
+ <!-- Error Message -->
2587
+ @if (error) {
2588
+ <div class="error-message">
2589
+ {{ error }}
2590
+ </div>
2290
2591
  }
2291
2592
 
2292
- <!-- Loading Overlay -->
2593
+ <!-- Loading State -->
2293
2594
  @if (loading) {
2294
2595
  <div class="loading-overlay">
2295
2596
  <div class="spinner"></div>
2296
2597
  </div>
2297
2598
  }
2599
+
2600
+ <!-- Register Link -->
2601
+ <div class="register-link">
2602
+ Don't have an account?
2603
+ <a href="#" (click)="onRegisterClick($event)">Sign up</a>
2604
+ </div>
2298
2605
  </div>
2299
- `, styles: [".tenant-login-dialog{padding:24px;max-width:450px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.welcome-message{margin-bottom:16px;padding:12px;background:#e8f5e9;border-radius:4px;text-align:center;font-size:14px;color:#2e7d32}.selector-description{margin-bottom:20px;font-size:14px;color:#666;text-align:center}.email-form,.form-group{margin-bottom:16px}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.tenant-list{margin-bottom:20px;display:flex;flex-direction:column;gap:12px}.tenant-item{display:flex;align-items:flex-start;gap:12px;padding:16px;border:2px solid #e0e0e0;border-radius:6px;cursor:pointer;transition:all .2s}.tenant-item:hover{border-color:#4285f4;background:#f8f9ff}.tenant-item.selected{border-color:#4285f4;background:#e8f0fe}.tenant-radio{flex-shrink:0;padding-top:2px}.tenant-radio input[type=radio]{width:18px;height:18px;cursor:pointer}.tenant-info{flex:1}.tenant-name{font-size:16px;font-weight:500;color:#333;margin-bottom:4px}.tenant-meta{font-size:13px;color:#666}.tenant-role{font-weight:500;color:#4285f4}.tenant-separator{margin:0 6px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}.create-tenant-link{margin-top:16px;padding-top:16px;border-top:1px solid #e0e0e0;text-align:center;font-size:14px;color:#666}.create-tenant-link a{color:#4285f4;text-decoration:none;font-weight:500}.create-tenant-link a:hover{text-decoration:underline}\n"] }]
2300
- }], ctorParameters: () => [{ type: AuthService }], propDecorators: { title: [{
2301
- type: Input
2302
- }], providers: [{
2303
- type: Input
2304
- }], showTenantSelector: [{
2305
- type: Input
2306
- }], autoSelectSingleTenant: [{
2307
- type: Input
2308
- }], allowTenantCreation: [{
2309
- type: Input
2310
- }], tenantSelectorTitle: [{
2311
- type: Input
2312
- }], tenantSelectorDescription: [{
2313
- type: Input
2314
- }], continueButtonText: [{
2315
- type: Input
2316
- }], registerLinkText: [{
2317
- type: Input
2318
- }], registerLinkAction: [{
2319
- type: Input
2320
- }], createTenantLinkText: [{
2321
- type: Input
2322
- }], createTenantLinkAction: [{
2606
+ `, styles: [".login-dialog{padding:24px;max-width:400px;position:relative}.login-title{margin:0 0 24px;font-size:24px;font-weight:500;text-align:center}.email-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:50%;transform:translateY(-50%);background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.divider{margin:16px 0;text-align:center;position:relative}.divider:before{content:\"\";position:absolute;top:50%;left:0;right:0;height:1px;background:#ddd}.divider span{background:#fff;padding:0 12px;position:relative;color:#666;font-size:12px}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffc;display:flex;align-items:center;justify-content:center}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.register-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.register-link a{color:#4285f4;text-decoration:none}.register-link a:hover{text-decoration:underline}\n"] }]
2607
+ }], ctorParameters: () => [{ type: AuthService }], propDecorators: { providers: [{
2323
2608
  type: Input
2324
- }], tenantSelected: [{
2325
- type: Output
2326
- }], createTenant: [{
2327
- type: Output
2328
2609
  }] } });
2329
2610
 
2330
2611
  class TenantRegisterComponent {
@@ -2371,6 +2652,8 @@ class TenantRegisterComponent {
2371
2652
  slugError = '';
2372
2653
  useEmailPassword = false;
2373
2654
  oauthProviders = [];
2655
+ showPassword = false;
2656
+ showConfirmPassword = false;
2374
2657
  constructor(auth) {
2375
2658
  this.auth = auth;
2376
2659
  }
@@ -2668,30 +2951,44 @@ class TenantRegisterComponent {
2668
2951
  class="form-control">
2669
2952
  </div>
2670
2953
 
2671
- <div class="form-group">
2954
+ <div class="form-group password-group">
2672
2955
  <label for="password">Password *</label>
2673
2956
  <input
2674
2957
  id="password"
2675
2958
  [(ngModel)]="password"
2676
2959
  name="password"
2677
2960
  placeholder="Create a password"
2678
- type="password"
2961
+ [type]="showPassword ? 'text' : 'password'"
2679
2962
  required
2680
2963
  minlength="8"
2681
- class="form-control">
2964
+ class="form-control password-input">
2965
+ <button
2966
+ type="button"
2967
+ class="password-toggle"
2968
+ (click)="showPassword = !showPassword"
2969
+ [attr.aria-label]="showPassword ? 'Hide password' : 'Show password'">
2970
+ {{ showPassword ? '👁️' : '👁️‍🗨️' }}
2971
+ </button>
2682
2972
  <small class="form-hint">At least 8 characters</small>
2683
2973
  </div>
2684
2974
 
2685
- <div class="form-group">
2975
+ <div class="form-group password-group">
2686
2976
  <label for="confirmPassword">Confirm Password *</label>
2687
2977
  <input
2688
2978
  id="confirmPassword"
2689
2979
  [(ngModel)]="confirmPassword"
2690
2980
  name="confirmPassword"
2691
2981
  placeholder="Confirm your password"
2692
- type="password"
2982
+ [type]="showConfirmPassword ? 'text' : 'password'"
2693
2983
  required
2694
- class="form-control">
2984
+ class="form-control password-input">
2985
+ <button
2986
+ type="button"
2987
+ class="password-toggle"
2988
+ (click)="showConfirmPassword = !showConfirmPassword"
2989
+ [attr.aria-label]="showConfirmPassword ? 'Hide password' : 'Show password'">
2990
+ {{ showConfirmPassword ? '👁️' : '👁️‍🗨️' }}
2991
+ </button>
2695
2992
  </div>
2696
2993
 
2697
2994
  <button
@@ -2740,7 +3037,7 @@ class TenantRegisterComponent {
2740
3037
  <a href="#" (click)="onLoginClick($event)">{{ loginLinkAction }}</a>
2741
3038
  </div>
2742
3039
  </div>
2743
- `, isInline: true, styles: [".tenant-register-dialog{padding:24px;max-width:500px;position:relative}.register-title{margin:0 0 20px;font-size:24px;font-weight:500;text-align:center}.warning-box{display:flex;gap:12px;padding:16px;background:#fff3cd;border:1px solid #ffc107;border-radius:6px;margin-bottom:24px}.warning-icon{font-size:24px;line-height:1}.warning-content{flex:1}.warning-content strong{display:block;margin-bottom:4px;color:#856404;font-size:14px}.warning-content p{margin:0;color:#856404;font-size:13px;line-height:1.5}.section-header{font-size:16px;font-weight:600;margin:20px 0 12px;padding-bottom:8px;border-bottom:2px solid #e0e0e0;color:#333}.register-form,.form-group{margin-bottom:16px}.form-group label{display:block;margin-bottom:6px;font-size:14px;font-weight:500;color:#333}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box;transition:border-color .2s}.form-control:focus{outline:none;border-color:#4285f4}.form-control.input-error{border-color:#dc3545}.form-control.input-success{border-color:#28a745}.form-hint{display:block;margin-top:4px;font-size:12px;color:#666}.form-hint .checking{color:#666}.form-hint .available{color:#28a745;font-weight:500}.form-hint .error-text{color:#dc3545}.oauth-section{margin:16px 0}.oauth-description{margin-bottom:12px;font-size:14px;color:#666;text-align:center}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s,opacity .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.success-message{margin-top:16px;padding:12px;background:#efe;color:#3a3;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffffff2;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-text{margin:0;font-size:14px;color:#666}.login-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.login-link a{color:#4285f4;text-decoration:none}.login-link a:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i2.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
3040
+ `, isInline: true, styles: [".tenant-register-dialog{padding:24px;max-width:500px;position:relative}.register-title{margin:0 0 20px;font-size:24px;font-weight:500;text-align:center}.warning-box{display:flex;gap:12px;padding:16px;background:#fff3cd;border:1px solid #ffc107;border-radius:6px;margin-bottom:24px}.warning-icon{font-size:24px;line-height:1}.warning-content{flex:1}.warning-content strong{display:block;margin-bottom:4px;color:#856404;font-size:14px}.warning-content p{margin:0;color:#856404;font-size:13px;line-height:1.5}.section-header{font-size:16px;font-weight:600;margin:20px 0 12px;padding-bottom:8px;border-bottom:2px solid #e0e0e0;color:#333}.register-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-group label{display:block;margin-bottom:6px;font-size:14px;font-weight:500;color:#333}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box;transition:border-color .2s}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:38px;background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.form-control.input-error{border-color:#dc3545}.form-control.input-success{border-color:#28a745}.form-hint{display:block;margin-top:4px;font-size:12px;color:#666}.form-hint .checking{color:#666}.form-hint .available{color:#28a745;font-weight:500}.form-hint .error-text{color:#dc3545}.oauth-section{margin:16px 0}.oauth-description{margin-bottom:12px;font-size:14px;color:#666;text-align:center}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s,opacity .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.success-message{margin-top:16px;padding:12px;background:#efe;color:#3a3;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffffff2;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-text{margin:0;font-size:14px;color:#666}.login-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.login-link a{color:#4285f4;text-decoration:none}.login-link a:hover{text-decoration:underline}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i2.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i2.MinLengthValidator, selector: "[minlength][formControlName],[minlength][formControl],[minlength][ngModel]", inputs: ["minlength"] }, { kind: "directive", type: i2.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i2.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
2744
3041
  }
2745
3042
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantRegisterComponent, decorators: [{
2746
3043
  type: Component,
@@ -2865,30 +3162,44 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
2865
3162
  class="form-control">
2866
3163
  </div>
2867
3164
 
2868
- <div class="form-group">
3165
+ <div class="form-group password-group">
2869
3166
  <label for="password">Password *</label>
2870
3167
  <input
2871
3168
  id="password"
2872
3169
  [(ngModel)]="password"
2873
3170
  name="password"
2874
3171
  placeholder="Create a password"
2875
- type="password"
3172
+ [type]="showPassword ? 'text' : 'password'"
2876
3173
  required
2877
3174
  minlength="8"
2878
- class="form-control">
3175
+ class="form-control password-input">
3176
+ <button
3177
+ type="button"
3178
+ class="password-toggle"
3179
+ (click)="showPassword = !showPassword"
3180
+ [attr.aria-label]="showPassword ? 'Hide password' : 'Show password'">
3181
+ {{ showPassword ? '👁️' : '👁️‍🗨️' }}
3182
+ </button>
2879
3183
  <small class="form-hint">At least 8 characters</small>
2880
3184
  </div>
2881
3185
 
2882
- <div class="form-group">
3186
+ <div class="form-group password-group">
2883
3187
  <label for="confirmPassword">Confirm Password *</label>
2884
3188
  <input
2885
3189
  id="confirmPassword"
2886
3190
  [(ngModel)]="confirmPassword"
2887
3191
  name="confirmPassword"
2888
3192
  placeholder="Confirm your password"
2889
- type="password"
3193
+ [type]="showConfirmPassword ? 'text' : 'password'"
2890
3194
  required
2891
- class="form-control">
3195
+ class="form-control password-input">
3196
+ <button
3197
+ type="button"
3198
+ class="password-toggle"
3199
+ (click)="showConfirmPassword = !showConfirmPassword"
3200
+ [attr.aria-label]="showConfirmPassword ? 'Hide password' : 'Show password'">
3201
+ {{ showConfirmPassword ? '👁️' : '👁️‍🗨️' }}
3202
+ </button>
2892
3203
  </div>
2893
3204
 
2894
3205
  <button
@@ -2937,7 +3248,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
2937
3248
  <a href="#" (click)="onLoginClick($event)">{{ loginLinkAction }}</a>
2938
3249
  </div>
2939
3250
  </div>
2940
- `, styles: [".tenant-register-dialog{padding:24px;max-width:500px;position:relative}.register-title{margin:0 0 20px;font-size:24px;font-weight:500;text-align:center}.warning-box{display:flex;gap:12px;padding:16px;background:#fff3cd;border:1px solid #ffc107;border-radius:6px;margin-bottom:24px}.warning-icon{font-size:24px;line-height:1}.warning-content{flex:1}.warning-content strong{display:block;margin-bottom:4px;color:#856404;font-size:14px}.warning-content p{margin:0;color:#856404;font-size:13px;line-height:1.5}.section-header{font-size:16px;font-weight:600;margin:20px 0 12px;padding-bottom:8px;border-bottom:2px solid #e0e0e0;color:#333}.register-form,.form-group{margin-bottom:16px}.form-group label{display:block;margin-bottom:6px;font-size:14px;font-weight:500;color:#333}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box;transition:border-color .2s}.form-control:focus{outline:none;border-color:#4285f4}.form-control.input-error{border-color:#dc3545}.form-control.input-success{border-color:#28a745}.form-hint{display:block;margin-top:4px;font-size:12px;color:#666}.form-hint .checking{color:#666}.form-hint .available{color:#28a745;font-weight:500}.form-hint .error-text{color:#dc3545}.oauth-section{margin:16px 0}.oauth-description{margin-bottom:12px;font-size:14px;color:#666;text-align:center}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s,opacity .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.success-message{margin-top:16px;padding:12px;background:#efe;color:#3a3;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffffff2;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-text{margin:0;font-size:14px;color:#666}.login-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.login-link a{color:#4285f4;text-decoration:none}.login-link a:hover{text-decoration:underline}\n"] }]
3251
+ `, styles: [".tenant-register-dialog{padding:24px;max-width:500px;position:relative}.register-title{margin:0 0 20px;font-size:24px;font-weight:500;text-align:center}.warning-box{display:flex;gap:12px;padding:16px;background:#fff3cd;border:1px solid #ffc107;border-radius:6px;margin-bottom:24px}.warning-icon{font-size:24px;line-height:1}.warning-content{flex:1}.warning-content strong{display:block;margin-bottom:4px;color:#856404;font-size:14px}.warning-content p{margin:0;color:#856404;font-size:13px;line-height:1.5}.section-header{font-size:16px;font-weight:600;margin:20px 0 12px;padding-bottom:8px;border-bottom:2px solid #e0e0e0;color:#333}.register-form,.form-group{margin-bottom:16px}.password-group{position:relative}.form-group label{display:block;margin-bottom:6px;font-size:14px;font-weight:500;color:#333}.form-control{width:100%;padding:12px;border:1px solid #ddd;border-radius:4px;font-size:14px;box-sizing:border-box;transition:border-color .2s}.password-input{padding-right:45px}.password-toggle{position:absolute;right:8px;top:38px;background:none;border:none;cursor:pointer;font-size:18px;padding:8px;line-height:1;opacity:.6;transition:opacity .2s}.password-toggle:hover{opacity:1}.password-toggle:focus{outline:2px solid #4285f4;outline-offset:2px;border-radius:4px}.form-control:focus{outline:none;border-color:#4285f4}.form-control.input-error{border-color:#dc3545}.form-control.input-success{border-color:#28a745}.form-hint{display:block;margin-top:4px;font-size:12px;color:#666}.form-hint .checking{color:#666}.form-hint .available{color:#28a745;font-weight:500}.form-hint .error-text{color:#dc3545}.oauth-section{margin:16px 0}.oauth-description{margin-bottom:12px;font-size:14px;color:#666;text-align:center}.oauth-buttons{display:flex;flex-direction:column;gap:12px}.btn{padding:12px 24px;border:none;border-radius:4px;font-size:14px;font-weight:500;cursor:pointer;transition:background-color .2s,opacity .2s}.btn:disabled{opacity:.6;cursor:not-allowed}.btn-block{width:100%}.btn-primary{background-color:#4285f4;color:#fff}.btn-primary:hover:not(:disabled){background-color:#357ae8}.btn-oauth{width:100%;background:#fff;color:#333;border:1px solid #ddd;display:flex;align-items:center;justify-content:center;gap:8px}.btn-oauth:hover:not(:disabled){background:#f8f8f8}.btn-google{border-color:#4285f4}.btn-linkedin{border-color:#0077b5}.btn-apple{border-color:#000}.btn-microsoft{border-color:#00a4ef}.btn-github{border-color:#333}.oauth-icon{font-size:18px}.switch-method{margin-top:12px;text-align:center;font-size:14px}.switch-method a{color:#4285f4;text-decoration:none}.switch-method a:hover{text-decoration:underline}.error-message{margin-top:16px;padding:12px;background:#fee;color:#c33;border-radius:4px;font-size:14px}.success-message{margin-top:16px;padding:12px;background:#efe;color:#3a3;border-radius:4px;font-size:14px}.loading-overlay{position:absolute;inset:0;background:#fffffff2;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px}.spinner{width:40px;height:40px;border:4px solid #f3f3f3;border-top:4px solid #4285f4;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.loading-text{margin:0;font-size:14px;color:#666}.login-link{margin-top:16px;text-align:center;font-size:14px;color:#666}.login-link a{color:#4285f4;text-decoration:none}.login-link a:hover{text-decoration:underline}\n"] }]
2941
3252
  }], ctorParameters: () => [{ type: AuthService }], propDecorators: { title: [{
2942
3253
  type: Input
2943
3254
  }], providers: [{
@@ -3047,7 +3358,7 @@ class TenantLoginDialogComponent {
3047
3358
  (createTenant)="onCreateTenant()">
3048
3359
  </lib-tenant-login>
3049
3360
  </div>
3050
- `, isInline: true, styles: [".dialog-wrapper{padding:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: TenantLoginComponent, selector: "lib-tenant-login", inputs: ["title", "providers", "showTenantSelector", "autoSelectSingleTenant", "allowTenantCreation", "tenantSelectorTitle", "tenantSelectorDescription", "continueButtonText", "registerLinkText", "registerLinkAction", "createTenantLinkText", "createTenantLinkAction"], outputs: ["tenantSelected", "createTenant"] }] });
3361
+ `, isInline: true, styles: [".dialog-wrapper{padding:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: TenantLoginComponent, selector: "lib-tenant-login", inputs: ["title", "providers", "showTenantSelector", "autoSelectSingleTenant", "prefillEmail", "allowTenantCreation", "tenantSelectorTitle", "tenantSelectorDescription", "continueButtonText", "registerLinkText", "registerLinkAction", "createTenantLinkText", "createTenantLinkAction"], outputs: ["tenantSelected", "createTenant"] }] });
3051
3362
  }
3052
3363
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantLoginDialogComponent, decorators: [{
3053
3364
  type: Component,
@@ -3204,5 +3515,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
3204
3515
  * Generated bundle index. Do not edit.
3205
3516
  */
3206
3517
 
3207
- export { ApiConnectionService, ApiResponse, AuthService, CsrfService, DbService, LoginDialogComponent, MyEnvironmentModel, NgxStoneScriptPhpClientModule, RegisterComponent, SigninStatusService, TenantLoginComponent, TenantLoginDialogComponent, TenantRegisterComponent, TenantRegisterDialogComponent, TokenService, VerifyStatus };
3518
+ export { ApiConnectionService, ApiResponse, AuthPageComponent, AuthService, CsrfService, DbService, LoginDialogComponent, MyEnvironmentModel, NgxStoneScriptPhpClientModule, RegisterComponent, SigninStatusService, TenantLoginComponent, TenantLoginDialogComponent, TenantRegisterComponent, TenantRegisterDialogComponent, TokenService, VerifyStatus };
3208
3519
  //# sourceMappingURL=progalaxyelabs-ngx-stonescriptphp-client.mjs.map