@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
|
-
|
|
511
|
-
|
|
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
|