@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.
@@ -1,43 +1,43 @@
1
- "use client";
2
-
3
- import React from "react";
4
- import { useCookies, CookiesProvider } from "react-cookie"
5
- import { ApiHelper, UserContextInterface } from "@churchapps/helpers";
6
-
7
- interface Props { context?: UserContextInterface, handleRedirect?: (url: string) => void }
8
-
9
- const LogoutPageContent: React.FC<Props> = (props) => {
10
- const [, , removeCookie] = useCookies(["jwt", "email", "name", "lastChurchId"]);
11
-
12
- removeCookie("jwt");
13
- removeCookie("email");
14
- removeCookie("name");
15
- removeCookie("lastChurchId");
16
-
17
- ApiHelper.clearPermissions();
18
- props.context?.setUser(null);
19
- props.context?.setPerson(null);
20
- props.context?.setUserChurches(null);
21
- props.context?.setUserChurch(null);
22
-
23
- setTimeout(() => {
24
- // a must check for Nextjs
25
- if (typeof window !== "undefined") {
26
- // Use handleRedirect function if available, otherwise fallback to window.location
27
- if (props.handleRedirect) {
28
- props.handleRedirect("/");
29
- } else {
30
- window.location.href = "/";
31
- }
32
- }
33
- }, 300);
34
- return null;
35
- }
36
-
37
- export const LogoutPage: React.FC<Props> = (props) => {
38
- return (
39
- <CookiesProvider defaultSetOptions={{ path: '/' }}>
40
- <LogoutPageContent {...props} />
41
- </CookiesProvider>
42
- );
43
- }
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { useCookies, CookiesProvider } from "react-cookie"
5
+ import { ApiHelper, UserContextInterface } from "@churchapps/helpers";
6
+
7
+ interface Props { context?: UserContextInterface, handleRedirect?: (url: string) => void }
8
+
9
+ const LogoutPageContent: React.FC<Props> = (props) => {
10
+ const [, , removeCookie] = useCookies(["jwt", "email", "name", "lastChurchId"]);
11
+
12
+ removeCookie("jwt");
13
+ removeCookie("email");
14
+ removeCookie("name");
15
+ removeCookie("lastChurchId");
16
+
17
+ ApiHelper.clearPermissions();
18
+ props.context?.setUser(null);
19
+ props.context?.setPerson(null);
20
+ props.context?.setUserChurches(null);
21
+ props.context?.setUserChurch(null);
22
+
23
+ setTimeout(() => {
24
+ // a must check for Nextjs
25
+ if (typeof window !== "undefined") {
26
+ // Use handleRedirect function if available, otherwise fallback to window.location
27
+ if (props.handleRedirect) {
28
+ props.handleRedirect("/");
29
+ } else {
30
+ window.location.href = "/";
31
+ }
32
+ }
33
+ }, 300);
34
+ return null;
35
+ }
36
+
37
+ export const LogoutPage: React.FC<Props> = (props) => {
38
+ return (
39
+ <CookiesProvider defaultSetOptions={{ path: '/' }}>
40
+ <LogoutPageContent {...props} />
41
+ </CookiesProvider>
42
+ );
43
+ }
@@ -1,247 +1,247 @@
1
- "use client";
2
-
3
- import React, { FormEventHandler } from "react";
4
- import { ApiHelper } from "@churchapps/helpers";
5
- import { Locale } from "../helpers";
6
- import { ResetPasswordRequestInterface, ResetPasswordResponseInterface } from "@churchapps/helpers";
7
- import { TextField, Typography, Card, CardContent, Button } from "@mui/material";
8
-
9
- interface Props {
10
- registerCallback: () => void,
11
- loginCallback: () => void
12
- }
13
-
14
- export const Forgot: React.FC<Props> = props => {
15
- const [errors, setErrors] = React.useState([]);
16
- const [successMessage, setSuccessMessage] = React.useState<React.ReactElement>(null);
17
- const [email, setEmail] = React.useState("");
18
- const [isSubmitting, setIsSubmitting] = React.useState(false);
19
-
20
- const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
21
- setEmail(e.target.value);
22
- }
23
-
24
- const validateEmail = (email: string) => (/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(email))
25
-
26
- const validate = () => {
27
- const result = [];
28
- if (!email) result.push(Locale.label("login.validate.email"));
29
- else if (!validateEmail(email)) result.push(Locale.label("login.validate.email"));
30
- setErrors(result);
31
- return result.length === 0;
32
- }
33
-
34
- const reset: FormEventHandler = (e) => {
35
- e.preventDefault();
36
- if (validate()) {
37
- setIsSubmitting(true);
38
- let req: ResetPasswordRequestInterface = { userEmail: email };
39
- ApiHelper.postAnonymous("/users/forgot", req, "MembershipApi").then((resp: ResetPasswordResponseInterface) => {
40
- if (resp.emailed) {
41
- setErrors([]);
42
- setSuccessMessage(
43
- <Typography textAlign="center" marginTop="35px">
44
- {Locale.label("login.resetSent")} <br /><br />
45
- <button
46
- style={{
47
- background: 'none',
48
- border: 'none',
49
- color: '#3b82f6',
50
- fontSize: '14px',
51
- cursor: 'pointer',
52
- textDecoration: 'none'
53
- }}
54
- onMouseOver={(e) => e.currentTarget.style.textDecoration = 'underline'}
55
- onMouseOut={(e) => e.currentTarget.style.textDecoration = 'none'}
56
- onClick={(e) => { e.preventDefault(); props.loginCallback(); }}
57
- >
58
- {Locale.label("login.goLogin")}
59
- </button>
60
- </Typography>
61
- );
62
- setEmail("");
63
- } else {
64
- setErrors(["We could not find an account with this email address"]);
65
- setSuccessMessage(<></>);
66
- }
67
- }).finally(() => { setIsSubmitting(false); });
68
- }
69
- }
70
-
71
- return (
72
- <div style={{ minHeight: '100vh', backgroundColor: 'white', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '16px' }}>
73
- <Card sx={{
74
- width: '100%',
75
- maxWidth: { xs: '400px', sm: '500px' },
76
- backgroundColor: 'white',
77
- border: '1px solid #e5e7eb',
78
- boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)'
79
- }}>
80
- <CardContent sx={{ textAlign: 'center', padding: '32px' }}>
81
- <div style={{ marginBottom: '32px' }}>
82
- <img
83
- src="/images/logo-login.png"
84
- alt="Church Logo"
85
- style={{
86
- maxWidth: '100%',
87
- width: 'auto',
88
- height: 'auto',
89
- maxHeight: '80px',
90
- marginBottom: '16px',
91
- objectFit: 'contain'
92
- }}
93
- />
94
- </div>
95
- <Typography
96
- component="h1"
97
- sx={{
98
- fontSize: '24px',
99
- fontWeight: 'bold',
100
- color: '#111827',
101
- marginBottom: '8px'
102
- }}
103
- >
104
- {Locale.label("login.resetPassword")}
105
- </Typography>
106
- <Typography
107
- sx={{
108
- color: '#6b7280',
109
- marginBottom: '32px'
110
- }}
111
- >
112
- Enter your email to receive password reset instructions
113
- </Typography>
114
-
115
- <form onSubmit={reset} style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
116
- {errors.length > 0 && (
117
- <div style={{
118
- backgroundColor: '#fef2f2',
119
- border: '1px solid #fecaca',
120
- borderRadius: '6px',
121
- padding: '12px',
122
- textAlign: 'left'
123
- }}>
124
- {errors.map((error, index) => (
125
- <div key={index} style={{ color: '#dc2626', fontSize: '14px' }}>{error}</div>
126
- ))}
127
- </div>
128
- )}
129
-
130
- {successMessage ? (
131
- <div style={{ textAlign: 'center', display: 'flex', flexDirection: 'column', gap: '16px' }}>
132
- {successMessage}
133
- </div>
134
- ) : (
135
- <>
136
- <Typography variant="body2" sx={{ color: '#6b7280', fontSize: '14px', textAlign: 'left' }}>
137
- {Locale.label("login.resetInstructions")}
138
- </Typography>
139
-
140
- <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
141
- <label htmlFor="forgot-email" style={{ fontSize: '14px', fontWeight: 500, color: '#374151', textAlign: 'left' }}>
142
- {Locale.label("login.email")}
143
- </label>
144
- <TextField
145
- id="forgot-email"
146
- name="forgot-email"
147
- type="email"
148
- placeholder={Locale.label("login.email")}
149
- value={email}
150
- onChange={handleChange}
151
- autoFocus
152
- required
153
- autoComplete="email"
154
- variant="outlined"
155
- fullWidth
156
- onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => e.key === "Enter" && reset}
157
- sx={{
158
- '& .MuiOutlinedInput-root': {
159
- backgroundColor: 'white',
160
- '& fieldset': {
161
- borderColor: '#d1d5db'
162
- },
163
- '&:hover fieldset': {
164
- borderColor: '#d1d5db'
165
- },
166
- '&.Mui-focused fieldset': {
167
- borderColor: '#3b82f6'
168
- },
169
- '& input': {
170
- color: '#111827',
171
- fontSize: '16px'
172
- }
173
- },
174
- '& .MuiInputLabel-root': {
175
- display: 'none'
176
- }
177
- }}
178
- />
179
- </div>
180
-
181
- <Button
182
- type="submit"
183
- variant="contained"
184
- fullWidth
185
- disabled={isSubmitting}
186
- sx={{
187
- backgroundColor: 'hsl(218, 85%, 55%)',
188
- color: 'white',
189
- padding: '12px',
190
- textTransform: 'none',
191
- fontSize: '16px',
192
- fontWeight: 500,
193
- borderRadius: '6px',
194
- '&:hover': {
195
- backgroundColor: 'hsl(218, 85%, 50%)'
196
- },
197
- '&:disabled': {
198
- backgroundColor: '#9ca3af'
199
- }
200
- }}
201
- >
202
- {isSubmitting ? "Sending..." : Locale.label("login.reset")}
203
- </Button>
204
-
205
- <div style={{ textAlign: 'center', display: 'flex', justifyContent: 'center', alignItems: 'center', gap: '8px' }}>
206
- <button
207
- type="button"
208
- onClick={(e) => { e.preventDefault(); props.registerCallback(); }}
209
- style={{
210
- background: 'none',
211
- border: 'none',
212
- color: '#3b82f6',
213
- fontSize: '14px',
214
- cursor: 'pointer',
215
- textDecoration: 'none'
216
- }}
217
- onMouseOver={(e) => e.currentTarget.style.textDecoration = 'underline'}
218
- onMouseOut={(e) => e.currentTarget.style.textDecoration = 'none'}
219
- >
220
- {Locale.label("login.register")}
221
- </button>
222
- <span style={{ fontSize: '14px', color: '#6b7280' }}>|</span>
223
- <button
224
- type="button"
225
- onClick={(e) => { e.preventDefault(); props.loginCallback(); }}
226
- style={{
227
- background: 'none',
228
- border: 'none',
229
- color: '#3b82f6',
230
- fontSize: '14px',
231
- cursor: 'pointer',
232
- textDecoration: 'none'
233
- }}
234
- onMouseOver={(e) => e.currentTarget.style.textDecoration = 'underline'}
235
- onMouseOut={(e) => e.currentTarget.style.textDecoration = 'none'}
236
- >
237
- {Locale.label("login.login")}
238
- </button>
239
- </div>
240
- </>
241
- )}
242
- </form>
243
- </CardContent>
244
- </Card>
245
- </div>
246
- );
1
+ "use client";
2
+
3
+ import React, { FormEventHandler } from "react";
4
+ import { ApiHelper } from "@churchapps/helpers";
5
+ import { Locale } from "../helpers";
6
+ import { ResetPasswordRequestInterface, ResetPasswordResponseInterface } from "@churchapps/helpers";
7
+ import { TextField, Typography, Card, CardContent, Button } from "@mui/material";
8
+
9
+ interface Props {
10
+ registerCallback: () => void,
11
+ loginCallback: () => void
12
+ }
13
+
14
+ export const Forgot: React.FC<Props> = props => {
15
+ const [errors, setErrors] = React.useState([]);
16
+ const [successMessage, setSuccessMessage] = React.useState<React.ReactElement>(null);
17
+ const [email, setEmail] = React.useState("");
18
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
19
+
20
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement>) => {
21
+ setEmail(e.target.value);
22
+ }
23
+
24
+ const validateEmail = (email: string) => (/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,3})+$/.test(email))
25
+
26
+ const validate = () => {
27
+ const result = [];
28
+ if (!email) result.push(Locale.label("login.validate.email"));
29
+ else if (!validateEmail(email)) result.push(Locale.label("login.validate.email"));
30
+ setErrors(result);
31
+ return result.length === 0;
32
+ }
33
+
34
+ const reset: FormEventHandler = (e) => {
35
+ e.preventDefault();
36
+ if (validate()) {
37
+ setIsSubmitting(true);
38
+ let req: ResetPasswordRequestInterface = { userEmail: email };
39
+ ApiHelper.postAnonymous("/users/forgot", req, "MembershipApi").then((resp: ResetPasswordResponseInterface) => {
40
+ if (resp.emailed) {
41
+ setErrors([]);
42
+ setSuccessMessage(
43
+ <Typography textAlign="center" marginTop="35px">
44
+ {Locale.label("login.resetSent")} <br /><br />
45
+ <button
46
+ style={{
47
+ background: 'none',
48
+ border: 'none',
49
+ color: '#3b82f6',
50
+ fontSize: '14px',
51
+ cursor: 'pointer',
52
+ textDecoration: 'none'
53
+ }}
54
+ onMouseOver={(e) => e.currentTarget.style.textDecoration = 'underline'}
55
+ onMouseOut={(e) => e.currentTarget.style.textDecoration = 'none'}
56
+ onClick={(e) => { e.preventDefault(); props.loginCallback(); }}
57
+ >
58
+ {Locale.label("login.goLogin")}
59
+ </button>
60
+ </Typography>
61
+ );
62
+ setEmail("");
63
+ } else {
64
+ setErrors(["We could not find an account with this email address"]);
65
+ setSuccessMessage(<></>);
66
+ }
67
+ }).finally(() => { setIsSubmitting(false); });
68
+ }
69
+ }
70
+
71
+ return (
72
+ <div style={{ minHeight: '100vh', backgroundColor: 'white', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '16px' }}>
73
+ <Card sx={{
74
+ width: '100%',
75
+ maxWidth: { xs: '400px', sm: '500px' },
76
+ backgroundColor: 'white',
77
+ border: '1px solid #e5e7eb',
78
+ boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)'
79
+ }}>
80
+ <CardContent sx={{ textAlign: 'center', padding: '32px' }}>
81
+ <div style={{ marginBottom: '32px' }}>
82
+ <img
83
+ src="/images/logo-login.png"
84
+ alt="Church Logo"
85
+ style={{
86
+ maxWidth: '100%',
87
+ width: 'auto',
88
+ height: 'auto',
89
+ maxHeight: '80px',
90
+ marginBottom: '16px',
91
+ objectFit: 'contain'
92
+ }}
93
+ />
94
+ </div>
95
+ <Typography
96
+ component="h1"
97
+ sx={{
98
+ fontSize: '24px',
99
+ fontWeight: 'bold',
100
+ color: '#111827',
101
+ marginBottom: '8px'
102
+ }}
103
+ >
104
+ {Locale.label("login.resetPassword")}
105
+ </Typography>
106
+ <Typography
107
+ sx={{
108
+ color: '#6b7280',
109
+ marginBottom: '32px'
110
+ }}
111
+ >
112
+ Enter your email to receive password reset instructions
113
+ </Typography>
114
+
115
+ <form onSubmit={reset} style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
116
+ {errors.length > 0 && (
117
+ <div style={{
118
+ backgroundColor: '#fef2f2',
119
+ border: '1px solid #fecaca',
120
+ borderRadius: '6px',
121
+ padding: '12px',
122
+ textAlign: 'left'
123
+ }}>
124
+ {errors.map((error, index) => (
125
+ <div key={index} style={{ color: '#dc2626', fontSize: '14px' }}>{error}</div>
126
+ ))}
127
+ </div>
128
+ )}
129
+
130
+ {successMessage ? (
131
+ <div style={{ textAlign: 'center', display: 'flex', flexDirection: 'column', gap: '16px' }}>
132
+ {successMessage}
133
+ </div>
134
+ ) : (
135
+ <>
136
+ <Typography variant="body2" sx={{ color: '#6b7280', fontSize: '14px', textAlign: 'left' }}>
137
+ {Locale.label("login.resetInstructions")}
138
+ </Typography>
139
+
140
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
141
+ <label htmlFor="forgot-email" style={{ fontSize: '14px', fontWeight: 500, color: '#374151', textAlign: 'left' }}>
142
+ {Locale.label("login.email")}
143
+ </label>
144
+ <TextField
145
+ id="forgot-email"
146
+ name="forgot-email"
147
+ type="email"
148
+ placeholder={Locale.label("login.email")}
149
+ value={email}
150
+ onChange={handleChange}
151
+ autoFocus
152
+ required
153
+ autoComplete="email"
154
+ variant="outlined"
155
+ fullWidth
156
+ onKeyDown={(e: React.KeyboardEvent<HTMLInputElement>) => e.key === "Enter" && reset}
157
+ sx={{
158
+ '& .MuiOutlinedInput-root': {
159
+ backgroundColor: 'white',
160
+ '& fieldset': {
161
+ borderColor: '#d1d5db'
162
+ },
163
+ '&:hover fieldset': {
164
+ borderColor: '#d1d5db'
165
+ },
166
+ '&.Mui-focused fieldset': {
167
+ borderColor: '#3b82f6'
168
+ },
169
+ '& input': {
170
+ color: '#111827',
171
+ fontSize: '16px'
172
+ }
173
+ },
174
+ '& .MuiInputLabel-root': {
175
+ display: 'none'
176
+ }
177
+ }}
178
+ />
179
+ </div>
180
+
181
+ <Button
182
+ type="submit"
183
+ variant="contained"
184
+ fullWidth
185
+ disabled={isSubmitting}
186
+ sx={{
187
+ backgroundColor: 'hsl(218, 85%, 55%)',
188
+ color: 'white',
189
+ padding: '12px',
190
+ textTransform: 'none',
191
+ fontSize: '16px',
192
+ fontWeight: 500,
193
+ borderRadius: '6px',
194
+ '&:hover': {
195
+ backgroundColor: 'hsl(218, 85%, 50%)'
196
+ },
197
+ '&:disabled': {
198
+ backgroundColor: '#9ca3af'
199
+ }
200
+ }}
201
+ >
202
+ {isSubmitting ? "Sending..." : Locale.label("login.reset")}
203
+ </Button>
204
+
205
+ <div style={{ textAlign: 'center', display: 'flex', justifyContent: 'center', alignItems: 'center', gap: '8px' }}>
206
+ <button
207
+ type="button"
208
+ onClick={(e) => { e.preventDefault(); props.registerCallback(); }}
209
+ style={{
210
+ background: 'none',
211
+ border: 'none',
212
+ color: '#3b82f6',
213
+ fontSize: '14px',
214
+ cursor: 'pointer',
215
+ textDecoration: 'none'
216
+ }}
217
+ onMouseOver={(e) => e.currentTarget.style.textDecoration = 'underline'}
218
+ onMouseOut={(e) => e.currentTarget.style.textDecoration = 'none'}
219
+ >
220
+ {Locale.label("login.register")}
221
+ </button>
222
+ <span style={{ fontSize: '14px', color: '#6b7280' }}>|</span>
223
+ <button
224
+ type="button"
225
+ onClick={(e) => { e.preventDefault(); props.loginCallback(); }}
226
+ style={{
227
+ background: 'none',
228
+ border: 'none',
229
+ color: '#3b82f6',
230
+ fontSize: '14px',
231
+ cursor: 'pointer',
232
+ textDecoration: 'none'
233
+ }}
234
+ onMouseOver={(e) => e.currentTarget.style.textDecoration = 'underline'}
235
+ onMouseOut={(e) => e.currentTarget.style.textDecoration = 'none'}
236
+ >
237
+ {Locale.label("login.login")}
238
+ </button>
239
+ </div>
240
+ </>
241
+ )}
242
+ </form>
243
+ </CardContent>
244
+ </Card>
245
+ </div>
246
+ );
247
247
  }