Files
stadtwerke/innungsapp/apps/admin/app/dashboard/einstellungen/page.tsx
Timo Knuth fca42db4d2 Rebuild as InnungsApp project: replace stadtwerke analysis with full documentation
- PRD: vollständige Produktspezifikation (5 Module, Scope, Akzeptanzkriterien)
- ARCHITECTURE: Tech Stack, Ordnerstruktur, Multi-Tenancy, Push, Kosten
- DATABASE_SCHEMA: Vollständiges SQL-Schema mit RLS Policies und Views
- USER_STORIES: 40+ Stories nach Rolle (Admin, Mitglied, Azubi, Obermeister)
- PERSONAS: 5 detaillierte Nutzerprofile mit Alltag, Zitaten und Erwartungen
- BUSINESS_MODEL: Preistabellen, Unit Economics, Revenue-Projektionen, Distribution
- ROADMAP: 6 Phasen, Sprint-Planung, Meilensteine und KPIs
- COMPETITIVE_ANALYSIS: Wettbewerbsmatrix, USPs, Preispositionierung
- API_DESIGN: Supabase Query Patterns, Edge Functions, Realtime Subscriptions
- ONBOARDING_FLOWS: 7 User Flows von Setup bis Fehlerfall
- GTM_STRATEGY: 3-Phasen-Vertrieb, Outreach-Sequenz, Einwandbehandlung
- AZUBI_MODULE: Video-Feed, 1-Click-Apply, Chat, Berichtsheft, Quiz
- DSGVO_KONZEPT: Rechtsgrundlagen, TOMs, AVV, Minderjährige, Incident Response
- FEATURES_BACKLOG: 72 Features nach MoSCoW + Technische Schulden

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-18 19:03:37 +01:00

116 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client'
import { trpc } from '@/lib/trpc-client'
import { useState } from 'react'
export default function EinstellungenPage() {
const { data: org, isLoading } = trpc.organizations.me.useQuery()
const updateMutation = trpc.organizations.update.useMutation()
const avvMutation = trpc.organizations.acceptAvv.useMutation()
const [name, setName] = useState('')
const [contactEmail, setContactEmail] = useState('')
if (isLoading) return <div className="text-gray-500">Wird geladen...</div>
if (!org) return null
return (
<div className="max-w-2xl space-y-8">
<h1 className="text-2xl font-bold text-gray-900">Einstellungen</h1>
{/* Org Settings */}
<div className="bg-white rounded-xl border shadow-sm p-6 space-y-4">
<h2 className="font-semibold text-gray-900">Innung</h2>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Name der Innung</label>
<input
defaultValue={org.name}
onChange={(e) => setName(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-brand-500"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-1">Kontakt-E-Mail</label>
<input
type="email"
defaultValue={org.contactEmail ?? ''}
onChange={(e) => setContactEmail(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-brand-500"
/>
</div>
<button
onClick={() => updateMutation.mutate({ name: name || undefined, contactEmail: contactEmail || undefined })}
disabled={updateMutation.isPending}
className="bg-brand-500 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-brand-600 disabled:opacity-60 transition-colors"
>
{updateMutation.isPending ? 'Wird gespeichert...' : 'Speichern'}
</button>
{updateMutation.isSuccess && (
<p className="text-sm text-green-600">Einstellungen gespeichert </p>
)}
</div>
{/* AVV */}
<div className="bg-white rounded-xl border shadow-sm p-6 space-y-4">
<h2 className="font-semibold text-gray-900">Auftragsverarbeitungsvertrag (AVV)</h2>
<p className="text-sm text-gray-600">
Der AVV regelt die Verarbeitung personenbezogener Daten im Auftrag Ihrer Innung
durch InnungsApp GmbH gemäß Art. 28 DSGVO.
</p>
<a
href="/avv.pdf"
download
className="inline-flex items-center gap-2 text-sm text-brand-600 hover:underline"
>
📄 AVV als PDF herunterladen
</a>
{org.avvAccepted ? (
<div className="bg-green-50 border border-green-200 rounded-lg p-4">
<p className="text-sm text-green-700 font-medium">
AVV akzeptiert am {org.avvAcceptedAt?.toLocaleDateString('de-DE')}
</p>
</div>
) : (
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4 space-y-3">
<p className="text-sm text-yellow-800 font-medium">
Der AVV muss vor dem Go-Live akzeptiert werden.
</p>
<label className="flex items-start gap-2 cursor-pointer">
<input
type="checkbox"
id="avv-check"
className="mt-0.5 rounded border-gray-300"
/>
<span className="text-sm text-gray-700">
Ich bestätige, dass ich den AVV gelesen habe und im Namen der Innung akzeptiere.
</span>
</label>
<button
onClick={() => {
const cb = document.getElementById('avv-check') as HTMLInputElement
if (!cb.checked) { alert('Bitte bestätigen Sie den AVV.'); return }
avvMutation.mutate()
}}
disabled={avvMutation.isPending}
className="bg-brand-500 text-white px-4 py-2 rounded-lg text-sm font-medium hover:bg-brand-600 disabled:opacity-60 transition-colors"
>
AVV verbindlich akzeptieren
</button>
</div>
)}
</div>
{/* Plan Info */}
<div className="bg-white rounded-xl border shadow-sm p-6">
<h2 className="font-semibold text-gray-900 mb-2">Plan</h2>
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-brand-100 text-brand-700 capitalize">
{org.plan}
</span>
<p className="text-sm text-gray-500 mt-2">
Für Upgrades oder Fragen zum Plan: kontakt@innungsapp.de
</p>
</div>
</div>
)
}