@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,306 +1,306 @@
1
- "use client";
2
-
3
- import React from "react";
4
- import { TextField, PaperProps, InputAdornment, IconButton, Card, CardContent, Typography, Button } from "@mui/material";
5
- import { Visibility, VisibilityOff } from "@mui/icons-material";
6
- import { Locale } from "../helpers";
7
-
8
- interface Props {
9
- login: (data: any) => void,
10
- isSubmitting: boolean,
11
- setShowRegister: (showRegister: boolean) => void,
12
- setShowForgot: (showForgot: boolean) => void,
13
- setErrors: (errors: string[]) => void;
14
- mainContainerCssProps?: PaperProps;
15
- defaultEmail?: string;
16
- defaultPassword?: string;
17
- showFooter?: boolean;
18
- }
19
-
20
- export const Login: React.FC<Props> = ({ mainContainerCssProps = {}, ...props }) => {
21
- const [email, setEmail] = React.useState(props.defaultEmail || "");
22
- const [password, setPassword] = React.useState(props.defaultPassword || "");
23
- const [showPassword, setShowPassword] = React.useState(false);
24
-
25
- const validateEmail = (email: string) => (/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(.\w{2,3})+$/.test(email))
26
-
27
- const validate = () => {
28
- const result = [];
29
- if (!email) result.push(Locale.label("login.validate.email"));
30
- else if (!validateEmail(email)) result.push(Locale.label("login.validate.email"));
31
- if (!password) result.push(Locale.label("login.validate.password"));
32
- console.log('Validation errors:', result);
33
- console.log('setErrors function:', props.setErrors);
34
- props.setErrors(result);
35
- return result.length === 0;
36
- }
37
-
38
- const submitLogin = () => {
39
- if (validate()) props.login({ email, password });
40
- }
41
-
42
- const handleShowRegister = (e: React.MouseEvent) => {
43
- e.preventDefault();
44
- props.setShowRegister(true);
45
- }
46
-
47
- return (
48
- <div id="login-container" style={{ minHeight: '100vh', backgroundColor: 'white', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '16px', position: 'relative' }}>
49
- <Card id="login-card" sx={{
50
- width: '100%',
51
- maxWidth: { xs: '400px', sm: '500px' },
52
- backgroundColor: 'white',
53
- border: '1px solid #e5e7eb',
54
- boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
55
- ...mainContainerCssProps
56
- }}>
57
- <CardContent id="login-content" sx={{ textAlign: 'center', padding: '32px' }}>
58
- <div id="login-logo" style={{ marginBottom: '32px' }}>
59
- <img
60
- id="login-logo-image"
61
- src="/images/logo-login.png"
62
- alt="Church Logo"
63
- style={{
64
- maxWidth: '100%',
65
- width: 'auto',
66
- height: 'auto',
67
- maxHeight: '80px',
68
- marginBottom: '16px',
69
- objectFit: 'contain'
70
- }}
71
- />
72
- </div>
73
- <Typography
74
- id="login-title"
75
- component="h1"
76
- sx={{
77
- fontSize: '24px',
78
- fontWeight: 'bold',
79
- color: '#111827',
80
- marginBottom: '8px'
81
- }}
82
- >
83
- {Locale.label("login.signInTitle")}
84
- </Typography>
85
- <Typography
86
- sx={{
87
- color: '#6b7280',
88
- marginBottom: '32px'
89
- }}
90
- >
91
- Enter your email and password to access your church
92
- </Typography>
93
-
94
- <form id="login-form" onSubmit={(e) => { e.preventDefault(); submitLogin(); }} style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
95
- <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
96
- <label htmlFor="email" style={{ fontSize: '14px', fontWeight: 500, color: '#374151', textAlign: 'left' }}>
97
- {Locale.label("login.email")}
98
- </label>
99
- <TextField
100
- id="login-email-field"
101
- name="email"
102
- type="email"
103
- placeholder={Locale.label("login.email")}
104
- value={email}
105
- onChange={(e) => setEmail(e.target.value)}
106
- autoFocus
107
- required
108
- autoComplete="email"
109
- variant="outlined"
110
- fullWidth
111
- sx={{
112
- '& .MuiOutlinedInput-root': {
113
- backgroundColor: 'white',
114
- '& fieldset': {
115
- borderColor: '#d1d5db'
116
- },
117
- '&:hover fieldset': {
118
- borderColor: '#d1d5db'
119
- },
120
- '&.Mui-focused fieldset': {
121
- borderColor: '#3b82f6'
122
- },
123
- '& input': {
124
- color: '#111827',
125
- fontSize: '16px'
126
- }
127
- },
128
- '& .MuiInputLabel-root': {
129
- display: 'none'
130
- }
131
- }}
132
- />
133
- </div>
134
-
135
- <div style={{ display: 'flex', flexDirection: 'column', gap: '8px', position: 'relative' }}>
136
- <label htmlFor="password" style={{ fontSize: '14px', fontWeight: 500, color: '#374151', textAlign: 'left' }}>
137
- {Locale.label("login.password")}
138
- </label>
139
- <TextField
140
- id="login-password-field"
141
- name="password"
142
- type={showPassword ? "text" : "password"}
143
- placeholder={Locale.label("login.password")}
144
- value={password}
145
- onChange={(e) => setPassword(e.target.value)}
146
- required
147
- autoComplete="current-password"
148
- variant="outlined"
149
- fullWidth
150
- InputProps={{
151
- endAdornment: (
152
- <InputAdornment position="end">
153
- <IconButton
154
- id="password-visibility-toggle"
155
- aria-label="toggle password visibility"
156
- onClick={() => setShowPassword(!showPassword)}
157
- edge="end"
158
- sx={{ color: '#6b7280' }}
159
- >
160
- {showPassword ? <VisibilityOff /> : <Visibility />}
161
- </IconButton>
162
- </InputAdornment>
163
- )
164
- }}
165
- sx={{
166
- '& .MuiOutlinedInput-root': {
167
- backgroundColor: 'white',
168
- paddingRight: '10px',
169
- '& fieldset': {
170
- borderColor: '#d1d5db'
171
- },
172
- '&:hover fieldset': {
173
- borderColor: '#d1d5db'
174
- },
175
- '&.Mui-focused fieldset': {
176
- borderColor: '#3b82f6'
177
- },
178
- '& input': {
179
- color: '#111827',
180
- fontSize: '16px'
181
- }
182
- },
183
- '& .MuiInputLabel-root': {
184
- display: 'none'
185
- }
186
- }}
187
- />
188
- </div>
189
-
190
- <Button
191
- id="login-submit-button"
192
- type="submit"
193
- variant="contained"
194
- fullWidth
195
- disabled={props.isSubmitting}
196
- sx={{
197
- backgroundColor: 'hsl(218, 85%, 55%)',
198
- color: 'white',
199
- padding: '12px',
200
- textTransform: 'none',
201
- fontSize: '16px',
202
- fontWeight: 500,
203
- borderRadius: '6px',
204
- '&:hover': {
205
- backgroundColor: 'hsl(218, 85%, 50%)'
206
- },
207
- '&:disabled': {
208
- backgroundColor: '#9ca3af'
209
- }
210
- }}
211
- >
212
- {props.isSubmitting ? Locale.label("common.pleaseWait") : Locale.label("login.signIn")}
213
- </Button>
214
-
215
- <div id="login-links" style={{ textAlign: 'center', display: 'flex', flexDirection: 'column', gap: '8px' }}>
216
- <button
217
- id="forgot-password-link"
218
- type="button"
219
- onClick={(e) => { e.preventDefault(); props.setShowForgot(true); }}
220
- style={{
221
- background: 'none',
222
- border: 'none',
223
- color: '#3b82f6',
224
- fontSize: '14px',
225
- cursor: 'pointer',
226
- textDecoration: 'none'
227
- }}
228
- onMouseOver={(e) => e.currentTarget.style.textDecoration = 'underline'}
229
- onMouseOut={(e) => e.currentTarget.style.textDecoration = 'none'}
230
- >
231
- {Locale.label("login.forgot")}
232
- </button>
233
- <div style={{ fontSize: '14px', color: '#6b7280' }}>
234
- Don't have an account?{' '}
235
- <button
236
- id="register-link"
237
- type="button"
238
- onClick={handleShowRegister}
239
- style={{
240
- background: 'none',
241
- border: 'none',
242
- color: '#3b82f6',
243
- fontSize: '14px',
244
- cursor: 'pointer',
245
- textDecoration: 'none'
246
- }}
247
- onMouseOver={(e) => e.currentTarget.style.textDecoration = 'underline'}
248
- onMouseOut={(e) => e.currentTarget.style.textDecoration = 'none'}
249
- >
250
- {Locale.label("login.register")}
251
- </button>
252
- </div>
253
- </div>
254
- </form>
255
- </CardContent>
256
- </Card>
257
-
258
- {props.showFooter && (
259
- <div id="login-footer" style={{
260
- position: 'fixed',
261
- bottom: 0,
262
- left: 0,
263
- right: 0,
264
- backgroundColor: '#f9fafb',
265
- borderTop: '1px solid #e5e7eb',
266
- padding: '16px',
267
- display: 'flex',
268
- justifyContent: 'center',
269
- alignItems: 'center',
270
- gap: '24px',
271
- fontSize: '14px',
272
- color: '#6b7280'
273
- }}>
274
- <span style={{ marginRight: '8px' }}>Ministry that's supported, not sold</span>
275
- <a
276
- href="https://churchapps.org/partner"
277
- target="_blank"
278
- rel="noopener noreferrer"
279
- style={{
280
- color: '#3b82f6',
281
- textDecoration: 'none',
282
- display: 'flex',
283
- alignItems: 'center',
284
- gap: '4px'
285
- }}>
286
- <span>💙</span> Donate
287
- </a>
288
- <a
289
- href="https://github.com/ChurchApps/ChurchAppsSupport/issues"
290
- target="_blank"
291
- rel="noopener noreferrer"
292
- style={{
293
- color: '#6b7280',
294
- textDecoration: 'none',
295
- display: 'flex',
296
- alignItems: 'center',
297
- gap: '4px'
298
- }}
299
- >
300
- <span>🐛</span> Report Bug
301
- </a>
302
- </div>
303
- )}
304
- </div>
305
- );
1
+ "use client";
2
+
3
+ import React from "react";
4
+ import { TextField, PaperProps, InputAdornment, IconButton, Card, CardContent, Typography, Button } from "@mui/material";
5
+ import { Visibility, VisibilityOff } from "@mui/icons-material";
6
+ import { Locale } from "../helpers";
7
+
8
+ interface Props {
9
+ login: (data: any) => void,
10
+ isSubmitting: boolean,
11
+ setShowRegister: (showRegister: boolean) => void,
12
+ setShowForgot: (showForgot: boolean) => void,
13
+ setErrors: (errors: string[]) => void;
14
+ mainContainerCssProps?: PaperProps;
15
+ defaultEmail?: string;
16
+ defaultPassword?: string;
17
+ showFooter?: boolean;
18
+ }
19
+
20
+ export const Login: React.FC<Props> = ({ mainContainerCssProps = {}, ...props }) => {
21
+ const [email, setEmail] = React.useState(props.defaultEmail || "");
22
+ const [password, setPassword] = React.useState(props.defaultPassword || "");
23
+ const [showPassword, setShowPassword] = React.useState(false);
24
+
25
+ const validateEmail = (email: string) => (/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(.\w{2,3})+$/.test(email))
26
+
27
+ const validate = () => {
28
+ const result = [];
29
+ if (!email) result.push(Locale.label("login.validate.email"));
30
+ else if (!validateEmail(email)) result.push(Locale.label("login.validate.email"));
31
+ if (!password) result.push(Locale.label("login.validate.password"));
32
+ console.log('Validation errors:', result);
33
+ console.log('setErrors function:', props.setErrors);
34
+ props.setErrors(result);
35
+ return result.length === 0;
36
+ }
37
+
38
+ const submitLogin = () => {
39
+ if (validate()) props.login({ email, password });
40
+ }
41
+
42
+ const handleShowRegister = (e: React.MouseEvent) => {
43
+ e.preventDefault();
44
+ props.setShowRegister(true);
45
+ }
46
+
47
+ return (
48
+ <div id="login-container" style={{ minHeight: '100vh', backgroundColor: 'white', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: '16px', position: 'relative' }}>
49
+ <Card id="login-card" sx={{
50
+ width: '100%',
51
+ maxWidth: { xs: '400px', sm: '500px' },
52
+ backgroundColor: 'white',
53
+ border: '1px solid #e5e7eb',
54
+ boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
55
+ ...mainContainerCssProps
56
+ }}>
57
+ <CardContent id="login-content" sx={{ textAlign: 'center', padding: '32px' }}>
58
+ <div id="login-logo" style={{ marginBottom: '32px' }}>
59
+ <img
60
+ id="login-logo-image"
61
+ src="/images/logo-login.png"
62
+ alt="Church Logo"
63
+ style={{
64
+ maxWidth: '100%',
65
+ width: 'auto',
66
+ height: 'auto',
67
+ maxHeight: '80px',
68
+ marginBottom: '16px',
69
+ objectFit: 'contain'
70
+ }}
71
+ />
72
+ </div>
73
+ <Typography
74
+ id="login-title"
75
+ component="h1"
76
+ sx={{
77
+ fontSize: '24px',
78
+ fontWeight: 'bold',
79
+ color: '#111827',
80
+ marginBottom: '8px'
81
+ }}
82
+ >
83
+ {Locale.label("login.signInTitle")}
84
+ </Typography>
85
+ <Typography
86
+ sx={{
87
+ color: '#6b7280',
88
+ marginBottom: '32px'
89
+ }}
90
+ >
91
+ Enter your email and password to access your church
92
+ </Typography>
93
+
94
+ <form id="login-form" onSubmit={(e) => { e.preventDefault(); submitLogin(); }} style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
95
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
96
+ <label htmlFor="email" style={{ fontSize: '14px', fontWeight: 500, color: '#374151', textAlign: 'left' }}>
97
+ {Locale.label("login.email")}
98
+ </label>
99
+ <TextField
100
+ id="login-email-field"
101
+ name="email"
102
+ type="email"
103
+ placeholder={Locale.label("login.email")}
104
+ value={email}
105
+ onChange={(e) => setEmail(e.target.value)}
106
+ autoFocus
107
+ required
108
+ autoComplete="email"
109
+ variant="outlined"
110
+ fullWidth
111
+ sx={{
112
+ '& .MuiOutlinedInput-root': {
113
+ backgroundColor: 'white',
114
+ '& fieldset': {
115
+ borderColor: '#d1d5db'
116
+ },
117
+ '&:hover fieldset': {
118
+ borderColor: '#d1d5db'
119
+ },
120
+ '&.Mui-focused fieldset': {
121
+ borderColor: '#3b82f6'
122
+ },
123
+ '& input': {
124
+ color: '#111827',
125
+ fontSize: '16px'
126
+ }
127
+ },
128
+ '& .MuiInputLabel-root': {
129
+ display: 'none'
130
+ }
131
+ }}
132
+ />
133
+ </div>
134
+
135
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '8px', position: 'relative' }}>
136
+ <label htmlFor="password" style={{ fontSize: '14px', fontWeight: 500, color: '#374151', textAlign: 'left' }}>
137
+ {Locale.label("login.password")}
138
+ </label>
139
+ <TextField
140
+ id="login-password-field"
141
+ name="password"
142
+ type={showPassword ? "text" : "password"}
143
+ placeholder={Locale.label("login.password")}
144
+ value={password}
145
+ onChange={(e) => setPassword(e.target.value)}
146
+ required
147
+ autoComplete="current-password"
148
+ variant="outlined"
149
+ fullWidth
150
+ InputProps={{
151
+ endAdornment: (
152
+ <InputAdornment position="end">
153
+ <IconButton
154
+ id="password-visibility-toggle"
155
+ aria-label="toggle password visibility"
156
+ onClick={() => setShowPassword(!showPassword)}
157
+ edge="end"
158
+ sx={{ color: '#6b7280' }}
159
+ >
160
+ {showPassword ? <VisibilityOff /> : <Visibility />}
161
+ </IconButton>
162
+ </InputAdornment>
163
+ )
164
+ }}
165
+ sx={{
166
+ '& .MuiOutlinedInput-root': {
167
+ backgroundColor: 'white',
168
+ paddingRight: '10px',
169
+ '& fieldset': {
170
+ borderColor: '#d1d5db'
171
+ },
172
+ '&:hover fieldset': {
173
+ borderColor: '#d1d5db'
174
+ },
175
+ '&.Mui-focused fieldset': {
176
+ borderColor: '#3b82f6'
177
+ },
178
+ '& input': {
179
+ color: '#111827',
180
+ fontSize: '16px'
181
+ }
182
+ },
183
+ '& .MuiInputLabel-root': {
184
+ display: 'none'
185
+ }
186
+ }}
187
+ />
188
+ </div>
189
+
190
+ <Button
191
+ id="login-submit-button"
192
+ type="submit"
193
+ variant="contained"
194
+ fullWidth
195
+ disabled={props.isSubmitting}
196
+ sx={{
197
+ backgroundColor: 'hsl(218, 85%, 55%)',
198
+ color: 'white',
199
+ padding: '12px',
200
+ textTransform: 'none',
201
+ fontSize: '16px',
202
+ fontWeight: 500,
203
+ borderRadius: '6px',
204
+ '&:hover': {
205
+ backgroundColor: 'hsl(218, 85%, 50%)'
206
+ },
207
+ '&:disabled': {
208
+ backgroundColor: '#9ca3af'
209
+ }
210
+ }}
211
+ >
212
+ {props.isSubmitting ? Locale.label("common.pleaseWait") : Locale.label("login.signIn")}
213
+ </Button>
214
+
215
+ <div id="login-links" style={{ textAlign: 'center', display: 'flex', flexDirection: 'column', gap: '8px' }}>
216
+ <button
217
+ id="forgot-password-link"
218
+ type="button"
219
+ onClick={(e) => { e.preventDefault(); props.setShowForgot(true); }}
220
+ style={{
221
+ background: 'none',
222
+ border: 'none',
223
+ color: '#3b82f6',
224
+ fontSize: '14px',
225
+ cursor: 'pointer',
226
+ textDecoration: 'none'
227
+ }}
228
+ onMouseOver={(e) => e.currentTarget.style.textDecoration = 'underline'}
229
+ onMouseOut={(e) => e.currentTarget.style.textDecoration = 'none'}
230
+ >
231
+ {Locale.label("login.forgot")}
232
+ </button>
233
+ <div style={{ fontSize: '14px', color: '#6b7280' }}>
234
+ Don't have an account?{' '}
235
+ <button
236
+ id="register-link"
237
+ type="button"
238
+ onClick={handleShowRegister}
239
+ style={{
240
+ background: 'none',
241
+ border: 'none',
242
+ color: '#3b82f6',
243
+ fontSize: '14px',
244
+ cursor: 'pointer',
245
+ textDecoration: 'none'
246
+ }}
247
+ onMouseOver={(e) => e.currentTarget.style.textDecoration = 'underline'}
248
+ onMouseOut={(e) => e.currentTarget.style.textDecoration = 'none'}
249
+ >
250
+ {Locale.label("login.register")}
251
+ </button>
252
+ </div>
253
+ </div>
254
+ </form>
255
+ </CardContent>
256
+ </Card>
257
+
258
+ {props.showFooter && (
259
+ <div id="login-footer" style={{
260
+ position: 'fixed',
261
+ bottom: 0,
262
+ left: 0,
263
+ right: 0,
264
+ backgroundColor: '#f9fafb',
265
+ borderTop: '1px solid #e5e7eb',
266
+ padding: '16px',
267
+ display: 'flex',
268
+ justifyContent: 'center',
269
+ alignItems: 'center',
270
+ gap: '24px',
271
+ fontSize: '14px',
272
+ color: '#6b7280'
273
+ }}>
274
+ <span style={{ marginRight: '8px' }}>Ministry that's supported, not sold</span>
275
+ <a
276
+ href="https://churchapps.org/partner"
277
+ target="_blank"
278
+ rel="noopener noreferrer"
279
+ style={{
280
+ color: '#3b82f6',
281
+ textDecoration: 'none',
282
+ display: 'flex',
283
+ alignItems: 'center',
284
+ gap: '4px'
285
+ }}>
286
+ <span>💙</span> Donate
287
+ </a>
288
+ <a
289
+ href="https://github.com/ChurchApps/ChurchAppsSupport/issues"
290
+ target="_blank"
291
+ rel="noopener noreferrer"
292
+ style={{
293
+ color: '#6b7280',
294
+ textDecoration: 'none',
295
+ display: 'flex',
296
+ alignItems: 'center',
297
+ gap: '4px'
298
+ }}
299
+ >
300
+ <span>🐛</span> Report Bug
301
+ </a>
302
+ </div>
303
+ )}
304
+ </div>
305
+ );
306
306
  }