@progalaxyelabs/ngx-stonescriptphp-client 1.1.2 → 1.3.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,7 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, NgModule } from '@angular/core';
2
+ import { Injectable, Inject, NgModule, Input, Component, EventEmitter, Output, Optional } from '@angular/core';
3
3
  import { BehaviorSubject } from 'rxjs';
4
4
  import { CommonModule } from '@angular/common';
5
+ import * as i2 from '@angular/forms';
6
+ import { FormsModule } from '@angular/forms';
5
7
 
6
8
  class ApiResponse {
7
9
  status;
@@ -100,6 +102,14 @@ class TokenService {
100
102
  localStorage.removeItem(this.lsAccessTokenKey);
101
103
  localStorage.removeItem(this.lsRefreshTokenKey);
102
104
  }
105
+ /**
106
+ * Check if there is a valid (non-empty) access token
107
+ * @returns True if access token exists and is not empty
108
+ */
109
+ hasValidAccessToken() {
110
+ const token = this.getAccessToken();
111
+ return token !== null && token !== '';
112
+ }
103
113
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TokenService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
104
114
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TokenService, providedIn: 'root' });
105
115
  }
@@ -110,6 +120,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
110
120
  }]
111
121
  }], ctorParameters: () => [] });
112
122
 
123
+ /**
124
+ * @deprecated Use boolean directly. Kept for backward compatibility.
125
+ */
126
+ var VerifyStatus;
127
+ (function (VerifyStatus) {
128
+ VerifyStatus["initialized"] = "initialized";
129
+ VerifyStatus["yes"] = "yes";
130
+ VerifyStatus["no"] = "no";
131
+ })(VerifyStatus || (VerifyStatus = {}));
113
132
  class SigninStatusService {
114
133
  status;
115
134
  constructor() {
@@ -121,6 +140,13 @@ class SigninStatusService {
121
140
  signedIn() {
122
141
  this.status.next(true);
123
142
  }
143
+ /**
144
+ * Set signin status
145
+ * @param isSignedIn - True if user is signed in, false otherwise
146
+ */
147
+ setSigninStatus(isSignedIn) {
148
+ this.status.next(isSignedIn);
149
+ }
124
150
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SigninStatusService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
125
151
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SigninStatusService, providedIn: 'root' });
126
152
  }
@@ -133,6 +159,16 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
133
159
 
134
160
  class MyEnvironmentModel {
135
161
  production = true;
162
+ /**
163
+ * Platform code identifier (e.g., 'progalaxy', 'hr', 'admin')
164
+ * Used for multi-tenant authentication
165
+ */
166
+ platformCode = '';
167
+ /**
168
+ * Accounts platform URL for centralized authentication
169
+ * @example 'https://accounts.progalaxyelabs.com'
170
+ */
171
+ accountsUrl = '';
136
172
  firebase = {
137
173
  projectId: '',
138
174
  appId: '',
@@ -496,6 +532,36 @@ class ApiConnectionService {
496
532
  }
497
533
  return '';
498
534
  }
535
+ /**
536
+ * Upload a drawing (uses upload server if configured, otherwise API server)
537
+ * @deprecated Platform-specific method - consider moving to platform service
538
+ */
539
+ async uploadDrawing(formData) {
540
+ const uploadHost = this.environment.uploadServer?.host || this.host;
541
+ const url = uploadHost + 'upload/drawing';
542
+ const fetchOptions = {
543
+ method: 'POST',
544
+ mode: 'cors',
545
+ redirect: 'error',
546
+ body: formData
547
+ };
548
+ return this.request(url, fetchOptions, null);
549
+ }
550
+ /**
551
+ * Upload an image (uses upload server if configured, otherwise API server)
552
+ * @deprecated Platform-specific method - consider moving to platform service
553
+ */
554
+ async uploadImage(formData) {
555
+ const uploadHost = this.environment.uploadServer?.host || this.host;
556
+ const url = uploadHost + 'upload/image';
557
+ const fetchOptions = {
558
+ method: 'POST',
559
+ mode: 'cors',
560
+ redirect: 'error',
561
+ body: formData
562
+ };
563
+ return this.request(url, fetchOptions, null);
564
+ }
499
565
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ApiConnectionService, deps: [{ token: TokenService }, { token: SigninStatusService }, { token: MyEnvironmentModel }, { token: CsrfService }], target: i0.ɵɵFactoryTarget.Injectable });
500
566
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ApiConnectionService, providedIn: 'root' });
501
567
  }
@@ -507,8 +573,528 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
507
573
  }], ctorParameters: () => [{ type: TokenService }, { type: SigninStatusService }, { type: MyEnvironmentModel }, { type: CsrfService }] });
508
574
 
509
575
  class AuthService {
510
- constructor() { }
511
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AuthService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
576
+ tokens;
577
+ signinStatus;
578
+ environment;
579
+ // Observable user state
580
+ userSubject = new BehaviorSubject(null);
581
+ user$ = this.userSubject.asObservable();
582
+ constructor(tokens, signinStatus, environment) {
583
+ this.tokens = tokens;
584
+ this.signinStatus = signinStatus;
585
+ this.environment = environment;
586
+ }
587
+ /**
588
+ * Login with email and password
589
+ */
590
+ async loginWithEmail(email, password) {
591
+ try {
592
+ const response = await fetch(`${this.environment.accountsUrl}/api/auth/login`, {
593
+ method: 'POST',
594
+ headers: { 'Content-Type': 'application/json' },
595
+ credentials: 'include', // Include cookies for refresh token
596
+ body: JSON.stringify({
597
+ email,
598
+ password,
599
+ platform: this.environment.platformCode
600
+ })
601
+ });
602
+ const data = await response.json();
603
+ if (data.success && data.access_token) {
604
+ this.tokens.setAccessToken(data.access_token);
605
+ this.signinStatus.setSigninStatus(true);
606
+ this.userSubject.next(data.user);
607
+ return { success: true, user: data.user };
608
+ }
609
+ return {
610
+ success: false,
611
+ message: data.message || 'Invalid credentials'
612
+ };
613
+ }
614
+ catch (error) {
615
+ return {
616
+ success: false,
617
+ message: 'Network error. Please try again.'
618
+ };
619
+ }
620
+ }
621
+ /**
622
+ * Login with Google OAuth (popup window)
623
+ */
624
+ async loginWithGoogle() {
625
+ return this.loginWithOAuth('google');
626
+ }
627
+ /**
628
+ * Login with GitHub OAuth (popup window)
629
+ */
630
+ async loginWithGitHub() {
631
+ return this.loginWithOAuth('github');
632
+ }
633
+ /**
634
+ * Login with LinkedIn OAuth (popup window)
635
+ */
636
+ async loginWithLinkedIn() {
637
+ return this.loginWithOAuth('linkedin');
638
+ }
639
+ /**
640
+ * Login with Apple OAuth (popup window)
641
+ */
642
+ async loginWithApple() {
643
+ return this.loginWithOAuth('apple');
644
+ }
645
+ /**
646
+ * Login with Microsoft OAuth (popup window)
647
+ */
648
+ async loginWithMicrosoft() {
649
+ return this.loginWithOAuth('microsoft');
650
+ }
651
+ /**
652
+ * Generic provider-based login (supports all OAuth providers)
653
+ * @param provider - The provider identifier
654
+ */
655
+ async loginWithProvider(provider) {
656
+ if (provider === 'emailPassword') {
657
+ throw new Error('Use loginWithEmail() for email/password authentication');
658
+ }
659
+ return this.loginWithOAuth(provider);
660
+ }
661
+ /**
662
+ * Generic OAuth login handler
663
+ * Opens popup window and listens for postMessage
664
+ */
665
+ async loginWithOAuth(provider) {
666
+ return new Promise((resolve) => {
667
+ const width = 500;
668
+ const height = 600;
669
+ const left = (window.screen.width - width) / 2;
670
+ const top = (window.screen.height - height) / 2;
671
+ const oauthUrl = `${this.environment.accountsUrl}/oauth/${provider}?` +
672
+ `platform=${this.environment.platformCode}&` +
673
+ `mode=popup`;
674
+ const popup = window.open(oauthUrl, `${provider}_login`, `width=${width},height=${height},left=${left},top=${top}`);
675
+ if (!popup) {
676
+ resolve({
677
+ success: false,
678
+ message: 'Popup blocked. Please allow popups for this site.'
679
+ });
680
+ return;
681
+ }
682
+ // Listen for message from popup
683
+ const messageHandler = (event) => {
684
+ // Verify origin
685
+ if (event.origin !== new URL(this.environment.accountsUrl).origin) {
686
+ return;
687
+ }
688
+ if (event.data.type === 'oauth_success') {
689
+ this.tokens.setAccessToken(event.data.access_token);
690
+ this.signinStatus.setSigninStatus(true);
691
+ this.userSubject.next(event.data.user);
692
+ window.removeEventListener('message', messageHandler);
693
+ popup.close();
694
+ resolve({
695
+ success: true,
696
+ user: event.data.user
697
+ });
698
+ }
699
+ else if (event.data.type === 'oauth_error') {
700
+ window.removeEventListener('message', messageHandler);
701
+ popup.close();
702
+ resolve({
703
+ success: false,
704
+ message: event.data.message || 'OAuth login failed'
705
+ });
706
+ }
707
+ };
708
+ window.addEventListener('message', messageHandler);
709
+ // Check if popup was closed manually
710
+ const checkClosed = setInterval(() => {
711
+ if (popup.closed) {
712
+ clearInterval(checkClosed);
713
+ window.removeEventListener('message', messageHandler);
714
+ resolve({
715
+ success: false,
716
+ message: 'Login cancelled'
717
+ });
718
+ }
719
+ }, 500);
720
+ });
721
+ }
722
+ /**
723
+ * Register new user
724
+ */
725
+ async register(email, password, displayName) {
726
+ try {
727
+ const response = await fetch(`${this.environment.accountsUrl}/api/auth/register`, {
728
+ method: 'POST',
729
+ headers: { 'Content-Type': 'application/json' },
730
+ credentials: 'include',
731
+ body: JSON.stringify({
732
+ email,
733
+ password,
734
+ display_name: displayName,
735
+ platform: this.environment.platformCode
736
+ })
737
+ });
738
+ const data = await response.json();
739
+ if (data.success && data.access_token) {
740
+ this.tokens.setAccessToken(data.access_token);
741
+ this.signinStatus.setSigninStatus(true);
742
+ this.userSubject.next(data.user);
743
+ return {
744
+ success: true,
745
+ user: data.user,
746
+ message: data.needs_verification ? 'Please verify your email' : undefined
747
+ };
748
+ }
749
+ return {
750
+ success: false,
751
+ message: data.message || 'Registration failed'
752
+ };
753
+ }
754
+ catch (error) {
755
+ return {
756
+ success: false,
757
+ message: 'Network error. Please try again.'
758
+ };
759
+ }
760
+ }
761
+ /**
762
+ * Sign out user
763
+ */
764
+ async signout() {
765
+ try {
766
+ await fetch(`${this.environment.accountsUrl}/api/auth/logout`, {
767
+ method: 'POST',
768
+ credentials: 'include'
769
+ });
770
+ }
771
+ catch (error) {
772
+ console.error('Logout API call failed:', error);
773
+ }
774
+ finally {
775
+ this.tokens.clear();
776
+ this.signinStatus.setSigninStatus(false);
777
+ this.userSubject.next(null);
778
+ }
779
+ }
780
+ /**
781
+ * Check for active session (call on app init)
782
+ */
783
+ async checkSession() {
784
+ if (this.tokens.hasValidAccessToken()) {
785
+ this.signinStatus.setSigninStatus(true);
786
+ return true;
787
+ }
788
+ // Try to refresh using httpOnly cookie
789
+ try {
790
+ const response = await fetch(`${this.environment.accountsUrl}/api/auth/refresh`, {
791
+ method: 'POST',
792
+ credentials: 'include'
793
+ });
794
+ if (!response.ok) {
795
+ this.signinStatus.setSigninStatus(false);
796
+ return false;
797
+ }
798
+ const data = await response.json();
799
+ if (data.access_token) {
800
+ this.tokens.setAccessToken(data.access_token);
801
+ this.userSubject.next(data.user);
802
+ this.signinStatus.setSigninStatus(true);
803
+ return true;
804
+ }
805
+ return false;
806
+ }
807
+ catch (error) {
808
+ this.signinStatus.setSigninStatus(false);
809
+ return false;
810
+ }
811
+ }
812
+ /**
813
+ * Check if user is authenticated
814
+ */
815
+ isAuthenticated() {
816
+ return this.tokens.hasValidAccessToken();
817
+ }
818
+ /**
819
+ * Get current user (synchronous)
820
+ */
821
+ getCurrentUser() {
822
+ return this.userSubject.value;
823
+ }
824
+ // ===== Multi-Tenant Methods =====
825
+ /**
826
+ * Register a new user AND create a new tenant (organization)
827
+ * This is used when a user wants to create their own organization
828
+ */
829
+ async registerTenant(data) {
830
+ try {
831
+ // If using OAuth, initiate OAuth flow first
832
+ if (data.provider !== 'emailPassword') {
833
+ return await this.registerTenantWithOAuth(data.tenantName, data.tenantSlug, data.provider);
834
+ }
835
+ // Email/password registration
836
+ const response = await fetch(`${this.environment.accountsUrl}/api/auth/register-tenant`, {
837
+ method: 'POST',
838
+ headers: { 'Content-Type': 'application/json' },
839
+ credentials: 'include',
840
+ body: JSON.stringify({
841
+ platform: this.environment.platformCode,
842
+ tenant_name: data.tenantName,
843
+ tenant_slug: data.tenantSlug,
844
+ display_name: data.displayName,
845
+ email: data.email,
846
+ password: data.password,
847
+ provider: 'emailPassword'
848
+ })
849
+ });
850
+ const result = await response.json();
851
+ if (result.success && result.access_token) {
852
+ this.tokens.setAccessToken(result.access_token);
853
+ this.signinStatus.setSigninStatus(true);
854
+ if (result.user) {
855
+ this.userSubject.next(result.user);
856
+ }
857
+ }
858
+ return result;
859
+ }
860
+ catch (error) {
861
+ return {
862
+ success: false,
863
+ message: 'Network error. Please try again.'
864
+ };
865
+ }
866
+ }
867
+ /**
868
+ * Register tenant with OAuth provider
869
+ * Opens popup window for OAuth flow
870
+ */
871
+ async registerTenantWithOAuth(tenantName, tenantSlug, provider) {
872
+ return new Promise((resolve) => {
873
+ const width = 500;
874
+ const height = 600;
875
+ const left = (window.screen.width - width) / 2;
876
+ const top = (window.screen.height - height) / 2;
877
+ // Build OAuth URL with tenant registration params
878
+ const oauthUrl = `${this.environment.accountsUrl}/oauth/${provider}?` +
879
+ `platform=${this.environment.platformCode}&` +
880
+ `mode=popup&` +
881
+ `action=register_tenant&` +
882
+ `tenant_name=${encodeURIComponent(tenantName)}&` +
883
+ `tenant_slug=${encodeURIComponent(tenantSlug)}`;
884
+ const popup = window.open(oauthUrl, `${provider}_register_tenant`, `width=${width},height=${height},left=${left},top=${top}`);
885
+ if (!popup) {
886
+ resolve({
887
+ success: false,
888
+ message: 'Popup blocked. Please allow popups for this site.'
889
+ });
890
+ return;
891
+ }
892
+ // Listen for message from popup
893
+ const messageHandler = (event) => {
894
+ // Verify origin
895
+ if (event.origin !== new URL(this.environment.accountsUrl).origin) {
896
+ return;
897
+ }
898
+ if (event.data.type === 'tenant_register_success') {
899
+ // Set tokens and user
900
+ if (event.data.access_token) {
901
+ this.tokens.setAccessToken(event.data.access_token);
902
+ this.signinStatus.setSigninStatus(true);
903
+ }
904
+ if (event.data.user) {
905
+ this.userSubject.next(event.data.user);
906
+ }
907
+ window.removeEventListener('message', messageHandler);
908
+ popup.close();
909
+ resolve({
910
+ success: true,
911
+ tenant: event.data.tenant,
912
+ user: event.data.user
913
+ });
914
+ }
915
+ else if (event.data.type === 'tenant_register_error') {
916
+ window.removeEventListener('message', messageHandler);
917
+ popup.close();
918
+ resolve({
919
+ success: false,
920
+ message: event.data.message || 'Tenant registration failed'
921
+ });
922
+ }
923
+ };
924
+ window.addEventListener('message', messageHandler);
925
+ // Check if popup was closed manually
926
+ const checkClosed = setInterval(() => {
927
+ if (popup.closed) {
928
+ clearInterval(checkClosed);
929
+ window.removeEventListener('message', messageHandler);
930
+ resolve({
931
+ success: false,
932
+ message: 'Registration cancelled'
933
+ });
934
+ }
935
+ }, 500);
936
+ });
937
+ }
938
+ /**
939
+ * Get all tenant memberships for the authenticated user
940
+ */
941
+ async getTenantMemberships() {
942
+ try {
943
+ const response = await fetch(`${this.environment.accountsUrl}/api/auth/memberships`, {
944
+ method: 'GET',
945
+ headers: {
946
+ 'Authorization': `Bearer ${this.tokens.getAccessToken()}`,
947
+ 'Content-Type': 'application/json'
948
+ },
949
+ credentials: 'include'
950
+ });
951
+ const data = await response.json();
952
+ return {
953
+ memberships: data.memberships || []
954
+ };
955
+ }
956
+ catch (error) {
957
+ return { memberships: [] };
958
+ }
959
+ }
960
+ /**
961
+ * Select a tenant for the current session
962
+ * Updates the JWT token with tenant context
963
+ */
964
+ async selectTenant(tenantId) {
965
+ try {
966
+ const response = await fetch(`${this.environment.accountsUrl}/api/auth/select-tenant`, {
967
+ method: 'POST',
968
+ headers: {
969
+ 'Authorization': `Bearer ${this.tokens.getAccessToken()}`,
970
+ 'Content-Type': 'application/json'
971
+ },
972
+ credentials: 'include',
973
+ body: JSON.stringify({ tenant_id: tenantId })
974
+ });
975
+ const data = await response.json();
976
+ if (data.success && data.access_token) {
977
+ this.tokens.setAccessToken(data.access_token);
978
+ return {
979
+ success: true,
980
+ access_token: data.access_token
981
+ };
982
+ }
983
+ return {
984
+ success: false,
985
+ message: data.message || 'Failed to select tenant'
986
+ };
987
+ }
988
+ catch (error) {
989
+ return {
990
+ success: false,
991
+ message: 'Network error. Please try again.'
992
+ };
993
+ }
994
+ }
995
+ /**
996
+ * Check if a tenant slug is available
997
+ */
998
+ async checkTenantSlugAvailable(slug) {
999
+ try {
1000
+ const response = await fetch(`${this.environment.accountsUrl}/api/auth/check-tenant-slug/${slug}`, {
1001
+ method: 'GET',
1002
+ headers: { 'Content-Type': 'application/json' }
1003
+ });
1004
+ const data = await response.json();
1005
+ return {
1006
+ available: data.available || false,
1007
+ suggestion: data.suggestion
1008
+ };
1009
+ }
1010
+ catch (error) {
1011
+ // On error, assume available (don't block registration)
1012
+ return { available: true };
1013
+ }
1014
+ }
1015
+ // ===== Backward Compatibility Methods =====
1016
+ // These methods are deprecated and maintained for backward compatibility
1017
+ // with existing platform code. New code should use the methods above.
1018
+ /**
1019
+ * @deprecated Use getCurrentUser()?.user_id instead
1020
+ */
1021
+ getUserId() {
1022
+ return this.userSubject.value?.user_id || 0;
1023
+ }
1024
+ /**
1025
+ * @deprecated Use getCurrentUser()?.display_name instead
1026
+ */
1027
+ getUserName() {
1028
+ return this.userSubject.value?.display_name || '';
1029
+ }
1030
+ /**
1031
+ * @deprecated Use getCurrentUser()?.photo_url instead
1032
+ */
1033
+ getPhotoUrl() {
1034
+ return this.userSubject.value?.photo_url || '';
1035
+ }
1036
+ /**
1037
+ * @deprecated Use getCurrentUser()?.display_name instead
1038
+ */
1039
+ getDisplayName() {
1040
+ return this.userSubject.value?.display_name || '';
1041
+ }
1042
+ /**
1043
+ * @deprecated Use `/profile/${getCurrentUser()?.user_id}` instead
1044
+ */
1045
+ getProfileUrl() {
1046
+ const userId = this.userSubject.value?.user_id;
1047
+ return userId ? `/profile/${userId}` : '';
1048
+ }
1049
+ /**
1050
+ * @deprecated Use isAuthenticated() instead
1051
+ */
1052
+ async signin() {
1053
+ return this.isAuthenticated();
1054
+ }
1055
+ /**
1056
+ * @deprecated Use loginWithEmail() instead
1057
+ */
1058
+ async verifyCredentials(email, password) {
1059
+ const result = await this.loginWithEmail(email, password);
1060
+ return result.success;
1061
+ }
1062
+ /**
1063
+ * @deprecated Check user.is_email_verified from getCurrentUser() instead
1064
+ */
1065
+ isSigninEmailValid() {
1066
+ return this.userSubject.value?.is_email_verified || false;
1067
+ }
1068
+ /**
1069
+ * @deprecated No longer needed - dialog is managed by platform
1070
+ */
1071
+ onDialogClose() {
1072
+ // No-op for backward compatibility
1073
+ }
1074
+ /**
1075
+ * @deprecated No longer needed - dialog is managed by platform
1076
+ */
1077
+ closeSocialAuthDialog() {
1078
+ // No-op for backward compatibility
1079
+ }
1080
+ /**
1081
+ * @deprecated Check if user exists by calling /api/auth/check-email endpoint
1082
+ */
1083
+ async getUserProfile(email) {
1084
+ try {
1085
+ const response = await fetch(`${this.environment.accountsUrl}/api/auth/check-email`, {
1086
+ method: 'POST',
1087
+ headers: { 'Content-Type': 'application/json' },
1088
+ body: JSON.stringify({ email })
1089
+ });
1090
+ const data = await response.json();
1091
+ return data.exists ? data.user : null;
1092
+ }
1093
+ catch (error) {
1094
+ return null;
1095
+ }
1096
+ }
1097
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AuthService, deps: [{ token: TokenService }, { token: SigninStatusService }, { token: MyEnvironmentModel }], target: i0.ɵɵFactoryTarget.Injectable });
512
1098
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AuthService, providedIn: 'root' });
513
1099
  }
514
1100
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AuthService, decorators: [{
@@ -516,7 +1102,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
516
1102
  args: [{
517
1103
  providedIn: 'root'
518
1104
  }]
519
- }], ctorParameters: () => [] });
1105
+ }], ctorParameters: () => [{ type: TokenService }, { type: SigninStatusService }, { type: MyEnvironmentModel, decorators: [{
1106
+ type: Inject,
1107
+ args: [MyEnvironmentModel]
1108
+ }] }] });
520
1109
 
521
1110
  class DbService {
522
1111
  constructor() { }
@@ -553,6 +1142,1951 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
553
1142
  }]
554
1143
  }] });
555
1144
 
1145
+ class LoginDialogComponent {
1146
+ auth;
1147
+ /**
1148
+ * REQUIRED: Which authentication providers to show in this dialog
1149
+ * @example ['google', 'linkedin', 'emailPassword']
1150
+ */
1151
+ providers = [];
1152
+ email = '';
1153
+ password = '';
1154
+ error = '';
1155
+ loading = false;
1156
+ oauthProviders = [];
1157
+ constructor(auth) {
1158
+ this.auth = auth;
1159
+ }
1160
+ ngOnInit() {
1161
+ if (!this.providers || this.providers.length === 0) {
1162
+ this.error = 'Configuration Error: No authentication providers specified. Please pass providers to LoginDialogComponent.';
1163
+ throw new Error('LoginDialogComponent requires providers input. Example: dialogRef.componentInstance.providers = [\'google\', \'emailPassword\']');
1164
+ }
1165
+ // Get OAuth providers (excluding emailPassword)
1166
+ this.oauthProviders = this.providers
1167
+ .filter(p => p !== 'emailPassword');
1168
+ }
1169
+ isProviderEnabled(provider) {
1170
+ return this.providers.includes(provider);
1171
+ }
1172
+ getProviderLabel(provider) {
1173
+ const labels = {
1174
+ google: 'Sign in with Google',
1175
+ linkedin: 'Sign in with LinkedIn',
1176
+ apple: 'Sign in with Apple',
1177
+ microsoft: 'Sign in with Microsoft',
1178
+ github: 'Sign in with GitHub',
1179
+ emailPassword: 'Sign in with Email'
1180
+ };
1181
+ return labels[provider];
1182
+ }
1183
+ getProviderIcon(provider) {
1184
+ // Platforms can customize icons via CSS classes: .btn-google, .btn-linkedin, etc.
1185
+ return undefined;
1186
+ }
1187
+ async onEmailLogin() {
1188
+ if (!this.email || !this.password) {
1189
+ this.error = 'Please enter email and password';
1190
+ return;
1191
+ }
1192
+ this.loading = true;
1193
+ this.error = '';
1194
+ try {
1195
+ const result = await this.auth.loginWithEmail(this.email, this.password);
1196
+ if (!result.success) {
1197
+ this.error = result.message || 'Login failed';
1198
+ }
1199
+ // On success, parent component/dialog should close automatically via user$ subscription
1200
+ }
1201
+ catch (err) {
1202
+ this.error = 'An unexpected error occurred';
1203
+ }
1204
+ finally {
1205
+ this.loading = false;
1206
+ }
1207
+ }
1208
+ async onOAuthLogin(provider) {
1209
+ this.loading = true;
1210
+ this.error = '';
1211
+ try {
1212
+ const result = await this.auth.loginWithProvider(provider);
1213
+ if (!result.success) {
1214
+ this.error = result.message || 'OAuth login failed';
1215
+ }
1216
+ // On success, parent component/dialog should close automatically via user$ subscription
1217
+ }
1218
+ catch (err) {
1219
+ this.error = 'An unexpected error occurred';
1220
+ }
1221
+ finally {
1222
+ this.loading = false;
1223
+ }
1224
+ }
1225
+ onRegisterClick(event) {
1226
+ event.preventDefault();
1227
+ // Platforms can override this or listen for a custom event
1228
+ // For now, just emit a console message
1229
+ console.log('Register clicked - platform should handle navigation');
1230
+ }
1231
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: LoginDialogComponent, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Component });
1232
+ 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: `
1233
+ <div class="login-dialog">
1234
+ <h2 class="login-title">Sign In</h2>
1235
+
1236
+ <!-- Email/Password Form (if enabled) -->
1237
+ @if (isProviderEnabled('emailPassword')) {
1238
+ <form (ngSubmit)="onEmailLogin()" class="email-form">
1239
+ <div class="form-group">
1240
+ <input
1241
+ [(ngModel)]="email"
1242
+ name="email"
1243
+ placeholder="Email"
1244
+ type="email"
1245
+ required
1246
+ class="form-control">
1247
+ </div>
1248
+ <div class="form-group">
1249
+ <input
1250
+ [(ngModel)]="password"
1251
+ name="password"
1252
+ placeholder="Password"
1253
+ type="password"
1254
+ required
1255
+ class="form-control">
1256
+ </div>
1257
+ <button
1258
+ type="submit"
1259
+ [disabled]="loading"
1260
+ class="btn btn-primary btn-block">
1261
+ {{ loading ? 'Signing in...' : getProviderLabel('emailPassword') }}
1262
+ </button>
1263
+ </form>
1264
+ }
1265
+
1266
+ <!-- Divider if both email and OAuth are present -->
1267
+ @if (isProviderEnabled('emailPassword') && oauthProviders.length > 0) {
1268
+ <div class="divider">
1269
+ <span>OR</span>
1270
+ </div>
1271
+ }
1272
+
1273
+ <!-- OAuth Providers -->
1274
+ @if (oauthProviders.length > 0) {
1275
+ <div class="oauth-buttons">
1276
+ @for (provider of oauthProviders; track provider) {
1277
+ <button
1278
+ (click)="onOAuthLogin(provider)"
1279
+ [disabled]="loading"
1280
+ class="btn btn-oauth btn-{{ provider }}">
1281
+ @if (getProviderIcon(provider)) {
1282
+ <span class="oauth-icon">
1283
+ {{ getProviderIcon(provider) }}
1284
+ </span>
1285
+ }
1286
+ {{ getProviderLabel(provider) }}
1287
+ </button>
1288
+ }
1289
+ </div>
1290
+ }
1291
+
1292
+ <!-- Error Message -->
1293
+ @if (error) {
1294
+ <div class="error-message">
1295
+ {{ error }}
1296
+ </div>
1297
+ }
1298
+
1299
+ <!-- Loading State -->
1300
+ @if (loading) {
1301
+ <div class="loading-overlay">
1302
+ <div class="spinner"></div>
1303
+ </div>
1304
+ }
1305
+
1306
+ <!-- Register Link -->
1307
+ <div class="register-link">
1308
+ Don't have an account?
1309
+ <a href="#" (click)="onRegisterClick($event)">Sign up</a>
1310
+ </div>
1311
+ </div>
1312
+ `, 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"] }] });
1313
+ }
1314
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: LoginDialogComponent, decorators: [{
1315
+ type: Component,
1316
+ args: [{ selector: 'lib-login-dialog', standalone: true, imports: [CommonModule, FormsModule], template: `
1317
+ <div class="login-dialog">
1318
+ <h2 class="login-title">Sign In</h2>
1319
+
1320
+ <!-- Email/Password Form (if enabled) -->
1321
+ @if (isProviderEnabled('emailPassword')) {
1322
+ <form (ngSubmit)="onEmailLogin()" class="email-form">
1323
+ <div class="form-group">
1324
+ <input
1325
+ [(ngModel)]="email"
1326
+ name="email"
1327
+ placeholder="Email"
1328
+ type="email"
1329
+ required
1330
+ class="form-control">
1331
+ </div>
1332
+ <div class="form-group">
1333
+ <input
1334
+ [(ngModel)]="password"
1335
+ name="password"
1336
+ placeholder="Password"
1337
+ type="password"
1338
+ required
1339
+ class="form-control">
1340
+ </div>
1341
+ <button
1342
+ type="submit"
1343
+ [disabled]="loading"
1344
+ class="btn btn-primary btn-block">
1345
+ {{ loading ? 'Signing in...' : getProviderLabel('emailPassword') }}
1346
+ </button>
1347
+ </form>
1348
+ }
1349
+
1350
+ <!-- Divider if both email and OAuth are present -->
1351
+ @if (isProviderEnabled('emailPassword') && oauthProviders.length > 0) {
1352
+ <div class="divider">
1353
+ <span>OR</span>
1354
+ </div>
1355
+ }
1356
+
1357
+ <!-- OAuth Providers -->
1358
+ @if (oauthProviders.length > 0) {
1359
+ <div class="oauth-buttons">
1360
+ @for (provider of oauthProviders; track provider) {
1361
+ <button
1362
+ (click)="onOAuthLogin(provider)"
1363
+ [disabled]="loading"
1364
+ class="btn btn-oauth btn-{{ provider }}">
1365
+ @if (getProviderIcon(provider)) {
1366
+ <span class="oauth-icon">
1367
+ {{ getProviderIcon(provider) }}
1368
+ </span>
1369
+ }
1370
+ {{ getProviderLabel(provider) }}
1371
+ </button>
1372
+ }
1373
+ </div>
1374
+ }
1375
+
1376
+ <!-- Error Message -->
1377
+ @if (error) {
1378
+ <div class="error-message">
1379
+ {{ error }}
1380
+ </div>
1381
+ }
1382
+
1383
+ <!-- Loading State -->
1384
+ @if (loading) {
1385
+ <div class="loading-overlay">
1386
+ <div class="spinner"></div>
1387
+ </div>
1388
+ }
1389
+
1390
+ <!-- Register Link -->
1391
+ <div class="register-link">
1392
+ Don't have an account?
1393
+ <a href="#" (click)="onRegisterClick($event)">Sign up</a>
1394
+ </div>
1395
+ </div>
1396
+ `, 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"] }]
1397
+ }], ctorParameters: () => [{ type: AuthService }], propDecorators: { providers: [{
1398
+ type: Input
1399
+ }] } });
1400
+
1401
+ class RegisterComponent {
1402
+ auth;
1403
+ displayName = '';
1404
+ email = '';
1405
+ password = '';
1406
+ confirmPassword = '';
1407
+ error = '';
1408
+ success = '';
1409
+ loading = false;
1410
+ constructor(auth) {
1411
+ this.auth = auth;
1412
+ }
1413
+ async onRegister() {
1414
+ // Reset messages
1415
+ this.error = '';
1416
+ this.success = '';
1417
+ // Validate fields
1418
+ if (!this.displayName || !this.email || !this.password || !this.confirmPassword) {
1419
+ this.error = 'Please fill in all fields';
1420
+ return;
1421
+ }
1422
+ if (this.password.length < 8) {
1423
+ this.error = 'Password must be at least 8 characters';
1424
+ return;
1425
+ }
1426
+ if (this.password !== this.confirmPassword) {
1427
+ this.error = 'Passwords do not match';
1428
+ return;
1429
+ }
1430
+ // Validate email format
1431
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1432
+ if (!emailRegex.test(this.email)) {
1433
+ this.error = 'Please enter a valid email address';
1434
+ return;
1435
+ }
1436
+ this.loading = true;
1437
+ try {
1438
+ const result = await this.auth.register(this.email, this.password, this.displayName);
1439
+ if (result.success) {
1440
+ this.success = result.message || 'Account created successfully!';
1441
+ // On success, parent component/dialog should close automatically via user$ subscription
1442
+ // or navigate to email verification page
1443
+ }
1444
+ else {
1445
+ this.error = result.message || 'Registration failed';
1446
+ }
1447
+ }
1448
+ catch (err) {
1449
+ this.error = 'An unexpected error occurred';
1450
+ }
1451
+ finally {
1452
+ this.loading = false;
1453
+ }
1454
+ }
1455
+ onLoginClick(event) {
1456
+ event.preventDefault();
1457
+ // Platforms can override this or listen for a custom event
1458
+ // For now, just emit a console message
1459
+ console.log('Login clicked - platform should handle navigation');
1460
+ }
1461
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RegisterComponent, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Component });
1462
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: RegisterComponent, isStandalone: true, selector: "lib-register", ngImport: i0, template: `
1463
+ <div class="register-dialog">
1464
+ <h2 class="register-title">Create Account</h2>
1465
+
1466
+ <form (ngSubmit)="onRegister()" class="register-form">
1467
+ <div class="form-group">
1468
+ <label for="displayName">Full Name</label>
1469
+ <input
1470
+ id="displayName"
1471
+ [(ngModel)]="displayName"
1472
+ name="displayName"
1473
+ placeholder="Enter your full name"
1474
+ type="text"
1475
+ required
1476
+ class="form-control">
1477
+ </div>
1478
+
1479
+ <div class="form-group">
1480
+ <label for="email">Email</label>
1481
+ <input
1482
+ id="email"
1483
+ [(ngModel)]="email"
1484
+ name="email"
1485
+ placeholder="Enter your email"
1486
+ type="email"
1487
+ required
1488
+ class="form-control">
1489
+ </div>
1490
+
1491
+ <div class="form-group">
1492
+ <label for="password">Password</label>
1493
+ <input
1494
+ id="password"
1495
+ [(ngModel)]="password"
1496
+ name="password"
1497
+ placeholder="Create a password"
1498
+ type="password"
1499
+ required
1500
+ minlength="8"
1501
+ class="form-control">
1502
+ <small class="form-hint">At least 8 characters</small>
1503
+ </div>
1504
+
1505
+ <div class="form-group">
1506
+ <label for="confirmPassword">Confirm Password</label>
1507
+ <input
1508
+ id="confirmPassword"
1509
+ [(ngModel)]="confirmPassword"
1510
+ name="confirmPassword"
1511
+ placeholder="Confirm your password"
1512
+ type="password"
1513
+ required
1514
+ class="form-control">
1515
+ </div>
1516
+
1517
+ <button
1518
+ type="submit"
1519
+ [disabled]="loading"
1520
+ class="btn btn-primary btn-block">
1521
+ {{ loading ? 'Creating account...' : 'Sign Up' }}
1522
+ </button>
1523
+ </form>
1524
+
1525
+ <!-- Error Message -->
1526
+ @if (error) {
1527
+ <div class="error-message">
1528
+ {{ error }}
1529
+ </div>
1530
+ }
1531
+
1532
+ <!-- Success Message -->
1533
+ @if (success) {
1534
+ <div class="success-message">
1535
+ {{ success }}
1536
+ </div>
1537
+ }
1538
+
1539
+ <!-- Loading State -->
1540
+ @if (loading) {
1541
+ <div class="loading-overlay">
1542
+ <div class="spinner"></div>
1543
+ </div>
1544
+ }
1545
+
1546
+ <!-- Login Link -->
1547
+ <div class="login-link">
1548
+ Already have an account?
1549
+ <a href="#" (click)="onLoginClick($event)">Sign in</a>
1550
+ </div>
1551
+ </div>
1552
+ `, 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"] }] });
1553
+ }
1554
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RegisterComponent, decorators: [{
1555
+ type: Component,
1556
+ args: [{ selector: 'lib-register', standalone: true, imports: [CommonModule, FormsModule], template: `
1557
+ <div class="register-dialog">
1558
+ <h2 class="register-title">Create Account</h2>
1559
+
1560
+ <form (ngSubmit)="onRegister()" class="register-form">
1561
+ <div class="form-group">
1562
+ <label for="displayName">Full Name</label>
1563
+ <input
1564
+ id="displayName"
1565
+ [(ngModel)]="displayName"
1566
+ name="displayName"
1567
+ placeholder="Enter your full name"
1568
+ type="text"
1569
+ required
1570
+ class="form-control">
1571
+ </div>
1572
+
1573
+ <div class="form-group">
1574
+ <label for="email">Email</label>
1575
+ <input
1576
+ id="email"
1577
+ [(ngModel)]="email"
1578
+ name="email"
1579
+ placeholder="Enter your email"
1580
+ type="email"
1581
+ required
1582
+ class="form-control">
1583
+ </div>
1584
+
1585
+ <div class="form-group">
1586
+ <label for="password">Password</label>
1587
+ <input
1588
+ id="password"
1589
+ [(ngModel)]="password"
1590
+ name="password"
1591
+ placeholder="Create a password"
1592
+ type="password"
1593
+ required
1594
+ minlength="8"
1595
+ class="form-control">
1596
+ <small class="form-hint">At least 8 characters</small>
1597
+ </div>
1598
+
1599
+ <div class="form-group">
1600
+ <label for="confirmPassword">Confirm Password</label>
1601
+ <input
1602
+ id="confirmPassword"
1603
+ [(ngModel)]="confirmPassword"
1604
+ name="confirmPassword"
1605
+ placeholder="Confirm your password"
1606
+ type="password"
1607
+ required
1608
+ class="form-control">
1609
+ </div>
1610
+
1611
+ <button
1612
+ type="submit"
1613
+ [disabled]="loading"
1614
+ class="btn btn-primary btn-block">
1615
+ {{ loading ? 'Creating account...' : 'Sign Up' }}
1616
+ </button>
1617
+ </form>
1618
+
1619
+ <!-- Error Message -->
1620
+ @if (error) {
1621
+ <div class="error-message">
1622
+ {{ error }}
1623
+ </div>
1624
+ }
1625
+
1626
+ <!-- Success Message -->
1627
+ @if (success) {
1628
+ <div class="success-message">
1629
+ {{ success }}
1630
+ </div>
1631
+ }
1632
+
1633
+ <!-- Loading State -->
1634
+ @if (loading) {
1635
+ <div class="loading-overlay">
1636
+ <div class="spinner"></div>
1637
+ </div>
1638
+ }
1639
+
1640
+ <!-- Login Link -->
1641
+ <div class="login-link">
1642
+ Already have an account?
1643
+ <a href="#" (click)="onLoginClick($event)">Sign in</a>
1644
+ </div>
1645
+ </div>
1646
+ `, 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"] }]
1647
+ }], ctorParameters: () => [{ type: AuthService }] });
1648
+
1649
+ class TenantLoginComponent {
1650
+ auth;
1651
+ // Component Configuration
1652
+ title = 'Sign In';
1653
+ providers = ['google'];
1654
+ showTenantSelector = true;
1655
+ autoSelectSingleTenant = true;
1656
+ allowTenantCreation = true;
1657
+ // Tenant Selector Labels
1658
+ tenantSelectorTitle = 'Select Organization';
1659
+ tenantSelectorDescription = 'Choose which organization you want to access:';
1660
+ continueButtonText = 'Continue';
1661
+ // Link Labels
1662
+ registerLinkText = "Don't have an account?";
1663
+ registerLinkAction = 'Sign up';
1664
+ createTenantLinkText = "Don't see your organization?";
1665
+ createTenantLinkAction = 'Create New Organization';
1666
+ // Outputs
1667
+ tenantSelected = new EventEmitter();
1668
+ createTenant = new EventEmitter();
1669
+ // Form Fields
1670
+ email = '';
1671
+ password = '';
1672
+ // State
1673
+ error = '';
1674
+ loading = false;
1675
+ useOAuth = true;
1676
+ oauthProviders = [];
1677
+ // Tenant Selection State
1678
+ showingTenantSelector = false;
1679
+ memberships = [];
1680
+ selectedTenantId = null;
1681
+ userName = '';
1682
+ constructor(auth) {
1683
+ this.auth = auth;
1684
+ }
1685
+ ngOnInit() {
1686
+ if (!this.providers || this.providers.length === 0) {
1687
+ this.error = 'Configuration Error: No authentication providers specified.';
1688
+ throw new Error('TenantLoginComponent requires providers input.');
1689
+ }
1690
+ this.oauthProviders = this.providers.filter(p => p !== 'emailPassword');
1691
+ // If only emailPassword is available, use it by default
1692
+ if (this.oauthProviders.length === 0 && this.isProviderEnabled('emailPassword')) {
1693
+ this.useOAuth = false;
1694
+ }
1695
+ }
1696
+ isProviderEnabled(provider) {
1697
+ return this.providers.includes(provider);
1698
+ }
1699
+ getProviderLabel(provider) {
1700
+ const labels = {
1701
+ google: 'Sign in with Google',
1702
+ linkedin: 'Sign in with LinkedIn',
1703
+ apple: 'Sign in with Apple',
1704
+ microsoft: 'Sign in with Microsoft',
1705
+ github: 'Sign in with GitHub',
1706
+ emailPassword: 'Sign in with Email'
1707
+ };
1708
+ return labels[provider];
1709
+ }
1710
+ getProviderIcon(provider) {
1711
+ return undefined;
1712
+ }
1713
+ toggleAuthMethod(event) {
1714
+ event.preventDefault();
1715
+ this.useOAuth = !this.useOAuth;
1716
+ this.error = '';
1717
+ }
1718
+ async onEmailLogin() {
1719
+ if (!this.email || !this.password) {
1720
+ this.error = 'Please enter email and password';
1721
+ return;
1722
+ }
1723
+ this.loading = true;
1724
+ this.error = '';
1725
+ try {
1726
+ const result = await this.auth.loginWithEmail(this.email, this.password);
1727
+ if (!result.success) {
1728
+ this.error = result.message || 'Login failed';
1729
+ return;
1730
+ }
1731
+ // Authentication successful, now handle tenant selection
1732
+ await this.handlePostAuthFlow();
1733
+ }
1734
+ catch (err) {
1735
+ this.error = err.message || 'An unexpected error occurred';
1736
+ }
1737
+ finally {
1738
+ this.loading = false;
1739
+ }
1740
+ }
1741
+ async onOAuthLogin(provider) {
1742
+ this.loading = true;
1743
+ this.error = '';
1744
+ try {
1745
+ const result = await this.auth.loginWithProvider(provider);
1746
+ if (!result.success) {
1747
+ this.error = result.message || 'OAuth login failed';
1748
+ return;
1749
+ }
1750
+ // Authentication successful, now handle tenant selection
1751
+ await this.handlePostAuthFlow();
1752
+ }
1753
+ catch (err) {
1754
+ this.error = err.message || 'An unexpected error occurred';
1755
+ }
1756
+ finally {
1757
+ this.loading = false;
1758
+ }
1759
+ }
1760
+ async handlePostAuthFlow() {
1761
+ if (!this.showTenantSelector) {
1762
+ // Tenant selection is disabled, emit event immediately
1763
+ this.tenantSelected.emit({
1764
+ tenantId: '',
1765
+ tenantSlug: '',
1766
+ role: ''
1767
+ });
1768
+ return;
1769
+ }
1770
+ // Fetch user's tenant memberships
1771
+ this.loading = true;
1772
+ try {
1773
+ const result = await this.auth.getTenantMemberships();
1774
+ if (!result.memberships || result.memberships.length === 0) {
1775
+ // User has no tenants, prompt to create one
1776
+ this.error = 'You are not a member of any organization. Please create one.';
1777
+ if (this.allowTenantCreation) {
1778
+ setTimeout(() => this.createTenant.emit(), 2000);
1779
+ }
1780
+ return;
1781
+ }
1782
+ this.memberships = result.memberships;
1783
+ // Get user name if available
1784
+ const currentUser = this.auth.getCurrentUser();
1785
+ if (currentUser) {
1786
+ this.userName = currentUser.display_name || currentUser.email;
1787
+ }
1788
+ // Auto-select if user has only one tenant
1789
+ if (this.memberships.length === 1 && this.autoSelectSingleTenant) {
1790
+ await this.selectAndContinue(this.memberships[0]);
1791
+ }
1792
+ else {
1793
+ // Show tenant selector
1794
+ this.showingTenantSelector = true;
1795
+ }
1796
+ }
1797
+ catch (err) {
1798
+ this.error = err.message || 'Failed to load organizations';
1799
+ }
1800
+ finally {
1801
+ this.loading = false;
1802
+ }
1803
+ }
1804
+ selectTenantItem(tenantId) {
1805
+ this.selectedTenantId = tenantId;
1806
+ }
1807
+ async onContinueWithTenant() {
1808
+ if (!this.selectedTenantId) {
1809
+ this.error = 'Please select an organization';
1810
+ return;
1811
+ }
1812
+ const membership = this.memberships.find(m => m.tenant_id === this.selectedTenantId);
1813
+ if (!membership) {
1814
+ this.error = 'Selected organization not found';
1815
+ return;
1816
+ }
1817
+ await this.selectAndContinue(membership);
1818
+ }
1819
+ async selectAndContinue(membership) {
1820
+ this.loading = true;
1821
+ this.error = '';
1822
+ try {
1823
+ const result = await this.auth.selectTenant(membership.tenant_id);
1824
+ if (!result.success) {
1825
+ this.error = result.message || 'Failed to select organization';
1826
+ return;
1827
+ }
1828
+ // Emit tenant selected event
1829
+ this.tenantSelected.emit({
1830
+ tenantId: membership.tenant_id,
1831
+ tenantSlug: membership.slug,
1832
+ role: membership.role
1833
+ });
1834
+ }
1835
+ catch (err) {
1836
+ this.error = err.message || 'An unexpected error occurred';
1837
+ }
1838
+ finally {
1839
+ this.loading = false;
1840
+ }
1841
+ }
1842
+ formatRole(role) {
1843
+ return role.charAt(0).toUpperCase() + role.slice(1);
1844
+ }
1845
+ formatLastAccessed(dateStr) {
1846
+ try {
1847
+ const date = new Date(dateStr);
1848
+ const now = new Date();
1849
+ const diffMs = now.getTime() - date.getTime();
1850
+ const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
1851
+ if (diffDays === 0)
1852
+ return 'today';
1853
+ if (diffDays === 1)
1854
+ return 'yesterday';
1855
+ if (diffDays < 7)
1856
+ return `${diffDays} days ago`;
1857
+ if (diffDays < 30)
1858
+ return `${Math.floor(diffDays / 7)} weeks ago`;
1859
+ return `${Math.floor(diffDays / 30)} months ago`;
1860
+ }
1861
+ catch {
1862
+ return dateStr;
1863
+ }
1864
+ }
1865
+ onCreateTenantClick(event) {
1866
+ event.preventDefault();
1867
+ this.createTenant.emit();
1868
+ }
1869
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantLoginComponent, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Component });
1870
+ 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: `
1871
+ <div class="tenant-login-dialog">
1872
+ @if (!showingTenantSelector) {
1873
+ <!-- Step 1: Authentication -->
1874
+ <h2 class="login-title">{{ title }}</h2>
1875
+
1876
+ <!-- Email/Password Form (if enabled) -->
1877
+ @if (isProviderEnabled('emailPassword') && !useOAuth) {
1878
+ <form (ngSubmit)="onEmailLogin()" class="email-form">
1879
+ <div class="form-group">
1880
+ <input
1881
+ [(ngModel)]="email"
1882
+ name="email"
1883
+ placeholder="Email"
1884
+ type="email"
1885
+ required
1886
+ class="form-control">
1887
+ </div>
1888
+ <div class="form-group">
1889
+ <input
1890
+ [(ngModel)]="password"
1891
+ name="password"
1892
+ placeholder="Password"
1893
+ type="password"
1894
+ required
1895
+ class="form-control">
1896
+ </div>
1897
+ <button
1898
+ type="submit"
1899
+ [disabled]="loading"
1900
+ class="btn btn-primary btn-block">
1901
+ {{ loading ? 'Signing in...' : 'Sign in with Email' }}
1902
+ </button>
1903
+ </form>
1904
+
1905
+ <!-- Divider -->
1906
+ @if (oauthProviders.length > 0) {
1907
+ <div class="divider">
1908
+ <span>OR</span>
1909
+ </div>
1910
+ }
1911
+ }
1912
+
1913
+ <!-- OAuth Providers -->
1914
+ @if (oauthProviders.length > 0 && (useOAuth || !isProviderEnabled('emailPassword'))) {
1915
+ <div class="oauth-buttons">
1916
+ @for (provider of oauthProviders; track provider) {
1917
+ <button
1918
+ type="button"
1919
+ (click)="onOAuthLogin(provider)"
1920
+ [disabled]="loading"
1921
+ class="btn btn-oauth btn-{{ provider }}">
1922
+ @if (getProviderIcon(provider)) {
1923
+ <span class="oauth-icon">
1924
+ {{ getProviderIcon(provider) }}
1925
+ </span>
1926
+ }
1927
+ {{ getProviderLabel(provider) }}
1928
+ </button>
1929
+ }
1930
+ </div>
1931
+
1932
+ <!-- Switch to Email/Password -->
1933
+ @if (isProviderEnabled('emailPassword') && oauthProviders.length > 0) {
1934
+ <div class="switch-method">
1935
+ <a href="#" (click)="toggleAuthMethod($event)">
1936
+ {{ useOAuth ? 'Use email/password instead' : 'Use OAuth instead' }}
1937
+ </a>
1938
+ </div>
1939
+ }
1940
+ }
1941
+
1942
+ <!-- Error Message -->
1943
+ @if (error) {
1944
+ <div class="error-message">
1945
+ {{ error }}
1946
+ </div>
1947
+ }
1948
+
1949
+ <!-- Register Link -->
1950
+ @if (allowTenantCreation) {
1951
+ <div class="register-link">
1952
+ {{ registerLinkText }}
1953
+ <a href="#" (click)="onCreateTenantClick($event)">{{ registerLinkAction }}</a>
1954
+ </div>
1955
+ }
1956
+ } @else {
1957
+ <!-- Step 2: Tenant Selection -->
1958
+ <h2 class="login-title">{{ tenantSelectorTitle }}</h2>
1959
+
1960
+ @if (userName) {
1961
+ <div class="welcome-message">
1962
+ Welcome back, <strong>{{ userName }}</strong>!
1963
+ </div>
1964
+ }
1965
+
1966
+ <p class="selector-description">{{ tenantSelectorDescription }}</p>
1967
+
1968
+ <div class="tenant-list">
1969
+ @for (membership of memberships; track membership.tenant_id) {
1970
+ <div
1971
+ class="tenant-item"
1972
+ [class.selected]="selectedTenantId === membership.tenant_id"
1973
+ (click)="selectTenantItem(membership.tenant_id)">
1974
+ <div class="tenant-radio">
1975
+ <input
1976
+ type="radio"
1977
+ [checked]="selectedTenantId === membership.tenant_id"
1978
+ [name]="'tenant-' + membership.tenant_id"
1979
+ [id]="'tenant-' + membership.tenant_id">
1980
+ </div>
1981
+ <div class="tenant-info">
1982
+ <div class="tenant-name">{{ membership.name }}</div>
1983
+ <div class="tenant-meta">
1984
+ <span class="tenant-role">{{ formatRole(membership.role) }}</span>
1985
+ @if (membership.last_accessed) {
1986
+ <span class="tenant-separator">·</span>
1987
+ <span class="tenant-last-accessed">
1988
+ Last accessed {{ formatLastAccessed(membership.last_accessed) }}
1989
+ </span>
1990
+ }
1991
+ </div>
1992
+ </div>
1993
+ </div>
1994
+ }
1995
+ </div>
1996
+
1997
+ <button
1998
+ type="button"
1999
+ (click)="onContinueWithTenant()"
2000
+ [disabled]="!selectedTenantId || loading"
2001
+ class="btn btn-primary btn-block">
2002
+ {{ loading ? 'Loading...' : continueButtonText }}
2003
+ </button>
2004
+
2005
+ <!-- Error Message -->
2006
+ @if (error) {
2007
+ <div class="error-message">
2008
+ {{ error }}
2009
+ </div>
2010
+ }
2011
+
2012
+ <!-- Create New Tenant Link -->
2013
+ @if (allowTenantCreation) {
2014
+ <div class="create-tenant-link">
2015
+ {{ createTenantLinkText }}
2016
+ <a href="#" (click)="onCreateTenantClick($event)">{{ createTenantLinkAction }}</a>
2017
+ </div>
2018
+ }
2019
+ }
2020
+
2021
+ <!-- Loading Overlay -->
2022
+ @if (loading) {
2023
+ <div class="loading-overlay">
2024
+ <div class="spinner"></div>
2025
+ </div>
2026
+ }
2027
+ </div>
2028
+ `, 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"] }] });
2029
+ }
2030
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantLoginComponent, decorators: [{
2031
+ type: Component,
2032
+ args: [{ selector: 'lib-tenant-login', standalone: true, imports: [CommonModule, FormsModule], template: `
2033
+ <div class="tenant-login-dialog">
2034
+ @if (!showingTenantSelector) {
2035
+ <!-- Step 1: Authentication -->
2036
+ <h2 class="login-title">{{ title }}</h2>
2037
+
2038
+ <!-- Email/Password Form (if enabled) -->
2039
+ @if (isProviderEnabled('emailPassword') && !useOAuth) {
2040
+ <form (ngSubmit)="onEmailLogin()" class="email-form">
2041
+ <div class="form-group">
2042
+ <input
2043
+ [(ngModel)]="email"
2044
+ name="email"
2045
+ placeholder="Email"
2046
+ type="email"
2047
+ required
2048
+ class="form-control">
2049
+ </div>
2050
+ <div class="form-group">
2051
+ <input
2052
+ [(ngModel)]="password"
2053
+ name="password"
2054
+ placeholder="Password"
2055
+ type="password"
2056
+ required
2057
+ class="form-control">
2058
+ </div>
2059
+ <button
2060
+ type="submit"
2061
+ [disabled]="loading"
2062
+ class="btn btn-primary btn-block">
2063
+ {{ loading ? 'Signing in...' : 'Sign in with Email' }}
2064
+ </button>
2065
+ </form>
2066
+
2067
+ <!-- Divider -->
2068
+ @if (oauthProviders.length > 0) {
2069
+ <div class="divider">
2070
+ <span>OR</span>
2071
+ </div>
2072
+ }
2073
+ }
2074
+
2075
+ <!-- OAuth Providers -->
2076
+ @if (oauthProviders.length > 0 && (useOAuth || !isProviderEnabled('emailPassword'))) {
2077
+ <div class="oauth-buttons">
2078
+ @for (provider of oauthProviders; track provider) {
2079
+ <button
2080
+ type="button"
2081
+ (click)="onOAuthLogin(provider)"
2082
+ [disabled]="loading"
2083
+ class="btn btn-oauth btn-{{ provider }}">
2084
+ @if (getProviderIcon(provider)) {
2085
+ <span class="oauth-icon">
2086
+ {{ getProviderIcon(provider) }}
2087
+ </span>
2088
+ }
2089
+ {{ getProviderLabel(provider) }}
2090
+ </button>
2091
+ }
2092
+ </div>
2093
+
2094
+ <!-- Switch to Email/Password -->
2095
+ @if (isProviderEnabled('emailPassword') && oauthProviders.length > 0) {
2096
+ <div class="switch-method">
2097
+ <a href="#" (click)="toggleAuthMethod($event)">
2098
+ {{ useOAuth ? 'Use email/password instead' : 'Use OAuth instead' }}
2099
+ </a>
2100
+ </div>
2101
+ }
2102
+ }
2103
+
2104
+ <!-- Error Message -->
2105
+ @if (error) {
2106
+ <div class="error-message">
2107
+ {{ error }}
2108
+ </div>
2109
+ }
2110
+
2111
+ <!-- Register Link -->
2112
+ @if (allowTenantCreation) {
2113
+ <div class="register-link">
2114
+ {{ registerLinkText }}
2115
+ <a href="#" (click)="onCreateTenantClick($event)">{{ registerLinkAction }}</a>
2116
+ </div>
2117
+ }
2118
+ } @else {
2119
+ <!-- Step 2: Tenant Selection -->
2120
+ <h2 class="login-title">{{ tenantSelectorTitle }}</h2>
2121
+
2122
+ @if (userName) {
2123
+ <div class="welcome-message">
2124
+ Welcome back, <strong>{{ userName }}</strong>!
2125
+ </div>
2126
+ }
2127
+
2128
+ <p class="selector-description">{{ tenantSelectorDescription }}</p>
2129
+
2130
+ <div class="tenant-list">
2131
+ @for (membership of memberships; track membership.tenant_id) {
2132
+ <div
2133
+ class="tenant-item"
2134
+ [class.selected]="selectedTenantId === membership.tenant_id"
2135
+ (click)="selectTenantItem(membership.tenant_id)">
2136
+ <div class="tenant-radio">
2137
+ <input
2138
+ type="radio"
2139
+ [checked]="selectedTenantId === membership.tenant_id"
2140
+ [name]="'tenant-' + membership.tenant_id"
2141
+ [id]="'tenant-' + membership.tenant_id">
2142
+ </div>
2143
+ <div class="tenant-info">
2144
+ <div class="tenant-name">{{ membership.name }}</div>
2145
+ <div class="tenant-meta">
2146
+ <span class="tenant-role">{{ formatRole(membership.role) }}</span>
2147
+ @if (membership.last_accessed) {
2148
+ <span class="tenant-separator">·</span>
2149
+ <span class="tenant-last-accessed">
2150
+ Last accessed {{ formatLastAccessed(membership.last_accessed) }}
2151
+ </span>
2152
+ }
2153
+ </div>
2154
+ </div>
2155
+ </div>
2156
+ }
2157
+ </div>
2158
+
2159
+ <button
2160
+ type="button"
2161
+ (click)="onContinueWithTenant()"
2162
+ [disabled]="!selectedTenantId || loading"
2163
+ class="btn btn-primary btn-block">
2164
+ {{ loading ? 'Loading...' : continueButtonText }}
2165
+ </button>
2166
+
2167
+ <!-- Error Message -->
2168
+ @if (error) {
2169
+ <div class="error-message">
2170
+ {{ error }}
2171
+ </div>
2172
+ }
2173
+
2174
+ <!-- Create New Tenant Link -->
2175
+ @if (allowTenantCreation) {
2176
+ <div class="create-tenant-link">
2177
+ {{ createTenantLinkText }}
2178
+ <a href="#" (click)="onCreateTenantClick($event)">{{ createTenantLinkAction }}</a>
2179
+ </div>
2180
+ }
2181
+ }
2182
+
2183
+ <!-- Loading Overlay -->
2184
+ @if (loading) {
2185
+ <div class="loading-overlay">
2186
+ <div class="spinner"></div>
2187
+ </div>
2188
+ }
2189
+ </div>
2190
+ `, 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"] }]
2191
+ }], ctorParameters: () => [{ type: AuthService }], propDecorators: { title: [{
2192
+ type: Input
2193
+ }], providers: [{
2194
+ type: Input
2195
+ }], showTenantSelector: [{
2196
+ type: Input
2197
+ }], autoSelectSingleTenant: [{
2198
+ type: Input
2199
+ }], allowTenantCreation: [{
2200
+ type: Input
2201
+ }], tenantSelectorTitle: [{
2202
+ type: Input
2203
+ }], tenantSelectorDescription: [{
2204
+ type: Input
2205
+ }], continueButtonText: [{
2206
+ type: Input
2207
+ }], registerLinkText: [{
2208
+ type: Input
2209
+ }], registerLinkAction: [{
2210
+ type: Input
2211
+ }], createTenantLinkText: [{
2212
+ type: Input
2213
+ }], createTenantLinkAction: [{
2214
+ type: Input
2215
+ }], tenantSelected: [{
2216
+ type: Output
2217
+ }], createTenant: [{
2218
+ type: Output
2219
+ }] } });
2220
+
2221
+ class TenantRegisterComponent {
2222
+ auth;
2223
+ // Component Configuration
2224
+ title = 'Create New Organization';
2225
+ providers = ['google'];
2226
+ requireTenantName = true;
2227
+ // Tenant Labels
2228
+ tenantSectionTitle = 'Organization Information';
2229
+ tenantNameLabel = 'Organization Name';
2230
+ tenantNamePlaceholder = 'Enter your organization name';
2231
+ tenantSlugLabel = 'Organization URL';
2232
+ tenantSlugPlaceholder = 'organization-name';
2233
+ urlPreviewEnabled = true;
2234
+ urlPreviewPrefix = 'medstoreapp.in/';
2235
+ // User Labels
2236
+ userSectionTitle = 'Your Information';
2237
+ oauthDescription = 'Recommended: Sign up with your Google account';
2238
+ // Warning Message
2239
+ ownershipTitle = 'CREATING A NEW ORGANIZATION';
2240
+ ownershipMessage = 'You are registering as an organization owner. This will create a new organization that you will manage. If you are an employee, DO NOT use this form. Ask your organization owner to invite you, then use the Login page.';
2241
+ // Buttons and Links
2242
+ submitButtonText = 'Create Organization';
2243
+ loginLinkText = 'Already have an account?';
2244
+ loginLinkAction = 'Sign in';
2245
+ // Outputs
2246
+ tenantCreated = new EventEmitter();
2247
+ navigateToLogin = new EventEmitter();
2248
+ // Form Fields
2249
+ tenantName = '';
2250
+ tenantSlug = '';
2251
+ displayName = '';
2252
+ email = '';
2253
+ password = '';
2254
+ confirmPassword = '';
2255
+ // State
2256
+ error = '';
2257
+ success = '';
2258
+ loading = false;
2259
+ loadingText = 'Creating your organization...';
2260
+ checkingSlug = false;
2261
+ slugAvailable = false;
2262
+ slugError = '';
2263
+ useEmailPassword = false;
2264
+ oauthProviders = [];
2265
+ constructor(auth) {
2266
+ this.auth = auth;
2267
+ }
2268
+ ngOnInit() {
2269
+ if (!this.providers || this.providers.length === 0) {
2270
+ this.error = 'Configuration Error: No authentication providers specified.';
2271
+ throw new Error('TenantRegisterComponent requires providers input.');
2272
+ }
2273
+ this.oauthProviders = this.providers.filter(p => p !== 'emailPassword');
2274
+ // If only emailPassword is available, show it by default
2275
+ if (this.oauthProviders.length === 0 && this.isProviderEnabled('emailPassword')) {
2276
+ this.useEmailPassword = true;
2277
+ }
2278
+ }
2279
+ isProviderEnabled(provider) {
2280
+ return this.providers.includes(provider);
2281
+ }
2282
+ getProviderLabel(provider) {
2283
+ const labels = {
2284
+ google: 'Sign up with Google',
2285
+ linkedin: 'Sign up with LinkedIn',
2286
+ apple: 'Sign up with Apple',
2287
+ microsoft: 'Sign up with Microsoft',
2288
+ github: 'Sign up with GitHub',
2289
+ emailPassword: 'Sign up with Email'
2290
+ };
2291
+ return labels[provider];
2292
+ }
2293
+ getProviderIcon(provider) {
2294
+ return undefined;
2295
+ }
2296
+ onTenantNameChange() {
2297
+ // Auto-generate slug from tenant name
2298
+ if (this.tenantName) {
2299
+ const slug = this.tenantName
2300
+ .toLowerCase()
2301
+ .replace(/[^a-z0-9\s-]/g, '')
2302
+ .replace(/\s+/g, '-')
2303
+ .replace(/-+/g, '-')
2304
+ .replace(/^-|-$/g, '');
2305
+ this.tenantSlug = slug;
2306
+ this.slugError = '';
2307
+ this.slugAvailable = false;
2308
+ }
2309
+ }
2310
+ async checkSlugAvailability() {
2311
+ if (!this.tenantSlug || this.tenantSlug.length < 3) {
2312
+ this.slugError = 'Slug must be at least 3 characters';
2313
+ return;
2314
+ }
2315
+ if (!/^[a-z0-9-]+$/.test(this.tenantSlug)) {
2316
+ this.slugError = 'Only lowercase letters, numbers, and hyphens allowed';
2317
+ return;
2318
+ }
2319
+ this.checkingSlug = true;
2320
+ this.slugError = '';
2321
+ try {
2322
+ const result = await this.auth.checkTenantSlugAvailable(this.tenantSlug);
2323
+ if (result.available) {
2324
+ this.slugAvailable = true;
2325
+ }
2326
+ else {
2327
+ this.slugError = 'This URL is already taken';
2328
+ if (result.suggestion) {
2329
+ this.slugError += `. Try: ${result.suggestion}`;
2330
+ }
2331
+ }
2332
+ }
2333
+ catch (err) {
2334
+ // Slug check failed, but don't block registration
2335
+ console.warn('Slug availability check failed:', err);
2336
+ }
2337
+ finally {
2338
+ this.checkingSlug = false;
2339
+ }
2340
+ }
2341
+ toggleAuthMethod(event) {
2342
+ event.preventDefault();
2343
+ this.useEmailPassword = !this.useEmailPassword;
2344
+ this.error = '';
2345
+ }
2346
+ isFormValid() {
2347
+ // Tenant information must be valid
2348
+ if (!this.tenantName || !this.tenantSlug) {
2349
+ return false;
2350
+ }
2351
+ if (this.slugError) {
2352
+ return false;
2353
+ }
2354
+ // If using email/password, check those fields
2355
+ if (this.useEmailPassword) {
2356
+ if (!this.displayName || !this.email || !this.password || !this.confirmPassword) {
2357
+ return false;
2358
+ }
2359
+ if (this.password.length < 8) {
2360
+ return false;
2361
+ }
2362
+ if (this.password !== this.confirmPassword) {
2363
+ return false;
2364
+ }
2365
+ }
2366
+ return true;
2367
+ }
2368
+ async onOAuthRegister(provider) {
2369
+ if (!this.isFormValid()) {
2370
+ this.error = 'Please complete the organization information';
2371
+ return;
2372
+ }
2373
+ this.loading = true;
2374
+ this.loadingText = `Signing up with ${provider}...`;
2375
+ this.error = '';
2376
+ try {
2377
+ const result = await this.auth.registerTenant({
2378
+ tenantName: this.tenantName,
2379
+ tenantSlug: this.tenantSlug,
2380
+ provider: provider
2381
+ });
2382
+ if (result.success && result.tenant && result.user) {
2383
+ this.success = 'Organization created successfully!';
2384
+ this.tenantCreated.emit({
2385
+ tenant: result.tenant,
2386
+ user: result.user
2387
+ });
2388
+ }
2389
+ else {
2390
+ this.error = result.message || 'Registration failed';
2391
+ }
2392
+ }
2393
+ catch (err) {
2394
+ this.error = err.message || 'An unexpected error occurred';
2395
+ }
2396
+ finally {
2397
+ this.loading = false;
2398
+ }
2399
+ }
2400
+ async onRegister() {
2401
+ if (!this.isFormValid()) {
2402
+ this.error = 'Please fill in all required fields correctly';
2403
+ return;
2404
+ }
2405
+ this.loading = true;
2406
+ this.loadingText = 'Creating your organization...';
2407
+ this.error = '';
2408
+ this.success = '';
2409
+ try {
2410
+ const result = await this.auth.registerTenant({
2411
+ tenantName: this.tenantName,
2412
+ tenantSlug: this.tenantSlug,
2413
+ displayName: this.displayName,
2414
+ email: this.email,
2415
+ password: this.password,
2416
+ provider: 'emailPassword'
2417
+ });
2418
+ if (result.success && result.tenant && result.user) {
2419
+ this.success = 'Organization created successfully!';
2420
+ this.tenantCreated.emit({
2421
+ tenant: result.tenant,
2422
+ user: result.user
2423
+ });
2424
+ }
2425
+ else {
2426
+ this.error = result.message || 'Registration failed';
2427
+ }
2428
+ }
2429
+ catch (err) {
2430
+ this.error = err.message || 'An unexpected error occurred';
2431
+ }
2432
+ finally {
2433
+ this.loading = false;
2434
+ }
2435
+ }
2436
+ onLoginClick(event) {
2437
+ event.preventDefault();
2438
+ this.navigateToLogin.emit();
2439
+ }
2440
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantRegisterComponent, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Component });
2441
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: TenantRegisterComponent, isStandalone: true, selector: "lib-tenant-register", inputs: { title: "title", providers: "providers", requireTenantName: "requireTenantName", tenantSectionTitle: "tenantSectionTitle", tenantNameLabel: "tenantNameLabel", tenantNamePlaceholder: "tenantNamePlaceholder", tenantSlugLabel: "tenantSlugLabel", tenantSlugPlaceholder: "tenantSlugPlaceholder", urlPreviewEnabled: "urlPreviewEnabled", urlPreviewPrefix: "urlPreviewPrefix", userSectionTitle: "userSectionTitle", oauthDescription: "oauthDescription", ownershipTitle: "ownershipTitle", ownershipMessage: "ownershipMessage", submitButtonText: "submitButtonText", loginLinkText: "loginLinkText", loginLinkAction: "loginLinkAction" }, outputs: { tenantCreated: "tenantCreated", navigateToLogin: "navigateToLogin" }, ngImport: i0, template: `
2442
+ <div class="tenant-register-dialog">
2443
+ <h2 class="register-title">{{ title }}</h2>
2444
+
2445
+ <!-- Ownership Warning Message -->
2446
+ <div class="warning-box">
2447
+ <div class="warning-icon">⚠️</div>
2448
+ <div class="warning-content">
2449
+ <strong>{{ ownershipTitle }}</strong>
2450
+ <p>{{ ownershipMessage }}</p>
2451
+ </div>
2452
+ </div>
2453
+
2454
+ <form (ngSubmit)="onRegister()" class="register-form">
2455
+ <!-- Tenant Information Section -->
2456
+ <div class="section-header">{{ tenantSectionTitle }}</div>
2457
+
2458
+ <div class="form-group">
2459
+ <label for="tenantName">{{ tenantNameLabel }} *</label>
2460
+ <input
2461
+ id="tenantName"
2462
+ [(ngModel)]="tenantName"
2463
+ name="tenantName"
2464
+ [placeholder]="tenantNamePlaceholder"
2465
+ type="text"
2466
+ required
2467
+ (input)="onTenantNameChange()"
2468
+ class="form-control">
2469
+ </div>
2470
+
2471
+ <div class="form-group">
2472
+ <label for="tenantSlug">{{ tenantSlugLabel }} *</label>
2473
+ <input
2474
+ id="tenantSlug"
2475
+ [(ngModel)]="tenantSlug"
2476
+ name="tenantSlug"
2477
+ [placeholder]="tenantSlugPlaceholder"
2478
+ type="text"
2479
+ required
2480
+ pattern="[a-z0-9-]+"
2481
+ (blur)="checkSlugAvailability()"
2482
+ class="form-control"
2483
+ [class.input-error]="slugError"
2484
+ [class.input-success]="slugAvailable && tenantSlug">
2485
+ @if (tenantSlug && urlPreviewEnabled) {
2486
+ <small class="form-hint">
2487
+ {{ urlPreviewPrefix }}{{ tenantSlug }}
2488
+ @if (checkingSlug) {
2489
+ <span class="checking">Checking...</span>
2490
+ }
2491
+ @if (slugAvailable && !checkingSlug) {
2492
+ <span class="available">✓ Available</span>
2493
+ }
2494
+ @if (slugError) {
2495
+ <span class="error-text">{{ slugError }}</span>
2496
+ }
2497
+ </small>
2498
+ }
2499
+ </div>
2500
+
2501
+ <!-- User Information Section -->
2502
+ <div class="section-header">{{ userSectionTitle }}</div>
2503
+
2504
+ <!-- OAuth Providers (Primary Option) -->
2505
+ @if (oauthProviders.length > 0 && !useEmailPassword) {
2506
+ <div class="oauth-section">
2507
+ <p class="oauth-description">{{ oauthDescription }}</p>
2508
+ <div class="oauth-buttons">
2509
+ @for (provider of oauthProviders; track provider) {
2510
+ <button
2511
+ type="button"
2512
+ (click)="onOAuthRegister(provider)"
2513
+ [disabled]="loading || !isFormValid()"
2514
+ class="btn btn-oauth btn-{{ provider }}">
2515
+ @if (getProviderIcon(provider)) {
2516
+ <span class="oauth-icon">
2517
+ {{ getProviderIcon(provider) }}
2518
+ </span>
2519
+ }
2520
+ {{ getProviderLabel(provider) }}
2521
+ </button>
2522
+ }
2523
+ </div>
2524
+ </div>
2525
+
2526
+ <!-- Switch to Email/Password -->
2527
+ @if (isProviderEnabled('emailPassword')) {
2528
+ <div class="switch-method">
2529
+ <a href="#" (click)="toggleAuthMethod($event)">
2530
+ {{ useEmailPassword ? 'Use OAuth instead' : 'Use email/password instead' }}
2531
+ </a>
2532
+ </div>
2533
+ }
2534
+ }
2535
+
2536
+ <!-- Email/Password Form (Fallback or Manual Entry) -->
2537
+ @if (useEmailPassword || oauthProviders.length === 0) {
2538
+ <div class="form-group">
2539
+ <label for="displayName">Full Name *</label>
2540
+ <input
2541
+ id="displayName"
2542
+ [(ngModel)]="displayName"
2543
+ name="displayName"
2544
+ placeholder="Enter your full name"
2545
+ type="text"
2546
+ required
2547
+ class="form-control">
2548
+ </div>
2549
+
2550
+ <div class="form-group">
2551
+ <label for="email">Email *</label>
2552
+ <input
2553
+ id="email"
2554
+ [(ngModel)]="email"
2555
+ name="email"
2556
+ placeholder="Enter your email"
2557
+ type="email"
2558
+ required
2559
+ class="form-control">
2560
+ </div>
2561
+
2562
+ <div class="form-group">
2563
+ <label for="password">Password *</label>
2564
+ <input
2565
+ id="password"
2566
+ [(ngModel)]="password"
2567
+ name="password"
2568
+ placeholder="Create a password"
2569
+ type="password"
2570
+ required
2571
+ minlength="8"
2572
+ class="form-control">
2573
+ <small class="form-hint">At least 8 characters</small>
2574
+ </div>
2575
+
2576
+ <div class="form-group">
2577
+ <label for="confirmPassword">Confirm Password *</label>
2578
+ <input
2579
+ id="confirmPassword"
2580
+ [(ngModel)]="confirmPassword"
2581
+ name="confirmPassword"
2582
+ placeholder="Confirm your password"
2583
+ type="password"
2584
+ required
2585
+ class="form-control">
2586
+ </div>
2587
+
2588
+ <button
2589
+ type="submit"
2590
+ [disabled]="loading || !isFormValid()"
2591
+ class="btn btn-primary btn-block">
2592
+ {{ loading ? 'Creating...' : submitButtonText }}
2593
+ </button>
2594
+
2595
+ <!-- Switch to OAuth -->
2596
+ @if (oauthProviders.length > 0) {
2597
+ <div class="switch-method">
2598
+ <a href="#" (click)="toggleAuthMethod($event)">
2599
+ Use OAuth instead
2600
+ </a>
2601
+ </div>
2602
+ }
2603
+ }
2604
+ </form>
2605
+
2606
+ <!-- Error Message -->
2607
+ @if (error) {
2608
+ <div class="error-message">
2609
+ {{ error }}
2610
+ </div>
2611
+ }
2612
+
2613
+ <!-- Success Message -->
2614
+ @if (success) {
2615
+ <div class="success-message">
2616
+ {{ success }}
2617
+ </div>
2618
+ }
2619
+
2620
+ <!-- Loading Overlay -->
2621
+ @if (loading) {
2622
+ <div class="loading-overlay">
2623
+ <div class="spinner"></div>
2624
+ <p class="loading-text">{{ loadingText }}</p>
2625
+ </div>
2626
+ }
2627
+
2628
+ <!-- Login Link -->
2629
+ <div class="login-link">
2630
+ {{ loginLinkText }}
2631
+ <a href="#" (click)="onLoginClick($event)">{{ loginLinkAction }}</a>
2632
+ </div>
2633
+ </div>
2634
+ `, 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"] }] });
2635
+ }
2636
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantRegisterComponent, decorators: [{
2637
+ type: Component,
2638
+ args: [{ selector: 'lib-tenant-register', standalone: true, imports: [CommonModule, FormsModule], template: `
2639
+ <div class="tenant-register-dialog">
2640
+ <h2 class="register-title">{{ title }}</h2>
2641
+
2642
+ <!-- Ownership Warning Message -->
2643
+ <div class="warning-box">
2644
+ <div class="warning-icon">⚠️</div>
2645
+ <div class="warning-content">
2646
+ <strong>{{ ownershipTitle }}</strong>
2647
+ <p>{{ ownershipMessage }}</p>
2648
+ </div>
2649
+ </div>
2650
+
2651
+ <form (ngSubmit)="onRegister()" class="register-form">
2652
+ <!-- Tenant Information Section -->
2653
+ <div class="section-header">{{ tenantSectionTitle }}</div>
2654
+
2655
+ <div class="form-group">
2656
+ <label for="tenantName">{{ tenantNameLabel }} *</label>
2657
+ <input
2658
+ id="tenantName"
2659
+ [(ngModel)]="tenantName"
2660
+ name="tenantName"
2661
+ [placeholder]="tenantNamePlaceholder"
2662
+ type="text"
2663
+ required
2664
+ (input)="onTenantNameChange()"
2665
+ class="form-control">
2666
+ </div>
2667
+
2668
+ <div class="form-group">
2669
+ <label for="tenantSlug">{{ tenantSlugLabel }} *</label>
2670
+ <input
2671
+ id="tenantSlug"
2672
+ [(ngModel)]="tenantSlug"
2673
+ name="tenantSlug"
2674
+ [placeholder]="tenantSlugPlaceholder"
2675
+ type="text"
2676
+ required
2677
+ pattern="[a-z0-9-]+"
2678
+ (blur)="checkSlugAvailability()"
2679
+ class="form-control"
2680
+ [class.input-error]="slugError"
2681
+ [class.input-success]="slugAvailable && tenantSlug">
2682
+ @if (tenantSlug && urlPreviewEnabled) {
2683
+ <small class="form-hint">
2684
+ {{ urlPreviewPrefix }}{{ tenantSlug }}
2685
+ @if (checkingSlug) {
2686
+ <span class="checking">Checking...</span>
2687
+ }
2688
+ @if (slugAvailable && !checkingSlug) {
2689
+ <span class="available">✓ Available</span>
2690
+ }
2691
+ @if (slugError) {
2692
+ <span class="error-text">{{ slugError }}</span>
2693
+ }
2694
+ </small>
2695
+ }
2696
+ </div>
2697
+
2698
+ <!-- User Information Section -->
2699
+ <div class="section-header">{{ userSectionTitle }}</div>
2700
+
2701
+ <!-- OAuth Providers (Primary Option) -->
2702
+ @if (oauthProviders.length > 0 && !useEmailPassword) {
2703
+ <div class="oauth-section">
2704
+ <p class="oauth-description">{{ oauthDescription }}</p>
2705
+ <div class="oauth-buttons">
2706
+ @for (provider of oauthProviders; track provider) {
2707
+ <button
2708
+ type="button"
2709
+ (click)="onOAuthRegister(provider)"
2710
+ [disabled]="loading || !isFormValid()"
2711
+ class="btn btn-oauth btn-{{ provider }}">
2712
+ @if (getProviderIcon(provider)) {
2713
+ <span class="oauth-icon">
2714
+ {{ getProviderIcon(provider) }}
2715
+ </span>
2716
+ }
2717
+ {{ getProviderLabel(provider) }}
2718
+ </button>
2719
+ }
2720
+ </div>
2721
+ </div>
2722
+
2723
+ <!-- Switch to Email/Password -->
2724
+ @if (isProviderEnabled('emailPassword')) {
2725
+ <div class="switch-method">
2726
+ <a href="#" (click)="toggleAuthMethod($event)">
2727
+ {{ useEmailPassword ? 'Use OAuth instead' : 'Use email/password instead' }}
2728
+ </a>
2729
+ </div>
2730
+ }
2731
+ }
2732
+
2733
+ <!-- Email/Password Form (Fallback or Manual Entry) -->
2734
+ @if (useEmailPassword || oauthProviders.length === 0) {
2735
+ <div class="form-group">
2736
+ <label for="displayName">Full Name *</label>
2737
+ <input
2738
+ id="displayName"
2739
+ [(ngModel)]="displayName"
2740
+ name="displayName"
2741
+ placeholder="Enter your full name"
2742
+ type="text"
2743
+ required
2744
+ class="form-control">
2745
+ </div>
2746
+
2747
+ <div class="form-group">
2748
+ <label for="email">Email *</label>
2749
+ <input
2750
+ id="email"
2751
+ [(ngModel)]="email"
2752
+ name="email"
2753
+ placeholder="Enter your email"
2754
+ type="email"
2755
+ required
2756
+ class="form-control">
2757
+ </div>
2758
+
2759
+ <div class="form-group">
2760
+ <label for="password">Password *</label>
2761
+ <input
2762
+ id="password"
2763
+ [(ngModel)]="password"
2764
+ name="password"
2765
+ placeholder="Create a password"
2766
+ type="password"
2767
+ required
2768
+ minlength="8"
2769
+ class="form-control">
2770
+ <small class="form-hint">At least 8 characters</small>
2771
+ </div>
2772
+
2773
+ <div class="form-group">
2774
+ <label for="confirmPassword">Confirm Password *</label>
2775
+ <input
2776
+ id="confirmPassword"
2777
+ [(ngModel)]="confirmPassword"
2778
+ name="confirmPassword"
2779
+ placeholder="Confirm your password"
2780
+ type="password"
2781
+ required
2782
+ class="form-control">
2783
+ </div>
2784
+
2785
+ <button
2786
+ type="submit"
2787
+ [disabled]="loading || !isFormValid()"
2788
+ class="btn btn-primary btn-block">
2789
+ {{ loading ? 'Creating...' : submitButtonText }}
2790
+ </button>
2791
+
2792
+ <!-- Switch to OAuth -->
2793
+ @if (oauthProviders.length > 0) {
2794
+ <div class="switch-method">
2795
+ <a href="#" (click)="toggleAuthMethod($event)">
2796
+ Use OAuth instead
2797
+ </a>
2798
+ </div>
2799
+ }
2800
+ }
2801
+ </form>
2802
+
2803
+ <!-- Error Message -->
2804
+ @if (error) {
2805
+ <div class="error-message">
2806
+ {{ error }}
2807
+ </div>
2808
+ }
2809
+
2810
+ <!-- Success Message -->
2811
+ @if (success) {
2812
+ <div class="success-message">
2813
+ {{ success }}
2814
+ </div>
2815
+ }
2816
+
2817
+ <!-- Loading Overlay -->
2818
+ @if (loading) {
2819
+ <div class="loading-overlay">
2820
+ <div class="spinner"></div>
2821
+ <p class="loading-text">{{ loadingText }}</p>
2822
+ </div>
2823
+ }
2824
+
2825
+ <!-- Login Link -->
2826
+ <div class="login-link">
2827
+ {{ loginLinkText }}
2828
+ <a href="#" (click)="onLoginClick($event)">{{ loginLinkAction }}</a>
2829
+ </div>
2830
+ </div>
2831
+ `, 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"] }]
2832
+ }], ctorParameters: () => [{ type: AuthService }], propDecorators: { title: [{
2833
+ type: Input
2834
+ }], providers: [{
2835
+ type: Input
2836
+ }], requireTenantName: [{
2837
+ type: Input
2838
+ }], tenantSectionTitle: [{
2839
+ type: Input
2840
+ }], tenantNameLabel: [{
2841
+ type: Input
2842
+ }], tenantNamePlaceholder: [{
2843
+ type: Input
2844
+ }], tenantSlugLabel: [{
2845
+ type: Input
2846
+ }], tenantSlugPlaceholder: [{
2847
+ type: Input
2848
+ }], urlPreviewEnabled: [{
2849
+ type: Input
2850
+ }], urlPreviewPrefix: [{
2851
+ type: Input
2852
+ }], userSectionTitle: [{
2853
+ type: Input
2854
+ }], oauthDescription: [{
2855
+ type: Input
2856
+ }], ownershipTitle: [{
2857
+ type: Input
2858
+ }], ownershipMessage: [{
2859
+ type: Input
2860
+ }], submitButtonText: [{
2861
+ type: Input
2862
+ }], loginLinkText: [{
2863
+ type: Input
2864
+ }], loginLinkAction: [{
2865
+ type: Input
2866
+ }], tenantCreated: [{
2867
+ type: Output
2868
+ }], navigateToLogin: [{
2869
+ type: Output
2870
+ }] } });
2871
+
2872
+ /**
2873
+ * Dialog wrapper for TenantLoginComponent
2874
+ *
2875
+ * Usage with Angular Material Dialog:
2876
+ * ```typescript
2877
+ * const dialogRef = this.dialog.open(TenantLoginDialogComponent, {
2878
+ * width: '450px',
2879
+ * data: {
2880
+ * providers: ['google'],
2881
+ * showTenantSelector: true
2882
+ * }
2883
+ * });
2884
+ *
2885
+ * dialogRef.afterClosed().subscribe(result => {
2886
+ * if (result && result.tenantId) {
2887
+ * console.log('Logged in to tenant:', result.tenantSlug);
2888
+ * // Redirect to dashboard
2889
+ * }
2890
+ * });
2891
+ * ```
2892
+ *
2893
+ * Usage with custom dialog service:
2894
+ * ```typescript
2895
+ * const dialog = this.dialogService.open(TenantLoginDialogComponent, {
2896
+ * providers: ['google', 'emailPassword'],
2897
+ * autoSelectSingleTenant: true
2898
+ * });
2899
+ * ```
2900
+ */
2901
+ class TenantLoginDialogComponent {
2902
+ data;
2903
+ dialogRef;
2904
+ constructor(injectedData, injectedDialogRef) {
2905
+ // Support both Angular Material Dialog and custom dialog implementations
2906
+ this.data = injectedData || {};
2907
+ this.dialogRef = injectedDialogRef;
2908
+ }
2909
+ onTenantSelected(event) {
2910
+ // Close dialog and return the selected tenant
2911
+ if (this.dialogRef && this.dialogRef.close) {
2912
+ this.dialogRef.close(event);
2913
+ }
2914
+ }
2915
+ onCreateTenant() {
2916
+ // Close dialog and signal that user wants to create a tenant
2917
+ if (this.dialogRef && this.dialogRef.close) {
2918
+ this.dialogRef.close({ action: 'create_tenant' });
2919
+ }
2920
+ }
2921
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantLoginDialogComponent, deps: [{ token: 'DIALOG_DATA', optional: true }, { token: 'DIALOG_REF', optional: true }], target: i0.ɵɵFactoryTarget.Component });
2922
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: TenantLoginDialogComponent, isStandalone: true, selector: "lib-tenant-login-dialog", ngImport: i0, template: `
2923
+ <div class="dialog-wrapper">
2924
+ <lib-tenant-login
2925
+ [title]="data?.title || 'Sign In'"
2926
+ [providers]="data?.providers || ['google']"
2927
+ [showTenantSelector]="data?.showTenantSelector !== false"
2928
+ [autoSelectSingleTenant]="data?.autoSelectSingleTenant !== false"
2929
+ [allowTenantCreation]="data?.allowTenantCreation !== false"
2930
+ [tenantSelectorTitle]="data?.tenantSelectorTitle || 'Select Organization'"
2931
+ [tenantSelectorDescription]="data?.tenantSelectorDescription || 'Choose which organization you want to access:'"
2932
+ [continueButtonText]="data?.continueButtonText || 'Continue'"
2933
+ [registerLinkText]="data?.registerLinkText || 'Don\\'t have an account?'"
2934
+ [registerLinkAction]="data?.registerLinkAction || 'Sign up'"
2935
+ [createTenantLinkText]="data?.createTenantLinkText || 'Don\\'t see your organization?'"
2936
+ [createTenantLinkAction]="data?.createTenantLinkAction || 'Create New Organization'"
2937
+ (tenantSelected)="onTenantSelected($event)"
2938
+ (createTenant)="onCreateTenant()">
2939
+ </lib-tenant-login>
2940
+ </div>
2941
+ `, 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"] }] });
2942
+ }
2943
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantLoginDialogComponent, decorators: [{
2944
+ type: Component,
2945
+ args: [{ selector: 'lib-tenant-login-dialog', standalone: true, imports: [CommonModule, TenantLoginComponent], template: `
2946
+ <div class="dialog-wrapper">
2947
+ <lib-tenant-login
2948
+ [title]="data?.title || 'Sign In'"
2949
+ [providers]="data?.providers || ['google']"
2950
+ [showTenantSelector]="data?.showTenantSelector !== false"
2951
+ [autoSelectSingleTenant]="data?.autoSelectSingleTenant !== false"
2952
+ [allowTenantCreation]="data?.allowTenantCreation !== false"
2953
+ [tenantSelectorTitle]="data?.tenantSelectorTitle || 'Select Organization'"
2954
+ [tenantSelectorDescription]="data?.tenantSelectorDescription || 'Choose which organization you want to access:'"
2955
+ [continueButtonText]="data?.continueButtonText || 'Continue'"
2956
+ [registerLinkText]="data?.registerLinkText || 'Don\\'t have an account?'"
2957
+ [registerLinkAction]="data?.registerLinkAction || 'Sign up'"
2958
+ [createTenantLinkText]="data?.createTenantLinkText || 'Don\\'t see your organization?'"
2959
+ [createTenantLinkAction]="data?.createTenantLinkAction || 'Create New Organization'"
2960
+ (tenantSelected)="onTenantSelected($event)"
2961
+ (createTenant)="onCreateTenant()">
2962
+ </lib-tenant-login>
2963
+ </div>
2964
+ `, styles: [".dialog-wrapper{padding:0}\n"] }]
2965
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
2966
+ type: Optional
2967
+ }, {
2968
+ type: Inject,
2969
+ args: ['DIALOG_DATA']
2970
+ }] }, { type: undefined, decorators: [{
2971
+ type: Optional
2972
+ }, {
2973
+ type: Inject,
2974
+ args: ['DIALOG_REF']
2975
+ }] }] });
2976
+
2977
+ /**
2978
+ * Dialog wrapper for TenantRegisterComponent
2979
+ *
2980
+ * Usage with Angular Material Dialog:
2981
+ * ```typescript
2982
+ * const dialogRef = this.dialog.open(TenantRegisterDialogComponent, {
2983
+ * width: '500px',
2984
+ * data: {
2985
+ * providers: ['google'],
2986
+ * tenantNameLabel: 'Store Name'
2987
+ * }
2988
+ * });
2989
+ *
2990
+ * dialogRef.afterClosed().subscribe(result => {
2991
+ * if (result && result.tenant) {
2992
+ * console.log('Tenant created:', result.tenant);
2993
+ * }
2994
+ * });
2995
+ * ```
2996
+ *
2997
+ * Usage with custom dialog service:
2998
+ * ```typescript
2999
+ * const dialog = this.dialogService.open(TenantRegisterDialogComponent, {
3000
+ * providers: ['google', 'emailPassword']
3001
+ * });
3002
+ * ```
3003
+ */
3004
+ class TenantRegisterDialogComponent {
3005
+ data;
3006
+ dialogRef;
3007
+ constructor(injectedData, injectedDialogRef) {
3008
+ // Support both Angular Material Dialog and custom dialog implementations
3009
+ this.data = injectedData || {};
3010
+ this.dialogRef = injectedDialogRef;
3011
+ }
3012
+ onTenantCreated(event) {
3013
+ // Close dialog and return the created tenant
3014
+ if (this.dialogRef && this.dialogRef.close) {
3015
+ this.dialogRef.close(event);
3016
+ }
3017
+ }
3018
+ onNavigateToLogin() {
3019
+ // Close dialog without result (user wants to login instead)
3020
+ if (this.dialogRef && this.dialogRef.close) {
3021
+ this.dialogRef.close({ action: 'navigate_to_login' });
3022
+ }
3023
+ }
3024
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantRegisterDialogComponent, deps: [{ token: 'DIALOG_DATA', optional: true }, { token: 'DIALOG_REF', optional: true }], target: i0.ɵɵFactoryTarget.Component });
3025
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: TenantRegisterDialogComponent, isStandalone: true, selector: "lib-tenant-register-dialog", ngImport: i0, template: `
3026
+ <div class="dialog-wrapper">
3027
+ <lib-tenant-register
3028
+ [title]="data?.title || 'Create New Organization'"
3029
+ [providers]="data?.providers || ['google']"
3030
+ [requireTenantName]="data?.requireTenantName !== false"
3031
+ [tenantSectionTitle]="data?.tenantSectionTitle || 'Organization Information'"
3032
+ [tenantNameLabel]="data?.tenantNameLabel || 'Organization Name'"
3033
+ [tenantNamePlaceholder]="data?.tenantNamePlaceholder || 'Enter your organization name'"
3034
+ [tenantSlugLabel]="data?.tenantSlugLabel || 'Organization URL'"
3035
+ [tenantSlugPlaceholder]="data?.tenantSlugPlaceholder || 'organization-name'"
3036
+ [urlPreviewEnabled]="data?.urlPreviewEnabled !== false"
3037
+ [urlPreviewPrefix]="data?.urlPreviewPrefix || 'app.example.com/'"
3038
+ [userSectionTitle]="data?.userSectionTitle || 'Your Information'"
3039
+ [oauthDescription]="data?.oauthDescription || 'Recommended: Sign up with your Google account'"
3040
+ [ownershipTitle]="data?.ownershipTitle || 'CREATING A NEW ORGANIZATION'"
3041
+ [ownershipMessage]="data?.ownershipMessage || 'You are registering as an organization owner. If you are an employee, use Login instead.'"
3042
+ [submitButtonText]="data?.submitButtonText || 'Create Organization'"
3043
+ [loginLinkText]="data?.loginLinkText || 'Already have an account?'"
3044
+ [loginLinkAction]="data?.loginLinkAction || 'Sign in'"
3045
+ (tenantCreated)="onTenantCreated($event)"
3046
+ (navigateToLogin)="onNavigateToLogin()">
3047
+ </lib-tenant-register>
3048
+ </div>
3049
+ `, isInline: true, styles: [".dialog-wrapper{padding:0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "component", type: TenantRegisterComponent, selector: "lib-tenant-register", inputs: ["title", "providers", "requireTenantName", "tenantSectionTitle", "tenantNameLabel", "tenantNamePlaceholder", "tenantSlugLabel", "tenantSlugPlaceholder", "urlPreviewEnabled", "urlPreviewPrefix", "userSectionTitle", "oauthDescription", "ownershipTitle", "ownershipMessage", "submitButtonText", "loginLinkText", "loginLinkAction"], outputs: ["tenantCreated", "navigateToLogin"] }] });
3050
+ }
3051
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TenantRegisterDialogComponent, decorators: [{
3052
+ type: Component,
3053
+ args: [{ selector: 'lib-tenant-register-dialog', standalone: true, imports: [CommonModule, TenantRegisterComponent], template: `
3054
+ <div class="dialog-wrapper">
3055
+ <lib-tenant-register
3056
+ [title]="data?.title || 'Create New Organization'"
3057
+ [providers]="data?.providers || ['google']"
3058
+ [requireTenantName]="data?.requireTenantName !== false"
3059
+ [tenantSectionTitle]="data?.tenantSectionTitle || 'Organization Information'"
3060
+ [tenantNameLabel]="data?.tenantNameLabel || 'Organization Name'"
3061
+ [tenantNamePlaceholder]="data?.tenantNamePlaceholder || 'Enter your organization name'"
3062
+ [tenantSlugLabel]="data?.tenantSlugLabel || 'Organization URL'"
3063
+ [tenantSlugPlaceholder]="data?.tenantSlugPlaceholder || 'organization-name'"
3064
+ [urlPreviewEnabled]="data?.urlPreviewEnabled !== false"
3065
+ [urlPreviewPrefix]="data?.urlPreviewPrefix || 'app.example.com/'"
3066
+ [userSectionTitle]="data?.userSectionTitle || 'Your Information'"
3067
+ [oauthDescription]="data?.oauthDescription || 'Recommended: Sign up with your Google account'"
3068
+ [ownershipTitle]="data?.ownershipTitle || 'CREATING A NEW ORGANIZATION'"
3069
+ [ownershipMessage]="data?.ownershipMessage || 'You are registering as an organization owner. If you are an employee, use Login instead.'"
3070
+ [submitButtonText]="data?.submitButtonText || 'Create Organization'"
3071
+ [loginLinkText]="data?.loginLinkText || 'Already have an account?'"
3072
+ [loginLinkAction]="data?.loginLinkAction || 'Sign in'"
3073
+ (tenantCreated)="onTenantCreated($event)"
3074
+ (navigateToLogin)="onNavigateToLogin()">
3075
+ </lib-tenant-register>
3076
+ </div>
3077
+ `, styles: [".dialog-wrapper{padding:0}\n"] }]
3078
+ }], ctorParameters: () => [{ type: undefined, decorators: [{
3079
+ type: Optional
3080
+ }, {
3081
+ type: Inject,
3082
+ args: ['DIALOG_DATA']
3083
+ }] }, { type: undefined, decorators: [{
3084
+ type: Optional
3085
+ }, {
3086
+ type: Inject,
3087
+ args: ['DIALOG_REF']
3088
+ }] }] });
3089
+
556
3090
  /*
557
3091
  * Public API Surface of ngx-stonescriptphp-client
558
3092
  */
@@ -561,5 +3095,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
561
3095
  * Generated bundle index. Do not edit.
562
3096
  */
563
3097
 
564
- export { ApiConnectionService, ApiResponse, AuthService, CsrfService, DbService, MyEnvironmentModel, NgxStoneScriptPhpClientModule, SigninStatusService, TokenService };
3098
+ export { ApiConnectionService, ApiResponse, AuthService, CsrfService, DbService, LoginDialogComponent, MyEnvironmentModel, NgxStoneScriptPhpClientModule, RegisterComponent, SigninStatusService, TenantLoginComponent, TenantLoginDialogComponent, TenantRegisterComponent, TenantRegisterDialogComponent, TokenService, VerifyStatus };
565
3099
  //# sourceMappingURL=progalaxyelabs-ngx-stonescriptphp-client.mjs.map