feat: enhance routing and UI components for intern and mentor sections
This commit is contained in:
+8
-3
@@ -1,4 +1,5 @@
|
||||
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
|
||||
import { lazy, Suspense } from "react";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
import Landing from "./Pages/Landing";
|
||||
import AboutUs from "./Pages/AboutUs";
|
||||
@@ -56,6 +57,10 @@ import NotFoundPage from "./Pages/NotFoundPage";
|
||||
import InternCertificateDetail from "./Pages/Internify/intern/InternCertDetail";
|
||||
import VerificationCert from "./Pages/Internify/VerificationCert";
|
||||
|
||||
const InternCertificatePage = lazy(() => import("./Pages/Internify/intern/InternCertDetail"));
|
||||
const MentorCertForm = lazy(() => import("./Pages/Internify/mentor/CertificateForm"));
|
||||
const MentorCertResult = lazy(() => import("./Pages/Internify/mentor/CertificateResult"));
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
@@ -111,7 +116,7 @@ function App() {
|
||||
<Route index element={<HomeInternPage />} />
|
||||
<Route path="projects" element={<ProjectsPage />} />
|
||||
<Route path="certificates" element={<SertificateList />} />
|
||||
<Route path="certificates/:slug" element={<InternCertificateDetail />} />
|
||||
<Route path="certificates/:slug" element={<InternCertificatePage />} />
|
||||
<Route path="projects/:slug" element={<ProjectDetailPage />} />
|
||||
<Route path="projects/:slug/:taskSlug" element={<TaskSubmission />} />
|
||||
<Route path="faq" element={<FAQPage />} />
|
||||
@@ -126,8 +131,8 @@ function App() {
|
||||
<Route index element={<HomeMentorPage />} />
|
||||
<Route path="projects" element={<MentorProjectsPage />} />
|
||||
<Route path="certificates" element={<MentorCertificatePage />} />
|
||||
<Route path="certificates/:slug" element={<CertificateDetail />} />
|
||||
<Route path="certificates/:slug/result" element={<CertificateResult />} />
|
||||
<Route path="certificates/:slug" element={<MentorCertForm />} />
|
||||
<Route path="certificates/:slug/result" element={<MentorCertResult />} />
|
||||
<Route path="intern" element={<MentorInternPage />} />
|
||||
<Route path="projects/:slug" element={<MentorDetailProject />} />
|
||||
<Route path="projects/:slug/:taskSlug" element={<MentorProjectsDetailPage />} />
|
||||
|
||||
@@ -1,35 +1,36 @@
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { FaInstagram, FaLinkedin, FaRegEnvelope } from "react-icons/fa";
|
||||
import logoHumic from "../assets/logo.png"
|
||||
import logoHumic from "../assets/logo.png";
|
||||
|
||||
const footerLinks = {
|
||||
Internship: [
|
||||
{ label: "Beranda", path: "/" },
|
||||
{ label: "Tentang", path: "/about-us" },
|
||||
{ label: "Life At Humic", path: "/our-developer" },
|
||||
],
|
||||
External: [
|
||||
{ label: "Humic Website", path: "https://humic.telkomuniversity.ac.id/" },
|
||||
{ label: "Our Activity", path: "/activity" },
|
||||
{ label: "Life At Humic", path: "/life-humic" },
|
||||
{ label: "Our Developer", path: "/developer" },
|
||||
],
|
||||
};
|
||||
const internshipLinks = [
|
||||
{ label: "Beranda", path: "/" },
|
||||
{ label: "Tentang", path: "/about-us" },
|
||||
{ label: "Life At Humic", path: "/our-developer" },
|
||||
];
|
||||
|
||||
const externalLinks = [
|
||||
{ label: "Humic Website", href: "https://humic.telkomuniversity.ac.id/" },
|
||||
{ label: "Our Activity", href: "https://humic.telkomuniversity.ac.id/humicinmedia/" },
|
||||
{ label: "Life At Humic", href: "#" },
|
||||
{ label: "Our Developer", href: "#" },
|
||||
];
|
||||
|
||||
const socials = [
|
||||
{ label: "Instagram", icon: FaInstagram, href: "https://www.instagram.com/humicengineering/" },
|
||||
{ label: "LinkedIn", icon: FaLinkedin, href: "https://www.linkedin.com/company/humic-engineering/" },
|
||||
{ label: "Email", icon: FaRegEnvelope, href: "https://humic.telkomuniversity.ac.id/" },
|
||||
{ label: "Email", icon: FaRegEnvelope, href: "#" },
|
||||
];
|
||||
|
||||
|
||||
export function FooterIntern() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<footer className="bg-white border-t border-gray-300 px-14 py-8 flex gap-12 flex-wrap">
|
||||
{/* Brand info */}
|
||||
<div className="flex flex-col gap-2 flex-shrink-0 w-70">
|
||||
<div className="flex flex-col gap-2 flex-shrink-0 w-70 items-start">
|
||||
<img
|
||||
src={logoHumic}
|
||||
alt="logo humic"
|
||||
className="w-[160px]"
|
||||
/>
|
||||
<address className="text-xs text-gray-800 leading-relaxed not-italic">
|
||||
Gedung Kultubai Selatan, Blok F
|
||||
Jl. Telekomunikasi, Terusan Buah Batu Bandung
|
||||
@@ -53,32 +54,43 @@ export function FooterIntern() {
|
||||
|
||||
{/* Link columns */}
|
||||
<div className="flex gap-12 flex-1 justify-end flex-wrap">
|
||||
{Object.entries(footerLinks).map(([title, links]) => (
|
||||
<div key={title} className="flex flex-col gap-2 min-w-[110px]">
|
||||
<p className="text-[13px] font-bold text-gray-900">{title}</p>
|
||||
<ul className="flex flex-col gap-0.5">
|
||||
{links.map(({ label, path }) => (
|
||||
<li key={label}>
|
||||
<button
|
||||
onClick={() => navigate(path)}
|
||||
className="text-[12.5px] text-gray-600 hover:text-red-800 transition-colors text-left bg-transparent border-none p-0 cursor-pointer"
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
<div className="flex flex-col gap-2 min-w-[140px]">
|
||||
<p className="text-[13px] font-bold text-gray-900">Internship</p>
|
||||
<ul className="flex flex-col gap-0.5">
|
||||
{internshipLinks.map(({ label, path }) => (
|
||||
<li key={label}>
|
||||
<Link
|
||||
to={path}
|
||||
className="text-[12.5px] text-gray-600 hover:text-red-800 transition-colors"
|
||||
>
|
||||
{label}
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-2 min-w-[140px]">
|
||||
<p className="text-[13px] font-bold text-gray-900">Eksternal</p>
|
||||
<ul className="flex flex-col gap-0.5">
|
||||
{externalLinks.map(({ label, href }) => (
|
||||
<li key={label}>
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-[12.5px] text-gray-600 hover:text-red-800 transition-colors"
|
||||
>
|
||||
{label}
|
||||
</a>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<img
|
||||
src={logoHumic}
|
||||
alt="logo humic"
|
||||
className='w-[110px] h-[68px]'
|
||||
/>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
export default FooterIntern;
|
||||
|
||||
export default FooterIntern
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Bell, Menu } from "lucide-react"
|
||||
import { useState } from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import humiclogo from "../assets/logo.png";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useCurrentUser, resolveFileUrl, getInitials } from "../hooks/useUser";
|
||||
@@ -11,6 +11,11 @@ export default function Header({ toggleSidebar }: { toggleSidebar: () => void })
|
||||
const [imageError, setImageError] = useState(false);
|
||||
const { unreadCount } = useNotifications();
|
||||
const photoUrl = resolveFileUrl(user?.profile_picture);
|
||||
|
||||
useEffect(() => {
|
||||
setImageError(false);
|
||||
}, [photoUrl]);
|
||||
|
||||
const displayName = user?.full_name?.trim() || [user?.nama_depan, user?.nama_belakang].filter(Boolean).join(" ") || "";
|
||||
|
||||
const initials = getInitials(displayName);
|
||||
@@ -27,7 +32,7 @@ export default function Header({ toggleSidebar }: { toggleSidebar: () => void })
|
||||
</button>
|
||||
|
||||
<button>
|
||||
<img src={humiclogo} alt="Humic Logo" className="w-[100px] cursor-pointer" />
|
||||
<img src={humiclogo} alt="Humic Logo" className="w-[120px] cursor-pointer" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -35,7 +40,7 @@ export default function Header({ toggleSidebar }: { toggleSidebar: () => void })
|
||||
<button onClick={() => nav("notifications")} className="relative">
|
||||
<Bell className="w-5 h-5 hover:text-red-800"/>
|
||||
{unreadCount > 0 && (
|
||||
<span className="absolute -top-1 -right-1 w-2.5 h-2.5 bg-red-600 rounded-full border-2 border-white" />
|
||||
<span className="absolute -top-1 -right-0.5 w-2.5 h-2.5 bg-red-600 rounded-full border-2 border-white" />
|
||||
)}
|
||||
</button>
|
||||
|
||||
@@ -46,8 +51,8 @@ export default function Header({ toggleSidebar }: { toggleSidebar: () => void })
|
||||
>
|
||||
{hasPhoto ? (
|
||||
<img
|
||||
src={`${import.meta.env.VITE_API_BASE_URL}${user?.profile_picture ?? ""}`}
|
||||
alt={user?.full_name ?? "Profile"}
|
||||
src={photoUrl!}
|
||||
alt={displayName || "Profile"}
|
||||
className="w-full h-full object-cover"
|
||||
onError={() => setImageError(true)}
|
||||
/>
|
||||
|
||||
@@ -82,7 +82,7 @@ const Navbar = () => {
|
||||
}}
|
||||
className="block w-full text-left hover:text-red-600 transition"
|
||||
>
|
||||
Internify
|
||||
Portal LMS
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -93,7 +93,7 @@ export default function SidebarIntern({ isOpen, closeSidebar }: SidebarProps) {
|
||||
PREFERENCES
|
||||
</p>
|
||||
<nav className="flex flex-col gap-0.5">
|
||||
{prefItems.map(({ label, icon: Icon, path, danger, isLogout }) => {
|
||||
{prefItems.map(({ label, icon: Icon, path, isLogout }) => {
|
||||
const isActive = location.pathname === path;
|
||||
return (
|
||||
<button
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function SubmitStatusTable({
|
||||
: "Assignment was submitted early";
|
||||
|
||||
const rows = [
|
||||
{ label: "Submission status", value: "Submitted for grading" },
|
||||
{ label: "Submission status", value: "Submitted" },
|
||||
{ label: "Time Remaining", value: timeLabel },
|
||||
];
|
||||
|
||||
|
||||
@@ -23,12 +23,18 @@ export default function MentorProjectsPage() {
|
||||
refetch();
|
||||
};
|
||||
|
||||
const STATUS_ORDER: Record<string, number> = {
|
||||
active: 0,
|
||||
completed: 1,
|
||||
archived: 2,
|
||||
};
|
||||
|
||||
|
||||
const renderContent = () => {
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="flex flex-row gap-3 w-full">
|
||||
{[...Array(4)].map((_, i) => (
|
||||
{[...Array(4)].map((_) => (
|
||||
<MentorProjectCardSkeleton />
|
||||
))}
|
||||
</div>
|
||||
@@ -48,7 +54,12 @@ export default function MentorProjectsPage() {
|
||||
);
|
||||
}
|
||||
|
||||
return projects.map(project => (
|
||||
const sorted = [...projects].sort(
|
||||
(a, b) =>
|
||||
(STATUS_ORDER[a.status] ?? 99) - (STATUS_ORDER[b.status] ?? 99)
|
||||
);
|
||||
|
||||
return sorted.map(project => (
|
||||
<MentorProjectCard
|
||||
key={project.id}
|
||||
{...project}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useRef, useEffect, useState, useCallback } from "react";
|
||||
import { useRef, useEffect, useState } from "react";
|
||||
import { X } from "lucide-react";
|
||||
import { PROJECT_ICON_MAP, PROJECT_ICON_CODES } from "../../../../lib/ProjectIcons";
|
||||
import { LuSendHorizontal, LuMail } from "react-icons/lu";
|
||||
|
||||
Reference in New Issue
Block a user