Update Add user hospital portal

This commit is contained in:
ivan-sim
2024-05-21 10:36:39 +07:00
parent 0cd2cf71fc
commit f50a4f6409
14 changed files with 366 additions and 114 deletions

View File

@@ -93,27 +93,6 @@ class AuthController extends Controller
} }
public function verifyEmail(Request $request) public function verifyEmail(Request $request)
{
$request->validate([
'email' => 'required|email',
]);
$user = User::query()
->where('email', $request->email)
->first();
if (!$user) {
return response(['Message' => 'User Tidak Ditemukan'], 404);
}
Event(new ForgetPassword($user));
// Mail::to($user->email)->send(new SendVerifyEmail($user));
return response()->json($user);
}
public function forgotPassword(Request $request)
{ {
$data = [ $data = [
'email' => $request->email, 'email' => $request->email,
@@ -144,7 +123,7 @@ class AuthController extends Controller
'user_id' => $user->id, 'user_id' => $user->id,
'email' => $emailTo, 'email' => $emailTo,
'title' => 'Forgot Password', 'title' => 'Forgot Password',
'description' => 'Request forgot password from App Doctor', 'description' => 'Request forgot password from Hospital Portal',
'type' => 1, 'type' => 1,
'isUnRead' => true, 'isUnRead' => true,
'created_by' => auth()->check() ? auth()->user()->id : null, 'created_by' => auth()->check() ? auth()->user()->id : null,
@@ -168,7 +147,7 @@ class AuthController extends Controller
$dataEmail = [ $dataEmail = [
'email' => $emailTo, 'email' => $emailTo,
'name' => $nameTo, 'name' => $nameTo,
'subject' => 'Request Forgot Password from App Doctor Date '. date('Y-m-d H:i:s'), 'subject' => 'Request Forgot Password from Hospital Portal Date '. date('Y-m-d H:i:s'),
'body' => View::make('email/forgot_password', ['token' => $token])->render(), 'body' => View::make('email/forgot_password', ['token' => $token])->render(),
]; ];
Helper::sendEmail($dataEmail); Helper::sendEmail($dataEmail);
@@ -176,7 +155,7 @@ class AuthController extends Controller
$res = DB::table('password_resets') $res = DB::table('password_resets')
->where('email', '=', $request->email) ->where('email', '=', $request->email)
->where('token', '=', $token) ->where('token', '=', $token)
->get(); ->first();
return ApiResponse::apiResponse("Success", $res, trans('Message.success'), 200); return ApiResponse::apiResponse("Success", $res, trans('Message.success'), 200);
} }
@@ -186,4 +165,131 @@ class AuthController extends Controller
} }
} }
} }
public function verifCode(Request $request)
{
$data = [
'email' => $request->email,
'token' => $request->token,
];
$validator = Validator::make($request->all(), [
'email' => 'required|email',
'token' => 'required|numeric',
], [
'email.required' => trans('Validation.required',['attribute' => 'Email']),
'email.email' => trans('Validation.email'),
'token.required' => trans('Validation.required',['attribute' => 'Token']),
'token.numeric' => trans('Validation.required',['attribute' => 'Code Numeric']),
]);
if ($validator->fails())
{
return ApiResponse::apiResponse('Bad Request', $data, $validator->errors(), 400);
}
else
{
//Check Time
$check = DB::table('password_resets')
->where('email', '=', $request->email)
->where('token', '=', $request->token)
->select('created_at')
->first();
if($check)
{
$created_at = strtotime($check->created_at); // Konversi string waktu ke UNIX timestamp
$now = time(); // Waktu sekarang dalam UNIX timestamp
// Hitung selisih waktu dalam menit
$diffInMinutes = ($now - $created_at) / 60;
if ($diffInMinutes > 60) {
return ApiResponse::apiResponse('Not Found', $data, trans('Message.token_expired'), 404);
} else {
// Lanjutkan dengan proses pemulihan kata sandi
return ApiResponse::apiResponse("Success", $data, trans('Message.success'), 200);
}
}
else
{
return ApiResponse::apiResponse('Not Found', $data, trans('Message.not_found'), 404);
}
}
}
public function forgetPassword(Request $request)
{
$data = [
'email' => $request->email,
'token' => $request->token,
'new_password' => $request->new_password
];
$validator = Validator::make($request->all(), [
'email' => 'required|email',
'token' => 'required|numeric',
'new_password' => [
'required',
'min:8',
'regex:/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$/'
]
], [
'email.required' => trans('Validation.required',['attribute' => 'Email']),
'email.email' => trans('Validation.email'),
'token.required' => trans('Validation.required',['attribute' => 'Token']),
'new_password.required' => trans('Validation.required',['attribute' => 'New Password']),
'new_password.min' => trans('Validation.min',['attribute' => 'New Password']),
'new_password.regex' => trans('Validation.regex',['attribute' => 'New Password']),
]);
if($request->new_password != $request->confirm_new_password)
{
return ApiResponse::apiResponse('Bad Request', $data, 'Confirm password is not the same', 400);
}
else if ($validator->fails())
{
return ApiResponse::apiResponse('Bad Request', $data, $validator->errors(), 400);
}
else
{
//Check Time
$check = DB::table('password_resets')
->where('email', '=', $request->email)
->where('token', '=', $request->token)
->select('created_at')
->first();
if($check)
{
$created_at = strtotime($check->created_at); // Konversi string waktu ke UNIX timestamp
$now = time(); // Waktu sekarang dalam UNIX timestamp
// Hitung selisih waktu dalam menit
$diffInMinutes = ($now - $created_at) / 60;
if ($diffInMinutes > 60) {
return ApiResponse::apiResponse('Not Found', $data, trans('Message.token_expired'), 404);
} else {
// Lanjutkan dengan proses pemulihan kata sandi
$user = User::where('email', $request->email)->first();
if ($user)
{
$newPassword = Hash::make($request->new_password);
$user->password = $newPassword;
$user->save();
return ApiResponse::apiResponse("Success", $data, trans('Message.success'), 200);
}
else
{
return ApiResponse::apiResponse('Not Found', $data, trans('Message.token_expired'), 404);
}
}
}
else
{
return ApiResponse::apiResponse('Not Found', $data, trans('Message.not_found'), 404);
}
}
}
} }

View File

@@ -29,8 +29,9 @@ Route::prefix('v1')->group(function() {
}); });
}); });
Route::post('forgot-password', [AuthController::class, 'forgotPassword']); Route::post('forget-password', [AuthController::class, 'forgetPassword']);
// Route::post('verify-email', [AuthController::class, 'verifyEmail'])->name('verify-email'); Route::post('verify-email', [AuthController::class, 'verifyEmail'])->name('verify-email');
Route::post('verify-code', [AuthController::class, 'verifCode']);
Route::middleware('auth:sanctum')->group(function () { Route::middleware('auth:sanctum')->group(function () {

View File

@@ -60,5 +60,23 @@
"txtDialogConfirmation": "Are you sure you want to proceed with this action?", "txtDialogConfirmation": "Are you sure you want to proceed with this action?",
"txtStartDate": "Start Date", "txtStartDate": "Start Date",
"txtEndDate": "End Date", "txtEndDate": "End Date",
"txtHelp1" : "Has problem with your account?" "txtHelp1" : "Has problem with your account?",
"txtLupaSandi": "Forgot password?",
"txtIngatkanSaya": "Remember me",
"txtLogin": "Login",
"txtForgotYourPassword": "Forgot your password?",
"txtPleaseEnterPassword": "Please enter the email address associated with your account and We will email you a code to reset your password.",
"txtBack": "Back",
"txtSuccessSend": "Request sent successfully",
"txtCodeConfirm": "We have sent a confirmation email to",
"txtPleasCheck": "Please check your email.",
"txtCheckEmail": "Please check your email!",
"txtEmail": "We have emailed a 6-digit confirmation code and check spam folder, please enter the code in below box to verify your email.",
"txtDont": "Dont have a code?",
"txtResendCode": "Resend code",
"txtSecond": "Second",
"txtPleaseInput": "Please enter your new password.",
"txtNewPassword": "New Password",
"txtConfPassword": "Confirm Kata Sandi"
} }

View File

@@ -60,5 +60,22 @@
"txtDialogConfirmation": "Apakah Anda yakin ingin melanjutkan tindakan ini?", "txtDialogConfirmation": "Apakah Anda yakin ingin melanjutkan tindakan ini?",
"txtStartDate": "Tanggal Mulai", "txtStartDate": "Tanggal Mulai",
"txtEndDate": "Tanggal Akhir", "txtEndDate": "Tanggal Akhir",
"txtHelp1" : "Punya masalah dengan akun Anda?" "txtHelp1" : "Punya masalah dengan akun Anda?",
"txtLupaSandi": "Lupa sandi?",
"txtIngatkanSaya": "Ingatkan saya",
"txtLogin": "Masuk",
"txtForgotYourPassword": "Lupa password anda?",
"txtPleaseEnterPassword": "Silakan masukkan alamat email yang terkait dengan akun Anda dan Kami akan mengirimkan email berisi kode untuk mengatur ulang kata sandi Anda.",
"txtBack": "Kembali",
"txtSuccessSend": "Permintaan berhasil dikirim",
"txtCodeConfirm": "Kami telah mengirimkan email konfirmasi ke",
"txtPleasCheck": "Mohon cek email Anda.",
"txtCheckEmail": "Mohon periksa email Anda!",
"txtEmail": "Kami telah mengirimkan kode konfirmasi 6 digit melalui email cek juga difolder spam, silakan masukkan kode di kotak bawah ini untuk memverifikasi email Anda.",
"txtDont": "Tidak mendapatkan kode?",
"txtResendCode": "Kirim ulang kode",
"txtSecond": "Detik",
"txtPleaseInput": "Mohon masukan kata sandi baru Anda.",
"txtNewPassword": "Kata Sandi Baru",
"txtConfPassword": "Konfirmasi Kata Sandi"
} }

View File

@@ -12,6 +12,8 @@ import Iconify from '@/components/Iconify';
// sections // sections
import { ForgetPasswordForm } from '@/sections/auth/forget-password'; import { ForgetPasswordForm } from '@/sections/auth/forget-password';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
import { useState, useContext, useEffect } from 'react';
import { LanguageContext } from '@/contexts/LanguageContext';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -25,6 +27,8 @@ const RootStyle = styled('div')(({ theme }) => ({
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export default function ForgetPassword() { export default function ForgetPassword() {
const { localeData } = useContext(LanguageContext);
const [searchParams, setSearchParams] = useSearchParams(); const [searchParams, setSearchParams] = useSearchParams();
const token = searchParams.get('token'); const token = searchParams.get('token');
@@ -42,12 +46,12 @@ export default function ForgetPassword() {
startIcon={<Iconify icon={'eva:arrow-ios-back-fill'} width={20} height={20} />} startIcon={<Iconify icon={'eva:arrow-ios-back-fill'} width={20} height={20} />}
sx={{ mb: 3 }} sx={{ mb: 3 }}
> >
Back {localeData.txtBack}
</Button> </Button>
<Typography variant="h3" paragraph></Typography> <Typography variant="h3" paragraph></Typography>
<Typography sx={{ color: 'text.secondary' }}> <Typography sx={{ color: 'text.secondary' }}>
Please enter your new password. {localeData.txtPleaseInput}
</Typography> </Typography>
<Box sx={{ mt: 5, mb: 3 }}> <Box sx={{ mt: 5, mb: 3 }}>

View File

@@ -1,4 +1,4 @@
import { useState } from 'react'; import { useState, useContext } from 'react';
import { Link as RouterLink } from 'react-router-dom'; import { Link as RouterLink } from 'react-router-dom';
// @mui // @mui
import { styled } from '@mui/material/styles'; import { styled } from '@mui/material/styles';
@@ -13,6 +13,9 @@ import Page from '@/components/Page';
import { ResetPasswordForm } from '@/sections/auth/reset-password'; import { ResetPasswordForm } from '@/sections/auth/reset-password';
// assets // assets
import { SentIcon } from '@/assets'; import { SentIcon } from '@/assets';
import { LanguageContext } from '@/contexts/LanguageContext';
import { useNavigate } from 'react-router-dom'; // Import useNavigate
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -27,10 +30,17 @@ const RootStyle = styled('div')(({ theme }) => ({
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export default function ResetPassword() { export default function ResetPassword() {
const { localeData } = useContext(LanguageContext);
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
const [sent, setSent] = useState(false); const [sent, setSent] = useState(false);
const navigate = useNavigate();
const handleSent = () => {
setSent(true);
};
return ( return (
<Page title="Reset Password" sx={{ height: 1 }}> <Page title="Reset Password" sx={{ height: 1 }}>
<RootStyle> <RootStyle>
@@ -41,16 +51,15 @@ export default function ResetPassword() {
{!sent ? ( {!sent ? (
<> <>
<Typography variant="h3" paragraph> <Typography variant="h3" paragraph>
Forgot your password? {localeData.txtForgotYourPassword}
</Typography> </Typography>
<Typography sx={{ color: 'text.secondary', mb: 5 }}> <Typography sx={{ color: 'text.secondary', mb: 5 }}>
Please enter the email address associated with your account and We will email you {localeData.txtPleaseEnterPassword}
a link to reset your password.
</Typography> </Typography>
<ResetPasswordForm <ResetPasswordForm
onSent={() => setSent(true)} onSent={handleSent}
onGetEmail={(value) => setEmail(value)} onGetEmail={(value) => setEmail(value)}
/> />
@@ -61,7 +70,7 @@ export default function ResetPassword() {
to={PATH_AUTH.login} to={PATH_AUTH.login}
sx={{ mt: 3 }} sx={{ mt: 3 }}
> >
Back {localeData.txtBack}
</Button> </Button>
</> </>
) : ( ) : (
@@ -69,24 +78,24 @@ export default function ResetPassword() {
<SentIcon sx={{ mb: 5, mx: 'auto', height: 160 }} /> <SentIcon sx={{ mb: 5, mx: 'auto', height: 160 }} />
<Typography variant="h3" gutterBottom> <Typography variant="h3" gutterBottom>
Request sent successfully {localeData.txtSuccessSend}
</Typography> </Typography>
<Typography> <Typography>
We have sent a confirmation email to &nbsp; {localeData.txtCodeConfirm} &nbsp;
<strong>{email}</strong> <strong>{email}</strong>
<br /> <br />
Please check your email. {localeData.txtPleasCheck}
</Typography> </Typography>
<Button <Button
size="large" size="large"
variant="contained" variant="contained"
component={RouterLink} component={RouterLink}
to={PATH_AUTH.login} to={PATH_AUTH.verify}
sx={{ mt: 5 }} sx={{ mt: 5 }}
> >
Back Next
</Button> </Button>
</Box> </Box>
)} )}

View File

@@ -1,4 +1,4 @@
import { Link as RouterLink } from 'react-router-dom'; import { Link as RouterLink, useLocation } 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, Button, Link, Container, Typography } from '@mui/material';
@@ -11,6 +11,9 @@ import Page from '@/components/Page';
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 { useState, useContext, useEffect } from 'react';
import { LanguageContext } from '@/contexts/LanguageContext';
import axios from '@/utils/axios';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -21,9 +24,41 @@ const RootStyle = styled('div')(({ theme }) => ({
padding: theme.spacing(12, 0), padding: theme.spacing(12, 0),
})); }));
function useQuery() {
return new URLSearchParams(useLocation().search);
}
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export default function VerifyCode() { export default function VerifyCode() {
const { localeData } = useContext(LanguageContext);
const query = useQuery();
const email = query.get('email');
const [timer, setTimer] = useState(60); // Initialize timer with 60 seconds
const [canResend, setCanResend] = useState(false); // State to control resend button visibility
useEffect(() => {
const interval = setInterval(() => {
setTimer((prev) => {
if (prev > 0) {
return prev - 1;
} else {
clearInterval(interval);
setCanResend(true); // Enable resend button when timer reaches 0
return 0;
}
});
}, 1000);
return () => clearInterval(interval); // Cleanup interval on component unmount
}, []);
const handleResend = () => {
setCanResend(false);
setTimer(60); // Reset timer to 60 seconds
// Add logic to resend the code here
axios.post('/verify-email', {email: email});
};
return ( return (
<Page title="Verify" sx={{ height: 1 }}> <Page title="Verify" sx={{ height: 1 }}>
<RootStyle> <RootStyle>
@@ -34,30 +69,42 @@ export default function VerifyCode() {
<Button <Button
size="small" size="small"
component={RouterLink} component={RouterLink}
to={PATH_AUTH.login} to={PATH_AUTH.resetPassword}
startIcon={<Iconify icon={'eva:arrow-ios-back-fill'} width={20} height={20} />} startIcon={<Iconify icon={'eva:arrow-ios-back-fill'} width={20} height={20} />}
sx={{ mb: 3 }} sx={{ mb: 3 }}
> >
Back {localeData.txtBack}
</Button> </Button>
<Typography variant="h3" paragraph> <Typography variant="h3" paragraph>
Please check your email! {localeData.txtCheckEmail}
</Typography> </Typography>
<Typography variant='subtitle1'>{email}</Typography>
<Typography sx={{ color: 'text.secondary' }}> <Typography sx={{ color: 'text.secondary' }}>
We have emailed a 6-digit confirmation code to acb@domain, please enter the code in {localeData.txtEmail}
below box to verify your email.
</Typography> </Typography>
<Box sx={{ mt: 5, mb: 3 }}> <Box sx={{ mt: 5, mb: 3 }}>
<VerifyCodeForm /> <VerifyCodeForm
onGetEmail={email}
/>
</Box> </Box>
<Typography variant="body2" align="center"> {/* <Typography variant="body2" align="center">
Dont have a code? &nbsp; Dont have a code? &nbsp;
<Link variant="subtitle2" underline="none" onClick={() => {}}> <Link variant="subtitle2" underline="none" onClick={() => {}}>
Resend code Resend code
</Link> </Link>
</Typography> */}
<Typography variant="body2" align="center">
{localeData.txtDont} &nbsp;
{canResend ? (
<Link sx={{cursor: 'pointer'}} variant="subtitle2" underline="none" onClick={handleResend}>
{localeData.txtResendCode}
</Link>
) : (
<span>{`${localeData.txtResendCode} ${timer} ${localeData.txtSecond}`}</span>
)}
</Typography> </Typography>
</Box> </Box>
</Container> </Container>

View File

@@ -52,9 +52,9 @@ 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: 'forgot-password', element: <ResetPassword /> }, { path: 'reset-password', element: <ResetPassword /> },
{ path: 'forget-password', element: <ForgetPassword /> }, { path: 'forget-password', element: <ForgetPassword /> },
// { path: 'verify', element: <VerifyCode /> }, { path: 'verify', element: <VerifyCode /> },
], ],
}, },
// { // {
@@ -117,7 +117,7 @@ export default function Router() {
} }
const Login = Loadable(lazy(() => import('@/pages/auth/Login'))); const Login = Loadable(lazy(() => import('@/pages/auth/Login')));
const ResetPassword = Loadable(lazy(() => import('@/pages/auth/VerifyCode'))); const ResetPassword = Loadable(lazy(() => import('@/pages/auth/ResetPassword')));
const ForgetPassword = Loadable(lazy(() => import('@/pages/auth/ForgetPassword'))); const ForgetPassword = Loadable(lazy(() => import('@/pages/auth/ForgetPassword')));
// Dashboard // Dashboard

View File

@@ -12,5 +12,5 @@ export const PATH_AUTH = {
loginUnprotected: path(ROOTS_AUTH, '/login-unprotected'), loginUnprotected: path(ROOTS_AUTH, '/login-unprotected'),
registerUnprotected: path(ROOTS_AUTH, '/register-unprotected'), registerUnprotected: path(ROOTS_AUTH, '/register-unprotected'),
verify: path(ROOTS_AUTH, '/verify'), verify: path(ROOTS_AUTH, '/verify'),
resetPassword: path(ROOTS_AUTH, '/forgot-password'), resetPassword: path(ROOTS_AUTH, '/reset-password'),
}; };

View File

@@ -2,8 +2,8 @@ import * as Yup from 'yup';
// form // form
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
import { useForm } from 'react-hook-form'; import { useForm } from 'react-hook-form';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useCallback, useEffect, useContext, useMemo, useRef, useState } from 'react';
import { Link as RouterLink, useNavigate } from 'react-router-dom'; import { Link as RouterLink, useNavigate, useLocation } from 'react-router-dom';
// @mui // @mui
import { Alert, IconButton, InputAdornment, Stack, Typography } from '@mui/material'; import { Alert, IconButton, InputAdornment, Stack, Typography } from '@mui/material';
@@ -14,6 +14,9 @@ import useIsMountedRef from '@/hooks/useIsMountedRef';
import { FormProvider, RHFTextField } from '@/components/hook-form'; import { FormProvider, RHFTextField } from '@/components/hook-form';
import axios from '@/utils/axios'; import axios from '@/utils/axios';
import Iconify from '@/components/Iconify'; import Iconify from '@/components/Iconify';
import { LanguageContext } from '@/contexts/LanguageContext';
import { useSnackbar } from 'notistack';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -26,9 +29,19 @@ type Props = {
token: string; token: string;
}; };
function useQuery() {
return new URLSearchParams(useLocation().search);
}
export default function ForgetPasswordForm({ token }: Props) { export default function ForgetPasswordForm({ token }: Props) {
const { localeData } = useContext(LanguageContext);
const isMountedRef = useIsMountedRef(); const isMountedRef = useIsMountedRef();
const navigate = useNavigate(); const navigate = useNavigate();
const query = useQuery();
const email = query.get('email');
const token_ = query.get('token');
const { enqueueSnackbar } = useSnackbar();
const [showPasswordNew, setShowPasswordNew] = useState(false); const [showPasswordNew, setShowPasswordNew] = useState(false);
const [showPasswordConfirmNew, setShowPasswordConfirmNew] = useState(false); const [showPasswordConfirmNew, setShowPasswordConfirmNew] = useState(false);
@@ -47,19 +60,19 @@ export default function ForgetPasswordForm({ token }: Props) {
const onSubmit = async (data: FormValuesProps) => { const onSubmit = async (data: FormValuesProps) => {
try { try {
await axios.post('/forget-password', { ...data, token }); await axios.post('/forget-password', { ...data, email:email, token:token_ });
console.log(data);
// await new Promise((resolve) => setTimeout(resolve, 500)); // await new Promise((resolve) => setTimeout(resolve, 500));
await new Promise((resolve) => setTimeout(resolve, 500)); await new Promise((resolve) => setTimeout(resolve, 500));
if (isMountedRef.current) { if (isMountedRef.current) {
enqueueSnackbar('Password reset was successful', { variant: 'success' });
navigate('/auth/login', { replace: true }); navigate('/auth/login', { replace: true });
} }
} catch (error) { } catch (error) {
console.log(error.response.data);
if (isMountedRef.current) { if (isMountedRef.current) {
setError('afterSubmit', { ...error, message: error.response.data.message }); setError('afterSubmit', { ...error, message: error.response.data.meta.message });
} }
} }
}; };
@@ -68,10 +81,10 @@ export default function ForgetPasswordForm({ token }: Props) {
<FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}> <FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={3}> <Stack spacing={3}>
{!!errors.afterSubmit && <Alert severity="error">{errors.afterSubmit.message}</Alert>} {!!errors.afterSubmit && <Alert severity="error">{errors.afterSubmit.message}</Alert>}
<Typography>Kata Sandi Baru</Typography> <Typography>{localeData.txtNewPassword}</Typography>
<RHFTextField <RHFTextField
name="new_password" name="new_password"
label="Kata Sandi Baru" label={localeData.txtNewPassword}
type={showPasswordNew ? 'text' : 'password'} type={showPasswordNew ? 'text' : 'password'}
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (
@@ -83,10 +96,10 @@ export default function ForgetPasswordForm({ token }: Props) {
), ),
}} }}
/> />
<Typography>Konfirmasi Kata Sandi </Typography> <Typography>{localeData.txtConfPassword}</Typography>
<RHFTextField <RHFTextField
name="confirm_new_password" name="confirm_new_password"
label="Konfirmasi Kata Sandi" label={localeData.txtConfPassword}
type={showPasswordConfirmNew ? 'text' : 'password'} type={showPasswordConfirmNew ? 'text' : 'password'}
InputProps={{ InputProps={{
endAdornment: ( endAdornment: (

View File

@@ -100,9 +100,9 @@ export default function LoginForm() {
</Stack> </Stack>
<Stack direction="row" alignItems="center" justifyContent="space-between" sx={{ my: 2 }}> <Stack direction="row" alignItems="center" justifyContent="space-between" sx={{ my: 2 }}>
<RHFCheckbox name="remember" label="Remember me"/> <RHFCheckbox name="remember" label={localeData.txtIngatkanSaya}/>
<Link component={RouterLink} variant="subtitle2" to={PATH_AUTH.resetPassword}> <Link component={RouterLink} variant="subtitle2" to={PATH_AUTH.resetPassword}>
Forgot password? {localeData.txtLupaSandi}
</Link> </Link>
</Stack> </Stack>
@@ -114,7 +114,7 @@ export default function LoginForm() {
loading={isSubmitting} loading={isSubmitting}
sx={{ marginTop: 2 }} sx={{ marginTop: 2 }}
> >
Login {localeData.txtLogin}
</LoadingButton> </LoadingButton>
</FormProvider> </FormProvider>
); );

View File

@@ -10,6 +10,7 @@ import useIsMountedRef from '@/hooks/useIsMountedRef';
// components // components
import { FormProvider, RHFTextField } from '@/components/hook-form'; import { FormProvider, RHFTextField } from '@/components/hook-form';
import axios from '@/utils/axios'; import axios from '@/utils/axios';
import { useNavigate } from 'react-router-dom';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -26,6 +27,8 @@ type Props = {
export default function ResetPasswordForm({ onSent, onGetEmail }: Props) { export default function ResetPasswordForm({ onSent, onGetEmail }: Props) {
const isMountedRef = useIsMountedRef(); const isMountedRef = useIsMountedRef();
const navigate = useNavigate();
const ResetPasswordSchema = Yup.object().shape({ const ResetPasswordSchema = Yup.object().shape({
email: Yup.string().email('Email must be a valid email address').required('Email is required'), email: Yup.string().email('Email must be a valid email address').required('Email is required'),
}); });
@@ -43,19 +46,22 @@ export default function ResetPasswordForm({ onSent, onGetEmail }: Props) {
const onSubmit = async (data: FormValuesProps) => { const onSubmit = async (data: FormValuesProps) => {
try { try {
await axios.post('/verify-email', data); const response = await axios.post('/verify-email', data);
console.log(data); if(response.data.data.email)
{
onGetEmail(response.data.data.email);
navigate(`/auth/verify?email=${response.data.data.email}`);
}
// await new Promise((resolve) => setTimeout(resolve, 500)); // await new Promise((resolve) => setTimeout(resolve, 500));
await new Promise((resolve) => setTimeout(resolve, 500)); await new Promise((resolve) => setTimeout(resolve, 500));
if (isMountedRef.current) { if (isMountedRef.current) {
onSent(); onSent();
onGetEmail(data.email); onGetEmail(response.data.data.email);
} }
} catch (error) { } catch (error) {
console.log(error.response.data);
if (isMountedRef.current) { if (isMountedRef.current) {
setError('afterSubmit', { ...error, message: error.response.data.message }); setError('afterSubmit', { ...error, message: error.response.data.meta.message });
} }
} }
}; };

View File

@@ -6,9 +6,12 @@ import { useEffect } from 'react';
import { useForm, Controller } from 'react-hook-form'; import { useForm, Controller } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup'; import { yupResolver } from '@hookform/resolvers/yup';
// @mui // @mui
import { OutlinedInput, Stack } from '@mui/material'; import { OutlinedInput, Alert, Stack } from '@mui/material';
import useIsMountedRef from '@/hooks/useIsMountedRef';
import { LoadingButton } from '@mui/lab'; import { LoadingButton } from '@mui/lab';
import { FormProvider, RHFTextField } from '@/components/hook-form';
// routes // routes
import axios from '@/utils/axios';
// import { PATH_DASHBOARD } from '@/routes/paths'; // import { PATH_DASHBOARD } from '@/routes/paths';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
@@ -23,8 +26,11 @@ type FormValuesProps = {
}; };
type ValueNames = 'code1' | 'code2' | 'code3' | 'code4' | 'code5' | 'code6'; type ValueNames = 'code1' | 'code2' | 'code3' | 'code4' | 'code5' | 'code6';
type Props = {
export default function VerifyCodeForm() { onGetEmail: (value: string) => void;
};
export default function VerifyCodeForm({ onGetEmail }: Props) {
const isMountedRef = useIsMountedRef();
const navigate = useNavigate(); const navigate = useNavigate();
const { enqueueSnackbar } = useSnackbar(); const { enqueueSnackbar } = useSnackbar();
@@ -51,8 +57,9 @@ export default function VerifyCodeForm() {
watch, watch,
control, control,
setValue, setValue,
setError,
handleSubmit, handleSubmit,
formState: { isSubmitting, isValid }, formState: { isSubmitting, isValid , errors},
} = useForm({ } = useForm({
mode: 'onBlur', mode: 'onBlur',
resolver: yupResolver(VerifyCodeSchema), resolver: yupResolver(VerifyCodeSchema),
@@ -67,16 +74,36 @@ export default function VerifyCodeForm() {
}, []); }, []);
const onSubmit = async (data: FormValuesProps) => { const onSubmit = async (data: FormValuesProps) => {
// try {
// await new Promise((resolve) => setTimeout(resolve, 500));
// console.log('code:', Object.values(data).join(''));
// enqueueSnackbar('Verify success!', { variant: 'success' });
// navigate('/dashboard', { replace: true });
// } catch (error) {
// console.error(error);
// }
try { try {
await new Promise((resolve) => setTimeout(resolve, 500)); const response = await axios.post('/verify-code', {email: onGetEmail, token: Object.values(data).join('')});
console.log('code:', Object.values(data).join('')); // await new Promise((resolve) => setTimeout(resolve, 500));
await new Promise((resolve) => setTimeout(resolve, 500));
enqueueSnackbar('Verify success!', { variant: 'success' }); if(response.data.meta.code === 200)
{
navigate('/dashboard', { replace: true }); navigate(`/auth/forget-password?email=${response.data.data.email}&token=${response.data.data.token}`);
} catch (error) { }
console.error(error); if (isMountedRef.current) {
} if(response.data.meta.code === 404)
{
setError('afterSubmit', { ...response, message: response.data.meta.message });
}
}
} catch (error) {
if (isMountedRef.current) {
setError('afterSubmit', { ...error, message: error.response.data.meta.message });
}
}
}; };
const handlePasteClipboard = (event: ClipboardEvent) => { const handlePasteClipboard = (event: ClipboardEvent) => {
@@ -115,35 +142,38 @@ export default function VerifyCodeForm() {
return ( return (
<form onSubmit={handleSubmit(onSubmit)}> <form onSubmit={handleSubmit(onSubmit)}>
<Stack direction="row" spacing={2} justifyContent="center"> <Stack spacing={3}>
{Object.keys(values).map((name, index) => ( {!!errors.afterSubmit && <Alert severity="error">{errors.afterSubmit.message}</Alert>}
<Controller <Stack direction="row" spacing={2} justifyContent="center">
key={name} {Object.keys(values).map((name, index) => (
name={`code${index + 1}` as ValueNames} <Controller
control={control} key={name}
render={({ field }) => ( name={`code${index + 1}` as ValueNames}
<OutlinedInput control={control}
{...field} render={({ field }) => (
id="field-code" <OutlinedInput
autoFocus={index === 0} {...field}
placeholder="-" id="field-code"
onChange={(event: React.ChangeEvent<HTMLInputElement>) => autoFocus={index === 0}
handleChangeWithNextField(event, field.onChange) placeholder="-"
} onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
inputProps={{ handleChangeWithNextField(event, field.onChange)
maxLength: 1, }
sx: { inputProps={{
p: 0, maxLength: 1,
textAlign: 'center', sx: {
width: { xs: 36, sm: 56 }, p: 0,
height: { xs: 36, sm: 56 }, textAlign: 'center',
}, width: { xs: 36, sm: 56 },
}} height: { xs: 36, sm: 56 },
/> },
)} }}
/> />
))} )}
</Stack> />
))}
</Stack>
</Stack>
<LoadingButton <LoadingButton
fullWidth fullWidth

View File

@@ -25,7 +25,8 @@ import axios from './axios';
// }, timeLeft); // }, timeLeft);
// }; // };
let expiredCookie = '12 * 60'; // let expiredCookie = 1/24/60; 1 menit
let expiredCookie = 12/24; //12 jam
const setCookie = (name:any, value:any, days:any) => { const setCookie = (name:any, value:any, days:any) => {
let expires = ""; let expires = "";