@blackcode_sa/metaestetics-api 1.14.17 → 1.14.23
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.
- package/dist/admin/index.js +2 -13
- package/dist/admin/index.mjs +2 -13
- package/dist/index.d.mts +11 -14
- package/dist/index.d.ts +11 -14
- package/dist/index.js +66 -170
- package/dist/index.mjs +119 -223
- package/package.json +1 -1
- package/src/admin/mailing/practitionerInvite/templates/invitation.template.ts +2 -13
- package/src/services/auth/auth.service.ts +9 -197
- package/src/services/practitioner/practitioner.service.ts +79 -4
- package/src/services/user/user.service.ts +1 -51
package/dist/admin/index.js
CHANGED
|
@@ -11860,21 +11860,10 @@ var practitionerInvitationTemplate = `
|
|
|
11860
11860
|
|
|
11861
11861
|
<p>This token will expire on <strong>{{expirationDate}}</strong>.</p>
|
|
11862
11862
|
|
|
11863
|
-
<p
|
|
11864
|
-
|
|
11865
|
-
<p><strong>Option 1: Sign in with Google (Recommended)</strong></p>
|
|
11866
|
-
<ol>
|
|
11867
|
-
<li>Open the MetaEsthetics Doctor App</li>
|
|
11868
|
-
<li>Click "Sign in with Google" on the login screen</li>
|
|
11869
|
-
<li>Select your Google account (use the email address: {{practitionerEmail}})</li>
|
|
11870
|
-
<li>You'll see an invitation to join {{clinicName}} - simply select it and join!</li>
|
|
11871
|
-
</ol>
|
|
11872
|
-
|
|
11873
|
-
<p><strong>Option 2: Use Email/Password with Token</strong></p>
|
|
11863
|
+
<p>To create your account:</p>
|
|
11874
11864
|
<ol>
|
|
11875
11865
|
<li>Visit {{registrationUrl}}</li>
|
|
11876
|
-
<li>
|
|
11877
|
-
<li>Enter your email ({{practitionerEmail}}) and create a password</li>
|
|
11866
|
+
<li>Enter your email and create a password</li>
|
|
11878
11867
|
<li>When prompted, enter the token above</li>
|
|
11879
11868
|
</ol>
|
|
11880
11869
|
|
package/dist/admin/index.mjs
CHANGED
|
@@ -11787,21 +11787,10 @@ var practitionerInvitationTemplate = `
|
|
|
11787
11787
|
|
|
11788
11788
|
<p>This token will expire on <strong>{{expirationDate}}</strong>.</p>
|
|
11789
11789
|
|
|
11790
|
-
<p
|
|
11791
|
-
|
|
11792
|
-
<p><strong>Option 1: Sign in with Google (Recommended)</strong></p>
|
|
11793
|
-
<ol>
|
|
11794
|
-
<li>Open the MetaEsthetics Doctor App</li>
|
|
11795
|
-
<li>Click "Sign in with Google" on the login screen</li>
|
|
11796
|
-
<li>Select your Google account (use the email address: {{practitionerEmail}})</li>
|
|
11797
|
-
<li>You'll see an invitation to join {{clinicName}} - simply select it and join!</li>
|
|
11798
|
-
</ol>
|
|
11799
|
-
|
|
11800
|
-
<p><strong>Option 2: Use Email/Password with Token</strong></p>
|
|
11790
|
+
<p>To create your account:</p>
|
|
11801
11791
|
<ol>
|
|
11802
11792
|
<li>Visit {{registrationUrl}}</li>
|
|
11803
|
-
<li>
|
|
11804
|
-
<li>Enter your email ({{practitionerEmail}}) and create a password</li>
|
|
11793
|
+
<li>Enter your email and create a password</li>
|
|
11805
11794
|
<li>When prompted, enter the token above</li>
|
|
11806
11795
|
</ol>
|
|
11807
11796
|
|
package/dist/index.d.mts
CHANGED
|
@@ -6992,9 +6992,10 @@ declare class PractitionerService extends BaseService {
|
|
|
6992
6992
|
/**
|
|
6993
6993
|
* Gets active tokens for a practitioner
|
|
6994
6994
|
* @param practitionerId ID of the practitioner
|
|
6995
|
+
* @param clinicId Optional clinic ID to filter tokens by. If provided, only returns tokens for this clinic.
|
|
6995
6996
|
* @returns Array of active tokens
|
|
6996
6997
|
*/
|
|
6997
|
-
getPractitionerActiveTokens(practitionerId: string): Promise<PractitionerToken[]>;
|
|
6998
|
+
getPractitionerActiveTokens(practitionerId: string, clinicId?: string): Promise<PractitionerToken[]>;
|
|
6998
6999
|
/**
|
|
6999
7000
|
* Gets a token by its string value and validates it
|
|
7000
7001
|
* @param tokenString The token string to find
|
|
@@ -7008,6 +7009,14 @@ declare class PractitionerService extends BaseService {
|
|
|
7008
7009
|
* @param userId ID of the user using the token
|
|
7009
7010
|
*/
|
|
7010
7011
|
markTokenAsUsed(tokenId: string, practitionerId: string, userId: string): Promise<void>;
|
|
7012
|
+
/**
|
|
7013
|
+
* Revokes a token by setting its status to REVOKED
|
|
7014
|
+
* @param tokenId ID of the token
|
|
7015
|
+
* @param practitionerId ID of the practitioner
|
|
7016
|
+
* @param clinicId ID of the clinic that owns the token. Used to verify ownership before revoking.
|
|
7017
|
+
* @throws Error if token doesn't exist or doesn't belong to the specified clinic
|
|
7018
|
+
*/
|
|
7019
|
+
revokeToken(tokenId: string, practitionerId: string, clinicId: string): Promise<void>;
|
|
7011
7020
|
/**
|
|
7012
7021
|
* Dohvata zdravstvenog radnika po ID-u
|
|
7013
7022
|
*/
|
|
@@ -8323,19 +8332,7 @@ declare class AuthService extends BaseService {
|
|
|
8323
8332
|
constructor(db: Firestore, auth: Auth, app: FirebaseApp, userService: UserService);
|
|
8324
8333
|
/**
|
|
8325
8334
|
* Waits for Firebase Auth state to settle after sign-in.
|
|
8326
|
-
*
|
|
8327
|
-
* In React Native with AsyncStorage persistence, there's a critical issue:
|
|
8328
|
-
* 1. signInWithCredential() sets auth.currentUser in memory immediately
|
|
8329
|
-
* 2. But AsyncStorage persistence happens asynchronously
|
|
8330
|
-
* 3. If AsyncStorage reads an old NULL value, it can OVERWRITE the current auth state
|
|
8331
|
-
* 4. This causes auth.currentUser to become NULL even after it was set
|
|
8332
|
-
*
|
|
8333
|
-
* This method uses onAuthStateChanged to wait for the auth state to be SET and STABLE.
|
|
8334
|
-
* It ensures the auth state persists through AsyncStorage operations.
|
|
8335
|
-
*
|
|
8336
|
-
* @param expectedUid - The UID we expect to see in auth.currentUser
|
|
8337
|
-
* @param timeoutMs - Maximum time to wait (default 5 seconds)
|
|
8338
|
-
* @returns Promise that resolves when auth state is ready and stable
|
|
8335
|
+
* In React Native with AsyncStorage persistence, auth state may not be immediately available.
|
|
8339
8336
|
*/
|
|
8340
8337
|
private waitForAuthStateToSettle;
|
|
8341
8338
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -6992,9 +6992,10 @@ declare class PractitionerService extends BaseService {
|
|
|
6992
6992
|
/**
|
|
6993
6993
|
* Gets active tokens for a practitioner
|
|
6994
6994
|
* @param practitionerId ID of the practitioner
|
|
6995
|
+
* @param clinicId Optional clinic ID to filter tokens by. If provided, only returns tokens for this clinic.
|
|
6995
6996
|
* @returns Array of active tokens
|
|
6996
6997
|
*/
|
|
6997
|
-
getPractitionerActiveTokens(practitionerId: string): Promise<PractitionerToken[]>;
|
|
6998
|
+
getPractitionerActiveTokens(practitionerId: string, clinicId?: string): Promise<PractitionerToken[]>;
|
|
6998
6999
|
/**
|
|
6999
7000
|
* Gets a token by its string value and validates it
|
|
7000
7001
|
* @param tokenString The token string to find
|
|
@@ -7008,6 +7009,14 @@ declare class PractitionerService extends BaseService {
|
|
|
7008
7009
|
* @param userId ID of the user using the token
|
|
7009
7010
|
*/
|
|
7010
7011
|
markTokenAsUsed(tokenId: string, practitionerId: string, userId: string): Promise<void>;
|
|
7012
|
+
/**
|
|
7013
|
+
* Revokes a token by setting its status to REVOKED
|
|
7014
|
+
* @param tokenId ID of the token
|
|
7015
|
+
* @param practitionerId ID of the practitioner
|
|
7016
|
+
* @param clinicId ID of the clinic that owns the token. Used to verify ownership before revoking.
|
|
7017
|
+
* @throws Error if token doesn't exist or doesn't belong to the specified clinic
|
|
7018
|
+
*/
|
|
7019
|
+
revokeToken(tokenId: string, practitionerId: string, clinicId: string): Promise<void>;
|
|
7011
7020
|
/**
|
|
7012
7021
|
* Dohvata zdravstvenog radnika po ID-u
|
|
7013
7022
|
*/
|
|
@@ -8323,19 +8332,7 @@ declare class AuthService extends BaseService {
|
|
|
8323
8332
|
constructor(db: Firestore, auth: Auth, app: FirebaseApp, userService: UserService);
|
|
8324
8333
|
/**
|
|
8325
8334
|
* Waits for Firebase Auth state to settle after sign-in.
|
|
8326
|
-
*
|
|
8327
|
-
* In React Native with AsyncStorage persistence, there's a critical issue:
|
|
8328
|
-
* 1. signInWithCredential() sets auth.currentUser in memory immediately
|
|
8329
|
-
* 2. But AsyncStorage persistence happens asynchronously
|
|
8330
|
-
* 3. If AsyncStorage reads an old NULL value, it can OVERWRITE the current auth state
|
|
8331
|
-
* 4. This causes auth.currentUser to become NULL even after it was set
|
|
8332
|
-
*
|
|
8333
|
-
* This method uses onAuthStateChanged to wait for the auth state to be SET and STABLE.
|
|
8334
|
-
* It ensures the auth state persists through AsyncStorage operations.
|
|
8335
|
-
*
|
|
8336
|
-
* @param expectedUid - The UID we expect to see in auth.currentUser
|
|
8337
|
-
* @param timeoutMs - Maximum time to wait (default 5 seconds)
|
|
8338
|
-
* @returns Promise that resolves when auth state is ready and stable
|
|
8335
|
+
* In React Native with AsyncStorage persistence, auth state may not be immediately available.
|
|
8339
8336
|
*/
|
|
8340
8337
|
private waitForAuthStateToSettle;
|
|
8341
8338
|
/**
|
package/dist/index.js
CHANGED
|
@@ -11533,6 +11533,24 @@ var PractitionerService = class extends BaseService {
|
|
|
11533
11533
|
if (!practitioner.clinics.includes(validatedData.clinicId)) {
|
|
11534
11534
|
throw new Error("Practitioner is not associated with this clinic");
|
|
11535
11535
|
}
|
|
11536
|
+
let expectedClinicGroupId = null;
|
|
11537
|
+
if (clinic.clinicGroupId === createdBy) {
|
|
11538
|
+
expectedClinicGroupId = createdBy;
|
|
11539
|
+
} else {
|
|
11540
|
+
try {
|
|
11541
|
+
const creatorClinic = await this.getClinicService().getClinic(createdBy);
|
|
11542
|
+
if (creatorClinic && creatorClinic.clinicGroupId === clinic.clinicGroupId) {
|
|
11543
|
+
expectedClinicGroupId = clinic.clinicGroupId;
|
|
11544
|
+
} else {
|
|
11545
|
+
throw new Error("Clinic does not belong to your clinic group");
|
|
11546
|
+
}
|
|
11547
|
+
} catch (error) {
|
|
11548
|
+
if (error.message === "Clinic does not belong to your clinic group") {
|
|
11549
|
+
throw error;
|
|
11550
|
+
}
|
|
11551
|
+
throw new Error("Clinic does not belong to your clinic group");
|
|
11552
|
+
}
|
|
11553
|
+
}
|
|
11536
11554
|
const expiration = validatedData.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1e3);
|
|
11537
11555
|
const tokenString = this.generateId().slice(0, 6).toUpperCase();
|
|
11538
11556
|
const token = {
|
|
@@ -11560,18 +11578,22 @@ var PractitionerService = class extends BaseService {
|
|
|
11560
11578
|
/**
|
|
11561
11579
|
* Gets active tokens for a practitioner
|
|
11562
11580
|
* @param practitionerId ID of the practitioner
|
|
11581
|
+
* @param clinicId Optional clinic ID to filter tokens by. If provided, only returns tokens for this clinic.
|
|
11563
11582
|
* @returns Array of active tokens
|
|
11564
11583
|
*/
|
|
11565
|
-
async getPractitionerActiveTokens(practitionerId) {
|
|
11584
|
+
async getPractitionerActiveTokens(practitionerId, clinicId) {
|
|
11566
11585
|
const tokensRef = (0, import_firestore32.collection)(
|
|
11567
11586
|
this.db,
|
|
11568
11587
|
`${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}`
|
|
11569
11588
|
);
|
|
11570
|
-
const
|
|
11571
|
-
tokensRef,
|
|
11589
|
+
const conditions = [
|
|
11572
11590
|
(0, import_firestore32.where)("status", "==", "active" /* ACTIVE */),
|
|
11573
11591
|
(0, import_firestore32.where)("expiresAt", ">", import_firestore32.Timestamp.now())
|
|
11574
|
-
|
|
11592
|
+
];
|
|
11593
|
+
if (clinicId) {
|
|
11594
|
+
conditions.push((0, import_firestore32.where)("clinicId", "==", clinicId));
|
|
11595
|
+
}
|
|
11596
|
+
const q = (0, import_firestore32.query)(tokensRef, ...conditions);
|
|
11575
11597
|
const querySnapshot = await (0, import_firestore32.getDocs)(q);
|
|
11576
11598
|
return querySnapshot.docs.map((doc47) => doc47.data());
|
|
11577
11599
|
}
|
|
@@ -11646,6 +11668,34 @@ var PractitionerService = class extends BaseService {
|
|
|
11646
11668
|
usedAt: import_firestore32.Timestamp.now()
|
|
11647
11669
|
});
|
|
11648
11670
|
}
|
|
11671
|
+
/**
|
|
11672
|
+
* Revokes a token by setting its status to REVOKED
|
|
11673
|
+
* @param tokenId ID of the token
|
|
11674
|
+
* @param practitionerId ID of the practitioner
|
|
11675
|
+
* @param clinicId ID of the clinic that owns the token. Used to verify ownership before revoking.
|
|
11676
|
+
* @throws Error if token doesn't exist or doesn't belong to the specified clinic
|
|
11677
|
+
*/
|
|
11678
|
+
async revokeToken(tokenId, practitionerId, clinicId) {
|
|
11679
|
+
const tokenRef = (0, import_firestore32.doc)(
|
|
11680
|
+
this.db,
|
|
11681
|
+
`${PRACTITIONERS_COLLECTION}/${practitionerId}/${REGISTER_TOKENS_COLLECTION}/${tokenId}`
|
|
11682
|
+
);
|
|
11683
|
+
const tokenDoc = await (0, import_firestore32.getDoc)(tokenRef);
|
|
11684
|
+
if (!tokenDoc.exists()) {
|
|
11685
|
+
throw new Error("Token not found");
|
|
11686
|
+
}
|
|
11687
|
+
const tokenData = tokenDoc.data();
|
|
11688
|
+
if (tokenData.clinicId !== clinicId) {
|
|
11689
|
+
throw new Error("Token does not belong to the specified clinic");
|
|
11690
|
+
}
|
|
11691
|
+
if (tokenData.status !== "active" /* ACTIVE */) {
|
|
11692
|
+
throw new Error("Token is not active and cannot be revoked");
|
|
11693
|
+
}
|
|
11694
|
+
await (0, import_firestore32.updateDoc)(tokenRef, {
|
|
11695
|
+
status: "revoked" /* REVOKED */,
|
|
11696
|
+
updatedAt: (0, import_firestore32.serverTimestamp)()
|
|
11697
|
+
});
|
|
11698
|
+
}
|
|
11649
11699
|
/**
|
|
11650
11700
|
* Dohvata zdravstvenog radnika po ID-u
|
|
11651
11701
|
*/
|
|
@@ -12880,11 +12930,6 @@ var PractitionerService = class extends BaseService {
|
|
|
12880
12930
|
var UserService = class extends BaseService {
|
|
12881
12931
|
constructor(db, auth, app, patientService, clinicAdminService, practitionerService) {
|
|
12882
12932
|
super(db, auth, app);
|
|
12883
|
-
if (!this.auth.__userServiceId) {
|
|
12884
|
-
this.auth.__userServiceId = "user-service-" + Date.now();
|
|
12885
|
-
}
|
|
12886
|
-
console.log("[USER_SERVICE] Constructor - auth ID:", this.auth.__userServiceId);
|
|
12887
|
-
console.log("[USER_SERVICE] Constructor - auth.__authServiceId:", this.auth.__authServiceId || "NOT SET");
|
|
12888
12933
|
if (!patientService) {
|
|
12889
12934
|
patientService = new PatientService(db, auth, app);
|
|
12890
12935
|
}
|
|
@@ -12911,25 +12956,6 @@ var UserService = class extends BaseService {
|
|
|
12911
12956
|
* Kreira novog korisnika na osnovu Firebase korisnika
|
|
12912
12957
|
*/
|
|
12913
12958
|
async createUser(firebaseUser, roles = ["patient" /* PATIENT */], options) {
|
|
12914
|
-
var _a, _b, _c, _d, _e, _f, _g, _h, _i;
|
|
12915
|
-
console.log("[USER_SERVICE] ====== CREATE USER DEBUG ======");
|
|
12916
|
-
console.log(this.auth);
|
|
12917
|
-
console.log("[USER_SERVICE] Auth instance ID:", ((_a = this.auth) == null ? void 0 : _a.__debugId) || "no-id");
|
|
12918
|
-
console.log("[USER_SERVICE] Current auth state:", {
|
|
12919
|
-
currentUser: ((_c = (_b = this.auth) == null ? void 0 : _b.currentUser) == null ? void 0 : _c.uid) || "NULL",
|
|
12920
|
-
currentUserEmail: ((_e = (_d = this.auth) == null ? void 0 : _d.currentUser) == null ? void 0 : _e.email) || "NULL",
|
|
12921
|
-
currentUserProvider: ((_g = (_f = this.auth) == null ? void 0 : _f.currentUser) == null ? void 0 : _g.providerId) || "NULL"
|
|
12922
|
-
});
|
|
12923
|
-
console.log("[USER_SERVICE] Firebase user passed to createUser:", {
|
|
12924
|
-
uid: (firebaseUser == null ? void 0 : firebaseUser.uid) || "NULL",
|
|
12925
|
-
email: (firebaseUser == null ? void 0 : firebaseUser.email) || "NULL",
|
|
12926
|
-
providerId: (firebaseUser == null ? void 0 : firebaseUser.providerId) || "NULL",
|
|
12927
|
-
isAnonymous: firebaseUser == null ? void 0 : firebaseUser.isAnonymous
|
|
12928
|
-
});
|
|
12929
|
-
console.log("[USER_SERVICE] Auth instances match:", ((_i = (_h = this.auth) == null ? void 0 : _h.currentUser) == null ? void 0 : _i.uid) === (firebaseUser == null ? void 0 : firebaseUser.uid));
|
|
12930
|
-
console.log("[USER_SERVICE] Document path:", `${USERS_COLLECTION}/${firebaseUser == null ? void 0 : firebaseUser.uid}`);
|
|
12931
|
-
console.log("[USER_SERVICE] Roles:", roles);
|
|
12932
|
-
console.log("[USER_SERVICE] ================================");
|
|
12933
12959
|
const userData = {
|
|
12934
12960
|
uid: firebaseUser.uid,
|
|
12935
12961
|
email: firebaseUser.email,
|
|
@@ -12939,22 +12965,7 @@ var UserService = class extends BaseService {
|
|
|
12939
12965
|
updatedAt: (0, import_firestore33.serverTimestamp)(),
|
|
12940
12966
|
lastLoginAt: (0, import_firestore33.serverTimestamp)()
|
|
12941
12967
|
};
|
|
12942
|
-
|
|
12943
|
-
uid: userData.uid,
|
|
12944
|
-
email: userData.email,
|
|
12945
|
-
roles: userData.roles
|
|
12946
|
-
});
|
|
12947
|
-
try {
|
|
12948
|
-
await (0, import_firestore33.setDoc)((0, import_firestore33.doc)(this.db, USERS_COLLECTION, userData.uid), userData);
|
|
12949
|
-
console.log("[USER_SERVICE] \u2705 setDoc SUCCEEDED for:", userData.uid);
|
|
12950
|
-
} catch (error) {
|
|
12951
|
-
console.error("[USER_SERVICE] \u274C setDoc FAILED:", {
|
|
12952
|
-
errorCode: error == null ? void 0 : error.code,
|
|
12953
|
-
errorMessage: error == null ? void 0 : error.message,
|
|
12954
|
-
uid: userData.uid
|
|
12955
|
-
});
|
|
12956
|
-
throw error;
|
|
12957
|
-
}
|
|
12968
|
+
await (0, import_firestore33.setDoc)((0, import_firestore33.doc)(this.db, USERS_COLLECTION, userData.uid), userData);
|
|
12958
12969
|
if (options == null ? void 0 : options.skipProfileCreation) {
|
|
12959
12970
|
return this.getUserById(userData.uid);
|
|
12960
12971
|
}
|
|
@@ -15384,59 +15395,28 @@ var validatePractitionerProfileData = async (profileData) => {
|
|
|
15384
15395
|
// src/services/auth/auth.service.ts
|
|
15385
15396
|
var AuthService = class extends BaseService {
|
|
15386
15397
|
constructor(db, auth, app, userService) {
|
|
15387
|
-
var _a;
|
|
15388
15398
|
super(db, auth, app);
|
|
15389
15399
|
this.googleProvider = new import_auth8.GoogleAuthProvider();
|
|
15390
15400
|
this.userService = userService || new UserService(db, auth, app);
|
|
15391
|
-
(0, import_auth8.onAuthStateChanged)(this.auth, (user) => {
|
|
15392
|
-
var _a2, _b;
|
|
15393
|
-
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
15394
|
-
const stackTrace = ((_a2 = new Error().stack) == null ? void 0 : _a2.split("\n").slice(2, 5).join("\n")) || "N/A";
|
|
15395
|
-
console.log(`[AUTH STATE CHANGE] ${timestamp}`);
|
|
15396
|
-
console.log(`[AUTH STATE CHANGE] User: ${(user == null ? void 0 : user.uid) || "NULL"} (email: ${(user == null ? void 0 : user.email) || "N/A"})`);
|
|
15397
|
-
console.log(`[AUTH STATE CHANGE] auth.currentUser: ${((_b = this.auth.currentUser) == null ? void 0 : _b.uid) || "NULL"}`);
|
|
15398
|
-
console.log(`[AUTH STATE CHANGE] Stack trace (first 3 frames):
|
|
15399
|
-
${stackTrace}`);
|
|
15400
|
-
console.log("[AUTH STATE CHANGE] ---");
|
|
15401
|
-
});
|
|
15402
|
-
console.log("[AUTH] AuthService initialized");
|
|
15403
|
-
console.log("[AUTH] Initial auth.currentUser:", ((_a = this.auth.currentUser) == null ? void 0 : _a.uid) || "NULL");
|
|
15404
15401
|
}
|
|
15405
15402
|
/**
|
|
15406
15403
|
* Waits for Firebase Auth state to settle after sign-in.
|
|
15407
|
-
*
|
|
15408
|
-
* In React Native with AsyncStorage persistence, there's a critical issue:
|
|
15409
|
-
* 1. signInWithCredential() sets auth.currentUser in memory immediately
|
|
15410
|
-
* 2. But AsyncStorage persistence happens asynchronously
|
|
15411
|
-
* 3. If AsyncStorage reads an old NULL value, it can OVERWRITE the current auth state
|
|
15412
|
-
* 4. This causes auth.currentUser to become NULL even after it was set
|
|
15413
|
-
*
|
|
15414
|
-
* This method uses onAuthStateChanged to wait for the auth state to be SET and STABLE.
|
|
15415
|
-
* It ensures the auth state persists through AsyncStorage operations.
|
|
15416
|
-
*
|
|
15417
|
-
* @param expectedUid - The UID we expect to see in auth.currentUser
|
|
15418
|
-
* @param timeoutMs - Maximum time to wait (default 5 seconds)
|
|
15419
|
-
* @returns Promise that resolves when auth state is ready and stable
|
|
15404
|
+
* In React Native with AsyncStorage persistence, auth state may not be immediately available.
|
|
15420
15405
|
*/
|
|
15421
15406
|
async waitForAuthStateToSettle(expectedUid, timeoutMs = 5e3) {
|
|
15422
|
-
var _a, _b
|
|
15407
|
+
var _a, _b;
|
|
15423
15408
|
if (((_a = this.auth.currentUser) == null ? void 0 : _a.uid) === expectedUid) {
|
|
15424
|
-
console.log("[AUTH] Auth state appears set, waiting for stability...");
|
|
15425
15409
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
15426
15410
|
if (((_b = this.auth.currentUser) == null ? void 0 : _b.uid) === expectedUid) {
|
|
15427
|
-
console.log("[AUTH] \u2705 Auth state stable for:", expectedUid);
|
|
15428
15411
|
return;
|
|
15429
15412
|
}
|
|
15430
15413
|
}
|
|
15431
|
-
console.log("[AUTH] Waiting for auth state to settle for:", expectedUid);
|
|
15432
|
-
console.log("[AUTH] Current auth.currentUser:", ((_c = this.auth.currentUser) == null ? void 0 : _c.uid) || "NULL");
|
|
15433
15414
|
return new Promise((resolve, reject) => {
|
|
15434
15415
|
const startTime = Date.now();
|
|
15435
15416
|
let resolved = false;
|
|
15436
15417
|
const unsubscribe = (0, import_auth8.onAuthStateChanged)(this.auth, (user) => {
|
|
15437
15418
|
if (resolved) return;
|
|
15438
15419
|
const currentUid = (user == null ? void 0 : user.uid) || null;
|
|
15439
|
-
console.log("[AUTH] onAuthStateChanged fired:", currentUid || "NULL");
|
|
15440
15420
|
if (currentUid === expectedUid) {
|
|
15441
15421
|
setTimeout(() => {
|
|
15442
15422
|
var _a2;
|
|
@@ -15445,27 +15425,17 @@ ${stackTrace}`);
|
|
|
15445
15425
|
resolved = true;
|
|
15446
15426
|
unsubscribe();
|
|
15447
15427
|
clearTimeout(timeout);
|
|
15448
|
-
const elapsed = Date.now() - startTime;
|
|
15449
|
-
console.log(`[AUTH] \u2705 Auth state settled and stable after ${elapsed}ms for:`, expectedUid);
|
|
15450
15428
|
resolve();
|
|
15451
|
-
} else {
|
|
15452
|
-
console.warn("[AUTH] \u26A0\uFE0F Auth state became NULL after being set, waiting more...");
|
|
15453
15429
|
}
|
|
15454
15430
|
}, 300);
|
|
15455
|
-
} else if (currentUid === null && Date.now() - startTime > 1e3) {
|
|
15456
|
-
console.error("[AUTH] \u274C Auth state became NULL after being set!");
|
|
15457
|
-
console.error("[AUTH] This indicates AsyncStorage persistence issue");
|
|
15458
15431
|
}
|
|
15459
15432
|
});
|
|
15460
15433
|
const timeout = setTimeout(() => {
|
|
15461
|
-
var _a2
|
|
15434
|
+
var _a2;
|
|
15462
15435
|
if (resolved) return;
|
|
15463
15436
|
resolved = true;
|
|
15464
15437
|
unsubscribe();
|
|
15465
|
-
|
|
15466
|
-
console.error("[AUTH] Expected UID:", expectedUid);
|
|
15467
|
-
console.error("[AUTH] Actual auth.currentUser:", ((_a2 = this.auth.currentUser) == null ? void 0 : _a2.uid) || "NULL");
|
|
15468
|
-
reject(new Error(`Timeout waiting for auth state to settle. Expected: ${expectedUid}, Got: ${((_b2 = this.auth.currentUser) == null ? void 0 : _b2.uid) || "NULL"}`));
|
|
15438
|
+
reject(new Error(`Timeout waiting for auth state to settle. Expected: ${expectedUid}, Got: ${((_a2 = this.auth.currentUser) == null ? void 0 : _a2.uid) || "NULL"}`));
|
|
15469
15439
|
}, timeoutMs);
|
|
15470
15440
|
});
|
|
15471
15441
|
}
|
|
@@ -16037,7 +16007,6 @@ ${stackTrace}`);
|
|
|
16037
16007
|
* @returns Object containing user and claimed practitioner
|
|
16038
16008
|
*/
|
|
16039
16009
|
async claimDraftProfilesWithGoogle(idToken, practitionerIds) {
|
|
16040
|
-
var _a, _b;
|
|
16041
16010
|
try {
|
|
16042
16011
|
console.log("[AUTH] Starting claim draft profiles with Google", {
|
|
16043
16012
|
practitionerIdsCount: practitionerIds.length,
|
|
@@ -16046,21 +16015,14 @@ ${stackTrace}`);
|
|
|
16046
16015
|
if (practitionerIds.length === 0) {
|
|
16047
16016
|
throw new AuthError("No practitioner profiles selected to claim", "AUTH/NO_PROFILES_SELECTED", 400);
|
|
16048
16017
|
}
|
|
16049
|
-
console.log("[AUTH] currentUser BEFORE sign-in:", ((_a = this.auth.currentUser) == null ? void 0 : _a.uid) || "NULL");
|
|
16050
|
-
console.log("[AUTH] Signing in with Google credential...");
|
|
16051
16018
|
const credential = import_auth8.GoogleAuthProvider.credential(idToken);
|
|
16052
16019
|
const result = await (0, import_auth8.signInWithCredential)(this.auth, credential);
|
|
16053
16020
|
const firebaseUser = result.user;
|
|
16054
|
-
console.log("[AUTH] currentUser IMMEDIATELY AFTER sign-in:", ((_b = this.auth.currentUser) == null ? void 0 : _b.uid) || "NULL");
|
|
16055
|
-
console.log("[AUTH] User returned from signInWithCredential:", firebaseUser.uid);
|
|
16056
16021
|
const practitionerService = new PractitionerService(this.db, this.auth, this.app);
|
|
16057
16022
|
let user;
|
|
16058
16023
|
try {
|
|
16059
16024
|
user = await this.userService.getUserById(firebaseUser.uid);
|
|
16060
|
-
console.log("[AUTH] User document found:", user.uid);
|
|
16061
16025
|
} catch (userError) {
|
|
16062
|
-
console.error("[AUTH] \u274C User document should already exist! It should have been created in signUpPractitionerWithGoogle.");
|
|
16063
|
-
console.error("[AUTH] This indicates a bug - User doc creation failed in the initial Google sign-in flow.");
|
|
16064
16026
|
throw new AuthError(
|
|
16065
16027
|
"User account not properly initialized. Please try signing in again.",
|
|
16066
16028
|
"AUTH/USER_NOT_INITIALIZED",
|
|
@@ -16069,30 +16031,22 @@ ${stackTrace}`);
|
|
|
16069
16031
|
}
|
|
16070
16032
|
let practitioner;
|
|
16071
16033
|
if (practitionerIds.length === 1) {
|
|
16072
|
-
console.log("[AUTH] Claiming single draft profile:", practitionerIds[0]);
|
|
16073
16034
|
practitioner = await practitionerService.claimDraftProfileWithGoogle(
|
|
16074
16035
|
practitionerIds[0],
|
|
16075
16036
|
firebaseUser.uid
|
|
16076
16037
|
);
|
|
16077
16038
|
} else {
|
|
16078
|
-
console.log("[AUTH] Claiming multiple draft profiles:", practitionerIds);
|
|
16079
16039
|
practitioner = await practitionerService.claimMultipleDraftProfilesWithGoogle(
|
|
16080
16040
|
practitionerIds,
|
|
16081
16041
|
firebaseUser.uid
|
|
16082
16042
|
);
|
|
16083
16043
|
}
|
|
16084
|
-
console.log("[AUTH] Draft profiles claimed:", practitioner.id);
|
|
16085
16044
|
if (!user.practitionerProfile || user.practitionerProfile !== practitioner.id) {
|
|
16086
|
-
console.log("[AUTH] Linking practitioner to user");
|
|
16087
16045
|
await this.userService.updateUser(firebaseUser.uid, {
|
|
16088
16046
|
practitionerProfile: practitioner.id
|
|
16089
16047
|
});
|
|
16090
16048
|
}
|
|
16091
16049
|
const updatedUser = await this.userService.getUserById(firebaseUser.uid);
|
|
16092
|
-
console.log("[AUTH] Draft profiles claimed successfully", {
|
|
16093
|
-
userId: updatedUser.uid,
|
|
16094
|
-
practitionerId: practitioner.id
|
|
16095
|
-
});
|
|
16096
16050
|
return {
|
|
16097
16051
|
user: updatedUser,
|
|
16098
16052
|
practitioner
|
|
@@ -16221,9 +16175,6 @@ ${stackTrace}`);
|
|
|
16221
16175
|
const credential = import_auth8.GoogleAuthProvider.credential(idToken);
|
|
16222
16176
|
const { user: firebaseUser } = await (0, import_auth8.signInWithCredential)(this.auth, credential);
|
|
16223
16177
|
console.log("[AUTH] Firebase user signed in:", firebaseUser.uid);
|
|
16224
|
-
console.log("[AUTH] Waiting for auth state to settle after sign-in...");
|
|
16225
|
-
await this.waitForAuthStateToSettle(firebaseUser.uid);
|
|
16226
|
-
console.log("[AUTH] \u2705 Auth state settled, proceeding with Firestore queries");
|
|
16227
16178
|
const existingUser = await this.userService.getUserById(firebaseUser.uid);
|
|
16228
16179
|
if (existingUser) {
|
|
16229
16180
|
console.log("[AUTH] Existing user found, returning profile:", existingUser.uid);
|
|
@@ -16249,7 +16200,6 @@ ${stackTrace}`);
|
|
|
16249
16200
|
* @returns Object containing user, practitioner (if exists), and draft profiles (if any)
|
|
16250
16201
|
*/
|
|
16251
16202
|
async signUpPractitionerWithGoogle(idToken) {
|
|
16252
|
-
var _a, _b, _c, _d;
|
|
16253
16203
|
try {
|
|
16254
16204
|
console.log("[AUTH] Starting practitioner Google Sign-In/Sign-Up");
|
|
16255
16205
|
let email;
|
|
@@ -16274,49 +16224,30 @@ ${stackTrace}`);
|
|
|
16274
16224
|
);
|
|
16275
16225
|
}
|
|
16276
16226
|
const normalizedEmail = email.toLowerCase().trim();
|
|
16277
|
-
console.log("[AUTH] Extracted email from Google token:", normalizedEmail);
|
|
16278
16227
|
const methods = await (0, import_auth8.fetchSignInMethodsForEmail)(this.auth, normalizedEmail);
|
|
16279
16228
|
const hasGoogleMethod = methods.includes(import_auth8.GoogleAuthProvider.GOOGLE_SIGN_IN_METHOD);
|
|
16280
16229
|
const hasEmailMethod = methods.includes(import_auth8.EmailAuthProvider.EMAIL_PASSWORD_SIGN_IN_METHOD);
|
|
16281
16230
|
const practitionerService = new PractitionerService(this.db, this.auth, this.app);
|
|
16282
16231
|
if (hasGoogleMethod) {
|
|
16283
|
-
console.log("[AUTH] User exists with Google provider, signing in");
|
|
16284
16232
|
const credential2 = import_auth8.GoogleAuthProvider.credential(idToken);
|
|
16285
16233
|
const { user: firebaseUser2 } = await (0, import_auth8.signInWithCredential)(this.auth, credential2);
|
|
16286
|
-
console.log("[AUTH] Waiting for auth state to settle after sign-in...");
|
|
16287
16234
|
await this.waitForAuthStateToSettle(firebaseUser2.uid);
|
|
16288
|
-
console.log("[AUTH] \u2705 Auth state settled, proceeding with Firestore queries");
|
|
16289
16235
|
let existingUser2 = null;
|
|
16290
16236
|
try {
|
|
16291
16237
|
existingUser2 = await this.userService.getUserById(firebaseUser2.uid);
|
|
16292
|
-
console.log("[AUTH] User document found:", existingUser2.uid);
|
|
16293
16238
|
} catch (userError) {
|
|
16294
|
-
console.log("[AUTH] User document not found in Firestore, checking for draft profiles", {
|
|
16295
|
-
errorCode: userError == null ? void 0 : userError.code,
|
|
16296
|
-
errorMessage: userError == null ? void 0 : userError.message,
|
|
16297
|
-
errorType: (_a = userError == null ? void 0 : userError.constructor) == null ? void 0 : _a.name,
|
|
16298
|
-
isAuthError: userError instanceof AuthError
|
|
16299
|
-
});
|
|
16300
16239
|
if (!this.auth.currentUser || this.auth.currentUser.uid !== firebaseUser2.uid) {
|
|
16301
|
-
|
|
16302
|
-
|
|
16303
|
-
console.error("[AUTH] Actual auth.currentUser:", ((_b = this.auth.currentUser) == null ? void 0 : _b.uid) || "NULL");
|
|
16304
|
-
console.log("[AUTH] Waiting for auth state to recover...");
|
|
16240
|
+
const credential3 = import_auth8.GoogleAuthProvider.credential(idToken);
|
|
16241
|
+
await (0, import_auth8.signInWithCredential)(this.auth, credential3);
|
|
16305
16242
|
await this.waitForAuthStateToSettle(firebaseUser2.uid, 2e3);
|
|
16306
16243
|
}
|
|
16307
16244
|
const practitionerService2 = new PractitionerService(this.db, this.auth, this.app);
|
|
16308
16245
|
const draftProfiles3 = await practitionerService2.getDraftProfilesByEmail(normalizedEmail);
|
|
16309
|
-
console.log("[AUTH] Draft profiles check result:", {
|
|
16310
|
-
email: normalizedEmail,
|
|
16311
|
-
draftProfilesCount: draftProfiles3.length,
|
|
16312
|
-
draftProfileIds: draftProfiles3.map((p) => p.id)
|
|
16313
|
-
});
|
|
16314
16246
|
if (draftProfiles3.length === 0) {
|
|
16315
|
-
console.log("[AUTH] No draft profiles found, signing out and throwing error");
|
|
16316
16247
|
try {
|
|
16317
16248
|
await (0, import_auth8.signOut)(this.auth);
|
|
16318
16249
|
} catch (signOutError) {
|
|
16319
|
-
console.warn("[AUTH] Error signing out
|
|
16250
|
+
console.warn("[AUTH] Error signing out:", signOutError);
|
|
16320
16251
|
}
|
|
16321
16252
|
throw new AuthError(
|
|
16322
16253
|
"No clinic invitation found for this email. Please contact your clinic administrator to receive an invitation, or use the token provided by your clinic.",
|
|
@@ -16324,28 +16255,20 @@ ${stackTrace}`);
|
|
|
16324
16255
|
404
|
|
16325
16256
|
);
|
|
16326
16257
|
}
|
|
16327
|
-
console.log("[AUTH] Draft profiles found, creating User document IMMEDIATELY after sign-in");
|
|
16328
|
-
console.log("[AUTH] auth.currentUser at User creation time:", ((_c = this.auth.currentUser) == null ? void 0 : _c.uid) || "NULL");
|
|
16329
16258
|
try {
|
|
16330
16259
|
const newUser = await this.userService.createUser(firebaseUser2, ["practitioner" /* PRACTITIONER */], {
|
|
16331
16260
|
skipProfileCreation: true
|
|
16332
16261
|
});
|
|
16333
|
-
console.log("[AUTH] \u2705 User document created successfully:", newUser.uid);
|
|
16334
16262
|
return {
|
|
16335
16263
|
user: newUser,
|
|
16336
16264
|
practitioner: null,
|
|
16337
16265
|
draftProfiles: draftProfiles3
|
|
16338
16266
|
};
|
|
16339
16267
|
} catch (createUserError) {
|
|
16340
|
-
console.error("[AUTH] \u274C Failed to create User document:", {
|
|
16341
|
-
errorCode: createUserError == null ? void 0 : createUserError.code,
|
|
16342
|
-
errorMessage: createUserError == null ? void 0 : createUserError.message,
|
|
16343
|
-
uid: firebaseUser2.uid
|
|
16344
|
-
});
|
|
16345
16268
|
try {
|
|
16346
16269
|
await (0, import_auth8.signOut)(this.auth);
|
|
16347
16270
|
} catch (signOutError) {
|
|
16348
|
-
console.warn("[AUTH] Error signing out
|
|
16271
|
+
console.warn("[AUTH] Error signing out:", signOutError);
|
|
16349
16272
|
}
|
|
16350
16273
|
throw createUserError;
|
|
16351
16274
|
}
|
|
@@ -16370,14 +16293,12 @@ ${stackTrace}`);
|
|
|
16370
16293
|
};
|
|
16371
16294
|
}
|
|
16372
16295
|
if (hasEmailMethod && !hasGoogleMethod) {
|
|
16373
|
-
console.log("[AUTH] User exists with email/password only");
|
|
16374
16296
|
throw new AuthError(
|
|
16375
16297
|
"An account with this email already exists. Please sign in with your email and password, then link your Google account in settings.",
|
|
16376
16298
|
"AUTH/EMAIL_ALREADY_EXISTS",
|
|
16377
16299
|
409
|
|
16378
16300
|
);
|
|
16379
16301
|
}
|
|
16380
|
-
console.log("[AUTH] Signing in with Google credential");
|
|
16381
16302
|
const credential = import_auth8.GoogleAuthProvider.credential(idToken);
|
|
16382
16303
|
let firebaseUser;
|
|
16383
16304
|
try {
|
|
@@ -16393,30 +16314,23 @@ ${stackTrace}`);
|
|
|
16393
16314
|
}
|
|
16394
16315
|
throw error;
|
|
16395
16316
|
}
|
|
16396
|
-
console.log("[AUTH] Waiting for auth state to settle after sign-in...");
|
|
16397
16317
|
await this.waitForAuthStateToSettle(firebaseUser.uid);
|
|
16398
|
-
console.log("[AUTH] \u2705 Auth state settled, proceeding with Firestore queries");
|
|
16399
16318
|
let existingUser = null;
|
|
16400
16319
|
try {
|
|
16401
16320
|
const existingUserDoc = await this.userService.getUserById(firebaseUser.uid);
|
|
16402
16321
|
if (existingUserDoc) {
|
|
16403
16322
|
existingUser = existingUserDoc;
|
|
16404
|
-
console.log("[AUTH] Found existing User document");
|
|
16405
16323
|
}
|
|
16406
16324
|
} catch (error) {
|
|
16407
|
-
console.error("[AUTH] Error checking for existing user:", error);
|
|
16408
16325
|
}
|
|
16409
|
-
console.log("[AUTH] Checking for draft profiles");
|
|
16410
16326
|
let draftProfiles = [];
|
|
16411
16327
|
try {
|
|
16412
16328
|
draftProfiles = await practitionerService.getDraftProfilesByEmail(normalizedEmail);
|
|
16413
|
-
console.log("[AUTH] Draft profiles check complete", { count: draftProfiles.length });
|
|
16414
16329
|
} catch (draftCheckError) {
|
|
16415
|
-
console.error("[AUTH] Error checking draft profiles:", draftCheckError);
|
|
16416
16330
|
try {
|
|
16417
16331
|
await (0, import_auth8.signOut)(this.auth);
|
|
16418
16332
|
} catch (signOutError) {
|
|
16419
|
-
console.warn("[AUTH] Error signing out
|
|
16333
|
+
console.warn("[AUTH] Error signing out:", signOutError);
|
|
16420
16334
|
}
|
|
16421
16335
|
throw new AuthError(
|
|
16422
16336
|
"No clinic invitation found for this email. Please contact your clinic administrator to receive an invitation, or use the token provided by your clinic.",
|
|
@@ -16427,55 +16341,38 @@ ${stackTrace}`);
|
|
|
16427
16341
|
let user;
|
|
16428
16342
|
if (existingUser) {
|
|
16429
16343
|
user = existingUser;
|
|
16430
|
-
console.log("[AUTH] Using existing user account");
|
|
16431
16344
|
} else {
|
|
16432
16345
|
if (draftProfiles.length === 0) {
|
|
16433
|
-
console.log("[AUTH] No draft profiles found, signing out and throwing error");
|
|
16434
16346
|
try {
|
|
16435
16347
|
await (0, import_auth8.signOut)(this.auth);
|
|
16436
16348
|
} catch (signOutError) {
|
|
16437
|
-
console.warn("[AUTH] Error signing out
|
|
16349
|
+
console.warn("[AUTH] Error signing out:", signOutError);
|
|
16438
16350
|
}
|
|
16439
|
-
|
|
16351
|
+
throw new AuthError(
|
|
16440
16352
|
"No clinic invitation found for this email. Please contact your clinic administrator to receive an invitation, or use the token provided by your clinic.",
|
|
16441
16353
|
"AUTH/NO_DRAFT_PROFILES",
|
|
16442
16354
|
404
|
|
16443
16355
|
);
|
|
16444
|
-
console.log("[AUTH] Throwing NO_DRAFT_PROFILES error:", noDraftError.code);
|
|
16445
|
-
throw noDraftError;
|
|
16446
16356
|
}
|
|
16447
16357
|
user = await this.userService.createUser(firebaseUser, ["practitioner" /* PRACTITIONER */], {
|
|
16448
16358
|
skipProfileCreation: true
|
|
16449
16359
|
});
|
|
16450
|
-
console.log("[AUTH] Created new user account with draft profiles available");
|
|
16451
16360
|
}
|
|
16452
16361
|
let practitioner = null;
|
|
16453
16362
|
if (user.practitionerProfile) {
|
|
16454
16363
|
practitioner = await practitionerService.getPractitioner(user.practitionerProfile);
|
|
16455
16364
|
}
|
|
16456
|
-
console.log("[AUTH] Google Sign-In complete", {
|
|
16457
|
-
userId: user.uid,
|
|
16458
|
-
hasPractitioner: !!practitioner,
|
|
16459
|
-
draftProfilesCount: draftProfiles.length
|
|
16460
|
-
});
|
|
16461
16365
|
return {
|
|
16462
16366
|
user,
|
|
16463
16367
|
practitioner,
|
|
16464
16368
|
draftProfiles
|
|
16465
16369
|
};
|
|
16466
16370
|
} catch (error) {
|
|
16467
|
-
console.error("[AUTH] Error in signUpPractitionerWithGoogle:", error);
|
|
16468
|
-
console.error("[AUTH] Error type:", (_d = error == null ? void 0 : error.constructor) == null ? void 0 : _d.name);
|
|
16469
|
-
console.error("[AUTH] Error instanceof AuthError:", error instanceof AuthError);
|
|
16470
|
-
console.error("[AUTH] Error code:", error == null ? void 0 : error.code);
|
|
16471
|
-
console.error("[AUTH] Error message:", error == null ? void 0 : error.message);
|
|
16472
16371
|
if (error instanceof AuthError) {
|
|
16473
|
-
console.log("[AUTH] Preserving AuthError:", error.code);
|
|
16474
16372
|
throw error;
|
|
16475
16373
|
}
|
|
16476
16374
|
const errorMessage = (error == null ? void 0 : error.message) || (error == null ? void 0 : error.toString()) || "";
|
|
16477
16375
|
if (errorMessage.includes("NO_DRAFT_PROFILES") || errorMessage.includes("clinic invitation")) {
|
|
16478
|
-
console.log("[AUTH] Detected clinic invitation error in message, converting to AuthError");
|
|
16479
16376
|
throw new AuthError(
|
|
16480
16377
|
"No clinic invitation found for this email. Please contact your clinic administrator to receive an invitation, or use the token provided by your clinic.",
|
|
16481
16378
|
"AUTH/NO_DRAFT_PROFILES",
|
|
@@ -16483,7 +16380,6 @@ ${stackTrace}`);
|
|
|
16483
16380
|
);
|
|
16484
16381
|
}
|
|
16485
16382
|
const wrappedError = handleFirebaseError(error);
|
|
16486
|
-
console.log("[AUTH] Wrapped error:", wrappedError.message);
|
|
16487
16383
|
if (wrappedError.message.includes("permissions") || wrappedError.message.includes("Account creation failed")) {
|
|
16488
16384
|
throw new AuthError(
|
|
16489
16385
|
"No clinic invitation found for this email. Please contact your clinic administrator to receive an invitation, or use the token provided by your clinic.",
|