diff --git a/package-lock.json b/package-lock.json index 38d9679..14a66ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,10 @@ "version": "0.1.0", "dependencies": { "cookies-next": "^4.3.0", + "framer-motion": "^12.42.0", + "lucide-react": "^1.21.0", "next": "16.2.9", + "next-themes": "^0.4.6", "nextjs-toploader": "^3.7.15", "react": "19.2.7", "react-dom": "19.2.7", @@ -37,6 +40,10 @@ "typescript": "^5", "vitest": "^4.1.9", "webpack-bundle-analyzer": "^4.10.2" + }, + "optionalDependencies": { + "@next/swc-linux-x64-gnu": "16.2.9", + "@next/swc-linux-x64-musl": "16.2.9" } }, "node_modules/@adobe/css-tools": { @@ -5260,6 +5267,33 @@ "node": ">= 6" } }, + "node_modules/framer-motion": { + "version": "12.42.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.42.0.tgz", + "integrity": "sha512-wp7EJnfWaaEScVygKv3e20udoRz+LbtxScsuTkakAxfXmt+ReC6WyPW2nINRAGvd+hG9odwcjBLyOTPjH5pBRA==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.42.0", + "motion-utils": "^12.39.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", @@ -7345,6 +7379,15 @@ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "license": "ISC" }, + "node_modules/lucide-react": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.21.0.tgz", + "integrity": "sha512-reEZMXq8Qdd5jg5XYkQ5TR1fB/GiQ7ih4vcrthYDtgjSDwh0i6/YLiGjsWsIwgN49gpAnd4J2elSNzncMEEUUQ==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/lz-string": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", @@ -7500,6 +7543,21 @@ "node": ">=16 || 14 >=14.17" } }, + "node_modules/motion-dom": { + "version": "12.42.0", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.42.0.tgz", + "integrity": "sha512-M63h4n8R+quJdNhBwuLlgxM+OLYa9+I/T2pzDRboB9fLXRdbou+Gw7Zury+SkpaCyACP1JHSjHgZ1EgTkBr30w==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.39.0" + } + }, + "node_modules/motion-utils": { + "version": "12.39.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.39.0.tgz", + "integrity": "sha512-8nadJAJjTtqRkmRF36FoJTrywK9nnFmnPwnSMyxaOCU7GDjN9RTMJIxx9De8ErM+vpPhMccr/6fo5WciyQLnMQ==", + "license": "MIT" + }, "node_modules/mrmime": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz", @@ -7606,6 +7664,16 @@ } } }, + "node_modules/next-themes": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/next-themes/-/next-themes-0.4.6.tgz", + "integrity": "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" + } + }, "node_modules/next/node_modules/@img/sharp-darwin-arm64": { "version": "0.34.5", "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", diff --git a/package.json b/package.json index 7749911..5d70c64 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,10 @@ }, "dependencies": { "cookies-next": "^4.3.0", + "framer-motion": "^12.42.0", + "lucide-react": "^1.21.0", "next": "16.2.9", + "next-themes": "^0.4.6", "nextjs-toploader": "^3.7.15", "react": "19.2.7", "react-dom": "19.2.7", @@ -25,23 +28,23 @@ }, "devDependencies": { "@next/bundle-analyzer": "16.2.9", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", "@types/negotiator": "^0.6.3", "@types/node": "^20", "@types/react": "19.2.17", "@types/react-dom": "19.2.3", + "@vitejs/plugin-react": "^6.0.3", "cross-env": "^7.0.3", "cypress": "^15.17.0", "eslint": "^9", - "jsdom": "^29.1.1", - "@testing-library/jest-dom": "^6.9.1", - "@testing-library/react": "^16.3.2", - "@vitejs/plugin-react": "^6.0.3", - "vitest": "^4.1.9", "eslint-config-next": "16.2.9", + "jsdom": "^29.1.1", "postcss": "^8", "prettier": "^3.3.3", "prettier-plugin-tailwindcss": "^0.6.6", "typescript": "^5", + "vitest": "^4.1.9", "webpack-bundle-analyzer": "^4.10.2" }, "optionalDependencies": { diff --git a/src/app/(routes)/(website)/(home)/page.tsx b/src/app/(routes)/(website)/(home)/page.tsx index 1618269..80c472e 100644 --- a/src/app/(routes)/(website)/(home)/page.tsx +++ b/src/app/(routes)/(website)/(home)/page.tsx @@ -1,9 +1,9 @@ -import Image from "next/image"; import Testimonials from "../components/testimonials/testimonials"; import Contact from "../components/contact/contact"; import AboutMe from "../components/aboutMe/aboutMe"; import Services from "../components/services/services"; import MyProjects from "../components/projects/projects"; +import Hero from "../components/hero/hero"; import { getDictionary } from "@/app/lib/functions/dictionaries"; import { getDefaultLocale } from "@/app/lib/functions/utils"; import { Metadata } from "next"; @@ -23,79 +23,16 @@ export const metadata: Metadata = { export default async function Home() { const dict = await getDictionary(await getDefaultLocale()); - const socialLinks = [ - { href: "#", src: "/images/icons/facebook.svg", alt: "facebook" }, - { href: "#", src: "/images/icons/twitter.svg", alt: "twitter" }, - { href: "#", src: "/images/icons/instagram.svg", alt: "instagram" }, - { href: "#", src: "/images/icons/linkedin.svg", alt: "linkedin" }, - ]; return ( -
- {/* Home */} -
- {/* start */} -
-
- Profile picture -
- {/* socials */} -
-
- {socialLinks.map(({ href, src, alt }) => ( - - {alt} - - ))} -
-
-
- {/* end */} -
-

- {dict.website.homePage.top.greeting} -

-

- {dict.website.homePage.top.name} -

-

- {dict.website.homePage.top.profession} -

-

- {dict.website.homePage.top.roll} -

-

- {dict.website.homePage.top.descriptionAboutMe} -

- {/* */} -
+
+
+ + + + + +
- - - - - - - - - -
); } diff --git a/src/app/(routes)/(website)/components/aboutMe/AboutMeContent.tsx b/src/app/(routes)/(website)/components/aboutMe/AboutMeContent.tsx new file mode 100644 index 0000000..71a72c9 --- /dev/null +++ b/src/app/(routes)/(website)/components/aboutMe/AboutMeContent.tsx @@ -0,0 +1,113 @@ +"use client"; + +import React from "react"; +import Image from "next/image"; +import { motion } from "framer-motion"; +import { AboutMeDataType } from "@/app/types/types"; + +interface AboutMeContentProps { + data: AboutMeDataType; +} + +const CATEGORIES = { + "Core Tech": ["React", "Next.js", "TypeScript", "JavaScript"], + "Backend & Tools": ["Laravel", "PHP", "MySQL", "Node.js", "Docker", "Git"], + "Architecture & Others": ["REST API", "TDD", "UI/UX Design", "Clean Architecture"], +}; + +export default function AboutMeContent({ data }: AboutMeContentProps) { + const categorizedSkills = { + "Core Tech": [], + "Backend & Tools": [], + "Architecture & Others": [], + "Other": [], + }; + + data.skills.forEach((skill) => { + let matched = false; + for (const [cat, skills] of Object.entries(CATEGORIES)) { + if (skills.some((s) => skill.title.toLowerCase().includes(s.toLowerCase()))) { + categorizedSkills[cat].push(skill.title); + matched = true; + break; + } + } + if (!matched) categorizedSkills["Other"].push(skill.title); + }); + + const isRTL = typeof document !== "undefined" && document.dir === "rtl"; + + return ( +
+ {/* Text Section */} + +

+ {data.title} +

+

+ {data.description} +

+ +
+ {Object.entries(categorizedSkills).map(([category, skills]) => ( + skills.length > 0 && ( + +

+ {category} +

+
+ {skills.map((skill, idx) => ( + + {skill} + + ))} +
+
+ ) + ))} +
+
+ + {/* Image Section */} + +
+
+
+ profile picture +
+
+
+
+ ); +} diff --git a/src/app/(routes)/(website)/components/aboutMe/aboutMe.tsx b/src/app/(routes)/(website)/components/aboutMe/aboutMe.tsx index 48d0f6d..cdfa056 100644 --- a/src/app/(routes)/(website)/components/aboutMe/aboutMe.tsx +++ b/src/app/(routes)/(website)/components/aboutMe/aboutMe.tsx @@ -3,47 +3,26 @@ import { cookies } from "next/headers"; import React from "react"; import Skeleton from "react-loading-skeleton"; import "react-loading-skeleton/dist/skeleton.css"; +import AboutMeContent from "./AboutMeContent"; export default async function AboutMe() { const aboutMeData: AboutMeDataType = await fetch( `${process.env.NEXT_PUBLIC_API_URL}/website/about-me/1/translation/${(await cookies()).get("selectedLocale")?.value ? (await cookies()).get("selectedLocale")?.value : (await cookies()).get("defaultLocale")?.value}`, ).then((res) => res.json()); - return ( -
-
-

- {aboutMeData?.title || } -

-

- {aboutMeData?.description || } -

-
    - {aboutMeData.skills.map((skill, index) => ( -
  • -

    {skill.title}

    -
    - -
    -
  • - ))} -
+ if (!aboutMeData) { + return ( +
+
+ + +
+
+ +
-
- profile picture -
-
- ); + ); + } + + return ; } diff --git a/src/app/(routes)/(website)/components/contact/contact.tsx b/src/app/(routes)/(website)/components/contact/contact.tsx index 6069056..e79d795 100644 --- a/src/app/(routes)/(website)/components/contact/contact.tsx +++ b/src/app/(routes)/(website)/components/contact/contact.tsx @@ -1,4 +1,8 @@ -import React from "react"; +"use client"; + +import React, { useState } from "react"; +import { motion } from "framer-motion"; +import { Send } from "lucide-react"; interface ContactProps { dict: { @@ -16,32 +20,85 @@ interface ContactProps { } export default function Contact({ dict }: ContactProps) { + const [isSubmitting, setIsSubmitting] = useState(false); + const [submitted, setSubmitted] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setIsSubmitting(true); + // Simulate API call + await new Promise((resolve) => setTimeout(resolve, 2000)); + setIsSubmitting(false); + setSubmitted(true); + setTimeout(() => setSubmitted(false), 5000); + }; + return ( -
-

- {dict.website.homePage.contact.slogan} -

-

- {dict.website.homePage.contact.description} -

-
- - + {dict.website.homePage.contact.slogan} + + + {dict.website.homePage.contact.description} +
-
+ + +
+
+ +
+ + {isSubmitting ? ( +
+ ) : submitted ? ( + + Sent! + + ) : ( + <> + {dict.website.homePage.contact.button} + + + )} + + + + ); } diff --git a/src/app/(routes)/(website)/components/footer/footer.tsx b/src/app/(routes)/(website)/components/footer/footer.tsx index a6d8bed..9c896a5 100644 --- a/src/app/(routes)/(website)/components/footer/footer.tsx +++ b/src/app/(routes)/(website)/components/footer/footer.tsx @@ -6,104 +6,76 @@ import Link from "next/link"; export default async function Footer() { const dict = await getDictionary(await getDefaultLocale()); + + const footerLinks = [ + { href: "/#home", label: dict.website.footer.Home }, + { href: "/#about-me", label: dict.website.footer.AboutMe }, + { href: "/#services", label: dict.website.footer.Services }, + { href: "/#projects", label: dict.website.footer.Projects }, + { href: "/#testimonials", label: dict.website.footer.Testimonials }, + { href: "/#contact", label: dict.website.footer.Contact }, + { href: "/blog", label: dict.website.footer.Blog }, + ]; + + const socialLinks = [ + { src: "/images/icons/facebook.svg", alt: "facebook" }, + { src: "/images/icons/twitter.svg", alt: "twitter" }, + { src: "/images/icons/instagram.svg", alt: "instagram" }, + { src: "/images/icons/linkedin.svg", alt: "linkedin" }, + ]; + return ( -
-
-
- logo -

+

); } diff --git a/src/app/(routes)/(website)/components/header/header.tsx b/src/app/(routes)/(website)/components/header/header.tsx index f5df8d7..ffe9cee 100644 --- a/src/app/(routes)/(website)/components/header/header.tsx +++ b/src/app/(routes)/(website)/components/header/header.tsx @@ -2,85 +2,74 @@ import React from "react"; import BurgerButton from "../burger-button/burgerButton"; import Link from "next/link"; import SelectLanguage from "../selectLanguage/selectLanguage"; +import { ThemeToggle } from "../theme-toggle/theme-toggle"; import { cookies } from "next/headers"; import { getDictionary } from "@/app/lib/functions/dictionaries"; import { getDefaultLocale } from "@/app/lib/functions/utils"; +import { motion } from "framer-motion"; export default async function Header() { const dict = await getDictionary(await getDefaultLocale()); + const navLinks = [ + { href: "/#home", label: dict.website.header.Home }, + { href: "/#about-me", label: dict.website.header.AboutMe }, + { href: "/#services", label: dict.website.header.Services }, + { href: "/#projects", label: dict.website.header.Projects }, + { href: "/#testimonials", label: dict.website.header.Testimonials }, + { href: "/#contact", label: dict.website.header.Contact }, + { href: "/blog", label: dict.website.header.Blog }, + ]; + return ( -