From 7dc79b3151fb02118627291c338fff8f63fdc702 Mon Sep 17 00:00:00 2001 From: parsa aghaei Date: Mon, 29 Jun 2026 09:32:40 +0330 Subject: [PATCH] fix: sticky header blur on scroll, testimonials carousel 6 items + RTL arrows, project modal dual-column, smooth scroll, hero btn no-wrap --- .../(website)/components/header/header.tsx | 65 +++++---- .../(website)/components/hero/hero.tsx | 2 +- .../components/projects/projects.tsx | 128 ++++++++++-------- .../selectLanguage/selectLanguage.tsx | 43 +++--- .../components/testimonials/testimonials.tsx | 37 +++-- src/app/globals.css | 18 +++ src/app/lib/functions/dictionaries.ts | 1 - 7 files changed, 179 insertions(+), 115 deletions(-) diff --git a/src/app/(routes)/(website)/components/header/header.tsx b/src/app/(routes)/(website)/components/header/header.tsx index aa1884e..76739a9 100644 --- a/src/app/(routes)/(website)/components/header/header.tsx +++ b/src/app/(routes)/(website)/components/header/header.tsx @@ -1,29 +1,51 @@ -import React from "react"; +"use client"; + +import React, { useEffect, useState } 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 { getCookie } from "cookies-next"; 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()); +export default function Header() { + const [dict, setDict] = useState(null); + const [scrolled, setScrolled] = useState(false); - 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 }, - ]; + useEffect(() => { + const loadDict = async () => { + const selectedLocale = getCookie("selectedLocale"); + const defaultLocale = getCookie("defaultLocale"); + const d = await getDictionary((selectedLocale || defaultLocale || "en") as any); + setDict(d); + }; + loadDict(); + + const handleScroll = () => setScrolled(window.scrollY > 50); + window.addEventListener("scroll", handleScroll, { passive: true }); + handleScroll(); + return () => window.removeEventListener("scroll", handleScroll); + }, []); + + const navLinks = dict + ? [ + { 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 ( ); diff --git a/src/app/(routes)/(website)/components/hero/hero.tsx b/src/app/(routes)/(website)/components/hero/hero.tsx index ab2dd44..f4437b5 100644 --- a/src/app/(routes)/(website)/components/hero/hero.tsx +++ b/src/app/(routes)/(website)/components/hero/hero.tsx @@ -128,7 +128,7 @@ export default function Hero({ dict }: HeroProps) { > {dict.website.homePage.top.cta}
diff --git a/src/app/(routes)/(website)/components/projects/projects.tsx b/src/app/(routes)/(website)/components/projects/projects.tsx index af124c4..7d631e9 100644 --- a/src/app/(routes)/(website)/components/projects/projects.tsx +++ b/src/app/(routes)/(website)/components/projects/projects.tsx @@ -36,7 +36,8 @@ export default function Projects({ dict }: MyProjectsProps) { const [selectedCategory, setSelectedCategory] = useState(""); const [loading, setLoading] = useState(true); const [selectedProject, setSelectedProject] = useState(null); - const locale = getCookie("selectedLocale") || getCookie("defaultLocale"); + const locale = (getCookie("selectedLocale") || getCookie("defaultLocale") || "en") as string; + const isRTL = locale === "fa"; useEffect(() => { const initialCategory = locale === "fa" ? "جدیدترین‌ها" : "Newest"; @@ -214,7 +215,7 @@ export default function Projects({ dict }: MyProjectsProps) { {project.category} - {locale === "fa" ? " مشاهده جزییات" : "View Details"} → + {isRTL ? "مشاهده جزییات" : "View Details"} → @@ -239,10 +240,10 @@ export default function Projects({ dict }: MyProjectsProps) {

- {locale === "fa" ? "پروژه‌ای یافت نشد" : "No projects found"} + {isRTL ? "پروژه‌ای یافت نشد" : "No projects found"}

- {locale === "fa" ? "در حال حاضر پروژه‌ای در این دسته‌بندی وجود ندارد. لطفاً دسته‌بندی دیگری را انتخاب کنید." : "No projects in this category yet. Please try another category."} + {isRTL ? "در حال حاضر پروژه‌ای در این دسته‌بندی وجود ندارد. لطفاً دسته‌بندی دیگری را انتخاب کنید." : "No projects in this category yet. Please try another category."}

)} @@ -266,7 +267,7 @@ export default function Projects({ dict }: MyProjectsProps) { exit={{ opacity: 0, scale: 0.9, y: 20 }} transition={{ duration: 0.3 }} onClick={(e) => e.stopPropagation()} - className="relative w-full max-w-3xl max-h-[90vh] overflow-y-auto rounded-3xl bg-background border border-border shadow-2xl" + className="relative w-full max-w-5xl max-h-[90vh] rounded-3xl bg-background border border-border shadow-2xl overflow-hidden" > -
- {selectedProject.images.map((image, idx) => ( - {`Image - ))} -
+
+ {/* Left: Image */} +
+ {selectedProject.images.map((image, idx) => ( + {`Image + ))} +
+
-
- - {selectedProject.category} - -

- {selectedProject.title} -

-

- {locale === "fa" - ? "توضیحات کامل پروژه در این بخش نمایش داده می‌شود." - : "Full project description will be displayed here."} -

-
+ {/* Right: Details */} +
+
+ + {selectedProject.category} + +

+ {selectedProject.title} +

-
- - - {currentModalIndex + 1} / {filteredProjects.length} - - +
+ + {selectedProject.category} + + + {isRTL ? "طراحی وب" : "Web Design"} + + + {isRTL ? "توسعه" : "Development"} + +
+ +

+ {isRTL + ? "این پروژه با استفاده از آخرین تکنولوژی‌های وب توسعه یافته است. تیم ما با دقت و توجه به جزئیات، راهکاری scalable و maintainable ارائه داده است که نیازهای کسب و کار را به بهترین شکل برآورده می‌کند. از طراحی رابط کاربری گرفته تا بهینه‌سازی performance، تمامی جنبه‌های این پروژه با بالاترین استانداردها پیاده‌سازی شده است." + : "This project was built using the latest web technologies. Our team delivered a scalable and maintainable solution with meticulous attention to detail, meeting business requirements effectively. From UI/UX design to performance optimization, every aspect was implemented to the highest standards."} +

+
+ + {/* Navigation */} +
+ + + {currentModalIndex + 1} / {filteredProjects.length} + + +
+
diff --git a/src/app/(routes)/(website)/components/selectLanguage/selectLanguage.tsx b/src/app/(routes)/(website)/components/selectLanguage/selectLanguage.tsx index 637c247..c63a878 100644 --- a/src/app/(routes)/(website)/components/selectLanguage/selectLanguage.tsx +++ b/src/app/(routes)/(website)/components/selectLanguage/selectLanguage.tsx @@ -1,40 +1,29 @@ "use client"; -import { setCookie } from "cookies-next"; -import { LocaleCookieType } from "@/app/types/types"; +import { getCookie, setCookie } from "cookies-next"; import { useRouter } from "next/navigation"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import Link from "next/link"; -interface SelectLanguageProps { - selectedLocale: LocaleCookieType | undefined; - defaultLocale: LocaleCookieType | undefined; -} - -export default function SelectLanguage({ - selectedLocale, - defaultLocale, -}: SelectLanguageProps) { +export default function SelectLanguage() { const router = useRouter(); const [isOpen, setisOpen] = useState(false); + const [currentLocale, setCurrentLocale] = useState(""); + + useEffect(() => { + const sl = getCookie("selectedLocale"); + const dl = getCookie("defaultLocale"); + setCurrentLocale(sl || dl || "en"); + }, []); return (
- {selectedLocale ? ( -

setisOpen(!isOpen)} - className="mx-2 mb-[5px] cursor-pointer select-none whitespace-nowrap border-b-2 border-[#FD6F00] px-2 py-[10px] pb-[5px] text-[15px] font-[600] text-[#FD6F00] hover:text-[#FD6F00] xl:text-[20px]" - > - {selectedLocale?.value} -

- ) : ( -

setisOpen(!isOpen)} - className="mx-2 mb-[5px] cursor-pointer select-none whitespace-nowrap px-2 py-[10px] pb-[5px] text-[15px] font-[600] hover:text-[#FD6F00] xl:text-[20px]" - > - {defaultLocale?.value} -

- )} +

setisOpen(!isOpen)} + className="mx-2 mb-[5px] cursor-pointer select-none whitespace-nowrap border-b-2 border-[#FD6F00] px-2 py-[10px] pb-[5px] text-[15px] font-[600] text-[#FD6F00] hover:text-[#FD6F00] xl:text-[20px]" + > + {currentLocale} +

    (null); + const [locale, setLocale] = useState("en"); + + useEffect(() => { + const sl = getCookie("selectedLocale"); + const dl = getCookie("defaultLocale"); + setLocale(sl || dl || "en"); + }, []); + + const isRTL = locale === "fa"; const maxIndex = Math.max(0, testimonials.length - 3); const scrollTo = (index: number) => { @@ -114,14 +131,14 @@ export default function Testimonials({ dict }: TestimonialsProps) { transition={{ duration: 0.5, delay: index * 0.1 }} viewport={{ once: true }} whileHover={{ y: -10 }} - className="relative p-8 rounded-3xl glass-panel flex flex-col h-full" + className="relative p-6 md:p-8 rounded-3xl glass-panel flex flex-col h-full" >
    -
    -

    +

    +

    "{testimonial.quote}"

    @@ -155,20 +172,20 @@ export default function Testimonials({ dict }: TestimonialsProps) { {testimonials.length > 3 && ( <>
    diff --git a/src/app/globals.css b/src/app/globals.css index 4cac22f..b9bfc7f 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -2,6 +2,10 @@ @tailwind components; @tailwind utilities; +html { + scroll-behavior: smooth; +} + @layer base { :root { --background: 240 10% 98%; @@ -82,6 +86,20 @@ scrollbar-width: none; } +.custom-scrollbar::-webkit-scrollbar { + width: 6px; +} +.custom-scrollbar::-webkit-scrollbar-track { + background: transparent; +} +.custom-scrollbar::-webkit-scrollbar-thumb { + background: hsl(var(--muted-foreground) / 0.3); + border-radius: 999px; +} +.custom-scrollbar::-webkit-scrollbar-thumb:hover { + background: hsl(var(--muted-foreground) / 0.5); +} + .mesh-gradient { background-image: radial-gradient(at 0% 0%, hsla(30, 80%, 50%, 0.15) 0px, transparent 50%), diff --git a/src/app/lib/functions/dictionaries.ts b/src/app/lib/functions/dictionaries.ts index 176da17..5c87d6d 100644 --- a/src/app/lib/functions/dictionaries.ts +++ b/src/app/lib/functions/dictionaries.ts @@ -1,5 +1,4 @@ import { Dictionary } from "@/app/types/types"; -import "server-only"; const dictionaries: { [key: string]: () => Promise } = { en: () => import("../dictionaries/en.json").then((module) => module.default),