revamp template

This commit is contained in:
fro1991
2025-02-01 16:44:51 +07:00
parent c432df568a
commit 276a289580
1212 changed files with 112762 additions and 0 deletions

31
src/auth/AuthPage.tsx Normal file
View File

@ -0,0 +1,31 @@
import { Navigate, Route, Routes } from 'react-router';
import {
Login,
ResetPassword,
ResetPasswordChange,
ResetPasswordChanged,
ResetPasswordCheckEmail,
ResetPasswordEnterEmail,
TwoFactorAuth
} from './pages/jwt';
import { AuthBrandedLayout } from '@/layouts/auth-branded';
import { CheckEmail } from '@/auth/pages/jwt';
const AuthPage = () => (
<Routes>
<Route element={<AuthBrandedLayout />}>
<Route index element={<Login />} />
<Route path="/login" element={<Login />} />
<Route path="/2fa" element={<TwoFactorAuth />} />
<Route path="/check-email" element={<CheckEmail />} />
<Route path="/reset-password" element={<ResetPassword />} />
<Route path="/reset-password/enter-email" element={<ResetPasswordEnterEmail />} />
<Route path="/reset-password/check-email" element={<ResetPasswordCheckEmail />} />
<Route path="/reset-password/:id" element={<ResetPasswordChange />} />
<Route path="/reset-password/changed" element={<ResetPasswordChanged />} />
<Route path="*" element={<Navigate to="/error/404" />} />
</Route>
</Routes>
);
export { AuthPage };

18
src/auth/RequireAuth.tsx Normal file
View File

@ -0,0 +1,18 @@
import { Navigate, Outlet, useLocation } from 'react-router-dom';
import { ScreenLoader } from '@/components/loaders';
import { useAuthContext } from './useAuthContext';
const RequireAuth = () => {
const { auth, loading } = useAuthContext();
const location = useLocation();
if (loading) {
return <ScreenLoader />;
}
return auth ? <Outlet /> : <Navigate to="/auth/login" state={{ from: location }} replace />;
};
export { RequireAuth };

68
src/auth/_helpers.ts Normal file
View File

@ -0,0 +1,68 @@
import { User as Auth0UserModel } from '@auth0/auth0-spa-js';
import { getData, setData } from '@/utils';
import { type AuthModel } from './_models';
const AUTH_LOCAL_STORAGE_KEY = `${import.meta.env.VITE_APP_NAME}-auth-v${
import.meta.env.VITE_APP_VERSION
}`;
const getAuth = (): AuthModel | undefined => {
try {
const auth = getData(AUTH_LOCAL_STORAGE_KEY) as AuthModel | undefined;
if (auth) {
return auth;
} else {
return undefined;
}
} catch (error) {
console.error('AUTH LOCAL STORAGE PARSE ERROR', error);
}
};
const setAuth = (auth: AuthModel | Auth0UserModel) => {
setData(AUTH_LOCAL_STORAGE_KEY, auth);
};
const removeAuth = () => {
if (!localStorage) {
return;
}
try {
localStorage.removeItem(AUTH_LOCAL_STORAGE_KEY);
} catch (error) {
console.error('AUTH LOCAL STORAGE REMOVE ERROR', error);
}
};
export function setupAxios(axios: any) {
axios.defaults.headers.Accept = 'application/json';
axios.interceptors.request.use(
(config: { headers: { Authorization: string }; params?: any; url?: any }) => {
const auth = getAuth();
if (auth?.access_token) {
config.headers.Authorization = `Bearer ${auth.access_token}`;
if (
config.url &&
!config.url.includes('api/b/') &&
!config.url.includes('api/bg/') &&
!config.url.includes('api/c/')
) {
config.params = {
...config.params,
token: auth.access_token || ''
};
}
}
return config;
},
async (err: any) => await Promise.reject(err)
);
}
export { AUTH_LOCAL_STORAGE_KEY, getAuth, removeAuth, setAuth };

34
src/auth/_models.ts Normal file
View File

@ -0,0 +1,34 @@
import { type TLanguageCode } from '@/i18n';
export interface AuthModel {
id: string;
access_token: string;
refresh_token: string;
role_name: string;
roles_list: [];
company: any;
user: any;
// api_token: string;
}
export interface UserModel {
id: number;
username: string;
name: string;
password: string | undefined;
email: string;
id_role: string;
role_name: string;
roles_list: [];
token: AuthModel;
first_name: string;
last_name: string;
fullname?: string;
occupation?: string;
companyName?: string;
phone?: string;
roles?: number[];
pic?: string;
language?: TLanguageCode;
}

5
src/auth/index.ts Normal file
View File

@ -0,0 +1,5 @@
export * from './_helpers';
export * from './_models';
export * from './AuthPage';
export * from './RequireAuth';
export * from './useAuthContext';

View File

@ -0,0 +1,49 @@
import { Link } from 'react-router-dom';
import { toAbsoluteUrl } from '@/utils';
const CheckEmail = () => {
return (
<div className="card max-w-[440px] w-full">
<div className="card-body p-10">
<div className="flex justify-center py-10">
<img
src={toAbsoluteUrl('/media/illustrations/30.svg')}
className="dark:hidden max-h-[130px]"
alt=""
/>
<img
src={toAbsoluteUrl('/media/illustrations/30-dark.svg')}
className="light:hidden max-h-[130px]"
alt=""
/>
</div>
<h3 className="text-lg font-medium text-gray-900 text-center mb-3">Check your email</h3>
<div className="text-2sm text-center text-gray-700 mb-7.5">
Please click the link sent to your email&nbsp;
<a href="#" className="text-2sm text-gray-900 font-medium hover:text-primary-active">
bob@keenthemes.com
</a>
<br />
to verify your account. Thank you
</div>
<div className="flex justify-center mb-5">
<Link to="/" className="btn btn-primary flex justify-center">
Back to Home
</Link>
</div>
<div className="flex items-center justify-center gap-1">
<span className="text-xs text-gray-700">Didnt receive an email?</span>
<Link to="/auth/classic/login" className="text-xs font-medium link">
Resend
</Link>
</div>
</div>
</div>
);
};
export { CheckEmail };

View File

@ -0,0 +1,189 @@
import { type MouseEvent, useState } from 'react';
import { Link, useLocation, useNavigate } from 'react-router-dom';
import clsx from 'clsx';
import * as Yup from 'yup';
import { useFormik } from 'formik';
import { KeenIcon } from '@/components';
import { toAbsoluteUrl } from '@/utils';
import { useAuthContext } from '@/auth';
import { useLayout } from '@/providers';
import { Alert } from '@/components';
import moment from 'moment';
const loginSchema = Yup.object().shape({
username: Yup.string().required('Username is required'),
password: Yup.string()
.min(3, 'Minimum 3 symbols')
.max(50, 'Maximum 50 symbols')
.required('Password is required'),
remember: Yup.boolean()
});
const initialValues = {
username: '',
password: '',
application: 'credit',
remember: false
};
const Login = () => {
const [loading, setLoading] = useState(false);
const { login } = useAuthContext();
const navigate = useNavigate();
const location = useLocation();
const from = location.state?.from?.pathname || '/';
const [showPassword, setShowPassword] = useState(false);
const { currentLayout } = useLayout();
const formik = useFormik({
initialValues,
validationSchema: loginSchema,
onSubmit: async (values, { setStatus, setSubmitting }) => {
setLoading(true);
try {
if (!login) {
throw new Error('JWTProvider is required for this form.');
}
await login(values.username, values.password, values.application);
console.log(login);
if (values.remember) {
localStorage.setItem('username', values.username);
} else {
localStorage.removeItem('username');
}
navigate(from, { replace: true });
} catch (error: any) {
if (error.response && error.response.data) {
setStatus(error.response.data.message);
} else {
setStatus('The login details are incorrect');
}
setSubmitting(false);
}
setLoading(false);
}
});
const togglePassword = (event: MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
setShowPassword(!showPassword);
};
return (
<div className="card max-w-[390px] border-0 shadow-none w-full">
<form className="card-body flex flex-col gap-5 p-5" onSubmit={formik.handleSubmit} noValidate>
<div className="flex align-center justify-center text-center mb-2.5">
<img
src={toAbsoluteUrl('/media/app/bri_tl_logo.png')}
className="h-[50px] max-w-none mb-2 text-center"
alt=""
/>
</div>
<div className="flex flex-col gap-3">
<div className="flex gap-3 items-center">
<div>
<img
src={toAbsoluteUrl('/media/app/brilian_apps_logo.png')}
className="h-[40px] max-w-none"
alt=""
/>
</div>
<h3 className="font-semibold text-gray-700" style={{ fontSize: '18px' }}>
/ Pengajuan Kredit
</h3>
</div>
</div>
{formik.status && <Alert variant="danger">{formik.status}</Alert>}
<div className="flex flex-col gap-1">
<label className="form-label text-gray-900 ps-2.5">Login</label>
<label className="input">
<input
placeholder="Enter username"
autoComplete="off"
{...formik.getFieldProps('username')}
className={clsx('form-control', {
'is-invalid': formik.touched.username && formik.errors.username
})}
/>
</label>
{formik.touched.username && formik.errors.username && (
<span role="alert" className="text-danger text-xs mt-1">
{formik.errors.username}
</span>
)}
</div>
<div className="flex flex-col gap-1">
<div className="flex items-center justify-between gap-1">
<label className="form-label text-gray-900 ps-2.5">Password</label>
</div>
<label className="input">
<input
type={showPassword ? 'text' : 'password'}
placeholder="Enter Password"
autoComplete="off"
{...formik.getFieldProps('password')}
className={clsx('form-control', {
'is-invalid': formik.touched.password && formik.errors.password
})}
/>
<button className="btn btn-icon" onClick={togglePassword}>
<KeenIcon icon="eye" className={clsx('text-gray-500', { hidden: showPassword })} />
<KeenIcon
icon="eye-slash"
className={clsx('text-gray-500', { hidden: !showPassword })}
/>
</button>
</label>
{formik.touched.password && formik.errors.password && (
<span role="alert" className="text-danger text-xs mt-1">
{formik.errors.password}
</span>
)}
</div>
<div className="flex items-center justify-between gap-1">
<label className="checkbox-group">
<input
className="checkbox checkbox-sm"
type="checkbox"
{...formik.getFieldProps('remember')}
/>
<span className="checkbox-label">Remember me</span>
</label>
<Link
to={
currentLayout?.name === 'auth-branded'
? '/auth/reset-password'
: '/auth/classic/reset-password'
}
className="text-2sm link shrink-0"
>
Forgot Password?
</Link>
</div>
<button
type="submit"
className="btn btn-primary flex justify-center grow"
disabled={loading || formik.isSubmitting}
>
{loading ? 'Please wait...' : 'Sign In'}
</button>
<div>
<p className="text-2sm" style={{ fontSize: '12px', letterSpacing: 0.25 }}>
Copyright {moment().year()} &copy; Bank Rakyat Indonesia Timor Leste.All rights
reserved.
</p>
</div>
</form>
</div>
);
};
export { Login };

View File

@ -0,0 +1,76 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
import { toAbsoluteUrl } from '@/utils';
import { KeenIcon } from '@/components';
const TwoFactorAuth = () => {
const [codeInputs, setCodeInputs] = useState(Array(6).fill(''));
const handleInputChange = (index: number, value: string) => {
if (value.length > 1) return;
const updatedInputs = [...codeInputs];
updatedInputs[index] = value;
setCodeInputs(updatedInputs);
};
return (
<div className="card max-w-[380px] w-full">
<form className="card-body flex flex-col gap-5 p-10">
<img
src={toAbsoluteUrl('/media/illustrations/34.svg')}
className="dark:hidden h-20 mb-2"
alt=""
/>
<img
src={toAbsoluteUrl('/media/illustrations/34-dark.svg')}
className="light:hidden h-20 mb-2"
alt=""
/>
<div className="text-center mb-2">
<h3 className="text-lg font-medium text-gray-900 mb-5">Verify your phone</h3>
<div className="flex flex-col">
<span className="text-2sm text-gray-700 mb-1.5">
Enter the verification code we sent to
</span>
<a href="#" className="text-sm font-medium text-gray-900">
****** 7859
</a>
</div>
</div>
<div className="flex flex-wrap justify-center gap-2.5">
{codeInputs.map((value, index) => (
<input
key={index}
type="text"
maxLength={1}
className="input focus:border-primary-clarity focus:ring focus:ring-primary-clarity size-10 shrink-0 px-0 text-center"
value={value}
onChange={(e) => handleInputChange(index, e.target.value)}
/>
))}
</div>
<div className="flex items-center justify-center mb-2">
<span className="text-xs text-gray-700 me-1.5">Didnt receive a code? (37s)</span>
<Link to="/auth/classic/login" className="text-xs link">
Resend
</Link>
</div>
<button className="btn btn-primary flex justify-center grow">Continue</button>
<Link
to="/auth/login"
className="flex items-center justify-center text-sm gap-2 text-gray-700 hover:text-primary"
>
<KeenIcon icon="black-left" />
Back to Login
</Link>
</form>
</div>
);
};
export { TwoFactorAuth };

View File

@ -0,0 +1,4 @@
export * from './reset-password';
export * from './Login';
export * from './CheckEmail';
export * from './TwoFactorAuth';

View File

@ -0,0 +1,134 @@
import clsx from 'clsx';
import { useFormik } from 'formik';
import { useState } from 'react';
import { Link } from 'react-router-dom';
import * as Yup from 'yup';
import { useNavigate } from 'react-router-dom';
import { useAuthContext } from '@/auth/useAuthContext';
import { Alert, KeenIcon } from '@/components';
import { useLayout } from '@/providers';
import { AxiosError } from 'axios';
const initialValues = {
email: ''
};
const forgotPasswordSchema = Yup.object().shape({
email: Yup.string()
.email('Wrong email format')
.min(3, 'Minimum 3 symbols')
.max(50, 'Maximum 50 symbols')
.required('Email is required')
});
const ResetPassword = () => {
const [loading, setLoading] = useState(false);
const [hasErrors, setHasErrors] = useState<boolean | undefined>(undefined);
const { requestPasswordResetLink } = useAuthContext();
const { currentLayout } = useLayout();
const navigate = useNavigate();
const formik = useFormik({
initialValues,
validationSchema: forgotPasswordSchema,
onSubmit: async (values, { setStatus, setSubmitting }) => {
setLoading(true);
setHasErrors(undefined);
try {
if (!requestPasswordResetLink) {
throw new Error('JWTProvider is required for this form.');
}
await requestPasswordResetLink(values.email);
setHasErrors(false);
setLoading(false);
const params = new URLSearchParams();
params.append('email', values.email);
navigate({
pathname:
currentLayout?.name === 'auth-branded'
? '/auth/reset-password/check-email'
: '/auth/classic/reset-password/check-email',
search: params.toString()
});
} catch (error) {
if (error instanceof AxiosError && error.response) {
setStatus(error.response.data.message);
} else {
setStatus('Password reset failed. Please try again.');
}
setHasErrors(true);
setLoading(false);
setSubmitting(false);
}
}
});
return (
<div className="card max-w-[370px] w-full">
<form
className="card-body flex flex-col gap-5 p-10"
noValidate
onSubmit={formik.handleSubmit}
>
<div className="text-center">
<h3 className="text-lg font-semibold text-gray-900">Your Email</h3>
<span className="text-2sm text-gray-600 font-medium">
Enter your email to reset password
</span>
</div>
{hasErrors && <Alert variant="danger">{formik.status}</Alert>}
{hasErrors === false && (
<Alert variant="success">
Password reset link sent. Please check your email to proceed
</Alert>
)}
<div className="flex flex-col gap-1">
<label className="form-label text-gray-900">Email</label>
<label className="input">
<input
type="email"
placeholder="email@email.com"
autoComplete="off"
{...formik.getFieldProps('email')}
className={clsx(
'form-control bg-transparent',
{ 'is-invalid': formik.touched.email && formik.errors.email },
{
'is-valid': formik.touched.email && !formik.errors.email
}
)}
/>
</label>
{formik.touched.email && formik.errors.email && (
<span role="alert" className="text-danger text-xs mt-1">
{formik.errors.email}
</span>
)}
</div>
<div className="flex flex-col gap-5 items-stretch">
<button
type="submit"
className="btn btn-primary flex justify-center grow"
disabled={loading || formik.isSubmitting}
>
{loading ? 'Please wait...' : 'Continue'}
</button>
<Link
to={currentLayout?.name === 'auth-branded' ? '/auth/login' : '/auth/classic/login'}
className="flex items-center justify-center text-sm gap-2 text-gray-700 hover:text-primary"
>
<KeenIcon icon="black-left" />
Back to Login
</Link>
</div>
</form>
</div>
);
};
export { ResetPassword };

View File

@ -0,0 +1,173 @@
import { useFormik } from 'formik';
import * as Yup from 'yup';
import { Alert, KeenIcon } from '@/components';
import { useAuthContext } from '@/auth';
import { useState } from 'react';
import clsx from 'clsx';
import { useNavigate } from 'react-router-dom';
import { useLayout } from '@/providers';
import { AxiosError } from 'axios';
import { useParams } from 'react-router-dom';
const passwordSchema = Yup.object().shape({
newPassword: Yup.string()
.min(6, 'Password must be at least 6 characters')
.required('New password is required'),
confirmPassword: Yup.string()
.oneOf([Yup.ref('newPassword')], 'Passwords must match')
.required('Please confirm your new password')
});
const ResetPasswordChange = () => {
const { currentLayout } = useLayout();
const { changePassword } = useAuthContext();
const navigate = useNavigate();
const [loading, setLoading] = useState(false);
const [hasErrors, setHasErrors] = useState<boolean | undefined>(undefined);
const [showNewPassword, setShowNewPassword] = useState(false);
const [showNewPasswordConfirmation, setShowNewPasswordConfirmation] = useState(false);
const { id } = useParams();
const formik = useFormik({
initialValues: {
newPassword: '',
confirmPassword: ''
},
validationSchema: passwordSchema,
onSubmit: async (values, { setStatus, setSubmitting }) => {
setLoading(true);
setHasErrors(undefined);
const token = id;
console.log(token);
if (!token) {
setHasErrors(true);
setStatus('Token and email properties are required');
setLoading(false);
setSubmitting(false);
return;
}
try {
await changePassword(token, values.newPassword, values.confirmPassword);
setHasErrors(false);
navigate(
currentLayout?.name === 'auth-branded'
? '/auth/reset-password/changed'
: '/auth/classic/reset-password/changed'
);
} catch (error) {
if (error instanceof AxiosError && error.response) {
setStatus(error.response.data.message);
} else {
setStatus('Password reset failed. Please try again.');
}
setHasErrors(true);
} finally {
setLoading(false);
setSubmitting(false);
}
}
});
return (
<div className="card max-w-[370px] w-full">
<form
className="card-body flex flex-col gap-5 p-10"
onSubmit={formik.handleSubmit}
noValidate
>
<div className="text-center">
<h3 className="text-lg font-medium text-gray-900">Reset Password</h3>
<span className="text-2sm text-gray-700">Enter your new password</span>
</div>
{hasErrors && <Alert variant="danger">{formik.status}</Alert>}
<div className="flex flex-col gap-1">
<label className="form-label text-gray-900">New Password</label>
<label className="input">
<input
type={showNewPassword ? 'text' : 'password'}
placeholder="Enter a new password"
autoComplete="off"
{...formik.getFieldProps('newPassword')}
className={clsx(
'form-control bg-transparent',
{ 'is-invalid': formik.touched.newPassword && formik.errors.newPassword },
{ 'is-valid': formik.touched.newPassword && !formik.errors.newPassword }
)}
/>
<button
className="btn btn-icon"
onClick={(e) => {
e.preventDefault();
setShowNewPassword(!showNewPassword);
}}
>
<KeenIcon icon="eye" className={clsx('text-gray-500', { hidden: showNewPassword })} />
<KeenIcon
icon="eye-slash"
className={clsx('text-gray-500', { hidden: !showNewPassword })}
/>
</button>
</label>
{formik.touched.newPassword && formik.errors.newPassword && (
<span role="alert" className="text-danger text-xs mt-1">
{formik.errors.newPassword}
</span>
)}
</div>
<div className="flex flex-col gap-1">
<label className="form-label font-normal text-gray-900">Confirm New Password</label>
<label className="input">
<input
type={showNewPasswordConfirmation ? 'text' : 'password'}
placeholder="Re-enter a new Password"
autoComplete="off"
{...formik.getFieldProps('confirmPassword')}
className={clsx(
'form-control bg-transparent',
{ 'is-invalid': formik.touched.confirmPassword && formik.errors.confirmPassword },
{ 'is-valid': formik.touched.confirmPassword && !formik.errors.confirmPassword }
)}
/>
<button
className="btn btn-icon"
onClick={(e) => {
e.preventDefault();
setShowNewPasswordConfirmation(!showNewPasswordConfirmation);
}}
>
<KeenIcon
icon="eye"
className={clsx('text-gray-500', { hidden: showNewPasswordConfirmation })}
/>
<KeenIcon
icon="eye-slash"
className={clsx('text-gray-500', { hidden: !showNewPasswordConfirmation })}
/>
</button>
</label>
{formik.touched.confirmPassword && formik.errors.confirmPassword && (
<span role="alert" className="text-danger text-xs mt-1">
{formik.errors.confirmPassword}
</span>
)}
</div>
<button
type="submit"
className="btn btn-primary flex justify-center grow"
disabled={loading}
>
{loading ? 'Please wait...' : 'Submit'}
</button>
</form>
</div>
);
};
export { ResetPasswordChange };

View File

@ -0,0 +1,46 @@
import { Link } from 'react-router-dom';
import { toAbsoluteUrl } from '@/utils';
import { useLayout } from '@/providers';
const ResetPasswordChanged = () => {
const { currentLayout } = useLayout();
return (
<div className="card max-w-[440px] w-full">
<div className="card-body p-10">
<div className="flex justify-center mb-5">
<img
src={toAbsoluteUrl('/media/illustrations/32.svg')}
className="dark:hidden max-h-[180px]"
alt=""
/>
<img
src={toAbsoluteUrl('/media/illustrations/32-dark.svg')}
className="light:hidden max-h-[180px]"
alt=""
/>
</div>
<h3 className="text-lg font-medium text-gray-900 text-center mb-4">
Your password is changed
</h3>
<div className="text-2sm text-center text-gray-700 mb-7.5">
Your password has been successfully updated.
<br />
Your account's security is our priority.
</div>
<div className="flex justify-center">
<Link
to={currentLayout?.name === 'auth-branded' ? '/auth/login' : '/auth/classic/login'}
className="btn btn-primary"
>
Sign in
</Link>
</div>
</div>
</div>
);
};
export { ResetPasswordChanged };

View File

@ -0,0 +1,72 @@
import { Link } from 'react-router-dom';
import { toAbsoluteUrl } from '@/utils';
import { useLayout } from '@/providers';
import { useEffect, useState } from 'react';
const ResetPasswordCheckEmail = () => {
const { currentLayout } = useLayout();
const [email, setEmail] = useState<string | null>(null);
useEffect(() => {
setEmail(new URLSearchParams(window.location.search).get('email'));
}, []);
return (
<div className="card max-w-[440px] w-full">
<div className="card-body p-10">
<div className="flex justify-center py-10">
<img
src={toAbsoluteUrl('/media/illustrations/30.svg')}
className="dark:hidden max-h-[130px]"
alt=""
/>
<img
src={toAbsoluteUrl('/media/illustrations/30-dark.svg')}
className="light:hidden max-h-[130px]"
alt=""
/>
</div>
<h3 className="text-lg font-medium text-gray-900 text-center mb-3">Check your email</h3>
<div className="text-2sm text-center text-gray-700 mb-7.5">
Please click the link sent to your email{' '}
<a href="#" className="text-2sm text-gray-800 font-medium hover:text-primary-active">
{email}
</a>
<br />
to reset your password. Thank you
</div>
<div className="flex justify-center mb-5">
<Link
to={
currentLayout?.name === 'auth-branded'
? '/auth/reset-password/changed'
: '/auth/classic/reset-password/changed'
}
className="btn btn-primary flex justify-center"
>
Skip for now
</Link>
</div>
<div className="flex items-center justify-center gap-1">
<span className="text-xs text-gray-600">Didnt receive an email?</span>
<Link
to={
currentLayout?.name === 'auth-branded'
? '/auth/reset-password/enter-email'
: '/auth/classic/reset-password/enter-email'
}
className="text-xs font-medium link"
>
Resend
</Link>
</div>
</div>
</div>
);
};
export { ResetPasswordCheckEmail };

View File

@ -0,0 +1,45 @@
import { useState } from 'react';
import { Link } from 'react-router-dom';
import { KeenIcon } from '@/components';
import { useLayout } from '@/providers';
const ResetPasswordEnterEmail = () => {
const { currentLayout } = useLayout();
const [searchInput, setSearchInput] = useState('');
return (
<div className="card max-w-[370px] w-full">
<form className="card-body flex flex-col gap-5 p-10">
<div className="text-center">
<h3 className="text-lg font-medium text-gray-900">Your Email</h3>
<span className="text-2sm text-gray-700">Enter your email to reset password</span>
</div>
<div className="flex flex-col gap-1">
<label className="form-label font-normal text-gray-900">Email</label>
<input
className="input"
type="text"
placeholder="email@email.com"
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
/>
</div>
<Link
to={
currentLayout?.name === 'auth-branded'
? '/auth/reset-password/check-email'
: '/auth/classic/reset-password/check-email'
}
className="btn btn-primary flex justify-center grow"
>
Continue
<KeenIcon icon="black-right" />
</Link>
</form>
</div>
);
};
export { ResetPasswordEnterEmail };

View File

@ -0,0 +1,5 @@
export * from './ResetPassword';
export * from './ResetPasswordChange';
export * from './ResetPasswordChanged';
export * from './ResetPasswordCheckEmail';
export * from './ResetPasswordEnterEmail';

View File

@ -0,0 +1,147 @@
/* eslint-disable no-unused-vars */
import axios, { AxiosResponse } from 'axios';
import {
createContext,
type Dispatch,
type PropsWithChildren,
type SetStateAction,
useEffect,
useState
} from 'react';
import * as authHelper from '../_helpers';
import { type AuthModel, type UserModel } from '@/auth';
import { apiConfig } from '@/config/api.config';
import { doSaveLogActivity } from '@/actions/GlobalActions';
const API_URL = apiConfig.service_user;
export const LOGIN_URL = `${API_URL}/login`;
export const FORGOT_PASSWORD_URL = `${API_URL}/reset_password`;
export const RESET_PASSWORD_URL = `${API_URL}/update_password`;
export const GET_USER_URL = `${API_URL}/user/detail`;
interface AuthContextProps {
loading: boolean;
setLoading: Dispatch<SetStateAction<boolean>>;
auth: AuthModel | undefined;
saveAuth: (auth: AuthModel | undefined) => void;
currentUser: UserModel | undefined;
setCurrentUser: Dispatch<SetStateAction<UserModel | undefined>>;
login: (email: string, password: string, application: string) => Promise<void>;
requestPasswordResetLink: (email: string) => Promise<void>;
changePassword: (token: string, password: string, password_confirmation: string) => Promise<void>;
getUser: () => Promise<AxiosResponse<any> | {}>;
logout: () => void;
verify: () => Promise<void>;
}
const AuthContext = createContext<AuthContextProps | null>(null);
const AuthProvider = ({ children }: PropsWithChildren) => {
const [loading, setLoading] = useState(true);
const [auth, setAuth] = useState<AuthModel | undefined>(authHelper.getAuth());
const [currentUser, setCurrentUser] = useState<UserModel | undefined>();
const verify = async () => {
if (auth) {
try {
const { data: user } = await getUser();
const createCacheUser = {
name: user.name,
email: user.email,
username: user.username,
role_name: auth.role_name
};
localStorage.setItem('user', JSON.stringify(createCacheUser));
} catch {
saveAuth(undefined);
setCurrentUser(undefined);
}
}
};
const saveAuth = (auth: AuthModel | undefined) => {
setAuth(auth);
if (auth) {
authHelper.setAuth(auth);
} else {
authHelper.removeAuth();
}
};
const login = async (username: string, password: string, application: string) => {
try {
const { data: auth } = await axios
.post(LOGIN_URL, { username, password, application })
.then((response) => response.data);
// console.log('auth :', auth);
saveAuth({ ...auth.token, id: auth.user.id, role_name: auth.role_name, user: auth.user });
setCurrentUser(auth.user);
const createActivity = {
module: 'Login',
description: `Login`,
action: 'l'
};
doSaveLogActivity(createActivity);
} catch (error: any) {
throw error;
}
};
const requestPasswordResetLink = async (email: string) => {
await axios.put(FORGOT_PASSWORD_URL + '/' + email + '/credit/');
};
const changePassword = async (token: string, password: string, retype_password: string) => {
await axios.put(`${RESET_PASSWORD_URL}/${token}`, {
password,
retype_password
});
};
const getUser = async () => {
let _axios = await axios
.get(`${GET_USER_URL}/${authHelper.getAuth()?.id}`)
.then((response) => response.data.data);
return { data: _axios };
};
const logout = async () => {
const createActivity = {
module: 'Logout',
description: `Logout`,
action: 'O'
};
await doSaveLogActivity(createActivity);
saveAuth(undefined);
setCurrentUser(undefined);
};
return (
<AuthContext.Provider
value={{
loading,
setLoading,
auth,
saveAuth,
currentUser,
setCurrentUser,
login,
// register,
requestPasswordResetLink,
changePassword,
getUser,
logout,
verify
}}
>
{children}
</AuthContext.Provider>
);
};
export { AuthContext, AuthProvider };

View File

@ -0,0 +1,10 @@
import { useContext } from 'react';
import { AuthContext } from './providers/JWTProvider';
export const useAuthContext = () => {
const context = useContext(AuthContext);
if (!context) throw new Error('useAuthContext must be used within AuthProvider');
return context;
};