chore: Docs umstrukturiert, Client-Updates, Scripts nach scripts/

This commit is contained in:
2026-01-28 20:00:37 +01:00
parent 4b38da3b85
commit 5ba12cb738
70 changed files with 1240 additions and 284 deletions

97
AUFRÄUMEN.md Normal file
View File

@@ -0,0 +1,97 @@
# 🧹 Aufräumen - Dateien organisieren
Diese Datei listet alle Dateien auf, die aufgeräumt/entfernt werden können.
## ✅ Was wurde gemacht
1. ✅ Webhook-Dokumentation in `docs/deployment/` organisiert
2. ✅ Scripts-README aktualisiert
3. ✅ Deployment-Dokumentation strukturiert
4. ✅ Projekt-Ordnung dokumentiert (`PROJEKT_ORDNUNG.md`)
## 🗑️ Dateien die entfernt werden können
### Scripts (veraltete Git-Commit-Scripts)
Diese können gelöscht werden, da Git-Commits direkt über `git commit` gemacht werden sollten:
```bash
scripts/git-commit.bat
scripts/git-commit.sh
scripts/git-commit-fix.bat
scripts/FINAL_COMMIT.bat
scripts/run-git-commit.ps1
scripts/COMMIT_COMMANDS.txt
scripts/COMMIT_MESSAGE.md
```
**Befehl zum Entfernen:**
```bash
cd scripts
rm git-commit.bat git-commit.sh git-commit-fix.bat FINAL_COMMIT.bat run-git-commit.ps1 COMMIT_COMMANDS.txt COMMIT_MESSAGE.md
```
### Dokumentation (kann archiviert werden)
Diese Task-spezifischen Dokumentationsdateien können in `docs/archive/` verschoben werden:
```bash
docs/development/TASK_5_COMPLETION.md
docs/server/TASK_4_COMPLETION_SUMMARY.md
docs/development/PROJECT_REVIEW_SUMMARY.md
```
**Befehl zum Archivieren:**
```bash
mkdir -p docs/archive
mv docs/development/TASK_5_COMPLETION.md docs/archive/
mv docs/server/TASK_4_COMPLETION_SUMMARY.md docs/archive/
mv docs/development/PROJECT_REVIEW_SUMMARY.md docs/archive/
```
## 📋 Optionale Aufräumarbeiten
### Temporäre Dateien im Root
Falls es temporäre Markdown-Dateien im Root gibt (z.B. von mir erstellt), können diese entfernt werden, nachdem die Informationen in die richtige Dokumentation übernommen wurden:
- `GITEA_WEBHOOK_ZUSAMMENFASSUNG.md` (falls vorhanden)
- `GITEA_AUTHORIZATION_HEADER.md` (falls vorhanden)
- `DEPLOYMENT_STATUS.md` (falls vorhanden)
- `DEPLOYMENT_KONFIGURIERT.md` (falls vorhanden)
**Hinweis:** Diese sollten bereits in `docs/deployment/` sein.
## ✅ Wichtige Dateien (NICHT löschen!)
Diese Dateien müssen bleiben:
- Alle Dateien in `client/`, `server/`, `docs/` (außer oben genannte)
- `README.md`, `STRUCTURE.md`, `PROJEKT_ORDNUNG.md`
- Alle Konfigurationsdateien (`.gitignore`, `package.json`, etc.)
- Alle aktiven Scripts (`deploy-to-server.mjs`, `setup-*.ps1`)
## 🎯 Empfehlung
1. **Sofort entfernen:** Veraltete Git-Commit-Scripts (werden nicht mehr benötigt)
2. **Archivieren:** Task-spezifische Dokumentation (für Referenz behalten)
3. **Prüfen:** Temporäre Root-Dateien (falls vorhanden, nach Übernahme entfernen)
## 📝 Nach dem Aufräumen
Nach dem Aufräumen sollte die Struktur sauberer sein:
```
scripts/
├── deploy-to-server.mjs ✅
├── setup-appwrite.ps1 ✅
├── setup-production.ps1 ✅
└── README.md ✅
docs/
├── deployment/ ✅ (vollständig organisiert)
├── setup/ ✅
├── development/ ✅ (teilweise archiviert)
├── server/ ✅ (teilweise archiviert)
└── archive/ ✅ (neu, für veraltete Docs)
```

142
PROJEKT_ORDNUNG.md Normal file
View File

@@ -0,0 +1,142 @@
# 📁 Projekt-Ordnung und Dateistruktur
Diese Datei beschreibt die Organisation aller Dateien im Projekt.
## ✅ Wichtige Dateien (behalten)
### Root-Verzeichnis
- **README.md** - Hauptdokumentation des Projekts
- **STRUCTURE.md** - Detaillierte Projektstruktur
- **.gitignore** - Git-Ignore-Regeln
- **.env.example** - Beispiel-Umgebungsvariablen
### Client (`client/`)
- Alle Source-Dateien in `src/`
- Konfigurationsdateien (`package.json`, `vite.config.ts`, etc.)
- **README.md** - Client-spezifische Dokumentation
### Server (`server/`)
- Alle Backend-Dateien
- **routes/** - API-Routen (inkl. `webhook.mjs` für automatisches Deployment)
- **config/** - Konfiguration
- **.env** - Umgebungsvariablen (nicht im Git!)
### Dokumentation (`docs/`)
- **deployment/** - Deployment-Anleitungen
- `GITEA_WEBHOOK_SETUP.md` - Vollständige Webhook-Anleitung
- `WEBHOOK_QUICK_START.md` - Schnellstart
- `WEBHOOK_AUTHORIZATION.md` - Authentifizierung
- `DEPLOYMENT_INSTRUCTIONS.md` - Manuelles Deployment
- `PRODUCTION_SETUP.md` - Production-Setup
- `PRODUCTION_FIXES.md` - Bekannte Probleme
- **setup/** - Setup-Anleitungen
- **development/** - Development-Dokumentation
- **server/** - Server-Dokumentation
### Scripts (`scripts/`)
- **deploy-to-server.mjs** - Deployment-Skript (wird vom Webhook aufgerufen)
- **setup-*.ps1** - Setup-Scripts
- **README.md** - Scripts-Dokumentation
### Marketing (`marketing/`)
- Alle Marketing-Materialien und Anleitungen
## 🗑️ Kann entfernt werden (temporäre/veraltete Dateien)
### Scripts (`scripts/`)
Diese Git-Commit-Scripts sind veraltet und können entfernt werden:
- `git-commit.bat`
- `git-commit.sh`
- `git-commit-fix.bat`
- `FINAL_COMMIT.bat`
- `run-git-commit.ps1`
- `COMMIT_COMMANDS.txt`
- `COMMIT_MESSAGE.md`
**Grund:** Git-Commits sollten direkt über `git commit` gemacht werden.
### Dokumentation (`docs/`)
Einige temporäre/veraltete Dokumentationsdateien können archiviert werden:
- `development/TASK_5_COMPLETION.md` - Task-spezifisch, kann archiviert werden
- `server/TASK_4_COMPLETION_SUMMARY.md` - Task-spezifisch, kann archiviert werden
- `development/PROJECT_REVIEW_SUMMARY.md` - Review-spezifisch, kann archiviert werden
**Empfehlung:** Verschiebe diese in `docs/archive/` statt zu löschen.
## 📋 Dateien-Organisation
### Aktuelle Struktur
```
/
├── client/ # Frontend
├── server/ # Backend
├── docs/ # Dokumentation
│ ├── deployment/ # Deployment-Docs ✅
│ ├── setup/ # Setup-Docs ✅
│ ├── development/ # Development-Docs (teilweise archivieren)
│ └── server/ # Server-Docs (teilweise archivieren)
├── scripts/ # Scripts
│ ├── deploy-to-server.mjs ✅
│ ├── setup-*.ps1 ✅
│ └── [veraltete Git-Scripts] ❌
├── marketing/ # Marketing ✅
└── README.md # Hauptdokumentation ✅
```
## 🧹 Aufräumen-Empfehlungen
### 1. Veraltete Scripts entfernen
```bash
# Diese Dateien können gelöscht werden:
scripts/git-commit.bat
scripts/git-commit.sh
scripts/git-commit-fix.bat
scripts/FINAL_COMMIT.bat
scripts/run-git-commit.ps1
scripts/COMMIT_COMMANDS.txt
scripts/COMMIT_MESSAGE.md
```
### 2. Temporäre Dokumentation archivieren
Erstelle `docs/archive/` und verschiebe:
- `docs/development/TASK_5_COMPLETION.md`
- `docs/server/TASK_4_COMPLETION_SUMMARY.md`
- `docs/development/PROJECT_REVIEW_SUMMARY.md`
### 3. README aktualisieren
Die `scripts/README.md` wurde bereits aktualisiert.
## ✅ Checkliste
- [x] Webhook-Dokumentation in `docs/deployment/` organisiert
- [x] Scripts-README aktualisiert
- [x] Deployment-Dokumentation strukturiert
- [ ] Veraltete Scripts entfernen (optional)
- [ ] Temporäre Dokumentation archivieren (optional)
## 📝 Wichtige Hinweise
1. **`.env` Dateien** sind nie im Git (siehe `.gitignore`)
2. **Temporäre Anleitungen** können nach erfolgreicher Einrichtung entfernt werden
3. **Task-spezifische Dokumentation** kann archiviert werden, sollte aber nicht gelöscht werden
4. **Alle produktiven Dateien** (Code, Konfiguration, aktive Dokumentation) bleiben erhalten
## 🔄 Regelmäßige Wartung
- **Monatlich:** Prüfe auf veraltete Scripts/Dokumentation
- **Nach großen Features:** Aktualisiere README und Dokumentation
- **Nach Deployment:** Entferne temporäre Deployment-Anleitungen (falls nicht mehr benötigt)

View File

@@ -29,9 +29,19 @@ EmailSorter ist eine SaaS-Anwendung, die automatisch E-Mails kategorisiert und s
│ ├── routes/ # API Routen │ ├── routes/ # API Routen
│ ├── services/ # Business Logic │ ├── services/ # Business Logic
│ └── package.json │ └── package.json
├── docs/ # Dokumentation
│ ├── setup/ # Setup-Anleitungen
│ ├── deployment/ # Deployment-Docs
│ ├── development/ # Development-Docs
│ └── server/ # Server-Dokumentation
├── scripts/ # Hilfs-Scripts
│ ├── git-commit.* # Git-Scripts
│ └── deploy-build.js # Deployment-Scripts
├── marketing/ # Marketing-Materialien
│ └── *.md # Marketing-Dokumentation
├── n8n/ # n8n Workflows ├── n8n/ # n8n Workflows
│ └── workflows/ │ └── workflows/
└── public/ # Legacy Frontend └── README.md # Diese Datei
``` ```
## Quick Start ## Quick Start
@@ -199,6 +209,8 @@ Siehe `n8n/README.md` für Details.
## Deployment ## Deployment
Siehe `docs/deployment/` für detaillierte Deployment-Anleitungen.
### Frontend (Vercel/Netlify) ### Frontend (Vercel/Netlify)
```bash ```bash
@@ -219,6 +231,17 @@ Aktualisiere die Webhook-URL im Stripe Dashboard auf deine Produktions-URL:
https://your-domain.com/api/subscription/webhook https://your-domain.com/api/subscription/webhook
``` ```
## Dokumentation
Alle Dokumentation befindet sich im `docs/` Ordner:
- **Setup:** `docs/setup/` - Setup-Anleitungen für Appwrite, OAuth, etc.
- **Deployment:** `docs/deployment/` - Production-Setup und Deployment
- **Development:** `docs/development/` - Development-Dokumentation
- **Server:** `docs/server/` - Server-spezifische Dokumentation
Siehe `docs/README.md` für eine vollständige Übersicht.
## Troubleshooting ## Troubleshooting
### Frontend startet nicht ### Frontend startet nicht

95
STRUCTURE.md Normal file
View File

@@ -0,0 +1,95 @@
# Projektstruktur-Übersicht
Diese Datei beschreibt die organisierte Struktur des Projekts.
## 📁 Hauptverzeichnisse
### `/client/`
React Frontend-Anwendung
- `src/` - Quellcode
- `public/` - Statische Assets
- `package.json` - Frontend-Dependencies
### `/server/`
Node.js Backend-Server
- `routes/` - API-Routen
- `services/` - Business-Logik
- `middleware/` - Express-Middleware
- `config/` - Konfiguration
- `utils/` - Utility-Funktionen
- `package.json` - Backend-Dependencies
### `/docs/`
Alle Dokumentation
- `setup/` - Setup-Anleitungen (Appwrite, OAuth, etc.)
- `deployment/` - Deployment & Production-Docs
- `development/` - Development-Dokumentation
- `server/` - Server-spezifische Dokumentation
- `examples/` - Beispiel-Code (z.B. starter-for-react)
- `legacy/` - Legacy-Dateien
### `/scripts/`
Hilfs-Scripts für Entwicklung & Deployment
- Git-Scripts (`git-commit.*`, `run-git-commit.ps1`)
- Deployment-Scripts (`deploy-build.js`)
- Setup-Scripts (`setup-appwrite.ps1`, `setup-production.ps1`)
- Commit-Hilfsdateien (`COMMIT_MESSAGE.md`, `COMMIT_COMMANDS.txt`)
### `/marketing/`
Marketing-Materialien und Dokumentation
- Logo-Dateien (SVG)
- Marketing-Guides (TikTok, YouTube, Product Hunt, etc.)
- Influencer-Templates
### `/n8n/`
n8n Workflow-Konfigurationen
- `workflows/` - Workflow-Definitionen
### `/.kiro/`
Kiro-Spezifikationen (Design, Requirements, Tasks)
## 📄 Root-Dateien
- `README.md` - Haupt-README mit Projektübersicht
- `STRUCTURE.md` - Diese Datei
- `.env.example` - Beispiel-Umgebungsvariablen
- `.gitignore` - Git-Ignore-Regeln
## 🎯 Organisationsprinzipien
1. **Dokumentation zentralisiert:** Alle `.md` Dateien sind in `docs/` organisiert
2. **Scripts getrennt:** Alle Scripts sind in `scripts/` gesammelt
3. **Sauberes Root:** Root-Verzeichnis enthält nur essenzielle Dateien
4. **Klare Kategorien:** Dokumentation nach Themen sortiert (setup, deployment, development)
## 📝 Wichtige Dateien
### Setup
- `docs/setup/SETUP_GUIDE.md` - Allgemeine Setup-Anleitung
- `docs/setup/APPWRITE_SETUP.md` - Appwrite-Konfiguration
- `docs/setup/GOOGLE_OAUTH_SETUP.md` - Google OAuth Setup
### Deployment
- `docs/deployment/PRODUCTION_SETUP.md` - Production-Server Setup
- `docs/deployment/DEPLOYMENT_INSTRUCTIONS.md` - Deployment-Anleitung
### Development
- `docs/development/PROJECT_REVIEW_SUMMARY.md` - Projekt-Review
- `docs/development/TESTING_SUMMARY.md` - Testing-Dokumentation
## 🔧 Scripts-Verwendung
Alle Scripts befinden sich in `scripts/`:
```bash
# Git-Commit (Windows)
scripts\git-commit.bat
# Git-Commit (PowerShell)
scripts\run-git-commit.ps1
# Deployment
node scripts\deploy-build.js
```
Siehe `scripts/README.md` für Details.

View File

@@ -21,10 +21,10 @@ initAnalytics()
// Loading spinner component // Loading spinner component
function LoadingSpinner() { function LoadingSpinner() {
return ( return (
<div className="min-h-screen flex items-center justify-center bg-slate-50"> <div className="min-h-screen flex items-center justify-center bg-slate-50 dark:bg-slate-900">
<div className="flex flex-col items-center gap-4"> <div className="flex flex-col items-center gap-4">
<div className="w-10 h-10 border-4 border-primary-200 border-t-primary-600 rounded-full animate-spin" /> <div className="w-10 h-10 border-4 border-primary-200 dark:border-primary-800 border-t-primary-600 dark:border-t-primary-400 rounded-full animate-spin" />
<p className="text-slate-500 text-sm">Loading...</p> <p className="text-slate-500 dark:text-slate-400 text-sm">Loading...</p>
</div> </div>
</div> </div>
) )

View File

@@ -41,17 +41,17 @@ export function FAQ() {
const [openIndex, setOpenIndex] = useState<number | null>(0) const [openIndex, setOpenIndex] = useState<number | null>(0)
return ( return (
<section id="faq" className="py-24 bg-slate-50"> <section id="faq" className="py-24 bg-slate-50 dark:bg-slate-900">
<div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Section header */} {/* Section header */}
<div className="text-center mb-16"> <div className="text-center mb-16">
<div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-100 mb-6"> <div className="inline-flex items-center justify-center w-16 h-16 rounded-2xl bg-primary-100 dark:bg-primary-900/30 mb-6">
<HelpCircle className="w-8 h-8 text-primary-600" /> <HelpCircle className="w-8 h-8 text-primary-600 dark:text-primary-400" />
</div> </div>
<h2 className="text-3xl sm:text-4xl font-bold text-slate-900 mb-4"> <h2 className="text-3xl sm:text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
FAQ FAQ
</h2> </h2>
<p className="text-lg text-slate-600"> <p className="text-lg text-slate-600 dark:text-slate-400">
Quick answers to common questions. Quick answers to common questions.
</p> </p>
</div> </div>
@@ -70,11 +70,11 @@ export function FAQ() {
</div> </div>
{/* Contact CTA */} {/* Contact CTA */}
<div className="mt-12 text-center p-6 bg-white rounded-2xl border border-slate-200"> <div className="mt-12 text-center p-6 bg-white dark:bg-slate-800 rounded-2xl border border-slate-200 dark:border-slate-700">
<p className="text-slate-600 mb-2">Still have questions?</p> <p className="text-slate-600 dark:text-slate-400 mb-2">Still have questions?</p>
<a <a
href="mailto:support@emailsorter.com" href="mailto:support@emailsorter.com"
className="text-primary-600 font-semibold hover:text-primary-700" className="text-primary-600 dark:text-primary-400 font-semibold hover:text-primary-700 dark:hover:text-primary-300"
> >
Contact us Contact us
</a> </a>
@@ -93,15 +93,15 @@ interface FAQItemProps {
function FAQItem({ question, answer, isOpen, onClick }: FAQItemProps) { function FAQItem({ question, answer, isOpen, onClick }: FAQItemProps) {
return ( return (
<div className="bg-white rounded-xl border border-slate-200 overflow-hidden"> <div className="bg-white dark:bg-slate-800 rounded-xl border border-slate-200 dark:border-slate-700 overflow-hidden">
<button <button
className="w-full px-6 py-4 text-left flex items-center justify-between hover:bg-slate-50 transition-colors" className="w-full px-6 py-4 text-left flex items-center justify-between hover:bg-slate-50 dark:hover:bg-slate-700 transition-colors"
onClick={onClick} onClick={onClick}
> >
<span className="font-semibold text-slate-900 pr-4">{question}</span> <span className="font-semibold text-slate-900 dark:text-slate-100 pr-4">{question}</span>
<ChevronDown <ChevronDown
className={cn( className={cn(
"w-5 h-5 text-slate-400 transition-transform duration-200 flex-shrink-0", "w-5 h-5 text-slate-400 dark:text-slate-500 transition-transform duration-200 flex-shrink-0",
isOpen && "rotate-180" isOpen && "rotate-180"
)} )}
/> />
@@ -112,7 +112,7 @@ function FAQItem({ question, answer, isOpen, onClick }: FAQItemProps) {
isOpen ? "max-h-40" : "max-h-0" isOpen ? "max-h-40" : "max-h-0"
)} )}
> >
<p className="px-6 pb-4 text-slate-600">{answer}</p> <p className="px-6 pb-4 text-slate-600 dark:text-slate-400">{answer}</p>
</div> </div>
</div> </div>
) )

View File

@@ -52,17 +52,17 @@ const features = [
export function Features() { export function Features() {
return ( return (
<section id="features" className="py-24 bg-slate-50"> <section id="features" className="py-24 bg-slate-50 dark:bg-slate-900">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Section header */} {/* Section header */}
<div className="text-center mb-16"> <div className="text-center mb-16">
<h2 className="text-3xl sm:text-4xl font-bold text-slate-900 mb-4"> <h2 className="text-3xl sm:text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
Everything you need for{' '} Everything you need for{' '}
<span className="text-transparent bg-clip-text bg-gradient-to-r from-primary-600 to-accent-500"> <span className="text-transparent bg-clip-text bg-gradient-to-r from-primary-600 to-accent-500">
Inbox Zero Inbox Zero
</span> </span>
</h2> </h2>
<p className="text-lg text-slate-600 max-w-2xl mx-auto"> <p className="text-lg text-slate-600 dark:text-slate-400 max-w-2xl mx-auto">
EmailSorter combines AI technology with proven email management methods EmailSorter combines AI technology with proven email management methods
for maximum productivity. for maximum productivity.
</p> </p>
@@ -77,17 +77,17 @@ export function Features() {
{/* Bottom illustration */} {/* Bottom illustration */}
<div className="mt-20 relative"> <div className="mt-20 relative">
<div className="bg-white rounded-3xl border border-slate-200 shadow-xl p-8 max-w-4xl mx-auto"> <div className="bg-white dark:bg-slate-800 rounded-3xl border border-slate-200 dark:border-slate-700 shadow-xl p-8 max-w-4xl mx-auto">
<div className="grid md:grid-cols-3 gap-8 items-center"> <div className="grid md:grid-cols-3 gap-8 items-center">
{/* Before */} {/* Before */}
<div className="text-center"> <div className="text-center">
<div className="w-20 h-20 mx-auto mb-4 rounded-2xl bg-red-100 flex items-center justify-center"> <div className="w-20 h-20 mx-auto mb-4 rounded-2xl bg-red-100 dark:bg-red-900/30 flex items-center justify-center">
<Inbox className="w-10 h-10 text-red-500" /> <Inbox className="w-10 h-10 text-red-500 dark:text-red-400" />
</div> </div>
<h4 className="font-semibold text-slate-900 mb-1">Before</h4> <h4 className="font-semibold text-slate-900 dark:text-slate-100 mb-1">Before</h4>
<p className="text-sm text-slate-500">Inbox chaos</p> <p className="text-sm text-slate-500 dark:text-slate-400">Inbox chaos</p>
<div className="mt-3 text-3xl font-bold text-red-500">847</div> <div className="mt-3 text-3xl font-bold text-red-500 dark:text-red-400">847</div>
<p className="text-xs text-slate-400">unread emails</p> <p className="text-xs text-slate-400 dark:text-slate-500">unread emails</p>
</div> </div>
{/* Arrow */} {/* Arrow */}
@@ -99,13 +99,13 @@ export function Features() {
{/* After */} {/* After */}
<div className="text-center"> <div className="text-center">
<div className="w-20 h-20 mx-auto mb-4 rounded-2xl bg-green-100 flex items-center justify-center"> <div className="w-20 h-20 mx-auto mb-4 rounded-2xl bg-green-100 dark:bg-green-900/30 flex items-center justify-center">
<Inbox className="w-10 h-10 text-green-500" /> <Inbox className="w-10 h-10 text-green-500 dark:text-green-400" />
</div> </div>
<h4 className="font-semibold text-slate-900 mb-1">After</h4> <h4 className="font-semibold text-slate-900 dark:text-slate-100 mb-1">After</h4>
<p className="text-sm text-slate-500">All sorted</p> <p className="text-sm text-slate-500 dark:text-slate-400">All sorted</p>
<div className="mt-3 text-3xl font-bold text-green-500">12</div> <div className="mt-3 text-3xl font-bold text-green-500 dark:text-green-400">12</div>
<p className="text-xs text-slate-400">important emails</p> <p className="text-xs text-slate-400 dark:text-slate-500">important emails</p>
</div> </div>
</div> </div>
</div> </div>
@@ -129,16 +129,16 @@ function FeatureCard({ icon: Icon, title, description, color, index, highlight }
<div <div
className={`group rounded-2xl p-6 border transition-all duration-300 ${ className={`group rounded-2xl p-6 border transition-all duration-300 ${
highlight highlight
? 'bg-gradient-to-br from-white to-slate-50 border-primary-200 hover:border-primary-300 hover:shadow-xl' ? 'bg-gradient-to-br from-white dark:from-slate-800 to-slate-50 dark:to-slate-800/50 border-primary-200 dark:border-primary-800 hover:border-primary-300 dark:hover:border-primary-700 hover:shadow-xl'
: 'bg-white border-slate-200 hover:border-primary-200 hover:shadow-lg' : 'bg-white dark:bg-slate-800 border-slate-200 dark:border-slate-700 hover:border-primary-200 dark:hover:border-primary-800 hover:shadow-lg'
}`} }`}
style={{ animationDelay: `${index * 0.1}s` }} style={{ animationDelay: `${index * 0.1}s` }}
> >
<div className={`w-14 h-14 rounded-xl bg-gradient-to-br ${color} flex items-center justify-center mb-5 group-hover:scale-110 transition-transform duration-300 shadow-lg`}> <div className={`w-14 h-14 rounded-xl bg-gradient-to-br ${color} flex items-center justify-center mb-5 group-hover:scale-110 transition-transform duration-300 shadow-lg`}>
<Icon className="w-7 h-7 text-white" /> <Icon className="w-7 h-7 text-white" />
</div> </div>
<h3 className={`${highlight ? 'text-2xl' : 'text-xl'} font-semibold text-slate-900 mb-2`}>{title}</h3> <h3 className={`${highlight ? 'text-2xl' : 'text-xl'} font-semibold text-slate-900 dark:text-slate-100 mb-2`}>{title}</h3>
<p className="text-slate-600">{description}</p> <p className="text-slate-600 dark:text-slate-400">{description}</p>
</div> </div>
) )
} }

View File

@@ -35,17 +35,17 @@ const steps = [
export function HowItWorks() { export function HowItWorks() {
return ( return (
<section id="how-it-works" className="py-24 bg-white"> <section id="how-it-works" className="py-24 bg-white dark:bg-slate-900">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Section header */} {/* Section header */}
<div className="text-center mb-16"> <div className="text-center mb-16">
<h2 className="text-3xl sm:text-4xl font-bold text-slate-900 mb-4"> <h2 className="text-3xl sm:text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
4 steps to a{' '} 4 steps to a{' '}
<span className="text-transparent bg-clip-text bg-gradient-to-r from-primary-600 to-accent-500"> <span className="text-transparent bg-clip-text bg-gradient-to-r from-primary-600 to-accent-500">
clean inbox clean inbox
</span> </span>
</h2> </h2>
<p className="text-lg text-slate-600 max-w-2xl mx-auto"> <p className="text-lg text-slate-600 dark:text-slate-400 max-w-2xl mx-auto">
Get started in minutes no technical knowledge required. Get started in minutes no technical knowledge required.
</p> </p>
</div> </div>
@@ -65,11 +65,11 @@ export function HowItWorks() {
{/* CTA */} {/* CTA */}
<div className="mt-16 text-center"> <div className="mt-16 text-center">
<div className="inline-flex flex-col items-center"> <div className="inline-flex flex-col items-center">
<ArrowDown className="w-8 h-8 text-primary-400 animate-bounce mb-4" /> <ArrowDown className="w-8 h-8 text-primary-400 dark:text-primary-500 animate-bounce mb-4" />
<p className="text-slate-600 mb-2">Ready to get started?</p> <p className="text-slate-600 dark:text-slate-400 mb-2">Ready to get started?</p>
<a <a
href="/register" href="/register"
className="text-primary-600 font-semibold hover:text-primary-700 transition-colors" className="text-primary-600 dark:text-primary-400 font-semibold hover:text-primary-700 dark:hover:text-primary-300 transition-colors"
> >
Try it free now Try it free now
</a> </a>
@@ -91,20 +91,20 @@ function StepCard({ icon: Icon, step, title, description }: StepCardProps) {
return ( return (
<div className="relative"> <div className="relative">
{/* Card */} {/* Card */}
<div className="bg-slate-50 rounded-2xl p-6 text-center hover:bg-white hover:shadow-xl transition-all duration-300 border border-transparent hover:border-slate-200"> <div className="bg-slate-50 dark:bg-slate-800 rounded-2xl p-6 text-center hover:bg-white dark:hover:bg-slate-700 hover:shadow-xl transition-all duration-300 border border-transparent hover:border-slate-200 dark:hover:border-slate-600">
{/* Step number */} {/* Step number */}
<div className="absolute -top-4 left-1/2 -translate-x-1/2 bg-gradient-to-r from-primary-500 to-primary-600 text-white text-sm font-bold px-4 py-1 rounded-full shadow-md"> <div className="absolute -top-4 left-1/2 -translate-x-1/2 bg-gradient-to-r from-primary-500 to-primary-600 dark:from-primary-600 dark:to-primary-700 text-white text-sm font-bold px-4 py-1 rounded-full shadow-md">
{step} {step}
</div> </div>
{/* Icon */} {/* Icon */}
<div className="w-16 h-16 mx-auto mt-4 mb-4 rounded-2xl bg-white shadow-md flex items-center justify-center"> <div className="w-16 h-16 mx-auto mt-4 mb-4 rounded-2xl bg-white dark:bg-slate-700 shadow-md flex items-center justify-center">
<Icon className="w-8 h-8 text-primary-600" /> <Icon className="w-8 h-8 text-primary-600 dark:text-primary-400" />
</div> </div>
{/* Content */} {/* Content */}
<h3 className="text-lg font-semibold text-slate-900 mb-2">{title}</h3> <h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-2">{title}</h3>
<p className="text-slate-600 text-sm">{description}</p> <p className="text-slate-600 dark:text-slate-400 text-sm">{description}</p>
</div> </div>
</div> </div>
) )

View File

@@ -64,7 +64,7 @@ export function Pricing() {
const navigate = useNavigate() const navigate = useNavigate()
return ( return (
<section id="pricing" className="py-24 bg-slate-50"> <section id="pricing" className="py-24 bg-slate-50 dark:bg-slate-900">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
{/* Section header */} {/* Section header */}
<div className="text-center mb-16"> <div className="text-center mb-16">
@@ -72,10 +72,10 @@ export function Pricing() {
<Sparkles className="w-3 h-3 mr-1" /> <Sparkles className="w-3 h-3 mr-1" />
14-day free trial 14-day free trial
</Badge> </Badge>
<h2 className="text-3xl sm:text-4xl font-bold text-slate-900 mb-4"> <h2 className="text-3xl sm:text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">
Simple, transparent pricing Simple, transparent pricing
</h2> </h2>
<p className="text-lg text-slate-600 max-w-2xl mx-auto"> <p className="text-lg text-slate-600 dark:text-slate-400 max-w-2xl mx-auto">
Choose the plan that fits you. Cancel anytime, no hidden costs. Choose the plan that fits you. Cancel anytime, no hidden costs.
</p> </p>
</div> </div>
@@ -93,11 +93,11 @@ export function Pricing() {
{/* FAQ teaser */} {/* FAQ teaser */}
<div className="mt-16 text-center"> <div className="mt-16 text-center">
<p className="text-slate-600"> <p className="text-slate-600 dark:text-slate-400">
Still have questions?{' '} Still have questions?{' '}
<button <button
onClick={() => document.getElementById('faq')?.scrollIntoView({ behavior: 'smooth' })} onClick={() => document.getElementById('faq')?.scrollIntoView({ behavior: 'smooth' })}
className="text-primary-600 font-semibold hover:text-primary-700" className="text-primary-600 dark:text-primary-400 font-semibold hover:text-primary-700 dark:hover:text-primary-300"
> >
Check our FAQ Check our FAQ
</button> </button>
@@ -131,15 +131,15 @@ function PricingCard({
}: PricingCardProps) { }: PricingCardProps) {
return ( return (
<div <div
className={`relative bg-white rounded-2xl p-8 ${ className={`relative bg-white dark:bg-slate-800 rounded-2xl p-8 ${
popular popular
? 'ring-2 ring-primary-500 shadow-xl scale-105' ? 'ring-2 ring-primary-500 dark:ring-primary-400 shadow-xl scale-105'
: 'border border-slate-200 hover:border-primary-200 hover:shadow-lg' : 'border border-slate-200 dark:border-slate-700 hover:border-primary-200 dark:hover:border-primary-800 hover:shadow-lg'
} transition-all duration-300`} } transition-all duration-300`}
> >
{popular && ( {popular && (
<div className="absolute -top-4 left-1/2 -translate-x-1/2"> <div className="absolute -top-4 left-1/2 -translate-x-1/2">
<Badge className="bg-primary-500 text-white border-0 shadow-md"> <Badge className="bg-primary-500 dark:bg-primary-600 text-white border-0 shadow-md">
Most Popular Most Popular
</Badge> </Badge>
</div> </div>
@@ -147,15 +147,15 @@ function PricingCard({
{/* Header */} {/* Header */}
<div className="text-center mb-6"> <div className="text-center mb-6">
<h3 className="text-xl font-bold text-slate-900 mb-1">{name}</h3> <h3 className="text-xl font-bold text-slate-900 dark:text-slate-100 mb-1">{name}</h3>
<p className="text-sm text-slate-500">{description}</p> <p className="text-sm text-slate-500 dark:text-slate-400">{description}</p>
</div> </div>
{/* Price */} {/* Price */}
<div className="text-center mb-8"> <div className="text-center mb-8">
<div className="flex items-baseline justify-center"> <div className="flex items-baseline justify-center">
<span className="text-5xl font-extrabold text-slate-900">${price}</span> <span className="text-5xl font-extrabold text-slate-900 dark:text-slate-100">${price}</span>
<span className="text-slate-500 ml-1">{period}</span> <span className="text-slate-500 dark:text-slate-400 ml-1">{period}</span>
</div> </div>
</div> </div>
@@ -164,15 +164,15 @@ function PricingCard({
{features.map((feature, index) => ( {features.map((feature, index) => (
<li key={index} className="flex items-center gap-3"> <li key={index} className="flex items-center gap-3">
{feature.included ? ( {feature.included ? (
<div className="w-5 h-5 rounded-full bg-green-100 flex items-center justify-center flex-shrink-0"> <div className="w-5 h-5 rounded-full bg-green-100 dark:bg-green-900/30 flex items-center justify-center flex-shrink-0">
<Check className="w-3 h-3 text-green-600" /> <Check className="w-3 h-3 text-green-600 dark:text-green-400" />
</div> </div>
) : ( ) : (
<div className="w-5 h-5 rounded-full bg-slate-100 flex items-center justify-center flex-shrink-0"> <div className="w-5 h-5 rounded-full bg-slate-100 dark:bg-slate-700 flex items-center justify-center flex-shrink-0">
<X className="w-3 h-3 text-slate-400" /> <X className="w-3 h-3 text-slate-400 dark:text-slate-500" />
</div> </div>
)} )}
<span className={feature.included ? 'text-slate-700' : 'text-slate-400'}> <span className={feature.included ? 'text-slate-700 dark:text-slate-300' : 'text-slate-400 dark:text-slate-500'}>
{feature.text} {feature.text}
</span> </span>
</li> </li>

View File

@@ -18,9 +18,19 @@ export { ID }
export const auth = { export const auth = {
// Create a new account // Create a new account
async register(email: string, password: string, name?: string) { async register(email: string, password: string, name?: string) {
// #region agent log
fetch('http://127.0.0.1:7242/ingest/4fa7412d-6f79-4871-8728-29c37c9e5772',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'appwrite.ts:20',message:'register called',data:{endpoint:APPWRITE_ENDPOINT,origin:window.location.origin,email},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{});
// #endregion
try {
const user = await account.create(ID.unique(), email, password, name) const user = await account.create(ID.unique(), email, password, name)
await this.login(email, password) await this.login(email, password)
return user return user
} catch (error) {
// #region agent log
fetch('http://127.0.0.1:7242/ingest/4fa7412d-6f79-4871-8728-29c37c9e5772',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({location:'appwrite.ts:23',message:'register error',data:{errorMessage:error instanceof Error ? error.message : 'Unknown error'},timestamp:Date.now(),sessionId:'debug-session',runId:'run1',hypothesisId:'A'})}).catch(()=>{});
// #endregion
throw error
}
}, },
// Login with email and password // Login with email and password

View File

@@ -29,22 +29,22 @@ export function ForgotPassword() {
} }
return ( return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 flex items-center justify-center p-4"> <div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 flex items-center justify-center p-4">
<div className="w-full max-w-md"> <div className="w-full max-w-md">
{/* Logo */} {/* Logo */}
<Link to="/" className="flex items-center justify-center gap-2 mb-8"> <Link to="/" className="flex items-center justify-center gap-2 mb-8">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center"> <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center">
<Mail className="w-5 h-5 text-white" /> <Mail className="w-5 h-5 text-white" />
</div> </div>
<span className="text-xl font-bold text-slate-900"> <span className="text-xl font-bold text-slate-900 dark:text-slate-100">
Email<span className="text-primary-600">Sorter</span> Email<span className="text-primary-600 dark:text-primary-400">Sorter</span>
</span> </span>
</Link> </Link>
<Card className="shadow-xl border-0"> <Card className="shadow-xl border-0 dark:bg-slate-800 dark:border-slate-700">
<CardHeader className="text-center pb-2"> <CardHeader className="text-center pb-2">
<CardTitle className="text-2xl">Passwort vergessen?</CardTitle> <CardTitle className="text-2xl dark:text-slate-100">Passwort vergessen?</CardTitle>
<CardDescription> <CardDescription className="dark:text-slate-400">
{sent {sent
? 'Prüfe dein E-Mail-Postfach' ? 'Prüfe dein E-Mail-Postfach'
: 'Gib deine E-Mail-Adresse ein und wir senden dir einen Link zum Zurücksetzen.' : 'Gib deine E-Mail-Adresse ein und wir senden dir einen Link zum Zurücksetzen.'
@@ -54,14 +54,14 @@ export function ForgotPassword() {
<CardContent> <CardContent>
{sent ? ( {sent ? (
<div className="text-center py-8"> <div className="text-center py-8">
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-green-100 flex items-center justify-center"> <div className="w-16 h-16 mx-auto mb-4 rounded-full bg-green-100 dark:bg-green-900/30 flex items-center justify-center">
<CheckCircle className="w-8 h-8 text-green-600" /> <CheckCircle className="w-8 h-8 text-green-600 dark:text-green-400" />
</div> </div>
<h3 className="font-semibold text-slate-900 mb-2">E-Mail gesendet!</h3> <h3 className="font-semibold text-slate-900 dark:text-slate-100 mb-2">E-Mail gesendet!</h3>
<p className="text-slate-600 mb-6"> <p className="text-slate-600 dark:text-slate-400 mb-6">
Wir haben dir eine E-Mail mit einem Link zum Zurücksetzen deines Passworts an <strong>{email}</strong> gesendet. Wir haben dir eine E-Mail mit einem Link zum Zurücksetzen deines Passworts an <strong className="text-slate-900 dark:text-slate-100">{email}</strong> gesendet.
</p> </p>
<p className="text-sm text-slate-500 mb-6"> <p className="text-sm text-slate-500 dark:text-slate-400 mb-6">
Keine E-Mail erhalten? Prüfe deinen Spam-Ordner oder versuche es erneut. Keine E-Mail erhalten? Prüfe deinen Spam-Ordner oder versuche es erneut.
</p> </p>
<div className="space-y-3"> <div className="space-y-3">
@@ -83,13 +83,13 @@ export function ForgotPassword() {
) : ( ) : (
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">
{error && ( {error && (
<div className="p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm"> <div className="p-3 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 rounded-lg text-red-700 dark:text-red-300 text-sm">
{error} {error}
</div> </div>
)} )}
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="email">E-Mail-Adresse</Label> <Label htmlFor="email" className="dark:text-slate-200">E-Mail-Adresse</Label>
<Input <Input
id="email" id="email"
type="email" type="email"
@@ -98,6 +98,7 @@ export function ForgotPassword() {
onChange={(e) => setEmail(e.target.value)} onChange={(e) => setEmail(e.target.value)}
required required
autoFocus autoFocus
className="dark:bg-slate-800 dark:border-slate-600 dark:text-slate-100"
/> />
</div> </div>
@@ -115,7 +116,7 @@ export function ForgotPassword() {
<div className="text-center"> <div className="text-center">
<Link <Link
to="/login" to="/login"
className="text-sm text-primary-600 hover:text-primary-700" className="text-sm text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300"
> >
<ArrowLeft className="w-4 h-4 inline mr-1" /> <ArrowLeft className="w-4 h-4 inline mr-1" />
Zurück zum Login Zurück zum Login

View File

@@ -3,7 +3,7 @@ import { ArrowLeft, Building2 } from 'lucide-react'
export function Imprint() { export function Imprint() {
return ( return (
<div className="min-h-screen bg-slate-50"> <div className="min-h-screen bg-slate-50 dark:bg-slate-900">
{/* Header */} {/* Header */}
<header className="bg-white dark:bg-slate-900 border-b border-slate-200 dark:border-slate-700"> <header className="bg-white dark:bg-slate-900 border-b border-slate-200 dark:border-slate-700">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4"> <div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
@@ -19,56 +19,56 @@ export function Imprint() {
{/* Content */} {/* Content */}
<main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12"> <main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-8 md:p-12"> <div className="bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 p-8 md:p-12">
{/* Title */} {/* Title */}
<div className="flex items-center gap-3 mb-8"> <div className="flex items-center gap-3 mb-8">
<div className="w-12 h-12 rounded-lg bg-primary-100 flex items-center justify-center"> <div className="w-12 h-12 rounded-lg bg-primary-100 dark:bg-primary-900/30 flex items-center justify-center">
<Building2 className="w-6 h-6 text-primary-600" /> <Building2 className="w-6 h-6 text-primary-600 dark:text-primary-400" />
</div> </div>
<div> <div>
<h1 className="text-3xl font-bold text-slate-900">Impressum</h1> <h1 className="text-3xl font-bold text-slate-900 dark:text-slate-100">Impressum</h1>
<p className="text-slate-500 mt-1">Legal Information</p> <p className="text-slate-500 dark:text-slate-400 mt-1">Legal Information</p>
</div> </div>
</div> </div>
{/* Content - Placeholder for webklar.com content */} {/* Content - Placeholder for webklar.com content */}
<div className="prose prose-slate max-w-none"> <div className="prose prose-slate max-w-none dark:prose-invert">
<p className="text-slate-600 mb-6"> <p className="text-slate-600 dark:text-slate-400 mb-6">
<strong>Note:</strong> This imprint is managed by webklar.com. Please refer to their imprint for detailed information. <strong>Note:</strong> This imprint is managed by webklar.com. Please refer to their imprint for detailed information.
</p> </p>
<div className="bg-slate-50 border border-slate-200 rounded-lg p-6 mb-8"> <div className="bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700 rounded-lg p-6 mb-8">
<h2 className="text-xl font-semibold text-slate-900 mb-4">Information according to § 5 TMG</h2> <h2 className="text-xl font-semibold text-slate-900 dark:text-slate-100 mb-4">Information according to § 5 TMG</h2>
<div className="space-y-6 text-slate-700"> <div className="space-y-6 text-slate-700 dark:text-slate-300">
<div> <div>
<h3 className="text-lg font-semibold text-slate-900 mb-2">Operator</h3> <h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-2">Operator</h3>
<p className="mb-2">EmailSorter is operated by:</p> <p className="mb-2">EmailSorter is operated by:</p>
<p className="mb-4"> <p className="mb-4">
<strong>webklar.com</strong><br /> <strong>webklar.com</strong><br />
Kenso Grimm, Justin Klein Kenso Grimm, Justin Klein
</p> </p>
<p className="text-sm text-slate-600 mb-4"> <p className="text-sm text-slate-600 dark:text-slate-400 mb-4">
For complete contact details and legal information, please visit:{' '} For complete contact details and legal information, please visit:{' '}
<a <a
href="https://webklar.com/impressum" href="https://webklar.com/impressum"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-primary-600 hover:text-primary-700 underline" className="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 underline"
> >
webklar.com/impressum webklar.com/impressum
</a> </a>
</p> </p>
</div> </div>
<div className="pt-6 border-t border-slate-200"> <div className="pt-6 border-t border-slate-200 dark:border-slate-700">
<h3 className="text-lg font-semibold text-slate-900 mb-2">Contact</h3> <h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-2">Contact</h3>
<div className="space-y-2"> <div className="space-y-2">
<p> <p>
<strong>Email:</strong>{' '} <strong>Email:</strong>{' '}
<a <a
href="mailto:support@webklar.com" href="mailto:support@webklar.com"
className="text-primary-600 hover:text-primary-700 underline" className="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 underline"
> >
support@webklar.com support@webklar.com
</a> </a>
@@ -77,23 +77,23 @@ export function Imprint() {
<strong>Phone:</strong>{' '} <strong>Phone:</strong>{' '}
<a <a
href="tel:+4917623726355" href="tel:+4917623726355"
className="text-primary-600 hover:text-primary-700 underline" className="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 underline"
> >
+49 176 23726355 +49 176 23726355
</a> </a>
{' / '} {' / '}
<a <a
href="tel:+491704969375" href="tel:+491704969375"
className="text-primary-600 hover:text-primary-700 underline" className="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 underline"
> >
+49 170 4969375 +49 170 4969375
</a> </a>
</p> </p>
<p className="mt-4 text-sm text-slate-600"> <p className="mt-4 text-sm text-slate-600 dark:text-slate-400">
For questions regarding EmailSorter specifically:{' '} For questions regarding EmailSorter specifically:{' '}
<a <a
href="mailto:support@emailsorter.com" href="mailto:support@emailsorter.com"
className="text-primary-600 hover:text-primary-700 underline" className="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 underline"
> >
support@emailsorter.com support@emailsorter.com
</a> </a>
@@ -101,8 +101,8 @@ export function Imprint() {
</div> </div>
</div> </div>
<div className="pt-6 border-t border-slate-200"> <div className="pt-6 border-t border-slate-200 dark:border-slate-700">
<h3 className="text-lg font-semibold text-slate-900 mb-2">Responsible for Content</h3> <h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-2">Responsible for Content</h3>
<p> <p>
The content of this website is the responsibility of webklar.com. The content of this website is the responsibility of webklar.com.
For detailed information, please refer to the official imprint at{' '} For detailed information, please refer to the official imprint at{' '}
@@ -110,23 +110,23 @@ export function Imprint() {
href="https://webklar.com/impressum" href="https://webklar.com/impressum"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-primary-600 hover:text-primary-700 underline" className="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 underline"
> >
webklar.com/impressum webklar.com/impressum
</a> </a>
</p> </p>
</div> </div>
<div className="pt-6 border-t border-slate-200"> <div className="pt-6 border-t border-slate-200 dark:border-slate-700">
<h3 className="text-lg font-semibold text-slate-900 mb-2">Liability for Links</h3> <h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-2">Liability for Links</h3>
<p> <p>
Our website contains links to external websites. We have no influence on the content of these websites. Our website contains links to external websites. We have no influence on the content of these websites.
Therefore, we cannot assume any liability for these external contents. Therefore, we cannot assume any liability for these external contents.
</p> </p>
</div> </div>
<div className="pt-6 border-t border-slate-200"> <div className="pt-6 border-t border-slate-200 dark:border-slate-700">
<h3 className="text-lg font-semibold text-slate-900 mb-2">Copyright</h3> <h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mb-2">Copyright</h3>
<p> <p>
The content and works on this website are subject to German copyright law. The content and works on this website are subject to German copyright law.
Reproduction, processing, distribution, and any form of commercialization require the written consent of the respective author or creator. Reproduction, processing, distribution, and any form of commercialization require the written consent of the respective author or creator.
@@ -134,14 +134,14 @@ export function Imprint() {
</div> </div>
</div> </div>
<div className="mt-8 pt-6 border-t border-slate-200"> <div className="mt-8 pt-6 border-t border-slate-200 dark:border-slate-700">
<p className="text-sm text-slate-500"> <p className="text-sm text-slate-500 dark:text-slate-400">
<strong>Important:</strong> This is a simplified version. For the complete and legally binding imprint, please visit{' '} <strong>Important:</strong> This is a simplified version. For the complete and legally binding imprint, please visit{' '}
<a <a
href="https://webklar.com/impressum" href="https://webklar.com/impressum"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-primary-600 hover:text-primary-700 underline" className="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 underline"
> >
webklar.com/impressum webklar.com/impressum
</a> </a>

View File

@@ -3,7 +3,7 @@ import { ArrowLeft, Shield } from 'lucide-react'
export function Privacy() { export function Privacy() {
return ( return (
<div className="min-h-screen bg-slate-50"> <div className="min-h-screen bg-slate-50 dark:bg-slate-900">
{/* Header */} {/* Header */}
<header className="bg-white dark:bg-slate-900 border-b border-slate-200 dark:border-slate-700"> <header className="bg-white dark:bg-slate-900 border-b border-slate-200 dark:border-slate-700">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4"> <div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
@@ -19,81 +19,81 @@ export function Privacy() {
{/* Content */} {/* Content */}
<main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12"> <main className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-12">
<div className="bg-white rounded-xl shadow-sm border border-slate-200 p-8 md:p-12"> <div className="bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 p-8 md:p-12">
{/* Title */} {/* Title */}
<div className="flex items-center gap-3 mb-8"> <div className="flex items-center gap-3 mb-8">
<div className="w-12 h-12 rounded-lg bg-primary-100 flex items-center justify-center"> <div className="w-12 h-12 rounded-lg bg-primary-100 dark:bg-primary-900/30 flex items-center justify-center">
<Shield className="w-6 h-6 text-primary-600" /> <Shield className="w-6 h-6 text-primary-600 dark:text-primary-400" />
</div> </div>
<div> <div>
<h1 className="text-3xl font-bold text-slate-900">Privacy Policy</h1> <h1 className="text-3xl font-bold text-slate-900 dark:text-slate-100">Privacy Policy</h1>
<p className="text-slate-500 mt-1">Last updated: {new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}</p> <p className="text-slate-500 dark:text-slate-400 mt-1">Last updated: {new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' })}</p>
</div> </div>
</div> </div>
{/* Content - Placeholder for webklar.com content */} {/* Content - Placeholder for webklar.com content */}
<div className="prose prose-slate max-w-none"> <div className="prose prose-slate max-w-none dark:prose-invert">
<p className="text-slate-600 mb-6"> <p className="text-slate-600 dark:text-slate-400 mb-6">
<strong>Note:</strong> This privacy policy is managed by webklar.com. Please refer to their privacy policy for detailed information. <strong>Note:</strong> This privacy policy is managed by webklar.com. Please refer to their privacy policy for detailed information.
</p> </p>
<div className="bg-slate-50 border border-slate-200 rounded-lg p-6 mb-8"> <div className="bg-slate-50 dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700 rounded-lg p-6 mb-8">
<h2 className="text-xl font-semibold text-slate-900 mb-4">Data Protection Information</h2> <h2 className="text-xl font-semibold text-slate-900 dark:text-slate-100 mb-4">Data Protection Information</h2>
<p className="text-slate-700 mb-4"> <p className="text-slate-700 dark:text-slate-300 mb-4">
EmailSorter is operated by webklar.com. The following privacy policy applies to the use of this website and our services. EmailSorter is operated by webklar.com. The following privacy policy applies to the use of this website and our services.
</p> </p>
<h3 className="text-lg font-semibold text-slate-900 mt-6 mb-3">1. Responsible Party</h3> <h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mt-6 mb-3">1. Responsible Party</h3>
<p className="text-slate-700 mb-4"> <p className="text-slate-700 dark:text-slate-300 mb-4">
The responsible party for data processing on this website is: The responsible party for data processing on this website is:
</p> </p>
<div className="bg-white border border-slate-200 rounded-lg p-4 mb-4"> <div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg p-4 mb-4">
<p className="text-slate-700 mb-2"> <p className="text-slate-700 dark:text-slate-300 mb-2">
<strong>webklar.com</strong><br /> <strong>webklar.com</strong><br />
Kenso Grimm, Justin Klein Kenso Grimm, Justin Klein
</p> </p>
<p className="text-slate-700 mb-2"> <p className="text-slate-700 dark:text-slate-300 mb-2">
<strong>Contact:</strong><br /> <strong>Contact:</strong><br />
Email: <a href="mailto:support@webklar.com" className="text-primary-600 hover:text-primary-700 underline">support@webklar.com</a><br /> Email: <a href="mailto:support@webklar.com" className="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 underline">support@webklar.com</a><br />
Phone: <a href="tel:+4917623726355" className="text-primary-600 hover:text-primary-700 underline">+49 176 23726355</a> Phone: <a href="tel:+4917623726355" className="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 underline">+49 176 23726355</a>
</p> </p>
<p className="text-sm text-slate-600 mt-3"> <p className="text-sm text-slate-600 dark:text-slate-400 mt-3">
For complete contact details, please refer to the <Link to="/imprint" className="text-primary-600 hover:text-primary-700 underline">Impressum</Link>. For complete contact details, please refer to the <Link to="/imprint" className="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 underline">Impressum</Link>.
</p> </p>
</div> </div>
<h3 className="text-lg font-semibold text-slate-900 mt-6 mb-3">2. Data Collection and Processing</h3> <h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mt-6 mb-3">2. Data Collection and Processing</h3>
<p className="text-slate-700 mb-4"> <p className="text-slate-700 dark:text-slate-300 mb-4">
When you use EmailSorter, we collect and process the following data: When you use EmailSorter, we collect and process the following data:
</p> </p>
<ul className="list-disc list-inside text-slate-700 mb-4 space-y-2 ml-4"> <ul className="list-disc list-inside text-slate-700 dark:text-slate-300 mb-4 space-y-2 ml-4">
<li>Account information (email address, name)</li> <li>Account information (email address, name)</li>
<li>Email metadata (sender, subject, date) for sorting purposes</li> <li>Email metadata (sender, subject, date) for sorting purposes</li>
<li>Usage statistics and preferences</li> <li>Usage statistics and preferences</li>
<li>Payment information (processed securely via Stripe)</li> <li>Payment information (processed securely via Stripe)</li>
</ul> </ul>
<h3 className="text-lg font-semibold text-slate-900 mt-6 mb-3">3. Purpose of Data Processing</h3> <h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mt-6 mb-3">3. Purpose of Data Processing</h3>
<p className="text-slate-700 mb-4"> <p className="text-slate-700 dark:text-slate-300 mb-4">
We process your data exclusively for the following purposes: We process your data exclusively for the following purposes:
</p> </p>
<ul className="list-disc list-inside text-slate-700 mb-4 space-y-2 ml-4"> <ul className="list-disc list-inside text-slate-700 dark:text-slate-300 mb-4 space-y-2 ml-4">
<li>Providing and improving the EmailSorter service</li> <li>Providing and improving the EmailSorter service</li>
<li>Automated email sorting and categorization</li> <li>Automated email sorting and categorization</li>
<li>Processing payments and subscriptions</li> <li>Processing payments and subscriptions</li>
<li>Customer support and communication</li> <li>Customer support and communication</li>
</ul> </ul>
<h3 className="text-lg font-semibold text-slate-900 mt-6 mb-3">4. Data Security</h3> <h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mt-6 mb-3">4. Data Security</h3>
<p className="text-slate-700 mb-4"> <p className="text-slate-700 dark:text-slate-300 mb-4">
We implement appropriate technical and organizational measures to protect your data against unauthorized access, loss, or destruction. We implement appropriate technical and organizational measures to protect your data against unauthorized access, loss, or destruction.
</p> </p>
<h3 className="text-lg font-semibold text-slate-900 mt-6 mb-3">5. Your Rights</h3> <h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mt-6 mb-3">5. Your Rights</h3>
<p className="text-slate-700 mb-4"> <p className="text-slate-700 dark:text-slate-300 mb-4">
You have the right to: You have the right to:
</p> </p>
<ul className="list-disc list-inside text-slate-700 mb-4 space-y-2 ml-4"> <ul className="list-disc list-inside text-slate-700 dark:text-slate-300 mb-4 space-y-2 ml-4">
<li>Access your personal data</li> <li>Access your personal data</li>
<li>Correct inaccurate data</li> <li>Correct inaccurate data</li>
<li>Request deletion of your data</li> <li>Request deletion of your data</li>
@@ -101,14 +101,14 @@ export function Privacy() {
<li>Data portability</li> <li>Data portability</li>
</ul> </ul>
<h3 className="text-lg font-semibold text-slate-900 mt-6 mb-3">6. Hosting and Third-Party Services</h3> <h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mt-6 mb-3">6. Hosting and Third-Party Services</h3>
<p className="text-slate-700 mb-4"> <p className="text-slate-700 dark:text-slate-300 mb-4">
<strong>Hosting:</strong> Our website is hosted by Netlify, which acts as a data processor. <strong>Hosting:</strong> Our website is hosted by Netlify, which acts as a data processor.
</p> </p>
<p className="text-slate-700 mb-4"> <p className="text-slate-700 dark:text-slate-300 mb-4">
We use the following third-party services: We use the following third-party services:
</p> </p>
<ul className="list-disc list-inside text-slate-700 mb-4 space-y-2 ml-4"> <ul className="list-disc list-inside text-slate-700 dark:text-slate-300 mb-4 space-y-2 ml-4">
<li><strong>Appwrite:</strong> User authentication and database</li> <li><strong>Appwrite:</strong> User authentication and database</li>
<li><strong>Stripe:</strong> Payment processing</li> <li><strong>Stripe:</strong> Payment processing</li>
<li><strong>Mistral AI:</strong> Email categorization</li> <li><strong>Mistral AI:</strong> Email categorization</li>
@@ -116,45 +116,45 @@ export function Privacy() {
<li><strong>Plausible (optional):</strong> Privacy-friendly analytics tool, if enabled</li> <li><strong>Plausible (optional):</strong> Privacy-friendly analytics tool, if enabled</li>
</ul> </ul>
<h3 className="text-lg font-semibold text-slate-900 mt-6 mb-3">6.1. Cookies and Tracking</h3> <h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mt-6 mb-3">6.1. Cookies and Tracking</h3>
<p className="text-slate-700 mb-4"> <p className="text-slate-700 dark:text-slate-300 mb-4">
We do not use external fonts or unnecessary cookies. If we use any tracking tools (such as Plausible), We do not use external fonts or unnecessary cookies. If we use any tracking tools (such as Plausible),
they are privacy-friendly and do not store personal data. We only process personal data to the extent they are privacy-friendly and do not store personal data. We only process personal data to the extent
that it is technically or organizationally necessary. that it is technically or organizationally necessary.
</p> </p>
<h3 className="text-lg font-semibold text-slate-900 mt-6 mb-3">7. Contact Form Data</h3> <h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mt-6 mb-3">7. Contact Form Data</h3>
<p className="text-slate-700 mb-4"> <p className="text-slate-700 dark:text-slate-300 mb-4">
Data that you send to us via contact forms will be stored and used for processing your inquiry. Data that you send to us via contact forms will be stored and used for processing your inquiry.
This data will not be shared with third parties without your consent. This data will not be shared with third parties without your consent.
</p> </p>
<h3 className="text-lg font-semibold text-slate-900 mt-6 mb-3">8. Contact</h3> <h3 className="text-lg font-semibold text-slate-900 dark:text-slate-100 mt-6 mb-3">8. Contact</h3>
<p className="text-slate-700 mb-4"> <p className="text-slate-700 dark:text-slate-300 mb-4">
For questions regarding data protection, please contact us: For questions regarding data protection, please contact us:
</p> </p>
<div className="bg-white border border-slate-200 rounded-lg p-4 mb-4"> <div className="bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-lg p-4 mb-4">
<p className="text-slate-700"> <p className="text-slate-700 dark:text-slate-300">
<strong>Email:</strong>{' '} <strong>Email:</strong>{' '}
<a href="mailto:support@webklar.com" className="text-primary-600 hover:text-primary-700 underline"> <a href="mailto:support@webklar.com" className="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 underline">
support@webklar.com support@webklar.com
</a> </a>
</p> </p>
<p className="text-slate-700 mt-2"> <p className="text-slate-700 dark:text-slate-300 mt-2">
<strong>Phone:</strong>{' '} <strong>Phone:</strong>{' '}
<a href="tel:+4917623726355" className="text-primary-600 hover:text-primary-700 underline"> <a href="tel:+4917623726355" className="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 underline">
+49 176 23726355 +49 176 23726355
</a> </a>
</p> </p>
<p className="text-sm text-slate-600 mt-3"> <p className="text-sm text-slate-600 dark:text-slate-400 mt-3">
For complete contact details, please refer to the <Link to="/imprint" className="text-primary-600 hover:text-primary-700 underline">Impressum</Link>. For complete contact details, please refer to the <Link to="/imprint" className="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 underline">Impressum</Link>.
</p> </p>
</div> </div>
<div className="mt-8 pt-6 border-t border-slate-200"> <div className="mt-8 pt-6 border-t border-slate-200 dark:border-slate-700">
<p className="text-sm text-slate-500"> <p className="text-sm text-slate-500 dark:text-slate-400">
<strong>Important:</strong> This is a simplified version. For the complete and legally binding privacy policy, please visit{' '} <strong>Important:</strong> This is a simplified version. For the complete and legally binding privacy policy, please visit{' '}
<a href="https://webklar.com/datenschutz" target="_blank" rel="noopener noreferrer" className="text-primary-600 hover:text-primary-700 underline"> <a href="https://webklar.com/datenschutz" target="_blank" rel="noopener noreferrer" className="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 underline">
webklar.com/datenschutz webklar.com/datenschutz
</a> </a>
</p> </p>

View File

@@ -114,30 +114,30 @@ export function Register() {
</div> </div>
{/* Right side - Form */} {/* Right side - Form */}
<div className="flex-1 flex items-center justify-center px-4 sm:px-6 lg:px-8 bg-white"> <div className="flex-1 flex items-center justify-center px-4 sm:px-6 lg:px-8 bg-white dark:bg-slate-900">
<div className="w-full max-w-md"> <div className="w-full max-w-md">
{/* Logo */} {/* Logo */}
<Link to="/" className="flex items-center gap-2 mb-8"> <Link to="/" className="flex items-center gap-2 mb-8">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center"> <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center">
<Mail className="w-5 h-5 text-white" /> <Mail className="w-5 h-5 text-white" />
</div> </div>
<span className="text-xl font-bold text-slate-900"> <span className="text-xl font-bold text-slate-900 dark:text-slate-100">
E-Mail-<span className="text-primary-600">Sorter</span> E-Mail-<span className="text-primary-600 dark:text-primary-400">Sorter</span>
</span> </span>
</Link> </Link>
<h1 className="text-3xl font-bold text-slate-900 mb-2"> <h1 className="text-3xl font-bold text-slate-900 dark:text-slate-100 mb-2">
Create account Create account
</h1> </h1>
<p className="text-slate-600 mb-8"> <p className="text-slate-600 dark:text-slate-400 mb-8">
Ready to go in less than a minute. Ready to go in less than a minute.
</p> </p>
{/* Error message */} {/* Error message */}
{error && ( {error && (
<div className="mb-6 p-4 bg-red-50 border border-red-200 rounded-xl flex items-start gap-3"> <div className="mb-6 p-4 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 rounded-xl flex items-start gap-3">
<AlertCircle className="w-5 h-5 text-red-500 flex-shrink-0 mt-0.5" /> <AlertCircle className="w-5 h-5 text-red-500 dark:text-red-400 flex-shrink-0 mt-0.5" />
<p className="text-sm text-red-600">{error}</p> <p className="text-sm text-red-600 dark:text-red-300">{error}</p>
</div> </div>
)} )}
@@ -217,16 +217,16 @@ export function Register() {
)} )}
</Button> </Button>
<p className="text-xs text-slate-500 text-center"> <p className="text-xs text-slate-500 dark:text-slate-400 text-center">
By signing up, you agree to our{' '} By signing up, you agree to our{' '}
<a href="#" className="text-primary-600 hover:underline">Terms of Service</a> and{' '} <a href="#" className="text-primary-600 dark:text-primary-400 hover:underline">Terms of Service</a> and{' '}
<a href="#" className="text-primary-600 hover:underline">Privacy Policy</a>. <a href="#" className="text-primary-600 dark:text-primary-400 hover:underline">Privacy Policy</a>.
</p> </p>
</form> </form>
<p className="mt-8 text-center text-slate-600"> <p className="mt-8 text-center text-slate-600 dark:text-slate-400">
Already have an account?{' '} Already have an account?{' '}
<Link to="/login" className="text-primary-600 font-semibold hover:text-primary-700"> <Link to="/login" className="text-primary-600 dark:text-primary-400 font-semibold hover:text-primary-700 dark:hover:text-primary-300">
Sign in Sign in
</Link> </Link>
</p> </p>

View File

@@ -83,24 +83,24 @@ export function ResetPassword() {
const passwordStrength = getPasswordStrength() const passwordStrength = getPasswordStrength()
return ( return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 flex items-center justify-center p-4"> <div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 flex items-center justify-center p-4">
<div className="w-full max-w-md"> <div className="w-full max-w-md">
{/* Logo */} {/* Logo */}
<Link to="/" className="flex items-center justify-center gap-2 mb-8"> <Link to="/" className="flex items-center justify-center gap-2 mb-8">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center"> <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center">
<Mail className="w-5 h-5 text-white" /> <Mail className="w-5 h-5 text-white" />
</div> </div>
<span className="text-xl font-bold text-slate-900"> <span className="text-xl font-bold text-slate-900 dark:text-slate-100">
Email<span className="text-primary-600">Sorter</span> Email<span className="text-primary-600 dark:text-primary-400">Sorter</span>
</span> </span>
</Link> </Link>
<Card className="shadow-xl border-0"> <Card className="shadow-xl border-0 dark:bg-slate-800 dark:border-slate-700">
<CardHeader className="text-center pb-2"> <CardHeader className="text-center pb-2">
<CardTitle className="text-2xl"> <CardTitle className="text-2xl dark:text-slate-100">
{success ? 'Passwort geändert!' : 'Neues Passwort festlegen'} {success ? 'Passwort geändert!' : 'Neues Passwort festlegen'}
</CardTitle> </CardTitle>
<CardDescription> <CardDescription className="dark:text-slate-400">
{success {success
? 'Dein Passwort wurde erfolgreich geändert.' ? 'Dein Passwort wurde erfolgreich geändert.'
: 'Wähle ein sicheres neues Passwort für deinen Account.' : 'Wähle ein sicheres neues Passwort für deinen Account.'
@@ -110,10 +110,10 @@ export function ResetPassword() {
<CardContent> <CardContent>
{success ? ( {success ? (
<div className="text-center py-8"> <div className="text-center py-8">
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-green-100 flex items-center justify-center"> <div className="w-16 h-16 mx-auto mb-4 rounded-full bg-green-100 dark:bg-green-900/30 flex items-center justify-center">
<CheckCircle className="w-8 h-8 text-green-600" /> <CheckCircle className="w-8 h-8 text-green-600 dark:text-green-400" />
</div> </div>
<p className="text-slate-600 mb-6"> <p className="text-slate-600 dark:text-slate-400 mb-6">
Du kannst dich jetzt mit deinem neuen Passwort anmelden. Du kannst dich jetzt mit deinem neuen Passwort anmelden.
</p> </p>
<Button onClick={() => navigate('/login')} className="w-full"> <Button onClick={() => navigate('/login')} className="w-full">
@@ -122,11 +122,11 @@ export function ResetPassword() {
</div> </div>
) : !userId || !secret ? ( ) : !userId || !secret ? (
<div className="text-center py-8"> <div className="text-center py-8">
<div className="w-16 h-16 mx-auto mb-4 rounded-full bg-red-100 flex items-center justify-center"> <div className="w-16 h-16 mx-auto mb-4 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center">
<XCircle className="w-8 h-8 text-red-600" /> <XCircle className="w-8 h-8 text-red-600 dark:text-red-400" />
</div> </div>
<h3 className="font-semibold text-slate-900 mb-2">Ungültiger Link</h3> <h3 className="font-semibold text-slate-900 dark:text-slate-100 mb-2">Ungültiger Link</h3>
<p className="text-slate-600 mb-6"> <p className="text-slate-600 dark:text-slate-400 mb-6">
Dieser Link zum Zurücksetzen des Passworts ist ungültig oder abgelaufen. Dieser Link zum Zurücksetzen des Passworts ist ungültig oder abgelaufen.
</p> </p>
<Link to="/forgot-password"> <Link to="/forgot-password">
@@ -136,13 +136,13 @@ export function ResetPassword() {
) : ( ) : (
<form onSubmit={handleSubmit} className="space-y-6"> <form onSubmit={handleSubmit} className="space-y-6">
{error && ( {error && (
<div className="p-3 bg-red-50 border border-red-200 rounded-lg text-red-700 text-sm"> <div className="p-3 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 rounded-lg text-red-700 dark:text-red-300 text-sm">
{error} {error}
</div> </div>
)} )}
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="password">Neues Passwort</Label> <Label htmlFor="password" className="dark:text-slate-200">Neues Passwort</Label>
<div className="relative"> <div className="relative">
<Input <Input
id="password" id="password"
@@ -152,11 +152,12 @@ export function ResetPassword() {
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
required required
autoFocus autoFocus
className="dark:bg-slate-800 dark:border-slate-600 dark:text-slate-100"
/> />
<button <button
type="button" type="button"
onClick={() => setShowPassword(!showPassword)} onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600" className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 dark:text-slate-500 hover:text-slate-600 dark:hover:text-slate-300"
> >
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />} {showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button> </button>
@@ -172,13 +173,13 @@ export function ResetPassword() {
className={`h-1 flex-1 rounded-full transition-colors ${ className={`h-1 flex-1 rounded-full transition-colors ${
level <= passwordStrength.strength level <= passwordStrength.strength
? passwordStrength.color ? passwordStrength.color
: 'bg-slate-200' : 'bg-slate-200 dark:bg-slate-700'
}`} }`}
/> />
))} ))}
</div> </div>
<p className={`text-xs ${ <p className={`text-xs ${
passwordStrength.strength < 3 ? 'text-red-500' : 'text-green-600' passwordStrength.strength < 3 ? 'text-red-500 dark:text-red-400' : 'text-green-600 dark:text-green-400'
}`}> }`}>
{passwordStrength.label} {passwordStrength.label}
</p> </p>
@@ -187,7 +188,7 @@ export function ResetPassword() {
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="confirmPassword">Passwort bestätigen</Label> <Label htmlFor="confirmPassword" className="dark:text-slate-200">Passwort bestätigen</Label>
<Input <Input
id="confirmPassword" id="confirmPassword"
type={showPassword ? 'text' : 'password'} type={showPassword ? 'text' : 'password'}
@@ -195,9 +196,10 @@ export function ResetPassword() {
value={confirmPassword} value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)} onChange={(e) => setConfirmPassword(e.target.value)}
required required
className="dark:bg-slate-800 dark:border-slate-600 dark:text-slate-100"
/> />
{confirmPassword && password !== confirmPassword && ( {confirmPassword && password !== confirmPassword && (
<p className="text-xs text-red-500">Passwörter stimmen nicht überein</p> <p className="text-xs text-red-500 dark:text-red-400">Passwörter stimmen nicht überein</p>
)} )}
</div> </div>

View File

@@ -283,17 +283,17 @@ export function Setup() {
// Show loading while checking accounts // Show loading while checking accounts
if (checkingAccounts) { if (checkingAccounts) {
return ( return (
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white flex items-center justify-center"> <div className="min-h-screen bg-gradient-to-b from-slate-50 to-white dark:from-slate-900 dark:to-slate-800 flex items-center justify-center">
<div className="text-center"> <div className="text-center">
<Loader2 className="w-8 h-8 animate-spin text-primary-600 mx-auto mb-4" /> <Loader2 className="w-8 h-8 animate-spin text-primary-600 dark:text-primary-400 mx-auto mb-4" />
<p className="text-slate-600">Setting up your account...</p> <p className="text-slate-600 dark:text-slate-400">Setting up your account...</p>
</div> </div>
</div> </div>
) )
} }
return ( return (
<div className="min-h-screen bg-gradient-to-b from-slate-50 to-white"> <div className="min-h-screen bg-gradient-to-b from-slate-50 to-white dark:from-slate-900 dark:to-slate-800">
<header className="bg-white/80 dark:bg-slate-900/80 backdrop-blur-sm border-b border-slate-200 dark:border-slate-700 sticky top-0 z-40"> <header className="bg-white/80 dark:bg-slate-900/80 backdrop-blur-sm border-b border-slate-200 dark:border-slate-700 sticky top-0 z-40">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4"> <div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@@ -315,13 +315,13 @@ export function Setup() {
{/* Success message after checkout */} {/* Success message after checkout */}
{isFromCheckout && ( {isFromCheckout && (
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pt-8"> <div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pt-8">
<div className="bg-green-50 border border-green-200 rounded-xl p-6 mb-6 flex items-start gap-4"> <div className="bg-green-50 dark:bg-green-900/30 border border-green-200 dark:border-green-800 rounded-xl p-6 mb-6 flex items-start gap-4">
<div className="w-10 h-10 rounded-full bg-green-500 flex items-center justify-center flex-shrink-0"> <div className="w-10 h-10 rounded-full bg-green-500 dark:bg-green-600 flex items-center justify-center flex-shrink-0">
<Check className="w-6 h-6 text-white" /> <Check className="w-6 h-6 text-white" />
</div> </div>
<div className="flex-1"> <div className="flex-1">
<h3 className="font-semibold text-green-900 mb-1">Payment successful!</h3> <h3 className="font-semibold text-green-900 dark:text-green-200 mb-1">Payment successful!</h3>
<p className="text-sm text-green-700"> <p className="text-sm text-green-700 dark:text-green-300">
Your subscription is active. Let's connect your email account to get started. Your subscription is active. Let's connect your email account to get started.
</p> </p>
</div> </div>
@@ -351,23 +351,23 @@ export function Setup() {
<div <div
className={`w-10 h-10 rounded-full flex items-center justify-center text-sm font-semibold transition-all duration-300 ${ className={`w-10 h-10 rounded-full flex items-center justify-center text-sm font-semibold transition-all duration-300 ${
index < stepIndex index < stepIndex
? 'bg-green-500 text-white shadow-lg shadow-green-500/30' ? 'bg-green-500 dark:bg-green-600 text-white shadow-lg shadow-green-500/30'
: index === stepIndex : index === stepIndex
? 'bg-primary-500 text-white ring-4 ring-primary-100 shadow-lg shadow-primary-500/30' ? 'bg-primary-500 dark:bg-primary-600 text-white ring-4 ring-primary-100 dark:ring-primary-900/50 shadow-lg shadow-primary-500/30'
: 'bg-slate-100 text-slate-400' : 'bg-slate-100 dark:bg-slate-700 text-slate-400 dark:text-slate-500'
}`} }`}
> >
{index < stepIndex ? <Check className="w-5 h-5" /> : index + 1} {index < stepIndex ? <Check className="w-5 h-5" /> : index + 1}
</div> </div>
<p className={`mt-2 text-xs font-medium hidden sm:block transition-colors ${ <p className={`mt-2 text-xs font-medium hidden sm:block transition-colors ${
index <= stepIndex ? 'text-slate-900' : 'text-slate-400' index <= stepIndex ? 'text-slate-900 dark:text-slate-100' : 'text-slate-400 dark:text-slate-500'
}`}> }`}>
{step.title} {step.title}
</p> </p>
</div> </div>
{index < steps.length - 1 && ( {index < steps.length - 1 && (
<div className={`w-16 sm:w-24 h-1 mx-2 rounded-full transition-colors duration-500 ${ <div className={`w-16 sm:w-24 h-1 mx-2 rounded-full transition-colors duration-500 ${
index < stepIndex ? 'bg-green-500' : 'bg-slate-200' index < stepIndex ? 'bg-green-500 dark:bg-green-600' : 'bg-slate-200 dark:bg-slate-700'
}`} /> }`} />
)} )}
</div> </div>
@@ -376,7 +376,7 @@ export function Setup() {
</div> </div>
{error && ( {error && (
<div className="mb-6 p-4 bg-red-50 border border-red-200 rounded-xl flex items-center gap-3 text-red-700"> <div className="mb-6 p-4 bg-red-50 dark:bg-red-900/30 border border-red-200 dark:border-red-800 rounded-xl flex items-center gap-3 text-red-700 dark:text-red-300">
<AlertCircle className="w-5 h-5 flex-shrink-0" /> <AlertCircle className="w-5 h-5 flex-shrink-0" />
<p>{error}</p> <p>{error}</p>
</div> </div>
@@ -388,8 +388,8 @@ export function Setup() {
<div className="w-24 h-24 mx-auto mb-6 rounded-2xl bg-gradient-to-br from-primary-100 to-primary-200 flex items-center justify-center shadow-xl shadow-primary-500/10"> <div className="w-24 h-24 mx-auto mb-6 rounded-2xl bg-gradient-to-br from-primary-100 to-primary-200 flex items-center justify-center shadow-xl shadow-primary-500/10">
<Link2 className="w-12 h-12 text-primary-600" /> <Link2 className="w-12 h-12 text-primary-600" />
</div> </div>
<h1 className="text-3xl font-bold text-slate-900 mb-3">Connect your email account</h1> <h1 className="text-3xl font-bold text-slate-900 dark:text-slate-100 mb-3">Connect your email account</h1>
<p className="text-lg text-slate-600 mb-10 max-w-md mx-auto"> <p className="text-lg text-slate-600 dark:text-slate-400 mb-10 max-w-md mx-auto">
Choose your email provider. The connection is secure and your data stays private. Choose your email provider. The connection is secure and your data stays private.
</p> </p>
@@ -416,10 +416,10 @@ export function Setup() {
<div className="relative"> <div className="relative">
<div className="absolute inset-0 flex items-center"> <div className="absolute inset-0 flex items-center">
<div className="w-full border-t border-slate-300"></div> <div className="w-full border-t border-slate-300 dark:border-slate-600"></div>
</div> </div>
<div className="relative flex justify-center text-sm"> <div className="relative flex justify-center text-sm">
<span className="px-4 bg-white text-slate-500">Or connect your inbox</span> <span className="px-4 bg-white dark:bg-slate-800 text-slate-500 dark:text-slate-400">Or connect your inbox</span>
</div> </div>
</div> </div>
@@ -427,12 +427,12 @@ export function Setup() {
<button <button
onClick={handleConnectGmail} onClick={handleConnectGmail}
disabled={connecting !== null} disabled={connecting !== null}
className="flex items-center gap-4 p-6 bg-white rounded-2xl border-2 border-slate-200 hover:border-red-300 hover:shadow-xl hover:shadow-red-500/10 transition-all text-left group disabled:opacity-50 disabled:cursor-not-allowed" className="flex items-center gap-4 p-6 bg-white dark:bg-slate-800 rounded-2xl border-2 border-slate-200 dark:border-slate-700 hover:border-red-300 dark:hover:border-red-600 hover:shadow-xl hover:shadow-red-500/10 transition-all text-left group disabled:opacity-50 disabled:cursor-not-allowed"
> >
{connecting === 'gmail' ? ( {connecting === 'gmail' ? (
<Loader2 className="w-12 h-12 animate-spin text-red-500" /> <Loader2 className="w-12 h-12 animate-spin text-red-500 dark:text-red-400" />
) : ( ) : (
<div className="w-12 h-12 rounded-xl bg-red-50 flex items-center justify-center group-hover:bg-red-100 transition-colors"> <div className="w-12 h-12 rounded-xl bg-red-50 dark:bg-red-900/30 flex items-center justify-center group-hover:bg-red-100 dark:group-hover:bg-red-900/50 transition-colors">
<svg viewBox="0 0 24 24" className="w-7 h-7"> <svg viewBox="0 0 24 24" className="w-7 h-7">
<path fill="#EA4335" d="M5.26 9.71L12 14.04l6.74-4.33-6.74-4.33z"/> <path fill="#EA4335" d="M5.26 9.71L12 14.04l6.74-4.33-6.74-4.33z"/>
<path fill="#34A853" d="M12 14.04l6.74-4.33v7.65c0 .7-.57 1.26-1.26 1.26H6.52c-.7 0-1.26-.57-1.26-1.26V9.71l6.74 4.33z"/> <path fill="#34A853" d="M12 14.04l6.74-4.33v7.65c0 .7-.57 1.26-1.26 1.26H6.52c-.7 0-1.26-.57-1.26-1.26V9.71l6.74 4.33z"/>
@@ -442,37 +442,37 @@ export function Setup() {
</div> </div>
)} )}
<div className="flex-1"> <div className="flex-1">
<p className="font-semibold text-slate-900">Gmail</p> <p className="font-semibold text-slate-900 dark:text-slate-100">Gmail</p>
<p className="text-sm text-slate-500">Google Workspace</p> <p className="text-sm text-slate-500 dark:text-slate-400">Google Workspace</p>
</div> </div>
<ChevronRight className="w-5 h-5 text-slate-400 group-hover:text-red-500 group-hover:translate-x-1 transition-all" /> <ChevronRight className="w-5 h-5 text-slate-400 dark:text-slate-500 group-hover:text-red-500 dark:group-hover:text-red-400 group-hover:translate-x-1 transition-all" />
</button> </button>
<button <button
onClick={handleConnectOutlook} onClick={handleConnectOutlook}
disabled={connecting !== null} disabled={connecting !== null}
className="flex items-center gap-4 p-6 bg-white rounded-2xl border-2 border-slate-200 hover:border-blue-300 hover:shadow-xl hover:shadow-blue-500/10 transition-all text-left group disabled:opacity-50 disabled:cursor-not-allowed" className="flex items-center gap-4 p-6 bg-white dark:bg-slate-800 rounded-2xl border-2 border-slate-200 dark:border-slate-700 hover:border-blue-300 dark:hover:border-blue-600 hover:shadow-xl hover:shadow-blue-500/10 transition-all text-left group disabled:opacity-50 disabled:cursor-not-allowed"
> >
{connecting === 'outlook' ? ( {connecting === 'outlook' ? (
<Loader2 className="w-12 h-12 animate-spin text-blue-500" /> <Loader2 className="w-12 h-12 animate-spin text-blue-500 dark:text-blue-400" />
) : ( ) : (
<div className="w-12 h-12 rounded-xl bg-blue-50 flex items-center justify-center group-hover:bg-blue-100 transition-colors"> <div className="w-12 h-12 rounded-xl bg-blue-50 dark:bg-blue-900/30 flex items-center justify-center group-hover:bg-blue-100 dark:group-hover:bg-blue-900/50 transition-colors">
<svg viewBox="0 0 24 24" className="w-7 h-7"> <svg viewBox="0 0 24 24" className="w-7 h-7">
<path fill="#0078D4" d="M11.5 3v8.5H3V3h8.5zm1 0H21v8.5h-8.5V3zM3 12.5h8.5V21H3v-8.5zm9.5 0H21V21h-8.5v-8.5z"/> <path fill="#0078D4" d="M11.5 3v8.5H3V3h8.5zm1 0H21v8.5h-8.5V3zM3 12.5h8.5V21H3v-8.5zm9.5 0H21V21h-8.5v-8.5z"/>
</svg> </svg>
</div> </div>
)} )}
<div className="flex-1"> <div className="flex-1">
<p className="font-semibold text-slate-900">Outlook</p> <p className="font-semibold text-slate-900 dark:text-slate-100">Outlook</p>
<p className="text-sm text-slate-500">Microsoft 365</p> <p className="text-sm text-slate-500 dark:text-slate-400">Microsoft 365</p>
</div> </div>
<ChevronRight className="w-5 h-5 text-slate-400 group-hover:text-blue-500 group-hover:translate-x-1 transition-all" /> <ChevronRight className="w-5 h-5 text-slate-400 dark:text-slate-500 group-hover:text-blue-500 dark:group-hover:text-blue-400 group-hover:translate-x-1 transition-all" />
</button> </button>
</div> </div>
</div> </div>
<div className="mt-10 p-4 bg-slate-50 rounded-xl max-w-lg mx-auto"> <div className="mt-10 p-4 bg-slate-50 dark:bg-slate-800/50 rounded-xl max-w-lg mx-auto">
<p className="text-sm text-slate-500"> <p className="text-sm text-slate-500 dark:text-slate-400">
🔒 Your data is secure. We don't store email content and only have read access. 🔒 Your data is secure. We don't store email content and only have read access.
</p> </p>
</div> </div>
@@ -485,16 +485,16 @@ export function Setup() {
<div className="w-24 h-24 mx-auto mb-6 rounded-2xl bg-gradient-to-br from-primary-100 to-primary-200 flex items-center justify-center shadow-xl shadow-primary-500/10"> <div className="w-24 h-24 mx-auto mb-6 rounded-2xl bg-gradient-to-br from-primary-100 to-primary-200 flex items-center justify-center shadow-xl shadow-primary-500/10">
<Settings className="w-12 h-12 text-primary-600" /> <Settings className="w-12 h-12 text-primary-600" />
</div> </div>
<h1 className="text-3xl font-bold text-slate-900 mb-3">Sorting Settings</h1> <h1 className="text-3xl font-bold text-slate-900 dark:text-slate-100 mb-3">Sorting Settings</h1>
<p className="text-lg text-slate-600 max-w-md mx-auto"> <p className="text-lg text-slate-600 dark:text-slate-400 max-w-md mx-auto">
Customize how strictly the AI should sort your emails. Customize how strictly the AI should sort your emails.
</p> </p>
</div> </div>
<Card className="max-w-lg mx-auto shadow-xl border-0"> <Card className="max-w-lg mx-auto shadow-xl border-0 dark:bg-slate-800 dark:border-slate-700">
<CardContent className="p-8 space-y-8"> <CardContent className="p-8 space-y-8">
<div> <div>
<label className="block text-sm font-semibold text-slate-900 mb-4">Sorting Intensity</label> <label className="block text-sm font-semibold text-slate-900 dark:text-slate-100 mb-4">Sorting Intensity</label>
<div className="grid grid-cols-3 gap-3"> <div className="grid grid-cols-3 gap-3">
{[ {[
{ id: 'light', name: 'Light', desc: 'Only obvious distractions', emoji: '🌱' }, { id: 'light', name: 'Light', desc: 'Only obvious distractions', emoji: '🌱' },
@@ -506,30 +506,30 @@ export function Setup() {
onClick={() => setPreferences(p => ({ ...p, sortingStrictness: option.id }))} onClick={() => setPreferences(p => ({ ...p, sortingStrictness: option.id }))}
className={`p-4 rounded-xl border-2 text-center transition-all ${ className={`p-4 rounded-xl border-2 text-center transition-all ${
preferences.sortingStrictness === option.id preferences.sortingStrictness === option.id
? 'border-primary-500 bg-primary-50 shadow-lg shadow-primary-500/10' ? 'border-primary-500 dark:border-primary-400 bg-primary-50 dark:bg-primary-900/30 shadow-lg shadow-primary-500/10'
: 'border-slate-200 hover:border-slate-300 bg-white' : 'border-slate-200 dark:border-slate-700 hover:border-slate-300 dark:hover:border-slate-600 bg-white dark:bg-slate-800'
}`} }`}
> >
<span className="text-2xl mb-2 block">{option.emoji}</span> <span className="text-2xl mb-2 block">{option.emoji}</span>
<p className="font-semibold text-slate-900">{option.name}</p> <p className="font-semibold text-slate-900 dark:text-slate-100">{option.name}</p>
<p className="text-xs text-slate-500 mt-1">{option.desc}</p> <p className="text-xs text-slate-500 dark:text-slate-400 mt-1">{option.desc}</p>
</button> </button>
))} ))}
</div> </div>
</div> </div>
<div className="flex items-center justify-between p-5 bg-gradient-to-r from-slate-50 to-slate-100 rounded-xl"> <div className="flex items-center justify-between p-5 bg-gradient-to-r from-slate-50 to-slate-100 dark:from-slate-800 dark:to-slate-700 rounded-xl">
<div> <div>
<p className="font-semibold text-slate-900">Historical emails</p> <p className="font-semibold text-slate-900 dark:text-slate-100">Historical emails</p>
<p className="text-sm text-slate-500">Analyze and sort last 30 days</p> <p className="text-sm text-slate-500 dark:text-slate-400">Analyze and sort last 30 days</p>
</div> </div>
<button <button
onClick={() => setPreferences(p => ({ ...p, historicalSync: !p.historicalSync }))} onClick={() => setPreferences(p => ({ ...p, historicalSync: !p.historicalSync }))}
className={`w-14 h-8 rounded-full transition-all duration-300 ${ className={`w-14 h-8 rounded-full transition-all duration-300 ${
preferences.historicalSync ? 'bg-primary-500 shadow-lg shadow-primary-500/30' : 'bg-slate-300' preferences.historicalSync ? 'bg-primary-500 dark:bg-primary-600 shadow-lg shadow-primary-500/30' : 'bg-slate-300 dark:bg-slate-600'
}`} }`}
> >
<div className={`w-6 h-6 bg-white rounded-full shadow-md transition-transform duration-300 ${ <div className={`w-6 h-6 bg-white dark:bg-slate-200 rounded-full shadow-md transition-transform duration-300 ${
preferences.historicalSync ? 'translate-x-7' : 'translate-x-1' preferences.historicalSync ? 'translate-x-7' : 'translate-x-1'
}`} /> }`} />
</button> </button>
@@ -545,8 +545,8 @@ export function Setup() {
<div className="w-24 h-24 mx-auto mb-6 rounded-2xl bg-gradient-to-br from-primary-100 to-primary-200 flex items-center justify-center shadow-xl shadow-primary-500/10"> <div className="w-24 h-24 mx-auto mb-6 rounded-2xl bg-gradient-to-br from-primary-100 to-primary-200 flex items-center justify-center shadow-xl shadow-primary-500/10">
<Zap className="w-12 h-12 text-primary-600" /> <Zap className="w-12 h-12 text-primary-600" />
</div> </div>
<h1 className="text-3xl font-bold text-slate-900 mb-3">Choose your categories</h1> <h1 className="text-3xl font-bold text-slate-900 dark:text-slate-100 mb-3">Choose your categories</h1>
<p className="text-lg text-slate-600 max-w-md mx-auto"> <p className="text-lg text-slate-600 dark:text-slate-400 max-w-md mx-auto">
Which categories should your emails be sorted into? Which categories should your emails be sorted into?
</p> </p>
</div> </div>
@@ -558,21 +558,21 @@ export function Setup() {
onClick={() => toggleCategory(category.id)} onClick={() => toggleCategory(category.id)}
className={`flex items-center gap-4 p-5 rounded-xl border-2 text-left transition-all ${ className={`flex items-center gap-4 p-5 rounded-xl border-2 text-left transition-all ${
selectedCategories.includes(category.id) selectedCategories.includes(category.id)
? 'border-primary-500 bg-primary-50 shadow-lg shadow-primary-500/10' ? 'border-primary-500 dark:border-primary-400 bg-primary-50 dark:bg-primary-900/30 shadow-lg shadow-primary-500/10'
: 'border-slate-200 bg-white hover:border-slate-300 hover:shadow-md' : 'border-slate-200 dark:border-slate-700 bg-white dark:bg-slate-800 hover:border-slate-300 dark:hover:border-slate-600 hover:shadow-md'
}`} }`}
> >
<div className={`w-12 h-12 rounded-xl ${category.color} flex items-center justify-center text-2xl shadow-lg`}> <div className={`w-12 h-12 rounded-xl ${category.color} flex items-center justify-center text-2xl shadow-lg`}>
{category.icon} {category.icon}
</div> </div>
<div className="flex-1"> <div className="flex-1">
<p className="font-semibold text-slate-900">{category.name}</p> <p className="font-semibold text-slate-900 dark:text-slate-100">{category.name}</p>
<p className="text-sm text-slate-500">{category.description}</p> <p className="text-sm text-slate-500 dark:text-slate-400">{category.description}</p>
</div> </div>
<div className={`w-6 h-6 rounded-full border-2 flex items-center justify-center transition-all ${ <div className={`w-6 h-6 rounded-full border-2 flex items-center justify-center transition-all ${
selectedCategories.includes(category.id) selectedCategories.includes(category.id)
? 'border-primary-500 bg-primary-500' ? 'border-primary-500 dark:border-primary-400 bg-primary-500 dark:bg-primary-600'
: 'border-slate-300' : 'border-slate-300 dark:border-slate-600'
}`}> }`}>
{selectedCategories.includes(category.id) && <Check className="w-4 h-4 text-white" />} {selectedCategories.includes(category.id) && <Check className="w-4 h-4 text-white" />}
</div> </div>
@@ -580,7 +580,7 @@ export function Setup() {
))} ))}
</div> </div>
<p className="text-center text-sm text-slate-500 mt-6"> <p className="text-center text-sm text-slate-500 dark:text-slate-400 mt-6">
You can change these categories later in settings. You can change these categories later in settings.
</p> </p>
</div> </div>
@@ -591,20 +591,20 @@ export function Setup() {
<div className="w-28 h-28 mx-auto mb-8 rounded-full bg-gradient-to-br from-green-100 to-green-200 flex items-center justify-center shadow-2xl shadow-green-500/20 animate-pulse"> <div className="w-28 h-28 mx-auto mb-8 rounded-full bg-gradient-to-br from-green-100 to-green-200 flex items-center justify-center shadow-2xl shadow-green-500/20 animate-pulse">
<Sparkles className="w-14 h-14 text-green-600" /> <Sparkles className="w-14 h-14 text-green-600" />
</div> </div>
<h1 className="text-4xl font-bold text-slate-900 mb-4">All set! 🎉</h1> <h1 className="text-4xl font-bold text-slate-900 dark:text-slate-100 mb-4">All set! 🎉</h1>
<p className="text-xl text-slate-600 mb-10 max-w-md mx-auto"> <p className="text-xl text-slate-600 dark:text-slate-400 mb-10 max-w-md mx-auto">
Your email account is connected. The AI will now start intelligent sorting. Your email account is connected. The AI will now start intelligent sorting.
</p> </p>
<div className="inline-flex items-center gap-4 p-5 bg-gradient-to-r from-slate-50 to-slate-100 rounded-2xl mb-10 shadow-lg"> <div className="inline-flex items-center gap-4 p-5 bg-gradient-to-r from-slate-50 to-slate-100 dark:from-slate-800 dark:to-slate-700 rounded-2xl mb-10 shadow-lg">
<div className="w-14 h-14 rounded-xl bg-white flex items-center justify-center shadow-md"> <div className="w-14 h-14 rounded-xl bg-white dark:bg-slate-700 flex items-center justify-center shadow-md">
<Mail className="w-7 h-7 text-primary-500" /> <Mail className="w-7 h-7 text-primary-500 dark:text-primary-400" />
</div> </div>
<div className="text-left"> <div className="text-left">
<p className="font-semibold text-slate-900 text-lg"> <p className="font-semibold text-slate-900 dark:text-slate-100 text-lg">
{connectedProvider === 'gmail' ? 'Gmail' : connectedProvider === 'outlook' ? 'Outlook' : 'Email'} connected {connectedProvider === 'gmail' ? 'Gmail' : connectedProvider === 'outlook' ? 'Outlook' : 'Email'} connected
</p> </p>
<p className="text-slate-500">{connectedEmail || user?.email}</p> <p className="text-slate-500 dark:text-slate-400">{connectedEmail || user?.email}</p>
</div> </div>
<Badge variant="success" className="text-sm px-3 py-1">Active</Badge> <Badge variant="success" className="text-sm px-3 py-1">Active</Badge>
</div> </div>
@@ -628,7 +628,7 @@ export function Setup() {
{currentStep !== 'connect' && currentStep !== 'complete' && ( {currentStep !== 'connect' && currentStep !== 'complete' && (
<div className="flex justify-between max-w-lg mx-auto"> <div className="flex justify-between max-w-lg mx-auto">
<Button variant="ghost" onClick={handleBack} className="text-slate-600"> <Button variant="ghost" onClick={handleBack} className="text-slate-600 dark:text-slate-400">
<ArrowLeft className="w-5 h-5 mr-2" /> <ArrowLeft className="w-5 h-5 mr-2" />
Back Back
</Button> </Button>

View File

@@ -51,26 +51,26 @@ export function VerifyEmail() {
} }
return ( return (
<div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 flex items-center justify-center p-4"> <div className="min-h-screen bg-gradient-to-br from-slate-50 to-slate-100 dark:from-slate-900 dark:to-slate-800 flex items-center justify-center p-4">
<div className="w-full max-w-md"> <div className="w-full max-w-md">
{/* Logo */} {/* Logo */}
<Link to="/" className="flex items-center justify-center gap-2 mb-8"> <Link to="/" className="flex items-center justify-center gap-2 mb-8">
<div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center"> <div className="w-10 h-10 rounded-xl bg-gradient-to-br from-primary-500 to-primary-700 flex items-center justify-center">
<Mail className="w-5 h-5 text-white" /> <Mail className="w-5 h-5 text-white" />
</div> </div>
<span className="text-xl font-bold text-slate-900"> <span className="text-xl font-bold text-slate-900 dark:text-slate-100">
Email<span className="text-primary-600">Sorter</span> Email<span className="text-primary-600 dark:text-primary-400">Sorter</span>
</span> </span>
</Link> </Link>
<Card className="shadow-xl border-0"> <Card className="shadow-xl border-0 dark:bg-slate-800 dark:border-slate-700">
<CardHeader className="text-center pb-2"> <CardHeader className="text-center pb-2">
<CardTitle className="text-2xl"> <CardTitle className="text-2xl dark:text-slate-100">
{status === 'loading' && 'E-Mail wird verifiziert...'} {status === 'loading' && 'E-Mail wird verifiziert...'}
{status === 'success' && 'E-Mail verifiziert!'} {status === 'success' && 'E-Mail verifiziert!'}
{status === 'error' && 'Verifizierung fehlgeschlagen'} {status === 'error' && 'Verifizierung fehlgeschlagen'}
</CardTitle> </CardTitle>
<CardDescription> <CardDescription className="dark:text-slate-400">
{status === 'loading' && 'Bitte warte einen Moment.'} {status === 'loading' && 'Bitte warte einen Moment.'}
{status === 'success' && 'Deine E-Mail-Adresse wurde erfolgreich bestätigt.'} {status === 'success' && 'Deine E-Mail-Adresse wurde erfolgreich bestätigt.'}
{status === 'error' && error} {status === 'error' && error}
@@ -79,25 +79,25 @@ export function VerifyEmail() {
<CardContent> <CardContent>
{status === 'loading' && ( {status === 'loading' && (
<div className="flex flex-col items-center py-12"> <div className="flex flex-col items-center py-12">
<Loader2 className="w-12 h-12 animate-spin text-primary-500 mb-4" /> <Loader2 className="w-12 h-12 animate-spin text-primary-500 dark:text-primary-400 mb-4" />
<p className="text-slate-500">Verifizierung läuft...</p> <p className="text-slate-500 dark:text-slate-400">Verifizierung läuft...</p>
</div> </div>
)} )}
{status === 'success' && ( {status === 'success' && (
<div className="text-center py-8"> <div className="text-center py-8">
<div className="w-20 h-20 mx-auto mb-6 rounded-full bg-green-100 flex items-center justify-center"> <div className="w-20 h-20 mx-auto mb-6 rounded-full bg-green-100 dark:bg-green-900/30 flex items-center justify-center">
<CheckCircle className="w-10 h-10 text-green-600" /> <CheckCircle className="w-10 h-10 text-green-600 dark:text-green-400" />
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<div className="p-4 bg-green-50 border border-green-100 rounded-xl"> <div className="p-4 bg-green-50 dark:bg-green-900/30 border border-green-100 dark:border-green-800 rounded-xl">
<p className="text-green-700 font-medium"> <p className="text-green-700 dark:text-green-300 font-medium">
Dein Account ist jetzt vollständig aktiviert! Dein Account ist jetzt vollständig aktiviert!
</p> </p>
</div> </div>
<p className="text-slate-600"> <p className="text-slate-600 dark:text-slate-400">
Du kannst jetzt alle Features von EmailSorter nutzen. Du kannst jetzt alle Features von EmailSorter nutzen.
</p> </p>
@@ -110,18 +110,18 @@ export function VerifyEmail() {
{status === 'error' && ( {status === 'error' && (
<div className="text-center py-8"> <div className="text-center py-8">
<div className="w-20 h-20 mx-auto mb-6 rounded-full bg-red-100 flex items-center justify-center"> <div className="w-20 h-20 mx-auto mb-6 rounded-full bg-red-100 dark:bg-red-900/30 flex items-center justify-center">
<XCircle className="w-10 h-10 text-red-600" /> <XCircle className="w-10 h-10 text-red-600 dark:text-red-400" />
</div> </div>
<div className="space-y-4"> <div className="space-y-4">
<div className="p-4 bg-red-50 border border-red-100 rounded-xl"> <div className="p-4 bg-red-50 dark:bg-red-900/30 border border-red-100 dark:border-red-800 rounded-xl">
<p className="text-red-700"> <p className="text-red-700 dark:text-red-300">
{error || 'Der Verifizierungslink ist ungültig oder abgelaufen.'} {error || 'Der Verifizierungslink ist ungültig oder abgelaufen.'}
</p> </p>
</div> </div>
<p className="text-slate-600 text-sm"> <p className="text-slate-600 dark:text-slate-400 text-sm">
Falls dein Link abgelaufen ist, kannst du eine neue Verifizierungs-E-Mail anfordern. Falls dein Link abgelaufen ist, kannst du eine neue Verifizierungs-E-Mail anfordern.
</p> </p>
@@ -142,9 +142,9 @@ export function VerifyEmail() {
</Card> </Card>
{/* Help text */} {/* Help text */}
<p className="text-center text-sm text-slate-500 mt-6"> <p className="text-center text-sm text-slate-500 dark:text-slate-400 mt-6">
Probleme? Kontaktiere uns unter{' '} Probleme? Kontaktiere uns unter{' '}
<a href="mailto:support@emailsorter.de" className="text-primary-600 hover:underline"> <a href="mailto:support@emailsorter.de" className="text-primary-600 dark:text-primary-400 hover:underline">
support@emailsorter.de support@emailsorter.de
</a> </a>
</p> </p>

60
docs/README.md Normal file
View File

@@ -0,0 +1,60 @@
# Dokumentation
Diese Dokumentation ist in verschiedene Kategorien unterteilt:
## 📁 Struktur
```
docs/
├── setup/ # Setup-Anleitungen
├── deployment/ # Deployment & Production
├── development/ # Development-Dokumentation
├── server/ # Server-spezifische Docs
├── examples/ # Beispiel-Code
└── legacy/ # Legacy-Dateien
```
## 📚 Kategorien
### Setup (`docs/setup/`)
- **APPWRITE_SETUP.md** - Appwrite Installation & Konfiguration
- **APPWRITE_CORS_SETUP.md** - CORS-Konfiguration für Appwrite
- **GOOGLE_OAUTH_SETUP.md** - Google OAuth Setup
- **SETUP_GUIDE.md** - Allgemeine Setup-Anleitung
- **FAVICON_SETUP.md** - Favicon-Konfiguration
### Deployment (`docs/deployment/`)
- **README.md** - Deployment-Übersicht
- **GITEA_WEBHOOK_SETUP.md** - Vollständige Anleitung für automatisches Deployment via Gitea Webhook
- **WEBHOOK_QUICK_START.md** - Schnellstart-Anleitung (5 Minuten)
- **WEBHOOK_AUTHORIZATION.md** - Webhook-Authentifizierung und Sicherheit
- **PRODUCTION_SETUP.md** - Production-Server Setup
- **PRODUCTION_FIXES.md** - Production-Fixes & Troubleshooting
- **DEPLOYMENT_INSTRUCTIONS.md** - Manuelle Deployment-Anleitungen
### Development (`docs/development/`)
- **GIT_AUTHENTICATION_FIX.md** - Git-Authentifizierung
- **PROJECT_RENAME_GUIDE.md** - Projekt-Umbenennung
- **PROJECT_REVIEW_SUMMARY.md** - Projekt-Review
- **TASK_5_COMPLETION.md** - Task-Completion-Dokumentation
- **TESTING_SUMMARY.md** - Testing-Zusammenfassung
### Server (`docs/server/`)
- **CORRECTNESS_VALIDATION.md** - Korrektheits-Validierung
- **E2E_TEST_GUIDE.md** - End-to-End Test Guide
- **ENDPOINT_VERIFICATION.md** - API-Endpoint-Verifikation
- **FRONTEND_VERIFICATION.md** - Frontend-Verifikation
- **MANUAL_TEST_CHECKLIST.md** - Manuelle Test-Checkliste
- **TASK_4_COMPLETION_SUMMARY.md** - Task 4 Completion
### Examples (`docs/examples/`)
- **starter-for-react/** - React Starter Template (Beispiel)
### Legacy (`docs/legacy/`)
- **public/** - Alte Public-Dateien (falls noch benötigt)
## 🚀 Schnellstart
1. **Erstes Setup:** Siehe `docs/setup/SETUP_GUIDE.md`
2. **Production Deployment:** Siehe `docs/deployment/PRODUCTION_SETUP.md`
3. **Development:** Siehe `docs/development/` für Development-Dokumentation

View File

@@ -0,0 +1,219 @@
# Gitea Webhook Setup - Automatisches Deployment
Diese Anleitung erklärt, wie du einen Gitea-Webhook einrichtest, um automatisch zu deployen, wenn Code gepusht wird.
## Übersicht
Der Webhook funktioniert folgendermaßen:
1. **Push auf Gitea** → Gitea sendet Webhook-Event an deinen Server
2. **Webhook-Handler** empfängt das Event und verifiziert die Signatur
3. **Deployment-Skript** wird ausgeführt:
- Git Pull (falls auf Server)
- Frontend Build (`npm run build`)
- Upload auf Production-Server (via SCP/SSH)
- Backend Neustart (optional, via PM2)
## Voraussetzungen
- ✅ Gitea-Repository mit deinem Code
- ✅ Production-Server mit SSH-Zugriff
- ✅ Node.js auf dem Server installiert
- ✅ PM2 installiert (optional, für Backend-Neustart)
## Schritt 1: Webhook-Secret generieren
Generiere ein sicheres Secret für die Webhook-Signatur-Verification:
```bash
# Generiere ein zufälliges Secret (32 Zeichen)
node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
```
**Wichtig:** Speichere dieses Secret sicher - du brauchst es in Schritt 3 und 4.
## Schritt 2: Environment Variables konfigurieren
Füge folgende Variablen zu deiner `server/.env` Datei hinzu:
```bash
# Gitea Webhook Secret (aus Schritt 1)
GITEA_WEBHOOK_SECRET=dein_generiertes_secret_hier
# Optional: Authorization Header Token
GITEA_WEBHOOK_AUTH_TOKEN=dein_auth_token_hier
# Server-Deployment (optional, nur wenn automatischer Upload gewünscht)
DEPLOY_SERVER_HOST=91.99.156.85
DEPLOY_SERVER_USER=root
DEPLOY_SERVER_PATH=/var/www/emailsorter
DEPLOY_SSH_KEY=/path/to/ssh/private/key # Optional, falls SSH-Key benötigt wird
DEPLOY_FRONTEND_PATH=/var/www/emailsorter/client/dist
DEPLOY_BACKEND_PATH=/var/www/emailsorter/server
# PM2 für Backend-Neustart (optional)
USE_PM2=true
```
## Schritt 3: Webhook in Gitea konfigurieren
1. **Öffne dein Repository** in Gitea
2. Gehe zu **Settings****Webhooks**
3. Klicke auf **Add Webhook****Gitea**
4. Fülle die Felder aus:
- **Target URL:**
```
https://emailsorter.webklar.com/api/webhook/gitea
```
(Ersetze mit deiner tatsächlichen Domain)
- **HTTP Method:** `POST`
- **Post Content Type:** `application/json`
- **Secret:**
```
dein_generiertes_secret_hier
```
(Das gleiche Secret wie in Schritt 1)
- **Authorization Header:** (Optional)
```
Bearer dein_auth_token_hier
```
- **Trigger On:**
- ✅ **Push Events** (wichtig!)
- Optional: **Create**, **Delete** (falls gewünscht)
- **Branch Filter:** `main` oder `master` (je nach deinem Standard-Branch)
5. Klicke auf **Add Webhook**
## Schritt 4: Webhook testen
### Option A: Test über Gitea UI
1. Gehe zurück zu **Settings** → **Webhooks**
2. Klicke auf deinen Webhook
3. Klicke auf **Test Delivery** → **Push Events**
4. Prüfe die Antwort:
- ✅ **Status 202** = Webhook empfangen, Deployment gestartet
- ❌ **Status 401** = Secret falsch
- ❌ **Status 500** = Server-Fehler (prüfe Server-Logs)
### Option B: Test über Git Push
1. Mache eine kleine Änderung (z.B. Kommentar in einer Datei)
2. Committe und pushe:
```bash
git add .
git commit -m "test: Webhook test"
git push
```
3. Prüfe die Server-Logs:
```bash
# Auf dem Server
pm2 logs emailsorter-backend
# Oder
tail -f /var/log/emailsorter/webhook.log
```
4. Du solltest sehen:
```
📥 Gitea Webhook empfangen
🚀 Starte Deployment...
📦 Baue Frontend...
✅ Deployment erfolgreich abgeschlossen
```
## Schritt 5: Deployment-Logs prüfen
Die Webhook-Handler loggen alle Schritte. Prüfe die Logs:
```bash
# PM2 Logs
pm2 logs emailsorter-backend
# Oder direkt im Server
tail -f server/logs/webhook.log
```
## Fehlerbehebung
### Webhook wird nicht ausgelöst
- ✅ Prüfe, ob die **Target URL** korrekt ist
- ✅ Prüfe, ob der Server erreichbar ist (`curl https://emailsorter.webklar.com/api/webhook/status`)
- ✅ Prüfe Gitea-Logs: **Settings** → **Webhooks** → **Delivery Log**
### "Ungültige Webhook-Signatur" (401)
- ✅ Prüfe, ob `GITEA_WEBHOOK_SECRET` in `server/.env` gesetzt ist
- ✅ Prüfe, ob das Secret in Gitea **genau gleich** ist (keine Leerzeichen!)
- ✅ Prüfe, ob der Webhook **"application/json"** als Content-Type verwendet
### Deployment schlägt fehl
- ✅ Prüfe Server-Logs für detaillierte Fehlermeldungen
- ✅ Prüfe, ob SSH-Zugriff funktioniert: `ssh root@91.99.156.85`
- ✅ Prüfe, ob `npm` und `node` auf dem Server installiert sind
- ✅ Prüfe, ob die Pfade (`DEPLOY_SERVER_PATH`) korrekt sind
### Frontend-Build fehlgeschlagen
- ✅ Prüfe, ob alle Dependencies installiert sind: `cd client && npm install`
- ✅ Prüfe, ob `.env.production` korrekt konfiguriert ist
- ✅ Prüfe Build-Logs für TypeScript/ESLint-Fehler
### Backend startet nicht neu
- ✅ Prüfe, ob PM2 installiert ist: `pm2 --version`
- ✅ Prüfe, ob `USE_PM2=true` in `.env` gesetzt ist
- ✅ Prüfe PM2-Status: `pm2 list`
## Sicherheit
### Best Practices
1. **Webhook-Secret:** Verwende immer ein starkes, zufälliges Secret
2. **HTTPS:** Stelle sicher, dass dein Server HTTPS verwendet (Let's Encrypt)
3. **Firewall:** Beschränke Webhook-Endpoint auf Gitea-IPs (optional)
4. **Rate Limiting:** Der Webhook-Endpoint ist bereits rate-limited
5. **Logs:** Prüfe regelmäßig die Webhook-Logs auf verdächtige Aktivitäten
## Alternative: Lokales Deployment ohne Server-Upload
Falls du den automatischen Upload auf den Server nicht möchtest, kannst du:
1. `DEPLOY_SERVER_HOST` **nicht** setzen
2. Das Deployment-Skript erstellt nur den Build lokal
3. Du lädst die Dateien manuell hoch oder verwendest ein anderes Tool
Der Webhook wird trotzdem ausgelöst und erstellt den Build, aber überspringt den Upload-Schritt.
## Manuelles Deployment auslösen
Du kannst das Deployment auch manuell auslösen:
```bash
# Auf dem Server
cd /var/www/emailsorter
node scripts/deploy-to-server.mjs
```
## Nächste Schritte
Nach erfolgreichem Setup:
1. ✅ Teste den Webhook mit einem kleinen Push
2. ✅ Prüfe, ob die Website aktualisiert wurde
3. ✅ Überwache die Logs für die ersten Deployments
4. ✅ Dokumentiere deine spezifische Konfiguration
## Support
Bei Problemen:
- Prüfe die Server-Logs
- Prüfe Gitea Webhook Delivery Logs
- Prüfe die Environment Variables
- Teste SSH-Verbindung manuell

View File

@@ -0,0 +1,51 @@
# Production Fixes - Wichtige Schritte
## ✅ Behoben
1. **Debug-Logs entfernt** - Alle Debug-Logs zu `127.0.0.1:7242` wurden entfernt
2. **Favicon-Problem behoben** - `site.webmanifest` verwendet jetzt vorhandene SVG-Dateien
## ⚠️ Noch zu beheben (im Appwrite Dashboard)
### 1. Appwrite CORS-Konfiguration
**Problem:** Appwrite erlaubt nur `https://localhost` statt `https://emailsorter.webklar.com`
**Lösung:**
1. Gehe zu: https://appwrite.webklar.com
2. Öffne dein Projekt
3. Gehe zu **Settings****Platforms** (oder **Web**)
4. Füge eine neue Platform hinzu:
- **Name:** Production
- **Hostname:** `emailsorter.webklar.com`
- **Origin:** `https://emailsorter.webklar.com`
5. Speichere die Änderungen
**ODER** bearbeite die existierende Platform und ändere den Hostname/Origin zu `https://emailsorter.webklar.com`
### 2. Backend-Server (502 Bad Gateway)
**Problem:** `/api/analytics/track` gibt 502 zurück - Backend-Server läuft nicht
**Lösung:**
1. SSH zum Server: `ssh user@webklar.com`
2. Prüfe ob Server läuft: `pm2 list` oder `ps aux | grep node`
3. Falls nicht: Starte den Server:
```bash
cd /path/to/ANDJJJJJJ/server
pm2 start index.mjs --name emailsorter-api
pm2 save
```
4. Prüfe Logs: `pm2 logs emailsorter-api`
### 3. Build deployen
Nach dem Commit und Push:
1. Kopiere den Inhalt von `client/dist` auf den Web-Server
2. Stelle sicher, dass die Dateien unter `https://emailsorter.webklar.com` erreichbar sind
## Nach allen Fixes
1. Leere den Browser-Cache (Strg+Shift+R)
2. Teste die Website
3. Prüfe die Browser-Konsole - sollte keine Fehler mehr zeigen

60
docs/deployment/README.md Normal file
View File

@@ -0,0 +1,60 @@
# Deployment-Dokumentation
Diese Dokumentation beschreibt alle Aspekte des Deployments für E-Mail-Sorter.
## 📚 Inhaltsverzeichnis
### Automatisches Deployment
- **[Gitea Webhook Setup](./GITEA_WEBHOOK_SETUP.md)** - Vollständige Anleitung für automatisches Deployment via Gitea Webhook
- **[Webhook Quick Start](./WEBHOOK_QUICK_START.md)** - Schnellstart-Anleitung (5 Minuten)
- **[Webhook Authorization](./WEBHOOK_AUTHORIZATION.md)** - Authentifizierung und Sicherheit
### Manuelles Deployment
- **[Deployment Instructions](./DEPLOYMENT_INSTRUCTIONS.md)** - Manuelle Deployment-Schritte
- **[Production Setup](./PRODUCTION_SETUP.md)** - Production-Server Setup
- **[Production Fixes](./PRODUCTION_FIXES.md)** - Bekannte Probleme und Lösungen
## 🚀 Schnellstart
Für automatisches Deployment siehe [Webhook Quick Start](./WEBHOOK_QUICK_START.md).
## 📋 Übersicht
### Automatisches Deployment (Empfohlen)
1. **Gitea Webhook einrichten** → Siehe [GITEA_WEBHOOK_SETUP.md](./GITEA_WEBHOOK_SETUP.md)
2. **Bei jedem Push** wird automatisch deployed
3. **Keine manuellen Schritte** nötig
### Manuelles Deployment
1. **Frontend bauen:** `cd client && npm run build`
2. **Dateien hochladen** auf Server
3. **Backend neustarten** (falls nötig)
Siehe [DEPLOYMENT_INSTRUCTIONS.md](./DEPLOYMENT_INSTRUCTIONS.md) für Details.
## 🔧 Konfiguration
Alle Deployment-Konfigurationen finden sich in `server/.env`:
```bash
# Webhook-Konfiguration
GITEA_WEBHOOK_SECRET=...
GITEA_WEBHOOK_AUTH_TOKEN=...
# Server-Deployment
DEPLOY_SERVER_HOST=91.99.156.85
DEPLOY_SERVER_USER=root
DEPLOY_SERVER_PATH=/var/www/emailsorter
USE_PM2=true
```
## 📞 Support
Bei Problemen:
1. Prüfe die Server-Logs
2. Siehe [Production Fixes](./PRODUCTION_FIXES.md)
3. Prüfe Webhook Delivery Logs in Gitea

View File

@@ -0,0 +1,83 @@
# Webhook Authorization Header - Anleitung
Der Webhook unterstützt **zwei Authentifizierungsmethoden**:
1. **Signature-Verification** (Standard, von Gitea)
2. **Authorization Header** (Optional, zusätzliche Sicherheit)
## Option 1: Nur Signature (Standard)
Das ist die Standard-Methode, die Gitea automatisch verwendet:
### Konfiguration
In `server/.env`:
```bash
GITEA_WEBHOOK_SECRET=dein_secret_hier
```
### In Gitea
- **Secret:** Trage das gleiche Secret ein
- **Authorization Header:** Nicht nötig
Gitea sendet automatisch den `X-Gitea-Signature` Header.
## Option 2: Authorization Header (Zusätzliche Sicherheit)
Falls du zusätzliche Sicherheit möchtest oder den Webhook manuell aufrufst:
### Schritt 1: Token generieren
```bash
node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
```
### Schritt 2: Server konfigurieren
In `server/.env`:
```bash
GITEA_WEBHOOK_SECRET=dein_secret_hier
GITEA_WEBHOOK_AUTH_TOKEN=dein_auth_token_hier
```
### Schritt 3: In Gitea konfigurieren
Gitea unterstützt **keine** Authorization Header direkt, aber du kannst:
#### Option A: Nur Signature verwenden (empfohlen)
- Lass `GITEA_WEBHOOK_AUTH_TOKEN` leer
- Nur `GITEA_WEBHOOK_SECRET` verwenden
#### Option B: Manuelle Webhook-Aufrufe
Wenn du den Webhook manuell aufrufst (z.B. via curl), verwende:
```bash
curl -X POST https://emailsorter.webklar.com/api/webhook/gitea \
-H "Content-Type: application/json" \
-H "X-Gitea-Signature: sha256=..." \
-H "Authorization: Bearer dein_auth_token_hier" \
-d '{"ref":"refs/heads/main",...}'
```
## Option 3: Beide Methoden kombinieren
Für maximale Sicherheit kannst du beide verwenden:
```bash
# In server/.env
GITEA_WEBHOOK_SECRET=secret_fuer_signature
GITEA_WEBHOOK_AUTH_TOKEN=token_fuer_auth_header
```
**Verhalten:**
- Wenn beide gesetzt sind, müssen **beide** passen
- Wenn nur eine gesetzt ist, reicht diese
## Empfehlung
**Für Gitea-Webhooks:** Verwende nur `GITEA_WEBHOOK_SECRET` (Signature)
**Für manuelle Aufrufe:** Verwende `GITEA_WEBHOOK_AUTH_TOKEN` (Authorization Header)
**Für maximale Sicherheit:** Verwende beide

View File

@@ -0,0 +1,61 @@
# Gitea Webhook - Quick Start Guide
## 🚀 Schnellstart (5 Minuten)
### Schritt 1: Secret generieren
```bash
node -e "console.log(require('crypto').randomBytes(16).toString('hex'))"
```
Kopiere das generierte Secret - du brauchst es gleich!
### Schritt 2: Server konfigurieren
Füge zu `server/.env` hinzu:
```bash
GITEA_WEBHOOK_SECRET=dein_generiertes_secret_hier
DEPLOY_SERVER_HOST=91.99.156.85
DEPLOY_SERVER_USER=root
DEPLOY_SERVER_PATH=/var/www/emailsorter
USE_PM2=true
```
### Schritt 3: Gitea Webhook einrichten
1. Gehe zu deinem Repository → **Settings****Webhooks**
2. Klicke **Add Webhook****Gitea**
3. Fülle aus:
- **Target URL:** `https://emailsorter.webklar.com/api/webhook/gitea`
- **Secret:** `dein_generiertes_secret_hier` (aus Schritt 1)
- **Trigger On:** ✅ **Push Events**
- **Branch Filter:** `main` oder `master`
4. Klicke **Add Webhook**
### Schritt 4: Testen
```bash
git add .
git commit -m "test: Webhook test"
git push
```
Prüfe die Server-Logs - du solltest sehen:
```
📥 Gitea Webhook empfangen
🚀 Starte Deployment...
✅ Deployment erfolgreich abgeschlossen
```
## ✅ Fertig!
Jetzt wird bei jedem Push automatisch deployed!
## 📚 Weitere Informationen
Siehe [GITEA_WEBHOOK_SETUP.md](./GITEA_WEBHOOK_SETUP.md) für:
- Detaillierte Anleitung
- Fehlerbehebung
- Sicherheitsbest Practices
- Server-Upload Konfiguration

View File

Before

Width:  |  Height:  |  Size: 1012 B

After

Width:  |  Height:  |  Size: 1012 B

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

52
scripts/README.md Normal file
View File

@@ -0,0 +1,52 @@
# Scripts
Dieser Ordner enthält alle Hilfs-Scripts für das Projekt.
## 🚀 Aktive Scripts
### Deployment
- **deploy-build.js** - Node.js Script für Build-Deployment (veraltet, wird durch Webhook ersetzt)
- **deploy-to-server.mjs** - Automatisches Deployment-Skript (wird vom Webhook aufgerufen)
### Setup
- **setup-appwrite.ps1** - Appwrite Setup-Script
- **setup-production.ps1** - Production Setup-Script
## 📦 Veraltete Scripts (können entfernt werden)
Die folgenden Scripts sind veraltet und werden nicht mehr benötigt:
- `git-commit.bat` - Git-Commits sollten direkt über Git gemacht werden
- `git-commit.sh` - Git-Commits sollten direkt über Git gemacht werden
- `git-commit-fix.bat` - Git-Commits sollten direkt über Git gemacht werden
- `FINAL_COMMIT.bat` - Git-Commits sollten direkt über Git gemacht werden
- `run-git-commit.ps1` - Git-Commits sollten direkt über Git gemacht werden
- `COMMIT_COMMANDS.txt` - Temporäre Datei
- `COMMIT_MESSAGE.md` - Temporäre Datei
**Empfehlung:** Diese Dateien können gelöscht werden, da Git-Commits direkt über `git commit` gemacht werden sollten.
## 📝 Verwendung
### Deployment-Script (automatisch)
Das Deployment-Script wird automatisch vom Webhook-Handler aufgerufen. Siehe `docs/deployment/GITEA_WEBHOOK_SETUP.md` für Details.
### Setup-Scripts
```powershell
# Appwrite Setup
.\setup-appwrite.ps1
# Production Setup
.\setup-production.ps1
```
## 🔧 Neue Scripts hinzufügen
Wenn du neue Scripts hinzufügst:
1. Dokumentiere sie in dieser README
2. Füge sie zu `.gitignore` hinzu, falls sie sensible Daten enthalten
3. Stelle sicher, dass sie plattformübergreifend funktionieren (oder dokumentiere Plattform-Anforderungen)

View File

@@ -37,7 +37,7 @@ if (-not $gitExe) {
Write-Host "Alternativ führe diese Befehle manuell in Git Bash aus:" -ForegroundColor Yellow Write-Host "Alternativ führe diese Befehle manuell in Git Bash aus:" -ForegroundColor Yellow
Write-Host "cd c:\Users\User\Documents\GitHub\ANDJJJJJJ" -ForegroundColor Cyan Write-Host "cd c:\Users\User\Documents\GitHub\ANDJJJJJJ" -ForegroundColor Cyan
Write-Host "git add ." -ForegroundColor Cyan Write-Host "git add ." -ForegroundColor Cyan
Write-Host "git commit -m `"feat: Control Panel Redesign v2.0 - Card-basiertes Layout, Side Panels, Dark Mode Fixes, Volle Breite Layout`"" -ForegroundColor Cyan Write-Host "git commit -m `"feat: Control Panel v2.0 - Cards, Side Panels, Dark Mode, volle Breite`"" -ForegroundColor Cyan
Write-Host "git push" -ForegroundColor Cyan Write-Host "git push" -ForegroundColor Cyan
exit 1 exit 1
} }
@@ -58,7 +58,7 @@ if ($LASTEXITCODE -ne 0) {
} }
Write-Host "Erstelle Commit..." -ForegroundColor Yellow Write-Host "Erstelle Commit..." -ForegroundColor Yellow
& $gitExe commit -m "feat: Control Panel Redesign v2.0 - Card-basiertes Layout, Side Panels, Dark Mode Fixes, Volle Breite Layout" & $gitExe commit -m "feat: Control Panel v2.0 - Cards, Side Panels, Dark Mode, volle Breite"
if ($LASTEXITCODE -ne 0) { if ($LASTEXITCODE -ne 0) {
Write-Host "❌ Fehler beim Commit" -ForegroundColor Red Write-Host "❌ Fehler beim Commit" -ForegroundColor Red