Skip to content

Commit 5a471e5

Browse files
committed
implement reset password, auth page styling, and add tomateto logo
1 parent 5d8585b commit 5a471e5

14 files changed

+8896
-6884
lines changed

package-lock.json

+7,916-6,851
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -59,5 +59,8 @@
5959
"last 1 firefox version",
6060
"last 1 safari version"
6161
]
62+
},
63+
"devDependencies": {
64+
"@svgr/webpack": "^6.3.0"
6265
}
6366
}
+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import { Stack, Alert, TextField, Button, Typography } from "@mui/material"
2+
import { confirmPasswordReset, sendPasswordResetEmail, verifyPasswordResetCode } from "firebase/auth";
3+
import { useEffect, useState } from "react";
4+
import { auth } from "../../firebase";
5+
import { firebaseErrorHandling } from "../../features/utility";
6+
import { useAppDispatch } from "../../app/hooks";
7+
import { openSnackbarInfo } from "../../features/app/snackbarSlice";
8+
import { LoadingButton } from "@mui/lab";
9+
import { useSearchParams } from "react-router-dom";
10+
11+
const ForgotPassword = () => {
12+
const [isLoading, setLoading] = useState(false);
13+
const [emailInput, setEmailInput] = useState('');
14+
const [errorText, setErrorText] = useState('');
15+
16+
const dispatch = useAppDispatch();
17+
18+
const handleSendLink = () => {
19+
setLoading(true);
20+
21+
sendPasswordResetEmail(auth, emailInput)
22+
.then(() => {
23+
dispatch(openSnackbarInfo("Recovery link sent"));
24+
setErrorText('');
25+
setLoading(false);
26+
})
27+
.catch((err) => {
28+
setErrorText(firebaseErrorHandling(err));
29+
setLoading(false);
30+
});
31+
}
32+
33+
return(
34+
<Stack width="100%" height="100%" justifyContent="space-between">
35+
<Stack width="100%" spacing={3}>
36+
<Typography>Enter your email address and we'll send you a link to get back into your account.</Typography>
37+
{ errorText && <Alert severity="error">{errorText}</Alert> }
38+
<TextField id="email-input" label="Email address" onChange={ (e) => {setEmailInput(e.target.value)} } />
39+
</Stack>
40+
<LoadingButton loading={isLoading} disabled={ emailInput ? false : true } sx={{ width: '100%', height: '45px' }} onClick={handleSendLink} variant="contained">Send Link</LoadingButton>
41+
</Stack>
42+
)
43+
}
44+
45+
const ResetPassword = () => {
46+
const [isLoading, setLoading] = useState(false);
47+
const [valid, setValid] = useState(true);
48+
49+
const [newPasswordInput, setNewPasswordInput] = useState('');
50+
const [confirmPasswordInput, setConfirmPasswordInput] = useState('');
51+
const [errorText, setErrorText] = useState('');
52+
53+
const [searchParams] = useSearchParams();
54+
const dispatch = useAppDispatch();
55+
56+
const actionCode = searchParams.get('oobCode');
57+
58+
useEffect(() => {
59+
if(actionCode){
60+
verifyPasswordResetCode(auth, actionCode)
61+
.then((res) => {
62+
setValid(true);
63+
setErrorText('');
64+
})
65+
.catch((err) => {
66+
setErrorText(firebaseErrorHandling(err));
67+
setValid(false);
68+
setLoading(false);
69+
})
70+
}
71+
else{
72+
setErrorText('Invalid request');
73+
setValid(false);
74+
setLoading(false);
75+
}
76+
}, [])
77+
78+
const handleResetPassword = () => {
79+
setLoading(true);
80+
81+
if(actionCode){
82+
confirmPasswordReset(auth, actionCode, newPasswordInput)
83+
.then((res) => {
84+
dispatch(openSnackbarInfo("Password saved"));
85+
setErrorText('');
86+
setLoading(false);
87+
})
88+
.catch((err) => {
89+
setErrorText(firebaseErrorHandling(err));
90+
setLoading(false);
91+
})
92+
}
93+
}
94+
95+
return(
96+
valid ?
97+
<Stack width="100%" height="100%" justifyContent="space-between">
98+
<Stack width="100%" spacing={3}>
99+
{ errorText && <Alert severity="error">{errorText}</Alert> }
100+
<TextField id="newpassword-input" label="New password" value={newPasswordInput} type="password" onChange={ (e) => {setNewPasswordInput(e.target.value)} } />
101+
<TextField id="confirmpassword-input" label="Confirm new password" value={confirmPasswordInput} type="password" onChange={ (e) => {setConfirmPasswordInput(e.target.value)} } />
102+
{ confirmPasswordInput && <Alert severity={ newPasswordInput != confirmPasswordInput ? "warning" : "success" }>{ newPasswordInput != confirmPasswordInput ? "Password does not match" : "Password matched" }</Alert> }
103+
</Stack>
104+
<LoadingButton loading={isLoading} sx={{ width: '100%', height: '45px' }} onClick={handleResetPassword} variant="contained" disabled={ !newPasswordInput || newPasswordInput != confirmPasswordInput ? true : false }>Save</LoadingButton>
105+
</Stack> :
106+
<Alert severity="error">{errorText}</Alert>
107+
)
108+
}
109+
110+
export { ForgotPassword, ResetPassword };

src/components/accounts/Login.tsx

+27-10
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,19 @@
1-
import { Alert, Box, Button, Stack, TextField, Typography } from "@mui/material";
1+
import { Alert, Box, Button, Paper, Stack, styled, TextField, Typography, useMediaQuery, useTheme } from "@mui/material";
22
import { Link, useLocation, useNavigate } from "react-router-dom";
33

4-
import { createUserWithEmailAndPassword, signInWithEmailAndPassword } from "firebase/auth";
4+
import { signInWithEmailAndPassword } from "firebase/auth";
55
import { useEffect, useState } from "react";
66
import { auth } from "../../firebase";
77
import { firebaseErrorHandling } from "../../features/utility";
88

9+
const LinkTypography = styled(Typography)(({ theme }) => ({
10+
color: theme.palette.primary.main,
11+
textDecoration: 'none',
12+
'&:hover': {
13+
textDecoration: 'underline'
14+
}
15+
})) as typeof Typography;
16+
917
const Login = () => {
1018
const [emailInput, setEmailInput] = useState('');
1119
const [passwordInput, setPasswordInput] = useState('');
@@ -37,14 +45,23 @@ const Login = () => {
3745
}, [isLogin]);
3846

3947
return(
40-
<Stack alignItems="center" justifyContent="center" spacing={3} sx={{ height: "100%" }}>
41-
{ errorText && <Alert severity="error">{errorText}</Alert> }
42-
{ location.state && !errorText && <Alert severity="info">Log in to continue</Alert> }
43-
<TextField id="email-input" label="Email" onChange={ (e) => {setEmailInput(e.target.value)} } />
44-
<TextField id="password-input" label="Password" type="password" onChange={ (e) => {setPasswordInput(e.target.value)} } />
45-
<Link to="/accounts/password-reset">Forgot password?</Link>
46-
<Button onClick={() => {setLogin(true)}} variant="contained">Log in</Button>
47-
<Typography>Don't have an account? <Link to="/accounts/signup">Sign up</Link></Typography>
48+
<Stack width="100%" height="100%" justifyContent="space-between">
49+
<Stack width="100%" spacing={3}>
50+
{ errorText && <Alert severity="error">{errorText}</Alert> }
51+
{ location.state && !errorText && <Alert severity="info">Log in to continue</Alert> }
52+
<TextField id="email-input" label="Email" onChange={ (e) => {setEmailInput(e.target.value)} } />
53+
<Stack width="100%" spacing={1}>
54+
<TextField id="password-input" label="Password" type="password" onChange={ (e) => {setPasswordInput(e.target.value)} } />
55+
<LinkTypography component={Link} to="/accounts/recover" sx={{ fontSize: '14px' }}>Forgot password?</LinkTypography>
56+
</Stack>
57+
</Stack>
58+
<Stack width="100%" spacing={6}>
59+
<Button sx={{ width: '100%', height: '45px' }} onClick={() => {setLogin(true)}} variant="contained">Log in</Button>
60+
<Stack direction="row" spacing={1}>
61+
<Typography>Don't have an account?</Typography>
62+
<LinkTypography component={Link} to="/accounts/signup">Sign up</LinkTypography>
63+
</Stack>
64+
</Stack>
4865
</Stack>
4966
)
5067
}

src/components/accounts/Signup.tsx

+24-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Stack, TextField, Button, Alert } from "@mui/material";
1+
import { Stack, TextField, Button, Alert, Typography, styled } from "@mui/material";
22
import { createUserWithEmailAndPassword, deleteUser, getIdToken } from "firebase/auth";
33
import { useEffect, useState } from "react";
44
import { Link, useNavigate } from "react-router-dom";
@@ -11,6 +11,14 @@ interface User {
1111
username: string;
1212
};
1313

14+
const LinkTypography = styled(Typography)(({ theme }) => ({
15+
color: theme.palette.primary.main,
16+
textDecoration: 'none',
17+
'&:hover': {
18+
textDecoration: 'underline'
19+
}
20+
})) as typeof Typography;
21+
1422
const Signup = () => {
1523
const [nameInput, setNameInput] = useState('');
1624
const [emailInput, setEmailInput] = useState('');
@@ -76,13 +84,21 @@ const Signup = () => {
7684
}, [isSignup])
7785

7886
return(
79-
<Stack alignItems="center" justifyContent="center" spacing={3} sx={{ height: "100%" }}>
80-
{ errorText && <Alert severity="error">{errorText}</Alert> }
81-
<TextField id="name-input" label="Name" onChange={ (e) => {setNameInput(e.target.value)} } />
82-
<TextField id="email-input" label="Email" onChange={ (e) => {setEmailInput(e.target.value)} } />
83-
<TextField id="username-input" label="Username" onChange={ (e) => {setUsernameInput(e.target.value)} } />
84-
<TextField id="password-input" label="Password" type="password" onChange={ (e) => {setPasswordInput(e.target.value)} } />
85-
<Button onClick={() => {setSignup(true)}} variant="contained">Sign Up</Button>
87+
<Stack width="100%" height="100%" justifyContent="space-between">
88+
<Stack spacing={3} width="100%">
89+
{ errorText && <Alert severity="error">{errorText}</Alert> }
90+
<TextField id="name-input" label="Name" onChange={ (e) => {setNameInput(e.target.value)} } />
91+
<TextField id="email-input" label="Email" onChange={ (e) => {setEmailInput(e.target.value)} } />
92+
<TextField id="username-input" label="Username" onChange={ (e) => {setUsernameInput(e.target.value)} } />
93+
<TextField id="password-input" label="Password" type="password" onChange={ (e) => {setPasswordInput(e.target.value)} } />
94+
</Stack>
95+
<Stack width="100%" spacing={6}>
96+
<Button sx={{ width: '100%', height: '45px' }} onClick={() => {setSignup(true)}} variant="contained">Sign Up</Button>
97+
<Stack direction="row" spacing={1}>
98+
<Typography>Have an account?</Typography>
99+
<LinkTypography component={Link} to="/accounts/login">Log in</LinkTypography>
100+
</Stack>
101+
</Stack>
86102
</Stack>
87103
)
88104
}

src/components/accounts/index.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1+
import { ForgotPassword, ResetPassword } from "./ForgotPassword";
12
import Login from "./Login";
23
import Signup from "./Signup";
34

4-
export { Login, Signup }
5+
export { Login, Signup, ForgotPassword, ResetPassword }

src/components/page/PageAppBar.tsx

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { AppBar, Box, Typography, InputBase, Stack, IconButton, Button, Menu, MenuItem, Popper, Popover, Grow, ClickAwayListener, MenuList, Paper, useMediaQuery } from "@mui/material";
1+
import { AppBar, Box, Typography, InputBase, Stack, SvgIcon, IconButton, Button, Menu, MenuItem, Popper, Popover, Grow, ClickAwayListener, MenuList, Paper, useMediaQuery } from "@mui/material";
22
import { styled, alpha, useTheme } from '@mui/material/styles';
33
import { Container, shadows } from '@mui/system';
44

55
import AddBoxOutlinedIcon from '@mui/icons-material/AddBoxOutlined';
6+
import { ReactComponent as TomatetoLightLogo } from "../../logos/tomatetolight-logo.svg";
67

78
import { useEffect, useRef, useState } from "react";
89
import { auth } from "../../firebase";
@@ -51,7 +52,7 @@ const PageAppBar = () => {
5152
<StyledAppBar elevation={ smUp ? 1 : 0 }>
5253
<Container maxWidth="md">
5354
<Stack direction="row" alignItems="center" justifyContent={ "space-between" }>
54-
{ !smUp && location.pathname != '/' ? <></> : <Logo>tomateto</Logo> }
55+
{ !smUp && location.pathname != '/' ? <></> : <SvgIcon sx={{ width: '160px', height: '60px' }} component={TomatetoLightLogo} inheritViewBox /> }
5556
{
5657
smUp &&
5758
<>{

src/components/post/NewPost.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ const NewPost = () => {
141141

142142
function handlePostSuccess(){
143143
dispatch(insertPost(newPost));
144-
dispatch(openSnackbarInfo("Your post was sent"))
144+
dispatch(openSnackbarInfo("Your post was sent"));
145145
console.log(newPost)
146146

147147
setContent("");

src/features/utility/firebaseErrorHandling.ts

+3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ const firebaseErrorHandling = (err: any) => {
2323
else if(err.code == 'auth/email-already-in-use'){
2424
return "The provided email is already in use by an existing user.";
2525
}
26+
else if(err.code == 'auth/invalid-action-code' || err.code == 'auth/expired-action-code'){
27+
return "Your request to reset your password has expired or the link has already been used.";
28+
}
2629
else{
2730
console.log(err);
2831
return "Sorry, something went wrong. Please try again soon."

src/index.css

+4
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,8 @@ em-emoji-picker {
6161
em-emoji-picker {
6262
width: 100vw;
6363
}
64+
}
65+
66+
svg {
67+
fill: currentColor;
6468
}

0 commit comments

Comments
 (0)