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 && (
)}
);
}
================================================
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 (
);
},
},
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 && (
)}
{post.author?.name || "Anonim"}
•
{new Date(post.publishedAt).toLocaleDateString()}
);
}
================================================
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 (
);
}
================================================
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
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
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 (
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 */}
filterPosts("all")}
className={`px-4 py-2 rounded-full text-sm font-medium transition-all duration-300 ${
activeCategory === "all"
? "bg-purple-600 text-white shadow-lg shadow-purple-500/30"
: "bg-neutral-800 text-gray-300 hover:bg-neutral-700 hover:text-white"
}`}
>
All Topics ({posts.length})
{/* Tab Categories */}
{categories.map((category) => {
const categoryPostCount = posts.filter((post) =>
post.categories?.some((cat) => cat._id === category._id)
).length;
return (
filterPosts(category._id)}
className={`px-4 py-2 rounded-full text-sm font-medium transition-all duration-300 ${
activeCategory === category._id
? "bg-purple-600 text-white shadow-lg shadow-purple-500/30"
: "bg-neutral-800 text-gray-300 hover:bg-neutral-700 hover:text-white"
}`}
>
{category.title} ({categoryPostCount})
);
})}
{/* Posts Grid */}
{filteredPosts.length === 0 ? (
Tidak ada artikel untuk kategori ini.
) : (
{filteredPosts.map((post) => (
{/* 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 && (
)}
{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 (
)
}
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 */}
{nav ? (
) : (
)}
{/* PERBAIKAN 2: Judul dipindahkan ke luar
);
};
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} >
motion.div>
{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) => (
setActiveIndex(index)}
className={`text-left transition-colors truncate ${
activeIndex === index
? "text-orange-400 border-l-2 border-orange-400 pl-2"
: "text-gray-400 hover:text-white pl-2"
}`}
>
{proj.title}
))}
{/* Project Details */}
{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 */}
{/* Skills Grid */}
{cat.data.map((skill, idx) => (
))}
{/* Skills Count */}
))}
);
};
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
muhamadalfinpratamaa@gmail.com
{/* Right side - Contact Form */}
);
};
export default ContactForm;
================================================
FILE: libs/NavbarLinks.ts
================================================
export const NavbarLinks = [
{
text: "Home",
href: "/",
},
{
text: "About Me",
href: "/about",
},
{
text: "Blogs",
href: "/blogs",
},
{
text: "Contact Me",
href: "/contact",
},
];
================================================
FILE: libs/constant.ts
================================================
export const Social_Icons = [
{
link: "https://github.com/alfinpratamaa",
image: "/Github.svg",
alt: "Github",
},
{
link: "https://www.instagram.com/visfiveor5",
image: "/Instagram.svg",
alt: "Instagram",
},
{
link: "https://www.linkedin.com/in/alfinpr/",
image: "/LinkedIn.svg",
alt: "LinkedIn",
},
{
link: "https://t.me/visfiveor5",
image: "/telegram.svg",
alt: "Telegram",
},
{
link: "https://wa.me/6285175369960",
image: "/whatsapp.svg",
alt: "Whatsapp",
},
];
export const Backend_Skill = [
{
skill_name: "Node.js",
Image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/nodejs/nodejs-original.svg",
width: 80,
height: 80,
},
{
skill_name: "Express.js",
Image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/express/express-original.svg",
width: 80,
height: 80,
},
{
skill_name: "NestJS",
Image: "https://img.jsdelivr.com/github.com/nestjs.png",
width: 80,
height: 80,
},
{
skill_name: "Golang",
Image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/go/go-original.svg",
width: 80,
height: 80,
},
{
skill_name: "PHP",
Image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/php/php-original.svg",
width: 80,
height: 80,
},
{
skill_name: "Laravel",
Image: "https://www.svgrepo.com/show/353985/laravel.svg",
width: 80,
height: 80,
},
{
skill_name: "PostgreSQL",
Image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/postgresql/postgresql-original.svg",
width: 80,
height: 80,
},
{
skill_name: "MySQL",
Image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/mysql/mysql-original.svg",
width: 80,
height: 80,
},
{
skill_name: "MongoDB",
Image:
"https://cdn.jsdelivr.net/gh/devicons/devicon/icons/mongodb/mongodb-original.svg",
width: 80,
height: 80,
},
];
export const Frontend_skill = [
{
skill_name: "Html 5",
Image: "/html.png",
width: 80,
height: 80,
},
{
skill_name: "Css",
Image: "/css.png",
width: 80,
height: 80,
},
{
skill_name: "Java Script",
Image: "/js.png",
width: 65,
height: 65,
},
{
skill_name: "Tailwind Css",
Image: "/tailwind.png",
width: 80,
height: 80,
},
{
skill_name: "React",
Image: "/react.png",
width: 80,
height: 80,
},
{
skill_name: "React Query",
Image: "/reactquery.png",
width: 80,
height: 80,
},
{
skill_name: "Type Script",
Image: "/ts.png",
width: 80,
height: 80,
},
{
skill_name: "Next js",
Image: "/Next.js.png",
width: 80,
height: 80,
},
];
export const tools = [
{
skill_name: "Figma",
Image: "/Figma.svg",
width: 80,
height: 80,
},
{
skill_name: "Git",
Image: "/git.png",
width: 80,
height: 80,
},
{
skill_name: "Github",
Image: "/Github.svg",
width: 80,
height: 80,
},
{
skill_name: "VS Code",
Image: "/vscode.png",
width: 80,
height: 80,
},
{
skill_name: "Postman",
Image: "/postman.png",
width: 80,
height: 80,
},
];
export const project_data = [
{
title: "Mangaloomp (Comics Reader)",
description:
"Mangaloom is a web application for reading manga online. Built with Next.js and Tailwind CSS, it features a clean UI, search functionality, and a vast collection of manga titles.",
image: "/mangaloom.png",
linkDemo: "https://mangaloom.app",
linkGithub: "https://github.com/Mangaloom/web",
date: "March 2024 - Present",
},
{
title: "E-Commerce Book Store",
description:
"An e-commerce website for books built with Laravel + Livewire and Tailwind CSS. Features include product browsing, shopping cart, user authentication, and admin panel for managing products and orders.",
image: "/ngabaca.png",
linkDemo: "https://ngabaca.me",
linkGithub: "https://github.com/Alfinpratamaa/ngabaca",
date: "June 2025 - August 2025",
},
{
title: "Selfgalery Web Apps",
description:
"Photo gallery application built with Next.js and Tailwind CSS for storing photos. Features upload, delete, and download functionality.",
image: "/selfgalery.png",
linkDemo: "https://selfgalery.vercel.app/",
linkGithub: "https://github.com/Alfinpratamaa/selfgalery",
date: "January 2024 - February 2024",
},
{
title: "Portfolio",
description: "Personal portfolio built with Next.js and Tailwind CSS",
image: "/portfolio.png",
linkDemo: "https://muhamadalfinpratama.vercel.app/",
linkGithub: "https://github.com/Alfinpratamaa/portfolio",
date: "June 2024",
},
];
================================================
FILE: libs/sanity.client.ts
================================================
import { createClient } from 'next-sanity'
const projectId = process.env.NEXT_PUBLIC_SANITY_PROJECT_ID
const dataset = process.env.NEXT_PUBLIC_SANITY_DATASET
const apiVersion = process.env.NEXT_PUBLIC_SANITY_API_VERSION
export const client = createClient({
projectId,
dataset,
apiVersion,
useCdn: true,
})
================================================
FILE: next.config.mjs
================================================
/** @type {import('next').NextConfig} */
const nextConfig = {
images: {
remotePatterns: [
{
protocol: "https",
hostname: "**",
},
{
protocol: "http",
hostname: "**",
},
],
},
output: "standalone",
};
export default nextConfig;
================================================
FILE: package.json
================================================
{
"name": "out",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbopack",
"build": "next build --turbopack",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@portabletext/react": "^4.0.1",
"@sanity/image-url": "^1.1.0",
"@types/mailgun-js": "^0.22.18",
"@types/nodemailer": "^7.0.1",
"@types/nodemailer-mailgun-transport": "^1.4.6",
"@types/react-scroll": "^1.8.10",
"@types/react-typing-effect": "^2.0.7",
"@vercel/analytics": "^1.5.0",
"clsx": "^2.1.1",
"framer-motion": "^12.23.12",
"mailgun-js": "^0.22.0",
"next": "^15.5.2",
"next-sanity": "^10.0.15",
"next-sitemap": "^4.2.3",
"nextjs-toploader": "^3.8.16",
"nodemailer": "^7.0.5",
"nodemailer-mailgun-transport": "^2.1.5",
"react": "^19.1.1",
"react-dom": "^19.1.1",
"react-icons": "^5.5.0",
"react-scroll": "^1.9.3",
"react-tippy": "^1.4.0",
"react-type-animation": "^3.2.0",
"sharp": "^0.34.3"
},
"devDependencies": {
"@react-three/drei": "^10.7.4",
"@react-three/fiber": "^9.3.0",
"@tailwindcss/postcss": "^4.1.12",
"@types/node": "^24.3.0",
"@types/react": "^19.1.11",
"@types/react-dom": "^19.1.8",
"autoprefixer": "^10.4.21",
"eslint": "^9.34.0",
"eslint-config-next": "^15.5.2",
"postcss": "^8.5.6",
"react-intersection-observer": "^9.16.0",
"tailwindcss": "^4.1.12",
"three": "^0.179.1",
"typescript": "^5.9.2"
},
"engines": {
"node": ">=20.x"
}
}
================================================
FILE: postcss.config.mjs
================================================
export default {
plugins: {
"@tailwindcss/postcss": {},
},
};
================================================
FILE: provider/ClientProvicer.tsx
================================================
"use client";
import NextTopLoader from "nextjs-toploader";
function ClientProvider() {
return (
<>
>
);
}
export { ClientProvider };
================================================
FILE: provider/SkillData.tsx
================================================
'use client';
import { useInView } from 'react-intersection-observer';
import { motion } from 'framer-motion';
import Image from 'next/image';
interface Props {
src: string;
width: number;
height: number;
index: number;
name?: string;
}
const SkillData = ({ src, width, height, index, name }: Props) => {
const [ref, inView] = useInView({
triggerOnce: true
})
const imageVariant = {
hidden: {
opacity: 0,
},
visible: {
opacity: 1
}
}
const animationDelay = 0.3
return (
)
}
export default SkillData
================================================
FILE: tailwind.config.js
================================================
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: "class",
content: [
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
// Or if using `src` directory:
"./src/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
container: {
padding: {
DEFAULT: "1rem",
sm: "2rem",
lg: "4rem",
xl: "15rem",
"2xl": "22rem",
},
},
extend: {
backgroundImage: {
"gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
"gradient-conic":
"conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
},
backgroundColor: {
primary: "#DD7C7F",
},
},
},
plugins: [],
};
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./*"
]
},
"target": "ES2017"
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}
================================================
FILE: types/index.ts
================================================
export type ProjectFrontmatter = {
slug?: string;
title: string;
publishedAt?: string;
lastUpdated?: string;
description: string;
category?: string;
techs?: string;
banner?: string;
link?: string;
github?: string;
youtube?: string;
};