feat: update intern routes and navigation to use '/internship' path; refactor token handling in API and authentication components

This commit is contained in:
Kemas M. Aryadary Rasyad
2026-08-03 14:42:22 +07:00
parent 252671a2cc
commit 5dbb21bf5a
9 changed files with 44 additions and 73 deletions
+1 -1
View File
@@ -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 />} />
+10 -10
View File
@@ -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;
+7 -26
View File
@@ -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.");
}
};
+1 -1
View File
@@ -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 });
+1 -1
View File
@@ -10,7 +10,7 @@ const PrivateRoute = () => {
useEffect(() => {
if (!token) {
navigate("/humic-20242025-2");
navigate("/login-admin");
}
}, [navigate, token]);
+12 -24
View File
@@ -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
View File
@@ -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;
});