Database eingefügt

This commit is contained in:
knuthtimo-lab 2025-09-02 11:53:33 +02:00
parent 1307a969d7
commit d71aaebe2a
29 changed files with 3207 additions and 283 deletions

View File

@ -0,0 +1,9 @@
{
"permissions": {
"allow": [
"Bash(npm run dev:*)"
],
"deny": [],
"ask": []
}
}

2
.gitignore vendored
View File

@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
.env

87
CLAUDE.md Normal file
View File

@ -0,0 +1,87 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Development Commands
- `npm run dev` - Start development server (Vite, runs on port 8080)
- `npm run build` - Build for production
- `npm run build:dev` - Build in development mode
- `npm run lint` - Run ESLint to check code quality
- `npm run preview` - Preview production build locally
## Project Architecture
This is a React + TypeScript application built with Vite, targeting the German renewable energy market. The app connects energy customers with solar and wind installation professionals.
### Core Technologies
- **Vite** - Build tool and dev server
- **React 18** - UI framework
- **TypeScript** - Type safety
- **React Router** - Client-side routing
- **TanStack Query** - Server state management
- **Supabase** - Backend database and authentication
- **shadcn/ui** - Component library built on Radix UI
- **Tailwind CSS** - Styling with custom energy-themed colors
### Application Structure
**Pages** (`src/pages/`):
- `Index.tsx` - Landing page with hero section and energy type overview
- `Solar.tsx` / `Wind.tsx` - Energy-specific information pages
- `InstallateurFinden.tsx` - Installer search/listing page
- `KostenloseBeratung.tsx` - Free consultation request page
- `UnternehmenListen.tsx` - Business listing page
**Components** (`src/components/`):
- Standard layout components: `Header`, `Footer`, `HeroSection`
- Feature components: `EnergyTypesSection`, `WhyChooseUsSection`, `EnergyTypeCard`
- Complete shadcn/ui component library in `ui/` subdirectory
**Database Integration** (`src/integrations/supabase/`):
- `client.ts` - Supabase client configuration
- `types.ts` - Auto-generated TypeScript types from Supabase schema
### Database Schema (Supabase)
Key tables:
- `installers` - Installation companies with location, certifications, ratings
- `quotes` - Customer quote requests with project details
- `installer_quotes` - Installer responses to quote requests
- `reviews` - Customer reviews for installers
- `contact_clicks` - Analytics for installer contact interactions
- `analytics_events` - General application usage analytics
Energy types: `solar` | `wind`
Quote status: `pending` | `accepted` | `rejected` | `expired`
### Styling System
Custom Tailwind configuration with energy-themed colors:
- `solar` colors - Orange/yellow theme for solar energy
- `wind` colors - Blue/teal theme for wind energy
- CSS custom properties defined in `src/index.css`
- Gradient backgrounds and shadows for visual branding
### Path Aliases
- `@/*` maps to `src/*` for clean imports
### TypeScript Configuration
- Relaxed strictness settings for rapid development
- Path mapping configured for `@/` alias
- Composite project structure with separate app and node configs
### Routing Architecture
All routes are defined in `src/App.tsx`:
- `/` - Homepage
- `/solar`, `/wind` - Energy type pages
- `/installateur-finden` - Installer search
- `/kostenlose-beratung` - Consultation requests
- `/unternehmen-listen` - Business listings
- `*` - 404 catch-all
### Component Patterns
- Functional components with hooks
- shadcn/ui components for consistent design
- Lucide React icons throughout the application
- German language content and URLs

118
package-lock.json generated
View File

@ -83,7 +83,6 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@ -847,7 +846,6 @@
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
@ -865,7 +863,6 @@
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/set-array": "^1.2.1",
@ -880,7 +877,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@ -890,7 +886,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@ -900,14 +895,12 @@
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@ -918,7 +911,6 @@
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
@ -932,7 +924,6 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
@ -942,7 +933,6 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
@ -956,7 +946,6 @@
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
@ -3008,14 +2997,14 @@
"version": "15.7.13",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
"dev": true,
"devOptional": true,
"license": "MIT"
},
"node_modules/@types/react": {
"version": "18.3.23",
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
"integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"@types/prop-types": "*",
@ -3026,7 +3015,7 @@
"version": "18.3.7",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
"dev": true,
"devOptional": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^18.0.0"
@ -3357,7 +3346,6 @@
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@ -3370,7 +3358,6 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
@ -3386,14 +3373,12 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
"dev": true,
"license": "MIT"
},
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"license": "ISC",
"dependencies": {
"normalize-path": "^3.0.0",
@ -3407,7 +3392,6 @@
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"dev": true,
"license": "MIT"
},
"node_modules/argparse": {
@ -3471,14 +3455,12 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -3502,7 +3484,6 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
@ -3558,7 +3539,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@ -3606,7 +3586,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
@ -3631,7 +3610,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
@ -3680,7 +3658,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
@ -3693,14 +3670,12 @@
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT"
},
"node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@ -3717,7 +3692,6 @@
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@ -3731,7 +3705,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
"dev": true,
"license": "MIT",
"bin": {
"cssesc": "bin/cssesc"
@ -3918,14 +3891,12 @@
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/dlv": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
"dev": true,
"license": "MIT"
},
"node_modules/dom-helpers": {
@ -3942,7 +3913,6 @@
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true,
"license": "MIT"
},
"node_modules/electron-to-chromium": {
@ -3984,7 +3954,6 @@
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true,
"license": "MIT"
},
"node_modules/esbuild": {
@ -4263,7 +4232,6 @@
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
@ -4280,7 +4248,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
@ -4307,7 +4274,6 @@
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
"integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
"dev": true,
"license": "ISC",
"dependencies": {
"reusify": "^1.0.4"
@ -4330,7 +4296,6 @@
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
@ -4381,7 +4346,6 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
"dev": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.0",
@ -4412,7 +4376,6 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@ -4427,7 +4390,6 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@ -4446,7 +4408,6 @@
"version": "10.4.5",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
"dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
@ -4467,7 +4428,6 @@
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.3"
@ -4480,7 +4440,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
@ -4490,7 +4449,6 @@
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
@ -4536,7 +4494,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@ -4605,7 +4562,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"license": "MIT",
"dependencies": {
"binary-extensions": "^2.0.0"
@ -4618,7 +4574,6 @@
"version": "2.15.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"hasown": "^2.0.2"
@ -4634,7 +4589,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@ -4644,7 +4598,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -4654,7 +4607,6 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
@ -4667,7 +4619,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
@ -4677,14 +4628,12 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC"
},
"node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
@ -4700,7 +4649,6 @@
"version": "1.21.6",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
"dev": true,
"license": "MIT",
"bin": {
"jiti": "bin/jiti.js"
@ -4774,7 +4722,6 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14"
@ -4787,7 +4734,6 @@
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"dev": true,
"license": "MIT"
},
"node_modules/locate-path": {
@ -5297,7 +5243,6 @@
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
"license": "ISC"
},
"node_modules/lucide-react": {
@ -5322,7 +5267,6 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 8"
@ -5332,7 +5276,6 @@
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true,
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
@ -5359,7 +5302,6 @@
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=16 || 14 >=14.17"
@ -5376,7 +5318,6 @@
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"any-promise": "^1.0.0",
@ -5388,7 +5329,6 @@
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true,
"funding": [
{
"type": "github",
@ -5431,7 +5371,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@ -5460,7 +5399,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@ -5520,7 +5458,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"dev": true,
"license": "BlueOak-1.0.0"
},
"node_modules/parent-module": {
@ -5550,7 +5487,6 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -5560,14 +5496,12 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true,
"license": "MIT"
},
"node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
@ -5584,14 +5518,12 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true,
"license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
@ -5604,7 +5536,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@ -5614,7 +5545,6 @@
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 6"
@ -5624,7 +5554,6 @@
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"dev": true,
"funding": [
{
"type": "opencollective",
@ -5653,7 +5582,6 @@
"version": "15.1.0",
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
"dev": true,
"license": "MIT",
"dependencies": {
"postcss-value-parser": "^4.0.0",
@ -5671,7 +5599,6 @@
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
"dev": true,
"license": "MIT",
"dependencies": {
"camelcase-css": "^2.0.1"
@ -5691,7 +5618,6 @@
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
@ -5727,7 +5653,6 @@
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
"dev": true,
"funding": [
{
"type": "opencollective",
@ -5753,7 +5678,6 @@
"version": "6.1.2",
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
"dev": true,
"license": "MIT",
"dependencies": {
"cssesc": "^3.0.0",
@ -5767,7 +5691,6 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"dev": true,
"license": "MIT"
},
"node_modules/prelude-ls": {
@ -5811,7 +5734,6 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
"dev": true,
"funding": [
{
"type": "github",
@ -6035,7 +5957,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
"dev": true,
"license": "MIT",
"dependencies": {
"pify": "^2.3.0"
@ -6045,7 +5966,6 @@
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
@ -6090,7 +6010,6 @@
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-core-module": "^2.13.0",
@ -6118,7 +6037,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
"dev": true,
"license": "MIT",
"engines": {
"iojs": ">=1.0.0",
@ -6165,7 +6083,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
"dev": true,
"funding": [
{
"type": "github",
@ -6211,7 +6128,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
@ -6224,7 +6140,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -6234,7 +6149,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
@ -6257,7 +6171,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
@ -6267,7 +6180,6 @@
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
@ -6286,7 +6198,6 @@
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
@ -6301,7 +6212,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -6311,14 +6221,12 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
@ -6331,7 +6239,6 @@
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.0.1"
@ -6348,7 +6255,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
@ -6361,7 +6267,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -6384,7 +6289,6 @@
"version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.2",
@ -6420,7 +6324,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@ -6443,7 +6346,6 @@
"version": "3.4.17",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
"dev": true,
"license": "MIT",
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
@ -6490,7 +6392,6 @@
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
"dev": true,
"license": "MIT",
"dependencies": {
"any-promise": "^1.0.0"
@ -6500,7 +6401,6 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
"dev": true,
"license": "MIT",
"dependencies": {
"thenify": ">= 3.1.0 < 4"
@ -6519,7 +6419,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
@ -6551,7 +6450,6 @@
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"dev": true,
"license": "Apache-2.0"
},
"node_modules/tslib": {
@ -6714,7 +6612,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"dev": true,
"license": "MIT"
},
"node_modules/vaul": {
@ -6832,7 +6729,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
@ -6858,7 +6754,6 @@
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
@ -6877,7 +6772,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
@ -6895,7 +6789,6 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
@ -6905,14 +6798,12 @@
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
@ -6927,7 +6818,6 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
@ -6940,7 +6830,6 @@
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@ -6974,7 +6863,6 @@
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz",
"integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==",
"dev": true,
"license": "ISC",
"bin": {
"yaml": "bin.mjs"

BIN
public/sun_flow_banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 MiB

View File

@ -6,6 +6,9 @@ import { BrowserRouter, Routes, Route } from "react-router-dom";
import Index from "./pages/Index";
import Solar from "./pages/Solar";
import Wind from "./pages/Wind";
import InstallateurFinden from "./pages/InstallateurFinden";
import KostenloseBeratung from "./pages/KostenloseBeratung";
import UnternehmenListen from "./pages/UnternehmenListen";
import NotFound from "./pages/NotFound";
const queryClient = new QueryClient();
@ -15,11 +18,14 @@ const App = () => (
<TooltipProvider>
<Toaster />
<Sonner />
<BrowserRouter>
<BrowserRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
<Routes>
<Route path="/" element={<Index />} />
<Route path="/solar" element={<Solar />} />
<Route path="/wind" element={<Wind />} />
<Route path="/installateur-finden" element={<InstallateurFinden />} />
<Route path="/kostenlose-beratung" element={<KostenloseBeratung />} />
<Route path="/unternehmen-listen" element={<UnternehmenListen />} />
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
<Route path="*" element={<NotFound />} />
</Routes>

View File

@ -0,0 +1,146 @@
import React from 'react';
import { Card, CardContent } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Sun, Wind, Calculator, ArrowRight, Zap } from 'lucide-react';
import { Link } from 'react-router-dom';
const CalculatorNavigation = () => {
return (
<section className="py-20 bg-gradient-to-br from-slate-50 via-blue-50 to-green-50">
<div className="container mx-auto px-4">
<div className="text-center mb-16">
<h2 className="text-3xl md:text-4xl font-bold mb-6 text-gray-800">
<Calculator className="inline-block mr-3 text-blue-600" />
Einsparungsrechner
</h2>
<p className="text-xl text-gray-600 max-w-3xl mx-auto leading-relaxed">
Berechnen Sie Ihre potentiellen Einsparungen mit unseren
spezialisierten Rechnern für Solar- und Windenergie
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-4xl mx-auto">
{/* Solar Calculator Card */}
<Card className="group hover:shadow-2xl transition-all duration-500 border-0 bg-gradient-to-br from-orange-50 to-yellow-50 overflow-hidden relative">
<div className="absolute inset-0 bg-gradient-to-r from-orange-400/5 to-yellow-400/5 group-hover:from-orange-400/10 group-hover:to-yellow-400/10 transition-all duration-500"></div>
<CardContent className="p-8 relative z-10">
<div className="flex items-start justify-between mb-6">
<div className="w-16 h-16 bg-gradient-to-r from-orange-500 to-yellow-500 rounded-2xl flex items-center justify-center group-hover:scale-110 transition-transform duration-300 shadow-lg">
<Sun className="w-8 h-8 text-white" />
</div>
<div className="bg-orange-100 text-orange-700 px-3 py-1 rounded-full text-sm font-semibold">
Beliebt
</div>
</div>
<h3 className="text-2xl font-bold mb-4 text-gray-800 group-hover:text-orange-600 transition-colors">
Solar-Einsparungsrechner
</h3>
<p className="text-gray-600 mb-6 leading-relaxed">
Erfahren Sie, wie viel Sie mit einer Photovoltaik-Anlage sparen können.
Berechnung basierend auf Ihrer Dachgröße, Standort und Energieverbrauch.
</p>
<div className="space-y-3 mb-8">
<div className="flex items-center text-sm text-gray-600">
<Zap className="w-4 h-4 mr-2 text-orange-500" />
<span>Monatliche & jährliche Einsparungen</span>
</div>
<div className="flex items-center text-sm text-gray-600">
<Zap className="w-4 h-4 mr-2 text-orange-500" />
<span>25-Jahre Gesamteinsparungen</span>
</div>
<div className="flex items-center text-sm text-gray-600">
<Zap className="w-4 h-4 mr-2 text-orange-500" />
<span>Amortisationsdauer</span>
</div>
</div>
<Button
asChild
size="lg"
className="w-full bg-gradient-to-r from-orange-500 to-yellow-500 hover:from-orange-600 hover:to-yellow-600 text-white font-semibold shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-300"
>
<Link to="/solar#calculator" className="flex items-center justify-center">
Solar-Rechner öffnen
<ArrowRight className="w-5 h-5 ml-2 group-hover:translate-x-1 transition-transform" />
</Link>
</Button>
</CardContent>
</Card>
{/* Wind Calculator Card */}
<Card className="group hover:shadow-2xl transition-all duration-500 border-0 bg-gradient-to-br from-cyan-50 to-emerald-50 overflow-hidden relative">
<div className="absolute inset-0 bg-gradient-to-r from-cyan-400/5 to-emerald-400/5 group-hover:from-cyan-400/10 group-hover:to-emerald-400/10 transition-all duration-500"></div>
<CardContent className="p-8 relative z-10">
<div className="flex items-start justify-between mb-6">
<div className="w-16 h-16 bg-gradient-to-r from-cyan-500 to-emerald-500 rounded-2xl flex items-center justify-center group-hover:scale-110 transition-transform duration-300 shadow-lg">
<Wind className="w-8 h-8 text-white animate-spin" />
</div>
<div className="bg-cyan-100 text-cyan-700 px-3 py-1 rounded-full text-sm font-semibold">
Neu
</div>
</div>
<h3 className="text-2xl font-bold mb-4 text-gray-800 group-hover:text-cyan-600 transition-colors">
Windenergie-Rechner
</h3>
<p className="text-gray-600 mb-6 leading-relaxed">
Berechnen Sie das Potential von Windenergie für Ihr Grundstück.
Berücksichtigt Grundstücksgröße, Windgeschwindigkeit und lokale Bestimmungen.
</p>
<div className="space-y-3 mb-8">
<div className="flex items-center text-sm text-gray-600">
<Wind className="w-4 h-4 mr-2 text-cyan-500" />
<span>Anlagengröße-Empfehlung</span>
</div>
<div className="flex items-center text-sm text-gray-600">
<Wind className="w-4 h-4 mr-2 text-cyan-500" />
<span>20-Jahre Einsparpotential</span>
</div>
<div className="flex items-center text-sm text-gray-600">
<Wind className="w-4 h-4 mr-2 text-cyan-500" />
<span>Förderungen & Zuschüsse</span>
</div>
</div>
<Button
asChild
size="lg"
className="w-full bg-gradient-to-r from-cyan-500 to-emerald-500 hover:from-cyan-600 hover:to-emerald-600 text-white font-semibold shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all duration-300"
>
<Link to="/wind#calculator" className="flex items-center justify-center">
Wind-Rechner öffnen
<ArrowRight className="w-5 h-5 ml-2 group-hover:translate-x-1 transition-transform" />
</Link>
</Button>
</CardContent>
</Card>
</div>
{/* Bottom CTA */}
<div className="text-center mt-16">
<p className="text-gray-600 mb-6">
Unsicher, welche Lösung für Sie geeignet ist?
</p>
<Button
asChild
variant="outline"
size="lg"
className="border-2 border-gray-300 text-gray-700 hover:bg-gray-50 font-semibold"
>
<Link to="/installateur-finden" className="flex items-center">
Alle Installateure ansehen
<ArrowRight className="w-4 h-4 ml-2" />
</Link>
</Button>
</div>
</div>
</section>
);
};
export default CalculatorNavigation;

View File

@ -8,7 +8,7 @@ interface EnergyTypeCardProps {
description: string;
image: string;
gradient: string;
buttonVariant: "solar" | "wind" | "geo" | "battery";
buttonVariant: "solar" | "wind";
href: string;
features: string[];
}

View File

@ -1,8 +1,6 @@
import EnergyTypeCard from "./EnergyTypeCard";
import solarImage from "@/assets/solar-installation.jpg";
import windImage from "@/assets/wind-turbines.jpg";
import geoImage from "@/assets/geothermal-system.jpg";
import batteryImage from "@/assets/battery-storage.jpg";
const EnergyTypesSection = () => {
const energyTypes = [
@ -33,34 +31,6 @@ const EnergyTypesSection = () => {
"Wartungsarme Technologie",
"Ideale Ergänzung zu Solar"
]
},
{
title: "Geothermie",
description: "Heizen und kühlen Sie Ihr Gebäude mit der natürlichen Erdwärme - effizient und umweltschonend.",
image: geoImage,
gradient: "bg-gradient-geo",
buttonVariant: "geo" as const,
href: "/geothermie",
features: [
"Ganzjährig konstante Temperaturen",
"Niedrige Betriebskosten",
"Heizen und Kühlen in einem System",
"Sehr lange Lebensdauer"
]
},
{
title: "Batteriespeicher",
description: "Speichern Sie überschüssige Energie und nutzen Sie sie bei Bedarf - für maximale Unabhängigkeit.",
image: batteryImage,
gradient: "bg-gradient-battery",
buttonVariant: "battery" as const,
href: "/batteriespeicher",
features: [
"24/7 Energieverfügbarkeit",
"Notstromfunktion",
"Intelligente Steuerung",
"Erhöhte Eigenverbrauchsquote"
]
}
];
@ -77,7 +47,7 @@ const EnergyTypesSection = () => {
</p>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
<div className="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-4xl mx-auto">
{energyTypes.map((type) => (
<EnergyTypeCard key={type.title} {...type} />
))}

View File

@ -52,16 +52,6 @@ const Footer = () => {
Wind-Installateure
</Link>
</li>
<li>
<Link to="/geothermie" className="text-primary-foreground/80 hover:text-geo transition-colors">
Geothermie-Installateure
</Link>
</li>
<li>
<Link to="/batteriespeicher" className="text-primary-foreground/80 hover:text-battery transition-colors">
Batteriespeicher-Installateure
</Link>
</li>
</ul>
</div>
@ -74,11 +64,6 @@ const Footer = () => {
Installateur Finden
</Link>
</li>
<li>
<Link to="/kostenlose-beratung" className="text-primary-foreground/80 hover:text-white transition-colors">
Kostenlose Beratung
</Link>
</li>
<li>
<Link to="/unternehmen-listen" className="text-primary-foreground/80 hover:text-white transition-colors">
Unternehmen Listen

View File

@ -23,12 +23,6 @@ const Header = () => {
<Link to="/wind" className="text-foreground hover:text-wind transition-colors font-medium">
Wind
</Link>
<Link to="/geothermie" className="text-foreground hover:text-geo transition-colors font-medium">
Geothermie
</Link>
<Link to="/batteriespeicher" className="text-foreground hover:text-battery transition-colors font-medium">
Batteriespeicher
</Link>
<Link to="/installateur-finden" className="text-foreground hover:text-primary transition-colors font-medium">
Installateur Finden
</Link>
@ -39,12 +33,6 @@ const Header = () => {
<Button variant="outline" size="sm" asChild>
<Link to="/unternehmen-listen">Unternehmen Listen</Link>
</Button>
<Button variant="hero" size="sm" asChild>
<Link to="/kostenlose-beratung">
<Phone className="w-4 h-4 mr-2" />
Kostenlose Beratung
</Link>
</Button>
</div>
{/* Mobile Menu Button */}

View File

@ -1,96 +1,111 @@
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Search, MapPin, Zap } from "lucide-react";
import { Search, MapPin, Zap, ArrowRight } from "lucide-react";
import { Link } from "react-router-dom";
import heroImage from "@/assets/hero-renewable-energy.jpg";
const HeroSection = () => {
return (
<section className="relative min-h-[600px] flex items-center overflow-hidden">
{/* Background Image with Overlay */}
{/* Background Image with Minimal Overlay */}
<div className="absolute inset-0">
<img
src={heroImage}
alt="Renewable Energy Solutions in Germany"
src="/sun_flow_banner.png"
alt="Sun Flow Banner - Renewable Energy Solutions in Germany"
className="w-full h-full object-cover"
/>
<div className="absolute inset-0 bg-gradient-to-r from-primary/80 via-primary/60 to-transparent"></div>
{/* Subtle overlay for text readability */}
<div className="absolute inset-0 bg-gradient-to-r from-black/30 via-black/20 to-transparent"></div>
</div>
{/* Content */}
<div className="container mx-auto px-4 relative z-10">
<div className="max-w-2xl text-white">
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold mb-6 leading-tight">
Finden Sie Ihren perfekten{" "}
<span className="bg-gradient-to-r from-solar via-wind to-geo bg-clip-text text-transparent">
Erneuerbaren Energie
</span>{" "}
Installateur
</h1>
{/* Clean headline with subtle color accents */}
<div className="mb-6">
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold leading-tight">
Finden Sie Ihren perfekten{" "}
<span className="text-yellow-300">
Erneuerbaren
</span>{" "}
<span className="text-cyan-200">
Energie
</span>{" "}
<span className="text-green-200">
Installateur
</span>
</h1>
</div>
<p className="text-xl md:text-2xl text-white/90 mb-8 leading-relaxed">
Vergleichen Sie qualifizierte Fachbetriebe für Solar, Wind, Geothermie
und Batteriespeicher in Ihrer Region. Kostenlos und unverbindlich.
{/* Clean description with subtle accents */}
<p className="text-xl md:text-2xl text-white/95 mb-8 leading-relaxed font-medium">
Vergleichen Sie qualifizierte Fachbetriebe für{" "}
<span className="text-yellow-200 font-semibold">Solar</span> und{" "}
<span className="text-cyan-200 font-semibold">Wind</span>
in Ihrer Region.{" "}
<span className="text-green-200 font-semibold">Kostenlos</span> und{" "}
<span className="text-white font-semibold">unverbindlich</span>.
</p>
{/* Search Box */}
<div className="bg-white/10 backdrop-blur-sm border border-white/20 rounded-2xl p-6 mb-8">
{/* Clean search box */}
<div className="bg-white/15 backdrop-blur-sm border border-white/30 rounded-2xl p-6 mb-8">
<div className="flex flex-col md:flex-row gap-4">
<div className="flex-1 relative">
<MapPin className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white/60 w-5 h-5" />
<div className="flex-1 relative group">
<MapPin className="absolute left-3 top-1/2 transform -translate-y-1/2 text-cyan-200 w-5 h-5" />
<Input
placeholder="PLZ oder Stadt eingeben..."
className="pl-10 bg-white/10 border-white/20 text-white placeholder:text-white/60 h-12"
className="pl-10 bg-white/20 border-white/30 text-white placeholder:text-white/70 h-12 focus:border-cyan-200 focus:ring-cyan-200/20 transition-all duration-300"
/>
</div>
<div className="flex-1 relative">
<Zap className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white/60 w-5 h-5" />
<select className="w-full h-12 pl-10 pr-4 bg-white/10 border border-white/20 rounded-lg text-white appearance-none">
<div className="flex-1 relative group">
<Zap className="absolute left-3 top-1/2 transform -translate-y-1/2 text-yellow-200 w-5 h-5" />
<select className="w-full h-12 pl-10 pr-4 bg-white/20 border-white/30 rounded-lg text-white appearance-none focus:border-yellow-200 focus:ring-yellow-200/20 transition-all duration-300">
<option value="">Energieart wählen</option>
<option value="solar">Solar</option>
<option value="wind">Wind</option>
<option value="geothermal">Geothermie</option>
<option value="battery">Batteriespeicher</option>
</select>
</div>
<Button variant="hero" size="lg" className="bg-white text-primary hover:bg-white/90">
<Button
variant="hero"
size="lg"
className="bg-cyan-600 hover:bg-cyan-700 text-white border-0 shadow-lg hover:shadow-xl transition-all duration-300"
>
<Search className="w-5 h-5 mr-2" />
Installateur Finden
</Button>
</div>
</div>
{/* CTA Buttons */}
{/* Clean CTA buttons */}
<div className="flex flex-col sm:flex-row gap-4">
<Button variant="hero" size="xl" asChild>
<Link to="/kostenlose-beratung">
Kostenlose Beratung Anfordern
</Link>
</Button>
<Button variant="outline" size="xl" className="border-white/30 text-white hover:bg-white hover:text-primary">
<Link to="/installateur-finden">
<Button
variant="outline"
size="xl"
className="border-2 border-white/40 text-white hover:bg-white hover:text-primary bg-white/10 backdrop-blur-sm hover:shadow-lg transition-all duration-300"
>
<Link to="/installateur-finden" className="flex items-center">
Alle Installateure Ansehen
<ArrowRight className="w-5 h-5 ml-2 group-hover:translate-x-1 transition-transform" />
</Link>
</Button>
</div>
</div>
</div>
{/* Floating Stats */}
{/* Clean floating stats */}
<div className="absolute bottom-8 right-8 hidden xl:block">
<div className="bg-white/10 backdrop-blur-sm border border-white/20 rounded-2xl p-6 text-white">
<div className="bg-white/20 backdrop-blur-sm border border-white/30 rounded-2xl p-6 text-white shadow-lg">
<div className="grid grid-cols-3 gap-6 text-center">
<div>
<div className="text-2xl font-bold">500+</div>
<div className="text-sm text-white/80">Installateure</div>
<div className="text-3xl font-bold text-yellow-200">500+</div>
<div className="text-sm text-white/90 font-medium">Installateure</div>
</div>
<div>
<div className="text-2xl font-bold">2,500+</div>
<div className="text-sm text-white/80">Projekte</div>
<div className="text-3xl font-bold text-cyan-200">2,500+</div>
<div className="text-sm text-white/90 font-medium">Projekte</div>
</div>
<div>
<div className="text-2xl font-bold">4.8</div>
<div className="text-sm text-white/80">Bewertung</div>
<div className="text-3xl font-bold text-green-200">4.8</div>
<div className="text-sm text-white/90 font-medium">Bewertung</div>
</div>
</div>
</div>

View File

@ -0,0 +1,538 @@
import React, { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { HelpCircle } from 'lucide-react';
import { analyticsService, quoteService } from '@/lib/database';
interface CalculatorResults {
monthlySavings: number;
yearlySavings: number;
lifetimeSavings: number;
systemSize: number;
installCost: number;
taxCredit: number;
netCost: number;
paybackPeriod: number;
}
const SolarCalculator = () => {
const [monthlyBill, setMonthlyBill] = useState<string>('150');
const [homeSize, setHomeSize] = useState<string>('2000');
const [roofType, setRoofType] = useState<string>('asphalt');
const [sunlightHours, setSunlightHours] = useState<string>('6');
const [electricityRate, setElectricityRate] = useState<string>('0.12');
const [zipCode, setZipCode] = useState<string>('');
const [results, setResults] = useState<CalculatorResults | null>(null);
const [showResults, setShowResults] = useState(false);
const [showQuoteForm, setShowQuoteForm] = useState(false);
// Quote form fields
const [customerName, setCustomerName] = useState('');
const [customerEmail, setCustomerEmail] = useState('');
const [customerPhone, setCustomerPhone] = useState('');
const calculateSavings = async () => {
const monthlyBillNum = parseFloat(monthlyBill) || 0;
const homeSizeNum = parseFloat(homeSize) || 0;
const sunlightHoursNum = parseFloat(sunlightHours) || 6;
const electricityRateNum = parseFloat(electricityRate) || 0.12;
if (monthlyBillNum === 0) {
alert('Bitte geben Sie Ihre monatliche Stromrechnung ein');
return;
}
// Calculate system size based on monthly usage
const monthlyUsage = monthlyBillNum / electricityRateNum; // kWh per month
const dailyUsage = monthlyUsage / 30; // kWh per day
const systemSize = Math.ceil(dailyUsage / sunlightHoursNum); // kW system size
// Roof type efficiency factors
const roofFactors: { [key: string]: number } = {
asphalt: 1.0,
tile: 0.95,
metal: 1.05,
flat: 0.9
};
const roofFactor = roofFactors[roofType] || 1.0;
const adjustedSystemSize = Math.max(3, Math.min(20, systemSize * roofFactor));
// Cost calculations (converted to EUR for German market)
const costPerWatt = 2.8; // Average cost per watt installed in EUR
const installCost = adjustedSystemSize * 1000 * costPerWatt;
const federalTaxCredit = installCost * 0.19; // German VAT can be deducted
const netCost = installCost - federalTaxCredit;
// Energy production calculations
const annualProduction = adjustedSystemSize * sunlightHoursNum * 365 * 0.85; // 85% efficiency
const monthlyProduction = annualProduction / 12;
// Savings calculations
const monthlySavings = Math.min(monthlyProduction * electricityRateNum, monthlyBillNum * 0.9);
const yearlySavings = monthlySavings * 12;
const lifetimeSavings = yearlySavings * 25; // 25-year system life
// Payback period
const paybackPeriod = Math.round(netCost / yearlySavings * 10) / 10;
setResults({
monthlySavings: Math.round(monthlySavings),
yearlySavings: Math.round(yearlySavings),
lifetimeSavings: Math.round(lifetimeSavings),
systemSize: Math.round(adjustedSystemSize * 10) / 10,
installCost: Math.round(installCost),
taxCredit: Math.round(federalTaxCredit),
netCost: Math.round(netCost),
paybackPeriod
});
setShowResults(true);
// Track calculator usage
try {
await analyticsService.trackEvent({
event_type: 'solar_calculator_used',
page_url: window.location.pathname,
user_agent: navigator.userAgent,
session_id: sessionStorage.getItem('session_id') || 'anonymous',
event_data: {
monthly_bill: monthlyBillNum,
home_size: homeSizeNum,
roof_type: roofType,
sunlight_hours: sunlightHoursNum,
electricity_rate: electricityRateNum,
zip_code: zipCode,
calculated_savings: {
monthly: Math.round(monthlySavings),
yearly: Math.round(yearlySavings),
lifetime: Math.round(lifetimeSavings),
system_size: Math.round(adjustedSystemSize * 10) / 10,
payback_period: paybackPeriod
}
}
});
} catch (error) {
console.error('Error tracking calculator usage:', error);
}
};
// Auto-populate electricity rate based on ZIP code (German postal codes)
useEffect(() => {
if (zipCode.length === 5) {
// Simplified German electricity rates by region
const rates: { [key: string]: number } = {
'0': 0.32, // Eastern Germany
'1': 0.34, // Berlin/Brandenburg
'2': 0.33, // Northern Germany
'3': 0.35, // Central Germany
'4': 0.33, // Western Germany
'5': 0.34, // NRW
'6': 0.35, // Hessen/Baden-Württemberg
'7': 0.33, // Baden-Württemberg
'8': 0.32, // Bavaria
'9': 0.31 // Bavaria
};
const firstDigit = zipCode[0];
const rate = rates[firstDigit] || 0.33;
setElectricityRate(rate.toFixed(2));
}
}, [zipCode]);
// Submit quote request
const handleQuoteSubmission = async (e: React.FormEvent) => {
e.preventDefault();
if (!customerName || !customerEmail || !results) {
alert('Bitte füllen Sie alle Pflichtfelder aus.');
return;
}
try {
await quoteService.submitQuote({
customer_name: customerName,
customer_email: customerEmail,
customer_phone: customerPhone,
energy_type: 'solar',
location: zipCode,
monthly_bill: parseFloat(monthlyBill),
property_size: parseFloat(homeSize),
property_type: 'residential',
roof_type: roofType,
estimated_cost: results.installCost,
estimated_savings: results.yearlySavings,
additional_requirements: `Berechnet: ${results.systemSize} kW System, ${results.paybackPeriod} Jahre Amortisation`,
status: 'pending'
});
alert('Ihr Angebot wurde erfolgreich eingereicht! Sie erhalten bald Kontakt von qualifizierten Installateuren.');
setShowQuoteForm(false);
// Reset form
setCustomerName('');
setCustomerEmail('');
setCustomerPhone('');
} catch (error) {
console.error('Error submitting quote:', error);
alert('Fehler beim Senden des Angebots. Bitte versuchen Sie es später erneut.');
}
};
const createSavingsChart = () => {
if (!results) return null;
const bars = [
{ label: 'Monat 1', value: results.monthlySavings },
{ label: 'Jahr 1', value: results.yearlySavings },
{ label: '5 Jahre', value: results.yearlySavings * 5 },
{ label: '10 Jahre', value: results.yearlySavings * 10 },
{ label: '25 Jahre', value: results.lifetimeSavings }
];
const maxValue = results.lifetimeSavings;
return (
<div className="flex items-end justify-center h-32 gap-3 mt-4">
{bars.map((bar, index) => (
<div key={index} className="flex flex-col items-center">
<div
className="bg-gradient-to-t from-solar to-solar-light rounded-t-sm w-6 transition-all duration-1000 ease-out"
style={{
height: `${(bar.value / maxValue) * 120}px`,
minHeight: '10px'
}}
></div>
<span className="text-xs text-muted-foreground mt-2 text-center">
{bar.label}
</span>
</div>
))}
</div>
);
};
return (
<div className="bg-gradient-to-br from-blue-900 via-blue-700 to-cyan-500 py-16">
<div className="container mx-auto px-4">
<div className="bg-white rounded-3xl shadow-2xl overflow-hidden backdrop-blur-sm">
{/* Header */}
<div className="bg-gradient-to-r from-orange-400 via-orange-500 to-red-500 text-white p-12 text-center relative overflow-hidden">
<div className="absolute inset-0 opacity-5">
<div className="absolute inset-0" style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Ccircle cx='30' cy='30' r='4'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`,
animation: 'float 20s linear infinite'
}}></div>
</div>
<h2 className="text-4xl md:text-5xl font-bold mb-4 bg-gradient-to-r from-white to-yellow-100 bg-clip-text text-transparent">
Solar-Einsparungsrechner
</h2>
<p className="text-xl opacity-95 font-light">
Entdecken Sie, wie viel Sie mit Solarenergie sparen können
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-0">
{/* Calculator Section */}
<div className="p-8 lg:p-12 bg-gradient-to-br from-slate-50 to-slate-100 border-r border-gray-200">
<div className="flex justify-center mb-8">
<div className="flex gap-4">
<div className="w-3 h-3 rounded-full bg-blue-500"></div>
<div className="w-3 h-3 rounded-full bg-gray-300"></div>
<div className="w-3 h-3 rounded-full bg-gray-300"></div>
</div>
</div>
<h3 className="text-2xl font-bold text-gray-800 mb-8 relative">
Berechnen Sie Ihre Solar-Einsparungen
<div className="absolute bottom-0 left-0 w-16 h-1 bg-gradient-to-r from-orange-400 to-orange-500 rounded"></div>
</h3>
<Card className="bg-white/70 border-gray-200 backdrop-blur-sm mb-6">
<CardContent className="p-6">
<div className="grid gap-6">
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Durchschnittliche monatliche Stromrechnung
<HelpCircle className="inline w-4 h-4 ml-1 text-gray-400" />
</label>
<div className="relative">
<span className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-600 font-semibold"></span>
<Input
type="number"
value={monthlyBill}
onChange={(e) => setMonthlyBill(e.target.value)}
placeholder="150"
className="pl-8 h-12 border-2 border-gray-200 focus:border-blue-500"
/>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Hausgröße (m²)
<HelpCircle className="inline w-4 h-4 ml-1 text-gray-400" />
</label>
<Input
type="number"
value={homeSize}
onChange={(e) => setHomeSize(e.target.value)}
placeholder="200"
className="h-12 border-2 border-gray-200 focus:border-blue-500"
/>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Dachtyp
<HelpCircle className="inline w-4 h-4 ml-1 text-gray-400" />
</label>
<Select value={roofType} onValueChange={setRoofType}>
<SelectTrigger className="h-12 border-2 border-gray-200">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="asphalt">Ziegeldach</SelectItem>
<SelectItem value="tile">Tonziegel</SelectItem>
<SelectItem value="metal">Metalldach</SelectItem>
<SelectItem value="flat">Flachdach</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</CardContent>
</Card>
<Card className="bg-white/70 border-gray-200 backdrop-blur-sm mb-8">
<CardContent className="p-6">
<div className="grid gap-6">
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Tägliche Sonnenstunden
<HelpCircle className="inline w-4 h-4 ml-1 text-gray-400" />
</label>
<Select value={sunlightHours} onValueChange={setSunlightHours}>
<SelectTrigger className="h-12 border-2 border-gray-200">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="4">4 Stunden (Weniger sonnige Gebiete)</SelectItem>
<SelectItem value="5">5 Stunden (Mäßige Sonne)</SelectItem>
<SelectItem value="6">6 Stunden (Gute Sonneneinstrahlung)</SelectItem>
<SelectItem value="7">7 Stunden (Sehr sonnig)</SelectItem>
<SelectItem value="8">8+ Stunden (Ausgezeichnete Sonne)</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Strompreis (pro kWh)
<HelpCircle className="inline w-4 h-4 ml-1 text-gray-400" />
</label>
<div className="relative">
<span className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-600 font-semibold"></span>
<Input
type="number"
value={electricityRate}
onChange={(e) => setElectricityRate(e.target.value)}
step="0.01"
placeholder="0.33"
className="pl-8 h-12 border-2 border-gray-200 focus:border-blue-500"
/>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Postleitzahl
<HelpCircle className="inline w-4 h-4 ml-1 text-gray-400" />
</label>
<Input
type="text"
value={zipCode}
onChange={(e) => setZipCode(e.target.value)}
placeholder="12345"
pattern="[0-9]{5}"
className="h-12 border-2 border-gray-200 focus:border-blue-500"
/>
</div>
</div>
</CardContent>
</Card>
<Button
onClick={calculateSavings}
className="w-full h-14 text-lg font-bold bg-gradient-to-r from-blue-600 to-blue-800 hover:from-blue-700 hover:to-blue-900 transform hover:-translate-y-1 transition-all duration-300 shadow-lg hover:shadow-xl"
>
Meine Einsparungen berechnen
</Button>
</div>
{/* Results Section */}
<div className="p-8 lg:p-12 bg-gradient-to-br from-white to-gray-50">
<h3 className="text-2xl font-bold text-gray-800 mb-8 relative">
Ihre Solar-Einsparungen
<div className="absolute bottom-0 left-0 w-16 h-1 bg-gradient-to-r from-blue-400 to-cyan-400 rounded"></div>
</h3>
{showResults && results ? (
<div className="space-y-6 animate-in fade-in duration-500">
<Card className="bg-gradient-to-r from-purple-600 to-purple-800 text-white">
<CardContent className="p-6 text-center">
<div className="text-3xl font-bold mb-1">{results.monthlySavings}</div>
<div className="opacity-90">Monatliche Einsparungen</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-r from-green-600 to-green-800 text-white">
<CardContent className="p-6 text-center">
<div className="text-3xl font-bold mb-1">{results.yearlySavings.toLocaleString()}</div>
<div className="opacity-90">Jährliche Einsparungen</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-r from-orange-600 to-red-600 text-white">
<CardContent className="p-6 text-center">
<div className="text-3xl font-bold mb-1">{results.lifetimeSavings.toLocaleString()}</div>
<div className="opacity-90">25-Jahre Einsparungen</div>
</CardContent>
</Card>
<Card className="bg-gray-50 border-gray-200">
<CardHeader>
<CardTitle className="text-center text-gray-800">Kostenaufschlüsselung</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3 text-sm">
<div className="flex justify-between py-2 border-b border-gray-200">
<span>Anlagengröße:</span>
<span className="font-semibold">{results.systemSize} kW</span>
</div>
<div className="flex justify-between py-2 border-b border-gray-200">
<span>Installationskosten:</span>
<span className="font-semibold">{results.installCost.toLocaleString()}</span>
</div>
<div className="flex justify-between py-2 border-b border-gray-200">
<span>Förderung/MwSt. (19%):</span>
<span className="font-semibold text-green-600">-{results.taxCredit.toLocaleString()}</span>
</div>
<div className="flex justify-between py-2 border-b border-gray-200">
<span>Nettokosten:</span>
<span className="font-semibold">{results.netCost.toLocaleString()}</span>
</div>
<div className="flex justify-between py-2 font-bold text-green-600">
<span>Amortisationsdauer:</span>
<span>{results.paybackPeriod} Jahre</span>
</div>
</div>
</CardContent>
</Card>
<Card className="bg-gray-50 border-gray-200">
<CardContent className="p-6">
<h4 className="font-semibold text-center mb-4 text-gray-800">
Einsparungen über die Zeit
</h4>
{createSavingsChart()}
</CardContent>
</Card>
{/* Quote Request Button */}
<div className="text-center">
<Button
onClick={() => setShowQuoteForm(true)}
className="bg-green-600 hover:bg-green-700 text-white font-semibold shadow-lg"
size="lg"
>
Kostenloses Angebot anfordern
</Button>
</div>
</div>
) : (
<div className="text-center text-gray-500 py-12">
<div className="text-lg mb-2">Bereit zur Berechnung</div>
<div className="text-sm">Füllen Sie das Formular aus und klicken Sie auf "Berechnen"</div>
</div>
)}
{/* Quote Form Modal */}
{showQuoteForm && (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<Card className="max-w-md w-full mx-4">
<CardHeader>
<CardTitle>Kostenloses Angebot anfordern</CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={handleQuoteSubmission} className="space-y-4">
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Name *
</label>
<Input
type="text"
value={customerName}
onChange={(e) => setCustomerName(e.target.value)}
required
placeholder="Ihr vollständiger Name"
/>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
E-Mail *
</label>
<Input
type="email"
value={customerEmail}
onChange={(e) => setCustomerEmail(e.target.value)}
required
placeholder="ihre.email@beispiel.de"
/>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Telefon (optional)
</label>
<Input
type="tel"
value={customerPhone}
onChange={(e) => setCustomerPhone(e.target.value)}
placeholder="+49 123 456 7890"
/>
</div>
<div className="flex gap-3 pt-4">
<Button
type="button"
variant="outline"
onClick={() => setShowQuoteForm(false)}
className="flex-1"
>
Abbrechen
</Button>
<Button
type="submit"
className="flex-1 bg-green-600 hover:bg-green-700"
>
Angebot anfordern
</Button>
</div>
</form>
</CardContent>
</Card>
</div>
)}
</div>
</div>
</div>
</div>
</div>
);
};
export default SolarCalculator;

View File

@ -19,13 +19,13 @@ const WhyChooseUsSection = () => {
icon: Award,
title: "Beste Preise",
description: "Durch unseren Vergleich erhalten Sie garantiert die besten Konditionen für Ihr Projekt.",
gradient: "bg-gradient-geo"
gradient: "bg-gradient-solar"
},
{
icon: Clock,
title: "Schnelle Vermittlung",
description: "In nur wenigen Minuten erhalten Sie passende Installateur-Vorschläge für Ihre Region.",
gradient: "bg-gradient-battery"
gradient: "bg-gradient-wind"
},
{
icon: HeartHandshake,

View File

@ -0,0 +1,478 @@
import React, { useState, useEffect } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { HelpCircle, Wind, Zap } from 'lucide-react';
import { analyticsService } from '@/lib/database';
interface WindCalculatorResults {
monthlySavings: number;
yearlySavings: number;
lifetimeSavings: number;
turbineSize: number;
installCost: number;
taxCredit: number;
netCost: number;
paybackPeriod: number;
}
const WindCalculator = () => {
const [monthlyBill, setMonthlyBill] = useState<string>('150');
const [propertySize, setPropertySize] = useState<string>('2');
const [zoning, setZoning] = useState<string>('rural');
const [windSpeed, setWindSpeed] = useState<string>('12');
const [turbineHeight, setTurbineHeight] = useState<string>('80');
const [electricityRate, setElectricityRate] = useState<string>('0.33');
const [zipCode, setZipCode] = useState<string>('');
const [results, setResults] = useState<WindCalculatorResults | null>(null);
const [showResults, setShowResults] = useState(false);
const calculateWindSavings = async () => {
const monthlyBillNum = parseFloat(monthlyBill) || 0;
const propertySizeNum = parseFloat(propertySize) || 0;
const windSpeedNum = parseFloat(windSpeed) || 12;
const turbineHeightNum = parseFloat(turbineHeight) || 80;
const electricityRateNum = parseFloat(electricityRate) || 0.33;
if (monthlyBillNum === 0) {
alert('Bitte geben Sie Ihre monatliche Stromrechnung ein');
return;
}
// Wind power calculation (simplified for German market)
const airDensity = 1.225; // kg/m³ at sea level
const powerCoefficient = 0.35; // Typical for small wind turbines
const efficiency = 0.85;
// Determine turbine size based on property and zoning
let maxTurbineKW = 0;
const zoningSizeFactors: { [key: string]: number } = {
rural: 1.5,
agricultural: 2.0,
suburban: 0.8,
urban: 0.5
};
const sizeFactor = zoningSizeFactors[zoning] || 1.0;
// Base turbine size on property size and height allowance
if (propertySizeNum >= 0.8 && turbineHeightNum >= 80) { // 0.8 hectares ≈ 2 acres
maxTurbineKW = Math.min(20, propertySizeNum * 3 * sizeFactor);
} else if (propertySizeNum >= 0.4 && turbineHeightNum >= 60) {
maxTurbineKW = Math.min(10, propertySizeNum * 2 * sizeFactor);
} else {
maxTurbineKW = Math.min(5, propertySizeNum * 1.5 * sizeFactor);
}
// Wind speed factor affects actual output
let windSpeedFactor = 1.0;
if (windSpeedNum >= 15) windSpeedFactor = 1.4;
else if (windSpeedNum >= 12) windSpeedFactor = 1.0;
else if (windSpeedNum >= 10) windSpeedFactor = 0.7;
else windSpeedFactor = 0.4;
const effectiveTurbineKW = maxTurbineKW * windSpeedFactor;
// Height bonus (higher = better wind)
let heightFactor = 1.0;
if (turbineHeightNum >= 120) heightFactor = 1.3;
else if (turbineHeightNum >= 100) heightFactor = 1.2;
else if (turbineHeightNum >= 80) heightFactor = 1.1;
else if (turbineHeightNum >= 60) heightFactor = 1.0;
else heightFactor = 0.9;
const finalTurbineKW = Math.max(1, effectiveTurbineKW * heightFactor);
// Annual energy production (kWh/year)
// Small wind turbines typically produce 25-35% capacity factor
const capacityFactor = Math.min(0.35, windSpeedNum * 0.025);
const annualProduction = finalTurbineKW * 8760 * capacityFactor; // 8760 hours/year
const monthlyProduction = annualProduction / 12;
// Cost calculations (adapted for German market)
const costPerKW = 6500; // EUR - Higher than solar due to complexity, adapted for German market
const installCost = finalTurbineKW * costPerKW;
const germanIncentives = installCost * 0.25; // KfW subsidies and regional incentives
const netCost = installCost - germanIncentives;
// Savings calculations
const monthlyUsage = monthlyBillNum / electricityRateNum;
const monthlySavings = Math.min(monthlyProduction * electricityRateNum, monthlyBillNum * 0.8);
const yearlySavings = monthlySavings * 12;
const lifetimeSavings = yearlySavings * 20; // 20-year turbine life
// Payback period
const paybackPeriod = Math.round(netCost / yearlySavings * 10) / 10;
setResults({
monthlySavings: Math.round(monthlySavings),
yearlySavings: Math.round(yearlySavings),
lifetimeSavings: Math.round(lifetimeSavings),
turbineSize: Math.round(finalTurbineKW * 10) / 10,
installCost: Math.round(installCost),
taxCredit: Math.round(germanIncentives),
netCost: Math.round(netCost),
paybackPeriod
});
setShowResults(true);
// Track wind calculator usage
try {
await analyticsService.trackEvent({
event_type: 'wind_calculator_used',
page_url: window.location.pathname,
user_agent: navigator.userAgent,
session_id: sessionStorage.getItem('session_id') || 'anonymous',
event_data: {
monthly_bill: monthlyBillNum,
property_size: propertySizeNum,
zoning: zoning,
wind_speed: windSpeedNum,
turbine_height: turbineHeightNum,
electricity_rate: electricityRateNum,
zip_code: zipCode,
calculated_savings: {
monthly: Math.round(monthlySavings),
yearly: Math.round(yearlySavings),
lifetime: Math.round(lifetimeSavings),
turbine_size: Math.round(finalTurbineKW * 10) / 10,
payback_period: paybackPeriod
}
}
});
} catch (error) {
console.error('Error tracking wind calculator usage:', error);
}
};
// Auto-populate electricity rate based on German ZIP code
useEffect(() => {
if (zipCode.length === 5) {
// German electricity rates by region (higher than solar calculator due to wind being less common)
const rates: { [key: string]: number } = {
'0': 0.34, // Eastern Germany
'1': 0.36, // Berlin/Brandenburg
'2': 0.35, // Northern Germany (better wind resources)
'3': 0.37, // Central Germany
'4': 0.35, // Western Germany
'5': 0.36, // NRW
'6': 0.37, // Hessen/Baden-Württemberg
'7': 0.35, // Baden-Württemberg
'8': 0.34, // Bavaria
'9': 0.33 // Bavaria
};
const firstDigit = zipCode[0];
const rate = rates[firstDigit] || 0.35;
setElectricityRate(rate.toFixed(2));
}
}, [zipCode]);
const createSavingsChart = () => {
if (!results) return null;
const bars = [
{ label: 'Monat 1', value: results.monthlySavings },
{ label: 'Jahr 1', value: results.yearlySavings },
{ label: '5 Jahre', value: results.yearlySavings * 5 },
{ label: '10 Jahre', value: results.yearlySavings * 10 },
{ label: '20 Jahre', value: results.lifetimeSavings }
];
const maxValue = results.lifetimeSavings;
return (
<div className="flex items-end justify-center h-32 gap-3 mt-4">
{bars.map((bar, index) => (
<div key={index} className="flex flex-col items-center">
<div
className="bg-gradient-to-t from-wind-dark to-wind-light rounded-t-sm w-6 transition-all duration-1000 ease-out animate-pulse"
style={{
height: `${(bar.value / maxValue) * 120}px`,
minHeight: '10px'
}}
></div>
<span className="text-xs text-muted-foreground mt-2 text-center">
{bar.label}
</span>
</div>
))}
</div>
);
};
return (
<div className="bg-gradient-to-br from-teal-800 via-emerald-700 to-green-600 py-16">
<div className="container mx-auto px-4">
<div className="bg-white rounded-3xl shadow-2xl overflow-hidden backdrop-blur-sm">
{/* Header */}
<div className="bg-gradient-to-r from-cyan-600 via-teal-600 to-slate-700 text-white p-12 text-center relative overflow-hidden">
<div className="absolute inset-0 opacity-5">
<div
className="absolute inset-0"
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg width='60' height='60' viewBox='0 0 60 60' xmlns='http://www.w3.org/2000/svg'%3E%3Cg fill='none' fill-rule='evenodd'%3E%3Cg fill='%23ffffff' fill-opacity='0.05'%3E%3Cpath d='M30 30c0-11.046 8.954-20 20-20s20 8.954 20 20-8.954 20-20 20-20-8.954-20-20zm0 0c0 11.046-8.954 20-20 20s-20-8.954-20-20 8.954-20 20-20 20 8.954 20 20z'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E")`,
animation: 'windFlow 15s linear infinite'
}}
></div>
</div>
<h2 className="text-4xl md:text-5xl font-bold mb-4 bg-gradient-to-r from-white to-cyan-100 bg-clip-text text-transparent">
<Wind className="inline-block mr-3 animate-spin" />
Windenergie-Einsparungsrechner
</h2>
<p className="text-xl opacity-95 font-light">
Nutzen Sie die Kraft des Windes, um Ihre Energiekosten zu senken
</p>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-0">
{/* Calculator Section */}
<div className="p-8 lg:p-12 bg-gradient-to-br from-emerald-50 to-green-100 border-r border-green-200">
<div className="flex justify-center mb-8">
<div className="flex gap-4">
<div className="w-3 h-3 rounded-full bg-emerald-600"></div>
<div className="w-3 h-3 rounded-full bg-gray-300"></div>
<div className="w-3 h-3 rounded-full bg-gray-300"></div>
</div>
</div>
<h3 className="text-2xl font-bold text-gray-800 mb-8 relative">
<Wind className="inline mr-2 text-emerald-600 animate-spin" />
Berechnen Sie Ihre Windenergie-Einsparungen
<div className="absolute bottom-0 left-0 w-16 h-1 bg-gradient-to-r from-cyan-500 to-teal-600 rounded"></div>
</h3>
<Card className="bg-white/70 border-green-200 backdrop-blur-sm mb-6">
<CardContent className="p-6">
<div className="grid gap-6">
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Durchschnittliche monatliche Stromrechnung
<HelpCircle className="inline w-4 h-4 ml-1 text-gray-400" />
</label>
<div className="relative">
<span className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-600 font-semibold"></span>
<Input
type="number"
value={monthlyBill}
onChange={(e) => setMonthlyBill(e.target.value)}
placeholder="150"
className="pl-8 h-12 border-2 border-green-200 focus:border-emerald-500"
/>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Grundstücksgröße (Hektar)
<HelpCircle className="inline w-4 h-4 ml-1 text-gray-400" />
</label>
<Input
type="number"
value={propertySize}
onChange={(e) => setPropertySize(e.target.value)}
placeholder="0.8"
step="0.1"
min="0"
className="h-12 border-2 border-green-200 focus:border-emerald-500"
/>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Grundstücksart
<HelpCircle className="inline w-4 h-4 ml-1 text-gray-400" />
</label>
<Select value={zoning} onValueChange={setZoning}>
<SelectTrigger className="h-12 border-2 border-green-200">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="rural">Ländlich</SelectItem>
<SelectItem value="suburban">Vorstädtisch</SelectItem>
<SelectItem value="urban">Städtisch</SelectItem>
<SelectItem value="agricultural">Landwirtschaftlich</SelectItem>
</SelectContent>
</Select>
</div>
</div>
</CardContent>
</Card>
<Card className="bg-white/70 border-green-200 backdrop-blur-sm mb-8">
<CardContent className="p-6">
<div className="grid gap-6">
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Durchschnittliche Windgeschwindigkeit (km/h)
<HelpCircle className="inline w-4 h-4 ml-1 text-gray-400" />
</label>
<Select value={windSpeed} onValueChange={setWindSpeed}>
<SelectTrigger className="h-12 border-2 border-green-200">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="8">13 km/h (Schwache Windzone)</SelectItem>
<SelectItem value="10">16 km/h (Mäßiger Wind)</SelectItem>
<SelectItem value="12">19 km/h (Gute Windressource)</SelectItem>
<SelectItem value="15">24 km/h (Ausgezeichneter Wind)</SelectItem>
<SelectItem value="18">29+ km/h (Hervorragender Wind)</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Max. erlaubte Anlagenhöhe (m)
<HelpCircle className="inline w-4 h-4 ml-1 text-gray-400" />
</label>
<Select value={turbineHeight} onValueChange={setTurbineHeight}>
<SelectTrigger className="h-12 border-2 border-green-200">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="40">12 m (Eingeschränkte Gebiete)</SelectItem>
<SelectItem value="60">18 m (Vorstädtisches Limit)</SelectItem>
<SelectItem value="80">24 m (Standard Wohngebiet)</SelectItem>
<SelectItem value="100">30 m (Ländliche Gebiete)</SelectItem>
<SelectItem value="120">36+ m (Keine Beschränkungen)</SelectItem>
</SelectContent>
</Select>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Strompreis (pro kWh)
<HelpCircle className="inline w-4 h-4 ml-1 text-gray-400" />
</label>
<div className="relative">
<span className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-600 font-semibold"></span>
<Input
type="number"
value={electricityRate}
onChange={(e) => setElectricityRate(e.target.value)}
step="0.01"
placeholder="0.35"
className="pl-8 h-12 border-2 border-green-200 focus:border-emerald-500"
/>
</div>
</div>
<div>
<label className="block text-sm font-semibold text-gray-700 mb-2">
Postleitzahl
<HelpCircle className="inline w-4 h-4 ml-1 text-gray-400" />
</label>
<Input
type="text"
value={zipCode}
onChange={(e) => setZipCode(e.target.value)}
placeholder="12345"
pattern="[0-9]{5}"
className="h-12 border-2 border-green-200 focus:border-emerald-500"
/>
</div>
</div>
</CardContent>
</Card>
<Button
onClick={calculateWindSavings}
className="w-full h-14 text-lg font-bold bg-gradient-to-r from-emerald-600 to-teal-700 hover:from-emerald-700 hover:to-teal-800 transform hover:-translate-y-1 transition-all duration-300 shadow-lg hover:shadow-xl"
>
<Wind className="w-5 h-5 mr-2" />
Meine Windenergie-Einsparungen berechnen
</Button>
</div>
{/* Results Section */}
<div className="p-8 lg:p-12 bg-gradient-to-br from-white to-gray-50">
<h3 className="text-2xl font-bold text-gray-800 mb-8 relative">
<Zap className="inline mr-2 text-emerald-600" />
Ihre Windenergie-Einsparungen
<div className="absolute bottom-0 left-0 w-16 h-1 bg-gradient-to-r from-emerald-500 to-green-600 rounded"></div>
</h3>
{showResults && results ? (
<div className="space-y-6 animate-in fade-in duration-500">
<Card className="bg-gradient-to-r from-emerald-600 to-teal-700 text-white relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-r from-white/10 to-transparent"></div>
<CardContent className="p-6 text-center relative z-10">
<div className="text-3xl font-bold mb-1">{results.monthlySavings}</div>
<div className="opacity-90">Monatliche Einsparungen</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-r from-teal-600 to-cyan-700 text-white relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-r from-white/10 to-transparent"></div>
<CardContent className="p-6 text-center relative z-10">
<div className="text-3xl font-bold mb-1">{results.yearlySavings.toLocaleString()}</div>
<div className="opacity-90">Jährliche Einsparungen</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-r from-cyan-600 to-blue-700 text-white relative overflow-hidden">
<div className="absolute inset-0 bg-gradient-to-r from-white/10 to-transparent"></div>
<CardContent className="p-6 text-center relative z-10">
<div className="text-3xl font-bold mb-1">{results.lifetimeSavings.toLocaleString()}</div>
<div className="opacity-90">20-Jahre Einsparungen</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-br from-emerald-50 to-green-100 border-green-200">
<CardHeader>
<CardTitle className="text-center text-gray-800">Windenergie-System Aufschlüsselung</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3 text-sm">
<div className="flex justify-between py-2 border-b border-green-200">
<span>Anlagengröße:</span>
<span className="font-semibold">{results.turbineSize} kW</span>
</div>
<div className="flex justify-between py-2 border-b border-green-200">
<span>Installationskosten:</span>
<span className="font-semibold">{results.installCost.toLocaleString()}</span>
</div>
<div className="flex justify-between py-2 border-b border-green-200">
<span>Förderung/Zuschüsse (25%):</span>
<span className="font-semibold text-green-600">-{results.taxCredit.toLocaleString()}</span>
</div>
<div className="flex justify-between py-2 border-b border-green-200">
<span>Nettokosten nach Förderung:</span>
<span className="font-semibold">{results.netCost.toLocaleString()}</span>
</div>
<div className="flex justify-between py-2 font-bold text-emerald-600">
<span>Amortisationsdauer:</span>
<span>{results.paybackPeriod} Jahre</span>
</div>
</div>
</CardContent>
</Card>
<Card className="bg-gradient-to-br from-emerald-50 to-green-100 border-green-200">
<CardContent className="p-6">
<h4 className="font-semibold text-center mb-4 text-gray-800">
Windenergie-Einsparungen über die Zeit
</h4>
{createSavingsChart()}
</CardContent>
</Card>
</div>
) : (
<div className="text-center text-gray-500 py-12">
<Wind className="w-16 h-16 mx-auto mb-4 text-emerald-400 animate-spin" />
<div className="text-lg mb-2">Bereit zur Berechnung</div>
<div className="text-sm">Füllen Sie das Formular aus und klicken Sie auf "Berechnen"</div>
</div>
)}
</div>
</div>
</div>
</div>
</div>
);
};
export default WindCalculator;

View File

@ -21,12 +21,8 @@ const buttonVariants = cva(
// Energy-specific variants
solar: "bg-gradient-solar text-white hover:shadow-solar transform hover:scale-105",
wind: "bg-gradient-wind text-white hover:shadow-wind transform hover:scale-105",
geo: "bg-gradient-geo text-white hover:shadow-geo transform hover:scale-105",
battery: "bg-gradient-battery text-white hover:shadow-battery transform hover:scale-105",
"solar-outline": "border-2 border-solar bg-transparent text-solar hover:bg-solar hover:text-white",
"wind-outline": "border-2 border-wind bg-transparent text-wind hover:bg-wind hover:text-white",
"geo-outline": "border-2 border-geo bg-transparent text-geo hover:bg-geo hover:text-white",
"battery-outline": "border-2 border-battery bg-transparent text-battery hover:bg-battery hover:text-white",
hero: "bg-gradient-hero text-white hover:shadow-lg transform hover:scale-105 border border-white/20",
},
size: {

View File

@ -26,18 +26,6 @@
--wind-light: 207 100% 85%;
--wind-dark: 207 80% 41%;
/* Energy Type Colors - Geothermal Green */
--geo-primary: 122 39% 49%;
--geo-secondary: 122 40% 56%;
--geo-light: 122 45% 80%;
--geo-dark: 122 35% 36%;
/* Energy Type Colors - Battery Purple */
--battery-primary: 291 64% 42%;
--battery-secondary: 291 64% 49%;
--battery-light: 291 70% 80%;
--battery-dark: 291 60% 29%;
/* Brand Colors */
--primary: 218 47% 18%;
--primary-foreground: 210 40% 98%;
@ -58,8 +46,6 @@
/* Energy Gradients */
--gradient-solar: linear-gradient(135deg, hsl(var(--solar-primary)) 0%, hsl(var(--solar-secondary)) 100%);
--gradient-wind: linear-gradient(135deg, hsl(var(--wind-primary)) 0%, hsl(var(--wind-secondary)) 100%);
--gradient-geo: linear-gradient(135deg, hsl(var(--geo-primary)) 0%, hsl(var(--geo-secondary)) 100%);
--gradient-battery: linear-gradient(135deg, hsl(var(--battery-primary)) 0%, hsl(var(--battery-secondary)) 100%);
--gradient-hero: linear-gradient(135deg, hsl(var(--primary)) 0%, hsl(218 47% 25%) 100%);
/* Shadows */
@ -68,8 +54,6 @@
--shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
--shadow-solar: 0 8px 32px hsl(var(--solar-primary) / 0.25);
--shadow-wind: 0 8px 32px hsl(var(--wind-primary) / 0.25);
--shadow-geo: 0 8px 32px hsl(var(--geo-primary) / 0.25);
--shadow-battery: 0 8px 32px hsl(var(--battery-primary) / 0.25);
/* Animations */
--transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
@ -189,17 +173,28 @@
background-clip: text;
}
.gradient-text-geo {
background: var(--gradient-geo);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
/* Solar Calculator Animations */
@keyframes float {
0% { transform: translate(0, 0); }
100% { transform: translate(-60px, -60px); }
}
.gradient-text-battery {
background: var(--gradient-battery);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
@keyframes growUp {
from { height: 0; }
}
@keyframes fade-in {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-in {
animation: fade-in 0.5s ease-out;
}
/* Wind Calculator Animations */
@keyframes windFlow {
0% { transform: translate(0, 0) rotate(0deg); }
100% { transform: translate(-60px, -60px) rotate(360deg); }
}
}

View File

@ -398,7 +398,7 @@ export type Database = {
}
}
Enums: {
energy_type: "solar" | "wind" | "geothermal" | "battery"
energy_type: "solar" | "wind"
installer_status: "active" | "inactive" | "pending" | "suspended"
quote_status: "pending" | "accepted" | "rejected" | "expired"
}
@ -528,7 +528,7 @@ export type CompositeTypes<
export const Constants = {
public: {
Enums: {
energy_type: ["solar", "wind", "geothermal", "battery"],
energy_type: ["solar", "wind"],
installer_status: ["active", "inactive", "pending", "suspended"],
quote_status: ["pending", "accepted", "rejected", "expired"],
},

180
src/lib/cleanDatabase.ts Normal file
View File

@ -0,0 +1,180 @@
import { supabase } from "@/integrations/supabase/client";
export const cleanAndReseedDatabase = async () => {
console.log('Cleaning existing installer data...');
try {
// First, delete all existing installers
const { error: deleteError } = await supabase
.from('installers')
.delete()
.not('id', 'is', null); // Delete all records
if (deleteError) {
console.error('Error deleting existing data:', deleteError);
throw deleteError;
}
console.log('Existing data cleaned successfully!');
// Now insert our proper German installer data
const germanInstallers = [
{
name: "SolarTech Pro München",
company_name: "SolarTech Pro GmbH",
email: "info@solartech-pro.de",
phone: "+49 89 123 4567",
website: "www.solartech-pro.de",
address: "Maximilianstrasse 15, 80539 München",
location: "München, Bayern",
energy_type: "solar",
description: "Spezialisiert auf hochwertige Photovoltaik-Anlagen für Privathaushalte und Gewerbe. Mit über 15 Jahren Erfahrung sind wir Ihr vertrauensvoller Partner für nachhaltige Energielösungen.",
specialties: ["Photovoltaik", "Solarthermie", "Energiespeicher", "Smart Home Integration"],
certifications: ["IHK Zertifiziert", "VDE Prüfung", "TÜV Süd"],
experience_years: 15,
rating: 4.8,
review_count: 127,
verified: true,
status: "active",
service_areas: ["München", "Augsburg", "Ingolstadt", "Landshut"],
languages: ["Deutsch", "Englisch"],
latitude: 48.1351,
longitude: 11.5820
},
{
name: "WindEnergie Solutions Hamburg",
company_name: "WindEnergie Solutions GmbH",
email: "kontakt@windenergie-solutions.de",
phone: "+49 40 987 6543",
website: "www.windenergie-solutions.de",
address: "Hafenstraße 42, 20359 Hamburg",
location: "Hamburg, Hamburg",
energy_type: "wind",
description: "Experten für Windkraftanlagen und erneuerbare Energielösungen. Wir planen und installieren Windenergieanlagen für private und gewerbliche Kunden.",
specialties: ["Windkraft", "Kleinwindanlagen", "Planung", "Wartung"],
certifications: ["BWE Mitglied", "VDMA Zertifikat", "ISO 9001"],
experience_years: 12,
rating: 4.6,
review_count: 89,
verified: true,
status: "active",
service_areas: ["Hamburg", "Bremen", "Schleswig-Holstein", "Niedersachsen"],
languages: ["Deutsch", "Englisch", "Dänisch"],
latitude: 53.5511,
longitude: 9.9937
},
{
name: "Grüne Energie Partner Berlin",
company_name: "Grüne Energie Partner GmbH",
email: "info@gruene-energie-partner.de",
phone: "+49 30 555 1234",
website: "www.gruene-energie-partner.de",
address: "Unter den Linden 77, 10117 Berlin",
location: "Berlin, Berlin",
energy_type: "solar",
description: "Vollständige Lösungen für erneuerbare Energien mit Fokus auf Nachhaltigkeit. Wir bieten sowohl Solar- als auch Windenergie-Lösungen aus einer Hand.",
specialties: ["Solar", "Wind", "Energieberatung", "Förderung", "Hybrid-Systeme"],
certifications: ["DGS Zertifikat", "Handwerkskammer Berlin", "KfW Effizienzexperte"],
experience_years: 18,
rating: 4.9,
review_count: 203,
verified: true,
status: "active",
service_areas: ["Berlin", "Brandenburg", "Potsdam"],
languages: ["Deutsch", "Englisch", "Polnisch"],
latitude: 52.5200,
longitude: 13.4050
},
{
name: "EcoSolar Systems Stuttgart",
company_name: "EcoSolar Systems GmbH",
email: "service@ecosolar-systems.de",
phone: "+49 711 789 0123",
website: "www.ecosolar-systems.de",
address: "Königstraße 28, 70173 Stuttgart",
location: "Stuttgart, Baden-Württemberg",
energy_type: "solar",
description: "Innovative Solaranlagen mit modernster Technologie für maximale Effizienz. Spezialisiert auf High-End Photovoltaik-Systeme mit intelligenter Steuerung.",
specialties: ["Hochleistungs-Solar", "Smart Home Integration", "Wartung", "Monitoring"],
certifications: ["Solarfachbetrieb", "E-Handwerk Innungsbetrieb", "BSW Solar"],
experience_years: 14,
rating: 4.7,
review_count: 156,
verified: true,
status: "active",
service_areas: ["Stuttgart", "Karlsruhe", "Heilbronn", "Tübingen"],
languages: ["Deutsch", "Englisch"],
latitude: 48.7758,
longitude: 9.1829
},
{
name: "NordWind Energie Bremen",
company_name: "NordWind Energie GmbH",
email: "info@nordwind-energie.de",
phone: "+49 421 456 7890",
website: "www.nordwind-energie.de",
address: "Weserstraße 50, 28199 Bremen",
location: "Bremen, Bremen",
energy_type: "wind",
description: "Spezialisten für Windenergie-Projekte in Norddeutschland. Von der Planung bis zur Wartung - wir begleiten Ihr Windenergie-Projekt vom ersten Tag an.",
specialties: ["Windparks", "Offshore", "Wartung", "Repowering"],
certifications: ["BWE Vollmitglied", "GWO Zertifikat", "VDMA Wind Power"],
experience_years: 16,
rating: 4.5,
review_count: 67,
verified: true,
status: "active",
service_areas: ["Bremen", "Bremerhaven", "Oldenburg", "Cuxhaven"],
languages: ["Deutsch", "Englisch", "Niederländisch"],
latitude: 53.0793,
longitude: 8.8017
},
{
name: "Bayerische Solar Solutions",
company_name: "Bayerische Solar Solutions GmbH",
email: "kontakt@bayerische-solar.de",
phone: "+49 89 321 6547",
website: "www.bayerische-solar.de",
address: "Marienplatz 8, 80331 München",
location: "München, Bayern",
energy_type: "solar",
description: "Traditioneller Handwerksbetrieb mit moderner Solartechnik. Seit 20 Jahren verlässlicher Partner für Photovoltaik-Anlagen in ganz Bayern.",
specialties: ["Dachintegration", "Fassadenmontage", "Carports", "Gewerbeanlagen"],
certifications: ["Handwerkskammer München", "BSW Solar", "TÜV Bayern"],
experience_years: 20,
rating: 4.6,
review_count: 189,
verified: true,
status: "active",
service_areas: ["München", "Rosenheim", "Freising", "Garmisch-Partenkirchen"],
languages: ["Deutsch", "Bayerisch"],
latitude: 48.1372,
longitude: 11.5755
}
];
// Insert the proper German installer data
const { data, error: insertError } = await supabase
.from('installers')
.insert(germanInstallers)
.select();
if (insertError) {
console.error('Error inserting German installer data:', insertError);
console.error('Insert error details:', {
message: insertError.message,
details: insertError.details,
code: insertError.code,
hint: insertError.hint
});
throw insertError;
}
console.log('German installer data inserted successfully:', data?.length, 'installers added');
return data;
} catch (error) {
console.error('Error in cleanAndReseedDatabase:', error);
throw error;
}
};

208
src/lib/database.ts Normal file
View File

@ -0,0 +1,208 @@
import React from "react";
import { supabase } from "@/integrations/supabase/client";
import type { Database } from "@/integrations/supabase/types";
type Installer = Database['public']['Tables']['installers']['Row'];
type InstallerInsert = Database['public']['Tables']['installers']['Insert'];
type Quote = Database['public']['Tables']['quotes']['Row'];
type QuoteInsert = Database['public']['Tables']['quotes']['Insert'];
type ContactClick = Database['public']['Tables']['contact_clicks']['Insert'];
type AnalyticsEvent = Database['public']['Tables']['analytics_events']['Insert'];
// Installer Services
export const installerService = {
// Get all installers with optional filters
async getInstallers(filters?: {
energyType?: string;
location?: string;
searchTerm?: string;
}) {
let query = supabase
.from('installers')
.select('*')
.eq('status', 'active')
.order('rating', { ascending: false });
if (filters?.energyType && filters.energyType !== 'all') {
query = query.contains('energy_type', [filters.energyType]);
}
if (filters?.location) {
query = query.ilike('location', `%${filters.location}%`);
}
if (filters?.searchTerm) {
query = query.or(`name.ilike.%${filters.searchTerm}%,description.ilike.%${filters.searchTerm}%,specialties.cs.{${filters.searchTerm}}`);
}
const { data, error } = await query;
if (error) {
console.error('Error fetching installers:', error);
console.error('Error details:', {
message: error.message,
details: error.details,
code: error.code,
hint: error.hint
});
throw error;
}
return data;
},
// Get installer by ID
async getInstaller(id: string) {
const { data, error } = await supabase
.from('installers')
.select('*')
.eq('id', id)
.single();
if (error) {
console.error('Error fetching installer:', error);
throw error;
}
return data;
},
// Add new installer
async createInstaller(installer: InstallerInsert) {
const { data, error } = await supabase
.from('installers')
.insert(installer)
.select()
.single();
if (error) {
console.error('Error creating installer:', error);
throw error;
}
return data;
}
};
// Quote Services
export const quoteService = {
// Submit new quote request
async submitQuote(quote: QuoteInsert) {
const { data, error } = await supabase
.from('quotes')
.insert(quote)
.select()
.single();
if (error) {
console.error('Error submitting quote:', error);
throw error;
}
return data;
},
// Get quotes for an installer
async getInstallerQuotes(installerId: string) {
const { data, error } = await supabase
.from('quotes')
.select('*')
.eq('assigned_installer_id', installerId)
.order('created_at', { ascending: false });
if (error) {
console.error('Error fetching installer quotes:', error);
throw error;
}
return data;
}
};
// Analytics Services
export const analyticsService = {
// Track contact click
async trackContactClick(contactClick: ContactClick) {
const { data, error } = await supabase
.from('contact_clicks')
.insert(contactClick);
if (error) {
console.error('Error tracking contact click:', error);
// Don't throw error for analytics - just log it
}
return data;
},
// Track general analytics event
async trackEvent(event: AnalyticsEvent) {
const { data, error } = await supabase
.from('analytics_events')
.insert(event);
if (error) {
console.error('Error tracking analytics event:', error);
// Don't throw error for analytics - just log it
}
return data;
}
};
// Utility functions
export const dbUtils = {
// Get user's IP address (simplified)
async getUserIP(): Promise<string> {
try {
const response = await fetch('https://api.ipify.org?format=json');
const data = await response.json();
return data.ip;
} catch {
return 'unknown';
}
},
// Get user agent
getUserAgent(): string {
return navigator.userAgent;
},
// Generate session ID
generateSessionId(): string {
return 'session_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now();
}
};
// Hook for getting installers with loading state
export const useInstallers = () => {
const [installers, setInstallers] = React.useState<Installer[]>([]);
const [loading, setLoading] = React.useState(true);
const [error, setError] = React.useState<string | null>(null);
const fetchInstallers = async (filters?: {
energyType?: string;
location?: string;
searchTerm?: string;
}) => {
try {
setLoading(true);
setError(null);
const data = await installerService.getInstallers(filters);
setInstallers(data || []);
} catch (err) {
setError(err instanceof Error ? err.message : 'An error occurred');
setInstallers([]);
} finally {
setLoading(false);
}
};
return {
installers,
loading,
error,
fetchInstallers,
refetch: fetchInstallers
};
};

102
src/lib/debugDatabase.ts Normal file
View File

@ -0,0 +1,102 @@
import { supabase } from "@/integrations/supabase/client";
export const testConnection = async () => {
console.log('=== TESTING SUPABASE CONNECTION ===');
try {
// Test basic connection
const { data, error } = await supabase
.from('installers')
.select('count', { count: 'exact', head: true });
if (error) {
console.error('Connection test failed:', error);
console.error('Error details:', {
message: error.message,
details: error.details,
code: error.code,
hint: error.hint
});
return false;
}
console.log('✅ Connection successful! Table has', data?.length || 0, 'records');
return true;
} catch (error) {
console.error('Connection test exception:', error);
return false;
}
};
export const debugDatabase = async () => {
console.log('=== DATABASE DEBUG ===');
try {
// Get all installers
const { data, error } = await supabase
.from('installers')
.select('*')
.order('created_at', { ascending: false });
if (error) {
console.error('Error fetching installers:', error);
return;
}
console.log(`Found ${data?.length || 0} installers in database:`);
data?.forEach((installer, index) => {
console.log(`\n${index + 1}. ${installer.name}`);
console.log(` Company: ${installer.company_name}`);
console.log(` Location: ${installer.location}`);
console.log(` Energy Type: ${installer.energy_type}`);
console.log(` Created: ${installer.created_at}`);
console.log(` ID: ${installer.id}`);
});
return data;
} catch (error) {
console.error('Debug error:', error);
}
};
export const forceDeleteAll = async () => {
console.log('=== FORCE DELETE ALL INSTALLERS ===');
try {
// First, let's see what's there
const { data: allData } = await supabase
.from('installers')
.select('*');
console.log('Records before deletion:', allData?.length || 0);
allData?.forEach((record, index) => {
console.log(`${index + 1}. ${record.name} (ID: ${record.id})`);
});
// Delete ALL records without any conditions
const { data, error } = await supabase
.from('installers')
.delete()
.not('id', 'is', null); // This will delete all records
if (error) {
console.error('Error in force delete:', error);
throw error;
}
console.log('All installers deleted successfully');
// Verify deletion
const { data: remainingData } = await supabase
.from('installers')
.select('*');
console.log('Records after deletion:', remainingData?.length || 0);
return data;
} catch (error) {
console.error('Force delete error:', error);
throw error;
}
};

155
src/lib/seedData.ts Normal file
View File

@ -0,0 +1,155 @@
import { installerService } from "./database";
import type { Database } from "@/integrations/supabase/types";
type InstallerInsert = Database['public']['Tables']['installers']['Insert'];
const sampleInstallers: InstallerInsert[] = [
{
name: "SolarTech Pro München",
company_name: "SolarTech Pro GmbH",
email: "info@solartech-pro.de",
phone: "+49 89 123 4567",
website: "www.solartech-pro.de",
address: "Maximilianstrasse 15, 80539 München",
location: "München, Bayern",
energy_type: "solar",
description: "Spezialisiert auf hochwertige Photovoltaik-Anlagen für Privathaushalte und Gewerbe. Mit über 15 Jahren Erfahrung sind wir Ihr vertrauensvoller Partner für nachhaltige Energielösungen.",
specialties: ["Photovoltaik", "Solarthermie", "Energiespeicher", "Smart Home Integration"],
certifications: ["IHK Zertifiziert", "VDE Prüfung", "TÜV Süd"],
experience_years: 15,
rating: 4.8,
review_count: 127,
verified: true,
status: "active",
service_areas: ["München", "Augsburg", "Ingolstadt", "Landshut"],
languages: ["Deutsch", "Englisch"],
latitude: 48.1351,
longitude: 11.5820
},
{
name: "WindEnergie Solutions Hamburg",
company_name: "WindEnergie Solutions GmbH",
email: "kontakt@windenergie-solutions.de",
phone: "+49 40 987 6543",
website: "www.windenergie-solutions.de",
address: "Hafenstraße 42, 20359 Hamburg",
location: "Hamburg, Hamburg",
energy_type: "wind",
description: "Experten für Windkraftanlagen und erneuerbare Energielösungen. Wir planen und installieren Windenergieanlagen für private und gewerbliche Kunden.",
specialties: ["Windkraft", "Kleinwindanlagen", "Planung", "Wartung"],
certifications: ["BWE Mitglied", "VDMA Zertifikat", "ISO 9001"],
experience_years: 12,
rating: 4.6,
review_count: 89,
verified: true,
status: "active",
service_areas: ["Hamburg", "Bremen", "Schleswig-Holstein", "Niedersachsen"],
languages: ["Deutsch", "Englisch", "Dänisch"],
latitude: 53.5511,
longitude: 9.9937
},
{
name: "Grüne Energie Partner Berlin",
company_name: "Grüne Energie Partner GmbH",
email: "info@gruene-energie-partner.de",
phone: "+49 30 555 1234",
website: "www.gruene-energie-partner.de",
address: "Unter den Linden 77, 10117 Berlin",
location: "Berlin, Berlin",
energy_type: "solar",
description: "Vollständige Lösungen für erneuerbare Energien mit Fokus auf Nachhaltigkeit. Wir bieten sowohl Solar- als auch Windenergie-Lösungen aus einer Hand.",
specialties: ["Solar", "Wind", "Energieberatung", "Förderung", "Hybrid-Systeme"],
certifications: ["DGS Zertifikat", "Handwerkskammer Berlin", "KfW Effizienzexperte"],
experience_years: 18,
rating: 4.9,
review_count: 203,
verified: true,
status: "active",
service_areas: ["Berlin", "Brandenburg", "Potsdam"],
languages: ["Deutsch", "Englisch", "Polnisch"],
latitude: 52.5200,
longitude: 13.4050
},
{
name: "EcoSolar Systems Stuttgart",
company_name: "EcoSolar Systems GmbH",
email: "service@ecosolar-systems.de",
phone: "+49 711 789 0123",
website: "www.ecosolar-systems.de",
address: "Königstraße 28, 70173 Stuttgart",
location: "Stuttgart, Baden-Württemberg",
energy_type: "solar",
description: "Innovative Solaranlagen mit modernster Technologie für maximale Effizienz. Spezialisiert auf High-End Photovoltaik-Systeme mit intelligenter Steuerung.",
specialties: ["Hochleistungs-Solar", "Smart Home Integration", "Wartung", "Monitoring"],
certifications: ["Solarfachbetrieb", "E-Handwerk Innungsbetrieb", "BSW Solar"],
experience_years: 14,
rating: 4.7,
review_count: 156,
verified: true,
status: "active",
service_areas: ["Stuttgart", "Karlsruhe", "Heilbronn", "Tübingen"],
languages: ["Deutsch", "Englisch"],
latitude: 48.7758,
longitude: 9.1829
},
{
name: "NordWind Energie Bremen",
company_name: "NordWind Energie GmbH",
email: "info@nordwind-energie.de",
phone: "+49 421 456 7890",
website: "www.nordwind-energie.de",
address: "Weserstraße 50, 28199 Bremen",
location: "Bremen, Bremen",
energy_type: "wind",
description: "Spezialisten für Windenergie-Projekte in Norddeutschland. Von der Planung bis zur Wartung - wir begleiten Ihr Windenergie-Projekt vom ersten Tag an.",
specialties: ["Windparks", "Offshore", "Wartung", "Repowering"],
certifications: ["BWE Vollmitglied", "GWO Zertifikat", "VDMA Wind Power"],
experience_years: 16,
rating: 4.5,
review_count: 67,
verified: true,
status: "active",
service_areas: ["Bremen", "Bremerhaven", "Oldenburg", "Cuxhaven"],
languages: ["Deutsch", "Englisch", "Niederländisch"],
latitude: 53.0793,
longitude: 8.8017
},
{
name: "Bayerische Solar Solutions",
company_name: "Bayerische Solar Solutions GmbH",
email: "kontakt@bayerische-solar.de",
phone: "+49 89 321 6547",
website: "www.bayerische-solar.de",
address: "Marienplatz 8, 80331 München",
location: "München, Bayern",
energy_type: "solar",
description: "Traditioneller Handwerksbetrieb mit moderner Solartechnik. Seit 20 Jahren verlässlicher Partner für Photovoltaik-Anlagen in ganz Bayern.",
specialties: ["Dachintegration", "Fassadenmontage", "Carports", "Gewerbeanlagen"],
certifications: ["Handwerkskammer München", "BSW Solar", "TÜV Bayern"],
experience_years: 20,
rating: 4.6,
review_count: 189,
verified: true,
status: "active",
service_areas: ["München", "Rosenheim", "Freising", "Garmisch-Partenkirchen"],
languages: ["Deutsch", "Bayerisch"],
latitude: 48.1372,
longitude: 11.5755
}
];
export const seedDatabase = async () => {
console.log('Starting to seed database with sample installer data...');
try {
for (const installer of sampleInstallers) {
await installerService.createInstaller(installer);
console.log(`Added installer: ${installer.name}`);
}
console.log('Database seeding completed successfully!');
} catch (error) {
console.error('Error seeding database:', error);
}
};
export { sampleInstallers };

View File

@ -1,6 +1,7 @@
import Header from "@/components/Header";
import HeroSection from "@/components/HeroSection";
import EnergyTypesSection from "@/components/EnergyTypesSection";
import CalculatorNavigation from "@/components/CalculatorNavigation";
import WhyChooseUsSection from "@/components/WhyChooseUsSection";
import Footer from "@/components/Footer";
@ -11,6 +12,7 @@ const Index = () => {
<main>
<HeroSection />
<EnergyTypesSection />
<CalculatorNavigation />
<WhyChooseUsSection />
</main>
<Footer />

View File

@ -0,0 +1,382 @@
import { useState, useEffect } from "react";
import { useSearchParams } from "react-router-dom";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Badge } from "@/components/ui/badge";
import { Search, MapPin, Star, Phone, Mail, Globe, Filter, AlertCircle } from "lucide-react";
import Header from "@/components/Header";
import { installerService, analyticsService } from "@/lib/database";
import { cleanAndReseedDatabase } from "@/lib/cleanDatabase";
import { debugDatabase, forceDeleteAll, testConnection } from "@/lib/debugDatabase";
import type { Database } from "@/integrations/supabase/types";
type Installer = Database['public']['Tables']['installers']['Row'];
const InstallateurFinden = () => {
const [searchParams, setSearchParams] = useSearchParams();
const [installers, setInstallers] = useState<Installer[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [searchTerm, setSearchTerm] = useState(searchParams.get("search") || "");
const [energyType, setEnergyType] = useState(searchParams.get("type") || "all");
const [location, setLocation] = useState(searchParams.get("location") || "");
// Load installers from database
const loadInstallers = async () => {
try {
setLoading(true);
setError(null);
const filters = {
energyType: energyType && energyType !== "all" ? energyType : undefined,
location: location || undefined,
searchTerm: searchTerm || undefined
};
const data = await installerService.getInstallers(filters);
setInstallers(data || []);
// Track search event
if (searchTerm || energyType !== "all" || location) {
analyticsService.trackEvent({
event_type: 'installer_search',
page_url: window.location.pathname,
event_data: filters,
user_agent: navigator.userAgent,
session_id: sessionStorage.getItem('session_id') || 'anonymous'
});
}
} catch (err) {
console.error('Error loading installers:', err);
setError('Fehler beim Laden der Installateure. Bitte versuchen Sie es später erneut.');
setInstallers([]);
} finally {
setLoading(false);
}
};
// Initial load
useEffect(() => {
loadInstallers();
}, []);
// Reload when filters change
useEffect(() => {
const timeoutId = setTimeout(() => {
loadInstallers();
}, 500); // Debounce search
return () => clearTimeout(timeoutId);
}, [searchTerm, energyType, location]);
// Update URL params when filters change
useEffect(() => {
const params = new URLSearchParams();
if (searchTerm) params.set("search", searchTerm);
if (energyType && energyType !== "all") params.set("type", energyType);
if (location) params.set("location", location);
setSearchParams(params);
}, [searchTerm, energyType, location, setSearchParams]);
const handleReset = () => {
setSearchTerm("");
setEnergyType("all");
setLocation("");
};
// Test connection
const handleTestConnection = async () => {
await testConnection();
};
// Debug database
const handleDebugDatabase = async () => {
await debugDatabase();
};
// Force delete all and reseed
const handleForceReseed = async () => {
try {
await forceDeleteAll();
await cleanAndReseedDatabase();
alert('Database forcefully reseeded!');
loadInstallers();
} catch (error) {
console.error('Error in force reseed:', error);
alert('Error in force reseed. Check console.');
}
};
// Clean and reseed with proper German data
const handleSeedDatabase = async () => {
try {
await cleanAndReseedDatabase();
alert('Deutsche Installateure erfolgreich geladen!');
loadInstallers(); // Reload the data
} catch (error) {
console.error('Error seeding database:', error);
alert('Fehler beim Laden der Installateure. Details in der Konsole.');
}
};
// Track contact clicks
const handleContactClick = async (installerId: string, contactType: string) => {
try {
await analyticsService.trackContactClick({
installer_id: installerId,
contact_type: contactType,
page_url: window.location.pathname,
user_agent: navigator.userAgent,
ip_address: await getClientIP()
});
} catch (error) {
console.error('Error tracking contact click:', error);
}
};
// Simple IP detection (fallback)
const getClientIP = async (): Promise<string> => {
try {
const response = await fetch('https://api.ipify.org?format=json');
const data = await response.json();
return data.ip;
} catch {
return 'unknown';
}
};
if (loading) {
return (
<div className="min-h-screen bg-background flex items-center justify-center">
<div className="text-center">
<div className="animate-spin rounded-full h-32 w-32 border-b-2 border-primary mx-auto"></div>
<p className="mt-4 text-muted-foreground">Installateure werden geladen...</p>
</div>
</div>
);
}
if (error) {
return (
<div className="min-h-screen bg-background">
<Header />
<div className="flex items-center justify-center py-32">
<div className="text-center max-w-md mx-auto px-4">
<AlertCircle className="w-16 h-16 text-destructive mx-auto mb-4" />
<h2 className="text-2xl font-bold text-foreground mb-2">Fehler beim Laden</h2>
<p className="text-muted-foreground mb-6">{error}</p>
<Button onClick={() => loadInstallers()} className="mb-4">
Erneut versuchen
</Button>
</div>
</div>
</div>
);
}
return (
<div className="min-h-screen bg-background">
{/* Header Navigation */}
<Header />
{/* Page Header */}
<div className="bg-gradient-to-r from-primary to-primary/80 text-white py-16 mt-16">
<div className="container mx-auto px-4">
<h1 className="text-4xl md:text-5xl font-bold mb-4">
Installateure Finden
</h1>
<p className="text-xl text-white/90 max-w-3xl">
Entdecken Sie qualifizierte Fachbetriebe für erneuerbare Energien in Ihrer Region
</p>
</div>
</div>
{/* Search and Filters */}
<div className="container mx-auto px-4 py-8">
<Card className="mb-8">
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Filter className="w-5 h-5" />
Suche & Filter
</CardTitle>
</CardHeader>
<CardContent>
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" />
<Input
placeholder="Installateur oder Spezialität suchen..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
<Select value={energyType} onValueChange={setEnergyType}>
<SelectTrigger>
<SelectValue placeholder="Energieart wählen" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">Alle Energiearten</SelectItem>
<SelectItem value="solar">Solar</SelectItem>
<SelectItem value="wind">Wind</SelectItem>
</SelectContent>
</Select>
<div className="relative">
<MapPin className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" />
<Input
placeholder="PLZ oder Stadt"
value={location}
onChange={(e) => setLocation(e.target.value)}
className="pl-10"
/>
</div>
<Button onClick={handleReset} variant="outline" className="w-full">
Filter zurücksetzen
</Button>
</div>
</CardContent>
</Card>
{/* Results */}
<div className="mb-4">
<p className="text-muted-foreground">
{installers.length} Installateur{installers.length !== 1 ? 'e' : ''} gefunden
</p>
</div>
{/* Installer Cards */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{installers.map((installer) => (
<Card key={installer.id} className="hover:shadow-lg transition-shadow">
<CardHeader>
<div className="flex justify-between items-start">
<div>
<CardTitle className="text-xl mb-2">{installer.name}</CardTitle>
<div className="flex items-center gap-2 text-muted-foreground mb-2">
<MapPin className="w-4 h-4" />
<span>{installer.location}</span>
</div>
<div className="flex items-center gap-2 mb-2">
<div className="flex items-center gap-1">
<Star className="w-4 h-4 fill-yellow-400 text-yellow-400" />
<span className="font-semibold">{installer.rating}</span>
<span className="text-muted-foreground">({installer.review_count || 0})</span>
</div>
<Badge variant="secondary">{installer.experience_years || 0} Jahre Erfahrung</Badge>
{installer.verified && (
<Badge variant="default" className="bg-green-100 text-green-800">
Verifiziert
</Badge>
)}
</div>
</div>
<div className="flex gap-2">
<Badge
variant={installer.energy_type === 'solar' ? 'default' : 'secondary'}
className={installer.energy_type === 'solar' ? 'bg-gradient-solar' : 'bg-gradient-wind'}
>
{installer.energy_type === 'solar' ? 'Solar' : 'Wind'}
</Badge>
</div>
</div>
</CardHeader>
<CardContent>
<p className="text-muted-foreground mb-4">{installer.description}</p>
<div className="mb-4">
<h4 className="font-semibold mb-2">Spezialitäten:</h4>
<div className="flex flex-wrap gap-2">
{installer.specialties?.map((specialty, index) => (
<Badge key={index} variant="outline" className="text-xs">
{specialty}
</Badge>
)) || (
<span className="text-muted-foreground text-sm">Keine Spezialitäten angegeben</span>
)}
</div>
</div>
{installer.certifications && installer.certifications.length > 0 && (
<div className="mb-4">
<h4 className="font-semibold mb-2">Zertifizierungen:</h4>
<div className="flex flex-wrap gap-2">
{installer.certifications.map((cert, index) => (
<Badge key={index} variant="outline" className="text-xs bg-blue-50 text-blue-700">
{cert}
</Badge>
))}
</div>
</div>
)}
<div className="flex flex-col sm:flex-row gap-2">
<Button
variant="outline"
size="sm"
className="flex-1"
onClick={() => {
handleContactClick(installer.id, 'phone');
window.location.href = `tel:${installer.phone}`;
}}
>
<Phone className="w-4 h-4 mr-2" />
Anrufen
</Button>
<Button
variant="outline"
size="sm"
className="flex-1"
onClick={() => {
handleContactClick(installer.id, 'email');
window.location.href = `mailto:${installer.email}`;
}}
>
<Mail className="w-4 h-4 mr-2" />
E-Mail
</Button>
{installer.website && (
<Button
variant="outline"
size="sm"
className="flex-1"
onClick={() => {
handleContactClick(installer.id, 'website');
window.open(`https://${installer.website}`, '_blank');
}}
>
<Globe className="w-4 h-4 mr-2" />
Website
</Button>
)}
</div>
</CardContent>
</Card>
))}
</div>
{installers.length === 0 && (
<Card className="text-center py-12">
<CardContent>
<p className="text-muted-foreground text-lg mb-4">
Keine Installateure gefunden, die Ihren Kriterien entsprechen.
</p>
<Button onClick={handleReset} variant="outline">
Alle Filter zurücksetzen
</Button>
</CardContent>
</Card>
)}
</div>
</div>
);
};
export default InstallateurFinden;

View File

@ -0,0 +1,364 @@
import { useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import { Phone, Mail, MapPin, Calendar, Clock, CheckCircle } from "lucide-react";
import Header from "@/components/Header";
const KostenloseBeratung = () => {
const [formData, setFormData] = useState({
firstName: "",
lastName: "",
email: "",
phone: "",
zipCode: "",
city: "",
energyType: "",
projectType: "",
description: "",
budget: "",
timeline: "",
contactPreference: "email",
newsletter: false,
privacyPolicy: false
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);
const handleInputChange = (field: string, value: string | boolean) => {
setFormData(prev => ({
...prev,
[field]: value
}));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 2000));
setIsSubmitting(false);
setIsSubmitted(true);
};
if (isSubmitted) {
return (
<div className="min-h-screen bg-background flex items-center justify-center">
<Card className="max-w-md text-center">
<CardContent className="pt-6">
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
<h2 className="text-2xl font-bold mb-2">Beratungsanfrage gesendet!</h2>
<p className="text-muted-foreground mb-4">
Vielen Dank für Ihr Interesse. Wir werden uns innerhalb von 24 Stunden bei Ihnen melden.
</p>
<Button onClick={() => window.location.href = "/"} className="w-full">
Zurück zur Startseite
</Button>
</CardContent>
</Card>
</div>
);
}
return (
<div className="min-h-screen bg-background">
{/* Header Navigation */}
<Header />
{/* Page Header */}
<div className="bg-gradient-to-r from-primary to-primary/80 text-white py-16 mt-16">
<div className="container mx-auto px-4">
<h1 className="text-4xl md:text-5xl font-bold mb-4">
Kostenlose Beratung
</h1>
<p className="text-xl text-white/90 max-w-3xl">
Lassen Sie sich von unseren Experten beraten und erhalten Sie ein maßgeschneidertes Angebot
</p>
</div>
</div>
<div className="container mx-auto px-4 py-8">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Form */}
<div className="lg:col-span-2">
<Card>
<CardHeader>
<CardTitle>Ihre Beratungsanfrage</CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Personal Information */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="firstName">Vorname *</Label>
<Input
id="firstName"
value={formData.firstName}
onChange={(e) => handleInputChange("firstName", e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="lastName">Nachname *</Label>
<Input
id="lastName"
value={formData.lastName}
onChange={(e) => handleInputChange("lastName", e.target.value)}
required
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="email">E-Mail *</Label>
<Input
id="email"
type="email"
value={formData.email}
onChange={(e) => handleInputChange("email", e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="phone">Telefon</Label>
<Input
id="phone"
type="tel"
value={formData.phone}
onChange={(e) => handleInputChange("phone", e.target.value)}
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="zipCode">PLZ *</Label>
<Input
id="zipCode"
value={formData.zipCode}
onChange={(e) => handleInputChange("zipCode", e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="city">Stadt *</Label>
<Input
id="city"
value={formData.city}
onChange={(e) => handleInputChange("city", e.target.value)}
required
/>
</div>
</div>
{/* Project Information */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="energyType">Energieart *</Label>
<Select value={formData.energyType} onValueChange={(value) => handleInputChange("energyType", value)}>
<SelectTrigger>
<SelectValue placeholder="Energieart wählen" />
</SelectTrigger>
<SelectContent>
<SelectItem value="solar">Solar</SelectItem>
<SelectItem value="wind">Wind</SelectItem>
<SelectItem value="both">Beide</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="projectType">Projekttyp *</Label>
<Select value={formData.projectType} onValueChange={(value) => handleInputChange("projectType", value)}>
<SelectTrigger>
<SelectValue placeholder="Projekttyp wählen" />
</SelectTrigger>
<SelectContent>
<SelectItem value="private">Privathaushalt</SelectItem>
<SelectItem value="commercial">Gewerbe</SelectItem>
<SelectItem value="industrial">Industrie</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="budget">Budget</Label>
<Select value={formData.budget} onValueChange={(value) => handleInputChange("budget", value)}>
<SelectTrigger>
<SelectValue placeholder="Budget wählen" />
</SelectTrigger>
<SelectContent>
<SelectItem value="under-10k">Unter 10.000 </SelectItem>
<SelectItem value="10k-25k">10.000 - 25.000 </SelectItem>
<SelectItem value="25k-50k">25.000 - 50.000 </SelectItem>
<SelectItem value="over-50k">Über 50.000 </SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="timeline">Zeitplan</Label>
<Select value={formData.timeline} onValueChange={(value) => handleInputChange("timeline", value)}>
<SelectTrigger>
<SelectValue placeholder="Zeitplan wählen" />
</SelectTrigger>
<SelectContent>
<SelectItem value="immediate">Sofort</SelectItem>
<SelectItem value="3months">Innerhalb 3 Monate</SelectItem>
<SelectItem value="6months">Innerhalb 6 Monate</SelectItem>
<SelectItem value="1year">Innerhalb 1 Jahr</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="description">Projektbeschreibung</Label>
<Textarea
id="description"
placeholder="Beschreiben Sie Ihr Projekt, Ihre Anforderungen und Ziele..."
value={formData.description}
onChange={(e) => handleInputChange("description", e.target.value)}
rows={4}
/>
</div>
<div className="space-y-2">
<Label>Kontaktpräferenz</Label>
<div className="flex gap-4">
<div className="flex items-center space-x-2">
<input
type="radio"
id="email-pref"
name="contactPreference"
value="email"
checked={formData.contactPreference === "email"}
onChange={(e) => handleInputChange("contactPreference", e.target.value)}
/>
<Label htmlFor="email-pref">E-Mail</Label>
</div>
<div className="flex items-center space-x-2">
<input
type="radio"
id="phone-pref"
name="contactPreference"
value="phone"
checked={formData.contactPreference === "phone"}
onChange={(e) => handleInputChange("contactPreference", e.target.value)}
/>
<Label htmlFor="phone-pref">Telefon</Label>
</div>
</div>
</div>
<div className="space-y-4">
<div className="flex items-center space-x-2">
<Checkbox
id="newsletter"
checked={formData.newsletter}
onCheckedChange={(checked) => handleInputChange("newsletter", checked as boolean)}
/>
<Label htmlFor="newsletter">
Ich möchte den Newsletter mit Tipps zu erneuerbaren Energien erhalten
</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="privacyPolicy"
checked={formData.privacyPolicy}
onCheckedChange={(checked) => handleInputChange("privacyPolicy", checked as boolean)}
required
/>
<Label htmlFor="privacyPolicy">
Ich akzeptiere die <a href="/datenschutz" className="text-primary hover:underline">Datenschutzerklärung</a> *
</Label>
</div>
</div>
<Button type="submit" className="w-full" disabled={isSubmitting}>
{isSubmitting ? "Wird gesendet..." : "Beratungsanfrage senden"}
</Button>
</form>
</CardContent>
</Card>
</div>
{/* Sidebar */}
<div className="space-y-6">
{/* Benefits */}
<Card>
<CardHeader>
<CardTitle>Ihre Vorteile</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-start gap-3">
<CheckCircle className="w-5 h-5 text-green-500 mt-0.5 flex-shrink-0" />
<div>
<h4 className="font-semibold">Kostenlose Beratung</h4>
<p className="text-sm text-muted-foreground">Unverbindliche Erstberatung ohne Kosten</p>
</div>
</div>
<div className="flex items-start gap-3">
<CheckCircle className="w-5 h-5 text-green-500 mt-0.5 flex-shrink-0" />
<div>
<h4 className="font-semibold">Expertenwissen</h4>
<p className="text-sm text-muted-foreground">Beratung durch zertifizierte Fachleute</p>
</div>
</div>
<div className="flex items-start gap-3">
<CheckCircle className="w-5 h-5 text-green-500 mt-0.5 flex-shrink-0" />
<div>
<h4 className="font-semibold">Maßgeschneiderte Lösungen</h4>
<p className="text-sm text-muted-foreground">Individuelle Beratung für Ihr Projekt</p>
</div>
</div>
<div className="flex items-start gap-3">
<CheckCircle className="w-5 h-5 text-green-500 mt-0.5 flex-shrink-0" />
<div>
<h4 className="font-semibold">Förderung & Finanzierung</h4>
<p className="text-sm text-muted-foreground">Informationen zu staatlichen Förderungen</p>
</div>
</div>
</CardContent>
</Card>
{/* Contact Info */}
<Card>
<CardHeader>
<CardTitle>Kontakt</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center gap-3">
<Phone className="w-5 h-5 text-primary" />
<span>+49 (0) 800 123 4567</span>
</div>
<div className="flex items-center gap-3">
<Mail className="w-5 h-5 text-primary" />
<span>beratung@energieprofis.de</span>
</div>
<div className="flex items-center gap-3">
<MapPin className="w-5 h-5 text-primary" />
<span>München, Deutschland</span>
</div>
<div className="flex items-center gap-3">
<Clock className="w-5 h-5 text-primary" />
<span>Mo-Fr: 8:00 - 18:00</span>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</div>
);
};
export default KostenloseBeratung;

View File

@ -1,5 +1,6 @@
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import SolarCalculator from "@/components/SolarCalculator";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Sun, Zap, TrendingUp, Shield, ArrowRight } from "lucide-react";
@ -62,11 +63,6 @@ const Solar = () => {
Solar-Installateur Finden
</Link>
</Button>
<Button variant="solar-outline" size="xl" className="border-white/30 text-white hover:bg-white hover:text-solar">
<Link to="/kostenlose-beratung?type=solar">
Kostenlose Beratung
</Link>
</Button>
</div>
</div>
</div>
@ -106,6 +102,9 @@ const Solar = () => {
</div>
</section>
{/* Solar Calculator Section */}
<SolarCalculator />
{/* CTA Section */}
<section className="py-20 bg-gradient-solar text-white">
<div className="container mx-auto px-4 text-center">
@ -118,8 +117,8 @@ const Solar = () => {
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Button variant="hero" size="xl" className="bg-white text-solar hover:bg-white/90" asChild>
<Link to="/kostenlose-beratung?type=solar">
Jetzt kostenlose Beratung anfordern
<Link to="/installateur-finden?type=solar">
Solar-Installateure finden
<ArrowRight className="w-5 h-5 ml-2" />
</Link>
</Button>

View File

@ -0,0 +1,446 @@
import { useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox";
import { Building2, MapPin, Phone, Mail, Globe, CheckCircle, Users, Award, Clock } from "lucide-react";
import Header from "@/components/Header";
const UnternehmenListen = () => {
const [formData, setFormData] = useState({
companyName: "",
contactPerson: "",
email: "",
phone: "",
website: "",
zipCode: "",
city: "",
energyTypes: [] as string[],
services: [] as string[],
description: "",
experience: "",
certifications: [] as string[],
coverageArea: "",
contactPreference: "email",
newsletter: false,
privacyPolicy: false
});
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);
const handleInputChange = (field: string, value: string | boolean | string[]) => {
setFormData(prev => ({
...prev,
[field]: value
}));
};
const handleEnergyTypeChange = (type: string, checked: boolean) => {
if (checked) {
setFormData(prev => ({
...prev,
energyTypes: [...prev.energyTypes, type]
}));
} else {
setFormData(prev => ({
...prev,
energyTypes: prev.energyTypes.filter(t => t !== type)
}));
}
};
const handleServiceChange = (service: string, checked: boolean) => {
if (checked) {
setFormData(prev => ({
...prev,
services: [...prev.services, service]
}));
} else {
setFormData(prev => ({
...prev,
services: prev.services.filter(s => s !== service)
}));
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setIsSubmitting(true);
// Simulate API call
await new Promise(resolve => setTimeout(resolve, 2000));
setIsSubmitting(false);
setIsSubmitted(true);
};
if (isSubmitted) {
return (
<div className="min-h-screen bg-background flex items-center justify-center">
<Card className="max-w-md text-center">
<CardContent className="pt-6">
<CheckCircle className="w-16 h-16 text-green-500 mx-auto mb-4" />
<h2 className="text-2xl font-bold mb-2">Anmeldung erfolgreich!</h2>
<p className="text-muted-foreground mb-4">
Vielen Dank für Ihre Anmeldung. Wir werden Ihr Unternehmen innerhalb von 48 Stunden prüfen und freischalten.
</p>
<Button onClick={() => window.location.href = "/"} className="w-full">
Zurück zur Startseite
</Button>
</CardContent>
</Card>
</div>
);
}
return (
<div className="min-h-screen bg-background">
{/* Header Navigation */}
<Header />
{/* Page Header */}
<div className="bg-gradient-to-r from-primary to-primary/80 text-white py-16 mt-16">
<div className="container mx-auto px-4">
<h1 className="text-4xl md:text-5xl font-bold mb-4">
Unternehmen Listen
</h1>
<p className="text-xl text-white/90 max-w-3xl">
Werden Sie Teil unseres Netzwerks und erreichen Sie neue Kunden für erneuerbare Energien
</p>
</div>
</div>
<div className="container mx-auto px-4 py-8">
<div className="grid grid-cols-1 lg:grid-cols-3 gap-8">
{/* Form */}
<div className="lg:col-span-2">
<Card>
<CardHeader>
<CardTitle>Unternehmensanmeldung</CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Company Information */}
<div className="space-y-2">
<Label htmlFor="companyName">Firmenname *</Label>
<Input
id="companyName"
value={formData.companyName}
onChange={(e) => handleInputChange("companyName", e.target.value)}
required
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="contactPerson">Ansprechpartner *</Label>
<Input
id="contactPerson"
value={formData.contactPerson}
onChange={(e) => handleInputChange("contactPerson", e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="email">E-Mail *</Label>
<Input
id="email"
type="email"
value={formData.email}
onChange={(e) => handleInputChange("email", e.target.value)}
required
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="phone">Telefon *</Label>
<Input
id="phone"
type="tel"
value={formData.phone}
onChange={(e) => handleInputChange("phone", e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="website">Website</Label>
<Input
id="website"
type="url"
value={formData.website}
onChange={(e) => handleInputChange("website", e.target.value)}
placeholder="https://www.example.de"
/>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="zipCode">PLZ *</Label>
<Input
id="zipCode"
value={formData.zipCode}
onChange={(e) => handleInputChange("zipCode", e.target.value)}
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="city">Stadt *</Label>
<Input
id="city"
value={formData.city}
onChange={(e) => handleInputChange("city", e.target.value)}
required
/>
</div>
</div>
{/* Energy Types */}
<div className="space-y-2">
<Label>Energiearten *</Label>
<div className="grid grid-cols-2 gap-4">
<div className="flex items-center space-x-2">
<Checkbox
id="solar"
checked={formData.energyTypes.includes("solar")}
onCheckedChange={(checked) => handleEnergyTypeChange("solar", checked as boolean)}
/>
<Label htmlFor="solar">Solar</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="wind"
checked={formData.energyTypes.includes("wind")}
onCheckedChange={(checked) => handleEnergyTypeChange("wind", checked as boolean)}
/>
<Label htmlFor="wind">Wind</Label>
</div>
</div>
</div>
{/* Services */}
<div className="space-y-2">
<Label>Angebotene Leistungen *</Label>
<div className="grid grid-cols-2 gap-4">
<div className="flex items-center space-x-2">
<Checkbox
id="planning"
checked={formData.services.includes("planning")}
onCheckedChange={(checked) => handleServiceChange("planning", checked as boolean)}
/>
<Label htmlFor="planning">Planung & Beratung</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="installation"
checked={formData.services.includes("installation")}
onCheckedChange={(checked) => handleServiceChange("installation", checked as boolean)}
/>
<Label htmlFor="installation">Installation</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="maintenance"
checked={formData.services.includes("maintenance")}
onCheckedChange={(checked) => handleServiceChange("maintenance", checked as boolean)}
/>
<Label htmlFor="maintenance">Wartung & Service</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="financing"
checked={formData.services.includes("financing")}
onCheckedChange={(checked) => handleServiceChange("financing", checked as boolean)}
/>
<Label htmlFor="financing">Finanzierung</Label>
</div>
</div>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="experience">Jahre Erfahrung *</Label>
<Select value={formData.experience} onValueChange={(value) => handleInputChange("experience", value)}>
<SelectTrigger>
<SelectValue placeholder="Erfahrung wählen" />
</SelectTrigger>
<SelectContent>
<SelectItem value="0-2">0-2 Jahre</SelectItem>
<SelectItem value="3-5">3-5 Jahre</SelectItem>
<SelectItem value="6-10">6-10 Jahre</SelectItem>
<SelectItem value="10+">Über 10 Jahre</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="coverageArea">Einzugsgebiet</Label>
<Input
id="coverageArea"
value={formData.coverageArea}
onChange={(e) => handleInputChange("coverageArea", e.target.value)}
placeholder="z.B. Bayern, 50km Umkreis"
/>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="description">Unternehmensbeschreibung *</Label>
<Textarea
id="description"
placeholder="Beschreiben Sie Ihr Unternehmen, Ihre Spezialitäten und warum Kunden Sie wählen sollten..."
value={formData.description}
onChange={(e) => handleInputChange("description", e.target.value)}
rows={4}
required
/>
</div>
<div className="space-y-2">
<Label>Kontaktpräferenz</Label>
<div className="flex gap-4">
<div className="flex items-center space-x-2">
<input
type="radio"
id="email-pref"
name="contactPreference"
value="email"
checked={formData.contactPreference === "email"}
onChange={(e) => handleInputChange("contactPreference", e.target.value)}
/>
<Label htmlFor="email-pref">E-Mail</Label>
</div>
<div className="flex items-center space-x-2">
<input
type="radio"
id="phone-pref"
name="contactPreference"
value="phone"
checked={formData.contactPreference === "phone"}
onChange={(e) => handleInputChange("contactPreference", e.target.value)}
/>
<Label htmlFor="phone-pref">Telefon</Label>
</div>
</div>
</div>
<div className="space-y-4">
<div className="flex items-center space-x-2">
<Checkbox
id="newsletter"
checked={formData.newsletter}
onCheckedChange={(checked) => handleInputChange("newsletter", checked as boolean)}
/>
<Label htmlFor="newsletter">
Ich möchte über neue Funktionen und Marketing-Möglichkeiten informiert werden
</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="privacyPolicy"
checked={formData.privacyPolicy}
onCheckedChange={(checked) => handleInputChange("privacyPolicy", checked as boolean)}
required
/>
<Label htmlFor="privacyPolicy">
Ich akzeptiere die <a href="/datenschutz" className="text-primary hover:underline">Datenschutzerklärung</a> *
</Label>
</div>
</div>
<Button type="submit" className="w-full" disabled={isSubmitting}>
{isSubmitting ? "Wird gesendet..." : "Unternehmen anmelden"}
</Button>
</form>
</CardContent>
</Card>
</div>
{/* Sidebar */}
<div className="space-y-6">
{/* Benefits */}
<Card>
<CardHeader>
<CardTitle>Ihre Vorteile</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-start gap-3">
<Users className="w-5 h-5 text-primary mt-0.5 flex-shrink-0" />
<div>
<h4 className="font-semibold">Neue Kunden gewinnen</h4>
<p className="text-sm text-muted-foreground">Erreichen Sie potenzielle Kunden in Ihrer Region</p>
</div>
</div>
<div className="flex items-start gap-3">
<Award className="w-5 h-5 text-primary mt-0.5 flex-shrink-0" />
<div>
<h4 className="font-semibold">Vertrauen aufbauen</h4>
<p className="text-sm text-muted-foreground">Präsentieren Sie sich als verifizierter Experte</p>
</div>
</div>
<div className="flex items-start gap-3">
<Clock className="w-5 h-5 text-primary mt-0.5 flex-shrink-0" />
<div>
<h4 className="font-semibold">Zeit sparen</h4>
<p className="text-sm text-muted-foreground">Kunden finden Sie automatisch über unsere Plattform</p>
</div>
</div>
<div className="flex items-start gap-3">
<Building2 className="w-5 h-5 text-primary mt-0.5 flex-shrink-0" />
<div>
<h4 className="font-semibold">Professioneller Auftritt</h4>
<p className="text-sm text-muted-foreground">Präsentieren Sie Ihr Unternehmen professionell</p>
</div>
</div>
</CardContent>
</Card>
{/* Process */}
<Card>
<CardHeader>
<CardTitle>Ablauf</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-start gap-3">
<div className="w-6 h-6 bg-primary text-white rounded-full flex items-center justify-center text-sm font-bold flex-shrink-0">1</div>
<div>
<h4 className="font-semibold">Anmeldung</h4>
<p className="text-sm text-muted-foreground">Füllen Sie das Formular aus</p>
</div>
</div>
<div className="flex items-start gap-3">
<div className="w-6 h-6 bg-primary text-white rounded-full flex items-center justify-center text-sm font-bold flex-shrink-0">2</div>
<div>
<h4 className="font-semibold">Prüfung</h4>
<p className="text-sm text-muted-foreground">Wir prüfen Ihre Angaben (48h)</p>
</div>
</div>
<div className="flex items-start gap-3">
<div className="w-6 h-6 bg-primary text-white rounded-full flex items-center justify-center text-sm font-bold flex-shrink-0">3</div>
<div>
<h4 className="font-semibold">Freischaltung</h4>
<p className="text-sm text-muted-foreground">Ihr Profil wird veröffentlicht</p>
</div>
</div>
<div className="flex items-start gap-3">
<div className="w-6 h-6 bg-primary text-white rounded-full flex items-center justify-center text-sm font-bold flex-shrink-0">4</div>
<div>
<h4 className="font-semibold">Kundenkontakte</h4>
<p className="text-sm text-muted-foreground">Erhalten Sie Anfragen von Kunden</p>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</div>
);
};
export default UnternehmenListen;

View File

@ -1,5 +1,6 @@
import Header from "@/components/Header";
import Footer from "@/components/Footer";
import WindCalculator from "@/components/WindCalculator";
import { Button } from "@/components/ui/button";
import { Card, CardContent } from "@/components/ui/card";
import { Wind, Zap, TrendingUp, Shield, ArrowRight } from "lucide-react";
@ -62,11 +63,6 @@ const WindPage = () => {
Wind-Installateur Finden
</Link>
</Button>
<Button variant="wind-outline" size="xl" className="border-white/30 text-white hover:bg-white hover:text-wind">
<Link to="/kostenlose-beratung?type=wind">
Kostenlose Beratung
</Link>
</Button>
</div>
</div>
</div>
@ -106,6 +102,9 @@ const WindPage = () => {
</div>
</section>
{/* Wind Calculator Section */}
<WindCalculator />
{/* CTA Section */}
<section className="py-20 bg-gradient-wind text-white">
<div className="container mx-auto px-4 text-center">
@ -118,8 +117,8 @@ const WindPage = () => {
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Button variant="hero" size="xl" className="bg-white text-wind hover:bg-white/90" asChild>
<Link to="/kostenlose-beratung?type=wind">
Jetzt kostenlose Beratung anfordern
<Link to="/installateur-finden?type=wind">
Wind-Installateure finden
<ArrowRight className="w-5 h-5 ml-2" />
</Link>
</Button>

View File

@ -65,18 +65,6 @@ export default {
light: 'hsl(var(--wind-light))',
dark: 'hsl(var(--wind-dark))'
},
geo: {
DEFAULT: 'hsl(var(--geo-primary))',
secondary: 'hsl(var(--geo-secondary))',
light: 'hsl(var(--geo-light))',
dark: 'hsl(var(--geo-dark))'
},
battery: {
DEFAULT: 'hsl(var(--battery-primary))',
secondary: 'hsl(var(--battery-secondary))',
light: 'hsl(var(--battery-light))',
dark: 'hsl(var(--battery-dark))'
},
sidebar: {
DEFAULT: 'hsl(var(--sidebar-background))',
foreground: 'hsl(var(--sidebar-foreground))',
@ -91,15 +79,11 @@ export default {
backgroundImage: {
'gradient-solar': 'var(--gradient-solar)',
'gradient-wind': 'var(--gradient-wind)',
'gradient-geo': 'var(--gradient-geo)',
'gradient-battery': 'var(--gradient-battery)',
'gradient-hero': 'var(--gradient-hero)'
},
boxShadow: {
'solar': 'var(--shadow-solar)',
'wind': 'var(--shadow-wind)',
'geo': 'var(--shadow-geo)',
'battery': 'var(--shadow-battery)'
'wind': 'var(--shadow-wind)'
},
transitionTimingFunction: {
'smooth': 'cubic-bezier(0.4, 0, 0.2, 1)',