@churchapps/apphelper-login 0.6.14 → 0.6.16

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.
Files changed (52) hide show
  1. package/dist/LoginPage.d.ts +1 -0
  2. package/dist/LoginPage.d.ts.map +1 -1
  3. package/dist/LoginPage.js +14 -3
  4. package/dist/LoginPage.js.map +1 -1
  5. package/dist/LogoutPage.d.ts.map +1 -1
  6. package/dist/LogoutPage.js +1 -1
  7. package/dist/LogoutPage.js.map +1 -1
  8. package/dist/components/Forgot.d.ts.map +1 -1
  9. package/dist/components/Forgot.js +71 -83
  10. package/dist/components/Forgot.js.map +1 -1
  11. package/dist/components/Login.d.ts.map +1 -1
  12. package/dist/components/Login.js +86 -106
  13. package/dist/components/Login.js.map +1 -1
  14. package/dist/components/LoginSetPassword.d.ts.map +1 -1
  15. package/dist/components/LoginSetPassword.js +70 -94
  16. package/dist/components/LoginSetPassword.js.map +1 -1
  17. package/dist/components/Register.d.ts.map +1 -1
  18. package/dist/components/Register.js +104 -132
  19. package/dist/components/Register.js.map +1 -1
  20. package/dist/components/SelectChurchModal.d.ts.map +1 -1
  21. package/dist/components/SelectChurchModal.js +2 -1
  22. package/dist/components/SelectChurchModal.js.map +1 -1
  23. package/dist/components/SelectChurchRegister.d.ts.map +1 -1
  24. package/dist/components/SelectChurchRegister.js +11 -5
  25. package/dist/components/SelectChurchRegister.js.map +1 -1
  26. package/dist/components/SelectChurchSearch.d.ts.map +1 -1
  27. package/dist/components/SelectChurchSearch.js +3 -2
  28. package/dist/components/SelectChurchSearch.js.map +1 -1
  29. package/dist/components/SelectableChurch.js +1 -1
  30. package/dist/components/SelectableChurch.js.map +1 -1
  31. package/dist/helpers/AnalyticsHelper.d.ts.map +1 -1
  32. package/dist/helpers/AnalyticsHelper.js +3 -3
  33. package/dist/helpers/AnalyticsHelper.js.map +1 -1
  34. package/dist/helpers/Locale.d.ts.map +1 -1
  35. package/dist/helpers/Locale.js +9 -11
  36. package/dist/helpers/Locale.js.map +1 -1
  37. package/package.json +98 -57
  38. package/src/LoginPage.tsx +0 -314
  39. package/src/LogoutPage.tsx +0 -43
  40. package/src/components/Forgot.tsx +0 -247
  41. package/src/components/Login.tsx +0 -304
  42. package/src/components/LoginSetPassword.tsx +0 -296
  43. package/src/components/Register.tsx +0 -371
  44. package/src/components/SelectChurchModal.tsx +0 -88
  45. package/src/components/SelectChurchRegister.tsx +0 -88
  46. package/src/components/SelectChurchSearch.tsx +0 -85
  47. package/src/components/SelectableChurch.tsx +0 -114
  48. package/src/helpers/AnalyticsHelper.ts +0 -44
  49. package/src/helpers/Locale.ts +0 -248
  50. package/src/helpers/index.ts +0 -2
  51. package/src/index.ts +0 -11
  52. package/tsconfig.json +0 -30
@@ -1,296 +0,0 @@
1
- "use client";
2
-
3
- import React from "react";
4
- import { IconButton, InputAdornment, TextField, Typography, Card, CardContent, Button } from "@mui/material";
5
- import { Visibility, VisibilityOff } from "@mui/icons-material";
6
- import { LoginResponseInterface, UserInterface } from "@churchapps/helpers";
7
- import { ApiHelper } from "@churchapps/helpers";
8
- import { Locale } from "../helpers";
9
-
10
- interface Props {
11
- appName: string,
12
- appUrl: string,
13
- setErrors: (errors: string[]) => void,
14
- setShowForgot: (showForgot: boolean) => void,
15
- isSubmitting: boolean,
16
- auth: string,
17
- login: (data: any) => void,
18
- }
19
-
20
- export const LoginSetPassword: React.FC<Props> = props => {
21
- const [password, setPassword] = React.useState("");
22
- const [verifyPassword, setVerifyPassword] = React.useState("");
23
- const [user, setUser] = React.useState<UserInterface>(null);
24
- const [showPassword, setShowPassword] = React.useState(false);
25
- const [linkExpired, setLinkExpired] = React.useState(false);
26
-
27
- const validate = () => {
28
- const result = [];
29
- if (!password) result.push(Locale.label("login.validate.password"));
30
- else if (password.length < 8) result.push(Locale.label("login.validate.passwordLength"));
31
- else if (password !== verifyPassword) result.push(Locale.label("login.validate.passwordMatch"));
32
- props.setErrors(result);
33
- return result.length === 0;
34
- }
35
-
36
- const submitChangePassword = () => {
37
- if (linkExpired) {
38
- window.open("/login", "_blank");
39
- } else if (validate()) {
40
- submit();
41
- }
42
- }
43
-
44
- const loadUser = () => {
45
- ApiHelper.postAnonymous("/users/login", { authGuid: props.auth }, "MembershipApi").then((resp: LoginResponseInterface) => {
46
- if (resp.user) setUser(resp.user);
47
- else props.setShowForgot(true);
48
- }).catch(() => {
49
- props.setShowForgot(true);
50
- });
51
- }
52
-
53
- const submit = async () => {
54
- const resp = await ApiHelper.postAnonymous("/users/setPasswordGuid", { authGuid: props.auth, newPassword: password, appName: props.appName, appUrl: props.appUrl }, "MembershipApi");
55
- if (resp.success) props.login({ email: user.email, password });
56
- else props.setShowForgot(true);
57
- }
58
-
59
- React.useEffect(() => {
60
- //Get the timestamp from the URL
61
- const urlParams = new URLSearchParams(window.location.search);
62
- const timestampParam = urlParams.get("timestamp");
63
- if (timestampParam) {
64
- const linkTimestamp = parseInt(timestampParam, 10);
65
- const currentTime = Date.now();
66
-
67
- //Check if the link is expired (2 min)
68
- if (currentTime - linkTimestamp > 600000) {
69
- setLinkExpired(true);
70
- } else {
71
- loadUser();
72
- }
73
- } else {
74
- setLinkExpired(true); //No timestamp means link is invalid
75
- }
76
- }, []);
77
-
78
- return (
79
- <div style={{ minHeight: '100vh', backgroundColor: 'white', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '16px' }}>
80
- <Card sx={{
81
- width: '100%',
82
- maxWidth: { xs: '400px', sm: '500px' },
83
- backgroundColor: 'white',
84
- border: '1px solid #e5e7eb',
85
- boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)'
86
- }}>
87
- <CardContent sx={{ textAlign: 'center', padding: '32px' }}>
88
- <div style={{ marginBottom: '32px' }}>
89
- <img
90
- src="/images/logo-login.png"
91
- alt="Church Logo"
92
- style={{
93
- maxWidth: '100%',
94
- width: 'auto',
95
- height: 'auto',
96
- maxHeight: '80px',
97
- marginBottom: '16px',
98
- objectFit: 'contain'
99
- }}
100
- />
101
- </div>
102
- <Typography
103
- component="h1"
104
- sx={{
105
- fontSize: '24px',
106
- fontWeight: 'bold',
107
- color: '#111827',
108
- marginBottom: '8px'
109
- }}
110
- >
111
- {Locale.label("login.setPassword")}
112
- </Typography>
113
- <Typography
114
- sx={{
115
- color: '#6b7280',
116
- marginBottom: '32px'
117
- }}
118
- >
119
- {linkExpired ? 'Your link has expired' : `Welcome back ${user?.firstName || ''}. Please set your password.`}
120
- </Typography>
121
-
122
- {linkExpired ? (
123
- <div style={{ textAlign: 'center', display: 'flex', flexDirection: 'column', gap: '16px' }}>
124
- <Typography sx={{ color: '#dc2626', marginBottom: '16px' }}>
125
- {Locale.label("login.expiredLink")}
126
- </Typography>
127
- <Button
128
- variant="contained"
129
- fullWidth
130
- onClick={submitChangePassword}
131
- disabled={props.isSubmitting}
132
- sx={{
133
- backgroundColor: 'hsl(218, 85%, 55%)',
134
- color: 'white',
135
- padding: '12px',
136
- textTransform: 'none',
137
- fontSize: '16px',
138
- fontWeight: 500,
139
- borderRadius: '6px',
140
- '&:hover': {
141
- backgroundColor: 'hsl(218, 85%, 50%)'
142
- },
143
- '&:disabled': {
144
- backgroundColor: '#9ca3af'
145
- }
146
- }}
147
- >
148
- {Locale.label("login.requestLink")}
149
- </Button>
150
- </div>
151
- ) : (
152
- <form onSubmit={(e) => { e.preventDefault(); submitChangePassword(); }} style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
153
- {user && (
154
- <Typography sx={{ color: '#6b7280', textAlign: 'center', marginBottom: '8px' }}>
155
- {Locale.label("login.welcomeBack")} {user.firstName}.
156
- </Typography>
157
- )}
158
-
159
- <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
160
- <label htmlFor="new-password" style={{ fontSize: '14px', fontWeight: 500, color: '#374151', textAlign: 'left' }}>
161
- {Locale.label("login.setPassword")}
162
- </label>
163
- <TextField
164
- id="new-password"
165
- name="new-password"
166
- type={showPassword ? "text" : "password"}
167
- placeholder={Locale.label("login.setPassword")}
168
- value={password}
169
- onChange={(e) => { e.preventDefault(); setPassword(e.target.value) }}
170
- required
171
- autoComplete="new-password"
172
- variant="outlined"
173
- fullWidth
174
- InputProps={{
175
- endAdornment: (
176
- <InputAdornment position="end">
177
- <IconButton
178
- aria-label="toggle password visibility"
179
- onClick={() => setShowPassword(!showPassword)}
180
- edge="end"
181
- sx={{ color: '#6b7280' }}
182
- >
183
- {showPassword ? <VisibilityOff /> : <Visibility />}
184
- </IconButton>
185
- </InputAdornment>
186
- )
187
- }}
188
- sx={{
189
- '& .MuiOutlinedInput-root': {
190
- backgroundColor: 'white',
191
- paddingRight: '10px',
192
- '& fieldset': {
193
- borderColor: '#d1d5db'
194
- },
195
- '&:hover fieldset': {
196
- borderColor: '#d1d5db'
197
- },
198
- '&.Mui-focused fieldset': {
199
- borderColor: '#3b82f6'
200
- },
201
- '& input': {
202
- color: '#111827',
203
- fontSize: '16px'
204
- }
205
- },
206
- '& .MuiInputLabel-root': {
207
- display: 'none'
208
- }
209
- }}
210
- />
211
- </div>
212
-
213
- <div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
214
- <label htmlFor="verify-password" style={{ fontSize: '14px', fontWeight: 500, color: '#374151', textAlign: 'left' }}>
215
- {Locale.label("login.verifyPassword")}
216
- </label>
217
- <TextField
218
- id="verify-password"
219
- name="verify-password"
220
- type={showPassword ? "text" : "password"}
221
- placeholder={Locale.label("login.verifyPassword")}
222
- value={verifyPassword}
223
- onChange={(e) => { e.preventDefault(); setVerifyPassword(e.target.value) }}
224
- required
225
- autoComplete="new-password"
226
- variant="outlined"
227
- fullWidth
228
- InputProps={{
229
- endAdornment: (
230
- <InputAdornment position="end">
231
- <IconButton
232
- aria-label="toggle password visibility"
233
- onClick={() => setShowPassword(!showPassword)}
234
- edge="end"
235
- sx={{ color: '#6b7280' }}
236
- >
237
- {showPassword ? <VisibilityOff /> : <Visibility />}
238
- </IconButton>
239
- </InputAdornment>
240
- )
241
- }}
242
- sx={{
243
- '& .MuiOutlinedInput-root': {
244
- backgroundColor: 'white',
245
- paddingRight: '10px',
246
- '& fieldset': {
247
- borderColor: '#d1d5db'
248
- },
249
- '&:hover fieldset': {
250
- borderColor: '#d1d5db'
251
- },
252
- '&.Mui-focused fieldset': {
253
- borderColor: '#3b82f6'
254
- },
255
- '& input': {
256
- color: '#111827',
257
- fontSize: '16px'
258
- }
259
- },
260
- '& .MuiInputLabel-root': {
261
- display: 'none'
262
- }
263
- }}
264
- />
265
- </div>
266
-
267
- <Button
268
- type="submit"
269
- variant="contained"
270
- fullWidth
271
- disabled={props.isSubmitting || !user}
272
- sx={{
273
- backgroundColor: 'hsl(218, 85%, 55%)',
274
- color: 'white',
275
- padding: '12px',
276
- textTransform: 'none',
277
- fontSize: '16px',
278
- fontWeight: 500,
279
- borderRadius: '6px',
280
- '&:hover': {
281
- backgroundColor: 'hsl(218, 85%, 50%)'
282
- },
283
- '&:disabled': {
284
- backgroundColor: '#9ca3af'
285
- }
286
- }}
287
- >
288
- {(props.isSubmitting || !user) ? Locale.label("common.pleaseWait") : Locale.label("login.signIn")}
289
- </Button>
290
- </form>
291
- )}
292
- </CardContent>
293
- </Card>
294
- </div>
295
- );
296
- }
@@ -1,371 +0,0 @@
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) => (
197
- <div key={error} 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
- };