@progalaxyelabs/ngx-stonescriptphp-client 1.1.2 → 1.2.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 } 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,337 @@ 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
+ // ===== Backward Compatibility Methods =====
825
+ // These methods are deprecated and maintained for backward compatibility
826
+ // with existing platform code. New code should use the methods above.
827
+ /**
828
+ * @deprecated Use getCurrentUser()?.user_id instead
829
+ */
830
+ getUserId() {
831
+ return this.userSubject.value?.user_id || 0;
832
+ }
833
+ /**
834
+ * @deprecated Use getCurrentUser()?.display_name instead
835
+ */
836
+ getUserName() {
837
+ return this.userSubject.value?.display_name || '';
838
+ }
839
+ /**
840
+ * @deprecated Use getCurrentUser()?.photo_url instead
841
+ */
842
+ getPhotoUrl() {
843
+ return this.userSubject.value?.photo_url || '';
844
+ }
845
+ /**
846
+ * @deprecated Use getCurrentUser()?.display_name instead
847
+ */
848
+ getDisplayName() {
849
+ return this.userSubject.value?.display_name || '';
850
+ }
851
+ /**
852
+ * @deprecated Use `/profile/${getCurrentUser()?.user_id}` instead
853
+ */
854
+ getProfileUrl() {
855
+ const userId = this.userSubject.value?.user_id;
856
+ return userId ? `/profile/${userId}` : '';
857
+ }
858
+ /**
859
+ * @deprecated Use isAuthenticated() instead
860
+ */
861
+ async signin() {
862
+ return this.isAuthenticated();
863
+ }
864
+ /**
865
+ * @deprecated Use loginWithEmail() instead
866
+ */
867
+ async verifyCredentials(email, password) {
868
+ const result = await this.loginWithEmail(email, password);
869
+ return result.success;
870
+ }
871
+ /**
872
+ * @deprecated Check user.is_email_verified from getCurrentUser() instead
873
+ */
874
+ isSigninEmailValid() {
875
+ return this.userSubject.value?.is_email_verified || false;
876
+ }
877
+ /**
878
+ * @deprecated No longer needed - dialog is managed by platform
879
+ */
880
+ onDialogClose() {
881
+ // No-op for backward compatibility
882
+ }
883
+ /**
884
+ * @deprecated No longer needed - dialog is managed by platform
885
+ */
886
+ closeSocialAuthDialog() {
887
+ // No-op for backward compatibility
888
+ }
889
+ /**
890
+ * @deprecated Check if user exists by calling /api/auth/check-email endpoint
891
+ */
892
+ async getUserProfile(email) {
893
+ try {
894
+ const response = await fetch(`${this.environment.accountsUrl}/api/auth/check-email`, {
895
+ method: 'POST',
896
+ headers: { 'Content-Type': 'application/json' },
897
+ body: JSON.stringify({ email })
898
+ });
899
+ const data = await response.json();
900
+ return data.exists ? data.user : null;
901
+ }
902
+ catch (error) {
903
+ return null;
904
+ }
905
+ }
906
+ 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
907
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AuthService, providedIn: 'root' });
513
908
  }
514
909
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: AuthService, decorators: [{
@@ -516,7 +911,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
516
911
  args: [{
517
912
  providedIn: 'root'
518
913
  }]
519
- }], ctorParameters: () => [] });
914
+ }], ctorParameters: () => [{ type: TokenService }, { type: SigninStatusService }, { type: MyEnvironmentModel, decorators: [{
915
+ type: Inject,
916
+ args: [MyEnvironmentModel]
917
+ }] }] });
520
918
 
521
919
  class DbService {
522
920
  constructor() { }
@@ -553,6 +951,510 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
553
951
  }]
554
952
  }] });
555
953
 
954
+ class LoginDialogComponent {
955
+ auth;
956
+ /**
957
+ * REQUIRED: Which authentication providers to show in this dialog
958
+ * @example ['google', 'linkedin', 'emailPassword']
959
+ */
960
+ providers = [];
961
+ email = '';
962
+ password = '';
963
+ error = '';
964
+ loading = false;
965
+ oauthProviders = [];
966
+ constructor(auth) {
967
+ this.auth = auth;
968
+ }
969
+ ngOnInit() {
970
+ if (!this.providers || this.providers.length === 0) {
971
+ this.error = 'Configuration Error: No authentication providers specified. Please pass providers to LoginDialogComponent.';
972
+ throw new Error('LoginDialogComponent requires providers input. Example: dialogRef.componentInstance.providers = [\'google\', \'emailPassword\']');
973
+ }
974
+ // Get OAuth providers (excluding emailPassword)
975
+ this.oauthProviders = this.providers
976
+ .filter(p => p !== 'emailPassword');
977
+ }
978
+ isProviderEnabled(provider) {
979
+ return this.providers.includes(provider);
980
+ }
981
+ getProviderLabel(provider) {
982
+ const labels = {
983
+ google: 'Sign in with Google',
984
+ linkedin: 'Sign in with LinkedIn',
985
+ apple: 'Sign in with Apple',
986
+ microsoft: 'Sign in with Microsoft',
987
+ github: 'Sign in with GitHub',
988
+ emailPassword: 'Sign in with Email'
989
+ };
990
+ return labels[provider];
991
+ }
992
+ getProviderIcon(provider) {
993
+ // Platforms can customize icons via CSS classes: .btn-google, .btn-linkedin, etc.
994
+ return undefined;
995
+ }
996
+ async onEmailLogin() {
997
+ if (!this.email || !this.password) {
998
+ this.error = 'Please enter email and password';
999
+ return;
1000
+ }
1001
+ this.loading = true;
1002
+ this.error = '';
1003
+ try {
1004
+ const result = await this.auth.loginWithEmail(this.email, this.password);
1005
+ if (!result.success) {
1006
+ this.error = result.message || 'Login failed';
1007
+ }
1008
+ // On success, parent component/dialog should close automatically via user$ subscription
1009
+ }
1010
+ catch (err) {
1011
+ this.error = 'An unexpected error occurred';
1012
+ }
1013
+ finally {
1014
+ this.loading = false;
1015
+ }
1016
+ }
1017
+ async onOAuthLogin(provider) {
1018
+ this.loading = true;
1019
+ this.error = '';
1020
+ try {
1021
+ const result = await this.auth.loginWithProvider(provider);
1022
+ if (!result.success) {
1023
+ this.error = result.message || 'OAuth login failed';
1024
+ }
1025
+ // On success, parent component/dialog should close automatically via user$ subscription
1026
+ }
1027
+ catch (err) {
1028
+ this.error = 'An unexpected error occurred';
1029
+ }
1030
+ finally {
1031
+ this.loading = false;
1032
+ }
1033
+ }
1034
+ onRegisterClick(event) {
1035
+ event.preventDefault();
1036
+ // Platforms can override this or listen for a custom event
1037
+ // For now, just emit a console message
1038
+ console.log('Register clicked - platform should handle navigation');
1039
+ }
1040
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: LoginDialogComponent, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Component });
1041
+ 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: `
1042
+ <div class="login-dialog">
1043
+ <h2 class="login-title">Sign In</h2>
1044
+
1045
+ <!-- Email/Password Form (if enabled) -->
1046
+ @if (isProviderEnabled('emailPassword')) {
1047
+ <form (ngSubmit)="onEmailLogin()" class="email-form">
1048
+ <div class="form-group">
1049
+ <input
1050
+ [(ngModel)]="email"
1051
+ name="email"
1052
+ placeholder="Email"
1053
+ type="email"
1054
+ required
1055
+ class="form-control">
1056
+ </div>
1057
+ <div class="form-group">
1058
+ <input
1059
+ [(ngModel)]="password"
1060
+ name="password"
1061
+ placeholder="Password"
1062
+ type="password"
1063
+ required
1064
+ class="form-control">
1065
+ </div>
1066
+ <button
1067
+ type="submit"
1068
+ [disabled]="loading"
1069
+ class="btn btn-primary btn-block">
1070
+ {{ loading ? 'Signing in...' : getProviderLabel('emailPassword') }}
1071
+ </button>
1072
+ </form>
1073
+ }
1074
+
1075
+ <!-- Divider if both email and OAuth are present -->
1076
+ @if (isProviderEnabled('emailPassword') && oauthProviders.length > 0) {
1077
+ <div class="divider">
1078
+ <span>OR</span>
1079
+ </div>
1080
+ }
1081
+
1082
+ <!-- OAuth Providers -->
1083
+ @if (oauthProviders.length > 0) {
1084
+ <div class="oauth-buttons">
1085
+ @for (provider of oauthProviders; track provider) {
1086
+ <button
1087
+ (click)="onOAuthLogin(provider)"
1088
+ [disabled]="loading"
1089
+ class="btn btn-oauth btn-{{ provider }}">
1090
+ @if (getProviderIcon(provider)) {
1091
+ <span class="oauth-icon">
1092
+ {{ getProviderIcon(provider) }}
1093
+ </span>
1094
+ }
1095
+ {{ getProviderLabel(provider) }}
1096
+ </button>
1097
+ }
1098
+ </div>
1099
+ }
1100
+
1101
+ <!-- Error Message -->
1102
+ @if (error) {
1103
+ <div class="error-message">
1104
+ {{ error }}
1105
+ </div>
1106
+ }
1107
+
1108
+ <!-- Loading State -->
1109
+ @if (loading) {
1110
+ <div class="loading-overlay">
1111
+ <div class="spinner"></div>
1112
+ </div>
1113
+ }
1114
+
1115
+ <!-- Register Link -->
1116
+ <div class="register-link">
1117
+ Don't have an account?
1118
+ <a href="#" (click)="onRegisterClick($event)">Sign up</a>
1119
+ </div>
1120
+ </div>
1121
+ `, 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"] }] });
1122
+ }
1123
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: LoginDialogComponent, decorators: [{
1124
+ type: Component,
1125
+ args: [{ selector: 'lib-login-dialog', standalone: true, imports: [CommonModule, FormsModule], template: `
1126
+ <div class="login-dialog">
1127
+ <h2 class="login-title">Sign In</h2>
1128
+
1129
+ <!-- Email/Password Form (if enabled) -->
1130
+ @if (isProviderEnabled('emailPassword')) {
1131
+ <form (ngSubmit)="onEmailLogin()" class="email-form">
1132
+ <div class="form-group">
1133
+ <input
1134
+ [(ngModel)]="email"
1135
+ name="email"
1136
+ placeholder="Email"
1137
+ type="email"
1138
+ required
1139
+ class="form-control">
1140
+ </div>
1141
+ <div class="form-group">
1142
+ <input
1143
+ [(ngModel)]="password"
1144
+ name="password"
1145
+ placeholder="Password"
1146
+ type="password"
1147
+ required
1148
+ class="form-control">
1149
+ </div>
1150
+ <button
1151
+ type="submit"
1152
+ [disabled]="loading"
1153
+ class="btn btn-primary btn-block">
1154
+ {{ loading ? 'Signing in...' : getProviderLabel('emailPassword') }}
1155
+ </button>
1156
+ </form>
1157
+ }
1158
+
1159
+ <!-- Divider if both email and OAuth are present -->
1160
+ @if (isProviderEnabled('emailPassword') && oauthProviders.length > 0) {
1161
+ <div class="divider">
1162
+ <span>OR</span>
1163
+ </div>
1164
+ }
1165
+
1166
+ <!-- OAuth Providers -->
1167
+ @if (oauthProviders.length > 0) {
1168
+ <div class="oauth-buttons">
1169
+ @for (provider of oauthProviders; track provider) {
1170
+ <button
1171
+ (click)="onOAuthLogin(provider)"
1172
+ [disabled]="loading"
1173
+ class="btn btn-oauth btn-{{ provider }}">
1174
+ @if (getProviderIcon(provider)) {
1175
+ <span class="oauth-icon">
1176
+ {{ getProviderIcon(provider) }}
1177
+ </span>
1178
+ }
1179
+ {{ getProviderLabel(provider) }}
1180
+ </button>
1181
+ }
1182
+ </div>
1183
+ }
1184
+
1185
+ <!-- Error Message -->
1186
+ @if (error) {
1187
+ <div class="error-message">
1188
+ {{ error }}
1189
+ </div>
1190
+ }
1191
+
1192
+ <!-- Loading State -->
1193
+ @if (loading) {
1194
+ <div class="loading-overlay">
1195
+ <div class="spinner"></div>
1196
+ </div>
1197
+ }
1198
+
1199
+ <!-- Register Link -->
1200
+ <div class="register-link">
1201
+ Don't have an account?
1202
+ <a href="#" (click)="onRegisterClick($event)">Sign up</a>
1203
+ </div>
1204
+ </div>
1205
+ `, 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"] }]
1206
+ }], ctorParameters: () => [{ type: AuthService }], propDecorators: { providers: [{
1207
+ type: Input
1208
+ }] } });
1209
+
1210
+ class RegisterComponent {
1211
+ auth;
1212
+ displayName = '';
1213
+ email = '';
1214
+ password = '';
1215
+ confirmPassword = '';
1216
+ error = '';
1217
+ success = '';
1218
+ loading = false;
1219
+ constructor(auth) {
1220
+ this.auth = auth;
1221
+ }
1222
+ async onRegister() {
1223
+ // Reset messages
1224
+ this.error = '';
1225
+ this.success = '';
1226
+ // Validate fields
1227
+ if (!this.displayName || !this.email || !this.password || !this.confirmPassword) {
1228
+ this.error = 'Please fill in all fields';
1229
+ return;
1230
+ }
1231
+ if (this.password.length < 8) {
1232
+ this.error = 'Password must be at least 8 characters';
1233
+ return;
1234
+ }
1235
+ if (this.password !== this.confirmPassword) {
1236
+ this.error = 'Passwords do not match';
1237
+ return;
1238
+ }
1239
+ // Validate email format
1240
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
1241
+ if (!emailRegex.test(this.email)) {
1242
+ this.error = 'Please enter a valid email address';
1243
+ return;
1244
+ }
1245
+ this.loading = true;
1246
+ try {
1247
+ const result = await this.auth.register(this.email, this.password, this.displayName);
1248
+ if (result.success) {
1249
+ this.success = result.message || 'Account created successfully!';
1250
+ // On success, parent component/dialog should close automatically via user$ subscription
1251
+ // or navigate to email verification page
1252
+ }
1253
+ else {
1254
+ this.error = result.message || 'Registration failed';
1255
+ }
1256
+ }
1257
+ catch (err) {
1258
+ this.error = 'An unexpected error occurred';
1259
+ }
1260
+ finally {
1261
+ this.loading = false;
1262
+ }
1263
+ }
1264
+ onLoginClick(event) {
1265
+ event.preventDefault();
1266
+ // Platforms can override this or listen for a custom event
1267
+ // For now, just emit a console message
1268
+ console.log('Login clicked - platform should handle navigation');
1269
+ }
1270
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RegisterComponent, deps: [{ token: AuthService }], target: i0.ɵɵFactoryTarget.Component });
1271
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: RegisterComponent, isStandalone: true, selector: "lib-register", ngImport: i0, template: `
1272
+ <div class="register-dialog">
1273
+ <h2 class="register-title">Create Account</h2>
1274
+
1275
+ <form (ngSubmit)="onRegister()" class="register-form">
1276
+ <div class="form-group">
1277
+ <label for="displayName">Full Name</label>
1278
+ <input
1279
+ id="displayName"
1280
+ [(ngModel)]="displayName"
1281
+ name="displayName"
1282
+ placeholder="Enter your full name"
1283
+ type="text"
1284
+ required
1285
+ class="form-control">
1286
+ </div>
1287
+
1288
+ <div class="form-group">
1289
+ <label for="email">Email</label>
1290
+ <input
1291
+ id="email"
1292
+ [(ngModel)]="email"
1293
+ name="email"
1294
+ placeholder="Enter your email"
1295
+ type="email"
1296
+ required
1297
+ class="form-control">
1298
+ </div>
1299
+
1300
+ <div class="form-group">
1301
+ <label for="password">Password</label>
1302
+ <input
1303
+ id="password"
1304
+ [(ngModel)]="password"
1305
+ name="password"
1306
+ placeholder="Create a password"
1307
+ type="password"
1308
+ required
1309
+ minlength="8"
1310
+ class="form-control">
1311
+ <small class="form-hint">At least 8 characters</small>
1312
+ </div>
1313
+
1314
+ <div class="form-group">
1315
+ <label for="confirmPassword">Confirm Password</label>
1316
+ <input
1317
+ id="confirmPassword"
1318
+ [(ngModel)]="confirmPassword"
1319
+ name="confirmPassword"
1320
+ placeholder="Confirm your password"
1321
+ type="password"
1322
+ required
1323
+ class="form-control">
1324
+ </div>
1325
+
1326
+ <button
1327
+ type="submit"
1328
+ [disabled]="loading"
1329
+ class="btn btn-primary btn-block">
1330
+ {{ loading ? 'Creating account...' : 'Sign Up' }}
1331
+ </button>
1332
+ </form>
1333
+
1334
+ <!-- Error Message -->
1335
+ @if (error) {
1336
+ <div class="error-message">
1337
+ {{ error }}
1338
+ </div>
1339
+ }
1340
+
1341
+ <!-- Success Message -->
1342
+ @if (success) {
1343
+ <div class="success-message">
1344
+ {{ success }}
1345
+ </div>
1346
+ }
1347
+
1348
+ <!-- Loading State -->
1349
+ @if (loading) {
1350
+ <div class="loading-overlay">
1351
+ <div class="spinner"></div>
1352
+ </div>
1353
+ }
1354
+
1355
+ <!-- Login Link -->
1356
+ <div class="login-link">
1357
+ Already have an account?
1358
+ <a href="#" (click)="onLoginClick($event)">Sign in</a>
1359
+ </div>
1360
+ </div>
1361
+ `, 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"] }] });
1362
+ }
1363
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RegisterComponent, decorators: [{
1364
+ type: Component,
1365
+ args: [{ selector: 'lib-register', standalone: true, imports: [CommonModule, FormsModule], template: `
1366
+ <div class="register-dialog">
1367
+ <h2 class="register-title">Create Account</h2>
1368
+
1369
+ <form (ngSubmit)="onRegister()" class="register-form">
1370
+ <div class="form-group">
1371
+ <label for="displayName">Full Name</label>
1372
+ <input
1373
+ id="displayName"
1374
+ [(ngModel)]="displayName"
1375
+ name="displayName"
1376
+ placeholder="Enter your full name"
1377
+ type="text"
1378
+ required
1379
+ class="form-control">
1380
+ </div>
1381
+
1382
+ <div class="form-group">
1383
+ <label for="email">Email</label>
1384
+ <input
1385
+ id="email"
1386
+ [(ngModel)]="email"
1387
+ name="email"
1388
+ placeholder="Enter your email"
1389
+ type="email"
1390
+ required
1391
+ class="form-control">
1392
+ </div>
1393
+
1394
+ <div class="form-group">
1395
+ <label for="password">Password</label>
1396
+ <input
1397
+ id="password"
1398
+ [(ngModel)]="password"
1399
+ name="password"
1400
+ placeholder="Create a password"
1401
+ type="password"
1402
+ required
1403
+ minlength="8"
1404
+ class="form-control">
1405
+ <small class="form-hint">At least 8 characters</small>
1406
+ </div>
1407
+
1408
+ <div class="form-group">
1409
+ <label for="confirmPassword">Confirm Password</label>
1410
+ <input
1411
+ id="confirmPassword"
1412
+ [(ngModel)]="confirmPassword"
1413
+ name="confirmPassword"
1414
+ placeholder="Confirm your password"
1415
+ type="password"
1416
+ required
1417
+ class="form-control">
1418
+ </div>
1419
+
1420
+ <button
1421
+ type="submit"
1422
+ [disabled]="loading"
1423
+ class="btn btn-primary btn-block">
1424
+ {{ loading ? 'Creating account...' : 'Sign Up' }}
1425
+ </button>
1426
+ </form>
1427
+
1428
+ <!-- Error Message -->
1429
+ @if (error) {
1430
+ <div class="error-message">
1431
+ {{ error }}
1432
+ </div>
1433
+ }
1434
+
1435
+ <!-- Success Message -->
1436
+ @if (success) {
1437
+ <div class="success-message">
1438
+ {{ success }}
1439
+ </div>
1440
+ }
1441
+
1442
+ <!-- Loading State -->
1443
+ @if (loading) {
1444
+ <div class="loading-overlay">
1445
+ <div class="spinner"></div>
1446
+ </div>
1447
+ }
1448
+
1449
+ <!-- Login Link -->
1450
+ <div class="login-link">
1451
+ Already have an account?
1452
+ <a href="#" (click)="onLoginClick($event)">Sign in</a>
1453
+ </div>
1454
+ </div>
1455
+ `, 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"] }]
1456
+ }], ctorParameters: () => [{ type: AuthService }] });
1457
+
556
1458
  /*
557
1459
  * Public API Surface of ngx-stonescriptphp-client
558
1460
  */
@@ -561,5 +1463,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImpo
561
1463
  * Generated bundle index. Do not edit.
562
1464
  */
563
1465
 
564
- export { ApiConnectionService, ApiResponse, AuthService, CsrfService, DbService, MyEnvironmentModel, NgxStoneScriptPhpClientModule, SigninStatusService, TokenService };
1466
+ export { ApiConnectionService, ApiResponse, AuthService, CsrfService, DbService, LoginDialogComponent, MyEnvironmentModel, NgxStoneScriptPhpClientModule, RegisterComponent, SigninStatusService, TokenService, VerifyStatus };
565
1467
  //# sourceMappingURL=progalaxyelabs-ngx-stonescriptphp-client.mjs.map