feat: update all sections
This commit is contained in:
parent
72cdecfdea
commit
a38936fb9a
8 changed files with 212 additions and 54 deletions
BIN
app/assets/avatar.png
Normal file
BIN
app/assets/avatar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 224 KiB |
103
app/components/business-section.tsx
Normal file
103
app/components/business-section.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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}
|
||||||
</a>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
|
|
||||||
{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}
|
||||||
</a>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<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>
|
<>
|
||||||
<a
|
<Button asChild>
|
||||||
href={profile.githubUrl}
|
<a
|
||||||
target="_blank"
|
href={profile.githubUrl[0]}
|
||||||
rel="noopener noreferrer"
|
target="_blank"
|
||||||
>
|
rel="noopener noreferrer"
|
||||||
<Github className="mr-2 h-5 w-5" />
|
>
|
||||||
GitHub
|
<Github className="mr-2 h-4 w-4" />
|
||||||
</a>
|
GitHub (Main)
|
||||||
</Button>
|
</a>
|
||||||
|
</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>
|
||||||
|
|
|
||||||
|
|
@ -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) => (
|
||||||
|
|
|
||||||
|
|
@ -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>
|
<>
|
||||||
<a
|
<Button asChild>
|
||||||
href={profile.githubUrl}
|
<a
|
||||||
target="_blank"
|
href={profile.githubUrl[0]}
|
||||||
rel="noopener noreferrer"
|
target="_blank"
|
||||||
>
|
rel="noopener noreferrer"
|
||||||
<Github className="mr-2 h-4 w-4" />
|
>
|
||||||
GitHub
|
<Github className="mr-2 h-4 w-4" />
|
||||||
</a>
|
GitHub (Main)
|
||||||
</Button>
|
</a>
|
||||||
|
</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">
|
||||||
|
|
|
||||||
|
|
@ -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}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue