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