feat: update all sections

This commit is contained in:
afiqzudinhadi 2025-06-07 00:08:14 +08:00
parent 72cdecfdea
commit a38936fb9a
8 changed files with 212 additions and 54 deletions

BIN
app/assets/avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

View file

@ -0,0 +1,103 @@
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Globe, Instagram, Facebook } from "lucide-react";
import { Badge } from "@/components/ui/badge";
interface Business {
_id: string;
name: string;
role: string;
established: string;
description: string;
url?: string;
logo?: string;
instagramUrl?: string;
websiteUrl?: string;
facebookUrl?: string;
tiktokUrl?: string;
}
interface BusinessSectionProps {
businesses: Business[];
}
export function BusinessSection({ businesses = [] }: BusinessSectionProps) {
return (
<section id="businesses" className="py-20">
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-4xl font-bold text-center mb-16">Businesses</h2>
<div className="grid md:grid-cols-2 gap-8">
{businesses.map((business) => (
<Card
key={business._id}
className="hover:shadow-lg transition-shadow"
>
<CardHeader>
<CardTitle className="flex items-center justify-between">
{business.name}
<Badge variant="secondary">Est. {business.established}</Badge>
</CardTitle>
<div className="text-sm text-foreground mb-1">
{business.role}
</div>
<CardDescription className="leading-relaxed">
{business.description}
</CardDescription>
</CardHeader>
<CardContent className="space-y-2">
<div className="flex gap-3 pt-2">
{business.instagramUrl && (
<a
href={business.instagramUrl}
target="_blank"
rel="noopener noreferrer"
aria-label="Instagram"
>
<Instagram className="w-5 h-5 text-pink-500" />
</a>
)}
{business.websiteUrl && (
<a
href={business.websiteUrl}
target="_blank"
rel="noopener noreferrer"
aria-label="Website"
>
<Globe className="w-5 h-5 text-blue-500" />
</a>
)}
{business.facebookUrl && (
<a
href={business.facebookUrl}
target="_blank"
rel="noopener noreferrer"
aria-label="Facebook"
>
<Facebook className="w-5 h-5 text-blue-700" />
</a>
)}
{business.tiktokUrl && (
<a
href={business.tiktokUrl}
target="_blank"
rel="noopener noreferrer"
aria-label="TikTok"
>
{/* <Tiktok className="w-5 h-5 text-black" /> */}
</a>
)}
</div>
</CardContent>
</Card>
))}
</div>
</div>
</section>
);
}

View file

@ -14,7 +14,7 @@ interface ContactSectionProps {
email: string; email: string;
phone?: string; phone?: string;
location: string; location: string;
githubUrl?: string; githubUrl?: string[];
linkedinUrl?: string; linkedinUrl?: string;
}; };
} }
@ -34,32 +34,36 @@ export function ContactSection({ profile }: ContactSectionProps) {
</CardHeader> </CardHeader>
<CardContent className="space-y-6"> <CardContent className="space-y-6">
<div className="grid gap-4"> <div className="grid gap-4">
<div className="flex items-center space-x-3 p-3 rounded-lg bg-muted/50"> <a
href={`mailto:${profile.email}`}
className="flex items-center space-x-3 p-3 rounded-lg bg-muted/50 hover:bg-muted transition-colors ring-0 hover:ring-2 hover:ring-primary/40 focus:outline-none focus:ring-2 focus:ring-primary/60"
style={{ textDecoration: "none" }}
>
<Mail className="h-5 w-5 text-primary" /> <Mail className="h-5 w-5 text-primary" />
<div> <div>
<p className="font-medium">Email</p> <p className="font-medium">Email</p>
<a <span className="text-muted-foreground hover:text-primary transition-colors">
href={`mailto:${profile.email}`}
className="text-muted-foreground hover:text-primary transition-colors"
>
{profile.email} {profile.email}
</span>
</div>
</a> </a>
</div>
</div>
{profile.phone && ( {profile.phone && (
<div className="flex items-center space-x-3 p-3 rounded-lg bg-muted/50"> <a
href={`https://wa.me/${profile.phone.replace(/\s+/g, "")}`}
className="flex items-center space-x-3 p-3 rounded-lg bg-muted/50 hover:bg-muted transition-colors ring-0 hover:ring-2 hover:ring-primary/40 focus:outline-none focus:ring-2 focus:ring-primary/60"
target="_blank"
rel="noopener noreferrer"
style={{ textDecoration: "none" }}
>
<Phone className="h-5 w-5 text-primary" /> <Phone className="h-5 w-5 text-primary" />
<div> <div>
<p className="font-medium">Phone</p> <p className="font-medium">Phone</p>
<a <span className="text-muted-foreground hover:text-primary transition-colors">
href={`tel:${profile.phone}`}
className="text-muted-foreground hover:text-primary transition-colors"
>
{profile.phone} {profile.phone}
</span>
</div>
</a> </a>
</div>
</div>
)} )}
<div className="flex items-center space-x-3 p-3 rounded-lg bg-muted/50"> <div className="flex items-center space-x-3 p-3 rounded-lg bg-muted/50">
@ -72,17 +76,31 @@ export function ContactSection({ profile }: ContactSectionProps) {
</div> </div>
<div className="flex justify-center space-x-4 pt-6"> <div className="flex justify-center space-x-4 pt-6">
{profile.githubUrl && ( {profile.githubUrl && profile.githubUrl.length > 0 && (
<Button variant="outline" size="lg" asChild> <>
<Button asChild>
<a <a
href={profile.githubUrl} href={profile.githubUrl[0]}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<Github className="mr-2 h-5 w-5" /> <Github className="mr-2 h-4 w-4" />
GitHub GitHub (Main)
</a> </a>
</Button> </Button>
{profile.githubUrl[1] && (
<Button asChild variant="outline">
<a
href={profile.githubUrl[1]}
target="_blank"
rel="noopener noreferrer"
>
<Github className="mr-2 h-4 w-4" />
GitHub (Alt)
</a>
</Button>
)}
</>
)} )}
{profile.linkedinUrl && ( {profile.linkedinUrl && (
<Button variant="outline" size="lg" asChild> <Button variant="outline" size="lg" asChild>
@ -96,12 +114,6 @@ export function ContactSection({ profile }: ContactSectionProps) {
</a> </a>
</Button> </Button>
)} )}
<Button size="lg" asChild>
<a href={`mailto:${profile.email}`}>
<Mail className="mr-2 h-5 w-5" />
Send Email
</a>
</Button>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>

View file

@ -13,7 +13,7 @@ interface Experience {
company: string; company: string;
position: string; position: string;
duration: string; duration: string;
description: string; description: string[];
technologies: string[]; technologies: string[];
current: boolean; current: boolean;
} }
@ -51,7 +51,11 @@ export function ExperienceSection({ experiences }: ExperienceSectionProps) {
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<p className="text-muted-foreground mb-4 leading-relaxed"> <p className="text-muted-foreground mb-4 leading-relaxed">
{exp.description} <ul className="list-disc list-inside space-y-1">
{exp.description.map((item, idx) => (
<li key={idx}>{item}</li>
))}
</ul>
</p> </p>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{exp.technologies.map((tech) => ( {exp.technologies.map((tech) => (

View file

@ -1,17 +1,21 @@
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Github, Linkedin, Mail, MapPin, Phone } from "lucide-react"; import { Github, Linkedin, Mail, MapPin, Phone } from "lucide-react";
import avatar from "@/assets/avatar.png";
interface HeroSectionProps { interface HeroSectionProps {
profile: { profile: {
name: string; name: string;
title: string; title: string;
bio: string;
email: string; email: string;
phone?: string; phone?: string;
location: string; location: string;
githubUrl?: string; githubUrl?: string[];
linkedinUrl?: string; linkedinUrl?: string;
about?: {
description: string[];
technologies?: string[];
};
}; };
} }
@ -32,6 +36,10 @@ export function HeroSection({ profile }: HeroSectionProps) {
<div className="text-center"> <div className="text-center">
<div className="mb-8"> <div className="mb-8">
<Avatar className="w-32 h-32 mx-auto mb-6"> <Avatar className="w-32 h-32 mx-auto mb-6">
<AvatarImage
src={avatar}
style={{ width: "100%", height: "100%" }}
/>
<AvatarFallback className="text-4xl font-bold bg-gradient-to-br from-primary to-primary/80 text-primary-foreground"> <AvatarFallback className="text-4xl font-bold bg-gradient-to-br from-primary to-primary/80 text-primary-foreground">
{profile.name {profile.name
.split(" ") .split(" ")
@ -46,22 +54,40 @@ export function HeroSection({ profile }: HeroSectionProps) {
{profile.title} {profile.title}
</h2> </h2>
<p className="text-xl text-muted-foreground max-w-3xl mx-auto leading-relaxed"> <p className="text-xl text-muted-foreground max-w-3xl mx-auto leading-relaxed">
{profile.bio} {profile.about?.description?.map((desc, idx) => (
<span key={idx} className={idx > 0 ? "block mt-2" : undefined}>
{desc}
</span>
))}
</p> </p>
</div> </div>
<div className="flex flex-wrap justify-center gap-4 mb-8"> <div className="flex flex-wrap justify-center gap-4 mb-8">
{profile.githubUrl && ( {profile.githubUrl && profile.githubUrl.length > 0 && (
<>
<Button asChild> <Button asChild>
<a <a
href={profile.githubUrl} href={profile.githubUrl[0]}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<Github className="mr-2 h-4 w-4" /> <Github className="mr-2 h-4 w-4" />
GitHub GitHub (Main)
</a> </a>
</Button> </Button>
{profile.githubUrl[1] && (
<Button asChild variant="outline">
<a
href={profile.githubUrl[1]}
target="_blank"
rel="noopener noreferrer"
>
<Github className="mr-2 h-4 w-4" />
GitHub (Alt)
</a>
</Button>
)}
</>
)} )}
{profile.linkedinUrl && ( {profile.linkedinUrl && (
<Button asChild variant="outline"> <Button asChild variant="outline">

View file

@ -12,7 +12,13 @@ export function Navigation({ profile }: NavigationProps) {
useEffect(() => { useEffect(() => {
const handleScroll = () => { const handleScroll = () => {
const sections = ["about", "experience", "projects", "skills", "contact"]; const sections = [
"about",
"experience",
"projects",
"businesses",
"contact",
];
const scrollPosition = window.scrollY + 100; const scrollPosition = window.scrollY + 100;
for (const section of sections) { for (const section of sections) {
@ -46,7 +52,7 @@ export function Navigation({ profile }: NavigationProps) {
<div className="flex justify-between items-center h-16"> <div className="flex justify-between items-center h-16">
<div className="font-bold text-xl">{profile.name}</div> <div className="font-bold text-xl">{profile.name}</div>
<div className="hidden md:flex space-x-2"> <div className="hidden md:flex space-x-2">
{["about", "experience", "projects", "skills", "contact"].map( {["about", "experience", "projects", "businesses", "contact"].map(
(section) => ( (section) => (
<Button <Button
key={section} key={section}

View file

@ -82,7 +82,7 @@ export function ProjectsSection({ projects }: ProjectsSectionProps) {
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<ExternalLink className="mr-2 h-4 w-4" /> <ExternalLink className="mr-2 h-4 w-4" />
Live Demo Go to site
</a> </a>
</Button> </Button>
)} )}
@ -143,7 +143,7 @@ export function ProjectsSection({ projects }: ProjectsSectionProps) {
rel="noopener noreferrer" rel="noopener noreferrer"
> >
<ExternalLink className="mr-1 h-3 w-3" /> <ExternalLink className="mr-1 h-3 w-3" />
Demo Go to site
</a> </a>
</Button> </Button>
)} )}

View file

@ -5,11 +5,17 @@ import { ExperienceSection } from "@/components/experience-section";
import { ProjectsSection } from "@/components/projects-section"; import { ProjectsSection } from "@/components/projects-section";
import { ContactSection } from "@/components/contact-section"; import { ContactSection } from "@/components/contact-section";
import { portfolioData } from "@/data/portfolio"; import { portfolioData } from "@/data/portfolio";
import ProfileCard from "@/components/profile-card";
import avatar from "@/assets/avatar.png";
import { BusinessSection } from "~/components/business-section";
export const meta: MetaFunction = () => { export const meta: MetaFunction = () => {
return [ return [
{ title: `${portfolioData.profile.name} - Portfolio` }, { title: `${portfolioData.profile.name} - Portfolio` },
{ name: "description", content: portfolioData.profile.bio }, {
name: "description",
content: portfolioData.profile.about?.description.join(" "),
},
]; ];
}; };
@ -20,6 +26,7 @@ export default function Index() {
<HeroSection profile={portfolioData.profile} /> <HeroSection profile={portfolioData.profile} />
<ExperienceSection experiences={portfolioData.experiences} /> <ExperienceSection experiences={portfolioData.experiences} />
<ProjectsSection projects={portfolioData.projects} /> <ProjectsSection projects={portfolioData.projects} />
<BusinessSection businesses={portfolioData.businesses} />
<ContactSection profile={portfolioData.profile} /> <ContactSection profile={portfolioData.profile} />
</div> </div>
); );