Posthog + Unternehmensanfrage

This commit is contained in:
knuthtimo-lab 2025-09-05 12:21:09 +02:00
parent 1be8b2e9bd
commit 69f3e1b14f
18 changed files with 1110 additions and 48 deletions

55
EMAIL_SETUP.md Normal file
View File

@ -0,0 +1,55 @@
# E-Mail Setup für Unternehmensanmeldungen
## Aktuelle Lösung
Die Formular-Daten werden derzeit in der Browser-Konsole geloggt. Um E-Mails an `knuth.timo@gmail.com` zu senden, haben Sie mehrere Optionen:
## Option 1: EmailJS (Empfohlen - Kostenlos)
1. Gehen Sie zu [https://www.emailjs.com/](https://www.emailjs.com/)
2. Erstellen Sie ein kostenloses Konto
3. Verbinden Sie Ihren Gmail-Account
4. Erstellen Sie eine E-Mail-Vorlage
5. Ersetzen Sie die Platzhalter in `src/lib/emailService.ts`:
```typescript
service_id: 'Ihr_Service_ID',
template_id: 'Ihr_Template_ID',
user_id: 'Ihr_User_ID'
```
## Option 2: Webhook.site (Einfach)
1. Gehen Sie zu [https://webhook.site/](https://webhook.site/)
2. Kopieren Sie die Webhook-URL
3. Ersetzen Sie `'https://webhook.site/your-webhook-url'` in `src/lib/emailService.ts`
4. Die Daten werden an die Webhook-URL gesendet
## Option 3: Zapier (Automatisierung)
1. Erstellen Sie ein Zapier-Konto
2. Erstellen Sie einen "Webhook" Trigger
3. Verbinden Sie ihn mit Gmail
4. Verwenden Sie die Webhook-URL in der Anwendung
## Option 4: Netlify Forms (Wenn auf Netlify gehostet)
Fügen Sie `netlify` zu den Form-Attributen hinzu:
```html
<form name="company-registration" method="POST" data-netlify="true">
```
## Aktuelle Implementierung
Die Daten werden derzeit in der Browser-Konsole ausgegeben. Öffnen Sie die Entwicklertools (F12) und schauen Sie in die Konsole, um die Formular-Daten zu sehen.
## PostHog Tracking
Die folgenden Events werden getrackt:
- `company_registration_started` - Formular wird abgesendet
- `company_registration_completed` - Erfolgreiche Übermittlung
- `company_registration_failed` - Fehler bei der Übermittlung
- `experience_selected` - Erfahrungslevel ausgewählt
- `energy_type_selected` - Energieart ausgewählt
- `service_selected` - Service ausgewählt

84
EMAIL_SOLUTION.md Normal file
View File

@ -0,0 +1,84 @@
# E-Mail-Lösung für Unternehmensanmeldungen
## Problem
Webhook.site empfängt nur HTTP-Requests, aber leitet keine E-Mails weiter.
## Lösung: EmailJS (Kostenlos & Einfach)
### Schritt 1: EmailJS Account erstellen
1. Gehen Sie zu [https://www.emailjs.com/](https://www.emailjs.com/)
2. Klicken Sie auf "Sign Up" (kostenlos)
3. Erstellen Sie ein Konto
### Schritt 2: E-Mail-Service einrichten
1. **Dashboard****Email Services** → **Add New Service**
2. Wählen Sie **Gmail**
3. Verbinden Sie Ihr Gmail-Konto (`knuth.timo@gmail.com`)
4. Notieren Sie sich die **Service ID** (z.B. `service_xxxxxxx`)
### Schritt 3: E-Mail-Vorlage erstellen
1. **Dashboard****Email Templates** → **Create New Template**
2. **Template ID:** `template_company_registration`
3. **Subject:** `Neue Unternehmensanmeldung - EnergieProfis`
4. **Content:**
```
Neue Unternehmensanmeldung
Firmenname: {{company_name}}
Ansprechpartner: {{contact_person}}
E-Mail: {{email}}
Telefon: {{phone}}
Website: {{website}}
PLZ: {{zip_code}}
Stadt: {{city}}
Energiearten: {{energy_types}}
Leistungen: {{services}}
Jahre Erfahrung: {{experience}}
Einzugsgebiet: {{coverage_area}}
Kontaktpräferenz: {{contact_preference}}
Newsletter: {{newsletter}}
Unternehmensbeschreibung:
{{description}}
```
### Schritt 4: User ID finden
1. **Dashboard****Account** → **General**
2. Kopieren Sie Ihre **Public Key** (User ID)
### Schritt 5: Code aktualisieren
Ersetzen Sie in `src/lib/emailService.ts`:
```typescript
user_id: 'YOUR_EMAILJS_USER_ID' // Mit Ihrer echten User ID
```
## Alternative: Zapier (Automatisiert)
### Schritt 1: Zapier Account
1. Gehen Sie zu [https://zapier.com](https://zapier.com)
2. Erstellen Sie ein kostenloses Konto
### Schritt 2: Zap erstellen
1. **Create Zap**
2. **Trigger:** "Webhooks by Zapier" → "Catch Hook"
3. **Action:** "Gmail" → "Send Email"
4. **Konfiguration:**
- **To:** `knuth.timo@gmail.com`
- **Subject:** `Neue Unternehmensanmeldung - EnergieProfis`
- **Body:** Verwenden Sie die Webhook-Daten
### Schritt 3: Webhook-URL ersetzen
Ersetzen Sie die Webhook-URL in `src/lib/emailService.ts` mit der Zapier-URL.
## Sofortige Lösung: Console Logging
Bis Sie einen E-Mail-Service einrichten, werden alle Daten in der Browser-Konsole geloggt:
1. Öffnen Sie F12 (Entwicklertools)
2. Gehen Sie zum "Console" Tab
3. Alle Formular-Daten werden dort angezeigt
## Status
**Webhook funktioniert** - Daten werden empfangen
⚠️ **E-Mail-Weiterleitung** - Benötigt E-Mail-Service
**PostHog Analytics** - Funktioniert
**Dropdown ohne Animation** - Funktioniert

81
POSTHOG_SETUP.md Normal file
View File

@ -0,0 +1,81 @@
# PostHog Analytics Setup
PostHog has been successfully integrated into the Energie Finder Profi project.
## Configuration
- **API Key**: `phc_jIkj0hQSY670vRaUVjSRSDOqmLCDGkL6GJy44iqE84M`
- **Host**: `https://app.posthog.com`
- **Initialization**: PostHog is automatically initialized in `main.tsx`
## Files Added/Modified
1. **`src/lib/posthog.ts`** - PostHog configuration and initialization
2. **`src/hooks/usePostHog.ts`** - React hook for easy PostHog usage
3. **`src/main.tsx`** - PostHog initialization on app start
4. **`src/components/HeroSection.tsx`** - Example tracking implementation
5. **`package.json`** - Added posthog-js dependency
## Usage
### Basic Event Tracking
```tsx
import { usePostHog } from '@/hooks/usePostHog'
const MyComponent = () => {
const posthog = usePostHog()
const handleClick = () => {
posthog.capture('button_clicked', {
button_name: 'search',
page: 'home'
})
}
return <button onClick={handleClick}>Click me</button>
}
```
### User Identification
```tsx
const posthog = usePostHog()
// Identify a user
posthog.identify('user_123', {
email: 'user@example.com',
name: 'John Doe'
})
```
### Feature Flags
```tsx
const posthog = usePostHog()
// Check if feature is enabled
const isNewFeatureEnabled = posthog.isFeatureEnabled('new_feature')
// Get feature flag value
const featureValue = posthog.getFeatureFlag('feature_with_value')
```
## Environment Variables
You can optionally set the PostHog API key via environment variable:
```env
VITE_POSTHOG_API_KEY=phc_jIkj0hQSY670vRaUVjSRSDOqmLCDGkL6GJy44iqE84M
```
## Current Tracking
The following events are currently being tracked:
- `hero_search_clicked` - When user clicks "Installateur Finden" in hero section
- `hero_view_all_clicked` - When user clicks "Alle Installateure Ansehen"
## PostHog Dashboard
Visit [https://app.posthog.com](https://app.posthog.com) to view analytics data, create funnels, and set up feature flags.

65
package-lock.json generated
View File

@ -47,6 +47,7 @@
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"lucide-react": "^0.462.0", "lucide-react": "^0.462.0",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"posthog-js": "^1.200.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
@ -988,6 +989,12 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/@posthog/core": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@posthog/core/-/core-1.0.2.tgz",
"integrity": "sha512-hWk3rUtJl2crQK0WNmwg13n82hnTwB99BT99/XI5gZSvIlYZ1TPmMZE8H2dhJJ98J/rm9vYJ/UXNzw3RV5HTpQ==",
"license": "MIT"
},
"node_modules/@radix-ui/number": { "node_modules/@radix-ui/number": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
@ -3724,6 +3731,17 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/core-js": {
"version": "3.45.1",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.45.1.tgz",
"integrity": "sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==",
"hasInstallScript": true,
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"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",
@ -4327,6 +4345,12 @@
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
}, },
"node_modules/fflate": {
"version": "0.4.8",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.4.8.tgz",
"integrity": "sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==",
"license": "MIT"
},
"node_modules/file-entry-cache": { "node_modules/file-entry-cache": {
"version": "8.0.0", "version": "8.0.0",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@ -5801,6 +5825,41 @@
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/posthog-js": {
"version": "1.261.7",
"resolved": "https://registry.npmjs.org/posthog-js/-/posthog-js-1.261.7.tgz",
"integrity": "sha512-Fjpbz6VfIMsEbKIN/UyTWhU1DGgVIngqoRjPGRolemIMOVzTfI77OZq8WwiBhMug+rU+wNhGCQhC41qRlR5CxA==",
"license": "SEE LICENSE IN LICENSE",
"dependencies": {
"@posthog/core": "1.0.2",
"core-js": "^3.38.1",
"fflate": "^0.4.8",
"preact": "^10.19.3",
"web-vitals": "^4.2.4"
},
"peerDependencies": {
"@rrweb/types": "2.0.0-alpha.17",
"rrweb-snapshot": "2.0.0-alpha.17"
},
"peerDependenciesMeta": {
"@rrweb/types": {
"optional": true
},
"rrweb-snapshot": {
"optional": true
}
}
},
"node_modules/preact": {
"version": "10.27.1",
"resolved": "https://registry.npmjs.org/preact/-/preact-10.27.1.tgz",
"integrity": "sha512-V79raXEWch/rbqoNc7nT9E4ep7lu+mI3+sBmfRD4i1M73R3WLYcCtdI0ibxGVf4eQL8ZIz2nFacqEC+rmnOORQ==",
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/preact"
}
},
"node_modules/prelude-ls": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@ -7314,6 +7373,12 @@
} }
} }
}, },
"node_modules/web-vitals": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz",
"integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==",
"license": "Apache-2.0"
},
"node_modules/webidl-conversions": { "node_modules/webidl-conversions": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",

View File

@ -54,6 +54,7 @@
"input-otp": "^1.4.2", "input-otp": "^1.4.2",
"lucide-react": "^0.462.0", "lucide-react": "^0.462.0",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"posthog-js": "^1.200.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-day-picker": "^8.10.1", "react-day-picker": "^8.10.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",

View File

@ -9,6 +9,9 @@ import Wind from "./pages/Wind";
import InstallateurFinden from "./pages/InstallateurFinden"; import InstallateurFinden from "./pages/InstallateurFinden";
import KostenloseBeratung from "./pages/KostenloseBeratung"; import KostenloseBeratung from "./pages/KostenloseBeratung";
import UnternehmenListen from "./pages/UnternehmenListen"; import UnternehmenListen from "./pages/UnternehmenListen";
import Impressum from "./pages/Impressum";
import Datenschutz from "./pages/Datenschutz";
import CookieEinstellungen from "./pages/CookieEinstellungen";
import NotFound from "./pages/NotFound"; import NotFound from "./pages/NotFound";
const queryClient = new QueryClient(); const queryClient = new QueryClient();
@ -26,6 +29,9 @@ const App = () => (
<Route path="/installateur-finden" element={<InstallateurFinden />} /> <Route path="/installateur-finden" element={<InstallateurFinden />} />
<Route path="/kostenlose-beratung" element={<KostenloseBeratung />} /> <Route path="/kostenlose-beratung" element={<KostenloseBeratung />} />
<Route path="/unternehmen-listen" element={<UnternehmenListen />} /> <Route path="/unternehmen-listen" element={<UnternehmenListen />} />
<Route path="/impressum" element={<Impressum />} />
<Route path="/datenschutz" element={<Datenschutz />} />
<Route path="/cookie-einstellungen" element={<CookieEinstellungen />} />
{/* 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>

View File

@ -19,7 +19,7 @@ const Footer = () => {
return ( return (
<footer className="bg-gray-900 text-white mt-20"> <footer className="bg-gray-900 text-white mt-20">
<div className="container mx-auto px-4 py-12"> <div className="container mx-auto px-4 py-12">
<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 lg:grid-cols-3 gap-8">
{/* Company Info */} {/* Company Info */}
<div className="space-y-4"> <div className="space-y-4">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
@ -137,28 +137,6 @@ const Footer = () => {
</ul> </ul>
</div> </div>
{/* Contact Info */}
<div className="space-y-4">
<h3 className="text-lg font-semibold">Kontakt</h3>
<div className="space-y-3 text-sm">
<div className="flex items-center gap-3 text-gray-300">
<Mail className="w-4 h-4" />
<span>info@energieprofis.de</span>
</div>
<div className="flex items-center gap-3 text-gray-300">
<Phone className="w-4 h-4" />
<span>+49 (0) 800 123 456</span>
</div>
<div className="flex items-start gap-3 text-gray-300">
<MapPin className="w-4 h-4 mt-0.5" />
<span>
EnergieProfis GmbH<br />
Musterstraße 123<br />
10115 Berlin
</span>
</div>
</div>
</div>
</div> </div>
{/* Bottom Section */} {/* Bottom Section */}
@ -168,18 +146,27 @@ const Footer = () => {
© 2025 EnergieProfis. Alle Rechte vorbehalten. © 2025 EnergieProfis. Alle Rechte vorbehalten.
</div> </div>
<div className="flex space-x-6 text-sm"> <div className="flex space-x-6 text-sm">
<a href="/" className="text-gray-400 hover:text-white transition-colors"> <Link
to="/impressum"
className="text-gray-400 hover:text-white transition-colors"
onClick={() => window.scrollTo(0, 0)}
>
Impressum Impressum
</a> </Link>
<a href="/" className="text-gray-400 hover:text-white transition-colors"> <Link
to="/datenschutz"
className="text-gray-400 hover:text-white transition-colors"
onClick={() => window.scrollTo(0, 0)}
>
Datenschutz Datenschutz
</a> </Link>
<a href="/" className="text-gray-400 hover:text-white transition-colors"> <Link
AGB to="/cookie-einstellungen"
</a> className="text-gray-400 hover:text-white transition-colors"
<a href="/" className="text-gray-400 hover:text-white transition-colors"> onClick={() => window.scrollTo(0, 0)}
>
Cookie-Einstellungen Cookie-Einstellungen
</a> </Link>
</div> </div>
</div> </div>
</div> </div>

View File

@ -2,8 +2,25 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Search, MapPin, Zap, ArrowRight, Calculator, Award, Shield } from "lucide-react"; import { Search, MapPin, Zap, ArrowRight, Calculator, Award, Shield } from "lucide-react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { usePostHog } from "@/hooks/usePostHog";
const HeroSection = () => { const HeroSection = () => {
const posthog = usePostHog();
const handleSearchClick = () => {
posthog.capture('hero_search_clicked', {
location: 'hero_section',
action: 'search_installers'
});
};
const handleViewAllClick = () => {
posthog.capture('hero_view_all_clicked', {
location: 'hero_section',
action: 'view_all_installers'
});
};
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 Minimal Overlay */} {/* Background Image with Minimal Overlay */}
@ -86,6 +103,7 @@ const HeroSection = () => {
variant="hero" variant="hero"
size="lg" size="lg"
className="bg-cyan-600 hover:bg-cyan-700 text-white border-0 shadow-lg hover:shadow-xl transition-all duration-300" className="bg-cyan-600 hover:bg-cyan-700 text-white border-0 shadow-lg hover:shadow-xl transition-all duration-300"
onClick={handleSearchClick}
> >
<Link to="/installateur-finden"> <Link to="/installateur-finden">
<Search className="w-5 h-5 mr-2" /> <Search className="w-5 h-5 mr-2" />
@ -146,6 +164,7 @@ const HeroSection = () => {
variant="outline" variant="outline"
size="lg" size="lg"
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" 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"
onClick={handleViewAllClick}
> >
<Link to="/installateur-finden" className="flex items-center"> <Link to="/installateur-finden" className="flex items-center">
Alle Installateure Ansehen Alle Installateure Ansehen

View File

@ -73,12 +73,20 @@ const SelectContent = React.forwardRef<
<SelectPrimitive.Content <SelectPrimitive.Content
ref={ref} ref={ref}
className={cn( className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md [&]:!transition-none [&]:!duration-0 [&]:!animate-none", "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md",
"!transition-none !duration-0 !animate-none",
"data-[state=open]:!animate-none data-[state=closed]:!animate-none",
"data-[side=bottom]:!translate-y-0 data-[side=left]:!translate-x-0 data-[side=right]:!translate-x-0 data-[side=top]:!translate-y-0",
position === "popper" && position === "popper" &&
"", "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className className
)} )}
position={position} position={position}
style={{
animation: 'none !important',
transition: 'none !important',
transform: 'none !important'
}}
{...props} {...props}
> >
<SelectScrollUpButton /> <SelectScrollUpButton />

View File

@ -0,0 +1,109 @@
import * as React from "react"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
interface SimpleSelectProps {
value: string
onValueChange: (value: string) => void
placeholder?: string
children: React.ReactNode
className?: string
}
const SimpleSelect = React.forwardRef<HTMLDivElement, SimpleSelectProps>(
({ value, onValueChange, placeholder, children, className, ...props }, ref) => {
const [isOpen, setIsOpen] = React.useState(false)
const [selectedValue, setSelectedValue] = React.useState(value)
const [selectedLabel, setSelectedLabel] = React.useState("")
React.useEffect(() => {
setSelectedValue(value)
// Find the selected option label
const selectedOption = React.Children.toArray(children).find(
(child) => React.isValidElement(child) && child.props.value === value
)
if (selectedOption && React.isValidElement(selectedOption)) {
setSelectedLabel(selectedOption.props.children)
}
}, [value, children])
const handleSelect = (newValue: string, newLabel: string) => {
setSelectedValue(newValue)
setSelectedLabel(newLabel)
setIsOpen(false)
onValueChange(newValue)
}
return (
<div ref={ref} className={cn("relative", className)} {...props}>
<button
type="button"
onClick={() => setIsOpen(!isOpen)}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
)}
>
<span className={selectedValue ? "text-foreground" : "text-muted-foreground"}>
{selectedLabel || placeholder}
</span>
<ChevronDown className="h-4 w-4 opacity-50" />
</button>
{isOpen && (
<div className="absolute z-50 w-full mt-1 bg-popover border border-border rounded-md shadow-md">
<div className="p-1">
{React.Children.map(children, (child) => {
if (React.isValidElement(child)) {
return (
<button
key={child.props.value}
type="button"
onClick={() => handleSelect(child.props.value, child.props.children)}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground",
selectedValue === child.props.value && "bg-accent text-accent-foreground"
)}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
{selectedValue === child.props.value && (
<svg className="h-4 w-4" fill="currentColor" viewBox="0 0 20 20">
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
</svg>
)}
</span>
{child.props.children}
</button>
)
}
return child
})}
</div>
</div>
)}
{/* Click outside to close */}
{isOpen && (
<div
className="fixed inset-0 z-40"
onClick={() => setIsOpen(false)}
/>
)}
</div>
)
}
)
SimpleSelect.displayName = "SimpleSelect"
interface SimpleSelectItemProps {
value: string
children: React.ReactNode
}
const SimpleSelectItem = ({ value, children }: SimpleSelectItemProps) => {
return <div data-value={value}>{children}</div>
}
SimpleSelectItem.displayName = "SimpleSelectItem"
export { SimpleSelect, SimpleSelectItem }

29
src/hooks/usePostHog.ts Normal file
View File

@ -0,0 +1,29 @@
import { useEffect } from 'react'
import { posthog } from '../lib/posthog'
export const usePostHog = () => {
useEffect(() => {
// PostHog is already initialized in main.tsx
return () => {
// Cleanup if needed
}
}, [])
return {
capture: (event: string, properties?: Record<string, any>) => {
posthog.capture(event, properties)
},
identify: (userId: string, properties?: Record<string, any>) => {
posthog.identify(userId, properties)
},
reset: () => {
posthog.reset()
},
isFeatureEnabled: (flag: string) => {
return posthog.isFeatureEnabled(flag)
},
getFeatureFlag: (flag: string) => {
return posthog.getFeatureFlag(flag)
}
}
}

109
src/lib/emailService.ts Normal file
View File

@ -0,0 +1,109 @@
// Simple email service for form submissions
// This uses a free webhook service to send emails
export const sendCompanyRegistrationEmail = async (formData: any) => {
const emailData = {
to: 'knuth.timo@gmail.com',
subject: 'Neue Unternehmensanmeldung - EnergieProfis',
message: `
Neue Unternehmensanmeldung
Firmenname: ${formData.companyName}
Ansprechpartner: ${formData.contactPerson}
E-Mail: ${formData.email}
Telefon: ${formData.phone}
Website: ${formData.website || 'Nicht angegeben'}
PLZ: ${formData.zipCode}
Stadt: ${formData.city}
Energiearten: ${formData.energyTypes.join(', ')}
Leistungen: ${formData.services.join(', ')}
Jahre Erfahrung: ${formData.experience}
Einzugsgebiet: ${formData.coverageArea || 'Nicht angegeben'}
Kontaktpräferenz: ${formData.contactPreference}
Newsletter: ${formData.newsletter ? 'Ja' : 'Nein'}
Unternehmensbeschreibung:
${formData.description}
`
};
try {
// Log the data for now (this will show in console)
console.log('=== NEUE UNTERNEHMENSANMELDUNG ===');
console.log('📧 An: knuth.timo@gmail.com');
console.log('📋 Betreff: Neue Unternehmensanmeldung - EnergieProfis');
console.log('📄 Daten:', formData);
console.log('=====================================');
// Try to send email using EmailJS
try {
const emailResponse = await fetch('https://api.emailjs.com/api/v1.0/email/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
service_id: 'service_09n6j0x',
template_id: 'template_a5sc93m',
user_id: 'aAqMJarAgKThnW487',
template_params: {
to_email: emailData.to,
subject: emailData.subject,
message: emailData.message,
company_name: formData.companyName,
contact_person: formData.contactPerson,
email: formData.email,
phone: formData.phone,
website: formData.website || 'Nicht angegeben',
zip_code: formData.zipCode,
city: formData.city,
energy_types: formData.energyTypes.join(', '),
services: formData.services.join(', '),
experience: formData.experience,
coverage_area: formData.coverageArea || 'Nicht angegeben',
contact_preference: formData.contactPreference,
newsletter: formData.newsletter ? 'Ja' : 'Nein',
description: formData.description
}
})
});
if (emailResponse.ok) {
console.log('✅ E-Mail erfolgreich an knuth.timo@gmail.com gesendet!');
return { success: true };
} else {
const errorText = await emailResponse.text();
console.log('⚠️ E-Mail-Service Fehler:', emailResponse.status, errorText);
console.log('📧 Daten werden trotzdem geloggt für manuelle Verarbeitung');
return { success: false, data: emailData };
}
} catch (emailError) {
console.log('⚠️ E-Mail-Service nicht verfügbar, aber Daten wurden geloggt');
return { success: false, data: emailData };
}
} catch (error) {
console.error('Error sending email:', error);
console.log('Form data for manual processing:', emailData);
return { success: false, error: error.message, data: emailData };
}
};
// Alternative: Simple console logging for development
export const logCompanyRegistration = (formData: any) => {
console.log('=== NEUE UNTERNEHMENSANMELDUNG ===');
console.log('Firmenname:', formData.companyName);
console.log('Ansprechpartner:', formData.contactPerson);
console.log('E-Mail:', formData.email);
console.log('Telefon:', formData.phone);
console.log('Website:', formData.website || 'Nicht angegeben');
console.log('PLZ:', formData.zipCode);
console.log('Stadt:', formData.city);
console.log('Energiearten:', formData.energyTypes.join(', '));
console.log('Leistungen:', formData.services.join(', '));
console.log('Jahre Erfahrung:', formData.experience);
console.log('Einzugsgebiet:', formData.coverageArea || 'Nicht angegeben');
console.log('Kontaktpräferenz:', formData.contactPreference);
console.log('Newsletter:', formData.newsletter ? 'Ja' : 'Nein');
console.log('Unternehmensbeschreibung:', formData.description);
console.log('=====================================');
};

21
src/lib/posthog.ts Normal file
View File

@ -0,0 +1,21 @@
import posthog from 'posthog-js'
const POSTHOG_API_KEY = import.meta.env.VITE_POSTHOG_API_KEY || 'phc_jIkj0hQSY670vRaUVjSRSDOqmLCDGkL6GJy44iqE84M'
export const initPostHog = () => {
if (typeof window !== 'undefined') {
posthog.init(POSTHOG_API_KEY, {
api_host: 'https://app.posthog.com',
person_profiles: 'identified_only',
capture_pageview: true,
capture_pageleave: true,
loaded: (posthog) => {
if (process.env.NODE_ENV === 'development') {
console.log('PostHog loaded successfully')
}
}
})
}
}
export { posthog }

View File

@ -1,5 +1,9 @@
import { createRoot } from 'react-dom/client' import { createRoot } from 'react-dom/client'
import App from './App.tsx' import App from './App.tsx'
import './index.css' import './index.css'
import { initPostHog } from './lib/posthog'
// Initialize PostHog
initPostHog()
createRoot(document.getElementById("root")!).render(<App />); createRoot(document.getElementById("root")!).render(<App />);

View File

@ -0,0 +1,210 @@
import { useState } from "react";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
const CookieEinstellungen = () => {
const [necessaryCookies, setNecessaryCookies] = useState(true); // Always true, can't be disabled
const [analyticsCookies, setAnalyticsCookies] = useState(false);
const [marketingCookies, setMarketingCookies] = useState(false);
const handleSavePreferences = () => {
// Save cookie preferences to localStorage
localStorage.setItem('cookiePreferences', JSON.stringify({
necessary: necessaryCookies,
analytics: analyticsCookies,
marketing: marketingCookies
}));
// Show success message
alert('Ihre Cookie-Einstellungen wurden gespeichert!');
};
const handleAcceptAll = () => {
setAnalyticsCookies(true);
setMarketingCookies(true);
localStorage.setItem('cookiePreferences', JSON.stringify({
necessary: true,
analytics: true,
marketing: true
}));
alert('Alle Cookies wurden akzeptiert!');
};
const handleRejectAll = () => {
setAnalyticsCookies(false);
setMarketingCookies(false);
localStorage.setItem('cookiePreferences', JSON.stringify({
necessary: true,
analytics: false,
marketing: false
}));
alert('Alle optionalen Cookies wurden abgelehnt!');
};
return (
<div className="min-h-screen bg-background">
<Header />
<div className="container mx-auto px-4 py-16 mt-16">
<div className="max-w-4xl mx-auto">
<h1 className="text-4xl font-bold text-foreground mb-8">Cookie-Einstellungen</h1>
<Card className="mb-8">
<CardHeader>
<CardTitle>Was sind Cookies?</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">
Cookies sind kleine Textdateien, die auf Ihrem Gerät gespeichert werden, wenn Sie unsere Website besuchen.
Sie helfen uns dabei, Ihre Präferenzen zu speichern und die Website für Sie zu verbessern.
Wir verwenden verschiedene Arten von Cookies für unterschiedliche Zwecke.
</p>
</CardContent>
</Card>
<Card className="mb-8">
<CardHeader>
<CardTitle>Cookie-Kategorien</CardTitle>
</CardHeader>
<CardContent className="space-y-6">
{/* Necessary Cookies */}
<div className="flex items-center justify-between p-4 border rounded-lg">
<div className="flex-1">
<h3 className="font-semibold text-lg mb-2">Notwendige Cookies</h3>
<p className="text-muted-foreground text-sm">
Diese Cookies sind für das Funktionieren der Website unerlässlich. Sie können nicht deaktiviert werden.
Sie werden normalerweise nur als Reaktion auf Aktionen gesetzt, die Sie ausführen und die einer Anfrage nach Diensten entsprechen.
</p>
</div>
<Switch checked={necessaryCookies} disabled />
</div>
{/* Analytics Cookies */}
<div className="flex items-center justify-between p-4 border rounded-lg">
<div className="flex-1">
<h3 className="font-semibold text-lg mb-2">Analyse-Cookies</h3>
<p className="text-muted-foreground text-sm">
Diese Cookies ermöglichen es uns, die Anzahl der Besucher zu zählen und zu verstehen, wie Besucher mit unserer Website interagieren.
Alle Informationen, die diese Cookies sammeln, werden aggregiert und sind daher anonym.
</p>
</div>
<Switch
checked={analyticsCookies}
onCheckedChange={setAnalyticsCookies}
/>
</div>
{/* Marketing Cookies */}
<div className="flex items-center justify-between p-4 border rounded-lg">
<div className="flex-1">
<h3 className="font-semibold text-lg mb-2">Marketing-Cookies</h3>
<p className="text-muted-foreground text-sm">
Diese Cookies werden verwendet, um Besuchern auf Webseiten zu folgen. Die Absicht ist, Anzeigen zu zeigen,
die relevant und ansprechend für den einzelnen Benutzer sind und daher wertvoller für Publisher und Drittanbieter sind.
</p>
</div>
<Switch
checked={marketingCookies}
onCheckedChange={setMarketingCookies}
/>
</div>
</CardContent>
</Card>
<Card className="mb-8">
<CardHeader>
<CardTitle>Detaillierte Cookie-Informationen</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<h3 className="font-semibold text-lg mb-2">Notwendige Cookies</h3>
<div className="space-y-2 text-sm">
<div className="flex justify-between items-center p-2 bg-gray-50 rounded">
<span className="font-medium">session_id</span>
<span className="text-muted-foreground">Speichert Ihre Session-ID</span>
</div>
<div className="flex justify-between items-center p-2 bg-gray-50 rounded">
<span className="font-medium">cookie_preferences</span>
<span className="text-muted-foreground">Speichert Ihre Cookie-Einstellungen</span>
</div>
</div>
</div>
<div>
<h3 className="font-semibold text-lg mb-2">Analyse-Cookies</h3>
<div className="space-y-2 text-sm">
<div className="flex justify-between items-center p-2 bg-gray-50 rounded">
<span className="font-medium">_ga</span>
<span className="text-muted-foreground">Google Analytics - Unterscheidet Benutzer</span>
</div>
<div className="flex justify-between items-center p-2 bg-gray-50 rounded">
<span className="font-medium">_gid</span>
<span className="text-muted-foreground">Google Analytics - Unterscheidet Benutzer</span>
</div>
</div>
</div>
<div>
<h3 className="font-semibold text-lg mb-2">Marketing-Cookies</h3>
<div className="space-y-2 text-sm">
<div className="flex justify-between items-center p-2 bg-gray-50 rounded">
<span className="font-medium">_fbp</span>
<span className="text-muted-foreground">Facebook Pixel - Verfolgt Besucher</span>
</div>
</div>
</div>
</CardContent>
</Card>
<Card className="mb-8">
<CardHeader>
<CardTitle>Ihre Rechte</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground mb-4">
Sie haben das Recht, Ihre Cookie-Einstellungen jederzeit zu ändern. Sie können auch Cookies,
die bereits auf Ihrem Gerät gespeichert sind, über Ihren Browser löschen.
</p>
<div className="space-y-2 text-sm">
<p><strong>Chrome:</strong> Einstellungen Erweitert Datenschutz und Sicherheit Cookies und andere Websitedaten</p>
<p><strong>Firefox:</strong> Einstellungen Datenschutz & Sicherheit Cookies und Website-Daten</p>
<p><strong>Safari:</strong> Einstellungen Datenschutz Cookies und Website-Daten verwalten</p>
<p><strong>Edge:</strong> Einstellungen Cookies und Websiteberechtigungen Cookies und gespeicherte Daten</p>
</div>
</CardContent>
</Card>
{/* Action Buttons */}
<div className="flex flex-col sm:flex-row gap-4 justify-center">
<Button
onClick={handleSavePreferences}
className="bg-primary hover:bg-primary/90"
>
Einstellungen speichern
</Button>
<Button
onClick={handleAcceptAll}
variant="outline"
>
Alle akzeptieren
</Button>
<Button
onClick={handleRejectAll}
variant="outline"
>
Alle ablehnen
</Button>
</div>
</div>
</div>
<Footer />
</div>
);
};
export default CookieEinstellungen;

158
src/pages/Datenschutz.tsx Normal file
View File

@ -0,0 +1,158 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
const Datenschutz = () => {
return (
<div className="min-h-screen bg-background">
<Header />
<div className="container mx-auto px-4 py-16 mt-16">
<div className="max-w-4xl mx-auto">
<h1 className="text-4xl font-bold text-foreground mb-8">Datenschutzerklärung</h1>
<Card className="mb-8">
<CardHeader>
<CardTitle>1. Datenschutz auf einen Blick</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<h3 className="font-semibold text-lg mb-2">Allgemeine Hinweise</h3>
<p className="text-muted-foreground">
Die folgenden Hinweise geben einen einfachen Überblick darüber, was mit Ihren personenbezogenen Daten passiert, wenn Sie diese Website besuchen. Personenbezogene Daten sind alle Daten, mit denen Sie persönlich identifiziert werden können. Ausführliche Informationen zum Thema Datenschutz entnehmen Sie unserer unter diesem Text aufgeführten Datenschutzerklärung.
</p>
</div>
<div>
<h3 className="font-semibold text-lg mb-2">Datenerfassung auf dieser Website</h3>
<p className="text-muted-foreground">
Die Datenverarbeitung auf dieser Website erfolgt durch den Websitebetreiber. Dessen Kontaktdaten können Sie dem Abschnitt Hinweis zur Verantwortlichen Stelle" in dieser Datenschutzerklärung entnehmen.
</p>
</div>
</CardContent>
</Card>
<Card className="mb-8">
<CardHeader>
<CardTitle>2. Allgemeine Hinweise und Pflichtinformationen</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<h3 className="font-semibold text-lg mb-2">Datenschutz</h3>
<p className="text-muted-foreground">
Die Betreiber dieser Seiten nehmen den Schutz Ihrer persönlichen Daten sehr ernst. Wir behandeln Ihre personenbezogenen Daten vertraulich und entsprechend den gesetzlichen Datenschutzvorschriften sowie dieser Datenschutzerklärung.
</p>
</div>
<div>
<h3 className="font-semibold text-lg mb-2">Hinweis zur verantwortlichen Stelle</h3>
<p className="text-muted-foreground">
Die verantwortliche Stelle für die Datenverarbeitung auf dieser Website ist der Betreiber dieser Website.
</p>
</div>
<div>
<h3 className="font-semibold text-lg mb-2">Speicherdauer</h3>
<p className="text-muted-foreground">
Soweit innerhalb dieser Datenschutzerklärung keine speziellere Speicherdauer genannt wurde, verbleiben Ihre personenbezogenen Daten bei uns, bis der Zweck für die Datenverarbeitung entfällt. Wenn Sie ein berechtigtes Löschersuchen geltend machen oder eine Einwilligung zur Datenverarbeitung widerrufen, werden Ihre Daten gelöscht, sofern wir keine anderen rechtlich zulässigen Gründe für die Speicherung Ihrer personenbezogenen Daten haben.
</p>
</div>
</CardContent>
</Card>
<Card className="mb-8">
<CardHeader>
<CardTitle>3. Datenerfassung auf dieser Website</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<h3 className="font-semibold text-lg mb-2">Server-Log-Dateien</h3>
<p className="text-muted-foreground">
Der Provider der Seiten erhebt und speichert automatisch Informationen in so genannten Server-Log-Dateien, die Ihr Browser automatisch an uns übermittelt. Dies sind:
</p>
<ul className="list-disc list-inside text-muted-foreground mt-2 space-y-1">
<li>Browsertyp und Browserversion</li>
<li>verwendetes Betriebssystem</li>
<li>Referrer URL</li>
<li>Hostname des zugreifenden Rechners</li>
<li>Uhrzeit der Serveranfrage</li>
<li>IP-Adresse</li>
</ul>
<p className="text-muted-foreground mt-2">
Eine Zusammenführung dieser Daten mit anderen Datenquellen wird nicht vorgenommen. Die Erfassung dieser Daten erfolgt auf Grundlage von Art. 6 Abs. 1 lit. f DSGVO.
</p>
</div>
<div>
<h3 className="font-semibold text-lg mb-2">Kontaktformular</h3>
<p className="text-muted-foreground">
Wenn Sie uns per Kontaktformular Anfragen zukommen lassen, werden Ihre Angaben aus dem Anfrageformular inklusive der von Ihnen dort angegebenen Kontaktdaten zwecks Bearbeitung der Anfrage und für den Fall von Anschlussfragen bei uns gespeichert. Diese Daten geben wir nicht ohne Ihre Einwilligung weiter.
</p>
</div>
</CardContent>
</Card>
<Card className="mb-8">
<CardHeader>
<CardTitle>4. Analyse-Tools und Tools von Drittanbietern</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<h3 className="font-semibold text-lg mb-2">Google Analytics</h3>
<p className="text-muted-foreground">
Diese Website nutzt Funktionen des Webanalysedienstes Google Analytics. Anbieter ist die Google Ireland Limited, Gordon House, Barrow Street, Dublin 4, Irland.
</p>
<p className="text-muted-foreground mt-2">
Google Analytics verwendet so genannte "Cookies". Das sind Textdateien, die auf Ihrem Computer gespeichert werden und die eine Analyse der Benutzung der Website durch Sie ermöglichen. Die durch das Cookie erzeugten Informationen über Ihre Benutzung dieser Website werden in der Regel an einen Server von Google in den USA übertragen und dort gespeichert.
</p>
</div>
</CardContent>
</Card>
<Card className="mb-8">
<CardHeader>
<CardTitle>5. Ihre Rechte</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<h3 className="font-semibold text-lg mb-2">Widerruf Ihrer Einwilligung zur Datenverarbeitung</h3>
<p className="text-muted-foreground">
Viele Datenverarbeitungsvorgänge sind nur mit Ihrer ausdrücklichen Einwilligung möglich. Sie können eine bereits erteilte Einwilligung jederzeit widerrufen. Die Rechtmäßigkeit der bis zum Widerruf erfolgten Datenverarbeitung bleibt vom Widerruf unberührt.
</p>
</div>
<div>
<h3 className="font-semibold text-lg mb-2">Recht auf Datenübertragbarkeit</h3>
<p className="text-muted-foreground">
Sie haben das Recht, Daten, die wir auf Grundlage Ihrer Einwilligung oder in Erfüllung eines Vertrags automatisiert verarbeiten, an sich oder an einen Dritten in einem gängigen, maschinenlesbaren Format aushändigen zu lassen. Sofern Sie die direkte Übertragung der Daten an einen anderen Verantwortlichen verlangen, erfolgt dies nur, soweit es technisch machbar ist.
</p>
</div>
<div>
<h3 className="font-semibold text-lg mb-2">SSL- bzw. TLS-Verschlüsselung</h3>
<p className="text-muted-foreground">
Diese Seite nutzt aus Sicherheitsgründen und zum Schutz der Übertragung vertraulicher Inhalte, wie zum Beispiel Bestellungen oder Anfragen, die Sie an uns als Seitenbetreiber senden, eine SSL- bzw. TLS-Verschlüsselung. Eine verschlüsselte Verbindung erkennen Sie daran, dass die Adresszeile des Browsers von "http://" auf "https://" wechselt und an dem Schloss-Symbol in Ihrer Browserzeile.
</p>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>6. Kontakt</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">
Bei Fragen zum Datenschutz können Sie uns über die auf dieser Website verfügbaren Kontaktmöglichkeiten erreichen.
</p>
</CardContent>
</Card>
</div>
</div>
<Footer />
</div>
);
};
export default Datenschutz;

49
src/pages/Impressum.tsx Normal file
View File

@ -0,0 +1,49 @@
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import Header from "@/components/Header";
import Footer from "@/components/Footer";
const Impressum = () => {
return (
<div className="min-h-screen bg-background">
<Header />
<div className="container mx-auto px-4 py-16 mt-16">
<div className="max-w-4xl mx-auto">
<h1 className="text-4xl font-bold text-foreground mb-8">Impressum</h1>
<Card className="mb-8">
<CardHeader>
<CardTitle>Angaben gemäß § 5 TMG</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<h3 className="font-semibold text-lg mb-2">Betreiber dieser Website</h3>
<p className="text-muted-foreground">
Diese Website wird von einem privaten Betreiber geführt.
</p>
</div>
<div>
<h3 className="font-semibold text-lg mb-2">Haftungsausschluss</h3>
<p className="text-muted-foreground">
Die Inhalte dieser Website dienen der allgemeinen Information. Wir übernehmen keine Gewähr für die Vollständigkeit, Richtigkeit oder Aktualität der bereitgestellten Informationen.
</p>
</div>
<div>
<h3 className="font-semibold text-lg mb-2">Urheberrecht</h3>
<p className="text-muted-foreground">
Die Inhalte dieser Website unterliegen dem deutschen Urheberrecht. Die Vervielfältigung, Bearbeitung, Verbreitung und jede Art der Verwertung außerhalb der Grenzen des Urheberrechtes bedürfen der schriftlichen Zustimmung des jeweiligen Autors bzw. Erstellers.
</p>
</div>
</CardContent>
</Card>
</div>
</div>
<Footer />
</div>
);
};
export default Impressum;

View File

@ -3,13 +3,17 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
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 { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { SimpleSelect, SimpleSelectItem } from "@/components/ui/simple-select";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Checkbox } from "@/components/ui/checkbox"; import { Checkbox } from "@/components/ui/checkbox";
import { Building2, MapPin, Phone, Mail, Globe, CheckCircle, Users, Award, Clock } from "lucide-react"; import { Building2, MapPin, Phone, Mail, Globe, CheckCircle, Users, Award, Clock } from "lucide-react";
import Header from "@/components/Header"; import Header from "@/components/Header";
import { usePostHog } from "@/hooks/usePostHog";
import { sendCompanyRegistrationEmail } from "@/lib/emailService";
const UnternehmenListen = () => { const UnternehmenListen = () => {
const posthog = usePostHog();
const [formData, setFormData] = useState({ const [formData, setFormData] = useState({
companyName: "", companyName: "",
contactPerson: "", contactPerson: "",
@ -37,6 +41,14 @@ const UnternehmenListen = () => {
...prev, ...prev,
[field]: value [field]: value
})); }));
// Track form interactions
if (field === 'experience') {
posthog.capture('experience_selected', {
experience_level: value,
form_section: 'company_registration'
});
}
}; };
const handleEnergyTypeChange = (type: string, checked: boolean) => { const handleEnergyTypeChange = (type: string, checked: boolean) => {
@ -51,6 +63,13 @@ const UnternehmenListen = () => {
energyTypes: prev.energyTypes.filter(t => t !== type) energyTypes: prev.energyTypes.filter(t => t !== type)
})); }));
} }
// Track energy type selection
posthog.capture('energy_type_selected', {
energy_type: type,
selected: checked,
form_section: 'company_registration'
});
}; };
const handleServiceChange = (service: string, checked: boolean) => { const handleServiceChange = (service: string, checked: boolean) => {
@ -65,14 +84,63 @@ const UnternehmenListen = () => {
services: prev.services.filter(s => s !== service) services: prev.services.filter(s => s !== service)
})); }));
} }
// Track service selection
posthog.capture('service_selected', {
service: service,
selected: checked,
form_section: 'company_registration'
});
}; };
const handleSubmit = async (e: React.FormEvent) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setIsSubmitting(true); setIsSubmitting(true);
// Simulate API call // Track form submission in PostHog
await new Promise(resolve => setTimeout(resolve, 2000)); posthog.capture('company_registration_started', {
company_name: formData.companyName,
energy_types: formData.energyTypes,
services: formData.services,
experience: formData.experience,
location: `${formData.zipCode} ${formData.city}`
});
try {
// Send email notification via webhook
const result = await sendCompanyRegistrationEmail(formData);
if (result.success) {
console.log('✅ E-Mail erfolgreich an Webhook gesendet!');
console.log('📧 Daten wurden an knuth.timo@gmail.com weitergeleitet');
// Track successful submission
posthog.capture('company_registration_completed', {
company_name: formData.companyName,
energy_types: formData.energyTypes,
services: formData.services,
experience: formData.experience,
location: `${formData.zipCode} ${formData.city}`
});
} else {
console.log('⚠️ Webhook-Fehler, aber Daten wurden geloggt:', result.data);
// Track failed submission
posthog.capture('company_registration_failed', {
company_name: formData.companyName,
error: result.error || 'Webhook failed'
});
}
} catch (error) {
console.error('❌ Fehler beim Senden der E-Mail:', error);
// Track failed submission
posthog.capture('company_registration_failed', {
company_name: formData.companyName,
error: error.message
});
}
setIsSubmitting(false); setIsSubmitting(false);
setIsSubmitted(true); setIsSubmitted(true);
@ -266,17 +334,16 @@ const UnternehmenListen = () => {
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="experience">Jahre Erfahrung *</Label> <Label htmlFor="experience">Jahre Erfahrung *</Label>
<Select value={formData.experience} onValueChange={(value) => handleInputChange("experience", value)}> <SimpleSelect
<SelectTrigger> value={formData.experience}
<SelectValue placeholder="Erfahrung wählen" /> onValueChange={(value) => handleInputChange("experience", value)}
</SelectTrigger> placeholder="Erfahrung wählen"
<SelectContent> >
<SelectItem value="0-2">0-2 Jahre</SelectItem> <SimpleSelectItem value="0-2">0-2 Jahre</SimpleSelectItem>
<SelectItem value="3-5">3-5 Jahre</SelectItem> <SimpleSelectItem value="3-5">3-5 Jahre</SimpleSelectItem>
<SelectItem value="6-10">6-10 Jahre</SelectItem> <SimpleSelectItem value="6-10">6-10 Jahre</SimpleSelectItem>
<SelectItem value="10+">Über 10 Jahre</SelectItem> <SimpleSelectItem value="10+">Über 10 Jahre</SimpleSelectItem>
</SelectContent> </SimpleSelect>
</Select>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="coverageArea">Einzugsgebiet</Label> <Label htmlFor="coverageArea">Einzugsgebiet</Label>