diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 312bdf5..507941a 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -4,7 +4,8 @@ "Bash(find:*)", "Bash(mvn clean:*)", "Bash(mvn test:*)", - "Bash(npm run build:*)" + "Bash(npm run build:*)", + "Bash(npm run lint)" ], "deny": [] } diff --git a/frontend/app/(root)/Home.tsx b/frontend/app/(root)/Home.tsx index 3bb024a..d67d1a4 100644 --- a/frontend/app/(root)/Home.tsx +++ b/frontend/app/(root)/Home.tsx @@ -1,28 +1,18 @@ 'use client'; -import React, {useEffect} from "react"; -import HomeServices from "@/app/(root)/sections/HomeServices"; -import {motion} from "framer-motion"; +import React from "react"; +import { motion } from "framer-motion"; +import { useScrollToSection } from "@/hooks/useScrollToSection"; import Hero from "@/app/(root)/sections/Hero"; +import HomeServices from "@/app/(root)/sections/HomeServices"; import About from "@/app/(root)/sections/About"; import ProcessSection from "@/app/(root)/sections/ProcessSection"; import WhyUs from "@/app/(root)/sections/WhyUs"; -import Faq from "@/app/(root)/sections/Faq"; import ReferralSection from "@/app/(root)/sections/ReferralSection"; +import Faq from "@/app/(root)/sections/Faq"; const Home = () => { - useEffect(() => { - const scrollToId = localStorage.getItem('scrollToId') - if (scrollToId) { - localStorage.removeItem('scrollToId') - const el = document.getElementById(scrollToId) - if (el) { - setTimeout(() => { - el.scrollIntoView({behavior: 'smooth', block: 'start'}) - }, 200) - } - } - }, []) + useScrollToSection(); return ( { return ( @@ -9,23 +10,9 @@ const About = () => { className="relative w-full py-24 bg-background text-foreground transition-colors duration-700 ease-in-out">
- {/* Title */} - - Über uns - - - {/* Text */} diff --git a/frontend/app/(root)/sections/Hero.tsx b/frontend/app/(root)/sections/Hero.tsx index 631673e..87f0de2 100644 --- a/frontend/app/(root)/sections/Hero.tsx +++ b/frontend/app/(root)/sections/Hero.tsx @@ -1,74 +1,16 @@ 'use client'; -import {motion} from 'framer-motion'; -import Image from 'next/image'; -import {Typewriter} from 'react-simple-typewriter'; -import PulsatingButton from "@/components/PulsatingButton"; +import { HeroBackground } from '@/components/Hero/HeroBackground'; +import { HeroContent } from '@/components/Hero/HeroContent'; const Hero = () => { return (
- {/* Background */} -
- Rhein river aerial view -
-
- - {/* Content */} -
- - Digitale Lösungen,
die wirklich passen. -
- - - Wir entwickeln individuelle Softwarelösungen für Unternehmen und Startups. - - - - - - -
- -
- -
+ +
); }; diff --git a/frontend/app/(root)/sections/HomeServices.tsx b/frontend/app/(root)/sections/HomeServices.tsx index 063c2e3..02d056f 100644 --- a/frontend/app/(root)/sections/HomeServices.tsx +++ b/frontend/app/(root)/sections/HomeServices.tsx @@ -1,101 +1,35 @@ 'use client'; -import {motion} from 'framer-motion'; -import {ChevronRight} from 'lucide-react'; +import { motion } from 'framer-motion'; import Link from 'next/link'; - -const services = [ - { - title: 'Webdesign', - description: 'Moderne Websites, die Vertrauen schaffen und verkaufen.', - bullets: [ - 'Maßgeschneidertes Design', - 'Klare Struktur & überzeugende Inhalte', - 'Nutzerführung mit System & Strategie', - 'Für alle Geräte optimiert', - ], - }, - { - title: 'App-Entwicklung', - description: 'Skalierbare Apps für Web und Mobile – von der Idee bis zum Launch.', - bullets: [ - 'Plattformübergreifend mit modernen Technologien', - 'Backend & API-Entwicklung inklusive', - 'Individuelle Funktionen & Logik', - 'Stabil, performant & wartbar', - ], - }, - { - title: 'Interne Tools', - description: 'Digitale Werkzeuge, die Prozesse vereinfachen und Zeit sparen.', - bullets: [ - 'Prozessdigitalisierung & Automatisierung', - 'Zugeschnitten auf eure Workflows', - 'Skalierbar & zukunftssicher', - 'Intuitiv & effizient bedienbar', - ], - }, -]; +import { SectionTitle } from '@/components/ui/SectionTitle'; +import { ServiceCard } from '@/components/ui/ServiceCard'; +import { servicesData } from '@/constant/ServicesData'; const HomeServices = () => { return ( -
+
- - Leistungen - - - +
- {services.map((service, index) => ( - ( + -
-

{service.title}

-

{service.description}

-
    - {service.bullets.map((point, i) => ( -
  • - - {point} -
  • - ))} -
-
-
+ title={service.title} + description={service.description} + bullets={service.bullets} + index={index} + /> ))}

Du möchtest mehr über unsere Leistungen erfahren oder hast ein konkretes Projekt im Kopf? diff --git a/frontend/components/Footer/Footer.tsx b/frontend/components/Footer/Footer.tsx index 27ff14a..c04b650 100644 --- a/frontend/components/Footer/Footer.tsx +++ b/frontend/components/Footer/Footer.tsx @@ -2,13 +2,13 @@ import React from 'react'; import Link from 'next/link'; -import {motion} from 'framer-motion'; -import {Mail, Gavel, ShieldCheck, Cookie} from 'lucide-react'; +import { motion } from 'framer-motion'; +import { Mail, Gavel, ShieldCheck, Cookie } from 'lucide-react'; +import { FooterSection } from './FooterSection'; +import { useCookieSettings } from '@/hooks/useCookieSettings'; const Footer = () => { - const openCookieSettings = () => { - window.dispatchEvent(new Event('show-cookie-banner')); - }; + const { openCookieSettings } = useCookieSettings(); return ( {

- {/* Informationen */} - -

Informationen

-
    -
  • - - - Kontakt - -
  • -
-
+ +
  • + + + Kontakt + +
  • +
    - {/* Rechtliches */} - -

    Rechtliches

    -
      -
    • - - - Datenschutz - -
    • -
    • - - - Impressum - -
    • -
    • - - -
    • -
    -
    + +
  • + + + Datenschutz + +
  • +
  • + + + Impressum + +
  • +
  • + + +
  • +
    { + return ( + +

    {title}

    +
      + {children} +
    +
    + ); +}; \ No newline at end of file diff --git a/frontend/components/Hero/HeroBackground.tsx b/frontend/components/Hero/HeroBackground.tsx new file mode 100644 index 0000000..6d05e27 --- /dev/null +++ b/frontend/components/Hero/HeroBackground.tsx @@ -0,0 +1,23 @@ +'use client'; + +import Image from 'next/image'; + +interface HeroBackgroundProps { + imageSrc: string; + imageAlt: string; +} + +export const HeroBackground = ({ imageSrc, imageAlt }: HeroBackgroundProps) => { + return ( +
    + {imageAlt} +
    +
    + ); +}; \ No newline at end of file diff --git a/frontend/components/Hero/HeroContent.tsx b/frontend/components/Hero/HeroContent.tsx new file mode 100644 index 0000000..f1bb3ea --- /dev/null +++ b/frontend/components/Hero/HeroContent.tsx @@ -0,0 +1,58 @@ +'use client'; + +import { motion } from 'framer-motion'; +import { Typewriter } from 'react-simple-typewriter'; +import PulsatingButton from '@/components/PulsatingButton'; + +const typewriterWords = ['Webdesign', 'App-Entwicklung', 'Interne Tools']; + +export const HeroContent = () => { + return ( +
    + + Digitale Lösungen,
    die wirklich passen. +
    + + + Wir entwickeln individuelle Softwarelösungen für Unternehmen und Startups. + + + + + + +
    + +
    +
    + ); +}; \ No newline at end of file diff --git a/frontend/components/Navbar/DesktopNav.tsx b/frontend/components/Navbar/DesktopNav.tsx new file mode 100644 index 0000000..1b1e559 --- /dev/null +++ b/frontend/components/Navbar/DesktopNav.tsx @@ -0,0 +1,32 @@ +'use client'; + +import Link from 'next/link'; +import { Button } from '@/components/ui/button'; +import { ThemeToggle } from '@/components/theme-toggle'; +import { navLinks } from '@/constant/NavigationData'; + +interface DesktopNavProps { + onNavClick: (id: string) => void; +} + +export const DesktopNav = ({ onNavClick }: DesktopNavProps) => { + return ( + + ); +}; \ No newline at end of file diff --git a/frontend/components/Navbar/MobileNav.tsx b/frontend/components/Navbar/MobileNav.tsx new file mode 100644 index 0000000..4059c0a --- /dev/null +++ b/frontend/components/Navbar/MobileNav.tsx @@ -0,0 +1,43 @@ +'use client'; + +import Link from 'next/link'; +import { Button } from '@/components/ui/button'; +import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'; +import { Menu } from 'lucide-react'; +import { ThemeToggle } from '@/components/theme-toggle'; +import { navLinks } from '@/constant/NavigationData'; + +interface MobileNavProps { + onNavClick: (id: string) => void; +} + +export const MobileNav = ({ onNavClick }: MobileNavProps) => { + return ( +
    + + + + + + +
    + {navLinks.map((link) => ( + + ))} + +
    +
    +
    +
    + ); +}; \ No newline at end of file diff --git a/frontend/components/Navbar/NavLogo.tsx b/frontend/components/Navbar/NavLogo.tsx new file mode 100644 index 0000000..296e5be --- /dev/null +++ b/frontend/components/Navbar/NavLogo.tsx @@ -0,0 +1,16 @@ +'use client'; + +interface NavLogoProps { + onLogoClick: () => void; +} + +export const NavLogo = ({ onLogoClick }: NavLogoProps) => { + return ( + + ); +}; \ No newline at end of file diff --git a/frontend/components/Navbar/Navbar.tsx b/frontend/components/Navbar/Navbar.tsx index fc3a2d3..d9d744a 100644 --- a/frontend/components/Navbar/Navbar.tsx +++ b/frontend/components/Navbar/Navbar.tsx @@ -1,98 +1,21 @@ 'use client'; import React from 'react'; -import Link from 'next/link'; -import {usePathname, useRouter} from 'next/navigation'; -import {Button} from '@/components/ui/button'; -import {Sheet, SheetContent, SheetTrigger} from '@/components/ui/sheet'; -import {Menu} from 'lucide-react'; -import {ThemeToggle} from '@/components/theme-toggle'; - -const navLinks = [ - {id: 'services', label: 'Leistungen'}, - {id: 'about', label: 'Über uns'}, - {id: 'process', label: 'Ablauf'}, - {id: 'whyus', label: 'Warum wir'}, - {id: 'referral', label: 'Empfehlung'}, - {id: 'faq', label: 'FAQ'}, -]; +import { useScrollNavigation } from '@/hooks/useScrollNavigation'; +import { NavLogo } from './NavLogo'; +import { DesktopNav } from './DesktopNav'; +import { MobileNav } from './MobileNav'; const Navbar = () => { - const pathname = usePathname(); - const router = useRouter(); - - const handleNavClick = (id: string) => { - if (typeof window === 'undefined') return - - if (pathname === '/') { - const el = document.getElementById(id) - if (el) { - el.scrollIntoView({behavior: 'smooth', block: 'start'}) - } - } else { - localStorage.setItem('scrollToId', id) - router.push('/') - } - } + const { handleNavClick } = useScrollNavigation(); return (
    -
    +
    - - - {/* Desktop nav */} - - - {/* Mobile nav */} -
    - - - - - - -
    - {navLinks.map((link) => ( - - ))} - -
    -
    -
    -
    + handleNavClick('start')} /> + +
    diff --git a/frontend/components/ui/SectionTitle.tsx b/frontend/components/ui/SectionTitle.tsx new file mode 100644 index 0000000..90b9315 --- /dev/null +++ b/frontend/components/ui/SectionTitle.tsx @@ -0,0 +1,55 @@ +'use client'; + +import { motion } from 'framer-motion'; + +interface SectionTitleProps { + title: string; + subtitle?: string; + className?: string; + showUnderline?: boolean; + underlineColor?: string; +} + +export const SectionTitle = ({ + title, + subtitle, + className = "", + showUnderline = true, + underlineColor = "bg-amber-500" +}: SectionTitleProps) => { + return ( +
    + + {title} + + + {showUnderline && ( + + )} + + {subtitle && ( + + {subtitle} + + )} +
    + ); +}; \ No newline at end of file diff --git a/frontend/components/ui/ServiceCard.tsx b/frontend/components/ui/ServiceCard.tsx new file mode 100644 index 0000000..ea8e8c8 --- /dev/null +++ b/frontend/components/ui/ServiceCard.tsx @@ -0,0 +1,43 @@ +'use client'; + +import { motion } from 'framer-motion'; +import { ChevronRight } from 'lucide-react'; +import { ReactNode } from 'react'; + +interface ServiceCardProps { + title: string; + description: string; + bullets: string[]; + index: number; + children?: ReactNode; +} + +export const ServiceCard = ({ title, description, bullets, index, children }: ServiceCardProps) => { + return ( + +
    +

    {title}

    +

    {description}

    +
      + {bullets.map((point, i) => ( +
    • + + {point} +
    • + ))} +
    + {children &&
    {children}
    } +
    +
    + ); +}; \ No newline at end of file diff --git a/frontend/constant/NavigationData.ts b/frontend/constant/NavigationData.ts new file mode 100644 index 0000000..da7518b --- /dev/null +++ b/frontend/constant/NavigationData.ts @@ -0,0 +1,13 @@ +export interface NavLink { + id: string; + label: string; +} + +export const navLinks: NavLink[] = [ + { id: 'services', label: 'Leistungen' }, + { id: 'about', label: 'Über uns' }, + { id: 'process', label: 'Ablauf' }, + { id: 'whyus', label: 'Warum wir' }, + { id: 'referral', label: 'Empfehlung' }, + { id: 'faq', label: 'FAQ' }, +]; \ No newline at end of file diff --git a/frontend/constant/ServicesData.ts b/frontend/constant/ServicesData.ts new file mode 100644 index 0000000..3524704 --- /dev/null +++ b/frontend/constant/ServicesData.ts @@ -0,0 +1,38 @@ +export interface ServiceData { + title: string; + description: string; + bullets: string[]; +} + +export const servicesData: ServiceData[] = [ + { + title: 'Webdesign', + description: 'Moderne Websites, die Vertrauen schaffen und verkaufen.', + bullets: [ + 'Maßgeschneidertes Design', + 'Klare Struktur & überzeugende Inhalte', + 'Nutzerführung mit System & Strategie', + 'Für alle Geräte optimiert', + ], + }, + { + title: 'App-Entwicklung', + description: 'Skalierbare Apps für Web und Mobile – von der Idee bis zum Launch.', + bullets: [ + 'Plattformübergreifend mit modernen Technologien', + 'Backend & API-Entwicklung inklusive', + 'Individuelle Funktionen & Logik', + 'Stabil, performant & wartbar', + ], + }, + { + title: 'Interne Tools', + description: 'Digitale Werkzeuge, die Prozesse vereinfachen und Zeit sparen.', + bullets: [ + 'Prozessdigitalisierung & Automatisierung', + 'Zugeschnitten auf eure Workflows', + 'Skalierbar & zukunftssicher', + 'Intuitiv & effizient bedienbar', + ], + }, +]; \ No newline at end of file diff --git a/frontend/hooks/useCookieSettings.ts b/frontend/hooks/useCookieSettings.ts new file mode 100644 index 0000000..7d6b2cc --- /dev/null +++ b/frontend/hooks/useCookieSettings.ts @@ -0,0 +1,11 @@ +'use client'; + +import { useCallback } from 'react'; + +export const useCookieSettings = () => { + const openCookieSettings = useCallback(() => { + window.dispatchEvent(new Event('show-cookie-banner')); + }, []); + + return { openCookieSettings }; +}; \ No newline at end of file diff --git a/frontend/hooks/useScrollNavigation.ts b/frontend/hooks/useScrollNavigation.ts new file mode 100644 index 0000000..eb0e4af --- /dev/null +++ b/frontend/hooks/useScrollNavigation.ts @@ -0,0 +1,32 @@ +'use client'; + +import { usePathname, useRouter } from 'next/navigation'; +import { useCallback } from 'react'; + +export const useScrollNavigation = () => { + const pathname = usePathname(); + const router = useRouter(); + + const handleNavClick = useCallback((id: string) => { + if (typeof window === 'undefined') return; + + if (pathname === '/') { + const el = document.getElementById(id); + if (el) { + el.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + } else { + localStorage.setItem('scrollToId', id); + router.push('/'); + } + }, [pathname, router]); + + const scrollToSection = useCallback((id: string) => { + const el = document.getElementById(id); + if (el) { + el.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } + }, []); + + return { handleNavClick, scrollToSection }; +}; \ No newline at end of file diff --git a/frontend/hooks/useScrollToSection.ts b/frontend/hooks/useScrollToSection.ts new file mode 100644 index 0000000..67fe5ec --- /dev/null +++ b/frontend/hooks/useScrollToSection.ts @@ -0,0 +1,18 @@ +'use client'; + +import { useEffect } from 'react'; + +export const useScrollToSection = () => { + useEffect(() => { + const scrollToId = localStorage.getItem('scrollToId'); + if (scrollToId) { + localStorage.removeItem('scrollToId'); + const el = document.getElementById(scrollToId); + if (el) { + setTimeout(() => { + el.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }, 200); + } + } + }, []); +}; \ No newline at end of file