feat: update intern routes and navigation to use '/internship' path; refactor token handling in API and authentication components
This commit is contained in:
+1
-1
@@ -109,7 +109,7 @@ function App() {
|
||||
|
||||
{/* Intern only */}
|
||||
<Route element={<ProtectedRoutes allowedRoles={["intern"]} />}>
|
||||
<Route path="/intern" element={<InternLayout />}>
|
||||
<Route path="/internship" element={<InternLayout />}>
|
||||
<Route index element={<HomeInternPage />} />
|
||||
<Route path="projects" element={<ProjectsPage />} />
|
||||
<Route path="certificates" element={<SertificateList />} />
|
||||
|
||||
@@ -42,15 +42,15 @@ const NameInternBreadcrumb = ({ match }: BreadcrumbMatch) => {
|
||||
|
||||
const breadcrumbRoutes = [
|
||||
// --- AREA INTERN ---
|
||||
{ path: '/intern', breadcrumb: 'Home' },
|
||||
{ path: '/intern/projects', breadcrumb: 'Projects' },
|
||||
{ path: '/intern/projects/:slug', breadcrumb: ProjectBreadcrumb },
|
||||
{ path: '/intern/projects/:slug/:taskSlug', breadcrumb: TaskBreadcrumb },
|
||||
{ path: '/intern/certificates', breadcrumb: 'Certificates' },
|
||||
{ path: '/intern/certificates/:slug', breadcrumb: CertBreadcrumb },
|
||||
{ path: '/intern/faq', breadcrumb: 'FAQ' },
|
||||
{ path: '/intern/notifications', breadcrumb: 'Notifications' },
|
||||
{ path: '/intern/settings', breadcrumb: 'Settings' },
|
||||
{ path: '/internship', breadcrumb: 'Home' },
|
||||
{ path: '/internship/projects', breadcrumb: 'Projects' },
|
||||
{ path: '/internship/projects/:slug', breadcrumb: ProjectBreadcrumb },
|
||||
{ path: '/internship/projects/:slug/:taskSlug', breadcrumb: TaskBreadcrumb },
|
||||
{ path: '/internship/certificates', breadcrumb: 'Certificates' },
|
||||
{ path: '/internship/certificates/:slug', breadcrumb: CertBreadcrumb },
|
||||
{ path: '/internship/faq', breadcrumb: 'FAQ' },
|
||||
{ path: '/internship/notifications', breadcrumb: 'Notifications' },
|
||||
{ path: '/internship/settings', breadcrumb: 'Settings' },
|
||||
|
||||
// --- AREA MENTOR / ADMIN ---
|
||||
{ path: '/mentor', breadcrumb: 'Home' },
|
||||
@@ -71,7 +71,7 @@ export default function Breadcrumbs(){
|
||||
const breadcrumbs = useBreadcrumbs(breadcrumbRoutes);
|
||||
const location = useLocation();
|
||||
|
||||
const rootPaths = ['/intern', '/mentor'];
|
||||
const rootPaths = ['/internship', '/mentor'];
|
||||
const isOnRootPath = rootPaths.includes(location.pathname);
|
||||
const fixbreadcumbs = breadcrumbs.filter(({ match }) => {
|
||||
if (match.pathname === '/') return false;
|
||||
|
||||
@@ -1,20 +1,11 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { decodeJWT } from "../utils/decodeJWT";
|
||||
import logo from "../../assets/logo.png";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { Mail, Lock, Eye, EyeOff } from 'lucide-react';
|
||||
import { useLogin } from "../../hooks/useUser";
|
||||
import { customToast } from "../utils/showToast";
|
||||
import { CircleQuestionMark } from 'lucide-react';
|
||||
|
||||
interface JwtPayload {
|
||||
id: string;
|
||||
email: string;
|
||||
role: "intern" | "mentor" | "admin";
|
||||
signature?: string | null;
|
||||
iat: number;
|
||||
exp: number;
|
||||
}
|
||||
import { decodeJWT } from "../utils/decodeJWT";
|
||||
|
||||
export default function InternifyLogin() {
|
||||
const navigate = useNavigate();
|
||||
@@ -40,28 +31,18 @@ export default function InternifyLogin() {
|
||||
}
|
||||
|
||||
const data = await login(email, password);
|
||||
if (!data) return;
|
||||
|
||||
if (!data?.token) {
|
||||
setError("Token login tidak ditemukan.");
|
||||
return;
|
||||
}
|
||||
const { token } = data;
|
||||
document.cookie = `token=${token}; path=/; SameSite=Strict`;
|
||||
|
||||
const decoded = decodeJWT<JwtPayload>(data.token);
|
||||
const decoded = decodeJWT(token);
|
||||
const role = decoded?.role;
|
||||
|
||||
if (!role) {
|
||||
setError("Token login tidak valid.");
|
||||
return;
|
||||
}
|
||||
|
||||
sessionStorage.setItem("token", data.token);
|
||||
|
||||
if (role === "intern") {
|
||||
navigate("/intern", { replace: true });
|
||||
navigate("/internship");
|
||||
} else if (role === "mentor" || role === "admin") {
|
||||
navigate("/mentor", { replace: true });
|
||||
navigate("/mentor");
|
||||
} else {
|
||||
sessionStorage.removeItem("token");
|
||||
setError("Role user tidak dikenali.");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ export default function ProjectsPage() {
|
||||
|
||||
if (projects.length === 0) {
|
||||
return (
|
||||
<div className="col-span-full flex flex-col items-center justify-center py-2 gap-3 mt-19">
|
||||
<div className="col-span-full flex flex-col items-center py-2 gap-3 mt-19">
|
||||
<img className="w-[440px]" src={TeamPana} alt="" />
|
||||
<p className="text-[18px] text-font-shade">Kamu belum tergabung ke projek apapun</p>
|
||||
</div>
|
||||
|
||||
@@ -5,14 +5,14 @@ import { MdOutlineAssignment, MdLogout } from "react-icons/md";
|
||||
import { HiUser } from "react-icons/hi2";
|
||||
|
||||
const navItems = [
|
||||
{ label: "Home", icon: GrHomeRounded, path: "/intern" },
|
||||
{ label: "List Projects", icon: MdOutlineAssignment, path: "/intern/projects" },
|
||||
{ label: "Certificates", icon: Award, path: "/intern/certificates" },
|
||||
{ label: "Home", icon: GrHomeRounded, path: "/internship" },
|
||||
{ label: "List Projects", icon: MdOutlineAssignment, path: "/internship/projects" },
|
||||
{ label: "Certificates", icon: Award, path: "/internship/certificates" },
|
||||
];
|
||||
|
||||
const prefItems = [
|
||||
{ label: "Settings", icon: Settings, path: "/intern/settings" },
|
||||
{ label: "FAQ", icon: HelpCircle, path: "/intern/faq" },
|
||||
{ label: "Settings", icon: Settings, path: "/internship/settings" },
|
||||
{ label: "FAQ", icon: HelpCircle, path: "/internship/faq" },
|
||||
{ label: "Logout", icon: MdLogout, path: "/login-internify", danger: true, isLogout: true },
|
||||
];
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function SidebarIntern({ isOpen, closeSidebar }: SidebarProps) {
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
document.cookie = "token=; path=/; max-age=86400; SameSite=Strict";
|
||||
document.cookie = "token=; Max-Age=0; path=/; SameSite=Strict";
|
||||
|
||||
closeSidebar();
|
||||
navigate("/login-internify", { replace: true });
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function SidebarMentor({ isOpen, closeSidebar }: SidebarProps) {
|
||||
};
|
||||
|
||||
const handleLogout = () => {
|
||||
document.cookie = "token=; path=/; max-age=86400; SameSite=Strict";
|
||||
document.cookie = "token=; Max-Age=0; path=/; SameSite=Strict";
|
||||
|
||||
closeSidebar();
|
||||
navigate("/login-internify", { replace: true });
|
||||
|
||||
@@ -10,7 +10,7 @@ const PrivateRoute = () => {
|
||||
|
||||
useEffect(() => {
|
||||
if (!token) {
|
||||
navigate("/humic-20242025-2");
|
||||
navigate("/login-admin");
|
||||
}
|
||||
}, [navigate, token]);
|
||||
|
||||
|
||||
@@ -1,42 +1,30 @@
|
||||
import { Outlet, Navigate } from "react-router-dom";
|
||||
import { decodeJWT } from "./decodeJWT";
|
||||
import { decodeJWT } from "../utils/decodeJWT";
|
||||
|
||||
type Role = "intern" | "admin" | "mentor";
|
||||
interface RouteProps {
|
||||
allowedRoles?: Role[];
|
||||
interface PrivateRouteProps {
|
||||
allowedRoles?: ("intern" | "admin" | "mentor")[];
|
||||
}
|
||||
|
||||
interface JwtPayload {
|
||||
id: string;
|
||||
email: string;
|
||||
role: "intern" | "mentor" | "admin";
|
||||
signature?: string | null;
|
||||
iat: number;
|
||||
exp: number;
|
||||
}
|
||||
|
||||
const ProtectedRoutes = ({ allowedRoles }: RouteProps) => {
|
||||
const token = sessionStorage.getItem("token");
|
||||
const ProtectedRoutes = ({ allowedRoles }: PrivateRouteProps) => {
|
||||
const token = document.cookie
|
||||
.split("; ")
|
||||
.find((row) => row.startsWith("token="))
|
||||
?.split("=")[1];
|
||||
|
||||
if (!token) {
|
||||
return <Navigate to="/login-internify" replace />;
|
||||
}
|
||||
|
||||
const decoded = decodeJWT<JwtPayload>(token);
|
||||
const userRole = decoded?.role as Role | undefined;
|
||||
const decoded = decodeJWT(token);
|
||||
const userRole = decoded?.role as "intern" | "admin" | "mentor" | undefined;
|
||||
|
||||
if (!userRole) {
|
||||
sessionStorage.removeItem("token");
|
||||
return <Navigate to="/login-internify" replace />;
|
||||
}
|
||||
|
||||
if (allowedRoles && !allowedRoles.includes(userRole)) {
|
||||
return (
|
||||
<Navigate
|
||||
to={userRole === "intern" ? "/intern" : "/mentor"}
|
||||
replace
|
||||
/>
|
||||
);
|
||||
if (userRole === "admin" || userRole === "mentor") return <Navigate to="/mentor" replace />;
|
||||
if (userRole === "intern") return <Navigate to="/internship" replace />;
|
||||
}
|
||||
|
||||
return <Outlet />;
|
||||
|
||||
+5
-3
@@ -1,18 +1,20 @@
|
||||
import axios from "axios";
|
||||
|
||||
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL;
|
||||
export const API_BASE_URL = import.meta.env.VITE_API_BASE_URL as string;
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL,
|
||||
});
|
||||
|
||||
api.interceptors.request.use((config) => {
|
||||
const token = sessionStorage.getItem("token");
|
||||
const token = document.cookie
|
||||
.split("; ")
|
||||
.find((row) => row.startsWith("token="))
|
||||
?.split("=")[1];
|
||||
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
return config;
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user