Skip to content

Commit a80792d

Browse files
committed
migrated sign in sign up
1 parent 4f47841 commit a80792d

5 files changed

Lines changed: 822 additions & 0 deletions

File tree

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,328 @@
1+
'use client';
2+
3+
import * as React from 'react';
4+
import Button from '@mui/material/Button';
5+
import CssBaseline from '@mui/material/CssBaseline';
6+
import TextField from '@mui/material/TextField';
7+
import Link from '@mui/material/Link';
8+
import Box from '@mui/material/Box';
9+
import Typography from '@mui/material/Typography';
10+
import Container from '@mui/material/Container';
11+
import GoogleIcon from '@mui/icons-material/Google';
12+
import GitHubIcon from '@mui/icons-material/GitHub';
13+
import AppleIcon from '@mui/icons-material/Apple';
14+
import { useRouter, useSearchParams } from 'next/navigation';
15+
import { useAppDispatch } from '../../hooks';
16+
import {
17+
login,
18+
loginFail,
19+
loginWithProvider,
20+
verifyEmail,
21+
} from '../../store/profile-reducer';
22+
import {
23+
OauthProvider,
24+
type EmailLogin,
25+
ProfileErrorSource,
26+
oathProviders,
27+
} from '../../types';
28+
import { useSelector } from 'react-redux';
29+
import { useFormik } from 'formik';
30+
import * as Yup from 'yup';
31+
import {
32+
Alert,
33+
Divider,
34+
IconButton,
35+
InputAdornment,
36+
Snackbar,
37+
Tooltip,
38+
useTheme,
39+
} from '@mui/material';
40+
import {
41+
selectEmailLoginError,
42+
selectUserProfileStatus,
43+
} from '../../store/selectors';
44+
import { getAuth, signInWithPopup, type UserCredential } from 'firebase/auth';
45+
import {
46+
ACCOUNT_TARGET,
47+
ADD_FEED_TARGET,
48+
COMPLETE_REGISTRATION_TARGET,
49+
POST_REGISTRATION_TARGET,
50+
} from '../../constants/Navigation';
51+
import { VisibilityOffOutlined, VisibilityOutlined } from '@mui/icons-material';
52+
53+
export default function SignIn(): React.ReactElement {
54+
const dispatch = useAppDispatch();
55+
const router = useRouter();
56+
const theme = useTheme();
57+
const userProfileStatus = useSelector(selectUserProfileStatus);
58+
const emailLoginError = useSelector(selectEmailLoginError);
59+
const [isSubmitted, setIsSubmitted] = React.useState(false);
60+
const [showPassword, setShowPassword] = React.useState(false);
61+
const [showNoEmailSnackbar, setShowNoEmailSnackbar] = React.useState(false);
62+
const searchParams = useSearchParams();
63+
64+
const SignInSchema = Yup.object().shape({
65+
email: Yup.string()
66+
.email('Email format is invalid.')
67+
.required('Email is required'),
68+
69+
password: Yup.string()
70+
.required('Password is required')
71+
.min(
72+
12,
73+
'Password is too short. Your password should be 12 characters minimum',
74+
),
75+
});
76+
77+
const formik = useFormik({
78+
initialValues: {
79+
email: '',
80+
password: '',
81+
},
82+
validationSchema: SignInSchema,
83+
validateOnChange: isSubmitted,
84+
validateOnBlur: true,
85+
onSubmit: (values) => {
86+
console.log('Submitting form with values:', values);
87+
const emailLogin: EmailLogin = {
88+
email: values.email,
89+
password: values.password,
90+
};
91+
dispatch(login(emailLogin));
92+
},
93+
});
94+
95+
React.useEffect(() => {
96+
if (userProfileStatus === 'registered') {
97+
if (searchParams.has('add_feed')) {
98+
router.push(ADD_FEED_TARGET);
99+
} else {
100+
router.push(ACCOUNT_TARGET);
101+
}
102+
}
103+
if (userProfileStatus === 'authenticated') {
104+
router.push(COMPLETE_REGISTRATION_TARGET + '?' + searchParams.toString());
105+
}
106+
if (userProfileStatus === 'unverified') {
107+
router.push(POST_REGISTRATION_TARGET + '?' + searchParams.toString());
108+
}
109+
}, [userProfileStatus, router, searchParams]);
110+
111+
const signInWithProvider = (oauthProvider: OauthProvider): void => {
112+
const auth = getAuth();
113+
const provider = oathProviders[oauthProvider];
114+
signInWithPopup(auth, provider)
115+
.then((userCredential: UserCredential) => {
116+
if (!userCredential.user.emailVerified) {
117+
dispatch(verifyEmail());
118+
}
119+
if (userCredential.user.email == null) {
120+
setShowNoEmailSnackbar(true);
121+
} else {
122+
dispatch(loginWithProvider({ oauthProvider, userCredential }));
123+
}
124+
})
125+
.catch((error) => {
126+
dispatch(
127+
loginFail({
128+
code: error.code,
129+
message: error.message,
130+
source: ProfileErrorSource.Login,
131+
}),
132+
);
133+
});
134+
};
135+
136+
return (
137+
<Container component='main' maxWidth='xs'>
138+
<Snackbar
139+
open={showNoEmailSnackbar}
140+
anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
141+
onClose={() => {
142+
setShowNoEmailSnackbar(false);
143+
}}
144+
>
145+
<Alert
146+
severity='error'
147+
onClose={() => {
148+
setShowNoEmailSnackbar(false);
149+
}}
150+
>
151+
No public email provided in Github account. Please use a different
152+
login method.
153+
</Alert>
154+
</Snackbar>
155+
<CssBaseline />
156+
<Box
157+
sx={{
158+
display: 'flex',
159+
flexDirection: 'column',
160+
}}
161+
>
162+
<Typography
163+
component='h1'
164+
variant='h5'
165+
color='primary'
166+
fontWeight='bold'
167+
>
168+
API Login
169+
</Typography>
170+
<Typography component='h5'>
171+
Don&apos;t have an account?{' '}
172+
<Link href='/sign-up' color={'inherit'} fontWeight='bold'>
173+
Register Here
174+
</Link>
175+
.
176+
</Typography>
177+
<Box
178+
component='form'
179+
onSubmit={formik.handleSubmit}
180+
noValidate
181+
sx={{
182+
mt: 1,
183+
display: 'flex',
184+
flexDirection: 'column',
185+
alignItems: 'center',
186+
}}
187+
>
188+
<TextField
189+
margin='normal'
190+
required
191+
fullWidth
192+
id='email'
193+
label='Email Address'
194+
name='email'
195+
autoComplete='email'
196+
autoFocus
197+
onChange={formik.handleChange}
198+
value={formik.values.email}
199+
error={formik.errors.email != null}
200+
data-cy='signInEmailInput'
201+
/>
202+
{formik.errors.email != null ? (
203+
<Alert severity='error'>{formik.errors.email}</Alert>
204+
) : null}
205+
<TextField
206+
margin='normal'
207+
required
208+
fullWidth
209+
name='password'
210+
label='Password'
211+
type={showPassword ? 'text' : 'password'}
212+
id='password'
213+
autoComplete='new-password'
214+
onChange={formik.handleChange}
215+
value={formik.values.password}
216+
error={formik.errors.password != null}
217+
data-cy='signInPasswordInput'
218+
InputProps={{
219+
endAdornment: (
220+
<InputAdornment position='end'>
221+
<Tooltip title='Toggle Password Visibility'>
222+
<IconButton
223+
color='primary'
224+
aria-label='toggle password visibility'
225+
onClick={() => {
226+
setShowPassword(!showPassword);
227+
}}
228+
>
229+
{showPassword ? (
230+
<VisibilityOutlined fontSize='small' />
231+
) : (
232+
<VisibilityOffOutlined fontSize='small' />
233+
)}
234+
</IconButton>
235+
</Tooltip>
236+
</InputAdornment>
237+
),
238+
}}
239+
/>
240+
{formik.errors.password != null ? (
241+
<Alert severity='error'>{formik.errors.password}</Alert>
242+
) : null}
243+
{/* TODO: Add remember me functionality
244+
<FormControlLabel
245+
control={<Checkbox value='remember' color='primary' />}
246+
label='Remember me'
247+
sx={{ width: '100%' }}
248+
/> */}
249+
<Typography component='h5' sx={{ textAlign: 'left', width: '100%' }}>
250+
Forgot your password?{' '}
251+
<Link href='/forgot-password' color={'inherit'} fontWeight='bold'>
252+
Reset Here
253+
</Link>
254+
.
255+
</Typography>
256+
<Button
257+
type='submit'
258+
variant='contained'
259+
sx={{ mt: 3, mb: 2 }}
260+
onClick={() => {
261+
setIsSubmitted(true);
262+
}}
263+
data-testid='signin'
264+
>
265+
Sign In
266+
</Button>
267+
{emailLoginError != null ? (
268+
<Alert severity='error'>{emailLoginError.message}</Alert>
269+
) : null}
270+
</Box>
271+
<Box
272+
sx={{
273+
display: 'flex',
274+
flexDirection: 'column',
275+
alignItems: 'center',
276+
mb: 2,
277+
}}
278+
>
279+
<Typography
280+
component='h5'
281+
sx={{
282+
zIndex: 1,
283+
backgroundColor: theme.palette.background.default,
284+
px: 2,
285+
}}
286+
>
287+
OR
288+
</Typography>
289+
<Divider sx={{ width: '100%', mb: 2, mt: '-12px' }} />
290+
</Box>
291+
292+
<Button
293+
variant='outlined'
294+
color='primary'
295+
sx={{ mb: 2 }}
296+
startIcon={<GoogleIcon />}
297+
onClick={() => {
298+
signInWithProvider(OauthProvider.Google);
299+
}}
300+
>
301+
Sign In With Google
302+
</Button>
303+
<Button
304+
variant='outlined'
305+
color='primary'
306+
sx={{ mb: 2 }}
307+
startIcon={<GitHubIcon />}
308+
onClick={() => {
309+
signInWithProvider(OauthProvider.Github);
310+
}}
311+
>
312+
Sign In With Github
313+
</Button>
314+
<Button
315+
variant='outlined'
316+
color='primary'
317+
sx={{ mb: 2 }}
318+
startIcon={<AppleIcon />}
319+
onClick={() => {
320+
signInWithProvider(OauthProvider.Apple);
321+
}}
322+
>
323+
Sign in With Apple
324+
</Button>
325+
</Box>
326+
</Container>
327+
);
328+
}

src/app/[locale]/sign-in/page.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { type ReactElement } from 'react';
2+
import SignIn from './SignIn';
3+
import { ReduxGateWrapper } from '../../components/ReduxGateWrapper';
4+
5+
export default function SignInPage(): ReactElement {
6+
return (
7+
<ReduxGateWrapper>
8+
<SignIn />
9+
</ReduxGateWrapper>
10+
);
11+
}

0 commit comments

Comments
 (0)