@progalaxyelabs/ngx-stonescriptphp-client 1.24.2 → 1.24.5
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 =
|
|
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 =
|
|
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) {
|
|
@@ -2566,6 +2570,14 @@ class TenantLoginComponent {
|
|
|
2566
2570
|
this.loading = false;
|
|
2567
2571
|
}
|
|
2568
2572
|
}
|
|
2573
|
+
/** Skip to OTP code entry when user already has a code */
|
|
2574
|
+
onSkipToOtpEntry() {
|
|
2575
|
+
this.error = '';
|
|
2576
|
+
this.otpMaskedIdentifier = this.otpIdentifier;
|
|
2577
|
+
this.otpDigits = ['', '', '', '', '', ''];
|
|
2578
|
+
this.otpStep = 'code';
|
|
2579
|
+
setTimeout(() => this.focusOtpInput(0), 50);
|
|
2580
|
+
}
|
|
2569
2581
|
/** Verify the entered OTP code */
|
|
2570
2582
|
async onOtpVerify() {
|
|
2571
2583
|
if (this.loading)
|
|
@@ -2580,7 +2592,26 @@ class TenantLoginComponent {
|
|
|
2580
2592
|
try {
|
|
2581
2593
|
const verifyResult = await this.auth.verifyOtp(this.otpNormalizedIdentifier, code);
|
|
2582
2594
|
if (!verifyResult.success) {
|
|
2583
|
-
|
|
2595
|
+
switch (verifyResult.error) {
|
|
2596
|
+
case 'otp_expired':
|
|
2597
|
+
this.resetToIdentifierStep('Your code has expired. Please request a new one.', 'orange');
|
|
2598
|
+
break;
|
|
2599
|
+
case 'otp_invalid': {
|
|
2600
|
+
const attempts = verifyResult.remaining_attempts;
|
|
2601
|
+
this.error = attempts !== undefined
|
|
2602
|
+
? `Invalid code. ${attempts} attempt${attempts === 1 ? '' : 's'} remaining.`
|
|
2603
|
+
: 'Invalid code. Please try again.';
|
|
2604
|
+
break;
|
|
2605
|
+
}
|
|
2606
|
+
case 'otp_rate_limited':
|
|
2607
|
+
this.resetToIdentifierStep('Too many attempts. Please try again later.', 'red');
|
|
2608
|
+
break;
|
|
2609
|
+
case 'otp_not_found':
|
|
2610
|
+
this.resetToIdentifierStep('No code found. Please request a new one.', 'orange');
|
|
2611
|
+
break;
|
|
2612
|
+
default:
|
|
2613
|
+
this.error = verifyResult.message || 'Invalid code. Please try again.';
|
|
2614
|
+
}
|
|
2584
2615
|
return;
|
|
2585
2616
|
}
|
|
2586
2617
|
this.otpVerifiedToken = verifyResult.verified_token;
|
|
@@ -2660,8 +2691,23 @@ class TenantLoginComponent {
|
|
|
2660
2691
|
this.otpDigits = ['', '', '', '', '', ''];
|
|
2661
2692
|
this.otpVerifiedToken = '';
|
|
2662
2693
|
this.error = '';
|
|
2694
|
+
this.otpIdentifierError = '';
|
|
2663
2695
|
this.clearResendTimer();
|
|
2664
2696
|
}
|
|
2697
|
+
/**
|
|
2698
|
+
* Reset to the identifier entry step (step 1) with an error message.
|
|
2699
|
+
* Used when OTP verification fails with a code that requires re-sending
|
|
2700
|
+
* (expired, rate-limited, not-found). Keeps the identifier pre-filled.
|
|
2701
|
+
*/
|
|
2702
|
+
resetToIdentifierStep(message, color) {
|
|
2703
|
+
this.otpStep = 'identifier';
|
|
2704
|
+
this.otpDigits = ['', '', '', '', '', ''];
|
|
2705
|
+
this.otpVerifiedToken = '';
|
|
2706
|
+
this.error = '';
|
|
2707
|
+
this.clearResendTimer();
|
|
2708
|
+
this.otpIdentifierError = message;
|
|
2709
|
+
this.otpIdentifierErrorColor = color;
|
|
2710
|
+
}
|
|
2665
2711
|
// ── OTP digit input handling ─────────────────────────────────────────────
|
|
2666
2712
|
onOtpDigitInput(event, index) {
|
|
2667
2713
|
const input = event.target;
|
|
@@ -2705,15 +2751,46 @@ class TenantLoginComponent {
|
|
|
2705
2751
|
get otpCode() {
|
|
2706
2752
|
return this.otpDigits.join('');
|
|
2707
2753
|
}
|
|
2754
|
+
get otpPlaceholderText() {
|
|
2755
|
+
const allowsEmail = this.otpIdentifierTypes.includes('email');
|
|
2756
|
+
const allowsPhone = this.otpIdentifierTypes.includes('phone');
|
|
2757
|
+
if (allowsEmail && allowsPhone) {
|
|
2758
|
+
return 'Enter Email or Phone Number';
|
|
2759
|
+
}
|
|
2760
|
+
else if (allowsEmail) {
|
|
2761
|
+
return 'Enter Email';
|
|
2762
|
+
}
|
|
2763
|
+
else if (allowsPhone) {
|
|
2764
|
+
return 'Enter Phone Number';
|
|
2765
|
+
}
|
|
2766
|
+
return 'Enter Identifier';
|
|
2767
|
+
}
|
|
2768
|
+
get otpIdentifierName() {
|
|
2769
|
+
const allowsEmail = this.otpIdentifierTypes.includes('email');
|
|
2770
|
+
const allowsPhone = this.otpIdentifierTypes.includes('phone');
|
|
2771
|
+
if (allowsEmail && allowsPhone) {
|
|
2772
|
+
return 'email or phone number';
|
|
2773
|
+
}
|
|
2774
|
+
else if (allowsEmail) {
|
|
2775
|
+
return 'email';
|
|
2776
|
+
}
|
|
2777
|
+
else if (allowsPhone) {
|
|
2778
|
+
return 'phone number';
|
|
2779
|
+
}
|
|
2780
|
+
return 'identifier';
|
|
2781
|
+
}
|
|
2708
2782
|
// ── OTP helpers ──────────────────────────────────────────────────────────
|
|
2709
2783
|
detectIdentifierType(value) {
|
|
2784
|
+
const allowsEmail = this.otpIdentifierTypes.includes('email');
|
|
2785
|
+
const allowsPhone = this.otpIdentifierTypes.includes('phone');
|
|
2710
2786
|
if (value.includes('@')) {
|
|
2711
2787
|
// Basic email validation
|
|
2712
|
-
|
|
2788
|
+
const isValidEmail = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
|
|
2789
|
+
return (isValidEmail && allowsEmail) ? 'email' : null;
|
|
2713
2790
|
}
|
|
2714
2791
|
const digits = value.replace(/\D/g, '');
|
|
2715
2792
|
if (digits.length >= 10 && digits.length <= 15) {
|
|
2716
|
-
return 'phone';
|
|
2793
|
+
return allowsPhone ? 'phone' : null;
|
|
2717
2794
|
}
|
|
2718
2795
|
return null;
|
|
2719
2796
|
}
|
|
@@ -2775,7 +2852,7 @@ class TenantLoginComponent {
|
|
|
2775
2852
|
this.createTenant.emit();
|
|
2776
2853
|
}
|
|
2777
2854
|
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: `
|
|
2855
|
+
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
2856
|
<div class="tenant-login-dialog">
|
|
2780
2857
|
@if (!showingTenantSelector) {
|
|
2781
2858
|
<!-- Step 1: Authentication -->
|
|
@@ -2786,11 +2863,16 @@ class TenantLoginComponent {
|
|
|
2786
2863
|
<!-- OTP Step 1: Identifier entry -->
|
|
2787
2864
|
@if (otpStep === 'identifier') {
|
|
2788
2865
|
<form (ngSubmit)="onOtpSend()" class="otp-form">
|
|
2866
|
+
@if (otpIdentifierError) {
|
|
2867
|
+
<div [class]="'otp-identifier-error otp-identifier-error--' + otpIdentifierErrorColor">
|
|
2868
|
+
{{ otpIdentifierError }}
|
|
2869
|
+
</div>
|
|
2870
|
+
}
|
|
2789
2871
|
<div class="form-group">
|
|
2790
2872
|
<input
|
|
2791
2873
|
[(ngModel)]="otpIdentifier"
|
|
2792
2874
|
name="otpIdentifier"
|
|
2793
|
-
placeholder="
|
|
2875
|
+
[placeholder]="otpPlaceholderText"
|
|
2794
2876
|
type="text"
|
|
2795
2877
|
required
|
|
2796
2878
|
autocomplete="email tel"
|
|
@@ -2806,6 +2888,14 @@ class TenantLoginComponent {
|
|
|
2806
2888
|
class="btn btn-primary btn-block">
|
|
2807
2889
|
{{ loading ? 'Sending...' : 'Send OTP' }}
|
|
2808
2890
|
</button>
|
|
2891
|
+
@if (error && otpIdentifier.trim()) {
|
|
2892
|
+
<button
|
|
2893
|
+
type="button"
|
|
2894
|
+
class="btn btn-link btn-block otp-already-have"
|
|
2895
|
+
(click)="onSkipToOtpEntry()">
|
|
2896
|
+
I already have an OTP
|
|
2897
|
+
</button>
|
|
2898
|
+
}
|
|
2809
2899
|
</form>
|
|
2810
2900
|
}
|
|
2811
2901
|
|
|
@@ -2852,7 +2942,7 @@ class TenantLoginComponent {
|
|
|
2852
2942
|
</div>
|
|
2853
2943
|
<div class="otp-back">
|
|
2854
2944
|
<a href="#" (click)="onOtpBack($event)">
|
|
2855
|
-
Use a different
|
|
2945
|
+
Use a different {{ otpIdentifierName }}
|
|
2856
2946
|
</a>
|
|
2857
2947
|
</div>
|
|
2858
2948
|
</div>
|
|
@@ -3052,7 +3142,7 @@ class TenantLoginComponent {
|
|
|
3052
3142
|
</div>
|
|
3053
3143
|
}
|
|
3054
3144
|
</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"] }] });
|
|
3145
|
+
`, 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
3146
|
}
|
|
3057
3147
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantLoginComponent, decorators: [{
|
|
3058
3148
|
type: Component,
|
|
@@ -3067,11 +3157,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3067
3157
|
<!-- OTP Step 1: Identifier entry -->
|
|
3068
3158
|
@if (otpStep === 'identifier') {
|
|
3069
3159
|
<form (ngSubmit)="onOtpSend()" class="otp-form">
|
|
3160
|
+
@if (otpIdentifierError) {
|
|
3161
|
+
<div [class]="'otp-identifier-error otp-identifier-error--' + otpIdentifierErrorColor">
|
|
3162
|
+
{{ otpIdentifierError }}
|
|
3163
|
+
</div>
|
|
3164
|
+
}
|
|
3070
3165
|
<div class="form-group">
|
|
3071
3166
|
<input
|
|
3072
3167
|
[(ngModel)]="otpIdentifier"
|
|
3073
3168
|
name="otpIdentifier"
|
|
3074
|
-
placeholder="
|
|
3169
|
+
[placeholder]="otpPlaceholderText"
|
|
3075
3170
|
type="text"
|
|
3076
3171
|
required
|
|
3077
3172
|
autocomplete="email tel"
|
|
@@ -3087,6 +3182,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3087
3182
|
class="btn btn-primary btn-block">
|
|
3088
3183
|
{{ loading ? 'Sending...' : 'Send OTP' }}
|
|
3089
3184
|
</button>
|
|
3185
|
+
@if (error && otpIdentifier.trim()) {
|
|
3186
|
+
<button
|
|
3187
|
+
type="button"
|
|
3188
|
+
class="btn btn-link btn-block otp-already-have"
|
|
3189
|
+
(click)="onSkipToOtpEntry()">
|
|
3190
|
+
I already have an OTP
|
|
3191
|
+
</button>
|
|
3192
|
+
}
|
|
3090
3193
|
</form>
|
|
3091
3194
|
}
|
|
3092
3195
|
|
|
@@ -3133,7 +3236,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3133
3236
|
</div>
|
|
3134
3237
|
<div class="otp-back">
|
|
3135
3238
|
<a href="#" (click)="onOtpBack($event)">
|
|
3136
|
-
Use a different
|
|
3239
|
+
Use a different {{ otpIdentifierName }}
|
|
3137
3240
|
</a>
|
|
3138
3241
|
</div>
|
|
3139
3242
|
</div>
|
|
@@ -3333,7 +3436,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3333
3436
|
</div>
|
|
3334
3437
|
}
|
|
3335
3438
|
</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"] }]
|
|
3439
|
+
`, 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
3440
|
}], ctorParameters: () => [{ type: AuthService }, { type: ProviderRegistryService }], propDecorators: { otpInputs: [{
|
|
3338
3441
|
type: ViewChildren,
|
|
3339
3442
|
args: ['otpInput']
|
|
@@ -3349,6 +3452,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImpo
|
|
|
3349
3452
|
type: Input
|
|
3350
3453
|
}], allowTenantCreation: [{
|
|
3351
3454
|
type: Input
|
|
3455
|
+
}], otpIdentifierTypes: [{
|
|
3456
|
+
type: Input
|
|
3352
3457
|
}], tenantSelectorTitle: [{
|
|
3353
3458
|
type: Input
|
|
3354
3459
|
}], tenantSelectorDescription: [{
|
|
@@ -3817,7 +3922,7 @@ class AuthPageComponent {
|
|
|
3817
3922
|
}
|
|
3818
3923
|
</div>
|
|
3819
3924
|
</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"] }] });
|
|
3925
|
+
`, 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
3926
|
}
|
|
3822
3927
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AuthPageComponent, decorators: [{
|
|
3823
3928
|
type: Component,
|
|
@@ -5094,7 +5199,7 @@ class TenantLoginDialogComponent {
|
|
|
5094
5199
|
(createTenant)="onCreateTenant()">
|
|
5095
5200
|
</lib-tenant-login>
|
|
5096
5201
|
</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"] }] });
|
|
5202
|
+
`, 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
5203
|
}
|
|
5099
5204
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TenantLoginDialogComponent, decorators: [{
|
|
5100
5205
|
type: Component,
|