@progalaxyelabs/ngx-stonescriptphp-client 1.24.2 → 1.24.4

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.
@@ -2241,6 +2241,7 @@ class TenantLoginComponent {
2241
2241
  autoSelectSingleTenant = true;
2242
2242
  prefillEmail; // Email to prefill (for account linking flow)
2243
2243
  allowTenantCreation = true;
2244
+ otpIdentifierTypes = ['email', 'phone']; // Allowed OTP identifier types
2244
2245
  // Tenant Selector Labels
2245
2246
  tenantSelectorTitle = 'Select Organization';
2246
2247
  tenantSelectorDescription = 'Choose which organization you want to access:';
@@ -2270,6 +2271,8 @@ class TenantLoginComponent {
2270
2271
  otpStep = 'identifier';
2271
2272
  otpIdentifier = '';
2272
2273
  otpIdentifierHint = '';
2274
+ otpIdentifierError = '';
2275
+ otpIdentifierErrorColor = 'red';
2273
2276
  otpNormalizedIdentifier = ''; // E.164 for phone, as-is for email
2274
2277
  otpMaskedIdentifier = '';
2275
2278
  otpDigits = ['', '', '', '', '', ''];
@@ -2528,12 +2531,12 @@ class TenantLoginComponent {
2528
2531
  async onOtpSend() {
2529
2532
  const raw = this.otpIdentifier.trim();
2530
2533
  if (!raw) {
2531
- this.error = 'Please enter your email or phone number';
2534
+ this.error = `Please enter your ${this.otpIdentifierName}`;
2532
2535
  return;
2533
2536
  }
2534
2537
  const type = this.detectIdentifierType(raw);
2535
2538
  if (!type) {
2536
- this.error = 'Please enter a valid email address or phone number';
2539
+ this.error = `Please enter a valid ${this.otpIdentifierName}`;
2537
2540
  return;
2538
2541
  }
2539
2542
  // Normalize: auto-prepend +91 for 10-digit Indian numbers
@@ -2546,6 +2549,7 @@ class TenantLoginComponent {
2546
2549
  }
2547
2550
  this.loading = true;
2548
2551
  this.error = '';
2552
+ this.otpIdentifierError = '';
2549
2553
  try {
2550
2554
  const result = await this.auth.sendOtp(this.otpNormalizedIdentifier);
2551
2555
  if (!result.success) {
@@ -2580,7 +2584,26 @@ class TenantLoginComponent {
2580
2584
  try {
2581
2585
  const verifyResult = await this.auth.verifyOtp(this.otpNormalizedIdentifier, code);
2582
2586
  if (!verifyResult.success) {
2583
- this.error = 'Invalid code. Please try again.';
2587
+ switch (verifyResult.error) {
2588
+ case 'otp_expired':
2589
+ this.resetToIdentifierStep('Your code has expired. Please request a new one.', 'orange');
2590
+ break;
2591
+ case 'otp_invalid': {
2592
+ const attempts = verifyResult.remaining_attempts;
2593
+ this.error = attempts !== undefined
2594
+ ? `Invalid code. ${attempts} attempt${attempts === 1 ? '' : 's'} remaining.`
2595
+ : 'Invalid code. Please try again.';
2596
+ break;
2597
+ }
2598
+ case 'otp_rate_limited':
2599
+ this.resetToIdentifierStep('Too many attempts. Please try again later.', 'red');
2600
+ break;
2601
+ case 'otp_not_found':
2602
+ this.resetToIdentifierStep('No code found. Please request a new one.', 'orange');
2603
+ break;
2604
+ default:
2605
+ this.error = verifyResult.message || 'Invalid code. Please try again.';
2606
+ }
2584
2607
  return;
2585
2608
  }
2586
2609
  this.otpVerifiedToken = verifyResult.verified_token;
@@ -2656,11 +2679,26 @@ class TenantLoginComponent {
2656
2679
  /** Go back to identifier entry */
2657
2680
  onOtpBack(event) {
2658
2681
  event.preventDefault();
2682
+ this.otpStep = 'identifier';
2683
+ this.otpDigits = ['', '', '', '', '', ''];
2684
+ this.otpVerifiedToken = '';
2685
+ this.error = '';
2686
+ this.otpIdentifierError = '';
2687
+ this.clearResendTimer();
2688
+ }
2689
+ /**
2690
+ * Reset to the identifier entry step (step 1) with an error message.
2691
+ * Used when OTP verification fails with a code that requires re-sending
2692
+ * (expired, rate-limited, not-found). Keeps the identifier pre-filled.
2693
+ */
2694
+ resetToIdentifierStep(message, color) {
2659
2695
  this.otpStep = 'identifier';
2660
2696
  this.otpDigits = ['', '', '', '', '', ''];
2661
2697
  this.otpVerifiedToken = '';
2662
2698
  this.error = '';
2663
2699
  this.clearResendTimer();
2700
+ this.otpIdentifierError = message;
2701
+ this.otpIdentifierErrorColor = color;
2664
2702
  }
2665
2703
  // ── OTP digit input handling ─────────────────────────────────────────────
2666
2704
  onOtpDigitInput(event, index) {
@@ -2705,15 +2743,46 @@ class TenantLoginComponent {
2705
2743
  get otpCode() {
2706
2744
  return this.otpDigits.join('');
2707
2745
  }
2746
+ get otpPlaceholderText() {
2747
+ const allowsEmail = this.otpIdentifierTypes.includes('email');
2748
+ const allowsPhone = this.otpIdentifierTypes.includes('phone');
2749
+ if (allowsEmail && allowsPhone) {
2750
+ return 'Enter Email or Phone Number';
2751
+ }
2752
+ else if (allowsEmail) {
2753
+ return 'Enter Email';
2754
+ }
2755
+ else if (allowsPhone) {
2756
+ return 'Enter Phone Number';
2757
+ }
2758
+ return 'Enter Identifier';
2759
+ }
2760
+ get otpIdentifierName() {
2761
+ const allowsEmail = this.otpIdentifierTypes.includes('email');
2762
+ const allowsPhone = this.otpIdentifierTypes.includes('phone');
2763
+ if (allowsEmail && allowsPhone) {
2764
+ return 'email or phone number';
2765
+ }
2766
+ else if (allowsEmail) {
2767
+ return 'email';
2768
+ }
2769
+ else if (allowsPhone) {
2770
+ return 'phone number';
2771
+ }
2772
+ return 'identifier';
2773
+ }
2708
2774
  // ── OTP helpers ──────────────────────────────────────────────────────────
2709
2775
  detectIdentifierType(value) {
2776
+ const allowsEmail = this.otpIdentifierTypes.includes('email');
2777
+ const allowsPhone = this.otpIdentifierTypes.includes('phone');
2710
2778
  if (value.includes('@')) {
2711
2779
  // Basic email validation
2712
- return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) ? 'email' : null;
2780
+ const isValidEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
2781
+ return (isValidEmail && allowsEmail) ? 'email' : null;
2713
2782
  }
2714
2783
  const digits = value.replace(/\D/g, '');
2715
2784
  if (digits.length >= 10 && digits.length <= 15) {
2716
- return 'phone';
2785
+ return allowsPhone ? 'phone' : null;
2717
2786
  }
2718
2787
  return null;
2719
2788
  }
@@ -2775,7 +2844,7 @@ class TenantLoginComponent {
2775
2844
  this.createTenant.emit();
2776
2845
  }
2777
2846
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantLoginComponent, deps: [{ token: AuthService }, { token: ProviderRegistryService }], target: i0.ɵɵFactoryTarget.Component });
2778
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", 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", needsOnboarding: "needsOnboarding", createTenant: "createTenant" }, viewQueries: [{ propertyName: "otpInputs", predicate: ["otpInput"], descendants: true }], ngImport: i0, template: `
2847
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: TenantLoginComponent, isStandalone: true, selector: "lib-tenant-login", inputs: { title: "title", providers: "providers", showTenantSelector: "showTenantSelector", autoSelectSingleTenant: "autoSelectSingleTenant", prefillEmail: "prefillEmail", allowTenantCreation: "allowTenantCreation", otpIdentifierTypes: "otpIdentifierTypes", tenantSelectorTitle: "tenantSelectorTitle", tenantSelectorDescription: "tenantSelectorDescription", continueButtonText: "continueButtonText", registerLinkText: "registerLinkText", registerLinkAction: "registerLinkAction", createTenantLinkText: "createTenantLinkText", createTenantLinkAction: "createTenantLinkAction" }, outputs: { tenantSelected: "tenantSelected", needsOnboarding: "needsOnboarding", createTenant: "createTenant" }, viewQueries: [{ propertyName: "otpInputs", predicate: ["otpInput"], descendants: true }], ngImport: i0, template: `
2779
2848
  <div class="tenant-login-dialog">
2780
2849
  @if (!showingTenantSelector) {
2781
2850
  <!-- Step 1: Authentication -->
@@ -2786,11 +2855,16 @@ class TenantLoginComponent {
2786
2855
  <!-- OTP Step 1: Identifier entry -->
2787
2856
  @if (otpStep === 'identifier') {
2788
2857
  <form (ngSubmit)="onOtpSend()" class="otp-form">
2858
+ @if (otpIdentifierError) {
2859
+ <div [class]="'otp-identifier-error otp-identifier-error--' + otpIdentifierErrorColor">
2860
+ {{ otpIdentifierError }}
2861
+ </div>
2862
+ }
2789
2863
  <div class="form-group">
2790
2864
  <input
2791
2865
  [(ngModel)]="otpIdentifier"
2792
2866
  name="otpIdentifier"
2793
- placeholder="Enter Email or Phone Number"
2867
+ [placeholder]="otpPlaceholderText"
2794
2868
  type="text"
2795
2869
  required
2796
2870
  autocomplete="email tel"
@@ -2852,7 +2926,7 @@ class TenantLoginComponent {
2852
2926
  </div>
2853
2927
  <div class="otp-back">
2854
2928
  <a href="#" (click)="onOtpBack($event)">
2855
- Use a different email or phone
2929
+ Use a different {{ otpIdentifierName }}
2856
2930
  </a>
2857
2931
  </div>
2858
2932
  </div>
@@ -3052,7 +3126,7 @@ class TenantLoginComponent {
3052
3126
  </div>
3053
3127
  }
3054
3128
  </div>
3055
- `, 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,.otp-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}.field-hint{margin-top:4px;font-size:12px;color:#888}.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}.btn-zoho{background-color:#f0483e;color:#fff;border:1px solid #d63b32}.btn-zoho:hover{background-color:#d63b32}.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}.otp-subtitle{text-align:center;font-size:14px;color:#555;margin-bottom:20px}.otp-digits{display:flex;justify-content:center;gap:8px;margin-bottom:20px}.otp-digit-input{width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid #ddd;border-radius:8px;outline:none;transition:border-color .2s;box-sizing:border-box}.otp-digit-input:focus{border-color:#4285f4}.otp-actions{margin-bottom:12px}.otp-resend{text-align:center;font-size:14px;margin-bottom:8px}.resend-timer{color:#888}.resend-link{color:#4285f4;text-decoration:none;cursor:pointer}.resend-link:hover{text-decoration:underline}.otp-back{text-align:center;font-size:13px}.otp-back a{color:#888;text-decoration:none}.otp-back a:hover{text-decoration:underline;color:#4285f4}.otp-register-section .otp-subtitle{color:#2e7d32}.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: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4.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: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i4.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
3129
+ `, 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,.otp-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}.field-hint{margin-top:4px;font-size:12px;color:#888}.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}.btn-zoho{background-color:#f0483e;color:#fff;border:1px solid #d63b32}.btn-zoho:hover{background-color:#d63b32}.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}.otp-subtitle{text-align:center;font-size:14px;color:#555;margin-bottom:20px}.otp-digits{display:flex;justify-content:center;gap:8px;margin-bottom:20px}.otp-digit-input{width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid #ddd;border-radius:8px;outline:none;transition:border-color .2s;box-sizing:border-box}.otp-digit-input:focus{border-color:#4285f4}.otp-actions{margin-bottom:12px}.otp-resend{text-align:center;font-size:14px;margin-bottom:8px}.resend-timer{color:#888}.resend-link{color:#4285f4;text-decoration:none;cursor:pointer}.resend-link:hover{text-decoration:underline}.otp-back{text-align:center;font-size:13px}.otp-back a{color:#888;text-decoration:none}.otp-back a:hover{text-decoration:underline;color:#4285f4}.otp-register-section .otp-subtitle{color:#2e7d32}.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}.otp-identifier-error{margin-bottom:12px;padding:10px 12px;border-radius:4px;font-size:14px}.otp-identifier-error--red{background:#fee;color:#c33}.otp-identifier-error--orange{background:#fff3e0;color:#e65100}.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: "directive", type: i3.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i4.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i4.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: i4.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i4.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i4.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i4.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i4.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }] });
3056
3130
  }
3057
3131
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantLoginComponent, decorators: [{
3058
3132
  type: Component,
@@ -3067,11 +3141,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
3067
3141
  <!-- OTP Step 1: Identifier entry -->
3068
3142
  @if (otpStep === 'identifier') {
3069
3143
  <form (ngSubmit)="onOtpSend()" class="otp-form">
3144
+ @if (otpIdentifierError) {
3145
+ <div [class]="'otp-identifier-error otp-identifier-error--' + otpIdentifierErrorColor">
3146
+ {{ otpIdentifierError }}
3147
+ </div>
3148
+ }
3070
3149
  <div class="form-group">
3071
3150
  <input
3072
3151
  [(ngModel)]="otpIdentifier"
3073
3152
  name="otpIdentifier"
3074
- placeholder="Enter Email or Phone Number"
3153
+ [placeholder]="otpPlaceholderText"
3075
3154
  type="text"
3076
3155
  required
3077
3156
  autocomplete="email tel"
@@ -3133,7 +3212,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
3133
3212
  </div>
3134
3213
  <div class="otp-back">
3135
3214
  <a href="#" (click)="onOtpBack($event)">
3136
- Use a different email or phone
3215
+ Use a different {{ otpIdentifierName }}
3137
3216
  </a>
3138
3217
  </div>
3139
3218
  </div>
@@ -3333,7 +3412,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
3333
3412
  </div>
3334
3413
  }
3335
3414
  </div>
3336
- `, 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,.otp-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}.field-hint{margin-top:4px;font-size:12px;color:#888}.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}.btn-zoho{background-color:#f0483e;color:#fff;border:1px solid #d63b32}.btn-zoho:hover{background-color:#d63b32}.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}.otp-subtitle{text-align:center;font-size:14px;color:#555;margin-bottom:20px}.otp-digits{display:flex;justify-content:center;gap:8px;margin-bottom:20px}.otp-digit-input{width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid #ddd;border-radius:8px;outline:none;transition:border-color .2s;box-sizing:border-box}.otp-digit-input:focus{border-color:#4285f4}.otp-actions{margin-bottom:12px}.otp-resend{text-align:center;font-size:14px;margin-bottom:8px}.resend-timer{color:#888}.resend-link{color:#4285f4;text-decoration:none;cursor:pointer}.resend-link:hover{text-decoration:underline}.otp-back{text-align:center;font-size:13px}.otp-back a{color:#888;text-decoration:none}.otp-back a:hover{text-decoration:underline;color:#4285f4}.otp-register-section .otp-subtitle{color:#2e7d32}.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"] }]
3415
+ `, 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,.otp-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}.field-hint{margin-top:4px;font-size:12px;color:#888}.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}.btn-zoho{background-color:#f0483e;color:#fff;border:1px solid #d63b32}.btn-zoho:hover{background-color:#d63b32}.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}.otp-subtitle{text-align:center;font-size:14px;color:#555;margin-bottom:20px}.otp-digits{display:flex;justify-content:center;gap:8px;margin-bottom:20px}.otp-digit-input{width:44px;height:52px;text-align:center;font-size:22px;font-weight:600;border:2px solid #ddd;border-radius:8px;outline:none;transition:border-color .2s;box-sizing:border-box}.otp-digit-input:focus{border-color:#4285f4}.otp-actions{margin-bottom:12px}.otp-resend{text-align:center;font-size:14px;margin-bottom:8px}.resend-timer{color:#888}.resend-link{color:#4285f4;text-decoration:none;cursor:pointer}.resend-link:hover{text-decoration:underline}.otp-back{text-align:center;font-size:13px}.otp-back a{color:#888;text-decoration:none}.otp-back a:hover{text-decoration:underline;color:#4285f4}.otp-register-section .otp-subtitle{color:#2e7d32}.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}.otp-identifier-error{margin-bottom:12px;padding:10px 12px;border-radius:4px;font-size:14px}.otp-identifier-error--red{background:#fee;color:#c33}.otp-identifier-error--orange{background:#fff3e0;color:#e65100}.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"] }]
3337
3416
  }], ctorParameters: () => [{ type: AuthService }, { type: ProviderRegistryService }], propDecorators: { otpInputs: [{
3338
3417
  type: ViewChildren,
3339
3418
  args: ['otpInput']
@@ -3349,6 +3428,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
3349
3428
  type: Input
3350
3429
  }], allowTenantCreation: [{
3351
3430
  type: Input
3431
+ }], otpIdentifierTypes: [{
3432
+ type: Input
3352
3433
  }], tenantSelectorTitle: [{
3353
3434
  type: Input
3354
3435
  }], tenantSelectorDescription: [{
@@ -3817,7 +3898,7 @@ class AuthPageComponent {
3817
3898
  }
3818
3899
  </div>
3819
3900
  </div>
3820
- `, 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", "needsOnboarding", "createTenant"] }, { kind: "component", type: RegisterComponent, selector: "lib-register", outputs: ["navigateToLogin"] }] });
3901
+ `, 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", "otpIdentifierTypes", "tenantSelectorTitle", "tenantSelectorDescription", "continueButtonText", "registerLinkText", "registerLinkAction", "createTenantLinkText", "createTenantLinkAction"], outputs: ["tenantSelected", "needsOnboarding", "createTenant"] }, { kind: "component", type: RegisterComponent, selector: "lib-register", outputs: ["navigateToLogin"] }] });
3821
3902
  }
3822
3903
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AuthPageComponent, decorators: [{
3823
3904
  type: Component,
@@ -5094,7 +5175,7 @@ class TenantLoginDialogComponent {
5094
5175
  (createTenant)="onCreateTenant()">
5095
5176
  </lib-tenant-login>
5096
5177
  </div>
5097
- `, 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", "needsOnboarding", "createTenant"] }] });
5178
+ `, 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", "otpIdentifierTypes", "tenantSelectorTitle", "tenantSelectorDescription", "continueButtonText", "registerLinkText", "registerLinkAction", "createTenantLinkText", "createTenantLinkAction"], outputs: ["tenantSelected", "needsOnboarding", "createTenant"] }] });
5098
5179
  }
5099
5180
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantLoginDialogComponent, decorators: [{
5100
5181
  type: Component,