diff --git a/Modules/Client/Http/Controllers/Api/AuthController.php b/Modules/Client/Http/Controllers/Api/AuthController.php
index 8e36cde6..825fc3eb 100755
--- a/Modules/Client/Http/Controllers/Api/AuthController.php
+++ b/Modules/Client/Http/Controllers/Api/AuthController.php
@@ -2,45 +2,97 @@
namespace Modules\Client\Http\Controllers\Api;
+use App\Helpers\Helper;
use App\Http\Controllers\Controller;
use App\Models\User;
+use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
-use Illuminate\Support\Facades\Hash;
+use Symfony\Component\HttpFoundation\Response;
class AuthController extends Controller
{
public function login(Request $request)
{
$request->validate([
- 'email' => 'required|email',
- 'password' => 'required'
+ 'phoneOrEmail' => 'required'
]);
$user = User::query()
- ->where('email', $request->email)
- ->first();
+ ->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) {
- 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)) {
- return response(['message' => 'Password Salah'], 403);
+ if (filter_var($request->phoneOrEmail, FILTER_VALIDATE_EMAIL)) {
+ 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([
- 'message' => 'Selamat Datang',
- 'user' => $user,
- 'token' => $user->createToken('app')->plainTextToken
+ // TODO Send the OTP
+ if (filter_var($request->phoneOrEmail, FILTER_VALIDATE_EMAIL)) {
+ // Send Email
+ } else {
+ // Send Whatsapp
+ }
+
+ return Helper::responseJson(message: 'OTP Terkirim');
+ }
+
+ public function validateOtp(Request $request)
+ {
+ echo '
';
+ var_dump($_POST, $_GET);
+ echo '
';
+ 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)
{
- $token = $request->bearerToken();
- Auth::user()->tokens()->where('id', $token)->delete();
+ $request->user()->currentAccessToken()->delete();
- return response(['message' => 'Berhasil Logout.']);
+ return Helper::responseJson(message: 'Berhasil Logout.');
}
}
diff --git a/Modules/Client/Routes/api.php b/Modules/Client/Routes/api.php
index 09b10e41..418acb51 100755
--- a/Modules/Client/Routes/api.php
+++ b/Modules/Client/Routes/api.php
@@ -18,19 +18,18 @@ use Modules\Client\Http\Controllers\Api\UserController;
*/
Route::prefix('client')->group(function () {
-
- Route::post('login', [AuthController::class, 'login'])->name('login');
- Route::post('forget-password', [AuthController::class, 'forgetPassword'])->name('forget-password');
- Route::post('verify-email', [AuthController::class, 'verifyEmail'])->name('verify-email');
+
+ Route::controller(AuthController::class)->group(function () {
+ Route::post('login', 'login');
+ Route::post('verify-code', 'validateOtp');
+ });
Route::middleware('auth:sanctum')->group(function () {
-
+
Route::post('logout', [AuthController::class, 'logout'])->name('logout');
Route::get('/user', [UserController::class, 'index']);
Route::get('dashboard', [DashboardController::class, 'index']);
Route::get('members', [MemberController::class, 'index']);
-
});
-
});
diff --git a/frontend/client-portal/public/images/login-image.gif b/frontend/client-portal/public/images/login-image.gif
new file mode 100644
index 00000000..5f123cf8
Binary files /dev/null and b/frontend/client-portal/public/images/login-image.gif differ
diff --git a/frontend/client-portal/src/@types/auth.ts b/frontend/client-portal/src/@types/auth.ts
index 3df73c76..dbc15390 100755
--- a/frontend/client-portal/src/@types/auth.ts
+++ b/frontend/client-portal/src/@types/auth.ts
@@ -1,4 +1,4 @@
-import { UserCredential } from 'firebase/auth';
+// import { UserCredential } from 'firebase/auth';
// ----------------------------------------------------------------------
@@ -26,20 +26,20 @@ export type JWTContextType = {
isInitialized: boolean;
user: AuthUser;
method: 'jwt';
- login: (email: string, password: string) => Promise;
- register: (email: string, password: string, firstName: string, lastName: string) => Promise;
+ login: (phoneOrEmail: string) => Promise;
+ validateOtp: (phoneOrEmail: string, otp: string) => Promise
logout: () => Promise;
};
-export type FirebaseContextType = {
- isAuthenticated: boolean;
- isInitialized: boolean;
- user: AuthUser;
- method: 'firebase';
- login: (email: string, password: string) => Promise;
- register: (email: string, password: string, firstName: string, lastName: string) => Promise;
- logout: () => Promise;
-};
+// export type FirebaseContextType = {
+// isAuthenticated: boolean;
+// isInitialized: boolean;
+// user: AuthUser;
+// method: 'firebase';
+// login: (email: string, password: string) => Promise;
+// register: (email: string, password: string, firstName: string, lastName: string) => Promise;
+// logout: () => Promise;
+// };
export type AWSCognitoContextType = {
isAuthenticated: boolean;
diff --git a/frontend/client-portal/src/components/nav-section/type.ts b/frontend/client-portal/src/components/nav-section/type.ts
index 870407d8..f8a9ea44 100755
--- a/frontend/client-portal/src/components/nav-section/type.ts
+++ b/frontend/client-portal/src/components/nav-section/type.ts
@@ -29,7 +29,7 @@ export type NavItemProps = {
export interface NavSectionProps extends BoxProps {
isCollapse?: boolean;
navConfig: {
- subheader: string;
+ subheader?: string;
items: NavListProps[];
}[];
}
diff --git a/frontend/client-portal/src/contexts/LaravelAuthContext.tsx b/frontend/client-portal/src/contexts/LaravelAuthContext.tsx
index d30559b5..f78d2a81 100755
--- a/frontend/client-portal/src/contexts/LaravelAuthContext.tsx
+++ b/frontend/client-portal/src/contexts/LaravelAuthContext.tsx
@@ -1,18 +1,16 @@
import { createContext, ReactNode, useEffect, useReducer } from 'react';
// utils
import axios from '../utils/axios';
-// import { isValidToken, setSession } from '../utils/jwt';
-import { setSession, getSession, getUser } from '../utils/token';
+import { setSession, getSession } from '../utils/token';
// @types
import { ActionMap, AuthState, AuthUser, JWTContextType } from '../@types/auth';
// ----------------------------------------------------------------------
-import { Navigate, useLocation } from 'react-router-dom';
enum Types {
Initial = 'INITIALIZE',
Login = 'LOGIN',
+ ValidateOtp = 'VALIDATE-OTP',
Logout = 'LOGOUT',
- Register = 'REGISTER',
}
type JWTAuthPayload = {
@@ -20,13 +18,11 @@ type JWTAuthPayload = {
isAuthenticated: boolean;
user: AuthUser;
};
- [Types.Login]: {
+ [Types.Login]: undefined;
+ [Types.ValidateOtp]: {
user: AuthUser;
};
[Types.Logout]: undefined;
- [Types.Register]: {
- user: AuthUser;
- };
};
export type JWTActions = ActionMap[keyof ActionMap];
@@ -46,6 +42,12 @@ const JWTReducer = (state: AuthState, action: JWTActions) => {
user: action.payload.user,
};
case 'LOGIN':
+ return {
+ ...state,
+ isAuthenticated: false,
+ user: null,
+ };
+ case 'VALIDATE-OTP':
return {
...state,
isAuthenticated: true,
@@ -57,14 +59,6 @@ const JWTReducer = (state: AuthState, action: JWTActions) => {
isAuthenticated: false,
user: null,
};
-
- case 'REGISTER':
- return {
- ...state,
- isAuthenticated: true,
- user: action.payload.user,
- };
-
default:
return state;
}
@@ -80,15 +74,15 @@ type AuthProviderProps = {
function AuthProvider({ children }: AuthProviderProps) {
const [state, dispatch] = useReducer(JWTReducer, initialState);
- let location = useLocation();
useEffect(() => {
- const initialize = async () => {
- console.log('initialize', state)
+ (async () => {
+ console.log('initialize', state);
+
try {
const accessToken = getSession();
-
- console.log('')
+
+ console.log('');
if (accessToken) {
setSession(accessToken);
@@ -120,61 +114,55 @@ function AuthProvider({ children }: AuthProviderProps) {
},
});
}
- };
-
- initialize();
+ })();
}, []);
- const login = async (email: string, password: string) => {
- return axios
- .post('/login', { email, password })
- .then((response) => {
- const { user, token } = response.data;
- setSession(token);
+ const login = async (phoneOrEmail: string) =>
+ axios
+ .post('/login', { phoneOrEmail })
+ .then(() => {
+ dispatch({
+ type: Types.Login,
+ });
+ })
+ .catch((error) => {
+ if (error.response.status !== 404) throw error.response;
+ if (error.response.status !== 422) throw error.response;
+ });
- dispatch({
- type: Types.Login,
- payload: {
- user,
- }
- });
- })
- .catch(error => {
- if (error.response.status !== 404) throw error.response
- if (error.response.status !== 422) throw error.response
- })
- };
+ const validateOtp = async (phoneOrEmail: string, otp: string) =>
+ axios
+ .post('/verify-code', { otp })
+ .then((response) => {
+ const { user, token } = response.data.data;
+ setSession(token);
- const register = async (email: string, password: string, firstName: string, lastName: string) => {
- const response = await axios.post('/api/register', {
- email,
- password,
- firstName,
- lastName,
- });
- const { accessToken, user } = response.data;
-
- window.localStorage.setItem('accessToken', accessToken);
- dispatch({
- type: Types.Register,
- payload: {
- user,
- },
- });
- };
+ dispatch({
+ type: Types.ValidateOtp,
+ payload: {
+ user,
+ },
+ });
+ })
+ .catch((error) => {
+ if (error.response.status !== 404) throw error.response;
+ if (error.response.status !== 422) throw error.response;
+ });
const logout = async () => {
+ await axios.post('/logout');
setSession(null);
dispatch({ type: Types.Logout });
};
- return (
{children}
@@ -182,9 +170,9 @@ function AuthProvider({ children }: AuthProviderProps) {
);
// if (state.isInitialized) {
- // return (!state.isAuthenticated && location.pathname !== '/auth/login') ?
+ // return (!state.isAuthenticated && location.pathname !== '/auth/login') ?
// ()
- // : false && location.pathname == '/auth/login' ?
+ // : false && location.pathname == '/auth/login' ?
// ()
// : (
// PRIME CENTER
- )
- : (
+
+ ) : (
diff --git a/frontend/client-portal/src/pages/AlarmCenter/Index.tsx b/frontend/client-portal/src/pages/AlarmCenter/Index.tsx
new file mode 100755
index 00000000..793014eb
--- /dev/null
+++ b/frontend/client-portal/src/pages/AlarmCenter/Index.tsx
@@ -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 (
+
+
+
+
+ {/* */}
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/client-portal/src/pages/Members/Create.tsx b/frontend/client-portal/src/pages/Members/Create.tsx
index e0527248..90df6205 100755
--- a/frontend/client-portal/src/pages/Members/Create.tsx
+++ b/frontend/client-portal/src/pages/Members/Create.tsx
@@ -1,15 +1,14 @@
import * as Yup from 'yup';
-import { yupResolver } from "@hookform/resolvers/yup";
-import { Card, Collapse, Divider, Grid, Stack, Typography } from "@mui/material";
-import { useForm } from "react-hook-form";
-import { useParams } from "react-router-dom";
-import HeaderBreadcrumbs from "../../../components/HeaderBreadcrumbs";
-import { FormProvider, RHFCheckbox, RHFSelect, RHFTextField } from "../../../components/hook-form";
-import Page from "../../../components/Page";
-import useSettings from "../../../hooks/useSettings";
+import { yupResolver } from '@hookform/resolvers/yup';
+import { Card, Collapse, Divider, Grid, Stack, Typography } from '@mui/material';
+import { useForm } from 'react-hook-form';
+import { useParams } from 'react-router-dom';
+import HeaderBreadcrumbs from '../../components/HeaderBreadcrumbs';
+import { FormProvider, RHFCheckbox, RHFSelect, RHFTextField } from '../../components/hook-form';
+import Page from '../../components/Page';
+import useSettings from '../../hooks/useSettings';
import { useMemo, useState } from 'react';
-import Form from "./Form";
-
+import Form from './Form';
export default function Divisions() {
const { themeStretch } = useSettings();
@@ -48,13 +47,12 @@ export default function Divisions() {
} = methods;
const onSubmit = async (data: any) => {
- console.log(data)
+ console.log(data);
};
const pageTitle = 'Create Formularium';
return (
-
-
-
+
diff --git a/frontend/client-portal/src/pages/Members/Form.tsx b/frontend/client-portal/src/pages/Members/Form.tsx
index 9ec18471..da9803d0 100755
--- a/frontend/client-portal/src/pages/Members/Form.tsx
+++ b/frontend/client-portal/src/pages/Members/Form.tsx
@@ -33,10 +33,10 @@ import {
RHFMultiCheckbox,
RHFCheckbox,
RHFCustomMultiCheckbox,
-} from '../../../components/hook-form';
-import { Corporate } from '../../../@types/corporates';
-import axios from '../../../utils/axios';
-import { fCurrency } from '../../../utils/formatNumber';
+} from '../../components/hook-form';
+import { Corporate } from '../../@types/corporates';
+import axios from '../../utils/axios';
+import { fCurrency } from '../../utils/formatNumber';
const LabelStyle = styled(Typography)(({ theme }) => ({
...theme.typography.subtitle2,
@@ -110,26 +110,31 @@ export default function FormulariumForm({ isEdit, currentFormularium }: Props) {
if (!isEdit) {
const response = await axios.post('/master/formulariums', data);
} else {
- const response = await axios.put('/master/formulariums/' + currentFormularium?.id ?? '', data);
+ const response = await axios.put(
+ '/master/formulariums/' + currentFormularium?.id ?? '',
+ data
+ );
}
reset();
- enqueueSnackbar(!isEdit ? 'Formularium Created Successfully!' : 'Formularium Udpated Successfully!', { variant: 'success' });
+ enqueueSnackbar(
+ !isEdit ? 'Formularium Created Successfully!' : 'Formularium Udpated Successfully!',
+ { variant: 'success' }
+ );
navigate('/master/formularium');
} catch (error: any) {
if (error && error.response.status === 422) {
- for (const [key, value] of Object.entries(error.response.data.errors)) {
+ for (const [key, value] of Object.entries(error.response.data.errors)) {
setError(key, { message: value[0] });
enqueueSnackbar(value[0] ?? 'Failed Processing Request', { variant: 'error' });
}
- }
- else {
+ } else {
enqueueSnackbar(error.message ?? 'Failed Processing Request', { variant: 'error' });
}
}
- const ascent = document?.querySelector("ascent");
+ const ascent = document?.querySelector('ascent');
if (ascent != null) {
- ascent.innerHTML = "";
+ ascent.innerHTML = '';
}
};
@@ -151,35 +156,34 @@ export default function FormulariumForm({ isEdit, currentFormularium }: Props) {
setValue('logo', null);
};
- const linking_rules_checkbox_name = "linking_rules"
+ const linking_rules_checkbox_name = 'linking_rules';
const linking_tools = [
{
- "value" : "nrik",
- "label" : "No. KTP"
+ value: 'nrik',
+ label: 'No. KTP',
},
{
- "value" : "nik",
- "label" : "Nomor Induk Karyawan (NIK)"
+ value: 'nik',
+ label: 'Nomor Induk Karyawan (NIK)',
},
{
- "value" : "member_id",
- "label" : "Member ID"
+ value: 'member_id',
+ label: 'Member ID',
},
{
- "value" : "phone",
- "label" : "Nomor Telepon"
+ value: 'phone',
+ label: 'Nomor Telepon',
},
{
- "value" : "email",
- "label" : "E-Mail"
+ value: 'email',
+ label: 'E-Mail',
},
- ]
-
+ ];
- const importForm = useRef(null)
+ const importForm = useRef(null);
const [anchorEl, setAnchorEl] = useState(null);
- const [currentImportFileName, setCurrentImportFileName] = useState(null);
-
+ const [currentImportFileName, setCurrentImportFileName] = useState(null);
+
const handleClose = () => {
setAnchorEl(null);
};
@@ -189,51 +193,73 @@ export default function FormulariumForm({ isEdit, currentFormularium }: Props) {
handleClose();
importForm.current ? importForm.current.click() : console.log('No File selected');
} else {
- alert('No file selected')
+ alert('No file selected');
}
- }
+ };
const handleCancelImportButton = () => {
- importForm.current.value = "";
- importForm.current.dispatchEvent(new Event("change", { bubbles: true }));
- }
+ importForm.current.value = '';
+ importForm.current.dispatchEvent(new Event('change', { bubbles: true }));
+ };
const handleImportChange = (event: any) => {
if (event.target.files[0]) {
- setCurrentImportFileName(event.target.files[0].name)
+ setCurrentImportFileName(event.target.files[0].name);
} else {
setCurrentImportFileName(null);
}
- }
-
+ };
return (
-
-
Formularium Detail
-
- {(!(currentFormularium?.id) && Will be generated if empty)}
+
+ {!currentFormularium?.id && (
+ Will be generated if empty
+ )}
Formularium Drug List Import
-
+
-
- {(currentImportFileName && )}
+
+ {currentImportFileName && (
+
+ )}
-
+
{!isEdit ? 'Save New Corporate' : 'Save Update'}
-
);
-};
+}
diff --git a/frontend/client-portal/src/pages/auth/Login.tsx b/frontend/client-portal/src/pages/auth/Login.tsx
index a3ec2f16..ca0ae3ea 100755
--- a/frontend/client-portal/src/pages/auth/Login.tsx
+++ b/frontend/client-portal/src/pages/auth/Login.tsx
@@ -1,19 +1,15 @@
-import { capitalCase } from 'change-case';
-import { Link as RouterLink } from 'react-router-dom';
// @mui
import { styled } from '@mui/material/styles';
-import { Box, Card, Stack, Link, Alert, Tooltip, Container, Typography } from '@mui/material';
-// routes
-import { PATH_AUTH } from '../../routes/paths';
-// hooks
-import useAuth from '../../hooks/useAuth';
-import useResponsive from '../../hooks/useResponsive';
+import { Box, Card, Divider, Grid, Link, Stack, Typography } from '@mui/material';
// components
import Page from '../../components/Page';
-import Logo from '../../components/Logo';
import Image from '../../components/Image';
+import Logo from '../../components/Logo';
+// react
+import { useEffect, useState } from 'react';
// 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')]: {
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),
+ alignItems: 'center',
+}));
+
+const ContentStyle = styled(Card)(({ theme }) => ({
+ [theme.breakpoints.up('md')]: {
+ maxHeight: '600px',
+ maxWidth: '1000px',
+ },
}));
// ----------------------------------------------------------------------
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 (
-
-
- {smUp && (
-
- Has problem with your account? {''}
- {
- window.location.href = "mailto:admin@linksehat.com";
- e.preventDefault();
- }}>
- Contact Us
-
-
- )}
-
+
+
+
+
+
+
+
+
+
+
+ Sign in to LinkSehat
+
+
+ Enter your details below.
+
+
+
- {/* {mdUp && (
-
-
- Hi, Welcome Back
-
-
-
- )} */}
+ {formPhone ? (
+
+ ) : (
+
+ )}
-
-
-
-
-
- Sign in to LinkSehat
-
- Enter your details below.
-
+ Atau
-
- <>
-
- >
-
-
-
-
-
- {false && !smUp && (
-
- Don’t have an account?{' '}
-
- Get started
-
-
- )}
-
-
+
+ {formPhone ? (
+ handlerChange(event, false)}
+ >
+ Masuk menggunakan email
+
+ ) : (
+ handlerChange(event, true)}
+ >
+ Masuk menggunakan nomor handphone
+
+ )}
+
+
+
+
);
diff --git a/frontend/client-portal/src/pages/auth/Register.tsx b/frontend/client-portal/src/pages/auth/Register.tsx
deleted file mode 100755
index 89a364d3..00000000
--- a/frontend/client-portal/src/pages/auth/Register.tsx
+++ /dev/null
@@ -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 (
-
-
-
-
- {smUp && (
-
- Already have an account? {''}
-
- Login
-
-
- )}
-
-
- {mdUp && (
-
-
- Manage the job more effectively with Minimal
-
-
-
- )}
-
-
-
-
-
-
- Get started absolutely free.
-
-
- Free forever. No credit card needed.
-
-
-
- <>
-
- >
-
-
-
-
-
-
- By registering, I agree to Minimal
-
- Terms of Service
-
- {''}and{''}
-
- Privacy Policy
-
- .
-
-
- {!smUp && (
-
- Already have an account?{' '}
-
- Login
-
-
- )}
-
-
-
-
- );
-}
diff --git a/frontend/client-portal/src/pages/auth/ResetPassword.tsx b/frontend/client-portal/src/pages/auth/ResetPassword.tsx
deleted file mode 100755
index bd306e90..00000000
--- a/frontend/client-portal/src/pages/auth/ResetPassword.tsx
+++ /dev/null
@@ -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 (
-
-
-
-
-
-
- {!sent ? (
- <>
-
- Forgot your password?
-
-
-
- Please enter the email address associated with your account and We will email you
- a link to reset your password.
-
-
- setSent(true)}
- onGetEmail={(value) => setEmail(value)}
- />
-
-
- >
- ) : (
-
-
-
-
- Request sent successfully
-
-
-
- We have sent a confirmation email to
- {email}
-
- Please check your email.
-
-
-
-
- )}
-
-
-
-
- );
-}
diff --git a/frontend/client-portal/src/pages/auth/VerifyCode.tsx b/frontend/client-portal/src/pages/auth/VerifyCode.tsx
index 1ca75352..43cfacca 100755
--- a/frontend/client-portal/src/pages/auth/VerifyCode.tsx
+++ b/frontend/client-portal/src/pages/auth/VerifyCode.tsx
@@ -1,66 +1,109 @@
-import { Link as RouterLink } from 'react-router-dom';
+import { Link as RouterLink, useLocation, useNavigate } from 'react-router-dom';
// @mui
import { styled } from '@mui/material/styles';
-import { Box, Button, Link, Container, Typography } from '@mui/material';
-// layouts
-import LogoOnlyLayout from '../../layouts/LogoOnlyLayout';
-// routes
-import { PATH_AUTH } from '../../routes/paths';
+import { Box, Card, Divider, IconButton, Grid, Link, Stack, Typography } from '@mui/material';
// components
import Page from '../../components/Page';
+import Image from '../../components/Image';
import Iconify from '../../components/Iconify';
// sections
import { VerifyCodeForm } from '../../sections/auth/verify-code';
+import { useEffect } from 'react';
// ----------------------------------------------------------------------
const RootStyle = styled('div')(({ theme }) => ({
- display: 'flex',
- height: '100%',
+ [theme.breakpoints.up('md')]: {
+ display: 'flex',
+ },
+ minHeight: '100vh',
+ flexDirection: 'column',
+ justifyContent: '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() {
+ const { state } = useLocation();
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ if (state === null) {
+ navigate('/auth/login');
+ } else {
+ console.log(state);
+ }
+ }, [state, navigate]);
+
return (
-
+
-
+
+
+
+
+
+
+
+
+
+ navigate('/auth/login', { state: { formPhone: state.formPhone } })
+ }
+ >
+
+
+
+ Verifikasi OTP
+
+
+
+
+ Masukkan kode OTP anda disini
+
+
+
-
-
- }
- sx={{ mb: 3 }}
- >
- Back
-
-
-
- Please check your email!
-
-
- We have emailed a 6-digit confirmation code to acb@domain, please enter the code in
- below box to verify your email.
-
-
-
-
-
- Don’t have a code?
- {}}>
- Resend code
-
-
-
-
+ Atau
+
+
+ {state.formPhone === false ? (
+
+ navigate('/auth/login', { state: { formPhone: state.formPhone } })
+ }
+ >
+ Masuk menggunakan nomor handphone
+
+ ) : (
+
+ navigate('/auth/login', { state: { formPhone: state.formPhone } })
+ }
+ >
+ Masuk menggunakan email
+
+ )}
+
+
+
+
);
diff --git a/frontend/client-portal/src/routes/index.tsx b/frontend/client-portal/src/routes/index.tsx
index 671f5a36..789cca00 100755
--- a/frontend/client-portal/src/routes/index.tsx
+++ b/frontend/client-portal/src/routes/index.tsx
@@ -6,10 +6,6 @@ import LogoOnlyLayout from '../layouts/LogoOnlyLayout';
// components
import LoadingScreen from '../components/LoadingScreen';
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 AuthGuard from '../guards/AuthGuard';
@@ -42,11 +38,11 @@ export default function Router() {
),
},
{
- path: 'register',
+ path: 'verify-code',
element: (
-
+
),
@@ -54,7 +50,6 @@ export default function Router() {
// { path: 'login-unprotected', element: },
// { path: 'register-unprotected', element: },
// { path: 'reset-password', element: },
- // { path: 'verify', element: },
],
},
// {
@@ -68,29 +63,46 @@ export default function Router() {
- ),
- children:[
+
+ ),
+ children: [
{ element: , index: true },
{
path: 'dashboard',
element: ,
},
+ ],
+ },
+ {
+ path: '/members',
+ element: (
+
+
+
+
+
+ ),
+ children: [
{
- path: 'members',
element: ,
+ index: true,
},
- ]
+ {
+ path: 'create',
+ element: ,
+ },
+ ],
},
// {
// path: '/dashboard',
// element: ,
// children: [
// { element: , index: true },
- // { path: 'one', element:
+ // { path: 'one', element:
// },
- // { path: 'two', element:
+ // { path: 'two', element:
// },
- // { path: 'three', element:
+ // { path: 'three', element:
// },
// {
// path: 'user',
@@ -114,7 +126,9 @@ export default function Router() {
]);
}
+// Auth
const Login = Loadable(lazy(() => import('../pages/auth/Login')));
+const VerifyCode = Loadable(lazy(() => import('../pages/auth/VerifyCode')));
// Dashboard
const Dashboard = Loadable(lazy(() => import('../pages/Dashboard')));
@@ -122,4 +136,7 @@ const NotFound = Loadable(lazy(() => import('../pages/Page404')));
// Members
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')));
diff --git a/frontend/client-portal/src/sections/auth/login/LoginEmailForm.tsx b/frontend/client-portal/src/sections/auth/login/LoginEmailForm.tsx
new file mode 100755
index 00000000..73366a36
--- /dev/null
+++ b/frontend/client-portal/src/sections/auth/login/LoginEmailForm.tsx
@@ -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({
+ 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 (
+
+
+ Masukkan akun yang telah terdaftar
+ {!!errors.afterSubmit && {errors.afterSubmit.message}}
+
+
+
+
+
+ Login
+
+
+ );
+}
diff --git a/frontend/client-portal/src/sections/auth/login/LoginForm.tsx b/frontend/client-portal/src/sections/auth/login/LoginForm.tsx
deleted file mode 100755
index 8fba3741..00000000
--- a/frontend/client-portal/src/sections/auth/login/LoginForm.tsx
+++ /dev/null
@@ -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({
- 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 (
-
-
- Email : admin@linksehat.dev & Password : password
- {!!errors.afterSubmit && {errors.afterSubmit.message}}
-
-
-
-
- setShowPassword(!showPassword)} edge="end">
-
-
-
- ),
- }}
- />
-
-
-
-
-
- Forgot password?
-
-
-
-
- Login
-
-
- );
-}
diff --git a/frontend/client-portal/src/sections/auth/login/LoginPhoneForm.tsx b/frontend/client-portal/src/sections/auth/login/LoginPhoneForm.tsx
new file mode 100755
index 00000000..1ab770b2
--- /dev/null
+++ b/frontend/client-portal/src/sections/auth/login/LoginPhoneForm.tsx
@@ -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({
+ 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 (
+
+
+ Masukkan akun yang telah terdaftar
+ {!!errors.afterSubmit && {errors.afterSubmit.message}}
+
+ +62,
+ }}
+ />
+
+
+
+ Login
+
+
+ );
+}
diff --git a/frontend/client-portal/src/sections/auth/login/index.ts b/frontend/client-portal/src/sections/auth/login/index.ts
index 5c49358d..9722682f 100755
--- a/frontend/client-portal/src/sections/auth/login/index.ts
+++ b/frontend/client-portal/src/sections/auth/login/index.ts
@@ -1 +1,2 @@
-export { default as LoginForm } from './LoginForm';
+export { default as LoginEmailForm } from './LoginEmailForm';
+export { default as LoginPhoneForm } from './LoginPhoneForm';
\ No newline at end of file
diff --git a/frontend/client-portal/src/sections/auth/verify-code/VerifyCodeForm.tsx b/frontend/client-portal/src/sections/auth/verify-code/VerifyCodeForm.tsx
index cfe2ec6a..3af436dc 100755
--- a/frontend/client-portal/src/sections/auth/verify-code/VerifyCodeForm.tsx
+++ b/frontend/client-portal/src/sections/auth/verify-code/VerifyCodeForm.tsx
@@ -8,6 +8,7 @@ import { yupResolver } from '@hookform/resolvers/yup';
// @mui
import { OutlinedInput, Stack } from '@mui/material';
import { LoadingButton } from '@mui/lab';
+import useAuth from '../../../hooks/useAuth';
// routes
// import { PATH_DASHBOARD } from '../../../routes/paths';
@@ -18,14 +19,13 @@ type FormValuesProps = {
code2: string;
code3: 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 { validateOtp } = useAuth();
const { enqueueSnackbar } = useSnackbar();
@@ -34,8 +34,6 @@ export default function VerifyCodeForm() {
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 = {
@@ -43,8 +41,6 @@ export default function VerifyCodeForm() {
code2: '',
code3: '',
code4: '',
- code5: '',
- code6: '',
};
const {
@@ -68,12 +64,10 @@ export default function VerifyCodeForm() {
const onSubmit = async (data: FormValuesProps) => {
try {
- await new Promise((resolve) => setTimeout(resolve, 500));
- console.log('code:', Object.values(data).join(''));
-
+ await new Promise((resolve) => setTimeout(resolve, 750));
+ await validateOtp(phoneOrEmail, Object.values(data).join(''));
enqueueSnackbar('Verify success!');
-
- navigate('/dashboard', { replace: true });
+ navigate('/dashboard');
} catch (error) {
console.error(error);
}
@@ -114,8 +108,8 @@ export default function VerifyCodeForm() {
};
return (
-