@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,371 +1,371 @@
1
- "use client";
2
-
3
- import React, { FormEventHandler } from "react";
4
- import { LoginResponseInterface, RegisterUserInterface, UserInterface } from "@churchapps/helpers";
5
- import { ApiHelper } from "@churchapps/helpers";
6
- import { AnalyticsHelper, Locale } from "../helpers";
7
- import { TextField, Card, CardContent, Typography, Button } from "@mui/material";
8
-
9
- interface Props {
10
- appName?: string,
11
- appUrl?: string,
12
- updateErrors: (errors: string[]) => void,
13
- loginCallback?: () => void
14
- userRegisteredCallback?: (user: UserInterface) => Promise<void>;
15
- }
16
-
17
- export const Register: React.FC<Props> = (props) => {
18
-
19
- const cleanAppUrl = () => {
20
- if (!props.appUrl) return null;
21
- else {
22
- const index = props.appUrl.indexOf("/", 9);
23
- if (index === -1) return props.appUrl;
24
- else return props.appUrl.substring(0, index);
25
- }
26
- }
27
-
28
- const [registered, setRegistered] = React.useState(false);
29
- const [user, setUser] = React.useState<RegisterUserInterface>({ firstName: "", lastName: "", email: "", appName: props.appName, appUrl: cleanAppUrl() });
30
- const [errors, setErrors] = React.useState([]);
31
- const [isSubmitting, setIsSubmitting] = React.useState(false);
32
-
33
- const handleRegisterErrors = (errors: string[]) => {
34
- props.updateErrors(errors)
35
- }
36
-
37
- const handleRegisterSuccess = (resp: LoginResponseInterface) => {
38
- setRegistered(true);
39
- AnalyticsHelper.logEvent("User", "Register");
40
- if (props.userRegisteredCallback) props.userRegisteredCallback(resp.user);
41
- }
42
-
43
- const validateEmail = (email: string) => (/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(.\w{2,3})+$/.test(email))
44
-
45
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
46
- const u = { ...user }
47
- switch (e.target.name) {
48
- case "firstName": u.firstName = e.target.value; break;
49
- case "lastName": u.lastName = e.target.value; break;
50
- case "email": u.email = e.target.value; break;
51
- }
52
- setUser(u);
53
- }
54
-
55
- const validate = () => {
56
- let errors = [];
57
- if (!user.email?.trim()) errors.push(Locale.label("login.validate.email"));
58
- else if (!validateEmail(user.email)) errors.push(Locale.label("login.validate.email"));
59
- if (!user.firstName?.trim()) errors.push(Locale.label("login.validate.firstName"));
60
- if (!user.lastName?.trim()) errors.push(Locale.label("login.validate.lastName"));
61
- setErrors(errors);
62
- return errors.length === 0;
63
- }
64
-
65
- const register: FormEventHandler = (e) => {
66
- e.preventDefault();
67
- props.updateErrors([])
68
- if (validate()) {
69
- setIsSubmitting(true);
70
- ApiHelper.postAnonymous("/users/register", user, "MembershipApi")
71
- .then((resp: any) => {
72
- if (resp.errors) handleRegisterErrors(resp.errors);
73
- else handleRegisterSuccess(resp);
74
- })
75
- .catch((e: any) => { props.updateErrors([e.toString()]); throw e; })
76
- .finally(() => {
77
- setIsSubmitting(false)
78
- });
79
- }
80
- };
81
-
82
- if (registered) {
83
- return (
84
- <div style={{ minHeight: '100vh', backgroundColor: 'white', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '16px' }}>
85
- <Card sx={{
86
- width: '100%',
87
- maxWidth: { xs: '400px', sm: '500px' },
88
- backgroundColor: 'white',
89
- border: '1px solid #e5e7eb',
90
- boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)'
91
- }}>
92
- <CardContent sx={{ textAlign: 'center', padding: '32px' }}>
93
- <div style={{ marginBottom: '32px' }}>
94
- <img
95
- src="/images/logo-login.png"
96
- alt="Church Logo"
97
- style={{
98
- maxWidth: '100%',
99
- width: 'auto',
100
- height: 'auto',
101
- maxHeight: '80px',
102
- marginBottom: '16px',
103
- objectFit: 'contain'
104
- }}
105
- />
106
- </div>
107
- <Typography
108
- component="h1"
109
- sx={{
110
- fontSize: '24px',
111
- fontWeight: 'bold',
112
- color: '#111827',
113
- marginBottom: '32px'
114
- }}
115
- >
116
- Registration Complete
117
- </Typography>
118
- <Typography sx={{ color: '#6b7280', marginBottom: '24px' }}>
119
- {Locale.label("login.registerThankYou")}
120
- </Typography>
121
- <button
122
- type="button"
123
- onClick={(e) => { e.preventDefault(); props.loginCallback && props.loginCallback(); }}
124
- style={{
125
- background: 'none',
126
- border: 'none',
127
- color: '#3b82f6',
128
- fontSize: '14px',
129
- cursor: 'pointer',
130
- textDecoration: 'none'
131
- }}
132
- onMouseOver={(e) => e.currentTarget.style.textDecoration = 'underline'}
133
- onMouseOut={(e) => e.currentTarget.style.textDecoration = 'none'}
134
- >
135
- Back to sign in
136
- </button>
137
- </CardContent>
138
- </Card>
139
- </div>
140
- );
141
- }
142
-
143
- return (
144
- <div style={{ minHeight: '100vh', backgroundColor: 'white', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '16px' }}>
145
- <Card sx={{
146
- width: '100%',
147
- maxWidth: { xs: '400px', sm: '500px' },
148
- backgroundColor: 'white',
149
- border: '1px solid #e5e7eb',
150
- boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)'
151
- }}>
152
- <CardContent sx={{ textAlign: 'center', padding: '32px' }}>
153
- <div style={{ marginBottom: '32px' }}>
154
- <img
155
- src="/images/logo-login.png"
156
- alt="Church Logo"
157
- style={{
158
- maxWidth: '100%',
159
- width: 'auto',
160
- height: 'auto',
161
- maxHeight: '80px',
162
- marginBottom: '16px',
163
- objectFit: 'contain'
164
- }}
165
- />
166
- </div>
167
- <Typography
168
- component="h1"
169
- sx={{
170
- fontSize: '24px',
171
- fontWeight: 'bold',
172
- color: '#111827',
173
- marginBottom: '8px'
174
- }}
175
- >
176
- Create Account
177
- </Typography>
178
- <Typography
179
- sx={{
180
- color: '#6b7280',
181
- marginBottom: '32px'
182
- }}
183
- >
184
- Create a new account to access your church
185
- </Typography>
186
-
187
- <form onSubmit={register} style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
188
- {errors.length > 0 && (
189
- <div style={{
190
- backgroundColor: '#fef2f2',
191
- border: '1px solid #fecaca',
192
- borderRadius: '6px',
193
- padding: '12px',
194
- textAlign: 'left'
195
- }}>
196
- {errors.map((error, index) => (
197
- <div key={index} style={{ color: '#dc2626', fontSize: '14px' }}>{error}</div>
198
- ))}
199
- </div>
200
- )}
201
-
202
- <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px' }}>
203
- <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
204
- <label htmlFor="firstName" style={{ fontSize: '14px', fontWeight: 500, color: '#374151', textAlign: 'left' }}>
205
- First Name
206
- </label>
207
- <TextField
208
- id="firstName"
209
- name="firstName"
210
- type="text"
211
- placeholder="First Name"
212
- value={user.firstName}
213
- onChange={handleChange}
214
- required
215
- autoComplete="given-name"
216
- variant="outlined"
217
- fullWidth
218
- sx={{
219
- '& .MuiOutlinedInput-root': {
220
- backgroundColor: 'white',
221
- '& fieldset': {
222
- borderColor: '#d1d5db'
223
- },
224
- '&:hover fieldset': {
225
- borderColor: '#d1d5db'
226
- },
227
- '&.Mui-focused fieldset': {
228
- borderColor: '#3b82f6'
229
- },
230
- '& input': {
231
- color: '#111827',
232
- fontSize: '16px'
233
- }
234
- },
235
- '& .MuiInputLabel-root': {
236
- display: 'none'
237
- }
238
- }}
239
- />
240
- </div>
241
-
242
- <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
243
- <label htmlFor="lastName" style={{ fontSize: '14px', fontWeight: 500, color: '#374151', textAlign: 'left' }}>
244
- Last Name
245
- </label>
246
- <TextField
247
- id="lastName"
248
- name="lastName"
249
- type="text"
250
- placeholder="Last Name"
251
- value={user.lastName}
252
- onChange={handleChange}
253
- required
254
- autoComplete="family-name"
255
- variant="outlined"
256
- fullWidth
257
- sx={{
258
- '& .MuiOutlinedInput-root': {
259
- backgroundColor: 'white',
260
- '& fieldset': {
261
- borderColor: '#d1d5db'
262
- },
263
- '&:hover fieldset': {
264
- borderColor: '#d1d5db'
265
- },
266
- '&.Mui-focused fieldset': {
267
- borderColor: '#3b82f6'
268
- },
269
- '& input': {
270
- color: '#111827',
271
- fontSize: '16px'
272
- }
273
- },
274
- '& .MuiInputLabel-root': {
275
- display: 'none'
276
- }
277
- }}
278
- />
279
- </div>
280
- </div>
281
-
282
- <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
283
- <label htmlFor="email" style={{ fontSize: '14px', fontWeight: 500, color: '#374151', textAlign: 'left' }}>
284
- Email
285
- </label>
286
- <TextField
287
- id="email"
288
- name="email"
289
- type="email"
290
- placeholder="Email"
291
- value={user.email}
292
- onChange={handleChange}
293
- required
294
- autoComplete="email"
295
- variant="outlined"
296
- fullWidth
297
- sx={{
298
- '& .MuiOutlinedInput-root': {
299
- backgroundColor: 'white',
300
- '& fieldset': {
301
- borderColor: '#d1d5db'
302
- },
303
- '&:hover fieldset': {
304
- borderColor: '#d1d5db'
305
- },
306
- '&.Mui-focused fieldset': {
307
- borderColor: '#3b82f6'
308
- },
309
- '& input': {
310
- color: '#111827',
311
- fontSize: '16px'
312
- }
313
- },
314
- '& .MuiInputLabel-root': {
315
- display: 'none'
316
- }
317
- }}
318
- />
319
- </div>
320
-
321
- <Button
322
- type="submit"
323
- variant="contained"
324
- fullWidth
325
- disabled={isSubmitting}
326
- sx={{
327
- backgroundColor: 'hsl(218, 85%, 55%)',
328
- color: 'white',
329
- padding: '12px',
330
- textTransform: 'none',
331
- fontSize: '16px',
332
- fontWeight: 500,
333
- borderRadius: '6px',
334
- '&:hover': {
335
- backgroundColor: 'hsl(218, 85%, 50%)'
336
- },
337
- '&:disabled': {
338
- backgroundColor: '#9ca3af'
339
- }
340
- }}
341
- >
342
- {isSubmitting ? "Please wait..." : "Register"}
343
- </Button>
344
-
345
- <div style={{ textAlign: 'center' }}>
346
- <div style={{ fontSize: '14px', color: '#6b7280' }}>
347
- Already have an account?{' '}
348
- <button
349
- type="button"
350
- onClick={(e) => { e.preventDefault(); props.loginCallback && props.loginCallback(); }}
351
- style={{
352
- background: 'none',
353
- border: 'none',
354
- color: '#3b82f6',
355
- fontSize: '14px',
356
- cursor: 'pointer',
357
- textDecoration: 'none'
358
- }}
359
- onMouseOver={(e) => e.currentTarget.style.textDecoration = 'underline'}
360
- onMouseOut={(e) => e.currentTarget.style.textDecoration = 'none'}
361
- >
362
- Sign in
363
- </button>
364
- </div>
365
- </div>
366
- </form>
367
- </CardContent>
368
- </Card>
369
- </div>
370
- );
1
+ "use client";
2
+
3
+ import React, { FormEventHandler } from "react";
4
+ import { LoginResponseInterface, RegisterUserInterface, UserInterface } from "@churchapps/helpers";
5
+ import { ApiHelper } from "@churchapps/helpers";
6
+ import { AnalyticsHelper, Locale } from "../helpers";
7
+ import { TextField, Card, CardContent, Typography, Button } from "@mui/material";
8
+
9
+ interface Props {
10
+ appName?: string,
11
+ appUrl?: string,
12
+ updateErrors: (errors: string[]) => void,
13
+ loginCallback?: () => void
14
+ userRegisteredCallback?: (user: UserInterface) => Promise<void>;
15
+ }
16
+
17
+ export const Register: React.FC<Props> = (props) => {
18
+
19
+ const cleanAppUrl = () => {
20
+ if (!props.appUrl) return null;
21
+ else {
22
+ const index = props.appUrl.indexOf("/", 9);
23
+ if (index === -1) return props.appUrl;
24
+ else return props.appUrl.substring(0, index);
25
+ }
26
+ }
27
+
28
+ const [registered, setRegistered] = React.useState(false);
29
+ const [user, setUser] = React.useState<RegisterUserInterface>({ firstName: "", lastName: "", email: "", appName: props.appName, appUrl: cleanAppUrl() });
30
+ const [errors, setErrors] = React.useState([]);
31
+ const [isSubmitting, setIsSubmitting] = React.useState(false);
32
+
33
+ const handleRegisterErrors = (errors: string[]) => {
34
+ props.updateErrors(errors)
35
+ }
36
+
37
+ const handleRegisterSuccess = (resp: LoginResponseInterface) => {
38
+ setRegistered(true);
39
+ AnalyticsHelper.logEvent("User", "Register");
40
+ if (props.userRegisteredCallback) props.userRegisteredCallback(resp.user);
41
+ }
42
+
43
+ const validateEmail = (email: string) => (/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(.\w{2,3})+$/.test(email))
44
+
45
+ const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
46
+ const u = { ...user }
47
+ switch (e.target.name) {
48
+ case "firstName": u.firstName = e.target.value; break;
49
+ case "lastName": u.lastName = e.target.value; break;
50
+ case "email": u.email = e.target.value; break;
51
+ }
52
+ setUser(u);
53
+ }
54
+
55
+ const validate = () => {
56
+ let errors = [];
57
+ if (!user.email?.trim()) errors.push(Locale.label("login.validate.email"));
58
+ else if (!validateEmail(user.email)) errors.push(Locale.label("login.validate.email"));
59
+ if (!user.firstName?.trim()) errors.push(Locale.label("login.validate.firstName"));
60
+ if (!user.lastName?.trim()) errors.push(Locale.label("login.validate.lastName"));
61
+ setErrors(errors);
62
+ return errors.length === 0;
63
+ }
64
+
65
+ const register: FormEventHandler = (e) => {
66
+ e.preventDefault();
67
+ props.updateErrors([])
68
+ if (validate()) {
69
+ setIsSubmitting(true);
70
+ ApiHelper.postAnonymous("/users/register", user, "MembershipApi")
71
+ .then((resp: any) => {
72
+ if (resp.errors) handleRegisterErrors(resp.errors);
73
+ else handleRegisterSuccess(resp);
74
+ })
75
+ .catch((e: any) => { props.updateErrors([e.toString()]); throw e; })
76
+ .finally(() => {
77
+ setIsSubmitting(false)
78
+ });
79
+ }
80
+ };
81
+
82
+ if (registered) {
83
+ return (
84
+ <div style={{ minHeight: '100vh', backgroundColor: 'white', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '16px' }}>
85
+ <Card sx={{
86
+ width: '100%',
87
+ maxWidth: { xs: '400px', sm: '500px' },
88
+ backgroundColor: 'white',
89
+ border: '1px solid #e5e7eb',
90
+ boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)'
91
+ }}>
92
+ <CardContent sx={{ textAlign: 'center', padding: '32px' }}>
93
+ <div style={{ marginBottom: '32px' }}>
94
+ <img
95
+ src="/images/logo-login.png"
96
+ alt="Church Logo"
97
+ style={{
98
+ maxWidth: '100%',
99
+ width: 'auto',
100
+ height: 'auto',
101
+ maxHeight: '80px',
102
+ marginBottom: '16px',
103
+ objectFit: 'contain'
104
+ }}
105
+ />
106
+ </div>
107
+ <Typography
108
+ component="h1"
109
+ sx={{
110
+ fontSize: '24px',
111
+ fontWeight: 'bold',
112
+ color: '#111827',
113
+ marginBottom: '32px'
114
+ }}
115
+ >
116
+ Registration Complete
117
+ </Typography>
118
+ <Typography sx={{ color: '#6b7280', marginBottom: '24px' }}>
119
+ {Locale.label("login.registerThankYou")}
120
+ </Typography>
121
+ <button
122
+ type="button"
123
+ onClick={(e) => { e.preventDefault(); props.loginCallback && props.loginCallback(); }}
124
+ style={{
125
+ background: 'none',
126
+ border: 'none',
127
+ color: '#3b82f6',
128
+ fontSize: '14px',
129
+ cursor: 'pointer',
130
+ textDecoration: 'none'
131
+ }}
132
+ onMouseOver={(e) => e.currentTarget.style.textDecoration = 'underline'}
133
+ onMouseOut={(e) => e.currentTarget.style.textDecoration = 'none'}
134
+ >
135
+ Back to sign in
136
+ </button>
137
+ </CardContent>
138
+ </Card>
139
+ </div>
140
+ );
141
+ }
142
+
143
+ return (
144
+ <div style={{ minHeight: '100vh', backgroundColor: 'white', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '16px' }}>
145
+ <Card sx={{
146
+ width: '100%',
147
+ maxWidth: { xs: '400px', sm: '500px' },
148
+ backgroundColor: 'white',
149
+ border: '1px solid #e5e7eb',
150
+ boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)'
151
+ }}>
152
+ <CardContent sx={{ textAlign: 'center', padding: '32px' }}>
153
+ <div style={{ marginBottom: '32px' }}>
154
+ <img
155
+ src="/images/logo-login.png"
156
+ alt="Church Logo"
157
+ style={{
158
+ maxWidth: '100%',
159
+ width: 'auto',
160
+ height: 'auto',
161
+ maxHeight: '80px',
162
+ marginBottom: '16px',
163
+ objectFit: 'contain'
164
+ }}
165
+ />
166
+ </div>
167
+ <Typography
168
+ component="h1"
169
+ sx={{
170
+ fontSize: '24px',
171
+ fontWeight: 'bold',
172
+ color: '#111827',
173
+ marginBottom: '8px'
174
+ }}
175
+ >
176
+ Create Account
177
+ </Typography>
178
+ <Typography
179
+ sx={{
180
+ color: '#6b7280',
181
+ marginBottom: '32px'
182
+ }}
183
+ >
184
+ Create a new account to access your church
185
+ </Typography>
186
+
187
+ <form onSubmit={register} style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
188
+ {errors.length > 0 && (
189
+ <div style={{
190
+ backgroundColor: '#fef2f2',
191
+ border: '1px solid #fecaca',
192
+ borderRadius: '6px',
193
+ padding: '12px',
194
+ textAlign: 'left'
195
+ }}>
196
+ {errors.map((error, index) => (
197
+ <div key={index} style={{ color: '#dc2626', fontSize: '14px' }}>{error}</div>
198
+ ))}
199
+ </div>
200
+ )}
201
+
202
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '8px' }}>
203
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
204
+ <label htmlFor="firstName" style={{ fontSize: '14px', fontWeight: 500, color: '#374151', textAlign: 'left' }}>
205
+ First Name
206
+ </label>
207
+ <TextField
208
+ id="firstName"
209
+ name="firstName"
210
+ type="text"
211
+ placeholder="First Name"
212
+ value={user.firstName}
213
+ onChange={handleChange}
214
+ required
215
+ autoComplete="given-name"
216
+ variant="outlined"
217
+ fullWidth
218
+ sx={{
219
+ '& .MuiOutlinedInput-root': {
220
+ backgroundColor: 'white',
221
+ '& fieldset': {
222
+ borderColor: '#d1d5db'
223
+ },
224
+ '&:hover fieldset': {
225
+ borderColor: '#d1d5db'
226
+ },
227
+ '&.Mui-focused fieldset': {
228
+ borderColor: '#3b82f6'
229
+ },
230
+ '& input': {
231
+ color: '#111827',
232
+ fontSize: '16px'
233
+ }
234
+ },
235
+ '& .MuiInputLabel-root': {
236
+ display: 'none'
237
+ }
238
+ }}
239
+ />
240
+ </div>
241
+
242
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
243
+ <label htmlFor="lastName" style={{ fontSize: '14px', fontWeight: 500, color: '#374151', textAlign: 'left' }}>
244
+ Last Name
245
+ </label>
246
+ <TextField
247
+ id="lastName"
248
+ name="lastName"
249
+ type="text"
250
+ placeholder="Last Name"
251
+ value={user.lastName}
252
+ onChange={handleChange}
253
+ required
254
+ autoComplete="family-name"
255
+ variant="outlined"
256
+ fullWidth
257
+ sx={{
258
+ '& .MuiOutlinedInput-root': {
259
+ backgroundColor: 'white',
260
+ '& fieldset': {
261
+ borderColor: '#d1d5db'
262
+ },
263
+ '&:hover fieldset': {
264
+ borderColor: '#d1d5db'
265
+ },
266
+ '&.Mui-focused fieldset': {
267
+ borderColor: '#3b82f6'
268
+ },
269
+ '& input': {
270
+ color: '#111827',
271
+ fontSize: '16px'
272
+ }
273
+ },
274
+ '& .MuiInputLabel-root': {
275
+ display: 'none'
276
+ }
277
+ }}
278
+ />
279
+ </div>
280
+ </div>
281
+
282
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
283
+ <label htmlFor="email" style={{ fontSize: '14px', fontWeight: 500, color: '#374151', textAlign: 'left' }}>
284
+ Email
285
+ </label>
286
+ <TextField
287
+ id="email"
288
+ name="email"
289
+ type="email"
290
+ placeholder="Email"
291
+ value={user.email}
292
+ onChange={handleChange}
293
+ required
294
+ autoComplete="email"
295
+ variant="outlined"
296
+ fullWidth
297
+ sx={{
298
+ '& .MuiOutlinedInput-root': {
299
+ backgroundColor: 'white',
300
+ '& fieldset': {
301
+ borderColor: '#d1d5db'
302
+ },
303
+ '&:hover fieldset': {
304
+ borderColor: '#d1d5db'
305
+ },
306
+ '&.Mui-focused fieldset': {
307
+ borderColor: '#3b82f6'
308
+ },
309
+ '& input': {
310
+ color: '#111827',
311
+ fontSize: '16px'
312
+ }
313
+ },
314
+ '& .MuiInputLabel-root': {
315
+ display: 'none'
316
+ }
317
+ }}
318
+ />
319
+ </div>
320
+
321
+ <Button
322
+ type="submit"
323
+ variant="contained"
324
+ fullWidth
325
+ disabled={isSubmitting}
326
+ sx={{
327
+ backgroundColor: 'hsl(218, 85%, 55%)',
328
+ color: 'white',
329
+ padding: '12px',
330
+ textTransform: 'none',
331
+ fontSize: '16px',
332
+ fontWeight: 500,
333
+ borderRadius: '6px',
334
+ '&:hover': {
335
+ backgroundColor: 'hsl(218, 85%, 50%)'
336
+ },
337
+ '&:disabled': {
338
+ backgroundColor: '#9ca3af'
339
+ }
340
+ }}
341
+ >
342
+ {isSubmitting ? "Please wait..." : "Register"}
343
+ </Button>
344
+
345
+ <div style={{ textAlign: 'center' }}>
346
+ <div style={{ fontSize: '14px', color: '#6b7280' }}>
347
+ Already have an account?{' '}
348
+ <button
349
+ type="button"
350
+ onClick={(e) => { e.preventDefault(); props.loginCallback && props.loginCallback(); }}
351
+ style={{
352
+ background: 'none',
353
+ border: 'none',
354
+ color: '#3b82f6',
355
+ fontSize: '14px',
356
+ cursor: 'pointer',
357
+ textDecoration: 'none'
358
+ }}
359
+ onMouseOver={(e) => e.currentTarget.style.textDecoration = 'underline'}
360
+ onMouseOut={(e) => e.currentTarget.style.textDecoration = 'none'}
361
+ >
362
+ Sign in
363
+ </button>
364
+ </div>
365
+ </div>
366
+ </form>
367
+ </CardContent>
368
+ </Card>
369
+ </div>
370
+ );
371
371
  };