aboutsummaryrefslogtreecommitdiffstats
path: root/src/views/Authorization
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/views/Authorization/AuthorizationContainer.jsx45
-rw-r--r--src/views/Authorization/ForgotPasswordView.jsx227
-rw-r--r--src/views/Authorization/LoginView.jsx345
-rw-r--r--src/views/Authorization/Signup.jsx243
-rw-r--r--src/views/Authorization/UpdatePasswordView.jsx194
5 files changed, 1054 insertions, 0 deletions
diff --git a/src/views/Authorization/AuthorizationContainer.jsx b/src/views/Authorization/AuthorizationContainer.jsx
new file mode 100644
index 0000000..3bfc622
--- /dev/null
+++ b/src/views/Authorization/AuthorizationContainer.jsx
@@ -0,0 +1,45 @@
+// import Logo from 'Components/Logo'
+import { Box, Paper } from '@mui/material'
+import { styled } from '@mui/material/styles'
+
+const Container = styled('div')(({ theme }) => ({
+ minHeight: '100vh',
+ padding: '24px',
+ display: 'grid',
+ placeItems: 'start center',
+ [theme.breakpoints.up('sm')]: {
+ // center children
+ placeItems: 'center',
+ },
+}))
+
+const AuthCard = styled(Paper)(({ theme }) => ({
+ // border: "1px solid #c4c4c4",
+ padding: 24,
+ paddingTop: 32,
+ borderRadius: 24,
+ width: '100%',
+ maxWidth: '400px',
+ [theme.breakpoints.down('sm')]: {
+ maxWidth: 'unset',
+ },
+}))
+
+export default function AuthCardContainer({ children, ...props }) {
+ return (
+ <Container>
+ <AuthCard elevation={0}>
+ <Box
+ sx={{
+ display: 'grid',
+ placeItems: 'center',
+ paddingBottom: 4,
+ }}
+ >
+ {/* <Logo size='96px' /> */}
+ </Box>
+ {children}
+ </AuthCard>
+ </Container>
+ )
+}
diff --git a/src/views/Authorization/ForgotPasswordView.jsx b/src/views/Authorization/ForgotPasswordView.jsx
new file mode 100644
index 0000000..44601eb
--- /dev/null
+++ b/src/views/Authorization/ForgotPasswordView.jsx
@@ -0,0 +1,227 @@
+// create boilerplate for ResetPasswordView:
+import {
+ Box,
+ Button,
+ Container,
+ FormControl,
+ FormHelperText,
+ Input,
+ Sheet,
+ Snackbar,
+ Typography,
+} from '@mui/joy'
+import { useState } from 'react'
+import { useNavigate } from 'react-router-dom'
+
+import { API_URL } from './../../Config'
+
+const ForgotPasswordView = () => {
+ const navigate = useNavigate()
+ // const [showLoginSnackbar, setShowLoginSnackbar] = useState(false)
+ // const [snackbarMessage, setSnackbarMessage] = useState('')
+ const [resetStatusOk, setResetStatusOk] = useState(null)
+ const [email, setEmail] = useState('')
+ const [emailError, setEmailError] = useState(null)
+
+ const validateEmail = email => {
+ return !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(email)
+ }
+
+ const handleSubmit = async () => {
+ if (!email) {
+ return setEmailError('Email is required')
+ }
+
+ // validate email:
+ if (validateEmail(email)) {
+ setEmailError('Please enter a valid email address')
+ return
+ }
+
+ if (emailError) {
+ return
+ }
+
+ try {
+ const response = await fetch(`${API_URL}/auth/reset`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ email: email }),
+ })
+
+ if (response.ok) {
+ setResetStatusOk(true)
+ // wait 3 seconds and then redirect to login:
+ } else {
+ setResetStatusOk(false)
+ }
+ } catch (error) {
+ setResetStatusOk(false)
+ }
+ }
+
+ const handleEmailChange = e => {
+ setEmail(e.target.value)
+ if (validateEmail(e.target.value)) {
+ setEmailError('Please enter a valid email address')
+ } else {
+ setEmailError(null)
+ }
+ }
+
+ return (
+ <Container
+ component='main'
+ maxWidth='xs'
+
+ // make content center in the middle of the page:
+ >
+ <Box
+ sx={{
+ marginTop: 4,
+ display: 'flex',
+ flexDirection: 'column',
+
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ }}
+ >
+ <Sheet
+ component='form'
+ sx={{
+ mt: 1,
+ width: '100%',
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ padding: 2,
+ borderRadius: '8px',
+ boxShadow: 'md',
+ minHeight: '70vh',
+ justifyContent: 'space-between',
+ justifyItems: 'center',
+ }}
+ >
+ <Box>
+ <img
+ src='/src/assets/logo.svg'
+ alt='logo'
+ width='128px'
+ height='128px'
+ />
+ {/* <Logo /> */}
+ <Typography level='h2'>
+ Done
+ <span
+ style={{
+ color: '#06b6d4',
+ }}
+ >
+ tick
+ </span>
+ </Typography>
+ </Box>
+ {/* HERE */}
+ <Box sx={{ textAlign: 'center' }}></Box>
+ {resetStatusOk === null && (
+ <form onSubmit={handleSubmit}>
+ <div className='grid gap-6'>
+ <Typography level='body2' gutterBottom>
+ Enter your email, and we'll send you a link to get into your
+ account.
+ </Typography>
+ <FormControl error={emailError !== null}>
+ <Input
+ placeholder='Email'
+ type='email'
+ variant='soft'
+ fullWidth
+ size='lg'
+ value={email}
+ onChange={handleEmailChange}
+ error={emailError !== null}
+ onKeyDown={e => {
+ if (e.key === 'Enter') {
+ e.preventDefault()
+ handleSubmit()
+ }
+ }}
+ />
+ <FormHelperText>{emailError}</FormHelperText>
+ </FormControl>
+ <Box>
+ <Button
+ variant='solid'
+ size='lg'
+ fullWidth
+ sx={{
+ mb: 1,
+ }}
+ onClick={handleSubmit}
+ >
+ Reset Password
+ </Button>
+ <Button
+ fullWidth
+ size='lg'
+ variant='soft'
+ sx={{
+ width: '100%',
+ border: 'moccasin',
+ borderRadius: '8px',
+ }}
+ onClick={() => {
+ navigate('/login')
+ }}
+ color='neutral'
+ >
+ Back to Login
+ </Button>
+ </Box>
+ </div>
+ </form>
+ )}
+ {resetStatusOk != null && (
+ <>
+ <Box mt={-30}>
+ <Typography level='body-md'>
+ if there is an account associated with the email you entered,
+ you will receive an email with instructions on how to reset
+ your
+ </Typography>
+ </Box>
+ <Button
+ variant='soft'
+ size='lg'
+ sx={{ position: 'relative', bottom: '0' }}
+ onClick={() => {
+ navigate('/login')
+ }}
+ fullWidth
+ >
+ Go to Login
+ </Button>
+ </>
+ )}
+ <Snackbar
+ open={resetStatusOk ? resetStatusOk : resetStatusOk === false}
+ autoHideDuration={5000}
+ onClose={() => {
+ if (resetStatusOk) {
+ navigate('/login')
+ }
+ }}
+ >
+ {resetStatusOk
+ ? 'Reset email sent, check your email'
+ : 'Reset email failed, try again later'}
+ </Snackbar>
+ </Sheet>
+ </Box>
+ </Container>
+ )
+}
+
+export default ForgotPasswordView
diff --git a/src/views/Authorization/LoginView.jsx b/src/views/Authorization/LoginView.jsx
new file mode 100644
index 0000000..2ffcef4
--- /dev/null
+++ b/src/views/Authorization/LoginView.jsx
@@ -0,0 +1,345 @@
+import GoogleIcon from '@mui/icons-material/Google'
+import {
+ Avatar,
+ Box,
+ Button,
+ Container,
+ Divider,
+ Input,
+ Sheet,
+ Snackbar,
+ Typography,
+} from '@mui/joy'
+import Cookies from 'js-cookie'
+import React from 'react'
+import { useNavigate } from 'react-router-dom'
+import { LoginSocialGoogle } from 'reactjs-social-login'
+import { API_URL, GOOGLE_CLIENT_ID, REDIRECT_URL } from '../../Config'
+import { UserContext } from '../../contexts/UserContext'
+import Logo from '../../Logo'
+import { GetUserProfile } from '../../utils/Fetcher'
+const LoginView = () => {
+ const { userProfile, setUserProfile } = React.useContext(UserContext)
+ const [username, setUsername] = React.useState('')
+ const [password, setPassword] = React.useState('')
+ const [error, setError] = React.useState(null)
+ const Navigate = useNavigate()
+ const handleSubmit = async e => {
+ e.preventDefault()
+
+ fetch(`${API_URL}/auth/login`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ username, password }),
+ })
+ .then(response => {
+ if (response.status === 200) {
+ return response.json().then(data => {
+ localStorage.setItem('ca_token', data.token)
+ localStorage.setItem('ca_expiration', data.expire)
+ const redirectUrl = Cookies.get('ca_redirect')
+ // console.log('redirectUrl', redirectUrl)
+ if (redirectUrl) {
+ Cookies.remove('ca_redirect')
+ Navigate(redirectUrl)
+ } else {
+ Navigate('/my/chores')
+ }
+ })
+ } else if (response.status === 401) {
+ setError('Wrong username or password')
+ } else {
+ setError('An error occurred, please try again')
+ console.log('Login failed')
+ }
+ })
+ .catch(err => {
+ setError('Unable to communicate with server, please try again')
+ console.log('Login failed', err)
+ })
+ }
+
+ const loggedWithProvider = function (provider, data) {
+ console.log(provider, data)
+ return fetch(API_URL + `/auth/${provider}/callback`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({
+ provider: provider,
+ token:
+ data['access_token'] || // data["access_token"] is for Google
+ data['accessToken'], // data["accessToken"] is for Facebook
+ data: data,
+ }),
+ }).then(response => {
+ if (response.status === 200) {
+ return response.json().then(data => {
+ localStorage.setItem('ca_token', data.token)
+ localStorage.setItem('ca_expiration', data.expire)
+ // setIsLoggedIn(true);
+ getUserProfileAndNavigateToHome()
+ })
+ }
+ return response.json().then(error => {
+ setError("Couldn't log in with Google, please try again")
+ })
+ })
+ }
+ const getUserProfileAndNavigateToHome = () => {
+ GetUserProfile().then(data => {
+ data.json().then(data => {
+ setUserProfile(data.res)
+ // check if redirect url is set in cookie:
+ const redirectUrl = Cookies.get('ca_redirect')
+ if (redirectUrl) {
+ Cookies.remove('ca_redirect')
+ Navigate(redirectUrl)
+ } else {
+ Navigate('/my/chores')
+ }
+ })
+ })
+ }
+ const handleForgotPassword = () => {
+ Navigate('/forgot-password')
+ }
+ return (
+ <Container
+ component='main'
+ maxWidth='xs'
+
+ // make content center in the middle of the page:
+ >
+ <Box
+ sx={{
+ marginTop: 4,
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ }}
+ >
+ <Sheet
+ component='form'
+ sx={{
+ mt: 1,
+ width: '100%',
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ padding: 2,
+ borderRadius: '8px',
+ boxShadow: 'md',
+ }}
+ >
+ {/* <img
+ src='/src/assets/logo.svg'
+ alt='logo'
+ width='128px'
+ height='128px'
+ /> */}
+ <Logo />
+
+ <Typography level='h2'>
+ Done
+ <span
+ style={{
+ color: '#06b6d4',
+ }}
+ >
+ tick
+ </span>
+ </Typography>
+
+ {userProfile && (
+ <>
+ <Avatar
+ src={userProfile?.image}
+ alt={userProfile?.username}
+ size='lg'
+ sx={{
+ mt: 2,
+ width: '96px',
+ height: '96px',
+ mb: 1,
+ }}
+ />
+ <Typography level='body-md' alignSelf={'center'}>
+ Welcome back,{' '}
+ {userProfile?.displayName || userProfile?.username}
+ </Typography>
+
+ <Button
+ fullWidth
+ size='lg'
+ sx={{ mt: 3, mb: 2 }}
+ onClick={() => {
+ getUserProfileAndNavigateToHome()
+ }}
+ >
+ Continue as {userProfile.displayName || userProfile.username}
+ </Button>
+ <Button
+ type='submit'
+ fullWidth
+ size='lg'
+ q
+ variant='plain'
+ sx={{
+ width: '100%',
+ mb: 2,
+ border: 'moccasin',
+ borderRadius: '8px',
+ }}
+ onClick={() => {
+ setUserProfile(null)
+ localStorage.removeItem('ca_token')
+ localStorage.removeItem('ca_expiration')
+ // go to login page:
+ window.location.href = '/login'
+ }}
+ >
+ Logout
+ </Button>
+ </>
+ )}
+ {!userProfile && (
+ <>
+ <Typography level='body2'>
+ Sign in to your account to continue
+ </Typography>
+ <Typography level='body2' alignSelf={'start'} mt={4}>
+ Username
+ </Typography>
+ <Input
+ margin='normal'
+ required
+ fullWidth
+ id='email'
+ label='Email Address'
+ name='email'
+ autoComplete='email'
+ autoFocus
+ value={username}
+ onChange={e => {
+ setUsername(e.target.value)
+ }}
+ />
+ <Typography level='body2' alignSelf={'start'}>
+ Password:
+ </Typography>
+ <Input
+ margin='normal'
+ required
+ fullWidth
+ name='password'
+ label='Password'
+ type='password'
+ id='password'
+ value={password}
+ onChange={e => {
+ setPassword(e.target.value)
+ }}
+ />
+
+ <Button
+ type='submit'
+ fullWidth
+ size='lg'
+ variant='solid'
+ sx={{
+ width: '100%',
+ mt: 3,
+ mb: 2,
+ border: 'moccasin',
+ borderRadius: '8px',
+ }}
+ onClick={handleSubmit}
+ >
+ Sign In
+ </Button>
+ <Button
+ type='submit'
+ fullWidth
+ size='lg'
+ q
+ variant='plain'
+ sx={{
+ width: '100%',
+ mb: 2,
+ border: 'moccasin',
+ borderRadius: '8px',
+ }}
+ onClick={handleForgotPassword}
+ >
+ Forgot password?
+ </Button>
+ </>
+ )}
+ <Divider> or </Divider>
+
+ <Box sx={{ width: '100%' }}>
+ <LoginSocialGoogle
+ client_id={GOOGLE_CLIENT_ID}
+ redirect_uri={REDIRECT_URL}
+ scope='openid profile email'
+ discoveryDocs='claims_supported'
+ access_type='online'
+ isOnlyGetToken={true}
+ onResolve={({ provider, data }) => {
+ loggedWithProvider(provider, data)
+ }}
+ onReject={err => {
+ setError("Couldn't log in with Google, please try again")
+ }}
+ >
+ <Button
+ variant='soft'
+ color='neutral'
+ size='lg'
+ fullWidth
+ sx={{
+ width: '100%',
+ mt: 1,
+ mb: 1,
+ border: 'moccasin',
+ borderRadius: '8px',
+ }}
+ >
+ <div className='flex gap-2'>
+ <GoogleIcon />
+ Continue with Google
+ </div>
+ </Button>
+ </LoginSocialGoogle>
+ </Box>
+
+ <Button
+ onClick={() => {
+ Navigate('/signup')
+ }}
+ fullWidth
+ variant='soft'
+ size='lg'
+ // sx={{ mt: 3, mb: 2 }}
+ >
+ Create new account
+ </Button>
+ </Sheet>
+ </Box>
+ <Snackbar
+ open={error !== null}
+ onClose={() => setError(null)}
+ autoHideDuration={3000}
+ message={error}
+ >
+ {error}
+ </Snackbar>
+ </Container>
+ )
+}
+
+export default LoginView
diff --git a/src/views/Authorization/Signup.jsx b/src/views/Authorization/Signup.jsx
new file mode 100644
index 0000000..d83411f
--- /dev/null
+++ b/src/views/Authorization/Signup.jsx
@@ -0,0 +1,243 @@
+import {
+ Box,
+ Button,
+ Container,
+ Divider,
+ FormControl,
+ FormHelperText,
+ Input,
+ Sheet,
+ Typography,
+} from '@mui/joy'
+import React from 'react'
+import { useNavigate } from 'react-router-dom'
+import Logo from '../../Logo'
+import { login, signUp } from '../../utils/Fetcher'
+
+const SignupView = () => {
+ const [username, setUsername] = React.useState('')
+ const [password, setPassword] = React.useState('')
+ const Navigate = useNavigate()
+ const [displayName, setDisplayName] = React.useState('')
+ const [email, setEmail] = React.useState('')
+ const [usernameError, setUsernameError] = React.useState('')
+ const [passwordError, setPasswordError] = React.useState('')
+ const [emailError, setEmailError] = React.useState('')
+ const [displayNameError, setDisplayNameError] = React.useState('')
+ const [error, setError] = React.useState(null)
+ const handleLogin = (username, password) => {
+ login(username, password).then(response => {
+ if (response.status === 200) {
+ response.json().then(res => {
+ localStorage.setItem('ca_token', res.token)
+ localStorage.setItem('ca_expiration', res.expire)
+ setTimeout(() => {
+ // TODO: not sure if there is a race condition here
+ // but on first sign up it renavigates to login.
+ Navigate('/my/chores')
+ }, 500)
+ })
+ } else {
+ console.log('Login failed', response)
+ // Navigate('/login')
+ }
+ })
+ }
+ const handleSignUpValidation = () => {
+ // Reset errors before validation
+ setUsernameError(null)
+ setPasswordError(null)
+ setDisplayNameError(null)
+ setEmailError(null)
+
+ let isValid = true
+
+ if (!username.trim()) {
+ setUsernameError('Username is required')
+ isValid = false
+ }
+ if (username.length < 4) {
+ setUsernameError('Username must be at least 4 characters')
+ isValid = false
+ }
+ // if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
+ // setEmailError('Invalid email address')
+ // isValid = false
+ // }
+
+ if (password.length < 8) {
+ setPasswordError('Password must be at least 8 characters')
+ isValid = false
+ }
+
+ if (!displayName.trim()) {
+ setDisplayNameError('Display name is required')
+ isValid = false
+ }
+
+ // display name should only contain letters and spaces and numbers:
+ if (!/^[a-zA-Z0-9 ]+$/.test(displayName)) {
+ setDisplayNameError('Display name can only contain letters and numbers')
+ isValid = false
+ }
+
+ // username should only contain letters , numbers , dot and dash:
+ if (!/^[a-zA-Z0-9.-]+$/.test(username)) {
+ setUsernameError(
+ 'Username can only contain letters, numbers, dot and dash',
+ )
+ isValid = false
+ }
+
+ return isValid
+ }
+ const handleSubmit = async e => {
+ e.preventDefault()
+ if (!handleSignUpValidation()) {
+ return
+ }
+ signUp(username, password, displayName, email).then(response => {
+ if (response.status === 201) {
+ handleLogin(username, password)
+ } else {
+ console.log('Signup failed')
+ setError('Signup failed')
+ }
+ })
+ }
+
+ return (
+ <Container component='main' maxWidth='xs'>
+ <Box
+ sx={{
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ marginTop: 4,
+ }}
+ >
+ <Sheet
+ component='form'
+ sx={{
+ mt: 1,
+ width: '100%',
+ display: 'flex',
+ flexDirection: 'column',
+ // alignItems: 'center',
+ padding: 2,
+ borderRadius: '8px',
+ boxShadow: 'md',
+ }}
+ >
+ <Box
+ sx={{
+ display: 'flex',
+ alignItems: 'center',
+ flexDirection: 'column',
+ }}
+ >
+ <Logo />
+ <Typography level='h2'>
+ Done
+ <span
+ style={{
+ color: '#06b6d4',
+ }}
+ >
+ tick
+ </span>
+ </Typography>
+ <Typography level='body2'>
+ Create an account to get started!
+ </Typography>
+ </Box>
+ <Typography level='body2' alignSelf={'start'} mt={4}>
+ Username
+ </Typography>
+ <Input
+ margin='normal'
+ required
+ fullWidth
+ id='email'
+ label='Email Address'
+ name='email'
+ autoComplete='email'
+ autoFocus
+ value={username}
+ onChange={e => {
+ setUsernameError(null)
+ setUsername(e.target.value.trim())
+ }}
+ />
+ <FormControl error={usernameError}>
+ <FormHelperText c>{usernameError}</FormHelperText>
+ </FormControl>
+ {/* Error message display */}
+ <Typography level='body2' alignSelf={'start'}>
+ Password:
+ </Typography>
+ <Input
+ margin='normal'
+ required
+ fullWidth
+ name='password'
+ label='Password'
+ type='password'
+ id='password'
+ value={password}
+ onChange={e => {
+ setPasswordError(null)
+ setPassword(e.target.value)
+ }}
+ />
+ <FormControl error={passwordError}>
+ <FormHelperText>{passwordError}</FormHelperText>
+ </FormControl>
+ <Typography level='body2' alignSelf={'start'}>
+ Display Name:
+ </Typography>
+ <Input
+ margin='normal'
+ required
+ fullWidth
+ name='displayName'
+ label='Display Name'
+ id='displayName'
+ value={displayName}
+ onChange={e => {
+ setDisplayNameError(null)
+ setDisplayName(e.target.value)
+ }}
+ />
+ <FormControl error={displayNameError}>
+ <FormHelperText>{displayNameError}</FormHelperText>
+ </FormControl>
+ <Button
+ // type='submit'
+ size='lg'
+ fullWidth
+ variant='solid'
+ sx={{ mt: 3, mb: 1 }}
+ onClick={handleSubmit}
+ >
+ Sign Up
+ </Button>
+ <Divider> or </Divider>
+ <Button
+ size='lg'
+ onClick={() => {
+ Navigate('/login')
+ }}
+ fullWidth
+ variant='soft'
+ // sx={{ mt: 3, mb: 2 }}
+ >
+ Login
+ </Button>
+ </Sheet>
+ </Box>
+ </Container>
+ )
+}
+
+export default SignupView
diff --git a/src/views/Authorization/UpdatePasswordView.jsx b/src/views/Authorization/UpdatePasswordView.jsx
new file mode 100644
index 0000000..7177f2f
--- /dev/null
+++ b/src/views/Authorization/UpdatePasswordView.jsx
@@ -0,0 +1,194 @@
+// create boilerplate for ResetPasswordView:
+import {
+ Box,
+ Button,
+ Container,
+ FormControl,
+ FormHelperText,
+ Input,
+ Sheet,
+ Snackbar,
+ Typography,
+} from '@mui/joy'
+import { useState } from 'react'
+import { useNavigate, useSearchParams } from 'react-router-dom'
+
+import { API_URL } from '../../Config'
+import Logo from '../../Logo'
+
+const UpdatePasswordView = () => {
+ const navigate = useNavigate()
+ const [password, setPassword] = useState('')
+ const [passwordConfirm, setPasswordConfirm] = useState('')
+ const [passwordError, setPasswordError] = useState(null)
+ const [passworConfirmationError, setPasswordConfirmationError] =
+ useState(null)
+ const [searchParams] = useSearchParams()
+
+ const [updateStatusOk, setUpdateStatusOk] = useState(null)
+
+ const verifiticationCode = searchParams.get('c')
+
+ const handlePasswordChange = e => {
+ const password = e.target.value
+ setPassword(password)
+ if (password.length < 8) {
+ setPasswordError('Password must be at least 8 characters')
+ } else {
+ setPasswordError(null)
+ }
+ }
+ const handlePasswordConfirmChange = e => {
+ setPasswordConfirm(e.target.value)
+ if (e.target.value !== password) {
+ setPasswordConfirmationError('Passwords do not match')
+ } else {
+ setPasswordConfirmationError(null)
+ }
+ }
+
+ const handleSubmit = async () => {
+ if (passwordError != null || passworConfirmationError != null) {
+ return
+ }
+ try {
+ const response = await fetch(
+ `${API_URL}/auth/password?c=${verifiticationCode}`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify({ password: password }),
+ },
+ )
+
+ if (response.ok) {
+ setUpdateStatusOk(true)
+ // wait 3 seconds and then redirect to login:
+ setTimeout(() => {
+ navigate('/login')
+ }, 3000)
+ } else {
+ setUpdateStatusOk(false)
+ }
+ } catch (error) {
+ setUpdateStatusOk(false)
+ }
+ }
+ return (
+ <Container component='main' maxWidth='xs'>
+ <Box
+ sx={{
+ display: 'flex',
+ flexDirection: 'column',
+ alignItems: 'center',
+ marginTop: 4,
+ }}
+ >
+ <Sheet
+ component='form'
+ sx={{
+ mt: 1,
+ width: '100%',
+ display: 'flex',
+ flexDirection: 'column',
+ // alignItems: 'center',
+ padding: 2,
+ borderRadius: '8px',
+ boxShadow: 'md',
+ }}
+ >
+ <Box
+ sx={{
+ display: 'flex',
+ alignItems: 'center',
+ flexDirection: 'column',
+ }}
+ >
+ <Logo />
+ <Typography level='h2'>
+ Done
+ <span
+ style={{
+ color: '#06b6d4',
+ }}
+ >
+ tick
+ </span>
+ </Typography>
+ <Typography level='body2' mb={4}>
+ Please enter your new password below
+ </Typography>
+ </Box>
+
+ <FormControl error>
+ <Input
+ placeholder='Password'
+ type='password'
+ value={password}
+ onChange={handlePasswordChange}
+ error={passwordError !== null}
+ // onKeyDown={e => {
+ // if (e.key === 'Enter' && validateForm(validateFormInput)) {
+ // handleSubmit(e)
+ // }
+ // }}
+ />
+ <FormHelperText>{passwordError}</FormHelperText>
+ </FormControl>
+
+ <FormControl error>
+ <Input
+ placeholder='Confirm Password'
+ type='password'
+ value={passwordConfirm}
+ onChange={handlePasswordConfirmChange}
+ error={passworConfirmationError !== null}
+ // onKeyDown={e => {
+ // if (e.key === 'Enter' && validateForm(validateFormInput)) {
+ // handleSubmit(e)
+ // }
+ // }}
+ />
+ <FormHelperText>{passworConfirmationError}</FormHelperText>
+ </FormControl>
+ {/* helper to show password not matching : */}
+
+ <Button
+ fullWidth
+ size='lg'
+ sx={{
+ mt: 5,
+ mb: 1,
+ }}
+ onClick={handleSubmit}
+ >
+ Save Password
+ </Button>
+ <Button
+ fullWidth
+ size='lg'
+ variant='soft'
+ onClick={() => {
+ navigate('/login')
+ }}
+ >
+ Cancel
+ </Button>
+ </Sheet>
+ </Box>
+ <Snackbar
+ open={updateStatusOk !== true}
+ autoHideDuration={6000}
+ onClose={() => {
+ setUpdateStatusOk(null)
+ }}
+ >
+ Password update failed, try again later
+ </Snackbar>
+ </Container>
+ )
+}
+
+export default UpdatePasswordView