update, consume for login done
This commit is contained in:
@ -4,7 +4,8 @@ GENERATE_SOURCEMAP=false
|
|||||||
|
|
||||||
# VITE_APP_API_URL=
|
# VITE_APP_API_URL=
|
||||||
# VITE_APP_API_URL=http://127.0.0.1:4003/apitest
|
# VITE_APP_API_URL=http://127.0.0.1:4003/apitest
|
||||||
VITE_APP_API_URL=https://tpay.shiblysolution.id/api
|
# VITE_APP_API_URL=https://tpay.shiblysolution.id/api
|
||||||
|
VITE_APP_API_URL=http://127.0.0.1:7001/api
|
||||||
# VITE_APP_API_URL=http://api-v2.tpay.tl/api
|
# VITE_APP_API_URL=http://api-v2.tpay.tl/api
|
||||||
# VITE_APP_URL_TRANSACTION=https://tpay.shiblysolution.id/test/x/api
|
# VITE_APP_URL_TRANSACTION=https://tpay.shiblysolution.id/test/x/api
|
||||||
# VITE_APP_API_URL=http://127.0.0.1:4003/apitesting
|
# VITE_APP_API_URL=http://127.0.0.1:4003/apitesting
|
||||||
|
|||||||
@ -2,7 +2,7 @@ VITE_APP_NAME=tpay-dashboard-tl
|
|||||||
VITE_APP_VERSION=1=9.1.1
|
VITE_APP_VERSION=1=9.1.1
|
||||||
GENERATE_SOURCEMAP=false
|
GENERATE_SOURCEMAP=false
|
||||||
|
|
||||||
VITE_APP_API_URL=https://tpay.shiblysolution.id/api
|
VITE_APP_API_URL=http://127.0.0.1:7001/api
|
||||||
# VITE_APP_API_URL=http://api-v2.tpay.tl/api
|
# VITE_APP_API_URL=http://api-v2.tpay.tl/api
|
||||||
VITE_BASE_URL=/dashboard/
|
VITE_BASE_URL=/dashboard/
|
||||||
VITE_ENV=staging
|
VITE_ENV=staging
|
||||||
@ -41,9 +41,9 @@ export function setupAxios(axios: any) {
|
|||||||
(config: { headers: { Authorization: string }; params?: any; url?: any; httpsAgent: any }) => {
|
(config: { headers: { Authorization: string }; params?: any; url?: any; httpsAgent: any }) => {
|
||||||
const auth = getAuth();
|
const auth = getAuth();
|
||||||
|
|
||||||
// if (auth?.access_token) {
|
if (auth?.access_token) {
|
||||||
// config.headers.Authorization = `Bearer ${auth.access_token}`;
|
config.headers.Authorization = `Bearer ${auth.access_token}`;
|
||||||
// }
|
}
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
async (err: any) => await Promise.reject(err)
|
async (err: any) => await Promise.reject(err)
|
||||||
|
|||||||
@ -1,33 +1,33 @@
|
|||||||
import { type MouseEvent, useState } from 'react';
|
import { type MouseEvent, useState } from 'react';
|
||||||
import { useLocation, useNavigate } from 'react-router-dom';
|
import { Link, useLocation, useNavigate } from 'react-router-dom';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import * as Yup from 'yup';
|
import * as Yup from 'yup';
|
||||||
import { useFormik } from 'formik';
|
import { useFormik } from 'formik';
|
||||||
import { KeenIcon } from '@/components';
|
import { KeenIcon } from '@/components';
|
||||||
import { Alert } from '@/components';
|
import { toAbsoluteUrl } from '@/utils';
|
||||||
import { useAuthContext } from '@/auth';
|
import { useAuthContext } from '@/auth';
|
||||||
|
import { useLayout } from '@/providers';
|
||||||
|
import { Alert } from '@/components';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Helmet } from 'react-helmet';
|
import { Helmet } from 'react-helmet';
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Hardcoded credentials (no API call). The actual session (auth/currentUser)
|
|
||||||
// is still set through AuthContext's login(), which — while its provider is
|
|
||||||
// in DUMMY_MODE — stores a local dummy session instead of hitting the API.
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
const HARDCODED_USERNAME = 'admin';
|
|
||||||
const HARDCODED_PASSWORD = 'admin';
|
|
||||||
|
|
||||||
const loginSchema = Yup.object().shape({
|
const loginSchema = Yup.object().shape({
|
||||||
username: Yup.string().required('Username is required'),
|
username: Yup.string().required('Username is required'),
|
||||||
password: Yup.string()
|
password: Yup.string()
|
||||||
.min(3, 'Minimum 3 symbols')
|
.min(3, 'Minimum 3 symbols')
|
||||||
.max(50, 'Maximum 50 symbols')
|
.max(50, 'Maximum 50 symbols')
|
||||||
.required('Password is required')
|
.required('Password is required'),
|
||||||
|
token: Yup.string(),
|
||||||
|
// .max(3, 'Maximum 6 code'),
|
||||||
|
// .required('Token is required'),
|
||||||
|
remember: Yup.boolean()
|
||||||
});
|
});
|
||||||
|
|
||||||
const initialValues = {
|
const initialValues = {
|
||||||
username: '',
|
username: '',
|
||||||
password: ''
|
password: '',
|
||||||
|
token: '',
|
||||||
|
remember: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const Login = () => {
|
const Login = () => {
|
||||||
@ -35,33 +35,36 @@ const Login = () => {
|
|||||||
const { login } = useAuthContext();
|
const { login } = useAuthContext();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const from = (location.state as any)?.from?.pathname || '/';
|
const from = location.state?.from?.pathname || '/';
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const [showPassword, setShowPassword] = useState(false);
|
||||||
|
const { currentLayout } = useLayout();
|
||||||
|
|
||||||
const formik = useFormik({
|
const formik = useFormik({
|
||||||
initialValues,
|
initialValues,
|
||||||
validationSchema: loginSchema,
|
validationSchema: loginSchema,
|
||||||
onSubmit: async (values, { setStatus, setSubmitting }) => {
|
onSubmit: async (values, { setStatus, setSubmitting }) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setStatus(undefined);
|
|
||||||
|
|
||||||
if (values.username !== HARDCODED_USERNAME || values.password !== HARDCODED_PASSWORD) {
|
|
||||||
setStatus('Incorrect username or password. Please try again.');
|
|
||||||
setSubmitting(false);
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!login) throw new Error('AuthProvider is required for this form.');
|
if (!login) throw new Error('JWTProvider is required for this form.');
|
||||||
await login(values.username, values.password);
|
await login(values.username, values.password, values.token);
|
||||||
navigate(from, { replace: true });
|
|
||||||
} catch (error) {
|
if (values.remember) {
|
||||||
console.error('Login error:', error);
|
localStorage.setItem('username', values.username);
|
||||||
setStatus('Something went wrong while signing in. Please try again.');
|
} else {
|
||||||
setSubmitting(false);
|
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);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -80,10 +83,14 @@ const Login = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>REVENUE | Sign In</title>
|
<title>TPAY | Sign In</title>
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="card max-w-[390px] w-full">
|
<div className="card max-w-[390px] w-full">
|
||||||
<form className="card-body flex flex-col gap-5 p-10" onSubmit={formik.handleSubmit} noValidate>
|
<form
|
||||||
|
className="card-body flex flex-col gap-5 p-10"
|
||||||
|
onSubmit={formik.handleSubmit}
|
||||||
|
noValidate
|
||||||
|
>
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<h3 className="text-lg font-semibold text-gray-900 leading-none ">Sign in</h3>
|
<h3 className="text-lg font-semibold text-gray-900 leading-none ">Sign in</h3>
|
||||||
</div>
|
</div>
|
||||||
@ -137,6 +144,7 @@ const Login = () => {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="btn btn-primary flex justify-center grow"
|
className="btn btn-primary flex justify-center grow"
|
||||||
|
|||||||
@ -17,37 +17,7 @@ import { doSaveLogActivity } from '@/actions/GlobalActions';
|
|||||||
const API_URL = apiConfig.service_dashboard;
|
const API_URL = apiConfig.service_dashboard;
|
||||||
|
|
||||||
export const LOGIN_URL = `${API_URL}/login`;
|
export const LOGIN_URL = `${API_URL}/login`;
|
||||||
export const FORGOT_PASSWORD_URL = `${API_URL}/reset_password`;
|
export const GET_USER_URL = `${API_URL}/user/profile`;
|
||||||
export const RESET_PASSWORD_URL = `${API_URL}/update_password`;
|
|
||||||
export const GET_USER_URL = `${API_URL}/user/detail`;
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// DUMMY MODE — set to `false` to restore the real API-backed auth flow.
|
|
||||||
// While `true`, every method below skips its axios call and works off a
|
|
||||||
// local, hardcoded session instead. This keeps the rest of the app (route
|
|
||||||
// guards, `getAuth()`, etc.) working without a live backend.
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
const DUMMY_MODE = true;
|
|
||||||
|
|
||||||
const DUMMY_USER: UserModel = {
|
|
||||||
id: 1,
|
|
||||||
name: 'Administrator',
|
|
||||||
username: 'admin',
|
|
||||||
email: 'admin@example.com',
|
|
||||||
password: undefined,
|
|
||||||
customer: { id: 'dummy-customer-id' },
|
|
||||||
role_name: 'Admin'
|
|
||||||
};
|
|
||||||
|
|
||||||
const DUMMY_AUTH: AuthModel = {
|
|
||||||
id: 'dummy-user-id',
|
|
||||||
username: 'admin',
|
|
||||||
role_name: 'Admin',
|
|
||||||
user: DUMMY_USER,
|
|
||||||
statusbalance: 'Y',
|
|
||||||
access_token: 'dummy-token',
|
|
||||||
token_type: 'Bearer'
|
|
||||||
};
|
|
||||||
|
|
||||||
interface AuthContextProps {
|
interface AuthContextProps {
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
@ -57,9 +27,6 @@ interface AuthContextProps {
|
|||||||
currentUser: UserModel | undefined;
|
currentUser: UserModel | undefined;
|
||||||
setCurrentUser: Dispatch<SetStateAction<UserModel | undefined>>;
|
setCurrentUser: Dispatch<SetStateAction<UserModel | undefined>>;
|
||||||
login: (email: string, password: string, token?: string) => Promise<void>;
|
login: (email: string, password: string, token?: string) => Promise<void>;
|
||||||
requestPasswordResetLink: (email: string) => Promise<void>;
|
|
||||||
changePassword: (token: string, password: string, password_confirmation: string) => Promise<void>;
|
|
||||||
getUser: () => Promise<AxiosResponse<any> | {}>;
|
|
||||||
logout: () => void;
|
logout: () => void;
|
||||||
verify: () => Promise<void>;
|
verify: () => Promise<void>;
|
||||||
}
|
}
|
||||||
@ -81,24 +48,15 @@ const AuthProvider = ({ children }: PropsWithChildren) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const login = async (username: string, password: string, token?: string) => {
|
const login = async (username: string, password: string, token?: string) => {
|
||||||
if (DUMMY_MODE) {
|
|
||||||
// No API call — just persist a local dummy session.
|
|
||||||
saveAuth(DUMMY_AUTH);
|
|
||||||
setCurrentUser(DUMMY_USER);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data: auth } = await axios
|
const { data: auth } = await axios
|
||||||
.post(LOGIN_URL, { username, password, token })
|
.post(LOGIN_URL, { username, password })
|
||||||
.then((response) => response.data);
|
.then((response) => response.data);
|
||||||
|
|
||||||
const enhancedAuth: AuthModel = {
|
const enhancedAuth: AuthModel = {
|
||||||
...auth.token,
|
...auth.token,
|
||||||
id: auth.user.id,
|
id: auth.userid,
|
||||||
role_name: auth.role_name,
|
|
||||||
user: auth.user,
|
user: auth.user,
|
||||||
statusbalance: auth.role?.status_balance ?? null // SAFE ACCESS
|
|
||||||
};
|
};
|
||||||
|
|
||||||
saveAuth(enhancedAuth);
|
saveAuth(enhancedAuth);
|
||||||
@ -116,45 +74,15 @@ const AuthProvider = ({ children }: PropsWithChildren) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const requestPasswordResetLink = async (email: string) => {
|
|
||||||
if (DUMMY_MODE) {
|
|
||||||
console.warn('[AuthProvider] DUMMY_MODE: requestPasswordResetLink skipped (no API call).');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await axios.put(FORGOT_PASSWORD_URL + '/' + email);
|
|
||||||
};
|
|
||||||
|
|
||||||
const changePassword = async (token: string, password: string, retype_password: string) => {
|
|
||||||
if (DUMMY_MODE) {
|
|
||||||
console.warn('[AuthProvider] DUMMY_MODE: changePassword skipped (no API call).');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await axios.put(`${RESET_PASSWORD_URL}/${token}`, {
|
|
||||||
password,
|
|
||||||
retype_password
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const getUser = async () => {
|
const getUser = async () => {
|
||||||
if (DUMMY_MODE) {
|
|
||||||
return { data: DUMMY_USER };
|
|
||||||
}
|
|
||||||
|
|
||||||
let _axios = await axios
|
let _axios = await axios
|
||||||
.get(`${GET_USER_URL}/${authHelper.getAuth()?.id}`)
|
.get(`${GET_USER_URL}/${authHelper.getAuth()?.user?.id}`)
|
||||||
.then((response) => response.data.data);
|
.then((response) => response.data.data);
|
||||||
|
|
||||||
return { data: _axios };
|
return { data: _axios };
|
||||||
};
|
};
|
||||||
|
|
||||||
const verify = async () => {
|
const verify = async () => {
|
||||||
if (DUMMY_MODE) {
|
|
||||||
// Keep whatever session is already in memory/local storage as-is;
|
|
||||||
// no API round-trip to re-validate it.
|
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auth) {
|
if (auth) {
|
||||||
try {
|
try {
|
||||||
const { data: user } = await getUser();
|
const { data: user } = await getUser();
|
||||||
@ -162,15 +90,12 @@ const AuthProvider = ({ children }: PropsWithChildren) => {
|
|||||||
// Perbarui auth yang sekarang dengan statusbalance
|
// Perbarui auth yang sekarang dengan statusbalance
|
||||||
saveAuth({
|
saveAuth({
|
||||||
...auth,
|
...auth,
|
||||||
statusbalance: user.role.status_balance
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const createCacheUser = {
|
const createCacheUser = {
|
||||||
name: user.name,
|
name: user.name,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
role_name: auth.role_name,
|
|
||||||
statusbalance: user.role.status_balance
|
|
||||||
};
|
};
|
||||||
|
|
||||||
localStorage.setItem('user', JSON.stringify(createCacheUser));
|
localStorage.setItem('user', JSON.stringify(createCacheUser));
|
||||||
@ -181,20 +106,7 @@ const AuthProvider = ({ children }: PropsWithChildren) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ensures `loading` always resolves, even if no external wrapper calls
|
|
||||||
// verify() on app start. Safe to call twice if such a wrapper does exist.
|
|
||||||
useEffect(() => {
|
|
||||||
verify();
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const logout = async () => {
|
const logout = async () => {
|
||||||
if (DUMMY_MODE) {
|
|
||||||
saveAuth(undefined);
|
|
||||||
setCurrentUser(undefined);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const createActivity = {
|
const createActivity = {
|
||||||
module: 'Logout',
|
module: 'Logout',
|
||||||
description: `Logout`,
|
description: `Logout`,
|
||||||
@ -216,9 +128,6 @@ const AuthProvider = ({ children }: PropsWithChildren) => {
|
|||||||
currentUser,
|
currentUser,
|
||||||
setCurrentUser,
|
setCurrentUser,
|
||||||
login,
|
login,
|
||||||
requestPasswordResetLink,
|
|
||||||
changePassword,
|
|
||||||
getUser,
|
|
||||||
logout,
|
logout,
|
||||||
verify
|
verify
|
||||||
}}
|
}}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ const API_URL = import.meta.env.VITE_APP_API_URL;
|
|||||||
const apiConfig: apiConfigProps = {
|
const apiConfig: apiConfigProps = {
|
||||||
service_feedback: `${API_URL}/k`,
|
service_feedback: `${API_URL}/k`,
|
||||||
// service_dashboard: `${API_URL}${import.meta.env.VITE_ENV != 'development' ? '/d' : ''}`,
|
// service_dashboard: `${API_URL}${import.meta.env.VITE_ENV != 'development' ? '/d' : ''}`,
|
||||||
service_dashboard: `${API_URL}/d`,
|
service_dashboard: `${API_URL}`,
|
||||||
service_customer: `${API_URL}/c`,
|
service_customer: `${API_URL}/c`,
|
||||||
// service_master_data2: `${API_URL}/m`,
|
// service_master_data2: `${API_URL}/m`,
|
||||||
service_master_data: `${API_URL}/t`,
|
service_master_data: `${API_URL}/t`,
|
||||||
|
|||||||
@ -11,6 +11,8 @@ const HeaderTopbar = () => {
|
|||||||
const itemUserRef = useRef<any>(null);
|
const itemUserRef = useRef<any>(null);
|
||||||
const itemNotificationsRef = useRef<any>(null);
|
const itemNotificationsRef = useRef<any>(null);
|
||||||
|
|
||||||
|
console.log('HeaderTopbar getAuth():', getAuth());
|
||||||
|
|
||||||
const { isRTL } = useLanguage();
|
const { isRTL } = useLanguage();
|
||||||
|
|
||||||
const [isSticky, setIsSticky] = useState(false);
|
const [isSticky, setIsSticky] = useState(false);
|
||||||
@ -32,11 +34,11 @@ const HeaderTopbar = () => {
|
|||||||
<span
|
<span
|
||||||
className={`font-semibold text-lg leading-tight ${isSticky ? 'text-black' : 'text-white'}`}
|
className={`font-semibold text-lg leading-tight ${isSticky ? 'text-black' : 'text-white'}`}
|
||||||
>
|
>
|
||||||
{getAuth()?.user.name}
|
{getAuth()?.user?.name}
|
||||||
|
</span>
|
||||||
|
<span className={`text-xs ${isSticky ? 'text-gray-700' : 'text-white'}`}>
|
||||||
|
{getAuth()?.user?.email}
|
||||||
</span>
|
</span>
|
||||||
{/* <span className={`text-xs ${isSticky ? 'text-gray-700' : 'text-white'}`}>
|
|
||||||
as {getAuth()?.role_name}
|
|
||||||
</span> */}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Menu className="w-12 h-12">
|
<Menu className="w-12 h-12">
|
||||||
|
|||||||
@ -45,9 +45,9 @@ const DropdownUser = ({ menuItemRef }: IDropdownUserProps) => {
|
|||||||
alt=""
|
alt=""
|
||||||
/> */}
|
/> */}
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<p className="text-[14px] text-gray-800 font-semibold">{auth?.user.username}</p>
|
<p className="text-[14px] text-gray-800 font-semibold">{auth?.user?.username}</p>
|
||||||
<p className="text-xs text-gray-600 hover:text-primary font-medium leading-none">
|
<p className="text-xs text-gray-600 hover:text-primary font-medium leading-none">
|
||||||
{auth?.user.email}
|
{auth?.user?.email}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user