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

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';