Database eingefügt
This commit is contained in:
parent
1307a969d7
commit
d71aaebe2a
|
|
@ -0,0 +1,9 @@
|
|||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(npm run dev:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
|
|
@ -22,3 +22,5 @@ dist-ssr
|
|||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
.env
|
||||
|
|
@ -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
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
Binary file not shown.
|
After Width: | Height: | Size: 5.6 MiB |
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -8,7 +8,7 @@ interface EnergyTypeCardProps {
|
|||
description: string;
|
||||
image: string;
|
||||
gradient: string;
|
||||
buttonVariant: "solar" | "wind" | "geo" | "battery";
|
||||
buttonVariant: "solar" | "wind";
|
||||
href: string;
|
||||
features: string[];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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} />
|
||||
))}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 */}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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); }
|
||||
}
|
||||
}
|
||||
|
|
@ -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"],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
@ -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
|
||||
};
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
|
@ -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 };
|
||||
|
|
@ -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 />
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)',
|
||||
|
|
|
|||
Loading…
Reference in New Issue