login validate

This commit is contained in:
Muhammad Fajar
2022-11-14 13:50:10 +07:00
parent 20944e4992
commit 6eb225026a
21 changed files with 633 additions and 714 deletions

View File

@@ -2,45 +2,97 @@
namespace Modules\Client\Http\Controllers\Api; namespace Modules\Client\Http\Controllers\Api;
use App\Helpers\Helper;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Models\User; use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash; use Symfony\Component\HttpFoundation\Response;
class AuthController extends Controller class AuthController extends Controller
{ {
public function login(Request $request) public function login(Request $request)
{ {
$request->validate([ $request->validate([
'email' => 'required|email', 'phoneOrEmail' => 'required'
'password' => 'required'
]); ]);
$user = User::query() $user = User::query()
->where('email', $request->email) ->when(filter_var($request->phoneOrEmail, FILTER_VALIDATE_EMAIL), function (Builder $query) use ($request) {
->first(); $query->getQuery()->where('email', $request->phoneOrEmail);
}, function (Builder $query) use ($request) {
$query->getQuery()->where('phone', $request->phoneOrEmail);
})
->first();
if (!$user) { if (!$user) {
return response(['message' => 'User Tidak Ditemukan'], 404); $message = filter_var($request->phoneOrEmail, FILTER_VALIDATE_EMAIL) ? "User dengan alamat email " . $request->phoneOrEmail . " tidak ditemukan" : "User dengan nomor telepon " . $request->phoneOrEmail . " tidak ditemukan";
return Helper::responseJson(statusCode: Response::HTTP_NOT_FOUND, message: $message);
} }
if (!Hash::check($request->password, $user->password)) { if (filter_var($request->phoneOrEmail, FILTER_VALIDATE_EMAIL)) {
return response(['message' => 'Password Salah'], 403); User::query()->find($user->id)->update([
'email' => $request->phoneOrEmail,
'otp' => rand(1000, 9999),
'otp_created_at' => now()
]);
} else {
User::query()->find($user->id)->update([
'phone' => $request->phoneOrEmail,
'otp' => rand(1000, 9999),
'otp_created_at' => now()
]);
} }
return response([ // TODO Send the OTP
'message' => 'Selamat Datang', if (filter_var($request->phoneOrEmail, FILTER_VALIDATE_EMAIL)) {
'user' => $user, // Send Email
'token' => $user->createToken('app')->plainTextToken } else {
// Send Whatsapp
}
return Helper::responseJson(message: 'OTP Terkirim');
}
public function validateOtp(Request $request)
{
echo '<pre>';
var_dump($_POST, $_GET);
echo '</pre>';
exit;
$request->validate([
'phoneOrEmail' => 'required',
'otp' => 'required'
]); ]);
$user = User::query()
->when(filter_var($request->phoneOrEmail, FILTER_VALIDATE_EMAIL), function (Builder $query) use ($request) {
$query->getQuery()->where('email', $request->phoneOrEmail);
}, function (Builder $query) use ($request) {
$query->getQuery()->where('phone', $request->phoneOrEmail);
})
->first();
if ($user->otp == $request->otp) {
return Helper::responseJson(
data: [
'token' => $user->createToken('app')->plainTextToken,
'user' => $user,
],
message: 'Selamat Datang'
);
}
return Helper::responseJson(message: 'OTP yang anda masukan salah!');
} }
public function logout(Request $request) public function logout(Request $request)
{ {
$token = $request->bearerToken(); $request->user()->currentAccessToken()->delete();
Auth::user()->tokens()->where('id', $token)->delete();
return response(['message' => 'Berhasil Logout.']); return Helper::responseJson(message: 'Berhasil Logout.');
} }
} }

View File

@@ -19,9 +19,10 @@ use Modules\Client\Http\Controllers\Api\UserController;
Route::prefix('client')->group(function () { Route::prefix('client')->group(function () {
Route::post('login', [AuthController::class, 'login'])->name('login'); Route::controller(AuthController::class)->group(function () {
Route::post('forget-password', [AuthController::class, 'forgetPassword'])->name('forget-password'); Route::post('login', 'login');
Route::post('verify-email', [AuthController::class, 'verifyEmail'])->name('verify-email'); Route::post('verify-code', 'validateOtp');
});
Route::middleware('auth:sanctum')->group(function () { Route::middleware('auth:sanctum')->group(function () {
@@ -30,7 +31,5 @@ Route::prefix('client')->group(function () {
Route::get('dashboard', [DashboardController::class, 'index']); Route::get('dashboard', [DashboardController::class, 'index']);
Route::get('members', [MemberController::class, 'index']); Route::get('members', [MemberController::class, 'index']);
}); });
}); });

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 MiB

View File

@@ -1,4 +1,4 @@
import { UserCredential } from 'firebase/auth'; // import { UserCredential } from 'firebase/auth';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -26,20 +26,20 @@ export type JWTContextType = {
isInitialized: boolean; isInitialized: boolean;
user: AuthUser; user: AuthUser;
method: 'jwt'; method: 'jwt';
login: (email: string, password: string) => Promise<void>; login: (phoneOrEmail: string) => Promise<void>;
register: (email: string, password: string, firstName: string, lastName: string) => Promise<void>; validateOtp: (phoneOrEmail: string, otp: string) => Promise<void>
logout: () => Promise<void>; logout: () => Promise<void>;
}; };
export type FirebaseContextType = { // export type FirebaseContextType = {
isAuthenticated: boolean; // isAuthenticated: boolean;
isInitialized: boolean; // isInitialized: boolean;
user: AuthUser; // user: AuthUser;
method: 'firebase'; // method: 'firebase';
login: (email: string, password: string) => Promise<UserCredential>; // login: (email: string, password: string) => Promise<UserCredential>;
register: (email: string, password: string, firstName: string, lastName: string) => Promise<void>; // register: (email: string, password: string, firstName: string, lastName: string) => Promise<void>;
logout: () => Promise<void>; // logout: () => Promise<void>;
}; // };
export type AWSCognitoContextType = { export type AWSCognitoContextType = {
isAuthenticated: boolean; isAuthenticated: boolean;

View File

@@ -29,7 +29,7 @@ export type NavItemProps = {
export interface NavSectionProps extends BoxProps { export interface NavSectionProps extends BoxProps {
isCollapse?: boolean; isCollapse?: boolean;
navConfig: { navConfig: {
subheader: string; subheader?: string;
items: NavListProps[]; items: NavListProps[];
}[]; }[];
} }

View File

@@ -1,18 +1,16 @@
import { createContext, ReactNode, useEffect, useReducer } from 'react'; import { createContext, ReactNode, useEffect, useReducer } from 'react';
// utils // utils
import axios from '../utils/axios'; import axios from '../utils/axios';
// import { isValidToken, setSession } from '../utils/jwt'; import { setSession, getSession } from '../utils/token';
import { setSession, getSession, getUser } from '../utils/token';
// @types // @types
import { ActionMap, AuthState, AuthUser, JWTContextType } from '../@types/auth'; import { ActionMap, AuthState, AuthUser, JWTContextType } from '../@types/auth';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
import { Navigate, useLocation } from 'react-router-dom';
enum Types { enum Types {
Initial = 'INITIALIZE', Initial = 'INITIALIZE',
Login = 'LOGIN', Login = 'LOGIN',
ValidateOtp = 'VALIDATE-OTP',
Logout = 'LOGOUT', Logout = 'LOGOUT',
Register = 'REGISTER',
} }
type JWTAuthPayload = { type JWTAuthPayload = {
@@ -20,13 +18,11 @@ type JWTAuthPayload = {
isAuthenticated: boolean; isAuthenticated: boolean;
user: AuthUser; user: AuthUser;
}; };
[Types.Login]: { [Types.Login]: undefined;
[Types.ValidateOtp]: {
user: AuthUser; user: AuthUser;
}; };
[Types.Logout]: undefined; [Types.Logout]: undefined;
[Types.Register]: {
user: AuthUser;
};
}; };
export type JWTActions = ActionMap<JWTAuthPayload>[keyof ActionMap<JWTAuthPayload>]; export type JWTActions = ActionMap<JWTAuthPayload>[keyof ActionMap<JWTAuthPayload>];
@@ -46,6 +42,12 @@ const JWTReducer = (state: AuthState, action: JWTActions) => {
user: action.payload.user, user: action.payload.user,
}; };
case 'LOGIN': case 'LOGIN':
return {
...state,
isAuthenticated: false,
user: null,
};
case 'VALIDATE-OTP':
return { return {
...state, ...state,
isAuthenticated: true, isAuthenticated: true,
@@ -57,14 +59,6 @@ const JWTReducer = (state: AuthState, action: JWTActions) => {
isAuthenticated: false, isAuthenticated: false,
user: null, user: null,
}; };
case 'REGISTER':
return {
...state,
isAuthenticated: true,
user: action.payload.user,
};
default: default:
return state; return state;
} }
@@ -80,15 +74,15 @@ type AuthProviderProps = {
function AuthProvider({ children }: AuthProviderProps) { function AuthProvider({ children }: AuthProviderProps) {
const [state, dispatch] = useReducer(JWTReducer, initialState); const [state, dispatch] = useReducer(JWTReducer, initialState);
let location = useLocation();
useEffect(() => { useEffect(() => {
const initialize = async () => { (async () => {
console.log('initialize', state) console.log('initialize', state);
try { try {
const accessToken = getSession(); const accessToken = getSession();
console.log('') console.log('');
if (accessToken) { if (accessToken) {
setSession(accessToken); setSession(accessToken);
@@ -120,61 +114,55 @@ function AuthProvider({ children }: AuthProviderProps) {
}, },
}); });
} }
}; })();
initialize();
}, []); }, []);
const login = async (email: string, password: string) => { const login = async (phoneOrEmail: string) =>
return axios axios
.post('/login', { email, password }) .post('/login', { phoneOrEmail })
.then((response) => { .then(() => {
const { user, token } = response.data; dispatch({
setSession(token); type: Types.Login,
});
})
.catch((error) => {
if (error.response.status !== 404) throw error.response;
if (error.response.status !== 422) throw error.response;
});
dispatch({ const validateOtp = async (phoneOrEmail: string, otp: string) =>
type: Types.Login, axios
payload: { .post('/verify-code', { otp })
user, .then((response) => {
} const { user, token } = response.data.data;
}); setSession(token);
})
.catch(error => {
if (error.response.status !== 404) throw error.response
if (error.response.status !== 422) throw error.response
})
};
const register = async (email: string, password: string, firstName: string, lastName: string) => { dispatch({
const response = await axios.post('/api/register', { type: Types.ValidateOtp,
email, payload: {
password, user,
firstName, },
lastName, });
}); })
const { accessToken, user } = response.data; .catch((error) => {
if (error.response.status !== 404) throw error.response;
window.localStorage.setItem('accessToken', accessToken); if (error.response.status !== 422) throw error.response;
dispatch({ });
type: Types.Register,
payload: {
user,
},
});
};
const logout = async () => { const logout = async () => {
await axios.post('/logout');
setSession(null); setSession(null);
dispatch({ type: Types.Logout }); dispatch({ type: Types.Logout });
}; };
return (<AuthContext.Provider return (
<AuthContext.Provider
value={{ value={{
...state, ...state,
method: 'jwt', method: 'jwt',
login, login,
validateOtp,
logout, logout,
register,
}} }}
> >
{children} {children}

View File

@@ -18,9 +18,7 @@ const navConfig = [
// GENERAL // GENERAL
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
{ {
items: [ items: [{ title: 'Dashboard', path: '/dashboard', icon: ICONS.dashboard }],
{ title: 'Dashboard', path: '/dashboard', icon: ICONS.dashboard },
],
}, },
// Membership // Membership

View File

@@ -79,8 +79,8 @@ export default function NavbarVertical({ isOpenSidebar, onCloseSidebar }: Props)
<Typography ml={3}>PRIME CENTER</Typography> <Typography ml={3}>PRIME CENTER</Typography>
</Stack> </Stack>
<CollapseButton onToggleCollapse={onToggleCollapse} collapseClick={collapseClick} /> <CollapseButton onToggleCollapse={onToggleCollapse} collapseClick={collapseClick} />
</Stack>) </Stack>
: ( ) : (
<Stack direction="row" alignItems="center" justifyContent="space-between"> <Stack direction="row" alignItems="center" justifyContent="space-between">
<Logo /> <Logo />
</Stack> </Stack>

View File

@@ -0,0 +1,34 @@
// mui
import { Container, Grid, Card } from '@mui/material';
// components
import Page from '../../components/Page';
// utils
import useSettings from '../../hooks/useSettings';
// sections
// import ListTable from '../../sections/claimreports/ListTable';
// import ClaimStatusCard from '../../sections/claimreports/ClaimStatusCard';
import List from '../Members/List';
export default function Drugs() {
const { themeStretch } = useSettings();
// const { corporate_id } = useParams();
return (
<Page title="Claim Reports">
<Container maxWidth={themeStretch ? false : 'xl'}>
<Grid container spacing={2}>
<Grid item xs={12} lg={12} md={12}>
{/* <ClaimStatusCard /> */}
</Grid>
<Grid item xs={12} lg={12} md={12}>
<Card>
<List />
</Card>
</Grid>
</Grid>
</Container>
</Page>
);
}

View File

@@ -1,15 +1,14 @@
import * as Yup from 'yup'; import * as Yup from 'yup';
import { yupResolver } from "@hookform/resolvers/yup"; import { yupResolver } from '@hookform/resolvers/yup';
import { Card, Collapse, Divider, Grid, Stack, Typography } from "@mui/material"; import { Card, Collapse, Divider, Grid, Stack, Typography } from '@mui/material';
import { useForm } from "react-hook-form"; import { useForm } from 'react-hook-form';
import { useParams } from "react-router-dom"; import { useParams } from 'react-router-dom';
import HeaderBreadcrumbs from "../../../components/HeaderBreadcrumbs"; import HeaderBreadcrumbs from '../../components/HeaderBreadcrumbs';
import { FormProvider, RHFCheckbox, RHFSelect, RHFTextField } from "../../../components/hook-form"; import { FormProvider, RHFCheckbox, RHFSelect, RHFTextField } from '../../components/hook-form';
import Page from "../../../components/Page"; import Page from '../../components/Page';
import useSettings from "../../../hooks/useSettings"; import useSettings from '../../hooks/useSettings';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import Form from "./Form"; import Form from './Form';
export default function Divisions() { export default function Divisions() {
const { themeStretch } = useSettings(); const { themeStretch } = useSettings();
@@ -48,13 +47,12 @@ export default function Divisions() {
} = methods; } = methods;
const onSubmit = async (data: any) => { const onSubmit = async (data: any) => {
console.log(data) console.log(data);
}; };
const pageTitle = 'Create Formularium'; const pageTitle = 'Create Formularium';
return ( return (
<Page title={pageTitle}> <Page title={pageTitle}>
<HeaderBreadcrumbs <HeaderBreadcrumbs
heading={pageTitle} heading={pageTitle}
links={[ links={[
@@ -73,11 +71,14 @@ export default function Divisions() {
]} ]}
/> />
<Grid container spacing={2}> <Grid container spacing={2}>
<Grid item xs={12}> <Grid item xs={12}>
<Card sx={{ p: 2 }}> <Card sx={{ p: 2 }}>
<Form isSubmitting={isSubmitting} isEdit={isEdit} currentFormularium={currentFormularium} /> <Form
isSubmitting={isSubmitting}
isEdit={isEdit}
currentFormularium={currentFormularium}
/>
</Card> </Card>
</Grid> </Grid>
</Grid> </Grid>

View File

@@ -33,10 +33,10 @@ import {
RHFMultiCheckbox, RHFMultiCheckbox,
RHFCheckbox, RHFCheckbox,
RHFCustomMultiCheckbox, RHFCustomMultiCheckbox,
} from '../../../components/hook-form'; } from '../../components/hook-form';
import { Corporate } from '../../../@types/corporates'; import { Corporate } from '../../@types/corporates';
import axios from '../../../utils/axios'; import axios from '../../utils/axios';
import { fCurrency } from '../../../utils/formatNumber'; import { fCurrency } from '../../utils/formatNumber';
const LabelStyle = styled(Typography)(({ theme }) => ({ const LabelStyle = styled(Typography)(({ theme }) => ({
...theme.typography.subtitle2, ...theme.typography.subtitle2,
@@ -110,10 +110,16 @@ export default function FormulariumForm({ isEdit, currentFormularium }: Props) {
if (!isEdit) { if (!isEdit) {
const response = await axios.post('/master/formulariums', data); const response = await axios.post('/master/formulariums', data);
} else { } else {
const response = await axios.put('/master/formulariums/' + currentFormularium?.id ?? '', data); const response = await axios.put(
'/master/formulariums/' + currentFormularium?.id ?? '',
data
);
} }
reset(); reset();
enqueueSnackbar(!isEdit ? 'Formularium Created Successfully!' : 'Formularium Udpated Successfully!', { variant: 'success' }); enqueueSnackbar(
!isEdit ? 'Formularium Created Successfully!' : 'Formularium Udpated Successfully!',
{ variant: 'success' }
);
navigate('/master/formularium'); navigate('/master/formularium');
} catch (error: any) { } catch (error: any) {
if (error && error.response.status === 422) { if (error && error.response.status === 422) {
@@ -121,15 +127,14 @@ export default function FormulariumForm({ isEdit, currentFormularium }: Props) {
setError(key, { message: value[0] }); setError(key, { message: value[0] });
enqueueSnackbar(value[0] ?? 'Failed Processing Request', { variant: 'error' }); enqueueSnackbar(value[0] ?? 'Failed Processing Request', { variant: 'error' });
} }
} } else {
else {
enqueueSnackbar(error.message ?? 'Failed Processing Request', { variant: 'error' }); enqueueSnackbar(error.message ?? 'Failed Processing Request', { variant: 'error' });
} }
} }
const ascent = document?.querySelector("ascent"); const ascent = document?.querySelector('ascent');
if (ascent != null) { if (ascent != null) {
ascent.innerHTML = ""; ascent.innerHTML = '';
} }
}; };
@@ -151,34 +156,33 @@ export default function FormulariumForm({ isEdit, currentFormularium }: Props) {
setValue('logo', null); setValue('logo', null);
}; };
const linking_rules_checkbox_name = "linking_rules" const linking_rules_checkbox_name = 'linking_rules';
const linking_tools = [ const linking_tools = [
{ {
"value" : "nrik", value: 'nrik',
"label" : "No. KTP" label: 'No. KTP',
}, },
{ {
"value" : "nik", value: 'nik',
"label" : "Nomor Induk Karyawan (NIK)" label: 'Nomor Induk Karyawan (NIK)',
}, },
{ {
"value" : "member_id", value: 'member_id',
"label" : "Member ID" label: 'Member ID',
}, },
{ {
"value" : "phone", value: 'phone',
"label" : "Nomor Telepon" label: 'Nomor Telepon',
}, },
{ {
"value" : "email", value: 'email',
"label" : "E-Mail" label: 'E-Mail',
}, },
] ];
const importForm = useRef<HTMLInputElement>(null);
const importForm = useRef<HTMLInputElement>(null)
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null); const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const [currentImportFileName, setCurrentImportFileName] = useState<string|null>(null); const [currentImportFileName, setCurrentImportFileName] = useState<string | null>(null);
const handleClose = () => { const handleClose = () => {
setAnchorEl(null); setAnchorEl(null);
@@ -189,51 +193,73 @@ export default function FormulariumForm({ isEdit, currentFormularium }: Props) {
handleClose(); handleClose();
importForm.current ? importForm.current.click() : console.log('No File selected'); importForm.current ? importForm.current.click() : console.log('No File selected');
} else { } else {
alert('No file selected') alert('No file selected');
} }
} };
const handleCancelImportButton = () => { const handleCancelImportButton = () => {
importForm.current.value = ""; importForm.current.value = '';
importForm.current.dispatchEvent(new Event("change", { bubbles: true })); importForm.current.dispatchEvent(new Event('change', { bubbles: true }));
} };
const handleImportChange = (event: any) => { const handleImportChange = (event: any) => {
if (event.target.files[0]) { if (event.target.files[0]) {
setCurrentImportFileName(event.target.files[0].name) setCurrentImportFileName(event.target.files[0].name);
} else { } else {
setCurrentImportFileName(null); setCurrentImportFileName(null);
} }
} };
return ( return (
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}> <FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={3}> <Stack spacing={3}>
<Typography variant="h6">Formularium Detail</Typography> <Typography variant="h6">Formularium Detail</Typography>
<div> <div>
<RHFTextField name="code" label="Code" /> <RHFTextField name="code" label="Code" />
{(!(currentFormularium?.id) && <Typography variant="caption">Will be generated if empty</Typography>)} {!currentFormularium?.id && (
<Typography variant="caption">Will be generated if empty</Typography>
)}
</div> </div>
<RHFTextField name="name" label="Name" /> <RHFTextField name="name" label="Name" />
<Typography variant="h6">Formularium Drug List Import</Typography> <Typography variant="h6">Formularium Drug List Import</Typography>
<input type='file' id='file' ref={importForm} style={{ display: 'none' }} onChange={handleImportChange} accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain" /> <input
type="file"
id="file"
ref={importForm}
style={{ display: 'none' }}
onChange={handleImportChange}
accept=".csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel, text/plain"
/>
<ButtonGroup variant="outlined" aria-label="outlined button group" fullWidth> <ButtonGroup variant="outlined" aria-label="outlined button group" fullWidth>
<Button onClick={handleImportButton} fullWidth>{currentImportFileName ?? "No File Selected"}</Button> <Button onClick={handleImportButton} fullWidth>
{(currentImportFileName && <Button onClick={handleCancelImportButton} size="small" fullWidth={false} sx={{ p: 1.8 }}><CancelIcon color="error"/></Button>)} {currentImportFileName ?? 'No File Selected'}
</Button>
{currentImportFileName && (
<Button
onClick={handleCancelImportButton}
size="small"
fullWidth={false}
sx={{ p: 1.8 }}
>
<CancelIcon color="error" />
</Button>
)}
</ButtonGroup> </ButtonGroup>
<LoadingButton type="submit" variant="contained" size="large" fullWidth={true} loading={isSubmitting}> <LoadingButton
type="submit"
variant="contained"
size="large"
fullWidth={true}
loading={isSubmitting}
>
{!isEdit ? 'Save New Corporate' : 'Save Update'} {!isEdit ? 'Save New Corporate' : 'Save Update'}
</LoadingButton> </LoadingButton>
</Stack> </Stack>
</FormProvider> </FormProvider>
); );
}; }

View File

@@ -1,19 +1,15 @@
import { capitalCase } from 'change-case';
import { Link as RouterLink } from 'react-router-dom';
// @mui // @mui
import { styled } from '@mui/material/styles'; import { styled } from '@mui/material/styles';
import { Box, Card, Stack, Link, Alert, Tooltip, Container, Typography } from '@mui/material'; import { Box, Card, Divider, Grid, Link, Stack, Typography } from '@mui/material';
// routes
import { PATH_AUTH } from '../../routes/paths';
// hooks
import useAuth from '../../hooks/useAuth';
import useResponsive from '../../hooks/useResponsive';
// components // components
import Page from '../../components/Page'; import Page from '../../components/Page';
import Logo from '../../components/Logo';
import Image from '../../components/Image'; import Image from '../../components/Image';
import Logo from '../../components/Logo';
// react
import { useEffect, useState } from 'react';
// sections // sections
import { LoginForm } from '../../sections/auth/login'; import { LoginEmailForm, LoginPhoneForm } from '../../sections/auth/login';
import { useLocation } from 'react-router';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -21,117 +17,88 @@ const RootStyle = styled('div')(({ theme }) => ({
[theme.breakpoints.up('md')]: { [theme.breakpoints.up('md')]: {
display: 'flex', display: 'flex',
}, },
}));
const HeaderStyle = styled('header')(({ theme }) => ({
top: 0,
zIndex: 9,
lineHeight: 0,
width: '100%',
display: 'flex',
alignItems: 'center',
position: 'absolute',
padding: theme.spacing(3),
justifyContent: 'space-between',
[theme.breakpoints.up('md')]: {
alignItems: 'flex-start',
padding: theme.spacing(7, 5, 0, 7),
},
}));
const SectionStyle = styled(Card)(({ theme }) => ({
width: '100%',
maxWidth: 464,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
margin: theme.spacing(2, 0, 2, 2),
}));
const ContentStyle = styled('div')(({ theme }) => ({
maxWidth: 480,
margin: 'auto',
display: 'flex',
minHeight: '100vh', minHeight: '100vh',
flexDirection: 'column', flexDirection: 'column',
justifyContent: 'center', justifyContent: 'center',
padding: theme.spacing(12, 0), alignItems: 'center',
}));
const ContentStyle = styled(Card)(({ theme }) => ({
[theme.breakpoints.up('md')]: {
maxHeight: '600px',
maxWidth: '1000px',
},
})); }));
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export default function Login() { export default function Login() {
const { method } = useAuth(); const { state } = useLocation();
const [formPhone, setFormPhone] = useState(false);
const smUp = useResponsive('up', 'sm'); const handlerChange = (event: any, setForm: boolean) => {
event.preventDefault();
setFormPhone(setForm);
};
const mdUp = useResponsive('up', 'md'); useEffect(() => {
// if (state !== null) {
// setFormPhone(state.formPhone);
// }
// console.log(state);
}, [state]);
return ( return (
<Page title="Login"> <Page title="Login">
<RootStyle> <RootStyle>
<HeaderStyle> <ContentStyle>
<Logo sx={{ width: 150, height: 150 }} /> <Grid container>
{smUp && ( <Grid item xs={6}>
<Typography variant="body2" sx={{ mt: { md: -2 } }}> <Image visibleByDefault disabledEffect src="/images/login-image.gif" alt="login" />
Has problem with your account? {''} </Grid>
<Link variant="subtitle2" component={RouterLink} to="#" onClick={(e) => { <Grid item xs={6} sx={{ padding: 3 }}>
window.location.href = "mailto:admin@linksehat.com"; <Stack direction="row" alignItems="center" sx={{ mb: 5 }}>
e.preventDefault(); <Logo sx={{ width: 90, height: 90 }} />
}}> <Box sx={{ flexGrow: 1 }}>
Contact Us <Typography variant="h4" gutterBottom>
</Link> Sign in to LinkSehat
</Typography> </Typography>
)} <Typography variant="body1" sx={{ color: 'text.secondary' }}>
</HeaderStyle> Enter your details below.
</Typography>
</Box>
</Stack>
{/* {mdUp && ( {formPhone ? (
<SectionStyle> <LoginPhoneForm formPhone={formPhone} />
<Typography variant="h3" sx={{ px: 5, mt: 10, mb: 5 }}> ) : (
Hi, Welcome Back <LoginEmailForm formPhone={formPhone} />
</Typography> )}
<Image
visibleByDefault
disabledEffect
src="https://minimal-assets-api.vercel.app/assets/illustrations/illustration_login.png"
alt="login"
/>
</SectionStyle>
)} */}
<Container maxWidth="sm"> <Divider sx={{ marginTop: 5 }}>Atau</Divider>
<ContentStyle>
<Stack direction="row" alignItems="center" sx={{ mb: 5 }}>
<Box sx={{ flexGrow: 1 }}>
<Typography variant="h4" gutterBottom>
Sign in to LinkSehat
</Typography>
<Typography sx={{ color: 'text.secondary' }}>Enter your details below.</Typography>
</Box>
<Tooltip title={capitalCase(method)} placement="right"> <Stack sx={{ marginTop: 5 }}>
<> {formPhone ? (
<Image <Link
disabledEffect align="center"
src={`https://minimal-assets-api.vercel.app/assets/icons/auth/ic_${method}.png`} underline="hover"
sx={{ width: 32, height: 32 }} onClick={(event) => handlerChange(event, false)}
/> >
</> Masuk menggunakan email
</Tooltip> </Link>
</Stack> ) : (
<Link
<LoginForm /> align="center"
underline="hover"
{false && !smUp && ( onClick={(event) => handlerChange(event, true)}
<Typography variant="body2" align="center" sx={{ mt: 3 }}> >
Dont have an account?{' '} Masuk menggunakan nomor handphone
<Link variant="subtitle2" component={RouterLink} to={PATH_AUTH.register}> </Link>
Get started )}
</Link> </Stack>
</Typography> </Grid>
)} </Grid>
</ContentStyle> </ContentStyle>
</Container>
</RootStyle> </RootStyle>
</Page> </Page>
); );

View File

@@ -1,148 +0,0 @@
import { capitalCase } from 'change-case';
import { Link as RouterLink } from 'react-router-dom';
// @mui
import { styled } from '@mui/material/styles';
import { Box, Card, Link, Container, Typography, Tooltip } from '@mui/material';
// hooks
import useAuth from '../../hooks/useAuth';
import useResponsive from '../../hooks/useResponsive';
// routes
import { PATH_AUTH } from '../../routes/paths';
// components
import Page from '../../components/Page';
import Logo from '../../components/Logo';
import Image from '../../components/Image';
// sections
import { RegisterForm } from '../../sections/auth/register';
// ----------------------------------------------------------------------
const RootStyle = styled('div')(({ theme }) => ({
[theme.breakpoints.up('md')]: {
display: 'flex',
},
}));
const HeaderStyle = styled('header')(({ theme }) => ({
top: 0,
zIndex: 9,
lineHeight: 0,
width: '100%',
display: 'flex',
alignItems: 'center',
position: 'absolute',
padding: theme.spacing(3),
justifyContent: 'space-between',
[theme.breakpoints.up('md')]: {
alignItems: 'flex-start',
padding: theme.spacing(7, 5, 0, 7),
},
}));
const SectionStyle = styled(Card)(({ theme }) => ({
width: '100%',
maxWidth: 464,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
margin: theme.spacing(2, 0, 2, 2),
}));
const ContentStyle = styled('div')(({ theme }) => ({
maxWidth: 480,
margin: 'auto',
display: 'flex',
minHeight: '100vh',
flexDirection: 'column',
justifyContent: 'center',
padding: theme.spacing(12, 0),
}));
// ----------------------------------------------------------------------
export default function Register() {
const { method } = useAuth();
const smUp = useResponsive('up', 'sm');
const mdUp = useResponsive('up', 'md');
return (
<Page title="Register">
<RootStyle>
<HeaderStyle>
<Logo />
{smUp && (
<Typography variant="body2" sx={{ mt: { md: -2 } }}>
Already have an account? {''}
<Link variant="subtitle2" component={RouterLink} to={PATH_AUTH.login}>
Login
</Link>
</Typography>
)}
</HeaderStyle>
{mdUp && (
<SectionStyle>
<Typography variant="h3" sx={{ px: 5, mt: 10, mb: 5 }}>
Manage the job more effectively with Minimal
</Typography>
<Image
visibleByDefault
disabledEffect
alt="register"
src="https://minimal-assets-api.vercel.app/assets/illustrations/illustration_register.png"
/>
</SectionStyle>
)}
<Container>
<ContentStyle>
<Box sx={{ mb: 5, display: 'flex', alignItems: 'center' }}>
<Box sx={{ flexGrow: 1 }}>
<Typography variant="h4" gutterBottom>
Get started absolutely free.
</Typography>
<Typography sx={{ color: 'text.secondary' }}>
Free forever. No credit card needed.
</Typography>
</Box>
<Tooltip title={capitalCase(method)}>
<>
<Image
disabledEffect
src={`https://minimal-assets-api.vercel.app/assets/icons/auth/ic_${method}.png`}
sx={{ width: 32, height: 32 }}
/>
</>
</Tooltip>
</Box>
<RegisterForm />
<Typography variant="body2" align="center" sx={{ color: 'text.secondary', mt: 3 }}>
By registering, I agree to Minimal&nbsp;
<Link underline="always" color="text.primary" href="#">
Terms of Service
</Link>
{''}and{''}
<Link underline="always" color="text.primary" href="#">
Privacy Policy
</Link>
.
</Typography>
{!smUp && (
<Typography variant="body2" sx={{ mt: 3, textAlign: 'center' }}>
Already have an account?{' '}
<Link variant="subtitle2" to={PATH_AUTH.login} component={RouterLink}>
Login
</Link>
</Typography>
)}
</ContentStyle>
</Container>
</RootStyle>
</Page>
);
}

View File

@@ -1,98 +0,0 @@
import { useState } from 'react';
import { Link as RouterLink } from 'react-router-dom';
// @mui
import { styled } from '@mui/material/styles';
import { Box, Button, Container, Typography } from '@mui/material';
// layouts
import LogoOnlyLayout from '../../layouts/LogoOnlyLayout';
// routes
import { PATH_AUTH } from '../../routes/paths';
// components
import Page from '../../components/Page';
// sections
import { ResetPasswordForm } from '../../sections/auth/reset-password';
// assets
import { SentIcon } from '../../assets';
// ----------------------------------------------------------------------
const RootStyle = styled('div')(({ theme }) => ({
display: 'flex',
minHeight: '100%',
alignItems: 'center',
justifyContent: 'center',
padding: theme.spacing(12, 0),
}));
// ----------------------------------------------------------------------
export default function ResetPassword() {
const [email, setEmail] = useState('');
const [sent, setSent] = useState(false);
return (
<Page title="Reset Password" sx={{ height: 1 }}>
<RootStyle>
<LogoOnlyLayout />
<Container>
<Box sx={{ maxWidth: 480, mx: 'auto' }}>
{!sent ? (
<>
<Typography variant="h3" paragraph>
Forgot your password?
</Typography>
<Typography sx={{ color: 'text.secondary', mb: 5 }}>
Please enter the email address associated with your account and We will email you
a link to reset your password.
</Typography>
<ResetPasswordForm
onSent={() => setSent(true)}
onGetEmail={(value) => setEmail(value)}
/>
<Button
fullWidth
size="large"
component={RouterLink}
to={PATH_AUTH.login}
sx={{ mt: 1 }}
>
Back
</Button>
</>
) : (
<Box sx={{ textAlign: 'center' }}>
<SentIcon sx={{ mb: 5, mx: 'auto', height: 160 }} />
<Typography variant="h3" gutterBottom>
Request sent successfully
</Typography>
<Typography>
We have sent a confirmation email to &nbsp;
<strong>{email}</strong>
<br />
Please check your email.
</Typography>
<Button
size="large"
variant="contained"
component={RouterLink}
to={PATH_AUTH.login}
sx={{ mt: 5 }}
>
Back
</Button>
</Box>
)}
</Box>
</Container>
</RootStyle>
</Page>
);
}

View File

@@ -1,66 +1,109 @@
import { Link as RouterLink } from 'react-router-dom'; import { Link as RouterLink, useLocation, useNavigate } from 'react-router-dom';
// @mui // @mui
import { styled } from '@mui/material/styles'; import { styled } from '@mui/material/styles';
import { Box, Button, Link, Container, Typography } from '@mui/material'; import { Box, Card, Divider, IconButton, Grid, Link, Stack, Typography } from '@mui/material';
// layouts
import LogoOnlyLayout from '../../layouts/LogoOnlyLayout';
// routes
import { PATH_AUTH } from '../../routes/paths';
// components // components
import Page from '../../components/Page'; import Page from '../../components/Page';
import Image from '../../components/Image';
import Iconify from '../../components/Iconify'; import Iconify from '../../components/Iconify';
// sections // sections
import { VerifyCodeForm } from '../../sections/auth/verify-code'; import { VerifyCodeForm } from '../../sections/auth/verify-code';
import { useEffect } from 'react';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
const RootStyle = styled('div')(({ theme }) => ({ const RootStyle = styled('div')(({ theme }) => ({
display: 'flex', [theme.breakpoints.up('md')]: {
height: '100%', display: 'flex',
},
minHeight: '100vh',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
padding: theme.spacing(12, 0),
})); }));
const ContentStyle = styled(Card)(({ theme }) => ({
[theme.breakpoints.up('md')]: {
maxHeight: '600px',
maxWidth: '1000px',
},
}));
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export default function VerifyCode() { export default function VerifyCode() {
const { state } = useLocation();
const navigate = useNavigate();
useEffect(() => {
if (state === null) {
navigate('/auth/login');
} else {
console.log(state);
}
}, [state, navigate]);
return ( return (
<Page title="Verify" sx={{ height: 1 }}> <Page title="Login">
<RootStyle> <RootStyle>
<LogoOnlyLayout /> <ContentStyle>
<Grid container>
<Grid item xs={6}>
<Image visibleByDefault disabledEffect src="/images/login-image.gif" alt="login" />
</Grid>
<Grid item xs={6} sx={{ padding: 3 }}>
<Stack direction="column" sx={{ mb: 5 }}>
<Stack direction="row" alignItems="center">
<IconButton
onClick={() =>
navigate('/auth/login', { state: { formPhone: state.formPhone } })
}
>
<Iconify
icon="heroicons-outline:arrow-narrow-left"
sx={{ marginRight: '10px' }}
/>
</IconButton>
<Typography variant="h4" gutterBottom>
Verifikasi OTP
</Typography>
</Stack>
<Box sx={{ flexGrow: 1 }}>
<Typography variant="body1" sx={{ color: 'text.secondary' }}>
Masukkan kode OTP anda disini
</Typography>
</Box>
</Stack>
<Container>
<Box sx={{ maxWidth: 480, mx: 'auto' }}>
<Button
size="small"
component={RouterLink}
to={PATH_AUTH.login}
startIcon={<Iconify icon={'eva:arrow-ios-back-fill'} width={20} height={20} />}
sx={{ mb: 3 }}
>
Back
</Button>
<Typography variant="h3" paragraph>
Please check your email!
</Typography>
<Typography sx={{ color: 'text.secondary' }}>
We have emailed a 6-digit confirmation code to acb@domain, please enter the code in
below box to verify your email.
</Typography>
<Box sx={{ mt: 5, mb: 3 }}>
<VerifyCodeForm /> <VerifyCodeForm />
</Box>
<Typography variant="body2" align="center"> <Divider sx={{ marginTop: 5 }}>Atau</Divider>
Dont have a code? &nbsp;
<Link variant="subtitle2" underline="none" onClick={() => {}}> <Stack sx={{ marginTop: 5 }}>
Resend code {state.formPhone === false ? (
</Link> <Link
</Typography> align="center"
</Box> underline="hover"
</Container> onClick={() =>
navigate('/auth/login', { state: { formPhone: state.formPhone } })
}
>
Masuk menggunakan nomor handphone
</Link>
) : (
<Link
align="center"
underline="hover"
onClick={() =>
navigate('/auth/login', { state: { formPhone: state.formPhone } })
}
>
Masuk menggunakan email
</Link>
)}
</Stack>
</Grid>
</Grid>
</ContentStyle>
</RootStyle> </RootStyle>
</Page> </Page>
); );

View File

@@ -6,10 +6,6 @@ import LogoOnlyLayout from '../layouts/LogoOnlyLayout';
// components // components
import LoadingScreen from '../components/LoadingScreen'; import LoadingScreen from '../components/LoadingScreen';
import GuestGuard from '../guards/GuestGuard'; import GuestGuard from '../guards/GuestGuard';
import { RegisterForm } from '../sections/auth/register';
import Register from '../pages/auth/Register';
import ResetPassword from '../pages/auth/ResetPassword';
import VerifyCode from '../pages/auth/VerifyCode';
import { AuthProvider } from '../contexts/LaravelAuthContext'; import { AuthProvider } from '../contexts/LaravelAuthContext';
import AuthGuard from '../guards/AuthGuard'; import AuthGuard from '../guards/AuthGuard';
@@ -42,11 +38,11 @@ export default function Router() {
), ),
}, },
{ {
path: 'register', path: 'verify-code',
element: ( element: (
<AuthProvider> <AuthProvider>
<GuestGuard> <GuestGuard>
<RegisterForm /> <VerifyCode />
</GuestGuard> </GuestGuard>
</AuthProvider> </AuthProvider>
), ),
@@ -54,7 +50,6 @@ export default function Router() {
// { path: 'login-unprotected', element: <Login /> }, // { path: 'login-unprotected', element: <Login /> },
// { path: 'register-unprotected', element: <Register /> }, // { path: 'register-unprotected', element: <Register /> },
// { path: 'reset-password', element: <ResetPassword /> }, // { path: 'reset-password', element: <ResetPassword /> },
// { path: 'verify', element: <VerifyCode /> },
], ],
}, },
// { // {
@@ -68,18 +63,35 @@ export default function Router() {
<AuthGuard> <AuthGuard>
<DashboardLayout /> <DashboardLayout />
</AuthGuard> </AuthGuard>
</AuthProvider>), </AuthProvider>
children:[ ),
children: [
{ element: <Navigate to="/dashboard" replace />, index: true }, { element: <Navigate to="/dashboard" replace />, index: true },
{ {
path: 'dashboard', path: 'dashboard',
element: <Dashboard />, element: <Dashboard />,
}, },
],
},
{
path: '/members',
element: (
<AuthProvider>
<AuthGuard>
<DashboardLayout />
</AuthGuard>
</AuthProvider>
),
children: [
{ {
path: 'members',
element: <Members />, element: <Members />,
index: true,
}, },
] {
path: 'create',
element: <MembersCreate />,
},
],
}, },
// { // {
// path: '/dashboard', // path: '/dashboard',
@@ -114,7 +126,9 @@ export default function Router() {
]); ]);
} }
// Auth
const Login = Loadable(lazy(() => import('../pages/auth/Login'))); const Login = Loadable(lazy(() => import('../pages/auth/Login')));
const VerifyCode = Loadable(lazy(() => import('../pages/auth/VerifyCode')));
// Dashboard // Dashboard
const Dashboard = Loadable(lazy(() => import('../pages/Dashboard'))); const Dashboard = Loadable(lazy(() => import('../pages/Dashboard')));
@@ -122,4 +136,7 @@ const NotFound = Loadable(lazy(() => import('../pages/Page404')));
// Members // Members
const Members = Loadable(lazy(() => import('../pages/Members/Index'))); const Members = Loadable(lazy(() => import('../pages/Members/Index')));
const MedicinesCreate = Loadable(lazy(() => import('../pages/Medicines/Create'))); const MembersCreate = Loadable(lazy(() => import('../pages/Members/Create')));
// Alarm Center
// const AlarmCenter = Loadable(lazy(() => import('../pages/AlarmCenter/Index')));

View File

@@ -0,0 +1,88 @@
import * as Yup from 'yup';
import { useNavigate } from 'react-router-dom';
// form
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
// @mui
import { Stack, Alert } from '@mui/material';
import { LoadingButton } from '@mui/lab';
// hooks
import useAuth from '../../../hooks/useAuth';
import useIsMountedRef from '../../../hooks/useIsMountedRef';
// components
import { FormProvider, RHFTextField } from '../../../components/hook-form';
// ----------------------------------------------------------------------
type FormValuesProps = {
email: string;
afterSubmit?: string;
};
interface Props {
formPhone: boolean;
}
// ----------------------------------------------------------------------
export default function LoginForm({ formPhone }: Props) {
const { login } = useAuth();
const navigate = useNavigate();
const isMountedRef = useIsMountedRef();
const LoginSchema = Yup.object().shape({
email: Yup.string().email('Email must be a valid email address').required('Email is required'),
});
const defaultValues = {
email: '',
};
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);
navigate('/auth/verify-code', { state: { phoneOrEmail: data.email, formPhone } });
} catch (error: any) {
reset();
if (isMountedRef.current) {
setError('afterSubmit', { ...error, message: error.data.message });
}
}
};
return (
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={3}>
<Alert severity="info">Masukkan akun yang telah terdaftar</Alert>
{!!errors.afterSubmit && <Alert severity="error">{errors.afterSubmit.message}</Alert>}
<RHFTextField name="email" label="Email address" />
</Stack>
<LoadingButton
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
sx={{ marginTop: 2 }}
>
Login
</LoadingButton>
</FormProvider>
);
}

View File

@@ -1,117 +0,0 @@
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: 'admin@linksehat.dev',
password: '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 {
const loginResult = await login(data.email, data.password );
navigate('/dashboard');
} catch (error) {
console.error(error);
reset();
if (isMountedRef.current) {
setError('afterSubmit', { ...error, message: error.data.message });
}
}
};
return (
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={3}>
<Alert severity='primary'>Email : admin@linksehat.dev & Password : password</Alert>
{!!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>
);
}

View File

@@ -0,0 +1,86 @@
import * as Yup from 'yup';
import { useNavigate } from 'react-router-dom';
// form
import { useForm } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';
// @mui
import { Stack, Alert, InputAdornment } from '@mui/material';
import { LoadingButton } from '@mui/lab';
// components
import { FormProvider, RHFTextField } from '../../../components/hook-form';
import useAuth from '../../../hooks/useAuth';
// ----------------------------------------------------------------------
type FormValuesProps = {
phone: string;
afterSubmit?: string;
};
interface Props {
formPhone: boolean;
}
export default function LoginPhoneForm({ formPhone }: Props) {
const { login } = useAuth();
const navigate = useNavigate();
const LoginSchema = Yup.object().shape({
phone: Yup.string().required('Phone is required'),
});
const defaultValues = {
phone: '',
};
const methods = useForm<FormValuesProps>({
resolver: yupResolver(LoginSchema),
defaultValues,
});
const {
reset,
setError,
handleSubmit,
formState: { errors, isSubmitting },
} = methods;
const onSubmit = async (data: FormValuesProps) => {
try {
await login(0 + data.phone);
navigate('/auth/verify-code', { state: { phoneOrEmail: 0 + data.phone, formPhone } });
} catch (error: any) {
reset();
setError('afterSubmit', { ...error, message: error.response.data.message });
}
};
return (
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={3}>
<Alert severity="info">Masukkan akun yang telah terdaftar</Alert>
{!!errors.afterSubmit && <Alert severity="error">{errors.afterSubmit.message}</Alert>}
<RHFTextField
name="phone"
label="Phone Number"
type={'number'}
InputProps={{
startAdornment: <InputAdornment position="start">+62</InputAdornment>,
}}
/>
</Stack>
<LoadingButton
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
sx={{ marginTop: 2 }}
>
Login
</LoadingButton>
</FormProvider>
);
}

View File

@@ -1 +1,2 @@
export { default as LoginForm } from './LoginForm'; export { default as LoginEmailForm } from './LoginEmailForm';
export { default as LoginPhoneForm } from './LoginPhoneForm';

View File

@@ -8,6 +8,7 @@ import { yupResolver } from '@hookform/resolvers/yup';
// @mui // @mui
import { OutlinedInput, Stack } from '@mui/material'; import { OutlinedInput, Stack } from '@mui/material';
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
import useAuth from '../../../hooks/useAuth';
// routes // routes
// import { PATH_DASHBOARD } from '../../../routes/paths'; // import { PATH_DASHBOARD } from '../../../routes/paths';
@@ -18,14 +19,13 @@ type FormValuesProps = {
code2: string; code2: string;
code3: string; code3: string;
code4: string; code4: string;
code5: string;
code6: string;
}; };
type ValueNames = 'code1' | 'code2' | 'code3' | 'code4' | 'code5' | 'code6'; type ValueNames = 'code1' | 'code2' | 'code3' | 'code4';
export default function VerifyCodeForm() { export default function VerifyCodeForm({ phoneOrEmail }: any) {
const navigate = useNavigate(); const navigate = useNavigate();
const { validateOtp } = useAuth();
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
@@ -34,8 +34,6 @@ export default function VerifyCodeForm() {
code2: Yup.string().required('Code is required'), code2: Yup.string().required('Code is required'),
code3: Yup.string().required('Code is required'), code3: Yup.string().required('Code is required'),
code4: 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 = { const defaultValues = {
@@ -43,8 +41,6 @@ export default function VerifyCodeForm() {
code2: '', code2: '',
code3: '', code3: '',
code4: '', code4: '',
code5: '',
code6: '',
}; };
const { const {
@@ -68,12 +64,10 @@ export default function VerifyCodeForm() {
const onSubmit = async (data: FormValuesProps) => { const onSubmit = async (data: FormValuesProps) => {
try { try {
await new Promise((resolve) => setTimeout(resolve, 500)); await new Promise((resolve) => setTimeout(resolve, 750));
console.log('code:', Object.values(data).join('')); await validateOtp(phoneOrEmail, Object.values(data).join(''));
enqueueSnackbar('Verify success!'); enqueueSnackbar('Verify success!');
navigate('/dashboard');
navigate('/dashboard', { replace: true });
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
@@ -114,8 +108,8 @@ export default function VerifyCodeForm() {
}; };
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onChange={handleSubmit(onSubmit)}>
<Stack direction="row" spacing={2} justifyContent="center"> <Stack direction="row" spacing={2} justifyContent="space-evenly">
{Object.keys(values).map((name, index) => ( {Object.keys(values).map((name, index) => (
<Controller <Controller
key={name} key={name}
@@ -126,7 +120,7 @@ export default function VerifyCodeForm() {
{...field} {...field}
id="field-code" id="field-code"
autoFocus={index === 0} autoFocus={index === 0}
placeholder="-" placeholder=""
onChange={(event: React.ChangeEvent<HTMLInputElement>) => onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
handleChangeWithNextField(event, field.onChange) handleChangeWithNextField(event, field.onChange)
} }
@@ -144,18 +138,6 @@ export default function VerifyCodeForm() {
/> />
))} ))}
</Stack> </Stack>
<LoadingButton
fullWidth
size="large"
type="submit"
variant="contained"
loading={isSubmitting}
disabled={!isValid}
sx={{ mt: 3 }}
>
Verify
</LoadingButton>
</form> </form>
); );
} }