refactor: simplify project and tag icon handling

This commit is contained in:
2025-05-01 14:22:51 +02:00
parent fc5b43d029
commit 5b917142f2

View File

@ -9,68 +9,31 @@ import {
SiDiscord, SiDiscord,
} from "react-icons/si"; } from "react-icons/si";
import { BsLightningChargeFill } from "react-icons/bs"; import { BsLightningChargeFill } from "react-icons/bs";
import { FaCode } from "react-icons/fa";
import { HiOutlineCollection } from "react-icons/hi"; import { HiOutlineCollection } from "react-icons/hi";
import { MdLaunch } from "react-icons/md"; import { MdLaunch } from "react-icons/md";
import FadeIn from "../ui/FadeIn.astro"; import FadeIn from "../ui/FadeIn.astro";
interface Project { const iconMap = {
title: string; "Next.js": SiNextdotjs,
year: string; "Tailwind CSS": SiTailwindcss,
description: string; "TypeScript": SiTypescript,
icon: any; "PHP": SiPhp,
tags: Array<{ "JavaScript": SiJavascript,
name: string; "MySQL": SiMysql,
icon: any; "Performance": BsLightningChargeFill,
}>; "Discord API": SiDiscord,
link?: string;
}
const getTagIcon = (tag: string) => {
switch (tag) {
case "Next.js":
return SiNextdotjs;
case "Tailwind CSS":
return SiTailwindcss;
case "TypeScript":
return SiTypescript;
case "PHP":
return SiPhp;
case "JavaScript":
return SiJavascript;
case "MySQL":
return SiMysql;
case "Performance":
return BsLightningChargeFill;
case "Discord API":
return SiDiscord;
default:
return null;
}
}; };
const getProjectIcon = (title: string) => { const projects = [
if (title.includes("ventry")) {
return MdLaunch;
} else if (title.includes("ShareUpload")) {
return HiOutlineCollection;
} else if (title.includes("RestoreM")) {
return SiDiscord;
} else {
return FaCode;
}
};
const projects: Project[] = [
{ {
title: "ventry.host v2", title: "ventry.host v2",
year: "2025", year: "2025",
description: "Free file hosting revamped with a modern design and improved user experience.", description: "Free file hosting revamped with a modern design and improved user experience.",
icon: getProjectIcon("ventry.host v2"), icon: MdLaunch,
tags: [ tags: [
{ name: "Next.js", icon: getTagIcon("Next.js") }, { name: "Next.js", icon: iconMap["Next.js"] },
{ name: "Tailwind CSS", icon: getTagIcon("Tailwind CSS") }, { name: "Tailwind CSS", icon: iconMap["Tailwind CSS"] },
{ name: "TypeScript", icon: getTagIcon("TypeScript") }, { name: "TypeScript", icon: iconMap["TypeScript"] },
], ],
link: "https://ventry.host", link: "https://ventry.host",
}, },
@ -78,40 +41,36 @@ const projects: Project[] = [
title: "ventry.host", title: "ventry.host",
year: "2023", year: "2023",
description: "A free file hosting solution with thousands of daily visitors.", description: "A free file hosting solution with thousands of daily visitors.",
icon: getProjectIcon("ventry.host"), icon: MdLaunch,
tags: [ tags: [
{ name: "PHP", icon: getTagIcon("PHP") }, { name: "PHP", icon: iconMap["PHP"] },
{ name: "JavaScript", icon: getTagIcon("JavaScript") }, { name: "JavaScript", icon: iconMap["JavaScript"] },
{ name: "MySQL", icon: getTagIcon("MySQL") }, { name: "MySQL", icon: iconMap["MySQL"] },
], ],
}, },
{ {
title: "ShareUpload", title: "ShareUpload",
year: "2022", year: "2022",
description: "High-performance file sharing platform with unlimited storage.", description: "High-performance file sharing platform with unlimited storage.",
icon: getProjectIcon("ShareUpload"), icon: HiOutlineCollection,
tags: [ tags: [
{ name: "PHP", icon: getTagIcon("PHP") }, { name: "PHP", icon: iconMap["PHP"] },
{ name: "MySQL", icon: getTagIcon("MySQL") }, { name: "MySQL", icon: iconMap["MySQL"] },
{ name: "Performance", icon: getTagIcon("Performance") }, { name: "Performance", icon: iconMap["Performance"] },
], ],
}, },
{ {
title: "RestoreM", title: "RestoreM",
year: "2023", year: "2023",
description: "Discord server backup and restoration service.", description: "Discord server backup and restoration service.",
icon: getProjectIcon("RestoreM"), icon: SiDiscord,
tags: [ tags: [
{ name: "PHP", icon: getTagIcon("PHP") }, { name: "PHP", icon: iconMap["PHP"] },
{ name: "MySQL", icon: getTagIcon("MySQL") }, { name: "MySQL", icon: iconMap["MySQL"] },
{ name: "Discord API", icon: getTagIcon("Discord API") }, { name: "Discord API", icon: iconMap["Discord API"] },
], ],
}, },
]; ].sort((a, b) => parseInt(b.year) - parseInt(a.year));
const sortedProjects = [...projects].sort((a, b) => {
return parseInt(b.year) - parseInt(a.year);
});
--- ---
<section id="work" class="py-24 sm:py-40 px-4 sm:px-8 relative"> <section id="work" class="py-24 sm:py-40 px-4 sm:px-8 relative">
@ -138,7 +97,7 @@ const sortedProjects = [...projects].sort((a, b) => {
<div class="grid gap-24 sm:gap-32"> <div class="grid gap-24 sm:gap-32">
{ {
sortedProjects.map((project, index) => ( projects.map((project, index) => (
<FadeIn delay={index * 0.1}> <FadeIn delay={index * 0.1}>
<div class="group relative" data-project-card> <div class="group relative" data-project-card>
{project.link && ( {project.link && (
@ -182,17 +141,14 @@ const sortedProjects = [...projects].sort((a, b) => {
Technologies Technologies
</h4> </h4>
<div class="flex flex-wrap items-center gap-3 sm:gap-4 relative z-20 pointer-events-none"> <div class="flex flex-wrap items-center gap-3 sm:gap-4 relative z-20 pointer-events-none">
{project.tags.map((tag, tagIndex) => { {project.tags.map((tag) => (
const Icon = tag.icon; <span class="flex items-center text-sm sm:text-base theme-text-70 font-['Instrument_Sans'] tracking-tight font-medium py-2 px-5 group-hover:theme-text-90 transition-all duration-300 theme-bg-05 rounded-full shadow-sm border border-transparent group-hover:theme-border">
return ( <span class="mr-2 flex items-center">
<span class="flex items-center text-sm sm:text-base theme-text-70 font-['Instrument_Sans'] tracking-tight font-medium py-2 px-5 group-hover:theme-text-90 transition-all duration-300 theme-bg-05 rounded-full shadow-sm border border-transparent group-hover:theme-border"> <tag.icon className="text-lg" />
<span class="mr-2 flex items-center">
<Icon className="text-lg" />
</span>
{tag.name}
</span> </span>
); {tag.name}
})} </span>
))}
</div> </div>
</div> </div>
</div> </div>
@ -254,35 +210,21 @@ const sortedProjects = [...projects].sort((a, b) => {
const modal = document.getElementById('redirect-modal'); const modal = document.getElementById('redirect-modal');
const modalOverlay = document.getElementById('modal-overlay'); const modalOverlay = document.getElementById('modal-overlay');
const modalContent = modal?.querySelector('.max-w-md'); const modalContent = modal?.querySelector('.max-w-md');
const cancelButton = document.getElementById('cancel-redirect'); const redirectUrlEl = document.getElementById('redirect-url');
const confirmButton = document.getElementById('confirm-redirect');
const redirectUrlElement = document.getElementById('redirect-url');
let currentUrl = ''; let currentUrl = '';
let currentTitle = '';
function showModal(title, url) { function showModal(url) {
currentUrl = url; currentUrl = url;
currentTitle = title; if (redirectUrlEl) redirectUrlEl.textContent = new URL(url).hostname;
if (redirectUrlElement) {
redirectUrlElement.textContent = new URL(url).hostname;
}
if (modal) { if (modal) {
modal.classList.remove('pointer-events-none'); modal.classList.remove('pointer-events-none');
setTimeout(() => { setTimeout(() => {
modal.classList.add('opacity-100'); modal.classList.add('opacity-100');
if (modalOverlay) { modalOverlay?.classList.add('bg-opacity-60', 'backdrop-blur-sm');
modalOverlay.classList.add('bg-opacity-60', 'backdrop-blur-sm'); modalContent?.classList.remove('scale-95', 'opacity-0');
} modalContent?.classList.add('scale-100', 'opacity-100');
if (modalContent) {
modalContent.classList.remove('scale-95', 'opacity-0');
modalContent.classList.add('scale-100', 'opacity-100');
}
}, 10); }, 10);
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
} }
} }
@ -290,13 +232,9 @@ const sortedProjects = [...projects].sort((a, b) => {
function hideModal() { function hideModal() {
if (modal) { if (modal) {
modal.classList.remove('opacity-100'); modal.classList.remove('opacity-100');
if (modalOverlay) { modalOverlay?.classList.remove('bg-opacity-60', 'backdrop-blur-sm');
modalOverlay.classList.remove('bg-opacity-60', 'backdrop-blur-sm'); modalContent?.classList.remove('scale-100', 'opacity-100');
} modalContent?.classList.add('scale-95', 'opacity-0');
if (modalContent) {
modalContent.classList.remove('scale-100', 'opacity-100');
modalContent.classList.add('scale-95', 'opacity-0');
}
setTimeout(() => { setTimeout(() => {
modal.classList.add('pointer-events-none'); modal.classList.add('pointer-events-none');
@ -305,41 +243,24 @@ const sortedProjects = [...projects].sort((a, b) => {
} }
} }
const projectLinks = document.querySelectorAll('[data-project-link]'); document.querySelectorAll('[data-project-link]').forEach(link => {
projectLinks.forEach(link => {
link.addEventListener('click', (e) => { link.addEventListener('click', (e) => {
e.preventDefault(); e.preventDefault();
const url = link.getAttribute('data-project-url'); const url = link.getAttribute('data-project-url');
const title = link.getAttribute('data-project-title'); if (url) showModal(url);
if (url) {
showModal(title, url);
}
}); });
}); });
if (cancelButton) { document.getElementById('cancel-redirect')?.addEventListener('click', hideModal);
cancelButton.addEventListener('click', hideModal); modalOverlay?.addEventListener('click', hideModal);
} document.getElementById('confirm-redirect')?.addEventListener('click', () => {
if (currentUrl) window.open(currentUrl, '_blank');
if (modalOverlay) { hideModal();
modalOverlay.addEventListener('click', hideModal); });
}
if (confirmButton) {
confirmButton.addEventListener('click', () => {
if (currentUrl) {
window.open(currentUrl, '_blank');
}
hideModal();
});
}
document.addEventListener('keydown', (e) => { document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') { if (e.key === 'Escape') hideModal();
hideModal();
}
}); });
}); });
</script> </script>