Repository: Alfinpratamaa/HWID-SPOOFER Branch: main Commit: 47125543225a Files: 39 Total size: 79.1 KB Directory structure: gitextract_riayleb2/ ├── .gitignore ├── .nvmrc ├── README.md ├── app/ │ ├── about/ │ │ └── page.tsx │ ├── api/ │ │ └── contact/ │ │ └── route.ts │ ├── blogs/ │ │ ├── [slug]/ │ │ │ ├── PostContent.tsx │ │ │ └── page.tsx │ │ └── page.tsx │ ├── contact/ │ │ └── page.tsx │ ├── globals.css │ ├── layout.tsx │ ├── page.tsx │ ├── robots.ts │ └── sitemap.ts ├── components/ │ ├── AboutSection.tsx │ ├── BackgroundStar.tsx │ ├── Banner.tsx │ ├── BlogClientPage.tsx │ ├── ClientEffects.tsx │ ├── CursorBacground.tsx │ ├── LinksCard.tsx │ ├── Navbar.tsx │ ├── NebulaEffect.tsx │ ├── ProjectCard.tsx │ ├── ProjectSection.tsx │ ├── Skills.tsx │ ├── about.tsx │ └── contact.tsx ├── libs/ │ ├── NavbarLinks.ts │ ├── constant.ts │ └── sanity.client.ts ├── next.config.mjs ├── package.json ├── postcss.config.mjs ├── provider/ │ ├── ClientProvicer.tsx │ └── SkillData.tsx ├── tailwind.config.js ├── tsconfig.json └── types/ └── index.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. # dependencies /node_modules /.pnp .pnp.js .yarn/install-state.gz # testing /coverage # next.js /.next/ /out/ # production /build # misc .DS_Store *.pem # debug npm-debug.log* yarn-debug.log* yarn-error.log* # local env files .env*.local # vercel .vercel .env.local # typescript *.tsbuildinfo next-env.d.ts ================================================ FILE: .nvmrc ================================================ 20.19.1 ================================================ FILE: README.md ================================================ hello world ================================================ FILE: app/about/page.tsx ================================================ import AboutSection from "@/components/AboutSection"; import { Metadata } from "next"; import React from "react"; export const metadata: Metadata = { title: "About | evrea", description: "Frontendwith 1+ years of expertise. Junior Software Engineer. Specializing web apps, UX, and JavaScript technologies.", keywords: [ "Developer", "Portfolio", "Developer Portflio", "Muhamad Alfin Pratama", "Next.js", "React", "ReactNative", "Android", "nodejs", "alfin", "alfin pratama", "muhamad alfin", "muhamad alfin pratama", "muhammad alfin pratama", "frontend web", "Frontend Developer", "Front-end Developer", "Front End Developer", "Frontend Engineer", "Front-end Engineer", "Front End Engineer", "Muhamad Alfin", "Alfin Pratama", "Muhamad", "Alfin", "Pratama", "evrea", ], }; const AboutPage = () => { return (
); }; export default AboutPage; ================================================ FILE: app/api/contact/route.ts ================================================ export const runtime = "edge"; export const POST = async (req: Request) => { try { const { name, email, subject, message } = await req.json(); if (!name || !email || !subject || !message) { return Response.json( { error: "Please fill in all fields" }, { status: 400 } ); } const res = await fetch( `https://api.mailgun.net/v3/${process.env.MAILGUN_DOMAIN}/messages`, { method: "POST", headers: { Authorization: `Basic ${btoa(`api:${process.env.MAILGUN_PASSWORD}`)}`, "Content-Type": "application/x-www-form-urlencoded", }, body: new URLSearchParams({ from: process.env.MAILGUN_EMAIL!, to: "muhamadalfinpratamaa@gmail.com", subject, text: message, }), } ); if (!res.ok) throw new Error("Failed to send email"); return Response.json({ message: "Email sent" }, { status: 200 }); } catch (error) { console.error(error); return Response.json({ error: "Error sending email" }, { status: 500 }); } }; ================================================ FILE: app/blogs/[slug]/PostContent.tsx ================================================ import Image from "next/image"; import { PortableText } from "@portabletext/react"; export default function PostContent({ post, ptComponents, urlFor }: any) { return (

{post.title}

{post.mainImage && (
{post.title}
)}
); } ================================================ FILE: app/blogs/[slug]/page.tsx ================================================ import { client } from "@/libs/sanity.client"; import imageUrlBuilder from "@sanity/image-url"; import { groq } from "next-sanity"; import Image from "next/image"; import { notFound } from "next/navigation"; import { PortableText } from "@portabletext/react"; import { Metadata } from "next"; export const runtime = "edge"; const builder = imageUrlBuilder(client); function urlFor(source: any) { return builder.image(source); } const postQuery = groq`*[_type == "post" && slug.current == $slug][0] { ..., author-> { name, image, bio }, "categories": categories[]->title }`; const ptComponents = { types: { image: ({ value }: { value: any }) => { if (!value?.asset?._ref) return null; return (
{value.alt
); }, }, block: { h1: ({ children }: any) => (

{children}

), h2: ({ children }: any) => (

{children}

), h3: ({ children }: any) => (

{children}

), normal: ({ children }: any) =>

{children}

, blockquote: ({ children }: any) => (
{children}
), }, marks: { strong: ({ children }: any) => ( {children} ), em: ({ children }: any) => ( {children} ), link: ({ value, children }: any) => { const target = (value?.href || "").startsWith("http") ? "_blank" : undefined; return ( {children} ); }, }, list: { bullet: ({ children }: any) => ( ), number: ({ children }: any) => (
    {children}
), }, }; type PageProps = { params: Promise<{ slug: string }>; }; // Generate dynamic metadata export async function generateMetadata({ params, }: PageProps): Promise { const { slug } = await params; const post = await client.fetch(postQuery, { slug }); if (!post) { return { title: "Post Not Found", description: "The blog post you're looking for doesn't exist.", }; } const getPlainTextFromBody = (body: any[]): string => { if (!body || !Array.isArray(body)) return ""; return body .filter((block) => block._type === "block" && block.style === "normal") .map((block) => block.children?.map((child: any) => child.text).join("")) .join(" ") .slice(0, 160); }; const description = post.excerpt || getPlainTextFromBody(post.body) || "Read this blog post"; const imageUrl = post.mainImage ? urlFor(post.mainImage).width(1200).height(630).url() : null; return { title: `${post.title} - Blog Alfin Pratama`, description, keywords: post.categories?.join(", "), authors: [{ name: post.author?.name || "Anonymous" }], openGraph: { title: post.title, description, type: "article", publishedTime: post.publishedAt, authors: [post.author?.name || "Anonymous"], images: imageUrl ? [ { url: imageUrl, width: 1200, height: 630, alt: post.title, }, ] : [], }, twitter: { card: "summary_large_image", title: post.title, description, images: imageUrl ? [imageUrl] : [], }, alternates: { canonical: `/blogs/${slug}`, }, }; } export default async function PostPage({ params }: PageProps) { const { slug } = await params; const post = await client.fetch(postQuery, { slug }); if (!post) { notFound(); } return (
{/* Header Artikel */}

{post.title}

{post.author?.image && ( {`Foto )} {post.author?.name || "Anonim"}
{new Date(post.publishedAt).toLocaleDateString()}
{`Gambar
); } ================================================ FILE: app/blogs/page.tsx ================================================ import { client } from "@/libs/sanity.client"; import { groq } from "next-sanity"; import BlogClientPage from "@/components/BlogClientPage"; // Kita akan buat komponen ini import { Metadata } from "next"; export const runtime = 'edge'; export const metadata: Metadata = { title: "Blog - Alfin Pratama", description: "Sharing my thoughts, experiences, and insights on web development, programming, and technology.", openGraph: { title: "Blog - Alfin Pratama", description: "Sharing my thoughts, experiences, and insights on web development, programming, and technology.", url: "https://evrea.tech/blogs", siteName: "Alfin Pratama", images: [ { url: "https://evrea.tech/avatar2_.webp", width: 1200, height: 630, alt: "Blog - Alfin Pratama", }, ], locale: "id_ID", type: "website", }, keywords: [ "blog", "web development", "programming", "technology", "javascript", "react", "next.js", "sanity.io", "tailwindcss", "personal blog", "blog alfin", "coding", "software development", "frontend", "backend", "fullstack", "tutorials", "tips and tricks", "web design", "developer insights", "blog evrea", ], }; export interface Post { _id: string; title: string; slug: { current: string }; mainImage: any; publishedAt: string; authorName: string; authorImage: any; categories: Category[]; } export interface Category { _id: string; title: string; slug: { current: string }; } const postsQuery = groq`*[_type == "post"] | order(publishedAt desc) { _id, title, slug, mainImage, publishedAt, "authorName": author->name, "authorImage": author->image, "categories": categories[]->{ _id, title, slug } }`; const categoriesQuery = groq`*[_type == "category"] | order(title asc) { _id, title, slug }`; export default async function BlogPage() { const posts: Post[] = await client.fetch( postsQuery, {}, { cache: "no-store" } ); const categories: Category[] = await client.fetch( categoriesQuery, {}, { cache: "no-store", } ); return ; } ================================================ FILE: app/contact/page.tsx ================================================ import { type Metadata } from "next"; import ContactForm from "@/components/contact"; export const metadata: Metadata = { title: "Contact | evrea", description: "Contact me via email to start a conversation or ask me anything", keywords: [ "Contact", "Portfolio", "Developer Portflio", "Muhamad Alfin Pratama", "Next.js", "React", "ReactNative", "Android", "nodejs", "alfin", "alfin pratama", "muhamad alfin", "muhamad alfin pratama", "muhammad alfin pratama", "frontend web", "Frontend Developer", "Front-end Developer", "Front End Developer", "Frontend Engineer", "Front-end Engineer", "Front End Engineer", "Muhamad Alfin", "Alfin Pratama", "Muhamad", "Alfin", "Pratama", "evrea", ], }; const ContactPage = () => { return ; }; export default ContactPage; ================================================ FILE: app/globals.css ================================================ @import "tailwindcss"; :root { --foreground-rgb: 0, 0, 0; --background-start-rgb: 214, 219, 220; --background-end-rgb: 255, 255, 255; } @layer components { .banner-heading { @apply text-4xl font-bold text-white md:text-5xl; } } @media (prefers-color-scheme: dark) { :root { --foreground-rgb: 255, 255, 255; --background-start-rgb: 0, 0, 0; --background-end-rgb: 0, 0, 0; } } body { color: rgb(var(--foreground-rgb)); background: linear-gradient( to bottom, transparent, rgb(var(--background-end-rgb)) ) rgb(var(--background-start-rgb)); } @layer utilities { .text-balance { text-wrap: balance; } } .custom-scrollbar { scrollbar-width: thin; /* Firefox */ scrollbar-color: #6b21a8 #262626; /* thumb, track */ } /* Chrome, Edge, Safari */ .custom-scrollbar::-webkit-scrollbar { width: 6px; /* lebar scrollbar */ } .custom-scrollbar::-webkit-scrollbar-track { background: #262626; /* warna track */ border-radius: 10px; } .custom-scrollbar::-webkit-scrollbar-thumb { background-color: #6b21a8; /* warna thumb */ border-radius: 10px; } .custom-scrollbar::-webkit-scrollbar-thumb:hover { background-color: #9333ea; /* hover state */ } ================================================ FILE: app/layout.tsx ================================================ import type { Metadata } from "next"; import { Poppins } from "next/font/google"; import "./globals.css"; import Navbar from "@/components/Navbar"; import ClientEffects from "@/components/ClientEffects"; import { ClientProvider } from "@/provider/ClientProvicer"; import CursorBackground from "@/components/CursorBacground"; const poppins = Poppins({ subsets: ["latin"], weight: ["100", "200", "300", "400", "500", "600", "700", "800", "900"], variable: "--font-poppins", }); export const metadata: Metadata = { metadataBase: new URL("https://alfinpratamaa.github.io/"), title: "Muhamad Alfin Pratama", description: "Fullstack Developer & DevOps Engineer with 1+ years of expertise in JavaScript, TypeScript, Golang, and PHP. Experienced with Next.js, React.js, Laravel frameworks, Docker containerization, VPS hosting, and CI/CD pipelines.", keywords: [ "Fullstack Developer", "DevOps Engineer", "Portfolio", "Developer Portfolio", "Muhamad Alfin Pratama", "Next.js", "React", "ReactNative", "Laravel", "JavaScript", "TypeScript", "Golang", "PHP", "Docker", "VPS Hosting", "CI/CD", "DevOps", "Backend Developer", "Frontend Developer", "Fullstack", "Web Developer", "Software Engineer", "Software Developer", "Devops Engineer", "web development", "programming", "alfin", "alfin pratama", "muhamad alfin", "muhamad alfin pratama", "muhammad alfin pratama", "Muhamad Alfin", "Alfin Pratama", "Muhamad", "Alfin", "Pratama", "evrea", ], creator: "Muhamad Alfin Pratama", applicationName: "evrea", icons: "/avatar_2.webp", openGraph: { title: "Muhamad Alfin Pratama", description: "Fullstack Developer & DevOps Engineer with 1+ years of expertise in JavaScript, TypeScript, Golang, and PHP. Specializing in Next.js, React.js, Laravel, Docker, VPS hosting, and CI/CD pipelines.", images: "/Avatar_2.webp", }, alternates: { canonical: "https://evrea.tech", }, }; export default function RootLayout({ children, }: { children: React.ReactNode; }) { return (
{children}
); } ================================================ FILE: app/page.tsx ================================================ import Banner from "@/components/Banner"; export default function Home() { return (
); } ================================================ FILE: app/robots.ts ================================================ import { MetadataRoute } from "next"; export default function robots(): MetadataRoute.Robots { return { rules: { userAgent: "*", allow: "/", }, sitemap: "https://evrea.tech/sitemap.xml", }; } ================================================ FILE: app/sitemap.ts ================================================ // app/sitemap.ts import { MetadataRoute } from "next"; import { client } from "@/libs/sanity.client"; import { groq } from "next-sanity"; // Definisikan tipe untuk data post yang diambil interface Post { slug: { current: string; }; _updatedAt: string; // Gunakan _updatedAt untuk lastModified } export default async function sitemap(): Promise { const baseUrl = "https://evrea.tech"; // 1. Ambil semua slug post dari Sanity const query = groq`*[_type == "post"]{ "slug": slug.current, _updatedAt }`; const posts: Post[] = await client.fetch(query); const postUrls = posts.map((post) => ({ url: `${baseUrl}/blogs/${post.slug}`, lastModified: new Date(post._updatedAt), changeFrequency: "daily" as "daily", priority: 0.8, })); // 2. Gabungkan dengan URL statis Anda return [ { url: baseUrl, lastModified: new Date(), changeFrequency: "daily", priority: 1, }, { url: `${baseUrl}/about`, lastModified: new Date(), changeFrequency: "monthly", priority: 0.7, }, { url: `${baseUrl}/blogs`, lastModified: new Date(), changeFrequency: "daily", priority: 0.9, }, ...postUrls, ]; } ================================================ FILE: components/AboutSection.tsx ================================================ "use client"; import Image from "next/image"; import Link from "next/link"; import { motion } from "framer-motion"; import { FaGithub, FaInstagram, FaLinkedin, FaLocationDot, FaTelegram, FaWhatsapp, FaCalendar, } from "react-icons/fa6"; import { MdOutlineWork } from "react-icons/md"; import Skills from "./Skills"; import { FaExternalLinkAlt } from "react-icons/fa"; import { ProjectsSection } from "./ProjectSection"; // Data untuk media sosial const socials = [ { icon: , href: "https://www.instagram.com/visfiveor5" }, { icon: , href: "https://www.linkedin.com/in/alfinpr" }, { icon: , href: "https://www.github.com/Alfinpratamaa" }, { icon: , href: "https://wa.me/6285175369960" }, { icon: , href: "https://t.me/visfiveor5" }, ]; const AboutSection = () => { // Varian animasi untuk container utama (stagger effect) const containerVariants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { staggerChildren: 0.1, // Setiap anak akan muncul dengan jeda 0.1 detik }, }, }; // Varian animasi untuk setiap item di dalam grid const itemVariants = { hidden: { opacity: 0, y: 20 }, visible: { opacity: 1, y: 0, transition: { duration: 0.5 } }, }; return ( {/* ======================================= */} {/* KARTU 1: PROFIL UTAMA */} {/* ======================================= */}
Muhamad Alfin Pratama

Muhamad Alfin Pratama

Student | Frontend Web

Bandung, West Java, Indonesia

{/* ======================================= */} {/* KARTU 2: DESKRIPSI DIRI */} {/* ======================================= */}

I am a frontend developer with expertise in React, Next.js, and Tailwind CSS. Passionate about creating user-friendly interfaces and delivering high-quality code. Let's work together!

{/* ======================================= */} {/* KARTU 3: MEDIA SOSIAL */} {/* ======================================= */}
{socials.map((social, index) => ( {social.icon} ))}
{/* ======================================= */} {/* KARTU 4: EXPERIENCE */} {/* ======================================= */}

Experience

Frontend Developer Intern

Pilih Jurusan Logo Pilih Jurusan

September 2024 - December 2024

  • • Reorganized assets and components to improve code cleanliness and reusability
  • • Adapted and implemented unit testing using Cypress according to the latest code
  • • Developed new sections and pages based on project manager requests
  • • Built CMS with Firebase integration and data interaction using server actions in Next.js
{/* ======================================= */} {/* KARTU 5: PROJECTS */} {/* ======================================= */} {/* ======================================= */} {/* KARTU 6: SKILLS */} {/* ======================================= */}
); }; export default AboutSection; ================================================ FILE: components/BackgroundStar.tsx ================================================ "use client"; import React, { useMemo, useRef } from "react"; import { Canvas, useFrame } from "@react-three/fiber"; import * as THREE from "three"; import { Points, PointMaterial } from "@react-three/drei"; // @ts-ignore import * as random from "maath/random/dist/maath-random.esm"; const StarBackground = () => { const ref = useRef(null!); const positions = useMemo( () => random.inSphere(new Float32Array(5000 * 3), { radius: 1.2 }), [] ); const posRef = useRef(positions.slice()); useFrame((state, delta) => { if (!ref.current) return; const { mouse } = state; const pos = posRef.current; for (let i = 0; i < pos.length; i += 3) { const x = pos[i]; const y = pos[i + 1]; const mx = mouse.x * 1.2; const my = mouse.y * 1.2; const dx = x - mx; const dy = y - my; const dist = Math.sqrt(dx * dx + dy * dy); if (dist < 0.25) { const force = (0.25 - dist) * 0.03; pos[i] += dx * force; pos[i + 1] += dy * force; } } // kasih tau three.js kalau posisi berubah ref.current.geometry.attributes.position.needsUpdate = true; // rotasi background biar tetap hidup ref.current.rotation.x -= delta / 20; ref.current.rotation.y -= delta / 30; }); return ( ); }; const StarsCanvas: React.FC = () => (
); export default StarsCanvas; ================================================ FILE: components/Banner.tsx ================================================ "use client"; import React from "react"; import Image from "next/image"; import Link from "next/link"; import dynamic from "next/dynamic"; import { motion } from "framer-motion"; const TypeAnimation = dynamic( () => import("react-type-animation").then((mod) => mod.TypeAnimation), { ssr: false } ); const FaCloudDownloadAlt = dynamic( () => import("react-icons/fa").then((mod) => mod.FaCloudDownloadAlt), { ssr: false } ); const Banner: React.FC = () => { const containerVariants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { delay: 0.2, staggerChildren: 0.2, }, }, }; const itemVariants = { hidden: { opacity: 0, y: 30 }, visible: { opacity: 1, y: 0, transition: { duration: 0.6 } }, }; const cardVariants = { hidden: { opacity: 0, scale: 0.9 }, visible: { opacity: 1, scale: 1, transition: { duration: 0.7 } }, }; return (
Alfin

Hi,
I'm Alfin

{/* Typing Effect Card */}

Passionate Fullstack Developer with a focus on{" "} React.js, php, golang, nodejs {" "} development using{" "} Next.js, laravel, gofiber, expressjs {" "} framework, dedicated to crafting elegant and user-friendly web applications.

{/* Action Buttons */} Download CV{" "} See My blogs
); }; export default Banner; ================================================ FILE: components/BlogClientPage.tsx ================================================ "use client"; import Link from "next/link"; import Image from "next/image"; import { useState } from "react"; import { client } from "@/libs/sanity.client"; import imageUrlBuilder from "@sanity/image-url"; import { Post, Category } from "@/app/blogs/page"; const builder = imageUrlBuilder(client); function urlFor(source: any) { return builder.image(source); } interface BlogClientPageProps { posts: Post[]; categories: Category[]; } export default function BlogClientPage({ posts, categories, }: BlogClientPageProps) { const [filteredPosts, setFilteredPosts] = useState(posts); const [activeCategory, setActiveCategory] = useState("all"); const filterPosts = (categoryId: string) => { setActiveCategory(categoryId); if (categoryId === "all") { setFilteredPosts(posts); } else { const filtered = posts.filter((post) => post.categories?.some((cat) => cat._id === categoryId) ); setFilteredPosts(filtered); } }; return (
{/* Header */}

My Blog

Sharing my thoughts, experiences, and insights on web development, programming, and technology.

{/* Category Tabs */}

Filter by category :

{/* Tab Semua */} {/* Tab Categories */} {categories.map((category) => { const categoryPostCount = posts.filter((post) => post.categories?.some((cat) => cat._id === category._id) ).length; return ( ); })}
{/* Posts Grid */} {filteredPosts.length === 0 ? (

Tidak ada artikel untuk kategori ini.

) : (
{filteredPosts.map((post) => (
{`Gambar
{/* Categories Badge */} {post.categories && post.categories.length > 0 && (
{post.categories.slice(0, 2).map((category) => ( {category.title} ))} {post.categories.length > 2 && ( +{post.categories.length - 2} )}
)}

{post.title}

{post.authorImage && ( {`Foto )}
{post.authorName} {new Date(post.publishedAt).toLocaleDateString("id-ID")}
))}
)}
); } ================================================ FILE: components/ClientEffects.tsx ================================================ "use client"; import dynamic from "next/dynamic"; const StarsCanvas = dynamic(() => import("./BackgroundStar"), { ssr: false, }); const NebulaEffect = dynamic( () => import("./NebulaEffect").then((mod) => ({ default: mod.NebulaEffect, })), { ssr: false, } ); export default function ClientEffects() { return ( <> ); } ================================================ FILE: components/CursorBacground.tsx ================================================ "use client"; import { useState } from "react"; export default function CursorBackground({ children, }: { children: React.ReactNode; }) { const [pos, setPos] = useState({ x: 0, y: 0 }); return (
setPos({ x: e.clientX, y: e.clientY })} > {/* Layer gradasi mengikuti mouse */}
{/* Konten utama di atas */}
{children}
); } ================================================ FILE: components/LinksCard.tsx ================================================ import Image from "next/image"; interface LinksCardProps { name: string; icon?: any; } const LinksCard = ({ name, icon }: LinksCardProps) => { return (

{name}

{name}
) } export default LinksCard ================================================ FILE: components/Navbar.tsx ================================================ "use client"; import { NavbarLinks } from "@/libs/NavbarLinks"; import Link from "next/link"; import React, { useState } from "react"; import { motion } from "framer-motion"; import { AiOutlineClose, AiOutlineMenu } from "react-icons/ai"; const Navbar: React.FC<{}> = () => { const navItemVariants = { hidden: { opacity: 0, y: -20 }, visible: { opacity: 1, y: 0, transition: { duration: 0.5 } }, }; const containerVariants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { delayChildren: 0.2, staggerChildren: 0.1, }, }, }; const [nav, setNav] = useState(false); const handleNav = () => { setNav(!nav); }; return (
Evrea {/* Navbar for desktop (sudah benar) */}
{NavbarLinks.map((nav) => ( {nav.text} ))}
{/* Navbar for mobile (bagian yang diperbaiki) */}
{/* PERBAIKAN 1: Mengubah div menjadi button untuk aksesibilitas */}
{/* PERBAIKAN 2: Judul dipindahkan ke luar
    */}

    Evrea

      {" "} {/* Menambahkan ID untuk dihubungkan dengan aria-controls */} {/* PERBAIKAN 3: Struktur
    • -> dibenarkan */} {NavbarLinks.map((item) => (
    • setNav(false)} className="block p-4 text-black hover:text-white cursor-pointer" // 'block' agar link mengisi seluruh area
    • > {item.text}
    • ))}
); }; export default Navbar; ================================================ FILE: components/NebulaEffect.tsx ================================================ export const NebulaEffect = () => { return ( <>
); }; ================================================ FILE: components/ProjectCard.tsx ================================================ 'use client'; import Image from 'next/image'; import Link from 'next/link'; import { FaGithub, FaLink } from 'react-icons/fa'; import { motion } from 'framer-motion'; interface ProjectCardProps { srcImg: string; demoUrl: string; srcGithub: string; title: string; description: string; date: string; } const ProjectCard = ({ srcImg, demoUrl, srcGithub, title, description, date }: ProjectCardProps) => { // Variants for the container const containerVariants = { hidden: { opacity: 0 }, visible: { opacity: 1, transition: { delayChildren: 0.3, staggerChildren: 0.2, }, }, }; // Variants for the image const imageVariants = { hidden: { opacity: 0, x: -50 }, visible: { opacity: 1, x: 0, transition: { duration: 0.5 } }, }; // Variants for the text const textVariants = { hidden: { opacity: 0, x: 50 }, visible: { opacity: 1, x: 0, transition: { duration: 0.5 } }, }; return ( < motion.div className="relative w-full h-56 z-[1]" variants={imageVariants} > {title} {date} {title} {description}
); }; export default ProjectCard; ================================================ FILE: components/ProjectSection.tsx ================================================ "use client"; import { project_data } from "@/libs/constant"; import { motion } from "framer-motion"; import { useState } from "react"; import { FaGithub, FaExternalLinkAlt, FaCalendar } from "react-icons/fa"; import Image from "next/image"; import Link from "next/link"; export function ProjectsSection() { const [activeIndex, setActiveIndex] = useState(0); return ( {/* Header */}

Projects

{/* Sidebar Project List */}
{project_data.map((proj, index) => ( ))}
{/* Project Details */}
{project_data[activeIndex].title}

{project_data[activeIndex].title}

{/* Date */}
{project_data[activeIndex].date}
{/* Description */}

{project_data[activeIndex].description}

{/* Links */}
{project_data[activeIndex].linkDemo && ( Demo )} {project_data[activeIndex].linkGithub && ( Code )}
); } ================================================ FILE: components/Skills.tsx ================================================ "use client"; import { motion } from "framer-motion"; import Image from "next/image"; import { Frontend_skill, Backend_Skill, tools } from "@/libs/constant"; const categories = [ { title: "Frontend", data: Frontend_skill, color: "from-green-500/20 to-teal-500/20", }, { title: "Backend", data: Backend_Skill, color: "from-blue-500/20 to-purple-500/20", }, { title: "Tools", data: tools, color: "from-orange-500/20 to-red-500/20" }, ]; const Skills = () => { return (
Technical Skills Technologies and tools I work with
{categories.map((cat, i) => ( {/* Background Pattern */}
{/* Content */}
{/* Category Header */}

{cat.title}

{/* Skills Grid */}
{cat.data.map((skill, idx) => (
{skill.skill_name}

{skill.skill_name}

))}
{/* Skills Count */}

{cat.data.length} skills

))}
); }; export default Skills; ================================================ FILE: components/about.tsx ================================================ 'use client' import { Link as ScrollLink, Element } from 'react-scroll'; import Skills from '@/components/Skills'; import AboutSection from '@/components/AboutSection'; import { FaArrowDownLong } from "react-icons/fa6"; export const About = () => { return (
); }; ================================================ FILE: components/contact.tsx ================================================ "use client"; import { FaMapMarkerAlt, FaPhoneAlt, FaEnvelope } from "react-icons/fa"; import { motion } from "framer-motion"; import { useState } from "react"; const ContactForm = () => { const [name, setName] = useState(""); const [email, setEmail] = useState(""); const [subject, setSubject] = useState(""); const [message, setMessage] = useState(""); const [status, setStatus] = useState(""); const handleSubmit = async (event: React.FormEvent) => { event.preventDefault(); setStatus("Sending..."); try { const response = await fetch("/api/contact", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ name, email, subject, message }), }); const data = await response.json(); setStatus(data.message); } catch (error) { console.error("Error sending email:", error); setStatus("Error sending email"); } finally { setName(""); setEmail(""); setSubject(""); setMessage(""); } }; return (
{/* Left side - Contact Info */}

Get in touch

Fill in the form to start a conversation

Bandung City, West Java, Indonesia

+62 851 7536 9960

muhamadalfinpratamaa@gmail.com

{/* Right side - Contact Form */}
setName(e.target.value)} placeholder="Enter your full name" className="w-full py-3 px-4 rounded-lg bg-neutral-700/50 border border-neutral-600 text-white placeholder-gray-400 font-medium focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500/20 transition-all duration-300" required />
setEmail(e.target.value)} placeholder="Enter your email" className="w-full py-3 px-4 rounded-lg bg-neutral-700/50 border border-neutral-600 text-white placeholder-gray-400 font-medium focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500/20 transition-all duration-300" required />
setSubject(e.target.value)} placeholder="Enter email subject" className="w-full py-3 px-4 rounded-lg bg-neutral-700/50 border border-neutral-600 text-white placeholder-gray-400 font-medium focus:border-purple-500 focus:outline-none focus:ring-2 focus:ring-purple-500/20 transition-all duration-300" required />