add navigation on internify header: navigation to home from humic logo +

crosscheck line code
This commit is contained in:
Kemas M. Aryadary Rasyad
2026-07-22 02:15:11 +07:00
parent 4170a93173
commit e396914609
22 changed files with 220 additions and 235 deletions
+12 -11
View File
@@ -5,38 +5,39 @@ import logoHumic from "../assets/logo.png";
const internshipLinks = [ const internshipLinks = [
{ 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: "" },
{ label: "Our Developer", path: "/our-developer" }
]; ];
const externalLinks = [ const externalLinks = [
{ label: "Humic Website", href: "https://humic.telkomuniversity.ac.id/" }, { label: "Humic Website", href: "https://humic.telkomuniversity.ac.id/" },
{ label: "Our Activity", href: "https://humic.telkomuniversity.ac.id/humicinmedia/" }, { label: "Our Activity", href: "https://humic.telkomuniversity.ac.id/humicinmedia/" },
{ label: "Life At Humic", href: "#" }, { label: "Life At Humic", href: "" },
{ label: "Our Developer", href: "#" }, // { 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: "#" }, { label: "Email", icon: FaRegEnvelope, href: "" },
]; ];
export function FooterIntern() { export function FooterIntern() {
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 items-start"> <div className="flex flex-col gap-1 flex-shrink-0 w-70 items-start">
<img <img
src={logoHumic} src={logoHumic}
alt="logo humic" alt="logo humic"
className="w-[160px]" className="w-[130px]"
/> />
<address className="text-xs text-gray-800 leading-relaxed not-italic"> <address className="text-[13px] text-gray-800 not-italic mt-[10px]">
Gedung Kultubai Selatan, Blok F Gedung Kultubai Selatan, Blok F
Jl. Telekomunikasi, Terusan Buah Batu Bandung Jl. Telekomunikasi, Terusan Buah Batu Bandung
Jawa Barat, Indonesia. 40257 Jawa Barat, Indonesia. 40257
</address> </address>
<div className="flex gap-2 mt-1"> <div className="flex gap-2 mt-[13px]">
{socials.map(({ label, icon: Icon, href }) => ( {socials.map(({ label, icon: Icon, href }) => (
<a <a
key={label} key={label}
@@ -56,7 +57,7 @@ export function FooterIntern() {
<div className="flex gap-12 flex-1 justify-end flex-wrap"> <div className="flex gap-12 flex-1 justify-end flex-wrap">
<div className="flex flex-col gap-2 min-w-[140px]"> <div className="flex flex-col gap-2 min-w-[140px]">
<p className="text-[13px] font-bold text-gray-900">Internship</p> <p className="text-[13px] font-bold text-gray-900">Internship</p>
<ul className="flex flex-col gap-0.5"> <ul className="flex flex-col gap-0.5 font-semibold">
{internshipLinks.map(({ label, path }) => ( {internshipLinks.map(({ label, path }) => (
<li key={label}> <li key={label}>
<Link <Link
@@ -71,8 +72,8 @@ export function FooterIntern() {
</div> </div>
<div className="flex flex-col gap-2 min-w-[140px]"> <div className="flex flex-col gap-2 min-w-[140px]">
<p className="text-[13px] font-bold text-gray-900">Eksternal</p> <p className="text-[13px] font-bold text-gray-900">Lainnya</p>
<ul className="flex flex-col gap-0.5"> <ul className="flex flex-col gap-0.5 font-semibold">
{externalLinks.map(({ label, href }) => ( {externalLinks.map(({ label, href }) => (
<li key={label}> <li key={label}>
<a <a
+3 -1
View File
@@ -32,7 +32,9 @@ export default function Header({ toggleSidebar }: { toggleSidebar: () => void })
<Menu className="w-6 h-6" /> <Menu className="w-6 h-6" />
</button> </button>
<button> <button
onClick={() => nav("")}
>
<img src={humiclogo} alt="Humic Logo" className="w-[120px] cursor-pointer" /> <img src={humiclogo} alt="Humic Logo" className="w-[120px] cursor-pointer" />
</button> </button>
</div> </div>
+7 -7
View File
@@ -22,7 +22,7 @@ const Navbar = () => {
}; };
return ( return (
<nav className="bg-[#D7E1E8] px-4 py-5 mx-5 rounded-2xl shadow-md"> <nav className="bg-[#D7E1E8] px-10 py-5 mx-5 rounded-2xl shadow-md">
<div className="max-w-8xl mx-auto flex items-center justify-between"> <div className="max-w-8xl mx-auto flex items-center justify-between">
<img <img
src={humiclogo} src={humiclogo}
@@ -56,22 +56,22 @@ const Navbar = () => {
</div> </div>
{/* Desktop menu */} {/* Desktop menu */}
<div className="hidden md:flex items-center gap-6 text-[18px] font-semibold"> <div className="hidden md:flex items-center gap-8 text-[18px] font-semibold">
<button <button
onClick={() => navigate("/about-us")} onClick={() => navigate("/about-us")}
className="hover:text-blue-600 transition cursor-pointer" className="hover:text-gray-600 transition cursor-pointer"
> >
About Us About Us
</button> </button>
<button <button
onClick={handleContactClick} onClick={handleContactClick}
className="hover:text-blue-600 transition cursor-pointer" className="hover:text-gray-600 transition cursor-pointer"
> >
Contact Us Contact Us
</button> </button>
<button <button
onClick={() => navigate("/internships")} onClick={() => navigate("/internships")}
className="hover:text-blue-600 transition cursor-pointer" className="hover:text-gray-600 transition cursor-pointer"
> >
Internship Internship
</button> </button>
@@ -80,7 +80,7 @@ const Navbar = () => {
onClick={() => { onClick={() => {
navigate("/login-internify"); navigate("/login-internify");
}} }}
className="block w-full text-left hover:text-red-600 transition" className="hover:text-red-600 transition cursor-pointer"
> >
Portal LMS Portal LMS
</button> </button>
@@ -125,7 +125,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"
> >
Login Portal LMS
</button> </button>
</div> </div>
)} )}
-2
View File
@@ -1,5 +1,3 @@
// src/components/common/Avatar.tsx
export default function Avatar({ initials }: { initials: string }) { export default function Avatar({ initials }: { initials: string }) {
return ( return (
<div className="w-10 h-10 rounded-full flex items-center justify-center text-xs text-white flex-shrink-0 bg-primary"> <div className="w-10 h-10 rounded-full flex items-center justify-center text-xs text-white flex-shrink-0 bg-primary">
+106 -108
View File
@@ -1,108 +1,106 @@
import { Link, useLocation } from "react-router-dom"; import { Link, useLocation } from "react-router-dom";
import useBreadcrumbs from "use-react-router-breadcrumbs"; import useBreadcrumbs from "use-react-router-breadcrumbs";
import { ChevronRight } from "lucide-react"; import { ChevronRight } from "lucide-react";
import { useMyProjects, useProjects } from "../../hooks/useProjects"; import { useMyProjects, useProjects } from "../../hooks/useProjects";
import { useProjectTasks } from "../../hooks/useTasks"; import { useProjectTasks } from "../../hooks/useTasks";
type BreadcrumbMatch = { type BreadcrumbMatch = {
match: { params: { slug?: string; taskSlug?: string; nameIntern?: string } }; match: { params: { slug?: string; taskSlug?: string; nameIntern?: string } };
}; };
const unslugify = (slug = '') => const unslugify = (slug = '') =>
slug.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase()); slug.replace(/-/g, ' ').replace(/\b\w/g, (c) => c.toUpperCase());
const ProjectBreadcrumb = ({ match }: BreadcrumbMatch) => { const ProjectBreadcrumb = ({ match }: BreadcrumbMatch) => {
const { projects } = useProjects(); const { projects } = useProjects();
const slug = match.params.slug; const slug = match.params.slug;
const project = projects?.find((p) => p.slug === slug); const project = projects?.find((p) => p.slug === slug);
return <>{project?.project_name ?? unslugify(slug)}</>; return <>{project?.project_name ?? unslugify(slug)}</>;
}; };
const TaskBreadcrumb = ({ match }: BreadcrumbMatch) => { const TaskBreadcrumb = ({ match }: BreadcrumbMatch) => {
const {slug, taskSlug} = match.params; const {slug, taskSlug} = match.params;
const { projects } = useMyProjects(); const { projects } = useMyProjects();
const project = projects?.find((p) => p.slug === slug); const project = projects?.find((p) => p.slug === slug);
const { tasks } = useProjectTasks(project?.id ?? 0); const { tasks } = useProjectTasks(project?.id ?? 0);
const task = tasks?.find((t) => t.slug === taskSlug); const task = tasks?.find((t) => t.slug === taskSlug);
return <>{task?.title ?? unslugify(taskSlug)}</>; return <>{task?.title ?? unslugify(taskSlug)}</>;
}; };
// Breadcrumb untuk halaman detail sertifikat (menggunakan nama project dari slug) const CertBreadcrumb = ({ match }: BreadcrumbMatch) => {
const CertBreadcrumb = ({ match }: BreadcrumbMatch) => { const { projects } = useProjects();
const { projects } = useProjects(); const slug = match.params.slug;
const slug = match.params.slug; const project = projects?.find((p) => p.slug === slug);
const project = projects?.find((p) => p.slug === slug); return <>{project?.project_name ?? unslugify(slug)}</>;
return <>{project?.project_name ?? unslugify(slug)}</>; };
};
const NameInternBreadcrumb = ({ match }: BreadcrumbMatch) => {
// Breadcrumb untuk halaman submission intern (unslugify nameIntern) return <>{unslugify(match.params.nameIntern)}</>;
const NameInternBreadcrumb = ({ match }: BreadcrumbMatch) => { };
return <>{unslugify(match.params.nameIntern)}</>;
}; const breadcrumbRoutes = [
// --- AREA INTERN ---
const breadcrumbRoutes = [ { path: '/intern', breadcrumb: 'Home' },
// --- AREA INTERN --- { path: '/intern/projects', breadcrumb: 'Projects' },
{ path: '/intern', breadcrumb: 'Home' }, { path: '/intern/projects/:slug', breadcrumb: ProjectBreadcrumb },
{ path: '/intern/projects', breadcrumb: 'Projects' }, { path: '/intern/projects/:slug/:taskSlug', breadcrumb: TaskBreadcrumb },
{ path: '/intern/projects/:slug', breadcrumb: ProjectBreadcrumb }, { path: '/intern/certificates', breadcrumb: 'Certificates' },
{ path: '/intern/projects/:slug/:taskSlug', breadcrumb: TaskBreadcrumb }, { path: '/intern/certificates/:slug', breadcrumb: CertBreadcrumb },
{ path: '/intern/certificates', breadcrumb: 'Certificates' }, { path: '/intern/faq', breadcrumb: 'FAQ' },
{ path: '/intern/certificates/:slug', breadcrumb: CertBreadcrumb }, { path: '/intern/notifications', breadcrumb: 'Notifications' },
{ path: '/intern/faq', breadcrumb: 'FAQ' }, { path: '/intern/settings', breadcrumb: 'Settings' },
{ path: '/intern/notifications', breadcrumb: 'Notifications' },
{ path: '/intern/settings', breadcrumb: 'Settings' }, // --- AREA MENTOR / ADMIN ---
{ path: '/mentor', breadcrumb: 'Home' },
// --- AREA MENTOR / ADMIN --- { path: '/mentor/projects', breadcrumb: 'List Projects' },
{ path: '/mentor', breadcrumb: 'Home' }, { path: '/mentor/projects/:slug', breadcrumb: ProjectBreadcrumb },
{ path: '/mentor/projects', breadcrumb: 'List Projects' }, { path: '/mentor/projects/:slug/:taskSlug', breadcrumb: TaskBreadcrumb },
{ path: '/mentor/projects/:slug', breadcrumb: ProjectBreadcrumb }, { path: '/mentor/projects/:slug/:taskSlug/:nameIntern', breadcrumb: NameInternBreadcrumb },
{ path: '/mentor/projects/:slug/:taskSlug', breadcrumb: TaskBreadcrumb }, { path: '/mentor/certificates', breadcrumb: 'Certificates' },
{ path: '/mentor/projects/:slug/:taskSlug/:nameIntern', breadcrumb: NameInternBreadcrumb }, { path: '/mentor/certificates/:slug', breadcrumb: CertBreadcrumb },
{ path: '/mentor/certificates', breadcrumb: 'Certificates' }, { path: '/mentor/certificates/:slug/result', breadcrumb: 'Result' },
{ path: '/mentor/certificates/:slug', breadcrumb: CertBreadcrumb }, { path: '/mentor/intern', breadcrumb: 'List Intern' },
{ path: '/mentor/certificates/:slug/result', breadcrumb: 'Result' }, { path: '/mentor/faq', breadcrumb: 'FAQ' },
{ path: '/mentor/intern', breadcrumb: 'List Intern' }, { path: '/mentor/notifications', breadcrumb: 'Notifications' },
{ path: '/mentor/faq', breadcrumb: 'FAQ' }, { path: '/mentor/settings', breadcrumb: 'Settings' },
{ path: '/mentor/notifications', breadcrumb: 'Notifications' }, ];
{ path: '/mentor/settings', breadcrumb: 'Settings' },
]; export default function Breadcrumbs(){
const breadcrumbs = useBreadcrumbs(breadcrumbRoutes);
export default function Breadcrumbs(){ const location = useLocation();
const breadcrumbs = useBreadcrumbs(breadcrumbRoutes);
const location = useLocation(); const rootPaths = ['/intern', '/mentor'];
const isOnRootPath = rootPaths.includes(location.pathname);
const rootPaths = ['/intern', '/mentor']; const fixbreadcumbs = breadcrumbs.filter(({ match }) => {
const isOnRootPath = rootPaths.includes(location.pathname); if (match.pathname === '/') return false;
const fixbreadcumbs = breadcrumbs.filter(({ match }) => { if (rootPaths.includes(match.pathname) && !isOnRootPath) return false;
if (match.pathname === '/') return false; return true;
if (rootPaths.includes(match.pathname) && !isOnRootPath) return false; });
return true; return (
}); <nav className="text-[15px] mb-3">
return ( {fixbreadcumbs.map(({ match, breadcrumb }) => {
<nav className="text-[15px] mb-3"> const isLast = match.pathname === location.pathname;
{fixbreadcumbs.map(({ match, breadcrumb }) => {
const isLast = match.pathname === location.pathname; return (
<span key={match.pathname} className="inline-flex items-center">
return ( {isLast ? (
<span key={match.pathname} className="inline-flex items-center"> <span className="font-bold text-red-800">
{isLast ? ( {breadcrumb}
<span className="font-bold text-red-800">
{breadcrumb} </span>
) : (
</span> <>
) : ( <Link to={match.pathname} className="text-gray-800">
<> {breadcrumb}
<Link to={match.pathname} className="text-gray-800"> </Link>
{breadcrumb} <ChevronRight className="w-4 h-4 mx-1.5"/>
</Link> </>
<ChevronRight className="w-4 h-4 mx-1.5"/> )}
</> </span>
)} );
</span> })}
); </nav>
})} );
</nav> };
);
};
+78 -79
View File
@@ -1,79 +1,78 @@
import api from "../../lib/api"; import api from "../../lib/api";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
interface FAQ { interface FAQ {
id: number; id: number;
pertanyaan: string; pertanyaan: string;
jawaban: string; jawaban: string;
created_at: string; created_at: string;
} }
// Skeleton item that mirrors the real FAQ card shape function FAQSkeleton() {
function FAQSkeleton() { return (
return ( <div className="relative shadow-md py-5 px-10 rounded-md bg-white border-l-6 border-gray-200 animate-pulse">
<div className="relative shadow-md py-5 px-10 rounded-md bg-white border-l-6 border-gray-200 animate-pulse"> {/* Question bar */}
{/* Question bar */} <div className="h-5 bg-gray-200 rounded w-2/3 mb-3" />
<div className="h-5 bg-gray-200 rounded w-2/3 mb-3" /> {/* Answer bars */}
{/* Answer bars */} <div className="pl-8 space-y-2">
<div className="pl-8 space-y-2"> <div className="h-3 bg-gray-200 rounded w-full" />
<div className="h-3 bg-gray-200 rounded w-full" /> <div className="h-3 bg-gray-200 rounded w-4/5" />
<div className="h-3 bg-gray-200 rounded w-4/5" /> </div>
</div> </div>
</div> );
); }
}
export default function FAQPage() {
export default function FAQPage() { const [faq, setFaq] = useState<FAQ[]>([]);
const [faq, setFaq] = useState<FAQ[]>([]); const [loading, setLoading] = useState(false);
const [loading, setLoading] = useState(false);
useEffect(() => {
useEffect(() => { setLoading(true);
setLoading(true); api.get("/faq-api/get")
api.get("/faq-api/get") .then((res) => {
.then((res) => { setFaq(res.data.data);
setFaq(res.data.data); })
}) .catch((err) => {
.catch((err) => { console.error("Gagal fetch FAQ:", err);
console.error("Gagal fetch FAQ:", err); })
}) .finally(() => {
.finally(() => { setLoading(false);
setLoading(false); });
}); }, []);
}, []);
return (
return ( <div>
<div> <div className="mb-10">
<div className="mb-10"> <h1 className="page-title">FAQ</h1>
<h1 className="page-title">FAQ</h1> <p className="page-title-desc">
<p className="page-title-desc"> Quick answers to common questions about the Internify LMS platform.
Quick answers to common questions about the Internify LMS platform. </p>
</p> </div>
</div>
{/* FAQ Lists */}
{/* FAQ Lists */} {loading ? (
{loading ? ( <div className="space-y-8 max-w-auto">
<div className="space-y-8 max-w-auto"> {[...Array(5)].map((_, i) => (
{[...Array(5)].map((_, i) => ( <FAQSkeleton key={i} />
<FAQSkeleton key={i} /> ))}
))} </div>
</div> ) : (
) : ( <div className="space-y-8 max-w-auto">
<div className="space-y-8 max-w-auto"> {faq.map((item) => (
{faq.map((item) => ( <div key={item.id} className="relative shadow-md group py-5 px-10 rounded-md bg-white border-l-6 border-red hover:shadow-xl">
<div key={item.id} className="relative shadow-md group py-5 px-10 rounded-md bg-white border-l-6 border-red hover:shadow-xl">
{/* Item Question */}
{/* Item Question */} <h2 className="text-[17px] font-bold text-red tracking-tight leading-snug mb-2">
<h2 className="text-[17px] font-bold text-red tracking-tight leading-snug mb-2"> {item.pertanyaan}
{item.pertanyaan} </h2>
</h2>
<p className="text-[14px] pl-8 text-redfont-light leading-relaxed tracking-normal">
<p className="text-[14px] pl-8 text-redfont-light leading-relaxed tracking-normal"> {item.jawaban}
{item.jawaban} </p>
</p> </div>
</div> ))}
))} </div>
</div> )}
)} </div>
</div> );
); }
}
+6 -6
View File
@@ -58,9 +58,9 @@ export default function InternifyLogin() {
}; };
return ( return (
<div className="min-h-screen bg-background flex flex-col justify-between items-center py-12 px-4 font-sans selection:bg-red-foreground"> <div className="min-h-screen bg-background flex flex-col justify-between items-center py-6 px-4 selection:bg-red-foreground">
<div className="w-full flex flex-col items-center"> <div className="w-full flex flex-col items-center mb-8">
<div className="text-center mb-12"> <div className="text-center mb-8">
<div className="flex items-center justify-center gap-2 font-bold text-2xl tracking-tight text-black"> <div className="flex items-center justify-center gap-2 font-bold text-2xl tracking-tight text-black">
<img src={logo} alt="Logo" /> <img src={logo} alt="Logo" />
</div> </div>
@@ -129,9 +129,9 @@ export default function InternifyLogin() {
</form> </form>
{/* Forgot Password */} {/* Forgot Password */}
<div className="text-center mt-6"> <div className="text-center mt-6">
<a href="#" className="text-xs font-semibold text-red-600 hover:underline"> <span className="text-xs font-semibold text-red-600">
Forgot Password? Forgot Password?
</a> </span>
</div> </div>
{/* Divider & Support */} {/* Divider & Support */}
@@ -144,7 +144,7 @@ export default function InternifyLogin() {
<div className="flex-grow border-t border-card-outline"></div> <div className="flex-grow border-t border-card-outline"></div>
</div> </div>
<button className="w-full mt-2 flex items-center justify-center gap-2 border border-card-outline rounded-full py-1.5 text-xs text-font-shade hover:bg-gray-50 transition-colors font-medium"> <button className="w-full mt-2 flex items-center justify-center gap-2 border border-card-outline rounded-full py-1.5 text-xs text-font-shade font-medium">
<CircleQuestionMark className="w-5 h-5" /> <CircleQuestionMark className="w-5 h-5" />
Frequently Asked Questions Frequently Asked Questions
</button> </button>
+1 -1
View File
@@ -41,7 +41,7 @@ export default function ProjectsPage() {
<h1 className="page-title-desc">View assigned projects and track their progress.</h1> <h1 className="page-title-desc">View assigned projects and track their progress.</h1>
</div> </div>
<div className="flex flex-wrap gap-4 items-stretch"> <div className="flex flex-wrap gap-3 items-stretch">
{renderContent()} {renderContent()}
</div> </div>
</div> </div>
@@ -87,7 +87,7 @@ export default function MentorProjectsPage() {
</button> </button>
</div> </div>
<div className="flex flex-wrap gap-4 items-stretch">{renderContent()}</div> <div className="flex flex-wrap gap-3 items-stretch">{renderContent()}</div>
{isModalOpen && ( {isModalOpen && (
<CreateProjectModal <CreateProjectModal
@@ -216,7 +216,7 @@ export default function CertificateDetail() {
</button> </button>
)} )}
{/* ── Project Scope ── */} {/* Project Scope */}
<div className="border border-card-outline rounded-xl p-5"> <div className="border border-card-outline rounded-xl p-5">
<p className="text-[13px] font-bold text-gray-400 uppercase tracking-widest mb-3"> <p className="text-[13px] font-bold text-gray-400 uppercase tracking-widest mb-3">
Project Scope Project Scope
@@ -45,7 +45,7 @@ export default function CertificateList() {
</p> </p>
</div> </div>
{/* Loading Skeleton — struktur sama persis dengan body */} {/* Loading Skeleton - struktur sama persis dengan body */}
{loading && ( {loading && (
<div> <div>
<div className="h-4 w-32 bg-gray-200 rounded mb-4 animate-pulse" /> <div className="h-4 w-32 bg-gray-200 rounded mb-4 animate-pulse" />
@@ -73,7 +73,7 @@ export default function CertificateList() {
</div> </div>
)} )}
{/* Section: List Projects — hanya tampil kalau ada project */} {/* Section: List Projects - hanya tampil kalau ada project */}
{hasProjects && ( {hasProjects && (
<div> <div>
<h2 className="text-base font-bold text-font-shade mb-4">List Projects</h2> <h2 className="text-base font-bold text-font-shade mb-4">List Projects</h2>
@@ -8,7 +8,6 @@ import { FileText } from 'lucide-react';
export default function MentorForumTab({ project }: { project: Project }) { export default function MentorForumTab({ project }: { project: Project }) {
const { tasks, loading, error } = useProjectTasks(project.id); const { tasks, loading, error } = useProjectTasks(project.id);
// Skeleton meniru layout final: kolom timeline 1/5 + list task card 4/5
const renderSkeleton = () => ( const renderSkeleton = () => (
<div className="flex flex-col gap-7 md:flex-row md:items-start mt-5 animate-pulse"> <div className="flex flex-col gap-7 md:flex-row md:items-start mt-5 animate-pulse">
<div className="w-full md:w-1/5 bg-white rounded-xl p-5 border border-box-border shrink-0"> <div className="w-full md:w-1/5 bg-white rounded-xl p-5 border border-box-border shrink-0">
@@ -29,14 +28,12 @@ export default function MentorForumTab({ project }: { project: Project }) {
</div> </div>
); );
// Empty hanya valid setelah loading selesai & tidak error
const isEmpty = !loading && !error && tasks.length === 0; const isEmpty = !loading && !error && tasks.length === 0;
return ( return (
<div> <div>
<HeroProject title={project.project_name} description={project.description} /> <HeroProject title={project.project_name} description={project.description} />
{/* Kondisi eksklusif: hanya satu state tampil dalam satu waktu */}
{loading ? ( {loading ? (
renderSkeleton() renderSkeleton()
) : error ? ( ) : error ? (
-1
View File
@@ -1,4 +1,3 @@
// utils/toast.tsx
import toast, { type Toast } from 'react-hot-toast'; import toast, { type Toast } from 'react-hot-toast';
import { FiX } from 'react-icons/fi'; import { FiX } from 'react-icons/fi';
import { FaCheck } from "react-icons/fa"; import { FaCheck } from "react-icons/fa";
-1
View File
@@ -1,4 +1,3 @@
// hooks/useCertificates.ts
import { useState, useEffect, useCallback } from "react"; import { useState, useEffect, useCallback } from "react";
import type { Certificate, CertificateVerifyResult } from "../types/certificate.types"; import type { Certificate, CertificateVerifyResult } from "../types/certificate.types";
import { import {
-1
View File
@@ -1,4 +1,3 @@
// hooks/useNotifications.ts
import { useState, useEffect, useCallback } from "react"; import { useState, useEffect, useCallback } from "react";
import { import {
getNotifications, getNotifications,
-1
View File
@@ -1,4 +1,3 @@
// hooks/useProject.ts
import { useState, useEffect, useCallback } from "react"; import { useState, useEffect, useCallback } from "react";
import type { import type {
Project, Project,
+2 -3
View File
@@ -1,4 +1,3 @@
// hooks/useTasks.ts
import { useState, useEffect, useCallback, useMemo } from "react"; import { useState, useEffect, useCallback, useMemo } from "react";
import type { import type {
ProjectTask, ProjectTask,
@@ -46,7 +45,7 @@ export const useProjectTasks = (projectId: number) => {
refetch(); refetch();
}, [refetch]); }, [refetch]);
return { tasks, loading, error, refetch }; // tambah refetch return { tasks, loading, error, refetch };
}; };
// GET /task-api/tasks/{id} // GET /task-api/tasks/{id}
@@ -276,7 +275,7 @@ export const useAllMentorTasks = (enabled: boolean) => {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
if (!enabled) return; // tidak fetch kalau intern if (!enabled) return;
setLoading(true); setLoading(true);
getAllMentorTasks() getAllMentorTasks()
.then(setTasks) .then(setTasks)
+1 -2
View File
@@ -1,5 +1,4 @@
// hooks/useUser.ts import { useState, useEffect, useCallback, useSyncExternalStore } from "react";
import { useState, useEffect, useCallback, useRef, useSyncExternalStore } from "react";
import { loginUser, getProfile, updateProfile } from "../services/UserService"; import { loginUser, getProfile, updateProfile } from "../services/UserService";
import type { Mahasiswa } from "../types/project.types"; import type { Mahasiswa } from "../types/project.types";
import { getMahasiswa } from "../services/MahasiswaService"; import { getMahasiswa } from "../services/MahasiswaService";
-1
View File
@@ -1,4 +1,3 @@
// services/mahasiswa.service.ts
import api from '../lib/api'; import api from '../lib/api';
import type { Mahasiswa, MahasiswaResponse } from "../types/project.types" import type { Mahasiswa, MahasiswaResponse } from "../types/project.types"
-1
View File
@@ -1,4 +1,3 @@
// services/ProjectService.ts
import api from "../lib/api"; import api from "../lib/api";
import type { import type {
Project, Project,
-1
View File
@@ -1,4 +1,3 @@
// services/TaskService.ts
import api from "../lib/api"; import api from "../lib/api";
import type { import type {
ProjectTask, ProjectTask,
-1
View File
@@ -1,4 +1,3 @@
// services/UserService.ts
import api from "../lib/api"; import api from "../lib/api";
import type { CurrentUser, UpdateProfilePayload } from "../types/user.types"; import type { CurrentUser, UpdateProfilePayload } from "../types/user.types";
import type { Mahasiswa, MahasiswaResponse } from "../types/project.types"; import type { Mahasiswa, MahasiswaResponse } from "../types/project.types";