├── .eslintrc.cjs ├── .gitignore ├── README.md ├── index.html ├── note.md ├── package-lock.json ├── package.json ├── postcss.config.js ├── public └── icon.png ├── src ├── App.jsx ├── assets │ ├── amikom-kantin.png │ ├── profile-photo.png │ ├── react-openai.png │ └── snake-amazon.png ├── components │ ├── About │ │ ├── AboutContent.jsx │ │ ├── AboutMe.jsx │ │ ├── Card.jsx │ │ └── SkillsContent.jsx │ ├── Footer │ │ ├── Copyright.jsx │ │ ├── Footer.jsx │ │ └── SocialIcon.jsx │ ├── Header │ │ ├── Header.jsx │ │ ├── NavList.jsx │ │ └── ThemeToggle.jsx │ ├── Hero │ │ └── Hero.jsx │ ├── Projects │ │ ├── MyProjects.jsx │ │ ├── OpenLink.jsx │ │ ├── ProjectDetails.jsx │ │ ├── ProjectList.jsx │ │ └── Thumbnail.jsx │ └── Services │ │ ├── Services.jsx │ │ └── ServicesList.jsx ├── index.css ├── main.jsx └── pages │ ├── AboutPage.jsx │ ├── HomePage.jsx │ ├── NotFoundPage.jsx │ └── ProjectPage.jsx ├── tailwind.config.js ├── vercel.json └── vite.config.js /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:react/recommended', 7 | 'plugin:react/jsx-runtime', 8 | 'plugin:react-hooks/recommended', 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parserOptions: { ecmaVersion: 'latest', sourceType: 'module' }, 12 | settings: { react: { version: '18.2' } }, 13 | plugins: ['react-refresh'], 14 | rules: { 15 | 'react-refresh/only-export-components': [ 16 | 'warn', 17 | { allowConstantExport: true }, 18 | ], 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Personal Website 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /note.md: -------------------------------------------------------------------------------- 1 | 1. Create new project Vite 2 | 3 | ``` 4 | pnpm create vite@latest . 5 | or 6 | pnpm create vite@latest project-name 7 | ``` 8 | 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "portfolio", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint . --ext js,jsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0", 15 | "react-ga4": "^2.1.0", 16 | "react-router-dom": "^6.21.1", 17 | "react-simple-typewriter": "^5.0.1", 18 | "tailwind-hamburgers": "^1.3.5" 19 | }, 20 | "devDependencies": { 21 | "@types/react": "^18.2.43", 22 | "@types/react-dom": "^18.2.17", 23 | "@vitejs/plugin-react": "^4.2.1", 24 | "autoprefixer": "^10.4.16", 25 | "eslint": "^8.55.0", 26 | "eslint-plugin-react": "^7.33.2", 27 | "eslint-plugin-react-hooks": "^4.6.0", 28 | "eslint-plugin-react-refresh": "^0.4.5", 29 | "postcss": "^8.4.33", 30 | "tailwindcss": "^3.4.0", 31 | "vite": "^5.0.8" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamdanzull/Personal-Website/a9abce9c5a50be8b802517844b01a8ccc3b904d6/public/icon.png -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react" 2 | import { BrowserRouter as Router, Routes, Route } from "react-router-dom" 3 | import HomePage from "./pages/HomePage" 4 | import ProjectPage from "./pages/ProjectPage" 5 | import NotFoundPage from "./pages/NotFoundPage" 6 | import AboutPage from "./pages/AboutPage" 7 | import ReactGA from "react-ga4"; 8 | 9 | const trackingId = "G-ETW3F6K6VY" 10 | ReactGA.initialize(trackingId); 11 | 12 | export default function App() { 13 | 14 | useEffect(() => { 15 | ReactGA.send({ hitType: "pageview", page: "/" }); 16 | }, []) 17 | 18 | return ( 19 | <> 20 | 21 | 22 | } /> 23 | } /> 24 | } /> 25 | } /> 26 | 27 | 28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /src/assets/amikom-kantin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamdanzull/Personal-Website/a9abce9c5a50be8b802517844b01a8ccc3b904d6/src/assets/amikom-kantin.png -------------------------------------------------------------------------------- /src/assets/profile-photo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamdanzull/Personal-Website/a9abce9c5a50be8b802517844b01a8ccc3b904d6/src/assets/profile-photo.png -------------------------------------------------------------------------------- /src/assets/react-openai.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamdanzull/Personal-Website/a9abce9c5a50be8b802517844b01a8ccc3b904d6/src/assets/react-openai.png -------------------------------------------------------------------------------- /src/assets/snake-amazon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hamdanzull/Personal-Website/a9abce9c5a50be8b802517844b01a8ccc3b904d6/src/assets/snake-amazon.png -------------------------------------------------------------------------------- /src/components/About/AboutContent.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react-refresh/only-export-components */ 2 | /* eslint-disable react/prop-types */ 3 | import { useTypewriter, Cursor } from 'react-simple-typewriter' 4 | 5 | export const text = [`Hi everyone 👋\nI'm Muhammad Hamdan Zulfa, a 20-year-old Software Engineer and a student at AMIKOM University based in Yogyakarta, Indonesia. Every day, I'm immersed in writing lines of code. Bugs and errors seem to be my constant companions.`, `I'm always curious to learn something new, especially in web technology and design. I fall in love with JavaScript and TypeScript. Not just coding, I also took care of what I wrote. Writing clean code and make me comfortable.`] 6 | 7 | export function AboutContent({ aboutToggle }) { 8 | const [typing] = useTypewriter({ 9 | words: text, 10 | loop: 0, 11 | typeSpeed: 20, 12 | deleteSpeed: 20, 13 | delaySpeed: 3000 14 | }) 15 | return ( 16 | <> 17 |

hamdanzull@Host:~$ node aboutMe.js

18 |

19 | {aboutToggle ? typing : 'See you 👋'} 20 | 21 |

22 | 23 | ) 24 | } -------------------------------------------------------------------------------- /src/components/About/AboutMe.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import { AboutContent } from './AboutContent'; 3 | import Card from './Card'; 4 | import SkillsContent from './SkillsContent'; 5 | 6 | export default function AboutMe({ aboutToggle }) { 7 | return ( 8 |
9 |
10 |

11 | About Me 12 |

13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 |
23 |
24 | ) 25 | } -------------------------------------------------------------------------------- /src/components/About/Card.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | export default function Card({ children, title }) { 3 | return ( 4 |
5 |
6 |

{title}

7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | {children} 15 |
16 |
17 | ) 18 | } -------------------------------------------------------------------------------- /src/components/About/SkillsContent.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | const skills = [ 3 | { component: HtmlIcon, size: 32, label: "01.png" }, 4 | { component: CssIcon, size: 32, label: "02.png" }, 5 | { component: JsIcon, size: 32, label: "03.png" }, 6 | { component: BootstrapIcon, size: 32, label: "04.png" }, 7 | { component: TailwindIcon, size: 32, label: "05.png" }, 8 | { component: NodeIcon, size: 32, label: "06.png" }, 9 | { component: ReactIcon, size: 32, label: "07.png" }, 10 | { component: ExpressIcon, size: 32, label: "08.png" }, 11 | { component: NextIcon, size: 32, label: "09.png" }, 12 | { component: ViteIcon, size: 32, label: "10.png" }, 13 | { component: MySqlIcon, size: 32, label: "11.png" }, 14 | { component: PostgresqlIcon, size: 32, label: "12.png" }, 15 | ]; 16 | 17 | export default function SkillsContent() { 18 | return ( 19 | <> 20 |

hamdanzull@Host:~$ ls skills/*.png

21 | 29 | 30 | ) 31 | } 32 | 33 | function HtmlIcon({ size }) { 34 | return ( 35 | 36 | 37 | 38 | ) 39 | } 40 | 41 | function CssIcon({ size }) { 42 | return ( 43 | 44 | 45 | 46 | ) 47 | } 48 | 49 | function JsIcon({ size }) { 50 | return ( 51 | 52 | 53 | 54 | ) 55 | } 56 | 57 | function TailwindIcon({ size }) { 58 | return ( 59 | 60 | 61 | 62 | ) 63 | } 64 | 65 | function BootstrapIcon({ size }) { 66 | return ( 67 | 68 | 69 | 70 | ) 71 | } 72 | 73 | function NodeIcon({ size }) { 74 | return ( 75 | 76 | 77 | 78 | ) 79 | } 80 | 81 | function ReactIcon({ size }) { 82 | return ( 83 | 84 | 85 | 86 | ) 87 | } 88 | 89 | function ExpressIcon({ size }) { 90 | return ( 91 | 92 | 93 | 94 | ) 95 | } 96 | 97 | function ViteIcon({ size }) { 98 | return ( 99 | 100 | 101 | 102 | ) 103 | } 104 | 105 | function NextIcon({ size }) { 106 | return ( 107 | 108 | 109 | 110 | ) 111 | } 112 | 113 | function MySqlIcon({ size }) { 114 | return ( 115 | 116 | 117 | 118 | ) 119 | } 120 | 121 | function PostgresqlIcon({ size }) { 122 | return ( 123 | 124 | 125 | 126 | ) 127 | } 128 | -------------------------------------------------------------------------------- /src/components/Footer/Copyright.jsx: -------------------------------------------------------------------------------- 1 | export default function Copyright() { 2 | 3 | return ( 4 | <> 5 |

© 2023-2024 hamdanzull - All Rights Reserved.

6 | 7 | ) 8 | } -------------------------------------------------------------------------------- /src/components/Footer/Footer.jsx: -------------------------------------------------------------------------------- 1 | import SocialIcon from './SocialIcon' 2 | import Copyright from './Copyright' 3 | 4 | export default function Footer() { 5 | return ( 6 | 12 | ) 13 | } -------------------------------------------------------------------------------- /src/components/Footer/SocialIcon.jsx: -------------------------------------------------------------------------------- 1 | const socials = [ 2 | { icon: LinkedinIcon, url: 'https://linkedin.com/in/hamdanzull' }, 3 | { icon: GithubIcon, url: 'https://www.instagram.com/hamdanzull' }, 4 | { icon: InstagramIcon, url: 'https://www.instagram.com/hamdanzull' }, 5 | { icon: FacebookIcon, url: 'https://www.facebook.com/hamdanzull' }, 6 | ]; 7 | 8 | export default function SocialIcon() { 9 | return ( 10 |
11 |

Connect with me

12 | 21 |
22 | ) 23 | } 24 | 25 | function LinkedinIcon() { 26 | return ( 27 | 28 | 29 | 30 | ) 31 | } 32 | 33 | function GithubIcon() { 34 | return ( 35 | 36 | 37 | 38 | ) 39 | } 40 | 41 | function InstagramIcon() { 42 | return ( 43 | 44 | 45 | 46 | ) 47 | } 48 | 49 | function FacebookIcon() { 50 | return ( 51 | 52 | 53 | 54 | ) 55 | } -------------------------------------------------------------------------------- /src/components/Header/Header.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import ThemeToggle from './ThemeToggle'; 3 | import NavList from './NavList'; 4 | 5 | export default function Header() { 6 | const [menuToggle, setMenuToggle] = useState(false); 7 | const handleMenu = () => setMenuToggle(!menuToggle); 8 | 9 | return ( 10 |
11 |
12 |
13 | 14 |

15 | hamdanzull 16 |

17 |
18 | 19 | 20 |
21 | 22 | {/* https://www.patrykgulas.com/hamburgers */} 23 | 31 |
32 |
33 |
34 |
35 | ) 36 | } -------------------------------------------------------------------------------- /src/components/Header/NavList.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | import { NavLink } from 'react-router-dom'; 3 | 4 | 5 | const navLinks = [ 6 | { to: '/', text: 'Home' }, 7 | { to: '/about', text: 'About' }, 8 | { to: '/projects', text: 'Projects' }, 9 | { to: '/services', text: 'Services' }, 10 | ]; 11 | 12 | export default function NavList({ menuToggle }) { 13 | return ( 14 | 25 | ) 26 | } -------------------------------------------------------------------------------- /src/components/Header/ThemeToggle.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | /* eslint-disable no-unused-vars */ 3 | import { useEffect, useState } from "react"; 4 | 5 | export default function ThemeToggle() { 6 | const [theme, setTheme] = useState(() => { 7 | return localStorage.getItem('theme') || 'light'; 8 | }); 9 | 10 | const handleSetTheme = () => { 11 | setTheme((prevTheme) => ( 12 | prevTheme === 'light' ? 'dark' : 'light' 13 | )); 14 | }; 15 | 16 | useEffect(() => { 17 | localStorage.setItem('theme', theme); 18 | document.documentElement.className = theme; 19 | }, [theme]); 20 | 21 | return ( 22 | 30 | ) 31 | } -------------------------------------------------------------------------------- /src/components/Hero/Hero.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/no-unescaped-entities */ 2 | /* eslint-disable react/prop-types */ 3 | import profile from '../../assets/profile-photo.png' 4 | 5 | export default function Hero({ aboutToggle, handleAbout }) { 6 | return ( 7 |
8 |
9 |
10 | Profile Photo 13 |
14 |
15 |

Haloo! I'm

16 |

17 | Muh. Hamdan Zulfa 18 | 19 | 20 |

21 |

A beginner enthusiast exploring modern tech,
especially in software development.

22 | 29 |
30 |
31 |
32 | ) 33 | } -------------------------------------------------------------------------------- /src/components/Projects/MyProjects.jsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from 'react-router-dom' 2 | import OpenLink from './OpenLink' 3 | import ProjectDetails from './ProjectDetails' 4 | import ProjectList from './ProjectList' 5 | import Thumbnail from './Thumbnail' 6 | // project image: 7 | import snakeAmazon from '../../assets/snake-amazon.png' 8 | import reactOpenai from '../../assets/react-openai.png' 9 | 10 | const projects = [ 11 | { 12 | id: 1, 13 | title: 'Snake Amazon', 14 | image: snakeAmazon, 15 | stacks: 'Pure HTML, CSS and Vanilla JS', 16 | description: 'A classic arcade-style game where you guide a snake through a grid, eating apples to grow longer while avoiding collisions. Simple and challenging, it revives the timeless joy of classic games.', 17 | link: 'https://wdp-12.github.io/Finalproject1_Kelompok1/', 18 | }, 19 | { 20 | id: 3, 21 | title: 'React OpenAI', 22 | image: reactOpenai, 23 | stacks: 'React & Tailwind CSS', 24 | description: "This project marks my introduction to React as I develop a chatbot application to interact with OpenAI. It represents my first step in mastering React and enhancing my programming skills.", 25 | link: 'https://openai.hamdanzull.my.id' 26 | } 27 | ] 28 | 29 | export default function MyProjects() { 30 | const direct = useNavigate(); 31 | return ( 32 |
33 |
34 |

My Projects

35 | 36 | {projects.map(project => ( 37 | 38 | 42 | 43 | 44 | 45 | 46 | ))} 47 | 48 | 52 |
53 |
54 | ) 55 | } -------------------------------------------------------------------------------- /src/components/Projects/OpenLink.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | export default function OpenLink({ link }) { 3 | return ( 4 | 6 | Demo App 7 | 8 | 9 | ) 10 | } -------------------------------------------------------------------------------- /src/components/Projects/ProjectDetails.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | export default function ProjectDetails({ project, children }) { 3 | return ( 4 |
5 |

{project.title}

6 |

Stacks: {project.stacks}

7 |

{project.description}

8 | {children} 9 |
10 | ) 11 | } -------------------------------------------------------------------------------- /src/components/Projects/ProjectList.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | export default function ProjectList({ children }) { 3 | return ( 4 | <> 5 |
6 | {children} 7 |
8 | 9 | ) 10 | } -------------------------------------------------------------------------------- /src/components/Projects/Thumbnail.jsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/prop-types */ 2 | export default function Thumbnail({ image, title }) { 3 | return ( 4 |
5 | {title} 7 |
8 | ) 9 | } -------------------------------------------------------------------------------- /src/components/Services/Services.jsx: -------------------------------------------------------------------------------- 1 | import ServicesList from "./ServicesList" 2 | 3 | 4 | export default function Services() { 5 | return ( 6 |
7 |
8 |

9 | Services 10 |

11 | 12 |
13 | 14 |
15 |
16 |
17 | ) 18 | } -------------------------------------------------------------------------------- /src/components/Services/ServicesList.jsx: -------------------------------------------------------------------------------- 1 | const services = [ 2 | { icon: InstallationIcon, name: 'Installation', desc: 'Offering hardware and software installation (Operating Systems), applications, games, and network infrastructure.' }, 3 | { icon: WebDevIcon, name: 'Web Development', desc: 'Crafting personalized portfolio websites, institutional sites, online stores (E-commerce), academic projects, and more.' }, 4 | { icon: DesignIcon, name: 'Design', desc: 'Creating website or app designs & prototypes, brand logos, product designs, mockups, advertisements and more.' }, 5 | { icon: AutomationIcon, name: 'Automation', desc: 'Providing Telegram & WhatsApp BOT services to enhance online business services, scraping data, and more.' }, 6 | ] 7 | 8 | export default function ServicesList() { 9 | return ( 10 | services.map((service, idx) => ( 11 |
12 |
13 | 14 |
15 |
16 |

{service.name}

17 |

{service.desc}

18 |
19 |
20 | )) 21 | ) 22 | } 23 | 24 | function InstallationIcon() { 25 | return ( 26 | 27 | 28 | 29 | 30 | ) 31 | } 32 | 33 | function WebDevIcon() { 34 | return ( 35 | 36 | 37 | 38 | ) 39 | } 40 | 41 | function DesignIcon() { 42 | return ( 43 | 44 | 45 | 46 | ) 47 | } 48 | 49 | function AutomationIcon() { 50 | return ( 51 | 52 | 53 | 54 | ) 55 | } -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700;800;900&family=Montserrat:wght@100;200;300;400;500;600;700;800;900&family=Pacifico&family=Ubuntu&display=swap'); 2 | 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.jsx' 4 | import './index.css' 5 | 6 | ReactDOM.createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /src/pages/AboutPage.jsx: -------------------------------------------------------------------------------- 1 | import { text } from "../components/About/AboutContent" 2 | import Card from "../components/About/Card" 3 | import SkillsContent from "../components/About/SkillsContent" 4 | import Header from "../components/Header/Header" 5 | import Footer from "../components/Footer/Footer" 6 | 7 | export default function AboutPage() { 8 | return ( 9 | <> 10 |
11 |
12 |
13 |

About Me

14 | {text.map((paragraph, index) => ( 15 |

16 | {paragraph} 17 |

18 | ))} 19 |
20 | 21 | 22 | 23 |
24 |

Lorem

25 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Voluptatum voluptate maxime amet modi optio tempora dolorem quibusdam earum? Modi error quae culpa dignissimos dicta tenetur maiores dolores minus impedit voluptatibus voluptatum dolor officia, ab sequi? Fugiat impedit voluptatum quis consequatur molestias tempora, provident ad possimus accusamus recusandae assumenda! Tenetur, doloremque!

26 |

Lorem ipsum dolor sit amet consectetur adipisicing elit. Eveniet tempora molestiae praesentium delectus illum sint id et laborum ipsum quasi, ad possimus consequuntur architecto aperiam dignissimos perspiciatis quas facilis ipsam ex nostrum voluptate! Sint itaque molestiae facilis quae placeat fuga distinctio, neque illo labore nesciunt doloremque eaque, sed fugiat illum optio nemo amet maiores! Quae amet pariatur explicabo voluptatem? Quas laborum error at unde in, enim aut earum praesentium dolor, dignissimos quo a architecto eum.

27 |
28 |
29 |