feat: template from chef

This commit is contained in:
afiqzudinhadi 2025-06-05 22:29:55 +08:00
parent 7335ea2581
commit 64feeaa877
15 changed files with 1001 additions and 0 deletions

View file

@ -0,0 +1,111 @@
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Mail, Phone, MapPin, Github, Linkedin } from "lucide-react";
interface ContactSectionProps {
profile: {
name: string;
email: string;
phone?: string;
location: string;
githubUrl?: string;
linkedinUrl?: string;
};
}
export function ContactSection({ profile }: ContactSectionProps) {
return (
<section id="contact" className="py-20 bg-muted/30">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8">
<h2 className="text-4xl font-bold text-center mb-16">Get In Touch</h2>
<Card className="max-w-2xl mx-auto">
<CardHeader className="text-center">
<CardTitle className="text-2xl">Let's Work Together</CardTitle>
<CardDescription className="text-lg">
I'm always interested in new opportunities and exciting projects.
Feel free to reach out if you'd like to collaborate!
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid gap-4">
<div className="flex items-center space-x-3 p-3 rounded-lg bg-muted/50">
<Mail className="h-5 w-5 text-primary" />
<div>
<p className="font-medium">Email</p>
<a
href={`mailto:${profile.email}`}
className="text-muted-foreground hover:text-primary transition-colors"
>
{profile.email}
</a>
</div>
</div>
{profile.phone && (
<div className="flex items-center space-x-3 p-3 rounded-lg bg-muted/50">
<Phone className="h-5 w-5 text-primary" />
<div>
<p className="font-medium">Phone</p>
<a
href={`tel:${profile.phone}`}
className="text-muted-foreground hover:text-primary transition-colors"
>
{profile.phone}
</a>
</div>
</div>
)}
<div className="flex items-center space-x-3 p-3 rounded-lg bg-muted/50">
<MapPin className="h-5 w-5 text-primary" />
<div>
<p className="font-medium">Location</p>
<p className="text-muted-foreground">{profile.location}</p>
</div>
</div>
</div>
<div className="flex justify-center space-x-4 pt-6">
{profile.githubUrl && (
<Button variant="outline" size="lg" asChild>
<a
href={profile.githubUrl}
target="_blank"
rel="noopener noreferrer"
>
<Github className="mr-2 h-5 w-5" />
GitHub
</a>
</Button>
)}
{profile.linkedinUrl && (
<Button variant="outline" size="lg" asChild>
<a
href={profile.linkedinUrl}
target="_blank"
rel="noopener noreferrer"
>
<Linkedin className="mr-2 h-5 w-5" />
LinkedIn
</a>
</Button>
)}
<Button size="lg" asChild>
<a href={`mailto:${profile.email}`}>
<Mail className="mr-2 h-5 w-5" />
Send Email
</a>
</Button>
</div>
</CardContent>
</Card>
</div>
</section>
);
}

View file

@ -0,0 +1,72 @@
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Separator } from "@/components/ui/separator";
interface Experience {
_id: string;
company: string;
position: string;
duration: string;
description: string;
technologies: string[];
current: boolean;
}
interface ExperienceSectionProps {
experiences: Experience[];
}
export function ExperienceSection({ experiences }: ExperienceSectionProps) {
return (
<section id="experience" 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">Experience</h2>
<div className="space-y-8">
{experiences.map((exp, index) => (
<div key={exp._id}>
<Card className="hover:shadow-lg transition-shadow">
<CardHeader>
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
<div>
<CardTitle className="text-xl">{exp.company}</CardTitle>
<CardDescription className="text-lg font-medium text-primary">
{exp.position}
</CardDescription>
<p className="text-sm text-muted-foreground">
{exp.duration}
</p>
</div>
{exp.current && (
<Badge variant="secondary" className="w-fit">
Current
</Badge>
)}
</div>
</CardHeader>
<CardContent>
<p className="text-muted-foreground mb-4 leading-relaxed">
{exp.description}
</p>
<div className="flex flex-wrap gap-2">
{exp.technologies.map((tech) => (
<Badge key={tech} variant="outline">
{tech}
</Badge>
))}
</div>
</CardContent>
</Card>
{index < experiences.length - 1 && <Separator className="my-8" />}
</div>
))}
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,107 @@
import { Button } from "@/components/ui/button";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
import { Github, Linkedin, Mail, MapPin, Phone } from "lucide-react";
interface HeroSectionProps {
profile: {
name: string;
title: string;
bio: string;
email: string;
phone?: string;
location: string;
githubUrl?: string;
linkedinUrl?: string;
};
}
export function HeroSection({ profile }: HeroSectionProps) {
const scrollToSection = (sectionId: string) => {
const element = document.getElementById(sectionId);
if (element) {
element.scrollIntoView({ behavior: "smooth" });
}
};
return (
<section
id="about"
className="pt-16 min-h-screen flex items-center bg-gradient-to-br from-primary/5 to-secondary/5"
>
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-20">
<div className="text-center">
<div className="mb-8">
<Avatar className="w-32 h-32 mx-auto mb-6">
<AvatarFallback className="text-4xl font-bold bg-gradient-to-br from-primary to-primary/80 text-primary-foreground">
{profile.name
.split(" ")
.map((n) => n[0])
.join("")}
</AvatarFallback>
</Avatar>
<h1 className="text-5xl md:text-6xl font-bold mb-4">
{profile.name}
</h1>
<h2 className="text-2xl md:text-3xl text-primary font-medium mb-6">
{profile.title}
</h2>
<p className="text-xl text-muted-foreground max-w-3xl mx-auto leading-relaxed">
{profile.bio}
</p>
</div>
<div className="flex flex-wrap justify-center gap-4 mb-8">
{profile.githubUrl && (
<Button asChild>
<a
href={profile.githubUrl}
target="_blank"
rel="noopener noreferrer"
>
<Github className="mr-2 h-4 w-4" />
GitHub
</a>
</Button>
)}
{profile.linkedinUrl && (
<Button asChild variant="outline">
<a
href={profile.linkedinUrl}
target="_blank"
rel="noopener noreferrer"
>
<Linkedin className="mr-2 h-4 w-4" />
LinkedIn
</a>
</Button>
)}
<Button
onClick={() => scrollToSection("contact")}
variant="secondary"
>
<Mail className="mr-2 h-4 w-4" />
Get In Touch
</Button>
</div>
<div className="flex flex-wrap justify-center gap-6 text-muted-foreground">
<div className="flex items-center">
<MapPin className="mr-2 h-4 w-4" />
{profile.location}
</div>
<div className="flex items-center">
<Mail className="mr-2 h-4 w-4" />
{profile.email}
</div>
{profile.phone && (
<div className="flex items-center">
<Phone className="mr-2 h-4 w-4" />
{profile.phone}
</div>
)}
</div>
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,66 @@
import { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
interface NavigationProps {
profile: {
name: string;
};
}
export function Navigation({ profile }: NavigationProps) {
const [activeSection, setActiveSection] = useState("about");
useEffect(() => {
const handleScroll = () => {
const sections = ["about", "experience", "projects", "skills", "contact"];
const scrollPosition = window.scrollY + 100;
for (const section of sections) {
const element = document.getElementById(section);
if (element) {
const offsetTop = element.offsetTop;
const offsetBottom = offsetTop + element.offsetHeight;
if (scrollPosition >= offsetTop && scrollPosition < offsetBottom) {
setActiveSection(section);
break;
}
}
}
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
const scrollToSection = (sectionId: string) => {
const element = document.getElementById(sectionId);
if (element) {
element.scrollIntoView({ behavior: "smooth" });
}
};
return (
<nav className="fixed top-0 left-0 right-0 z-50 bg-background/80 backdrop-blur-md border-b">
<div className="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
<div className="font-bold text-xl">{profile.name}</div>
<div className="hidden md:flex space-x-2">
{["about", "experience", "projects", "skills", "contact"].map(
(section) => (
<Button
key={section}
variant={activeSection === section ? "default" : "ghost"}
onClick={() => scrollToSection(section)}
className="capitalize"
>
{section}
</Button>
)
)}
</div>
</div>
</div>
</nav>
);
}

View file

@ -0,0 +1,160 @@
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Github, ExternalLink, Star } from "lucide-react";
interface Project {
_id: string;
title: string;
description: string;
technologies: string[];
githubUrl?: string;
liveUrl?: string;
featured: boolean;
}
interface ProjectsSectionProps {
projects: Project[];
}
export function ProjectsSection({ projects }: ProjectsSectionProps) {
const featuredProjects = projects.filter((p) => p.featured);
const otherProjects = projects.filter((p) => !p.featured);
return (
<section id="projects" className="py-20 bg-muted/30">
<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">Projects</h2>
{featuredProjects.length > 0 && (
<div className="mb-16">
<h3 className="text-2xl font-semibold mb-8 flex items-center">
<Star className="mr-2 h-6 w-6 text-yellow-500" />
Featured Projects
</h3>
<div className="grid md:grid-cols-2 gap-8">
{featuredProjects.map((project) => (
<Card
key={project._id}
className="hover:shadow-lg transition-shadow"
>
<CardHeader>
<CardTitle className="flex items-center justify-between">
{project.title}
<Badge variant="secondary">Featured</Badge>
</CardTitle>
<CardDescription className="leading-relaxed">
{project.description}
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-2 mb-4">
{project.technologies.map((tech) => (
<Badge key={tech} variant="outline">
{tech}
</Badge>
))}
</div>
<div className="flex gap-2">
{project.githubUrl && (
<Button size="sm" variant="outline" asChild>
<a
href={project.githubUrl}
target="_blank"
rel="noopener noreferrer"
>
<Github className="mr-2 h-4 w-4" />
Code
</a>
</Button>
)}
{project.liveUrl && (
<Button size="sm" asChild>
<a
href={project.liveUrl}
target="_blank"
rel="noopener noreferrer"
>
<ExternalLink className="mr-2 h-4 w-4" />
Live Demo
</a>
</Button>
)}
</div>
</CardContent>
</Card>
))}
</div>
</div>
)}
{otherProjects.length > 0 && (
<div>
<h3 className="text-2xl font-semibold mb-8">Other Projects</h3>
<div className="grid md:grid-cols-2 lg:grid-cols-3 gap-6">
{otherProjects.map((project) => (
<Card
key={project._id}
className="hover:shadow-lg transition-shadow"
>
<CardHeader>
<CardTitle className="text-lg">{project.title}</CardTitle>
<CardDescription className="text-sm leading-relaxed">
{project.description}
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-1 mb-4">
{project.technologies.slice(0, 3).map((tech) => (
<Badge key={tech} variant="outline" className="text-xs">
{tech}
</Badge>
))}
{project.technologies.length > 3 && (
<Badge variant="outline" className="text-xs">
+{project.technologies.length - 3}
</Badge>
)}
</div>
<div className="flex gap-2">
{project.githubUrl && (
<Button size="sm" variant="outline" asChild>
<a
href={project.githubUrl}
target="_blank"
rel="noopener noreferrer"
>
<Github className="mr-1 h-3 w-3" />
Code
</a>
</Button>
)}
{project.liveUrl && (
<Button size="sm" asChild>
<a
href={project.liveUrl}
target="_blank"
rel="noopener noreferrer"
>
<ExternalLink className="mr-1 h-3 w-3" />
Demo
</a>
</Button>
)}
</div>
</CardContent>
</Card>
))}
</div>
</div>
)}
</div>
</section>
);
}

View file

@ -0,0 +1,54 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Progress } from "@/components/ui/progress";
interface Skill {
name: string;
level: number;
}
interface SkillCategory {
_id: string;
category: string;
items: Skill[];
}
interface SkillsSectionProps {
skills: SkillCategory[];
}
export function SkillsSection({ skills }: SkillsSectionProps) {
return (
<section id="skills" 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">Skills</h2>
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-8">
{skills.map((skillCategory) => (
<Card
key={skillCategory._id}
className="hover:shadow-lg transition-shadow"
>
<CardHeader>
<CardTitle className="text-xl text-center">
{skillCategory.category}
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{skillCategory.items.map((skill) => (
<div key={skill.name} className="space-y-2">
<div className="flex justify-between text-sm">
<span className="font-medium">{skill.name}</span>
<span className="text-muted-foreground">
{skill.level}%
</span>
</div>
<Progress value={skill.level} className="h-2" />
</div>
))}
</CardContent>
</Card>
))}
</div>
</div>
</section>
);
}

View file

@ -0,0 +1,48 @@
import * as React from "react"
import * as AvatarPrimitive from "@radix-ui/react-avatar"
import { cn } from "~/lib/utils"
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarImage, AvatarFallback }

View file

@ -0,0 +1,36 @@
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "~/lib/utils"
const badgeVariants = cva(
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
}
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }

View file

@ -0,0 +1,57 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "~/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
{
variants: {
variant: {
default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-9 px-4 py-2",
sm: "h-8 rounded-md px-3 text-xs",
lg: "h-10 rounded-md px-8",
icon: "h-9 w-9",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

View file

@ -0,0 +1,76 @@
import * as React from "react"
import { cn } from "~/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-xl border bg-card text-card-foreground shadow",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View file

@ -0,0 +1,26 @@
import * as React from "react"
import * as ProgressPrimitive from "@radix-ui/react-progress"
import { cn } from "~/lib/utils"
const Progress = React.forwardRef<
React.ElementRef<typeof ProgressPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
<ProgressPrimitive.Root
ref={ref}
className={cn(
"relative h-2 w-full overflow-hidden rounded-full bg-primary/20",
className
)}
{...props}
>
<ProgressPrimitive.Indicator
className="h-full w-full flex-1 bg-primary transition-all"
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
/>
</ProgressPrimitive.Root>
))
Progress.displayName = ProgressPrimitive.Root.displayName
export { Progress }

View file

@ -0,0 +1,29 @@
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "~/lib/utils"
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
{...props}
/>
)
)
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator }

127
app/data/portfolio.ts Normal file
View file

@ -0,0 +1,127 @@
export const portfolioData = {
profile: {
name: "Alex Johnson",
title: "Full Stack Developer",
bio: "Passionate full-stack developer with 5+ years of experience building scalable web applications. I love creating elegant solutions to complex problems and am always eager to learn new technologies.",
email: "alex.johnson@example.com",
phone: "+1 (555) 123-4567",
location: "San Francisco, CA",
githubUrl: "https://github.com/alexjohnson",
linkedinUrl: "https://linkedin.com/in/alexjohnson",
},
experiences: [
{
_id: "1",
company: "TechCorp Inc.",
position: "Senior Full Stack Developer",
duration: "2022 - Present",
description:
"Lead development of microservices architecture serving 1M+ users. Mentored junior developers and implemented CI/CD pipelines that reduced deployment time by 60%.",
technologies: [
"React",
"Node.js",
"TypeScript",
"AWS",
"Docker",
"PostgreSQL",
],
current: true,
},
{
_id: "2",
company: "StartupXYZ",
position: "Full Stack Developer",
duration: "2020 - 2022",
description:
"Built the entire web platform from scratch using modern technologies. Collaborated with designers and product managers to deliver features that increased user engagement by 40%.",
technologies: ["Vue.js", "Python", "Django", "Redis", "MySQL"],
current: false,
},
{
_id: "3",
company: "Digital Agency",
position: "Frontend Developer",
duration: "2019 - 2020",
description:
"Developed responsive websites and web applications for various clients. Focused on performance optimization and accessibility standards.",
technologies: ["JavaScript", "HTML5", "CSS3", "Sass", "Webpack"],
current: false,
},
],
projects: [
{
_id: "1",
title: "E-commerce Platform",
description:
"A full-featured e-commerce platform with real-time inventory management, payment processing, and admin dashboard.",
technologies: ["Next.js", "Stripe", "Prisma", "PostgreSQL"],
githubUrl: "https://github.com/alexjohnson/ecommerce-platform",
liveUrl: "https://ecommerce-demo.example.com",
featured: true,
},
{
_id: "2",
title: "Task Management App",
description:
"Collaborative task management application with real-time updates, file sharing, and team communication features.",
technologies: ["React", "Socket.io", "Express", "MongoDB"],
githubUrl: "https://github.com/alexjohnson/task-manager",
liveUrl: "https://taskmanager-demo.example.com",
featured: true,
},
{
_id: "3",
title: "Weather Dashboard",
description:
"Interactive weather dashboard with location-based forecasts, historical data visualization, and weather alerts.",
technologies: ["Vue.js", "Chart.js", "OpenWeather API"],
githubUrl: "https://github.com/alexjohnson/weather-dashboard",
liveUrl: "https://weather-demo.example.com",
featured: false,
},
],
skills: [
{
_id: "1",
category: "Frontend",
items: [
{ name: "React", level: 90 },
{ name: "TypeScript", level: 85 },
{ name: "Vue.js", level: 80 },
{ name: "Next.js", level: 85 },
{ name: "Tailwind CSS", level: 90 },
],
},
{
_id: "2",
category: "Backend",
items: [
{ name: "Node.js", level: 90 },
{ name: "Python", level: 80 },
{ name: "Express", level: 85 },
{ name: "Django", level: 75 },
{ name: "GraphQL", level: 70 },
],
},
{
_id: "3",
category: "Database",
items: [
{ name: "PostgreSQL", level: 85 },
{ name: "MongoDB", level: 80 },
{ name: "Redis", level: 75 },
{ name: "MySQL", level: 80 },
],
},
{
_id: "4",
category: "DevOps",
items: [
{ name: "AWS", level: 80 },
{ name: "Docker", level: 85 },
{ name: "CI/CD", level: 80 },
{ name: "Kubernetes", level: 70 },
],
},
],
};

28
app/routes/_index.tsx Normal file
View file

@ -0,0 +1,28 @@
import type { MetaFunction } from "@remix-run/node";
import { Navigation } from "@/components/navigation";
import { HeroSection } from "@/components/hero-section";
import { ExperienceSection } from "@/components/experience-section";
import { ProjectsSection } from "@/components/projects-section";
import { SkillsSection } from "@/components/skills-section";
import { ContactSection } from "@/components/contact-section";
import { portfolioData } from "@/data/portfolio";
export const meta: MetaFunction = () => {
return [
{ title: `${portfolioData.profile.name} - Portfolio` },
{ name: "description", content: portfolioData.profile.bio },
];
};
export default function Index() {
return (
<div className="min-h-screen bg-background">
<Navigation profile={portfolioData.profile} />
<HeroSection profile={portfolioData.profile} />
<ExperienceSection experiences={portfolioData.experiences} />
<ProjectsSection projects={portfolioData.projects} />
<SkillsSection skills={portfolioData.skills} />
<ContactSection profile={portfolioData.profile} />
</div>
);
}

View file

@ -23,6 +23,10 @@
},
"homepage": "https://github.com/afiqzudinhadi/afiqzudinhadi.github.io#readme",
"dependencies": {
"@radix-ui/react-avatar": "^1.1.10",
"@radix-ui/react-progress": "^1.1.7",
"@radix-ui/react-separator": "^1.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@remix-run/node": "^2.16.8",
"@remix-run/react": "^2.16.8",
"@remix-run/serve": "^2.16.8",