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
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.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",
|
"version": "5.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
|
||||||
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
|
"integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=10"
|
||||||
|
|
@ -847,7 +846,6 @@
|
||||||
"version": "8.0.2",
|
"version": "8.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
|
||||||
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
|
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"string-width": "^5.1.2",
|
"string-width": "^5.1.2",
|
||||||
|
|
@ -865,7 +863,6 @@
|
||||||
"version": "0.3.5",
|
"version": "0.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz",
|
||||||
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
|
"integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/set-array": "^1.2.1",
|
"@jridgewell/set-array": "^1.2.1",
|
||||||
|
|
@ -880,7 +877,6 @@
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
|
||||||
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
|
|
@ -890,7 +886,6 @@
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
|
||||||
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
"integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.0.0"
|
"node": ">=6.0.0"
|
||||||
|
|
@ -900,14 +895,12 @@
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
|
||||||
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@jridgewell/trace-mapping": {
|
"node_modules/@jridgewell/trace-mapping": {
|
||||||
"version": "0.3.25",
|
"version": "0.3.25",
|
||||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
|
||||||
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
"integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/resolve-uri": "^3.1.0",
|
"@jridgewell/resolve-uri": "^3.1.0",
|
||||||
|
|
@ -918,7 +911,6 @@
|
||||||
"version": "2.1.5",
|
"version": "2.1.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||||
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nodelib/fs.stat": "2.0.5",
|
"@nodelib/fs.stat": "2.0.5",
|
||||||
|
|
@ -932,7 +924,6 @@
|
||||||
"version": "2.0.5",
|
"version": "2.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
|
||||||
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
|
|
@ -942,7 +933,6 @@
|
||||||
"version": "1.2.8",
|
"version": "1.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
|
||||||
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nodelib/fs.scandir": "2.1.5",
|
"@nodelib/fs.scandir": "2.1.5",
|
||||||
|
|
@ -956,7 +946,6 @@
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
|
||||||
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
|
|
@ -3008,14 +2997,14 @@
|
||||||
"version": "15.7.13",
|
"version": "15.7.13",
|
||||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
|
||||||
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
|
"integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "18.3.23",
|
"version": "18.3.23",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.23.tgz",
|
||||||
"integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
|
"integrity": "sha512-/LDXMQh55EzZQ0uVAZmKKhfENivEvWz6E+EYzh+/MCjMhNsotd+ZHhBGIjFDTi6+fz0OhQQQLbTgdQIxxCsC0w==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/prop-types": "*",
|
"@types/prop-types": "*",
|
||||||
|
|
@ -3026,7 +3015,7 @@
|
||||||
"version": "18.3.7",
|
"version": "18.3.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
|
||||||
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
"integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
|
||||||
"dev": true,
|
"devOptional": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@types/react": "^18.0.0"
|
"@types/react": "^18.0.0"
|
||||||
|
|
@ -3357,7 +3346,6 @@
|
||||||
"version": "6.1.0",
|
"version": "6.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz",
|
||||||
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
"integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
|
|
@ -3370,7 +3358,6 @@
|
||||||
"version": "4.3.0",
|
"version": "4.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-convert": "^2.0.1"
|
"color-convert": "^2.0.1"
|
||||||
|
|
@ -3386,14 +3373,12 @@
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
|
||||||
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
|
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/anymatch": {
|
"node_modules/anymatch": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
|
||||||
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"normalize-path": "^3.0.0",
|
"normalize-path": "^3.0.0",
|
||||||
|
|
@ -3407,7 +3392,6 @@
|
||||||
"version": "5.0.2",
|
"version": "5.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
|
||||||
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/argparse": {
|
"node_modules/argparse": {
|
||||||
|
|
@ -3471,14 +3455,12 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/binary-extensions": {
|
"node_modules/binary-extensions": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||||
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
|
|
@ -3502,7 +3484,6 @@
|
||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fill-range": "^7.1.1"
|
"fill-range": "^7.1.1"
|
||||||
|
|
@ -3558,7 +3539,6 @@
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
|
||||||
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
|
|
@ -3606,7 +3586,6 @@
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||||
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"anymatch": "~3.1.2",
|
"anymatch": "~3.1.2",
|
||||||
|
|
@ -3631,7 +3610,6 @@
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-glob": "^4.0.1"
|
"is-glob": "^4.0.1"
|
||||||
|
|
@ -3680,7 +3658,6 @@
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"color-name": "~1.1.4"
|
"color-name": "~1.1.4"
|
||||||
|
|
@ -3693,14 +3670,12 @@
|
||||||
"version": "1.1.4",
|
"version": "1.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/commander": {
|
"node_modules/commander": {
|
||||||
"version": "4.1.1",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
|
||||||
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
|
|
@ -3717,7 +3692,6 @@
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"path-key": "^3.1.0",
|
"path-key": "^3.1.0",
|
||||||
"shebang-command": "^2.0.0",
|
"shebang-command": "^2.0.0",
|
||||||
|
|
@ -3731,7 +3705,6 @@
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
|
||||||
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
"integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"cssesc": "bin/cssesc"
|
"cssesc": "bin/cssesc"
|
||||||
|
|
@ -3918,14 +3891,12 @@
|
||||||
"version": "1.2.2",
|
"version": "1.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
|
||||||
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/dlv": {
|
"node_modules/dlv": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
|
||||||
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
"integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/dom-helpers": {
|
"node_modules/dom-helpers": {
|
||||||
|
|
@ -3942,7 +3913,6 @@
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
|
||||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/electron-to-chromium": {
|
"node_modules/electron-to-chromium": {
|
||||||
|
|
@ -3984,7 +3954,6 @@
|
||||||
"version": "9.2.2",
|
"version": "9.2.2",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
|
||||||
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
|
|
@ -4263,7 +4232,6 @@
|
||||||
"version": "3.3.2",
|
"version": "3.3.2",
|
||||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||||
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
|
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nodelib/fs.stat": "^2.0.2",
|
"@nodelib/fs.stat": "^2.0.2",
|
||||||
|
|
@ -4280,7 +4248,6 @@
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-glob": "^4.0.1"
|
"is-glob": "^4.0.1"
|
||||||
|
|
@ -4307,7 +4274,6 @@
|
||||||
"version": "1.17.1",
|
"version": "1.17.1",
|
||||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
|
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
|
||||||
"integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
|
"integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"reusify": "^1.0.4"
|
"reusify": "^1.0.4"
|
||||||
|
|
@ -4330,7 +4296,6 @@
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
|
|
@ -4381,7 +4346,6 @@
|
||||||
"version": "3.3.0",
|
"version": "3.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz",
|
||||||
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
|
"integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-spawn": "^7.0.0",
|
"cross-spawn": "^7.0.0",
|
||||||
|
|
@ -4412,7 +4376,6 @@
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||||
"dev": true,
|
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
|
@ -4427,7 +4390,6 @@
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
|
|
@ -4446,7 +4408,6 @@
|
||||||
"version": "10.4.5",
|
"version": "10.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
|
||||||
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"foreground-child": "^3.1.0",
|
"foreground-child": "^3.1.0",
|
||||||
|
|
@ -4467,7 +4428,6 @@
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
|
||||||
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
"integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-glob": "^4.0.3"
|
"is-glob": "^4.0.3"
|
||||||
|
|
@ -4480,7 +4440,6 @@
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0"
|
"balanced-match": "^1.0.0"
|
||||||
|
|
@ -4490,7 +4449,6 @@
|
||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^2.0.1"
|
"brace-expansion": "^2.0.1"
|
||||||
|
|
@ -4536,7 +4494,6 @@
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
|
|
@ -4605,7 +4562,6 @@
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
|
||||||
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"binary-extensions": "^2.0.0"
|
"binary-extensions": "^2.0.0"
|
||||||
|
|
@ -4618,7 +4574,6 @@
|
||||||
"version": "2.15.1",
|
"version": "2.15.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz",
|
||||||
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
|
"integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"hasown": "^2.0.2"
|
"hasown": "^2.0.2"
|
||||||
|
|
@ -4634,7 +4589,6 @@
|
||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
|
@ -4644,7 +4598,6 @@
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
|
|
@ -4654,7 +4607,6 @@
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-extglob": "^2.1.1"
|
"is-extglob": "^2.1.1"
|
||||||
|
|
@ -4667,7 +4619,6 @@
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
|
|
@ -4677,14 +4628,12 @@
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/jackspeak": {
|
"node_modules/jackspeak": {
|
||||||
"version": "3.4.3",
|
"version": "3.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
|
||||||
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
|
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
|
||||||
"dev": true,
|
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@isaacs/cliui": "^8.0.2"
|
"@isaacs/cliui": "^8.0.2"
|
||||||
|
|
@ -4700,7 +4649,6 @@
|
||||||
"version": "1.21.6",
|
"version": "1.21.6",
|
||||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
|
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
|
||||||
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
|
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bin": {
|
"bin": {
|
||||||
"jiti": "bin/jiti.js"
|
"jiti": "bin/jiti.js"
|
||||||
|
|
@ -4774,7 +4722,6 @@
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||||
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
|
|
@ -4787,7 +4734,6 @@
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
|
||||||
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/locate-path": {
|
"node_modules/locate-path": {
|
||||||
|
|
@ -5297,7 +5243,6 @@
|
||||||
"version": "10.4.3",
|
"version": "10.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
|
||||||
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/lucide-react": {
|
"node_modules/lucide-react": {
|
||||||
|
|
@ -5322,7 +5267,6 @@
|
||||||
"version": "1.4.1",
|
"version": "1.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||||
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
|
|
@ -5332,7 +5276,6 @@
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"braces": "^3.0.3",
|
"braces": "^3.0.3",
|
||||||
|
|
@ -5359,7 +5302,6 @@
|
||||||
"version": "7.1.2",
|
"version": "7.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz",
|
||||||
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
"integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16 || 14 >=14.17"
|
"node": ">=16 || 14 >=14.17"
|
||||||
|
|
@ -5376,7 +5318,6 @@
|
||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||||
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"any-promise": "^1.0.0",
|
"any-promise": "^1.0.0",
|
||||||
|
|
@ -5388,7 +5329,6 @@
|
||||||
"version": "3.3.11",
|
"version": "3.3.11",
|
||||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
|
||||||
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
|
|
@ -5431,7 +5371,6 @@
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
|
||||||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
|
@ -5460,7 +5399,6 @@
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
||||||
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
|
|
@ -5520,7 +5458,6 @@
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
|
||||||
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
|
||||||
"dev": true,
|
|
||||||
"license": "BlueOak-1.0.0"
|
"license": "BlueOak-1.0.0"
|
||||||
},
|
},
|
||||||
"node_modules/parent-module": {
|
"node_modules/parent-module": {
|
||||||
|
|
@ -5550,7 +5487,6 @@
|
||||||
"version": "3.1.1",
|
"version": "3.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
|
|
@ -5560,14 +5496,12 @@
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
|
||||||
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/path-scurry": {
|
"node_modules/path-scurry": {
|
||||||
"version": "1.11.1",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
|
||||||
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
|
||||||
"dev": true,
|
|
||||||
"license": "BlueOak-1.0.0",
|
"license": "BlueOak-1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"lru-cache": "^10.2.0",
|
"lru-cache": "^10.2.0",
|
||||||
|
|
@ -5584,14 +5518,12 @@
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/picomatch": {
|
"node_modules/picomatch": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
|
|
@ -5604,7 +5536,6 @@
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
|
||||||
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
|
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
|
@ -5614,7 +5545,6 @@
|
||||||
"version": "4.0.6",
|
"version": "4.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz",
|
||||||
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
|
"integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 6"
|
"node": ">= 6"
|
||||||
|
|
@ -5624,7 +5554,6 @@
|
||||||
"version": "8.5.6",
|
"version": "8.5.6",
|
||||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
|
||||||
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
|
@ -5653,7 +5582,6 @@
|
||||||
"version": "15.1.0",
|
"version": "15.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
|
||||||
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
|
"integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"postcss-value-parser": "^4.0.0",
|
"postcss-value-parser": "^4.0.0",
|
||||||
|
|
@ -5671,7 +5599,6 @@
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz",
|
||||||
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
|
"integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"camelcase-css": "^2.0.1"
|
"camelcase-css": "^2.0.1"
|
||||||
|
|
@ -5691,7 +5618,6 @@
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz",
|
||||||
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
|
"integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
|
@ -5727,7 +5653,6 @@
|
||||||
"version": "6.2.0",
|
"version": "6.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
|
||||||
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
"integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "opencollective",
|
"type": "opencollective",
|
||||||
|
|
@ -5753,7 +5678,6 @@
|
||||||
"version": "6.1.2",
|
"version": "6.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
|
||||||
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
"integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cssesc": "^3.0.0",
|
"cssesc": "^3.0.0",
|
||||||
|
|
@ -5767,7 +5691,6 @@
|
||||||
"version": "4.2.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/prelude-ls": {
|
"node_modules/prelude-ls": {
|
||||||
|
|
@ -5811,7 +5734,6 @@
|
||||||
"version": "1.2.3",
|
"version": "1.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||||
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
|
|
@ -6035,7 +5957,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
|
||||||
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
"integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"pify": "^2.3.0"
|
"pify": "^2.3.0"
|
||||||
|
|
@ -6045,7 +5966,6 @@
|
||||||
"version": "3.6.0",
|
"version": "3.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
|
||||||
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"picomatch": "^2.2.1"
|
"picomatch": "^2.2.1"
|
||||||
|
|
@ -6090,7 +6010,6 @@
|
||||||
"version": "1.22.8",
|
"version": "1.22.8",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||||
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
"integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-core-module": "^2.13.0",
|
"is-core-module": "^2.13.0",
|
||||||
|
|
@ -6118,7 +6037,6 @@
|
||||||
"version": "1.0.4",
|
"version": "1.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
|
||||||
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
|
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"iojs": ">=1.0.0",
|
"iojs": ">=1.0.0",
|
||||||
|
|
@ -6165,7 +6083,6 @@
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
|
||||||
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
|
||||||
"dev": true,
|
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
"type": "github",
|
"type": "github",
|
||||||
|
|
@ -6211,7 +6128,6 @@
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"shebang-regex": "^3.0.0"
|
"shebang-regex": "^3.0.0"
|
||||||
|
|
@ -6224,7 +6140,6 @@
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
|
|
@ -6234,7 +6149,6 @@
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14"
|
"node": ">=14"
|
||||||
|
|
@ -6257,7 +6171,6 @@
|
||||||
"version": "1.2.1",
|
"version": "1.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
|
||||||
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
|
||||||
"dev": true,
|
|
||||||
"license": "BSD-3-Clause",
|
"license": "BSD-3-Clause",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
|
|
@ -6267,7 +6180,6 @@
|
||||||
"version": "5.1.2",
|
"version": "5.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
|
||||||
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"eastasianwidth": "^0.2.0",
|
"eastasianwidth": "^0.2.0",
|
||||||
|
|
@ -6286,7 +6198,6 @@
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"emoji-regex": "^8.0.0",
|
"emoji-regex": "^8.0.0",
|
||||||
|
|
@ -6301,7 +6212,6 @@
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
|
|
@ -6311,14 +6221,12 @@
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/string-width-cjs/node_modules/strip-ansi": {
|
"node_modules/string-width-cjs/node_modules/strip-ansi": {
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": "^5.0.1"
|
"ansi-regex": "^5.0.1"
|
||||||
|
|
@ -6331,7 +6239,6 @@
|
||||||
"version": "7.1.0",
|
"version": "7.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
|
||||||
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": "^6.0.1"
|
"ansi-regex": "^6.0.1"
|
||||||
|
|
@ -6348,7 +6255,6 @@
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": "^5.0.1"
|
"ansi-regex": "^5.0.1"
|
||||||
|
|
@ -6361,7 +6267,6 @@
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
|
|
@ -6384,7 +6289,6 @@
|
||||||
"version": "3.35.0",
|
"version": "3.35.0",
|
||||||
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
|
||||||
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
|
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jridgewell/gen-mapping": "^0.3.2",
|
"@jridgewell/gen-mapping": "^0.3.2",
|
||||||
|
|
@ -6420,7 +6324,6 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
|
||||||
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
|
|
@ -6443,7 +6346,6 @@
|
||||||
"version": "3.4.17",
|
"version": "3.4.17",
|
||||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
|
||||||
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
|
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@alloc/quick-lru": "^5.2.0",
|
"@alloc/quick-lru": "^5.2.0",
|
||||||
|
|
@ -6490,7 +6392,6 @@
|
||||||
"version": "3.3.1",
|
"version": "3.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
|
||||||
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"any-promise": "^1.0.0"
|
"any-promise": "^1.0.0"
|
||||||
|
|
@ -6500,7 +6401,6 @@
|
||||||
"version": "1.6.0",
|
"version": "1.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
|
||||||
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"thenify": ">= 3.1.0 < 4"
|
"thenify": ">= 3.1.0 < 4"
|
||||||
|
|
@ -6519,7 +6419,6 @@
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-number": "^7.0.0"
|
"is-number": "^7.0.0"
|
||||||
|
|
@ -6551,7 +6450,6 @@
|
||||||
"version": "0.1.13",
|
"version": "0.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
|
||||||
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
|
||||||
"dev": true,
|
|
||||||
"license": "Apache-2.0"
|
"license": "Apache-2.0"
|
||||||
},
|
},
|
||||||
"node_modules/tslib": {
|
"node_modules/tslib": {
|
||||||
|
|
@ -6714,7 +6612,6 @@
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/vaul": {
|
"node_modules/vaul": {
|
||||||
|
|
@ -6832,7 +6729,6 @@
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"isexe": "^2.0.0"
|
"isexe": "^2.0.0"
|
||||||
|
|
@ -6858,7 +6754,6 @@
|
||||||
"version": "8.1.0",
|
"version": "8.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
|
||||||
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": "^6.1.0",
|
"ansi-styles": "^6.1.0",
|
||||||
|
|
@ -6877,7 +6772,6 @@
|
||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": "^4.0.0",
|
"ansi-styles": "^4.0.0",
|
||||||
|
|
@ -6895,7 +6789,6 @@
|
||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
|
|
@ -6905,14 +6798,12 @@
|
||||||
"version": "8.0.0",
|
"version": "8.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
|
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
|
||||||
"version": "4.2.3",
|
"version": "4.2.3",
|
||||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"emoji-regex": "^8.0.0",
|
"emoji-regex": "^8.0.0",
|
||||||
|
|
@ -6927,7 +6818,6 @@
|
||||||
"version": "6.0.1",
|
"version": "6.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-regex": "^5.0.1"
|
"ansi-regex": "^5.0.1"
|
||||||
|
|
@ -6940,7 +6830,6 @@
|
||||||
"version": "6.2.1",
|
"version": "6.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
|
||||||
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
|
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
|
|
@ -6974,7 +6863,6 @@
|
||||||
"version": "2.6.0",
|
"version": "2.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.0.tgz",
|
||||||
"integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==",
|
"integrity": "sha512-a6ae//JvKDEra2kdi1qzCyrJW/WZCgFi8ydDV+eXExl95t+5R+ijnqHJbz9tmMh8FUjx3iv2fCQ4dclAQlO2UQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"bin": {
|
"bin": {
|
||||||
"yaml": "bin.mjs"
|
"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 Index from "./pages/Index";
|
||||||
import Solar from "./pages/Solar";
|
import Solar from "./pages/Solar";
|
||||||
import Wind from "./pages/Wind";
|
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";
|
import NotFound from "./pages/NotFound";
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
@ -15,11 +18,14 @@ const App = () => (
|
||||||
<TooltipProvider>
|
<TooltipProvider>
|
||||||
<Toaster />
|
<Toaster />
|
||||||
<Sonner />
|
<Sonner />
|
||||||
<BrowserRouter>
|
<BrowserRouter future={{ v7_startTransition: true, v7_relativeSplatPath: true }}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Index />} />
|
<Route path="/" element={<Index />} />
|
||||||
<Route path="/solar" element={<Solar />} />
|
<Route path="/solar" element={<Solar />} />
|
||||||
<Route path="/wind" element={<Wind />} />
|
<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 */}
|
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
||||||
<Route path="*" element={<NotFound />} />
|
<Route path="*" element={<NotFound />} />
|
||||||
</Routes>
|
</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;
|
description: string;
|
||||||
image: string;
|
image: string;
|
||||||
gradient: string;
|
gradient: string;
|
||||||
buttonVariant: "solar" | "wind" | "geo" | "battery";
|
buttonVariant: "solar" | "wind";
|
||||||
href: string;
|
href: string;
|
||||||
features: string[];
|
features: string[];
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
import EnergyTypeCard from "./EnergyTypeCard";
|
import EnergyTypeCard from "./EnergyTypeCard";
|
||||||
import solarImage from "@/assets/solar-installation.jpg";
|
import solarImage from "@/assets/solar-installation.jpg";
|
||||||
import windImage from "@/assets/wind-turbines.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 EnergyTypesSection = () => {
|
||||||
const energyTypes = [
|
const energyTypes = [
|
||||||
|
|
@ -33,34 +31,6 @@ const EnergyTypesSection = () => {
|
||||||
"Wartungsarme Technologie",
|
"Wartungsarme Technologie",
|
||||||
"Ideale Ergänzung zu Solar"
|
"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>
|
</p>
|
||||||
</div>
|
</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) => (
|
{energyTypes.map((type) => (
|
||||||
<EnergyTypeCard key={type.title} {...type} />
|
<EnergyTypeCard key={type.title} {...type} />
|
||||||
))}
|
))}
|
||||||
|
|
|
||||||
|
|
@ -52,16 +52,6 @@ const Footer = () => {
|
||||||
Wind-Installateure
|
Wind-Installateure
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -74,11 +64,6 @@ const Footer = () => {
|
||||||
Installateur Finden
|
Installateur Finden
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<Link to="/kostenlose-beratung" className="text-primary-foreground/80 hover:text-white transition-colors">
|
|
||||||
Kostenlose Beratung
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
<Link to="/unternehmen-listen" className="text-primary-foreground/80 hover:text-white transition-colors">
|
<Link to="/unternehmen-listen" className="text-primary-foreground/80 hover:text-white transition-colors">
|
||||||
Unternehmen Listen
|
Unternehmen Listen
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,6 @@ const Header = () => {
|
||||||
<Link to="/wind" className="text-foreground hover:text-wind transition-colors font-medium">
|
<Link to="/wind" className="text-foreground hover:text-wind transition-colors font-medium">
|
||||||
Wind
|
Wind
|
||||||
</Link>
|
</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">
|
<Link to="/installateur-finden" className="text-foreground hover:text-primary transition-colors font-medium">
|
||||||
Installateur Finden
|
Installateur Finden
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -39,12 +33,6 @@ const Header = () => {
|
||||||
<Button variant="outline" size="sm" asChild>
|
<Button variant="outline" size="sm" asChild>
|
||||||
<Link to="/unternehmen-listen">Unternehmen Listen</Link>
|
<Link to="/unternehmen-listen">Unternehmen Listen</Link>
|
||||||
</Button>
|
</Button>
|
||||||
<Button variant="hero" size="sm" asChild>
|
|
||||||
<Link to="/kostenlose-beratung">
|
|
||||||
<Phone className="w-4 h-4 mr-2" />
|
|
||||||
Kostenlose Beratung
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile Menu Button */}
|
{/* Mobile Menu Button */}
|
||||||
|
|
|
||||||
|
|
@ -1,96 +1,111 @@
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
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 { Link } from "react-router-dom";
|
||||||
import heroImage from "@/assets/hero-renewable-energy.jpg";
|
|
||||||
|
|
||||||
const HeroSection = () => {
|
const HeroSection = () => {
|
||||||
return (
|
return (
|
||||||
<section className="relative min-h-[600px] flex items-center overflow-hidden">
|
<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">
|
<div className="absolute inset-0">
|
||||||
<img
|
<img
|
||||||
src={heroImage}
|
src="/sun_flow_banner.png"
|
||||||
alt="Renewable Energy Solutions in Germany"
|
alt="Sun Flow Banner - Renewable Energy Solutions in Germany"
|
||||||
className="w-full h-full object-cover"
|
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>
|
</div>
|
||||||
|
|
||||||
{/* Content */}
|
{/* Content */}
|
||||||
<div className="container mx-auto px-4 relative z-10">
|
<div className="container mx-auto px-4 relative z-10">
|
||||||
<div className="max-w-2xl text-white">
|
<div className="max-w-2xl text-white">
|
||||||
<h1 className="text-4xl md:text-5xl lg:text-6xl font-bold mb-6 leading-tight">
|
{/* 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{" "}
|
Finden Sie Ihren perfekten{" "}
|
||||||
<span className="bg-gradient-to-r from-solar via-wind to-geo bg-clip-text text-transparent">
|
<span className="text-yellow-300">
|
||||||
Erneuerbaren Energie
|
Erneuerbaren
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
|
<span className="text-cyan-200">
|
||||||
|
Energie
|
||||||
|
</span>{" "}
|
||||||
|
<span className="text-green-200">
|
||||||
Installateur
|
Installateur
|
||||||
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
</div>
|
||||||
|
|
||||||
<p className="text-xl md:text-2xl text-white/90 mb-8 leading-relaxed">
|
{/* Clean description with subtle accents */}
|
||||||
Vergleichen Sie qualifizierte Fachbetriebe für Solar, Wind, Geothermie
|
<p className="text-xl md:text-2xl text-white/95 mb-8 leading-relaxed font-medium">
|
||||||
und Batteriespeicher in Ihrer Region. Kostenlos und unverbindlich.
|
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>
|
</p>
|
||||||
|
|
||||||
{/* Search Box */}
|
{/* Clean search box */}
|
||||||
<div className="bg-white/10 backdrop-blur-sm border border-white/20 rounded-2xl p-6 mb-8">
|
<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 flex-col md:flex-row gap-4">
|
||||||
<div className="flex-1 relative">
|
<div className="flex-1 relative group">
|
||||||
<MapPin className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white/60 w-5 h-5" />
|
<MapPin className="absolute left-3 top-1/2 transform -translate-y-1/2 text-cyan-200 w-5 h-5" />
|
||||||
<Input
|
<Input
|
||||||
placeholder="PLZ oder Stadt eingeben..."
|
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>
|
||||||
<div className="flex-1 relative">
|
<div className="flex-1 relative group">
|
||||||
<Zap className="absolute left-3 top-1/2 transform -translate-y-1/2 text-white/60 w-5 h-5" />
|
<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/10 border border-white/20 rounded-lg text-white appearance-none">
|
<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="">Energieart wählen</option>
|
||||||
<option value="solar">Solar</option>
|
<option value="solar">Solar</option>
|
||||||
<option value="wind">Wind</option>
|
<option value="wind">Wind</option>
|
||||||
<option value="geothermal">Geothermie</option>
|
|
||||||
<option value="battery">Batteriespeicher</option>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</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" />
|
<Search className="w-5 h-5 mr-2" />
|
||||||
Installateur Finden
|
Installateur Finden
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* CTA Buttons */}
|
{/* Clean CTA buttons */}
|
||||||
<div className="flex flex-col sm:flex-row gap-4">
|
<div className="flex flex-col sm:flex-row gap-4">
|
||||||
<Button variant="hero" size="xl" asChild>
|
<Button
|
||||||
<Link to="/kostenlose-beratung">
|
variant="outline"
|
||||||
Kostenlose Beratung Anfordern
|
size="xl"
|
||||||
</Link>
|
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"
|
||||||
</Button>
|
>
|
||||||
<Button variant="outline" size="xl" className="border-white/30 text-white hover:bg-white hover:text-primary">
|
<Link to="/installateur-finden" className="flex items-center">
|
||||||
<Link to="/installateur-finden">
|
|
||||||
Alle Installateure Ansehen
|
Alle Installateure Ansehen
|
||||||
|
<ArrowRight className="w-5 h-5 ml-2 group-hover:translate-x-1 transition-transform" />
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Floating Stats */}
|
{/* Clean floating stats */}
|
||||||
<div className="absolute bottom-8 right-8 hidden xl:block">
|
<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 className="grid grid-cols-3 gap-6 text-center">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-2xl font-bold">500+</div>
|
<div className="text-3xl font-bold text-yellow-200">500+</div>
|
||||||
<div className="text-sm text-white/80">Installateure</div>
|
<div className="text-sm text-white/90 font-medium">Installateure</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-2xl font-bold">2,500+</div>
|
<div className="text-3xl font-bold text-cyan-200">2,500+</div>
|
||||||
<div className="text-sm text-white/80">Projekte</div>
|
<div className="text-sm text-white/90 font-medium">Projekte</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-2xl font-bold">4.8★</div>
|
<div className="text-3xl font-bold text-green-200">4.8★</div>
|
||||||
<div className="text-sm text-white/80">Bewertung</div>
|
<div className="text-sm text-white/90 font-medium">Bewertung</div>
|
||||||
</div>
|
</div>
|
||||||
</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,
|
icon: Award,
|
||||||
title: "Beste Preise",
|
title: "Beste Preise",
|
||||||
description: "Durch unseren Vergleich erhalten Sie garantiert die besten Konditionen für Ihr Projekt.",
|
description: "Durch unseren Vergleich erhalten Sie garantiert die besten Konditionen für Ihr Projekt.",
|
||||||
gradient: "bg-gradient-geo"
|
gradient: "bg-gradient-solar"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: Clock,
|
icon: Clock,
|
||||||
title: "Schnelle Vermittlung",
|
title: "Schnelle Vermittlung",
|
||||||
description: "In nur wenigen Minuten erhalten Sie passende Installateur-Vorschläge für Ihre Region.",
|
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,
|
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
|
// Energy-specific variants
|
||||||
solar: "bg-gradient-solar text-white hover:shadow-solar transform hover:scale-105",
|
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",
|
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",
|
"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",
|
"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",
|
hero: "bg-gradient-hero text-white hover:shadow-lg transform hover:scale-105 border border-white/20",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
|
|
|
||||||
|
|
@ -26,18 +26,6 @@
|
||||||
--wind-light: 207 100% 85%;
|
--wind-light: 207 100% 85%;
|
||||||
--wind-dark: 207 80% 41%;
|
--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 */
|
/* Brand Colors */
|
||||||
--primary: 218 47% 18%;
|
--primary: 218 47% 18%;
|
||||||
--primary-foreground: 210 40% 98%;
|
--primary-foreground: 210 40% 98%;
|
||||||
|
|
@ -58,8 +46,6 @@
|
||||||
/* Energy Gradients */
|
/* Energy Gradients */
|
||||||
--gradient-solar: linear-gradient(135deg, hsl(var(--solar-primary)) 0%, hsl(var(--solar-secondary)) 100%);
|
--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-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%);
|
--gradient-hero: linear-gradient(135deg, hsl(var(--primary)) 0%, hsl(218 47% 25%) 100%);
|
||||||
|
|
||||||
/* Shadows */
|
/* 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-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-solar: 0 8px 32px hsl(var(--solar-primary) / 0.25);
|
||||||
--shadow-wind: 0 8px 32px hsl(var(--wind-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 */
|
/* Animations */
|
||||||
--transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
--transition-smooth: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
|
@ -189,17 +173,28 @@
|
||||||
background-clip: text;
|
background-clip: text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gradient-text-geo {
|
/* Solar Calculator Animations */
|
||||||
background: var(--gradient-geo);
|
@keyframes float {
|
||||||
-webkit-background-clip: text;
|
0% { transform: translate(0, 0); }
|
||||||
-webkit-text-fill-color: transparent;
|
100% { transform: translate(-60px, -60px); }
|
||||||
background-clip: text;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.gradient-text-battery {
|
@keyframes growUp {
|
||||||
background: var(--gradient-battery);
|
from { height: 0; }
|
||||||
-webkit-background-clip: text;
|
}
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
@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: {
|
Enums: {
|
||||||
energy_type: "solar" | "wind" | "geothermal" | "battery"
|
energy_type: "solar" | "wind"
|
||||||
installer_status: "active" | "inactive" | "pending" | "suspended"
|
installer_status: "active" | "inactive" | "pending" | "suspended"
|
||||||
quote_status: "pending" | "accepted" | "rejected" | "expired"
|
quote_status: "pending" | "accepted" | "rejected" | "expired"
|
||||||
}
|
}
|
||||||
|
|
@ -528,7 +528,7 @@ export type CompositeTypes<
|
||||||
export const Constants = {
|
export const Constants = {
|
||||||
public: {
|
public: {
|
||||||
Enums: {
|
Enums: {
|
||||||
energy_type: ["solar", "wind", "geothermal", "battery"],
|
energy_type: ["solar", "wind"],
|
||||||
installer_status: ["active", "inactive", "pending", "suspended"],
|
installer_status: ["active", "inactive", "pending", "suspended"],
|
||||||
quote_status: ["pending", "accepted", "rejected", "expired"],
|
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 Header from "@/components/Header";
|
||||||
import HeroSection from "@/components/HeroSection";
|
import HeroSection from "@/components/HeroSection";
|
||||||
import EnergyTypesSection from "@/components/EnergyTypesSection";
|
import EnergyTypesSection from "@/components/EnergyTypesSection";
|
||||||
|
import CalculatorNavigation from "@/components/CalculatorNavigation";
|
||||||
import WhyChooseUsSection from "@/components/WhyChooseUsSection";
|
import WhyChooseUsSection from "@/components/WhyChooseUsSection";
|
||||||
import Footer from "@/components/Footer";
|
import Footer from "@/components/Footer";
|
||||||
|
|
||||||
|
|
@ -11,6 +12,7 @@ const Index = () => {
|
||||||
<main>
|
<main>
|
||||||
<HeroSection />
|
<HeroSection />
|
||||||
<EnergyTypesSection />
|
<EnergyTypesSection />
|
||||||
|
<CalculatorNavigation />
|
||||||
<WhyChooseUsSection />
|
<WhyChooseUsSection />
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<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 Header from "@/components/Header";
|
||||||
import Footer from "@/components/Footer";
|
import Footer from "@/components/Footer";
|
||||||
|
import SolarCalculator from "@/components/SolarCalculator";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Sun, Zap, TrendingUp, Shield, ArrowRight } from "lucide-react";
|
import { Sun, Zap, TrendingUp, Shield, ArrowRight } from "lucide-react";
|
||||||
|
|
@ -62,11 +63,6 @@ const Solar = () => {
|
||||||
Solar-Installateur Finden
|
Solar-Installateur Finden
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -106,6 +102,9 @@ const Solar = () => {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Solar Calculator Section */}
|
||||||
|
<SolarCalculator />
|
||||||
|
|
||||||
{/* CTA Section */}
|
{/* CTA Section */}
|
||||||
<section className="py-20 bg-gradient-solar text-white">
|
<section className="py-20 bg-gradient-solar text-white">
|
||||||
<div className="container mx-auto px-4 text-center">
|
<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">
|
<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>
|
<Button variant="hero" size="xl" className="bg-white text-solar hover:bg-white/90" asChild>
|
||||||
<Link to="/kostenlose-beratung?type=solar">
|
<Link to="/installateur-finden?type=solar">
|
||||||
Jetzt kostenlose Beratung anfordern
|
Solar-Installateure finden
|
||||||
<ArrowRight className="w-5 h-5 ml-2" />
|
<ArrowRight className="w-5 h-5 ml-2" />
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</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 Header from "@/components/Header";
|
||||||
import Footer from "@/components/Footer";
|
import Footer from "@/components/Footer";
|
||||||
|
import WindCalculator from "@/components/WindCalculator";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { Wind, Zap, TrendingUp, Shield, ArrowRight } from "lucide-react";
|
import { Wind, Zap, TrendingUp, Shield, ArrowRight } from "lucide-react";
|
||||||
|
|
@ -62,11 +63,6 @@ const WindPage = () => {
|
||||||
Wind-Installateur Finden
|
Wind-Installateur Finden
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -106,6 +102,9 @@ const WindPage = () => {
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
{/* Wind Calculator Section */}
|
||||||
|
<WindCalculator />
|
||||||
|
|
||||||
{/* CTA Section */}
|
{/* CTA Section */}
|
||||||
<section className="py-20 bg-gradient-wind text-white">
|
<section className="py-20 bg-gradient-wind text-white">
|
||||||
<div className="container mx-auto px-4 text-center">
|
<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">
|
<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>
|
<Button variant="hero" size="xl" className="bg-white text-wind hover:bg-white/90" asChild>
|
||||||
<Link to="/kostenlose-beratung?type=wind">
|
<Link to="/installateur-finden?type=wind">
|
||||||
Jetzt kostenlose Beratung anfordern
|
Wind-Installateure finden
|
||||||
<ArrowRight className="w-5 h-5 ml-2" />
|
<ArrowRight className="w-5 h-5 ml-2" />
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -65,18 +65,6 @@ export default {
|
||||||
light: 'hsl(var(--wind-light))',
|
light: 'hsl(var(--wind-light))',
|
||||||
dark: 'hsl(var(--wind-dark))'
|
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: {
|
sidebar: {
|
||||||
DEFAULT: 'hsl(var(--sidebar-background))',
|
DEFAULT: 'hsl(var(--sidebar-background))',
|
||||||
foreground: 'hsl(var(--sidebar-foreground))',
|
foreground: 'hsl(var(--sidebar-foreground))',
|
||||||
|
|
@ -91,15 +79,11 @@ export default {
|
||||||
backgroundImage: {
|
backgroundImage: {
|
||||||
'gradient-solar': 'var(--gradient-solar)',
|
'gradient-solar': 'var(--gradient-solar)',
|
||||||
'gradient-wind': 'var(--gradient-wind)',
|
'gradient-wind': 'var(--gradient-wind)',
|
||||||
'gradient-geo': 'var(--gradient-geo)',
|
|
||||||
'gradient-battery': 'var(--gradient-battery)',
|
|
||||||
'gradient-hero': 'var(--gradient-hero)'
|
'gradient-hero': 'var(--gradient-hero)'
|
||||||
},
|
},
|
||||||
boxShadow: {
|
boxShadow: {
|
||||||
'solar': 'var(--shadow-solar)',
|
'solar': 'var(--shadow-solar)',
|
||||||
'wind': 'var(--shadow-wind)',
|
'wind': 'var(--shadow-wind)'
|
||||||
'geo': 'var(--shadow-geo)',
|
|
||||||
'battery': 'var(--shadow-battery)'
|
|
||||||
},
|
},
|
||||||
transitionTimingFunction: {
|
transitionTimingFunction: {
|
||||||
'smooth': 'cubic-bezier(0.4, 0, 0.2, 1)',
|
'smooth': 'cubic-bezier(0.4, 0, 0.2, 1)',
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue