@churchapps/apphelper-login 0.5.0 → 0.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +103 -103
- package/dist/helpers/AnalyticsHelper.d.ts.map +1 -1
- package/dist/helpers/AnalyticsHelper.js +21 -6
- package/dist/helpers/AnalyticsHelper.js.map +1 -1
- package/package.json +57 -57
- package/src/LoginPage.tsx +305 -305
- package/src/LogoutPage.tsx +43 -43
- package/src/components/Forgot.tsx +246 -246
- package/src/components/Login.tsx +305 -305
- package/src/components/LoginSetPassword.tsx +297 -297
- package/src/components/Register.tsx +370 -370
- package/src/components/SelectChurchModal.tsx +88 -88
- package/src/components/SelectChurchRegister.tsx +88 -88
- package/src/components/SelectChurchSearch.tsx +85 -85
- package/src/components/SelectableChurch.tsx +114 -114
- package/src/helpers/AnalyticsHelper.ts +44 -32
- package/src/helpers/Locale.ts +248 -248
- package/src/helpers/index.ts +1 -1
- package/src/index.ts +10 -10
- package/tsconfig.json +29 -29
package/src/LoginPage.tsx
CHANGED
|
@@ -1,305 +1,305 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import * as React from "react";
|
|
4
|
-
import { ErrorMessages, FloatingSupport, Loading } from "@churchapps/apphelper";
|
|
5
|
-
import { LoginResponseInterface, UserContextInterface, ChurchInterface, UserInterface, LoginUserChurchInterface, PersonInterface } from "@churchapps/helpers";
|
|
6
|
-
import { ApiHelper, ArrayHelper, UserHelper, CommonEnvironmentHelper } from "@churchapps/helpers";
|
|
7
|
-
import { AnalyticsHelper, Locale } from "./helpers";
|
|
8
|
-
import { useCookies, CookiesProvider } from "react-cookie"
|
|
9
|
-
import { Register } from "./components/Register"
|
|
10
|
-
import { SelectChurchModal } from "./components/SelectChurchModal"
|
|
11
|
-
import { Forgot } from "./components/Forgot";
|
|
12
|
-
import { Alert, PaperProps, Typography } from "@mui/material";
|
|
13
|
-
import { Login } from "./components/Login";
|
|
14
|
-
import { LoginSetPassword } from "./components/LoginSetPassword";
|
|
15
|
-
import ga4 from "react-ga4";
|
|
16
|
-
|
|
17
|
-
interface Props {
|
|
18
|
-
context: UserContextInterface,
|
|
19
|
-
jwt: string,
|
|
20
|
-
auth: string,
|
|
21
|
-
keyName?: string,
|
|
22
|
-
logo?: string,
|
|
23
|
-
appName?: string,
|
|
24
|
-
appUrl?: string,
|
|
25
|
-
returnUrl?: string,
|
|
26
|
-
userRegisteredCallback?: (user: UserInterface) => Promise<void>;
|
|
27
|
-
churchRegisteredCallback?: (church: ChurchInterface) => Promise<void>;
|
|
28
|
-
callbackErrors?: string[];
|
|
29
|
-
showLogo?: boolean;
|
|
30
|
-
showFooter?: boolean;
|
|
31
|
-
loginContainerCssProps?: PaperProps;
|
|
32
|
-
defaultEmail?: string;
|
|
33
|
-
defaultPassword?: string;
|
|
34
|
-
handleRedirect?: (url: string, user?: UserInterface, person?: PersonInterface, userChurch?: LoginUserChurchInterface, userChurches?: LoginUserChurchInterface[]) => void; // Function to handle redirects from parent component
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const LoginPageContent: React.FC<Props> = ({ showLogo = true, loginContainerCssProps, ...props }) => {
|
|
38
|
-
const [welcomeBackName, setWelcomeBackName] = React.useState("");
|
|
39
|
-
const [pendingAutoLogin, setPendingAutoLogin] = React.useState(false);
|
|
40
|
-
const [errors, setErrors] = React.useState([]);
|
|
41
|
-
const [cookies, setCookie] = useCookies(["jwt", "name", "email", "lastChurchId"]);
|
|
42
|
-
const [showForgot, setShowForgot] = React.useState(false);
|
|
43
|
-
const [showRegister, setShowRegister] = React.useState(false);
|
|
44
|
-
const [showSelectModal, setShowSelectModal] = React.useState(false);
|
|
45
|
-
const [loginResponse, setLoginResponse] = React.useState<LoginResponseInterface>(null)
|
|
46
|
-
const [userJwt, setUserJwt] = React.useState("");
|
|
47
|
-
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
48
|
-
|
|
49
|
-
const loginFormRef = React.useRef(null);
|
|
50
|
-
const location = typeof window !== "undefined" && window.location;
|
|
51
|
-
let selectedChurchId = "";
|
|
52
|
-
let registeredChurch: ChurchInterface = null;
|
|
53
|
-
let userJwtBackup = ""; //use state copy for storing between page updates. This copy for instant availability.
|
|
54
|
-
|
|
55
|
-
const cleanAppUrl = () => {
|
|
56
|
-
if (!props.appUrl) return null;
|
|
57
|
-
else {
|
|
58
|
-
const index = props.appUrl.indexOf("/", 9);
|
|
59
|
-
if (index === -1) return props.appUrl;
|
|
60
|
-
else return props.appUrl.substring(0, index);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
React.useEffect(() => {
|
|
65
|
-
if (props.callbackErrors?.length > 0) {
|
|
66
|
-
setErrors(props.callbackErrors)
|
|
67
|
-
}
|
|
68
|
-
}, [props.callbackErrors])
|
|
69
|
-
|
|
70
|
-
const performLogout = () => {
|
|
71
|
-
// Clear all authentication cookies
|
|
72
|
-
setCookie("jwt", "", { path: "/", maxAge: 0 });
|
|
73
|
-
setCookie("name", "", { path: "/", maxAge: 0 });
|
|
74
|
-
setCookie("email", "", { path: "/", maxAge: 0 });
|
|
75
|
-
setCookie("lastChurchId", "", { path: "/", maxAge: 0 });
|
|
76
|
-
// Clear any JWT in the ApiHelper
|
|
77
|
-
ApiHelper.clearPermissions();
|
|
78
|
-
// Clear user context
|
|
79
|
-
props.context.setUser(null);
|
|
80
|
-
props.context.setUserChurches([]);
|
|
81
|
-
props.context.setUserChurch(null);
|
|
82
|
-
props.context.setPerson(null);
|
|
83
|
-
// Show a logout success message
|
|
84
|
-
setErrors(["You have been successfully logged out."]);
|
|
85
|
-
|
|
86
|
-
// Handle redirect after logout
|
|
87
|
-
const search = new URLSearchParams(location?.search);
|
|
88
|
-
const returnUrl = search.get("returnUrl") || props.returnUrl || "/";
|
|
89
|
-
|
|
90
|
-
// Use handleRedirect if available, otherwise use window.location
|
|
91
|
-
if (props.handleRedirect) {
|
|
92
|
-
props.handleRedirect(returnUrl);
|
|
93
|
-
} else if (typeof window !== "undefined") {
|
|
94
|
-
window.location.href = returnUrl;
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const init = () => {
|
|
99
|
-
const search = new URLSearchParams(location?.search);
|
|
100
|
-
const action = search.get("action");
|
|
101
|
-
if (action === "logout") performLogout();
|
|
102
|
-
else if (action === "forgot") setShowForgot(true);
|
|
103
|
-
else if (action === "register") setShowRegister(true);
|
|
104
|
-
else {
|
|
105
|
-
if (!props.auth && props.jwt) {
|
|
106
|
-
setWelcomeBackName(cookies.name);
|
|
107
|
-
login({ jwt: props.jwt });
|
|
108
|
-
setPendingAutoLogin(true);
|
|
109
|
-
} else {
|
|
110
|
-
setPendingAutoLogin(false);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const handleLoginSuccess = async (resp: LoginResponseInterface) => {
|
|
116
|
-
userJwtBackup = resp.user.jwt;
|
|
117
|
-
setUserJwt(userJwtBackup);
|
|
118
|
-
ApiHelper.setDefaultPermissions(resp.user.jwt);
|
|
119
|
-
setLoginResponse(resp)
|
|
120
|
-
resp.userChurches.forEach(uc => { if (!uc.apis) uc.apis = []; });
|
|
121
|
-
UserHelper.userChurches = resp.userChurches;
|
|
122
|
-
|
|
123
|
-
setCookie("name", `${resp.user.firstName} ${resp.user.lastName}`, { path: "/" });
|
|
124
|
-
setCookie("email", resp.user.email, { path: "/" });
|
|
125
|
-
UserHelper.user = resp.user;
|
|
126
|
-
|
|
127
|
-
// JWT church selection is handled by the server response, no client-side decoding needed
|
|
128
|
-
|
|
129
|
-
const search = new URLSearchParams(location?.search);
|
|
130
|
-
const churchIdInParams = search.get("churchId");
|
|
131
|
-
|
|
132
|
-
if (props.keyName) selectChurchByKeyName();
|
|
133
|
-
else if (churchIdInParams) selectChurch(churchIdInParams);
|
|
134
|
-
else if (cookies.lastChurchId && ArrayHelper.getOne(resp.userChurches, "church.id", cookies.lastChurchId)) {
|
|
135
|
-
selectedChurchId = cookies.lastChurchId;
|
|
136
|
-
selectChurchById();
|
|
137
|
-
}
|
|
138
|
-
else setShowSelectModal(true);
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const selectChurchById = async () => {
|
|
142
|
-
await UserHelper.selectChurch(props.context, selectedChurchId, undefined);
|
|
143
|
-
|
|
144
|
-
setCookie("lastChurchId", selectedChurchId, { path: "/" });
|
|
145
|
-
|
|
146
|
-
if (registeredChurch) {
|
|
147
|
-
AnalyticsHelper.logEvent("Church", "Register", UserHelper.currentUserChurch.church.name);
|
|
148
|
-
try {
|
|
149
|
-
if (CommonEnvironmentHelper.GoogleAnalyticsTag && typeof (window) !== "undefined") {
|
|
150
|
-
ga4.gtag("event", "conversion", { send_to: "AW-427967381/Ba2qCLrXgJoYEJWHicwB" });
|
|
151
|
-
}
|
|
152
|
-
} catch { }
|
|
153
|
-
}
|
|
154
|
-
else AnalyticsHelper.logEvent("Church", "Select", UserHelper.currentUserChurch.church.name);
|
|
155
|
-
|
|
156
|
-
if (props.churchRegisteredCallback && registeredChurch) {
|
|
157
|
-
await props.churchRegisteredCallback(registeredChurch)
|
|
158
|
-
registeredChurch = null;
|
|
159
|
-
login({ jwt: userJwt || userJwtBackup });
|
|
160
|
-
} else await continueLoginProcess();
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const selectChurchByKeyName = async () => {
|
|
164
|
-
if (!ArrayHelper.getOne(UserHelper.userChurches, "church.subDomain", props.keyName)) {
|
|
165
|
-
const userChurch: LoginUserChurchInterface = await ApiHelper.post("/churches/select", { subDomain: props.keyName }, "MembershipApi");
|
|
166
|
-
UserHelper.setupApiHelper(userChurch);
|
|
167
|
-
setCookie("lastChurchId", userChurch.church.id, { path: "/" });
|
|
168
|
-
//create/claim the person record and relogin
|
|
169
|
-
await ApiHelper.get("/people/claim/" + userChurch.church.id, "MembershipApi");
|
|
170
|
-
login({ jwt: userJwt || userJwtBackup });
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
await UserHelper.selectChurch(props.context, undefined, props.keyName);
|
|
174
|
-
const selectedChurch = ArrayHelper.getOne(UserHelper.userChurches, "church.subDomain", props.keyName);
|
|
175
|
-
if (selectedChurch) setCookie("lastChurchId", selectedChurch.church.id, { path: "/" });
|
|
176
|
-
await continueLoginProcess()
|
|
177
|
-
return;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
async function continueLoginProcess() {
|
|
181
|
-
if (UserHelper.currentUserChurch) {
|
|
182
|
-
UserHelper.currentUserChurch.apis.forEach(api => {
|
|
183
|
-
if (api.keyName === "MembershipApi") setCookie("jwt", api.jwt, { path: "/" });
|
|
184
|
-
})
|
|
185
|
-
try {
|
|
186
|
-
if (UserHelper.currentUserChurch.church.id) ApiHelper.patch(`/userChurch/${UserHelper.user.id}`, { churchId: UserHelper.currentUserChurch.church.id, appName: props.appName, lastAccessed: new Date() }, "MembershipApi")
|
|
187
|
-
} catch (e) {
|
|
188
|
-
console.log("Could not update user church accessed date")
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
props.context.setUser(UserHelper.user);
|
|
193
|
-
props.context.setUserChurches(UserHelper.userChurches)
|
|
194
|
-
props.context.setUserChurch(UserHelper.currentUserChurch)
|
|
195
|
-
|
|
196
|
-
// Get or claim person before proceeding
|
|
197
|
-
let person;
|
|
198
|
-
try {
|
|
199
|
-
person = await ApiHelper.get(`/people/${UserHelper.currentUserChurch.person?.id}`, "MembershipApi");
|
|
200
|
-
if (person) props.context.setPerson(person);
|
|
201
|
-
} catch {
|
|
202
|
-
console.log("claiming person");
|
|
203
|
-
person = await ApiHelper.get("/people/claim/" + UserHelper.currentUserChurch.church.id, "MembershipApi");
|
|
204
|
-
props.context.setPerson(person);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Handle redirect with actual data
|
|
208
|
-
const search = new URLSearchParams(location?.search);
|
|
209
|
-
const returnUrl = search.get("returnUrl") || props.returnUrl || "/";
|
|
210
|
-
if (returnUrl && typeof window !== "undefined") {
|
|
211
|
-
// Use handleRedirect function if available, otherwise fallback to window.location
|
|
212
|
-
if (props.handleRedirect) {
|
|
213
|
-
console.log('Login handleRedirect - Passing userChurches:', UserHelper.userChurches);
|
|
214
|
-
props.handleRedirect(returnUrl, UserHelper.user, person, UserHelper.currentUserChurch, UserHelper.userChurches);
|
|
215
|
-
} else {
|
|
216
|
-
window.location.href = returnUrl;
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
async function selectChurch(churchId: string) {
|
|
222
|
-
try {
|
|
223
|
-
setErrors([])
|
|
224
|
-
selectedChurchId = churchId;
|
|
225
|
-
setCookie("lastChurchId", churchId, { path: "/" });
|
|
226
|
-
if (!ArrayHelper.getOne(UserHelper.userChurches, "church.id", churchId)) {
|
|
227
|
-
const userChurch: LoginUserChurchInterface = await ApiHelper.post("/churches/select", { churchId: churchId }, "MembershipApi");
|
|
228
|
-
UserHelper.setupApiHelper(userChurch);
|
|
229
|
-
|
|
230
|
-
//create/claim the person record and relogin
|
|
231
|
-
await ApiHelper.get("/people/claim/" + churchId, "MembershipApi");
|
|
232
|
-
login({ jwt: userJwt || userJwtBackup });
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
UserHelper.selectChurch(props.context, churchId, null).then(() => { continueLoginProcess() });
|
|
236
|
-
} catch (err) {
|
|
237
|
-
console.log("Error in selecting church: ", err)
|
|
238
|
-
setErrors([Locale.label("login.validate.selectingChurch")])
|
|
239
|
-
loginFormRef?.current?.setSubmitting(false);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const handleLoginErrors = (errors: string[]) => {
|
|
245
|
-
setWelcomeBackName("");
|
|
246
|
-
console.log(errors);
|
|
247
|
-
setErrors([Locale.label("login.validate.invalid")]);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
const login = async (data: any) => {
|
|
251
|
-
setErrors([])
|
|
252
|
-
setIsSubmitting(true);
|
|
253
|
-
try {
|
|
254
|
-
console.log("Logging in with data: ", data, "/users/login", ApiHelper.getConfig("MembershipApi"));
|
|
255
|
-
const resp: LoginResponseInterface = await ApiHelper.postAnonymous("/users/login", data, "MembershipApi");
|
|
256
|
-
setIsSubmitting(false);
|
|
257
|
-
handleLoginSuccess(resp);
|
|
258
|
-
} catch (e: any) {
|
|
259
|
-
setPendingAutoLogin(false);
|
|
260
|
-
setWelcomeBackName("");
|
|
261
|
-
if (!data.jwt) handleLoginErrors([e.toString()]);
|
|
262
|
-
setIsSubmitting(false);
|
|
263
|
-
}
|
|
264
|
-
};;
|
|
265
|
-
|
|
266
|
-
const getWelcomeBack = () => { if (welcomeBackName !== "") return (<><Alert severity="info"><div dangerouslySetInnerHTML={{ __html: Locale.label("login.welcomeName")?.replace("{}", welcomeBackName) }} /></Alert><Loading /></>); }
|
|
267
|
-
const getCheckEmail = () => { if (new URLSearchParams(location?.search).get("checkEmail") === "1") return <Alert severity="info">{Locale.label("login.registerThankYou")}</Alert> }
|
|
268
|
-
const handleRegisterCallback = () => { setShowForgot(false); setShowRegister(true); }
|
|
269
|
-
const handleLoginCallback = () => { setShowForgot(false); setShowRegister(false); }
|
|
270
|
-
const handleChurchRegistered = (church: ChurchInterface) => { registeredChurch = church; setShowRegister(false); console.log("Updated VERSION********") }
|
|
271
|
-
|
|
272
|
-
const getInputBox = () => {
|
|
273
|
-
if (showRegister) return (
|
|
274
|
-
|
|
275
|
-
<Register updateErrors={setErrors} appName={props.appName} appUrl={cleanAppUrl()} loginCallback={handleLoginCallback} userRegisteredCallback={props.userRegisteredCallback} />
|
|
276
|
-
|
|
277
|
-
);
|
|
278
|
-
else if (showForgot) return (<Forgot registerCallback={handleRegisterCallback} loginCallback={handleLoginCallback} />);
|
|
279
|
-
else if (props.auth) return (<LoginSetPassword setErrors={setErrors} setShowForgot={setShowForgot} isSubmitting={isSubmitting} auth={props.auth} login={login} appName={props.appName} appUrl={cleanAppUrl()} />)
|
|
280
|
-
else return <Login setShowRegister={setShowRegister} setShowForgot={setShowForgot} setErrors={setErrors} isSubmitting={isSubmitting} login={login} mainContainerCssProps={loginContainerCssProps} defaultEmail={props.defaultEmail} defaultPassword={props.defaultPassword} showFooter={props.showFooter} />;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
React.useEffect(init, []); //eslint-disable-line
|
|
284
|
-
|
|
285
|
-
return (
|
|
286
|
-
<>
|
|
287
|
-
<ErrorMessages errors={errors} />
|
|
288
|
-
{getWelcomeBack()}
|
|
289
|
-
{getCheckEmail()}
|
|
290
|
-
{!pendingAutoLogin && getInputBox()}
|
|
291
|
-
<SelectChurchModal show={showSelectModal} userChurches={loginResponse?.userChurches} selectChurch={selectChurch} registeredChurchCallback={handleChurchRegistered} errors={errors} appName={props.appName} handleRedirect={props.handleRedirect} />
|
|
292
|
-
<FloatingSupport appName={props.appName} />
|
|
293
|
-
</>
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
export const LoginPage: React.FC<Props> = (props) => {
|
|
299
|
-
// Always wrap with CookiesProvider to ensure context is available
|
|
300
|
-
return (
|
|
301
|
-
<CookiesProvider defaultSetOptions={{ path: '/' }}>
|
|
302
|
-
<LoginPageContent {...props} />
|
|
303
|
-
</CookiesProvider>
|
|
304
|
-
);
|
|
305
|
-
};
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import * as React from "react";
|
|
4
|
+
import { ErrorMessages, FloatingSupport, Loading } from "@churchapps/apphelper";
|
|
5
|
+
import { LoginResponseInterface, UserContextInterface, ChurchInterface, UserInterface, LoginUserChurchInterface, PersonInterface } from "@churchapps/helpers";
|
|
6
|
+
import { ApiHelper, ArrayHelper, UserHelper, CommonEnvironmentHelper } from "@churchapps/helpers";
|
|
7
|
+
import { AnalyticsHelper, Locale } from "./helpers";
|
|
8
|
+
import { useCookies, CookiesProvider } from "react-cookie"
|
|
9
|
+
import { Register } from "./components/Register"
|
|
10
|
+
import { SelectChurchModal } from "./components/SelectChurchModal"
|
|
11
|
+
import { Forgot } from "./components/Forgot";
|
|
12
|
+
import { Alert, PaperProps, Typography } from "@mui/material";
|
|
13
|
+
import { Login } from "./components/Login";
|
|
14
|
+
import { LoginSetPassword } from "./components/LoginSetPassword";
|
|
15
|
+
import ga4 from "react-ga4";
|
|
16
|
+
|
|
17
|
+
interface Props {
|
|
18
|
+
context: UserContextInterface,
|
|
19
|
+
jwt: string,
|
|
20
|
+
auth: string,
|
|
21
|
+
keyName?: string,
|
|
22
|
+
logo?: string,
|
|
23
|
+
appName?: string,
|
|
24
|
+
appUrl?: string,
|
|
25
|
+
returnUrl?: string,
|
|
26
|
+
userRegisteredCallback?: (user: UserInterface) => Promise<void>;
|
|
27
|
+
churchRegisteredCallback?: (church: ChurchInterface) => Promise<void>;
|
|
28
|
+
callbackErrors?: string[];
|
|
29
|
+
showLogo?: boolean;
|
|
30
|
+
showFooter?: boolean;
|
|
31
|
+
loginContainerCssProps?: PaperProps;
|
|
32
|
+
defaultEmail?: string;
|
|
33
|
+
defaultPassword?: string;
|
|
34
|
+
handleRedirect?: (url: string, user?: UserInterface, person?: PersonInterface, userChurch?: LoginUserChurchInterface, userChurches?: LoginUserChurchInterface[]) => void; // Function to handle redirects from parent component
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const LoginPageContent: React.FC<Props> = ({ showLogo = true, loginContainerCssProps, ...props }) => {
|
|
38
|
+
const [welcomeBackName, setWelcomeBackName] = React.useState("");
|
|
39
|
+
const [pendingAutoLogin, setPendingAutoLogin] = React.useState(false);
|
|
40
|
+
const [errors, setErrors] = React.useState([]);
|
|
41
|
+
const [cookies, setCookie] = useCookies(["jwt", "name", "email", "lastChurchId"]);
|
|
42
|
+
const [showForgot, setShowForgot] = React.useState(false);
|
|
43
|
+
const [showRegister, setShowRegister] = React.useState(false);
|
|
44
|
+
const [showSelectModal, setShowSelectModal] = React.useState(false);
|
|
45
|
+
const [loginResponse, setLoginResponse] = React.useState<LoginResponseInterface>(null)
|
|
46
|
+
const [userJwt, setUserJwt] = React.useState("");
|
|
47
|
+
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
48
|
+
|
|
49
|
+
const loginFormRef = React.useRef(null);
|
|
50
|
+
const location = typeof window !== "undefined" && window.location;
|
|
51
|
+
let selectedChurchId = "";
|
|
52
|
+
let registeredChurch: ChurchInterface = null;
|
|
53
|
+
let userJwtBackup = ""; //use state copy for storing between page updates. This copy for instant availability.
|
|
54
|
+
|
|
55
|
+
const cleanAppUrl = () => {
|
|
56
|
+
if (!props.appUrl) return null;
|
|
57
|
+
else {
|
|
58
|
+
const index = props.appUrl.indexOf("/", 9);
|
|
59
|
+
if (index === -1) return props.appUrl;
|
|
60
|
+
else return props.appUrl.substring(0, index);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
React.useEffect(() => {
|
|
65
|
+
if (props.callbackErrors?.length > 0) {
|
|
66
|
+
setErrors(props.callbackErrors)
|
|
67
|
+
}
|
|
68
|
+
}, [props.callbackErrors])
|
|
69
|
+
|
|
70
|
+
const performLogout = () => {
|
|
71
|
+
// Clear all authentication cookies
|
|
72
|
+
setCookie("jwt", "", { path: "/", maxAge: 0 });
|
|
73
|
+
setCookie("name", "", { path: "/", maxAge: 0 });
|
|
74
|
+
setCookie("email", "", { path: "/", maxAge: 0 });
|
|
75
|
+
setCookie("lastChurchId", "", { path: "/", maxAge: 0 });
|
|
76
|
+
// Clear any JWT in the ApiHelper
|
|
77
|
+
ApiHelper.clearPermissions();
|
|
78
|
+
// Clear user context
|
|
79
|
+
props.context.setUser(null);
|
|
80
|
+
props.context.setUserChurches([]);
|
|
81
|
+
props.context.setUserChurch(null);
|
|
82
|
+
props.context.setPerson(null);
|
|
83
|
+
// Show a logout success message
|
|
84
|
+
setErrors(["You have been successfully logged out."]);
|
|
85
|
+
|
|
86
|
+
// Handle redirect after logout
|
|
87
|
+
const search = new URLSearchParams(location?.search);
|
|
88
|
+
const returnUrl = search.get("returnUrl") || props.returnUrl || "/";
|
|
89
|
+
|
|
90
|
+
// Use handleRedirect if available, otherwise use window.location
|
|
91
|
+
if (props.handleRedirect) {
|
|
92
|
+
props.handleRedirect(returnUrl);
|
|
93
|
+
} else if (typeof window !== "undefined") {
|
|
94
|
+
window.location.href = returnUrl;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const init = () => {
|
|
99
|
+
const search = new URLSearchParams(location?.search);
|
|
100
|
+
const action = search.get("action");
|
|
101
|
+
if (action === "logout") performLogout();
|
|
102
|
+
else if (action === "forgot") setShowForgot(true);
|
|
103
|
+
else if (action === "register") setShowRegister(true);
|
|
104
|
+
else {
|
|
105
|
+
if (!props.auth && props.jwt) {
|
|
106
|
+
setWelcomeBackName(cookies.name);
|
|
107
|
+
login({ jwt: props.jwt });
|
|
108
|
+
setPendingAutoLogin(true);
|
|
109
|
+
} else {
|
|
110
|
+
setPendingAutoLogin(false);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const handleLoginSuccess = async (resp: LoginResponseInterface) => {
|
|
116
|
+
userJwtBackup = resp.user.jwt;
|
|
117
|
+
setUserJwt(userJwtBackup);
|
|
118
|
+
ApiHelper.setDefaultPermissions(resp.user.jwt);
|
|
119
|
+
setLoginResponse(resp)
|
|
120
|
+
resp.userChurches.forEach(uc => { if (!uc.apis) uc.apis = []; });
|
|
121
|
+
UserHelper.userChurches = resp.userChurches;
|
|
122
|
+
|
|
123
|
+
setCookie("name", `${resp.user.firstName} ${resp.user.lastName}`, { path: "/" });
|
|
124
|
+
setCookie("email", resp.user.email, { path: "/" });
|
|
125
|
+
UserHelper.user = resp.user;
|
|
126
|
+
|
|
127
|
+
// JWT church selection is handled by the server response, no client-side decoding needed
|
|
128
|
+
|
|
129
|
+
const search = new URLSearchParams(location?.search);
|
|
130
|
+
const churchIdInParams = search.get("churchId");
|
|
131
|
+
|
|
132
|
+
if (props.keyName) selectChurchByKeyName();
|
|
133
|
+
else if (churchIdInParams) selectChurch(churchIdInParams);
|
|
134
|
+
else if (cookies.lastChurchId && ArrayHelper.getOne(resp.userChurches, "church.id", cookies.lastChurchId)) {
|
|
135
|
+
selectedChurchId = cookies.lastChurchId;
|
|
136
|
+
selectChurchById();
|
|
137
|
+
}
|
|
138
|
+
else setShowSelectModal(true);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const selectChurchById = async () => {
|
|
142
|
+
await UserHelper.selectChurch(props.context, selectedChurchId, undefined);
|
|
143
|
+
|
|
144
|
+
setCookie("lastChurchId", selectedChurchId, { path: "/" });
|
|
145
|
+
|
|
146
|
+
if (registeredChurch) {
|
|
147
|
+
AnalyticsHelper.logEvent("Church", "Register", UserHelper.currentUserChurch.church.name);
|
|
148
|
+
try {
|
|
149
|
+
if (CommonEnvironmentHelper.GoogleAnalyticsTag && typeof (window) !== "undefined") {
|
|
150
|
+
ga4.gtag("event", "conversion", { send_to: "AW-427967381/Ba2qCLrXgJoYEJWHicwB" });
|
|
151
|
+
}
|
|
152
|
+
} catch { }
|
|
153
|
+
}
|
|
154
|
+
else AnalyticsHelper.logEvent("Church", "Select", UserHelper.currentUserChurch.church.name);
|
|
155
|
+
|
|
156
|
+
if (props.churchRegisteredCallback && registeredChurch) {
|
|
157
|
+
await props.churchRegisteredCallback(registeredChurch)
|
|
158
|
+
registeredChurch = null;
|
|
159
|
+
login({ jwt: userJwt || userJwtBackup });
|
|
160
|
+
} else await continueLoginProcess();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const selectChurchByKeyName = async () => {
|
|
164
|
+
if (!ArrayHelper.getOne(UserHelper.userChurches, "church.subDomain", props.keyName)) {
|
|
165
|
+
const userChurch: LoginUserChurchInterface = await ApiHelper.post("/churches/select", { subDomain: props.keyName }, "MembershipApi");
|
|
166
|
+
UserHelper.setupApiHelper(userChurch);
|
|
167
|
+
setCookie("lastChurchId", userChurch.church.id, { path: "/" });
|
|
168
|
+
//create/claim the person record and relogin
|
|
169
|
+
await ApiHelper.get("/people/claim/" + userChurch.church.id, "MembershipApi");
|
|
170
|
+
login({ jwt: userJwt || userJwtBackup });
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
await UserHelper.selectChurch(props.context, undefined, props.keyName);
|
|
174
|
+
const selectedChurch = ArrayHelper.getOne(UserHelper.userChurches, "church.subDomain", props.keyName);
|
|
175
|
+
if (selectedChurch) setCookie("lastChurchId", selectedChurch.church.id, { path: "/" });
|
|
176
|
+
await continueLoginProcess()
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function continueLoginProcess() {
|
|
181
|
+
if (UserHelper.currentUserChurch) {
|
|
182
|
+
UserHelper.currentUserChurch.apis.forEach(api => {
|
|
183
|
+
if (api.keyName === "MembershipApi") setCookie("jwt", api.jwt, { path: "/" });
|
|
184
|
+
})
|
|
185
|
+
try {
|
|
186
|
+
if (UserHelper.currentUserChurch.church.id) ApiHelper.patch(`/userChurch/${UserHelper.user.id}`, { churchId: UserHelper.currentUserChurch.church.id, appName: props.appName, lastAccessed: new Date() }, "MembershipApi")
|
|
187
|
+
} catch (e) {
|
|
188
|
+
console.log("Could not update user church accessed date")
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
props.context.setUser(UserHelper.user);
|
|
193
|
+
props.context.setUserChurches(UserHelper.userChurches)
|
|
194
|
+
props.context.setUserChurch(UserHelper.currentUserChurch)
|
|
195
|
+
|
|
196
|
+
// Get or claim person before proceeding
|
|
197
|
+
let person;
|
|
198
|
+
try {
|
|
199
|
+
person = await ApiHelper.get(`/people/${UserHelper.currentUserChurch.person?.id}`, "MembershipApi");
|
|
200
|
+
if (person) props.context.setPerson(person);
|
|
201
|
+
} catch {
|
|
202
|
+
console.log("claiming person");
|
|
203
|
+
person = await ApiHelper.get("/people/claim/" + UserHelper.currentUserChurch.church.id, "MembershipApi");
|
|
204
|
+
props.context.setPerson(person);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Handle redirect with actual data
|
|
208
|
+
const search = new URLSearchParams(location?.search);
|
|
209
|
+
const returnUrl = search.get("returnUrl") || props.returnUrl || "/";
|
|
210
|
+
if (returnUrl && typeof window !== "undefined") {
|
|
211
|
+
// Use handleRedirect function if available, otherwise fallback to window.location
|
|
212
|
+
if (props.handleRedirect) {
|
|
213
|
+
console.log('Login handleRedirect - Passing userChurches:', UserHelper.userChurches);
|
|
214
|
+
props.handleRedirect(returnUrl, UserHelper.user, person, UserHelper.currentUserChurch, UserHelper.userChurches);
|
|
215
|
+
} else {
|
|
216
|
+
window.location.href = returnUrl;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function selectChurch(churchId: string) {
|
|
222
|
+
try {
|
|
223
|
+
setErrors([])
|
|
224
|
+
selectedChurchId = churchId;
|
|
225
|
+
setCookie("lastChurchId", churchId, { path: "/" });
|
|
226
|
+
if (!ArrayHelper.getOne(UserHelper.userChurches, "church.id", churchId)) {
|
|
227
|
+
const userChurch: LoginUserChurchInterface = await ApiHelper.post("/churches/select", { churchId: churchId }, "MembershipApi");
|
|
228
|
+
UserHelper.setupApiHelper(userChurch);
|
|
229
|
+
|
|
230
|
+
//create/claim the person record and relogin
|
|
231
|
+
await ApiHelper.get("/people/claim/" + churchId, "MembershipApi");
|
|
232
|
+
login({ jwt: userJwt || userJwtBackup });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
UserHelper.selectChurch(props.context, churchId, null).then(() => { continueLoginProcess() });
|
|
236
|
+
} catch (err) {
|
|
237
|
+
console.log("Error in selecting church: ", err)
|
|
238
|
+
setErrors([Locale.label("login.validate.selectingChurch")])
|
|
239
|
+
loginFormRef?.current?.setSubmitting(false);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const handleLoginErrors = (errors: string[]) => {
|
|
245
|
+
setWelcomeBackName("");
|
|
246
|
+
console.log(errors);
|
|
247
|
+
setErrors([Locale.label("login.validate.invalid")]);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const login = async (data: any) => {
|
|
251
|
+
setErrors([])
|
|
252
|
+
setIsSubmitting(true);
|
|
253
|
+
try {
|
|
254
|
+
console.log("Logging in with data: ", data, "/users/login", ApiHelper.getConfig("MembershipApi"));
|
|
255
|
+
const resp: LoginResponseInterface = await ApiHelper.postAnonymous("/users/login", data, "MembershipApi");
|
|
256
|
+
setIsSubmitting(false);
|
|
257
|
+
handleLoginSuccess(resp);
|
|
258
|
+
} catch (e: any) {
|
|
259
|
+
setPendingAutoLogin(false);
|
|
260
|
+
setWelcomeBackName("");
|
|
261
|
+
if (!data.jwt) handleLoginErrors([e.toString()]);
|
|
262
|
+
setIsSubmitting(false);
|
|
263
|
+
}
|
|
264
|
+
};;
|
|
265
|
+
|
|
266
|
+
const getWelcomeBack = () => { if (welcomeBackName !== "") return (<><Alert severity="info"><div dangerouslySetInnerHTML={{ __html: Locale.label("login.welcomeName")?.replace("{}", welcomeBackName) }} /></Alert><Loading /></>); }
|
|
267
|
+
const getCheckEmail = () => { if (new URLSearchParams(location?.search).get("checkEmail") === "1") return <Alert severity="info">{Locale.label("login.registerThankYou")}</Alert> }
|
|
268
|
+
const handleRegisterCallback = () => { setShowForgot(false); setShowRegister(true); }
|
|
269
|
+
const handleLoginCallback = () => { setShowForgot(false); setShowRegister(false); }
|
|
270
|
+
const handleChurchRegistered = (church: ChurchInterface) => { registeredChurch = church; setShowRegister(false); console.log("Updated VERSION********") }
|
|
271
|
+
|
|
272
|
+
const getInputBox = () => {
|
|
273
|
+
if (showRegister) return (
|
|
274
|
+
|
|
275
|
+
<Register updateErrors={setErrors} appName={props.appName} appUrl={cleanAppUrl()} loginCallback={handleLoginCallback} userRegisteredCallback={props.userRegisteredCallback} />
|
|
276
|
+
|
|
277
|
+
);
|
|
278
|
+
else if (showForgot) return (<Forgot registerCallback={handleRegisterCallback} loginCallback={handleLoginCallback} />);
|
|
279
|
+
else if (props.auth) return (<LoginSetPassword setErrors={setErrors} setShowForgot={setShowForgot} isSubmitting={isSubmitting} auth={props.auth} login={login} appName={props.appName} appUrl={cleanAppUrl()} />)
|
|
280
|
+
else return <Login setShowRegister={setShowRegister} setShowForgot={setShowForgot} setErrors={setErrors} isSubmitting={isSubmitting} login={login} mainContainerCssProps={loginContainerCssProps} defaultEmail={props.defaultEmail} defaultPassword={props.defaultPassword} showFooter={props.showFooter} />;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
React.useEffect(init, []); //eslint-disable-line
|
|
284
|
+
|
|
285
|
+
return (
|
|
286
|
+
<>
|
|
287
|
+
<ErrorMessages errors={errors} />
|
|
288
|
+
{getWelcomeBack()}
|
|
289
|
+
{getCheckEmail()}
|
|
290
|
+
{!pendingAutoLogin && getInputBox()}
|
|
291
|
+
<SelectChurchModal show={showSelectModal} userChurches={loginResponse?.userChurches} selectChurch={selectChurch} registeredChurchCallback={handleChurchRegistered} errors={errors} appName={props.appName} handleRedirect={props.handleRedirect} />
|
|
292
|
+
<FloatingSupport appName={props.appName} />
|
|
293
|
+
</>
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
export const LoginPage: React.FC<Props> = (props) => {
|
|
299
|
+
// Always wrap with CookiesProvider to ensure context is available
|
|
300
|
+
return (
|
|
301
|
+
<CookiesProvider defaultSetOptions={{ path: '/' }}>
|
|
302
|
+
<LoginPageContent {...props} />
|
|
303
|
+
</CookiesProvider>
|
|
304
|
+
);
|
|
305
|
+
};
|