Separate Client Portal & Dashboard
This commit is contained in:
38
frontend/dashboard/src/sections/auth/AuthFirebaseSocial.tsx
Normal file
38
frontend/dashboard/src/sections/auth/AuthFirebaseSocial.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
// @mui
|
||||
import { Grid, Button, Divider, Typography } from '@mui/material';
|
||||
// components
|
||||
import Iconify from '../../components/Iconify';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
export default function AuthFirebaseSocial() {
|
||||
return (
|
||||
<>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs>
|
||||
<Button fullWidth size="large" color="inherit" variant="outlined">
|
||||
<Iconify icon={'eva:google-fill'} color="#DF3E30" width={24} height={24} />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs>
|
||||
<Button fullWidth size="large" color="inherit" variant="outlined">
|
||||
<Iconify icon={'eva:facebook-fill'} color="#1877F2" width={24} height={24} />
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs>
|
||||
<Button fullWidth size="large" color="inherit" variant="outlined">
|
||||
<Iconify icon={'eva:twitter-fill'} color="#1C9CEA" width={24} height={24} />
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
<Divider sx={{ my: 3 }}>
|
||||
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
||||
OR
|
||||
</Typography>
|
||||
</Divider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
115
frontend/dashboard/src/sections/auth/login/LoginForm.tsx
Normal file
115
frontend/dashboard/src/sections/auth/login/LoginForm.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import * as Yup from 'yup';
|
||||
import { useState } from 'react';
|
||||
import { Link as RouterLink, useNavigate } from 'react-router-dom';
|
||||
// form
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
// @mui
|
||||
import { Link, Stack, Alert, IconButton, InputAdornment } from '@mui/material';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
// routes
|
||||
import { PATH_AUTH } from '../../../routes/paths';
|
||||
// hooks
|
||||
import useAuth from '../../../hooks/useAuth';
|
||||
import useIsMountedRef from '../../../hooks/useIsMountedRef';
|
||||
// components
|
||||
import Iconify from '../../../components/Iconify';
|
||||
import { FormProvider, RHFTextField, RHFCheckbox } from '../../../components/hook-form';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type FormValuesProps = {
|
||||
email: string;
|
||||
password: string;
|
||||
remember: boolean;
|
||||
afterSubmit?: string;
|
||||
};
|
||||
|
||||
export default function LoginForm() {
|
||||
const { login } = useAuth();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const isMountedRef = useIsMountedRef();
|
||||
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
const LoginSchema = Yup.object().shape({
|
||||
email: Yup.string().email('Email must be a valid email address').required('Email is required'),
|
||||
password: Yup.string().required('Password is required'),
|
||||
});
|
||||
|
||||
const defaultValues = {
|
||||
email: '',
|
||||
password: '',
|
||||
remember: true,
|
||||
};
|
||||
|
||||
const methods = useForm<FormValuesProps>({
|
||||
resolver: yupResolver(LoginSchema),
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const {
|
||||
reset,
|
||||
setError,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
} = methods;
|
||||
|
||||
const onSubmit = async (data: FormValuesProps) => {
|
||||
try {
|
||||
await login(data.email, data.password );
|
||||
navigate('/dashboard/one');
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
|
||||
reset();
|
||||
|
||||
if (isMountedRef.current) {
|
||||
setError('afterSubmit', { ...error, message: error.message });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
|
||||
<Stack spacing={3}>
|
||||
{!!errors.afterSubmit && <Alert severity="error">{errors.afterSubmit.message}</Alert>}
|
||||
|
||||
<RHFTextField name="email" label="Email address" />
|
||||
|
||||
<RHFTextField
|
||||
name="password"
|
||||
label="Password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton onClick={() => setShowPassword(!showPassword)} edge="end">
|
||||
<Iconify icon={showPassword ? 'eva:eye-fill' : 'eva:eye-off-fill'} />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
</Stack>
|
||||
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between" sx={{ my: 2 }}>
|
||||
<RHFCheckbox name="remember" label="Remember me" />
|
||||
<Link component={RouterLink} variant="subtitle2" to={PATH_AUTH.resetPassword}>
|
||||
Forgot password?
|
||||
</Link>
|
||||
</Stack>
|
||||
|
||||
<LoadingButton
|
||||
fullWidth
|
||||
size="large"
|
||||
type="submit"
|
||||
variant="contained"
|
||||
loading={isSubmitting}
|
||||
>
|
||||
Login
|
||||
</LoadingButton>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
1
frontend/dashboard/src/sections/auth/login/index.ts
Normal file
1
frontend/dashboard/src/sections/auth/login/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as LoginForm } from './LoginForm';
|
||||
110
frontend/dashboard/src/sections/auth/register/RegisterForm.tsx
Normal file
110
frontend/dashboard/src/sections/auth/register/RegisterForm.tsx
Normal file
@@ -0,0 +1,110 @@
|
||||
import * as Yup from 'yup';
|
||||
import { useState } from 'react';
|
||||
// form
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
// @mui
|
||||
import { Stack, IconButton, InputAdornment, Alert } from '@mui/material';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
// hooks
|
||||
import useAuth from '../../../hooks/useAuth';
|
||||
import useIsMountedRef from '../../../hooks/useIsMountedRef';
|
||||
// components
|
||||
import Iconify from '../../../components/Iconify';
|
||||
import { FormProvider, RHFTextField } from '../../../components/hook-form';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type FormValuesProps = {
|
||||
email: string;
|
||||
password: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
afterSubmit?: string;
|
||||
};
|
||||
|
||||
export default function RegisterForm() {
|
||||
const { register } = useAuth();
|
||||
|
||||
const isMountedRef = useIsMountedRef();
|
||||
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
|
||||
const RegisterSchema = Yup.object().shape({
|
||||
firstName: Yup.string().required('First name required'),
|
||||
lastName: Yup.string().required('Last name required'),
|
||||
email: Yup.string().email('Email must be a valid email address').required('Email is required'),
|
||||
password: Yup.string().required('Password is required'),
|
||||
});
|
||||
|
||||
const defaultValues = {
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
email: '',
|
||||
password: '',
|
||||
};
|
||||
|
||||
const methods = useForm<FormValuesProps>({
|
||||
resolver: yupResolver(RegisterSchema),
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const {
|
||||
reset,
|
||||
setError,
|
||||
handleSubmit,
|
||||
formState: { errors, isSubmitting },
|
||||
} = methods;
|
||||
|
||||
const onSubmit = async (data: FormValuesProps) => {
|
||||
try {
|
||||
await register(data.email, data.password, data.firstName, data.lastName);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
reset();
|
||||
if (isMountedRef.current) {
|
||||
setError('afterSubmit', { ...error, message: error.message });
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
|
||||
<Stack spacing={3}>
|
||||
{!!errors.afterSubmit && <Alert severity="error">{errors.afterSubmit.message}</Alert>}
|
||||
|
||||
<Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>
|
||||
<RHFTextField name="firstName" label="First name" />
|
||||
<RHFTextField name="lastName" label="Last name" />
|
||||
</Stack>
|
||||
|
||||
<RHFTextField name="email" label="Email address" />
|
||||
|
||||
<RHFTextField
|
||||
name="password"
|
||||
label="Password"
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
InputProps={{
|
||||
endAdornment: (
|
||||
<InputAdornment position="end">
|
||||
<IconButton edge="end" onClick={() => setShowPassword(!showPassword)}>
|
||||
<Iconify icon={showPassword ? 'eva:eye-fill' : 'eva:eye-off-fill'} />
|
||||
</IconButton>
|
||||
</InputAdornment>
|
||||
),
|
||||
}}
|
||||
/>
|
||||
|
||||
<LoadingButton
|
||||
fullWidth
|
||||
size="large"
|
||||
type="submit"
|
||||
variant="contained"
|
||||
loading={isSubmitting}
|
||||
>
|
||||
Register
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
1
frontend/dashboard/src/sections/auth/register/index.ts
Normal file
1
frontend/dashboard/src/sections/auth/register/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as RegisterForm } from './RegisterForm';
|
||||
@@ -0,0 +1,70 @@
|
||||
import * as Yup from 'yup';
|
||||
// form
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
import { useForm } from 'react-hook-form';
|
||||
// @mui
|
||||
import { Stack } from '@mui/material';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
// hooks
|
||||
import useIsMountedRef from '../../../hooks/useIsMountedRef';
|
||||
// components
|
||||
import { FormProvider, RHFTextField } from '../../../components/hook-form';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type FormValuesProps = {
|
||||
email: string;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
onSent: VoidFunction;
|
||||
onGetEmail: (value: string) => void;
|
||||
};
|
||||
|
||||
export default function ResetPasswordForm({ onSent, onGetEmail }: Props) {
|
||||
const isMountedRef = useIsMountedRef();
|
||||
|
||||
const ResetPasswordSchema = Yup.object().shape({
|
||||
email: Yup.string().email('Email must be a valid email address').required('Email is required'),
|
||||
});
|
||||
|
||||
const methods = useForm<FormValuesProps>({
|
||||
resolver: yupResolver(ResetPasswordSchema),
|
||||
defaultValues: { email: 'demo@minimals.cc' },
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
formState: { isSubmitting },
|
||||
} = methods;
|
||||
|
||||
const onSubmit = async (data: FormValuesProps) => {
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
if (isMountedRef.current) {
|
||||
onSent();
|
||||
onGetEmail(data.email);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
|
||||
<Stack spacing={3}>
|
||||
<RHFTextField name="email" label="Email address" />
|
||||
|
||||
<LoadingButton
|
||||
fullWidth
|
||||
size="large"
|
||||
type="submit"
|
||||
variant="contained"
|
||||
loading={isSubmitting}
|
||||
>
|
||||
Reset Password
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
</FormProvider>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as ResetPasswordForm } from './ResetPasswordForm';
|
||||
@@ -0,0 +1,161 @@
|
||||
import * as Yup from 'yup';
|
||||
import { useSnackbar } from 'notistack';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useEffect } from 'react';
|
||||
// form
|
||||
import { useForm, Controller } from 'react-hook-form';
|
||||
import { yupResolver } from '@hookform/resolvers/yup';
|
||||
// @mui
|
||||
import { OutlinedInput, Stack } from '@mui/material';
|
||||
import { LoadingButton } from '@mui/lab';
|
||||
// routes
|
||||
// import { PATH_DASHBOARD } from '../../../routes/paths';
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
type FormValuesProps = {
|
||||
code1: string;
|
||||
code2: string;
|
||||
code3: string;
|
||||
code4: string;
|
||||
code5: string;
|
||||
code6: string;
|
||||
};
|
||||
|
||||
type ValueNames = 'code1' | 'code2' | 'code3' | 'code4' | 'code5' | 'code6';
|
||||
|
||||
export default function VerifyCodeForm() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { enqueueSnackbar } = useSnackbar();
|
||||
|
||||
const VerifyCodeSchema = Yup.object().shape({
|
||||
code1: Yup.string().required('Code is required'),
|
||||
code2: Yup.string().required('Code is required'),
|
||||
code3: Yup.string().required('Code is required'),
|
||||
code4: Yup.string().required('Code is required'),
|
||||
code5: Yup.string().required('Code is required'),
|
||||
code6: Yup.string().required('Code is required'),
|
||||
});
|
||||
|
||||
const defaultValues = {
|
||||
code1: '',
|
||||
code2: '',
|
||||
code3: '',
|
||||
code4: '',
|
||||
code5: '',
|
||||
code6: '',
|
||||
};
|
||||
|
||||
const {
|
||||
watch,
|
||||
control,
|
||||
setValue,
|
||||
handleSubmit,
|
||||
formState: { isSubmitting, isValid },
|
||||
} = useForm({
|
||||
mode: 'onBlur',
|
||||
resolver: yupResolver(VerifyCodeSchema),
|
||||
defaultValues,
|
||||
});
|
||||
|
||||
const values = watch();
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('paste', handlePasteClipboard);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
const onSubmit = async (data: FormValuesProps) => {
|
||||
try {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
console.log('code:', Object.values(data).join(''));
|
||||
|
||||
enqueueSnackbar('Verify success!');
|
||||
|
||||
navigate('/dashboard', { replace: true });
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handlePasteClipboard = (event: ClipboardEvent) => {
|
||||
let data: string | string[] = event?.clipboardData?.getData('Text') || '';
|
||||
|
||||
data = data.split('');
|
||||
|
||||
[].forEach.call(document.querySelectorAll('#field-code'), (node: any, index) => {
|
||||
node.value = data[index];
|
||||
const fieldIndex = `code${index + 1}`;
|
||||
setValue(fieldIndex as ValueNames, data[index]);
|
||||
});
|
||||
};
|
||||
|
||||
const handleChangeWithNextField = (
|
||||
event: React.ChangeEvent<HTMLInputElement>,
|
||||
handleChange: (event: React.ChangeEvent<HTMLInputElement>) => void
|
||||
) => {
|
||||
const { maxLength, value, name } = event.target;
|
||||
const fieldIndex = name.replace('code', '');
|
||||
|
||||
const fieldIntIndex = Number(fieldIndex);
|
||||
|
||||
if (value.length >= maxLength) {
|
||||
if (fieldIntIndex < 6) {
|
||||
const nextfield = document.querySelector(`input[name=code${fieldIntIndex + 1}]`);
|
||||
|
||||
if (nextfield !== null) {
|
||||
(nextfield as HTMLElement).focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handleChange(event);
|
||||
};
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onSubmit)}>
|
||||
<Stack direction="row" spacing={2} justifyContent="center">
|
||||
{Object.keys(values).map((name, index) => (
|
||||
<Controller
|
||||
key={name}
|
||||
name={`code${index + 1}` as ValueNames}
|
||||
control={control}
|
||||
render={({ field }) => (
|
||||
<OutlinedInput
|
||||
{...field}
|
||||
id="field-code"
|
||||
autoFocus={index === 0}
|
||||
placeholder="-"
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
handleChangeWithNextField(event, field.onChange)
|
||||
}
|
||||
inputProps={{
|
||||
maxLength: 1,
|
||||
sx: {
|
||||
p: 0,
|
||||
textAlign: 'center',
|
||||
width: { xs: 36, sm: 56 },
|
||||
height: { xs: 36, sm: 56 },
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
))}
|
||||
</Stack>
|
||||
|
||||
<LoadingButton
|
||||
fullWidth
|
||||
size="large"
|
||||
type="submit"
|
||||
variant="contained"
|
||||
loading={isSubmitting}
|
||||
disabled={!isValid}
|
||||
sx={{ mt: 3 }}
|
||||
>
|
||||
Verify
|
||||
</LoadingButton>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default as VerifyCodeForm } from './VerifyCodeForm';
|
||||
Reference in New Issue
Block a user