Merge remote-tracking branch 'origin/staging' into origin/production

This commit is contained in:
Linksehat Staging Server
2024-05-28 10:56:28 +07:00
51 changed files with 3643 additions and 377 deletions

View File

@@ -115,13 +115,12 @@ export default function List() {
const handleClose = () => {
setAnchorEl(null);
};
const handleImportButton = () => {
const [paramImport, setParamImport] = useState('');
const handleImportButton = (param:any) => {
setParamImport(param);
if (importForm?.current) {
handleClose();
importForm.current ? importForm.current.click() : console.log('No File selected');
} else {
alert('No file selected');
}
};
@@ -144,12 +143,18 @@ export default function List() {
formData.append('file', importForm.current?.files[0]);
setImportLoading(true);
let url = 'claim-requests/import';
if(paramImport == 'invoice')
{
url = 'customer-service/request/import-invoice'
}
axios
.post(`claim-requests/import`, formData)
.post(`${url}`, formData)
.then((response) => {
handleCancelImportButton();
loadDataTableData();
// loadDataTableData();
setImportResult(response.data);
setParamImport(response.data.metaData);
// alert('Succesfully read '+ response.data.total_successed_row + ' with ' + response.data.total_failed_row + ' failed rows');
setImportLoading(false);
})
@@ -166,6 +171,28 @@ export default function List() {
}
};
const handleExportReportFiled = async () => {
await axios
.post('customer-service/request/exportFiledInvoice', { params: importResult?.data.result_rows })
.then((res) => {
enqueueSnackbar('Data berhasil di Export', {
variant: 'success',
anchorOrigin: { horizontal: 'right', vertical: 'top' },
});
setImportLoading(false);
document.location.href = res.data.data.file_url;
})
.catch((err) =>
enqueueSnackbar('Data Gagal di Export', {
variant: 'error',
anchorOrigin: { horizontal: 'right', vertical: 'top' },
})
);
};
const handleGetTemplate = (type :string) => {
axios.get('corporates/import-document-example/' + type)
.then((response) => {
@@ -220,9 +247,11 @@ export default function List() {
'aria-labelledby': 'basic-button',
}}
>
<MenuItem onClick={handleImportButton}>Import</MenuItem>
<MenuItem onClick={() => {handleImportButton('claim')}}>Import</MenuItem>
<MenuItem onClick={() => {handleGetTemplate('claim-request')}}>Download Template</MenuItem>
<MenuItem onClick={() => {handleGetData('data-plan-benefit')}}>Download Claim Request</MenuItem>
<MenuItem onClick={() => {handleImportButton('invoice')}}>Import Invoice</MenuItem>
<MenuItem onClick={() => {handleGetTemplate('final-log-invoice')}}>Download Template Invoice</MenuItem>
</Menu>
{/* <Button
variant="contained"
@@ -265,16 +294,35 @@ export default function List() {
</LoadingButton>
</Stack>
)}
{importResult && (
{importResult && importResult?.metaData == 'invoice' ? (
<Stack direction={'row'} sx={{ px: 2, pb: 2 }}>
<Box sx={{ color: 'text.secondary' }}>
Last Import Result :{' '}
<Box sx={{ color: 'success.main', display: 'inline' }}>
{importResult.data.total_success_row ?? 0}
</Box>{' '}
Row Processed,{' '}
<Box sx={{ color: 'error.main', display: 'inline' }}>
{importResult.data.total_failed_row}
</Box>{' '}
Failed,
{/* {importResult.data.failed_rows.map((row, index) => (
<Typography variant='body' key={index} color="error"> [Code={row.code ? row.code : 'Required'}]</Typography>
))} */}
&nbsp;Report:
&nbsp;<u onClick={handleExportReportFiled} style={{cursor:'pointer'}}>Download Data Result Import</u>
</Box>
</Stack>
) : importResult ? (
<Stack direction={'row'} sx={{ px: 2, pb: 2 }}>
<Box sx={{ color: 'text.secondary' }}>
Last Import Result Report :{' '}
<a href={importResult.result_file?.url ?? '#'}>
{importResult.result_file?.name ?? '-'}
Last Import Result Report {paramImport} :{' '}
<a href={importResult?.result_file?.url ?? '#'}>
{importResult?.result_file?.name ?? '-'}
</a>
</Box>
</Stack>
)}
):''}
</div>
);
}

View File

@@ -14,7 +14,7 @@
"txtDialogMember3" : "Detail",
"txtDialogMember4" : "Please select services",
"txtDialogMember5" : "Admission Date",
"txtDialogMember6" : "Please select admission date",
"txtDialogMember6" : "Please select admission date",
"txtWarningDischargeDate" : "Please select discharge date",
"txtCreateAt" : "Create at",
"txtDateBirth" : "Date of Birth",
@@ -59,5 +59,24 @@
"txtApprove": "Approve",
"txtDialogConfirmation": "Are you sure you want to proceed with this action?",
"txtStartDate": "Start Date",
"txtEndDate": "End Date"
"txtEndDate": "End Date",
"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

@@ -59,5 +59,23 @@
"txtApprove": "Terima",
"txtDialogConfirmation": "Apakah Anda yakin ingin melanjutkan tindakan ini?",
"txtStartDate": "Tanggal Mulai",
"txtEndDate": "Tanggal Akhir"
"txtEndDate": "Tanggal Akhir",
"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

@@ -26,7 +26,7 @@ export type JWTContextType = {
isInitialized: boolean;
user: AuthUser;
method: 'jwt';
login: (email: string, password: string) => Promise<void>;
login: (email: string, password: string, rememberMe: boolean) => Promise<void>;
register: (email: string, password: string, firstName: string, lastName: string) => Promise<void>;
logout: () => Promise<void>;
};

View File

@@ -1,7 +1,24 @@
const getLocalizedData = async (locale) => {
const response = await fetch(`/lang/${locale}.json`); // Mengambil file lokal berdasarkan bahasa yang dipilih
const data = await response.json();
return data;
// const getLocalizedData = async (locale) => {
// const response = await fetch(`/lang/${locale}.json`); // Mengambil file lokal berdasarkan bahasa yang dipilih
// const data = await response.json();
// return data;
// };
// export default getLocalizedData;
// LocalizationUtil.js
import idID from './lang/id-ID.json';
import enUS from './lang/en-US.json';
const localizedData = {
'id-ID': idID,
'en-US': enUS,
// Tambahkan bahasa lain sesuai kebutuhan
};
export default getLocalizedData;
const getLocalizedData = async (locale) => {
return localizedData[locale] || localizedData['id-ID'];
};
export default getLocalizedData;

View File

@@ -93,7 +93,7 @@ export default function Table<T>({
]);
params.setAppliedParams(parameters);
};
const { localeData }: any = useContext(LanguageContext);
/* -------------------------------------------------------------------------- */
@@ -106,7 +106,7 @@ export default function Table<T>({
return (
<TableHead>
<TableRow>
{selected.useSelected && selected.selectedRows.length > 0 ? (
{selected.useSelected && selected.selectedRows.length > 0 ? (
<>
<TableCell style={{ backgroundColor: '#D1F1F1', }} align="left" colSpan={selected.totRows} sx={{ padding: 2 }}>
<Grid container alignItems="center" justifyContent="space-between">
@@ -169,10 +169,10 @@ export default function Table<T>({
</TableCell>
))}
</>
)}
</TableRow>
</TableHead>
);
@@ -294,7 +294,7 @@ export default function Table<T>({
</form>
</Grid>
}
</Fragment>
) : null }
@@ -380,7 +380,7 @@ export default function Table<T>({
</Select>
</FormControl>
</Grid>
) : null }
) : null }
{/* Export Report */}
@@ -389,11 +389,11 @@ export default function Table<T>({
<FormControl fullWidth>
<Button variant='contained' sx={{p:2}}>
<Download />
<Typography variant='inherit' sx={{marginLeft: 1}}>Export</Typography>
<Typography variant='inherit' sx={{marginLeft: 1}}>Export</Typography>
</Button>
</FormControl>
</Grid>
) : null }
) : null }
</Grid>
</Grid>
@@ -428,7 +428,7 @@ export default function Table<T>({
</TableCell>
):(
<TableCell>
</TableCell>
))}
{headCells &&
@@ -443,7 +443,7 @@ export default function Table<T>({
))
) : (
<TableRow>
<TableCell colSpan={6} align="center">
<TableCell colSpan={headCells?.length} align="center">
{localeData.txtDataNotFound}
</TableCell>
</TableRow>

View File

@@ -5,7 +5,7 @@ export const LanguageContext = createContext();
export const LanguageProvider = ({ children }) => {
const [currentLocale, setCurrentLocale] = useState(localStorage.getItem('currentLocale') ? localStorage.getItem('currentLocale') : 'id-ID');
const [localeData, setLocaleData] = useState('id');
const cancelToken = useRef(null);
const cancelToken = useRef(null);
useEffect(() => {
const fetchData = async () => {

View File

@@ -2,7 +2,7 @@ import { createContext, ReactNode, useEffect, useReducer } from 'react';
// utils
import axios from '@/utils/axios';
// import { isValidToken, setSession } from '@/utils/jwt';
import { setSession, getSession, setUser, getUser } from '@/utils/token';
import { setSession, getSession, setUser, getUser, getCookie } from '@/utils/token';
// @types
import { ActionMap, AuthState, AuthUser, JWTContextType } from '@/@types/auth';
// ----------------------------------------------------------------------
@@ -86,12 +86,16 @@ function AuthProvider({ children }: AuthProviderProps) {
const initialize = async () => {
try {
const accessToken = getSession();
if (accessToken) {
setSession(accessToken);
const rememberMe = getCookie('rememberMe') == 'OK' ? false : true;
if (accessToken) {
const userString = getUser();
const storedUser = userString ? JSON.parse(userString) : null;
setUser(storedUser, rememberMe);
setSession(accessToken, rememberMe);
const response = await axios.get('/user');
const user = response.data;
const response = await axios.get('/user');
const user = response.data;
dispatch({
type: Types.Initial,
payload: {
@@ -126,16 +130,16 @@ function AuthProvider({ children }: AuthProviderProps) {
headers: {
'Accept': 'application/json',
'Content-Type' : 'application/json',
'Accept-Language': (localStorage.getItem('currentLocale') ? localStorage.getItem('currentLocale') : 'id-ID'),
'Accept-Language': localStorage.getItem('currentLocale') ?? 'id-ID',
},
};
const login = async (email: string, password: string) => axios
const login = async (email: string, password: string, rememberMe: boolean) => axios
.post('/login', { email, password }, headers)
.then((response) => {
const { user, token } = response.data.data;
setSession(token);
setUser(user);
setSession(token, rememberMe);
setUser(user, rememberMe);
dispatch({
type: Types.Login,
@@ -168,8 +172,9 @@ function AuthProvider({ children }: AuthProviderProps) {
};
const logout = async () => {
setSession(null);
setUser(null);
await axios.post('logout');
setSession(null, false);
setUser(null, false);
dispatch({ type: Types.Logout });
};
@@ -187,9 +192,9 @@ function AuthProvider({ children }: AuthProviderProps) {
);
// if (state.isInitialized) {
// return (!state.isAuthenticated && location.pathname !== '/auth/login') ?
// return (!state.isAuthenticated && location.pathname !== '/auth/login') ?
// (<Navigate to="/auth/login" replace={true} />)
// : false && location.pathname == '/auth/login' ?
// : false && location.pathname == '/auth/login' ?
// (<Navigate to="/dashboard" replace={true} />)
// : (
// <AuthContext.Provider

View File

@@ -0,0 +1,82 @@
{
"greeting": "Hello",
"buttonText": "Click Me",
"infoLogin": "Enter the registered account",
"txtLogin1" : "Sign in to Hospital Portal",
"txtLogin2" : "Enter your details below",
"txtCardSearchMember1" : "Membership Query",
"txtCardSearchMember2" : "Search Member",
"txtCardSearchMember3" : "Date of Birth",
"txtCardSearchMember4" : "Member ID",
"txtCardSearchMember5" : "Member",
"txtDialogMember1" : "Services",
"txtDialogMember2" : "Request LOG",
"txtDialogMember3" : "Detail",
"txtDialogMember4" : "Please select services",
"txtDialogMember5" : "Admission Date",
"txtDialogMember6" : "Please select admission date",
"txtWarningDischargeDate" : "Please select discharge date",
"txtCreateAt" : "Create at",
"txtDateBirth" : "Date of Birth",
"txtGender" : "Gender",
"txtMaritalStatus" : "Marital Status",
"txtLanguage" : "Language",
"txtRelationship" : "Relationship",
"txtRequestDate" : "Request Date",
"txtMemberID" : "Member ID",
"txtClaimCode" : "Claim Code",
"txtRequestCode" : "Request Code",
"txtName" : "Name",
"txtStatus" : "Status",
"txtSearch" : "Search Name or Member ID...",
"txtAll" : "All",
"txtSubmissionDate" : "Admission Date",
"txtDataNotFound" : "Data Not Found",
"txtConditionDocument" : "Condition Document",
"txtDiagnosisDokument" : "Diagnosis Dokument",
"txtSupportingResultDocument" : "Supporting Result Document",
"txtAddResult" : "Add Result",
"txtServiceType" : "Service Type",
"txtAdditionalDocuments" : "Additional Documents",
"txtAddNew" : "Add New",
"txtAddress" : "Address",
"txtProvider": "Provider",
"txtAlertProvider" : "Please enter provider",
"txtHelp" : "Need help?",
"txtContactUs" : "Contact Us",
"txtNotifications" : "Notifications",
"txtYouHave" : "You have",
"txtUnm" : "unread messages",
"txtNew" : "New",
"txtBeforeThat" : "Before that",
"txtDischargeDate" : "Discharge Date",
"txtPatner" : "Patner",
"txtSelected": "Selected",
"txtConfirmation": "Confirmation",
"txtReason": "Reason Decline",
"txtCancel": "Cancel",
"txtDecline": "Decline",
"txtApprove": "Approve",
"txtDialogConfirmation": "Are you sure you want to proceed with this action?",
"txtStartDate": "Start Date",
"txtEndDate": "End Date",
"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

@@ -0,0 +1,81 @@
{
"greeting": "Halo",
"buttonText": "Klik Saya",
"infoLogin": "Masukan akun yang telah terdaftar",
"txtLogin1" : "Masuk ke Hospital Portal",
"txtLogin2" : "Masukkan detail Anda di bawah ini",
"txtCardSearchMember1" : "Pengajuan Jaminan",
"txtCardSearchMember2" : "Cari Anggota",
"txtCardSearchMember3" : "Tanggal Lahir",
"txtCardSearchMember4" : "Member ID",
"txtCardSearchMember5" : "Member",
"txtDialogMember1" : "Layanan",
"txtDialogMember2" : "Request LOG",
"txtDialogMember3" : "Detail",
"txtDialogMember4" : "Mohon pilih layanan",
"txtDialogMember5" : "Tanggal Masuk",
"txtDialogMember6" : "Mohon pilih tanggal masuk",
"txtWarningDischargeDate" : "Mohon pilih tanggal keluar",
"txtCreateAt" : "Tanggal Buat",
"txtDateBirth" : "Tanggal Lahir",
"txtGender" : "Jenis Kelamin",
"txtMaritalStatus" : "Status Perkawinan",
"txtLanguage" : "Bahasa",
"txtRelationship" : "Hubungan",
"txtRequestDate" : "Tanggal Permintaan",
"txtMemberID" : "ID Anggota",
"txtClaimCode" : "Kode Klaim",
"txtRequestCode" : "Kode Pengajuan",
"txtName" : "Nama",
"txtStatus" : "Status",
"txtSearch" : "Cari Nama atau ID Anggota...",
"txtAll" : "Semua",
"txtSubmissionDate" : "Tanggal Masuk",
"txtDataNotFound" : "Data Tidak Ditemukan",
"txtConditionDocument" : "Dokumen Kondisi",
"txtDiagnosisDokument" : "Dokumen Diagnosis",
"txtSupportingResultDocument" : "Dokumen Pendukung",
"txtAddResult" : "Tambah Hasil",
"txtServiceType" : "Tipe Layanan",
"txtAdditionalDocuments" : "Dokumen Tambahan",
"txtAddNew" : "Tambah Baru",
"txtAddress" : "Alamat",
"txtProvider": "Provider",
"txtAlertProvider" : "Mohon masukan provider",
"txtHelp" : "Butuh Bantuan?",
"txtContactUs" : "Kontak Kami",
"txtNotifications" : "Notifikasi",
"txtYouHave" : "Anda memiliki",
"txtUnm" : "pesan yang belum dibaca",
"txtNew" : "Baru",
"txtBeforeThat" : "Sebelum",
"txtDischargeDate" : "Tanggal Keluar",
"txtPatner" : "Rekanan",
"txtSelected": "Terpilih",
"txtConfirmation": "Konfirmasi",
"txtReason": "Alasan Penolakan",
"txtCancel": "Batal",
"txtDecline": "Tolak",
"txtApprove": "Terima",
"txtDialogConfirmation": "Apakah Anda yakin ingin melanjutkan tindakan ini?",
"txtStartDate": "Tanggal Mulai",
"txtEndDate": "Tanggal Akhir",
"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

@@ -8,6 +8,8 @@ import { IconButtonAnimate } from '@/components/animate';
import { useNavigate } from 'react-router-dom';
import useAuth from '@/hooks/useAuth';
import { getUser } from '@/utils/token';
// ----------------------------------------------------------------------
const MENU_OPTIONS = [
@@ -45,6 +47,8 @@ export default function AccountPopover() {
navigate('/auth/login');
};
const userString = getUser();
const storedUser = userString ? JSON.parse(userString) : null;
return (
<>
<IconButtonAnimate
@@ -89,7 +93,7 @@ export default function AccountPopover() {
Hospital Admin
</Typography>
<Typography variant="body2" sx={{ color: 'text.secondary' }} noWrap>
hospitaladmin@gmail.com
{storedUser?.email}
</Typography>
</Box>

View File

@@ -12,6 +12,8 @@ import Iconify from '@/components/Iconify';
// sections
import { ForgetPasswordForm } from '@/sections/auth/forget-password';
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() {
const { localeData } = useContext(LanguageContext);
const [searchParams, setSearchParams] = useSearchParams();
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} />}
sx={{ mb: 3 }}
>
Back
{localeData.txtBack}
</Button>
<Typography variant="h3" paragraph></Typography>
<Typography sx={{ color: 'text.secondary' }}>
Please enter your new password.
{localeData.txtPleaseInput}
</Typography>
<Box sx={{ mt: 5, mb: 3 }}>

View File

@@ -78,29 +78,28 @@ export default function Login() {
const smUp = useResponsive("up", "sm");
const mdUp = useResponsive("up", "md");
const handleClick = () => {
window.location.href = 'https://wa.me/6285890008500';
};
return (
<Page title="Login">
<RootStyle>
<HeaderStyle>
{/*<Logo sx={{ width: 150, height: 150 }} />
<Logo sx={{ width: 150, height: 150, display: 'none' }} />
{smUp && (
<Typography variant="body2" sx={{ mt: { md: -2 } }}>
Has problem with your account? {""}
{localeData.txtHelp1} {""}
<Link
variant="subtitle2"
component={RouterLink}
to="#"
onClick={(e) => {
window.location.href =
"mailto:admin@linksehat.com";
e.preventDefault();
}}
onClick={handleClick}
>
Contact Us
{localeData.txtContactUs}
</Link>
</Typography>
)}*/}
)}
</HeaderStyle>
{/* {mdUp && (
@@ -116,7 +115,7 @@ export default function Login() {
/>
</SectionStyle>
)} */}
<Container maxWidth="sm">
<ContentStyle>
<Card sx={{padding:2}}>
@@ -125,7 +124,7 @@ export default function Login() {
alignItems="center"
sx={{ mb: 5 }}
>
<Logo sx={{ width: 90, height: 90 }} />
<Box sx={{ flexGrow: 1 }}>
<Typography variant="h4" gutterBottom>

View File

@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useState, useContext } from 'react';
import { Link as RouterLink } from 'react-router-dom';
// @mui
import { styled } from '@mui/material/styles';
@@ -13,6 +13,9 @@ import Page from '@/components/Page';
import { ResetPasswordForm } from '@/sections/auth/reset-password';
// 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() {
const { localeData } = useContext(LanguageContext);
const [email, setEmail] = useState('');
const [sent, setSent] = useState(false);
const navigate = useNavigate();
const handleSent = () => {
setSent(true);
};
return (
<Page title="Reset Password" sx={{ height: 1 }}>
<RootStyle>
@@ -41,16 +51,15 @@ export default function ResetPassword() {
{!sent ? (
<>
<Typography variant="h3" paragraph>
Forgot your password?
{localeData.txtForgotYourPassword}
</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.
{localeData.txtPleaseEnterPassword}
</Typography>
<ResetPasswordForm
onSent={() => setSent(true)}
onSent={handleSent}
onGetEmail={(value) => setEmail(value)}
/>
@@ -61,7 +70,7 @@ export default function ResetPassword() {
to={PATH_AUTH.login}
sx={{ mt: 3 }}
>
Back
{localeData.txtBack}
</Button>
</>
) : (
@@ -69,24 +78,24 @@ export default function ResetPassword() {
<SentIcon sx={{ mb: 5, mx: 'auto', height: 160 }} />
<Typography variant="h3" gutterBottom>
Request sent successfully
{localeData.txtSuccessSend}
</Typography>
<Typography>
We have sent a confirmation email to &nbsp;
{localeData.txtCodeConfirm} &nbsp;
<strong>{email}</strong>
<br />
Please check your email.
{localeData.txtPleasCheck}
</Typography>
<Button
size="large"
variant="contained"
component={RouterLink}
to={PATH_AUTH.login}
to={PATH_AUTH.verify}
sx={{ mt: 5 }}
>
Back
Next
</Button>
</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
import { styled } from '@mui/material/styles';
import { Box, Button, Link, Container, Typography } from '@mui/material';
@@ -11,6 +11,9 @@ import Page from '@/components/Page';
import Iconify from '@/components/Iconify';
// sections
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),
}));
function useQuery() {
return new URLSearchParams(useLocation().search);
}
// ----------------------------------------------------------------------
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 (
<Page title="Verify" sx={{ height: 1 }}>
<RootStyle>
@@ -34,30 +69,42 @@ export default function VerifyCode() {
<Button
size="small"
component={RouterLink}
to={PATH_AUTH.login}
to={PATH_AUTH.resetPassword}
startIcon={<Iconify icon={'eva:arrow-ios-back-fill'} width={20} height={20} />}
sx={{ mb: 3 }}
>
Back
{localeData.txtBack}
</Button>
<Typography variant="h3" paragraph>
Please check your email!
{localeData.txtCheckEmail}
</Typography>
<Typography variant='subtitle1'>{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.
{localeData.txtEmail}
</Typography>
<Box sx={{ mt: 5, mb: 3 }}>
<VerifyCodeForm />
<VerifyCodeForm
onGetEmail={email}
/>
</Box>
<Typography variant="body2" align="center">
{/* <Typography variant="body2" align="center">
Dont have a code? &nbsp;
<Link variant="subtitle2" underline="none" onClick={() => {}}>
Resend code
</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>
</Box>
</Container>

View File

@@ -54,7 +54,7 @@ export default function Router() {
// { path: 'register-unprotected', element: <Register /> },
{ path: 'reset-password', element: <ResetPassword /> },
{ path: 'forget-password', element: <ForgetPassword /> },
// { path: 'verify', element: <VerifyCode /> },
{ path: 'verify', element: <VerifyCode /> },
],
},
// {

View File

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

View File

@@ -60,7 +60,7 @@ export default function LoginForm() {
const onSubmit = async (data: FormValuesProps) => {
try {
const loginResult = await login(data.email, data.password);
const loginResult = await login(data.email, data.password, data.remember);
navigate('/dashboard');
} catch (error) {
@@ -100,10 +100,10 @@ export default function LoginForm() {
</Stack>
<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}>
Forgot password?
</Link>*/}
{localeData.txtLupaSandi}
</Link>
</Stack>
<LoadingButton
@@ -114,7 +114,7 @@ export default function LoginForm() {
loading={isSubmitting}
sx={{ marginTop: 2 }}
>
Login
{localeData.txtLogin}
</LoadingButton>
</FormProvider>
);

View File

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

View File

@@ -25,34 +25,98 @@ import axios from './axios';
// }, timeLeft);
// };
const setSession = (accessToken: string | null) => {
// let expiredCookie = 1/24/60; 1 menit
let expiredCookie = 12/24; //12 jam
const setCookie = (name:any, value:any, days:any) => {
let expires = "";
if (days) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
expires = "; expires=" + date.toUTCString();
}
document.cookie = name + "=" + decodeURIComponent(value || "") + expires + "; path=/; SameSite=Strict";
};
const setSession = (accessToken: string | null, rememberMe: boolean) => {
if (accessToken) {
localStorage.setItem('accessToken', accessToken);
const userString = getUser();
const storedUser = userString ? JSON.parse(userString) : null;
if(rememberMe)
{
localStorage.setItem('accessToken', accessToken);
}
else
{
setCookie('accessToken', accessToken, expiredCookie);
setCookie('rememberMe', 'OK', expiredCookie);
}
axios.defaults.headers.common.Authorization = `Bearer ${accessToken}`;
axios.defaults.headers.common['Accept-Language'] = (localStorage.getItem('currentLocale') ? localStorage.getItem('currentLocale') : 'id-ID');
axios.defaults.headers.common['Accept-Language'] = localStorage.getItem('currentLocale') ?? 'id-ID';
axios.defaults.headers.common['Accept'] = 'application/json';
axios.defaults.headers.common['Content-Type'] = 'application/json';
axios.defaults.headers.common['Organization-id'] = storedUser?.organization_id;
// This function below will handle when token is expired
// const { exp } = jwtDecode(accessToken);
// handleTokenExpired(exp);
} else {
localStorage.removeItem('accessToken');
removeCookie('accessToken');
removeCookie('rememberMe');
delete axios.defaults.headers.common.Authorization;
delete axios.defaults.headers.common['Accept-Language'];
delete axios.defaults.headers.common['Accept'];
delete axios.defaults.headers.common['Content-Type'];
delete axios.defaults.headers.common['Content-Type'];
}
};
const setUser = (user: any) => {
const setUser = (user: any, rememberMe: boolean) => {
if (user) {
localStorage.setItem('user', user);
if(rememberMe)
{
localStorage.setItem('user', JSON.stringify(user));
}
else
{
setCookie('user', JSON.stringify(user), expiredCookie);
setCookie('rememberMe', 'OK', expiredCookie);
}
} else {
localStorage.removeItem('user');
removeCookie('user');
removeCookie('rememberMe');
}
};
const getSession = () => window.localStorage.getItem('accessToken')
const getUser = () => window.localStorage.getItem('user')
const getCookie = (name:any) => {
const cookies = document.cookie.split('; ');
for (let i = 0; i < cookies.length; i++) {
const cookiePair = cookies[i].split('=');
if (cookiePair[0] === name) {
return decodeURIComponent(cookiePair[1]);
}
}
return null;
};
export { setSession, getSession, setUser, getUser };
const getSession = () => {
const localToken = window.localStorage.getItem('accessToken');
const cookieToken = getCookie('accessToken');
// Prioritaskan token dari localStorage
return localToken || cookieToken;
};
// const getUser = () => window.localStorage.getItem('user') || window.sessionStorage.getItem('user')
const getUser = () => {
const localUser = window.localStorage.getItem('user');
const cookieUser = getCookie('user');
// Prioritaskan token dari localStorage
return localUser || cookieUser;
};
const removeCookie = (name:any) => {
document.cookie = name + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
};
export { setSession, getSession, setUser, getUser, getCookie };