From 7b22889e2322a62b9fa4bf064d94ef5e874f0768 Mon Sep 17 00:00:00 2001 From: DanielS Date: Wed, 24 Jun 2026 00:02:27 +0200 Subject: [PATCH] feat: add partner list and customer list explorer to admin settings --- shop/app/admin/einstellungen/page.tsx | 306 +++++++++++++++++++++++++- shop/lib/actions/end-customers.ts | 13 ++ shop/lib/actions/users.ts | 15 ++ 3 files changed, 325 insertions(+), 9 deletions(-) diff --git a/shop/app/admin/einstellungen/page.tsx b/shop/app/admin/einstellungen/page.tsx index 6968009..af98118 100644 --- a/shop/app/admin/einstellungen/page.tsx +++ b/shop/app/admin/einstellungen/page.tsx @@ -2,32 +2,320 @@ import { useState, useEffect } from 'react'; import { Switch } from '@/components/ui/switch'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { getPartners, createUser } from '@/lib/actions/users'; +import { getPartnerEndCustomers } from '@/lib/actions/end-customers'; +import { Plus, Users, Mail, Eye, Key, CheckCircle, ShieldAlert, Loader2 } from 'lucide-react'; export default function AdminSettings() { const [demoActive, setDemoActive] = useState(true); + const [partners, setPartners] = useState([]); + const [loadingPartners, setLoadingPartners] = useState(true); + + // Modal States + const [isCreateOpen, setIsCreateOpen] = useState(false); + const [isCustomersOpen, setIsCustomersOpen] = useState(false); + + // Selected Partner / Customers State + const [selectedPartner, setSelectedPartner] = useState(null); + const [partnerCustomers, setPartnerCustomers] = useState([]); + const [loadingCustomers, setLoadingCustomers] = useState(false); + + // Create Partner Form States + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [createError, setCreateError] = useState(null); + const [creating, setCreating] = useState(false); useEffect(() => { + // 1. Demo Banner Status auslesen const state = localStorage.getItem('demo_banner_disabled') !== 'true'; setDemoActive(state); + + // 2. Partner laden + loadPartners(); }, []); + const loadPartners = async () => { + setLoadingPartners(true); + try { + const data = await getPartners(); + setPartners(data); + } catch (err) { + console.error('Fehler beim Laden der Partner:', err); + } finally { + setLoadingPartners(false); + } + }; + const toggleDemo = (checked: boolean) => { setDemoActive(checked); localStorage.setItem('demo_banner_disabled', (!checked).toString()); - // Event feuern, damit andere Tabs/Komponenten sofort updaten window.dispatchEvent(new Event('storage_demo_changed')); }; + const handleCreatePartner = async (e: React.FormEvent) => { + e.preventDefault(); + setCreating(true); + setCreateError(null); + + try { + await createUser({ + email, + password: password || undefined, + email_confirm: true, + }); + setIsCreateOpen(false); + setEmail(''); + setPassword(''); + loadPartners(); + } catch (err: any) { + console.error('Fehler beim Erstellen des Partners:', err); + setCreateError(err.message || 'Ein Fehler ist aufgetreten.'); + } finally { + setCreating(false); + } + }; + + const handleShowCustomers = async (partner: any) => { + setSelectedPartner(partner); + setPartnerCustomers([]); + setLoadingCustomers(true); + setIsCustomersOpen(true); + + try { + const customers = await getPartnerEndCustomers(partner.id); + setPartnerCustomers(customers); + } catch (err) { + console.error('Fehler beim Laden der Partner-Kunden:', err); + } finally { + setLoadingCustomers(false); + } + }; + return ( -
-

Admin Einstellungen

-
-
-

Demo-Warnmeldungen

-

Schaltet gelbe Banner ein oder aus.

-
- +
+ {/* Page Header */} +
+

Admin Einstellungen

+

+ Verwalten Sie globale Shopeinstellungen, Partnerkonten und Kundenzuweisungen. +

+ + {/* Allgemeine Einstellungen */} +
+

Allgemeine Einstellungen

+
+
+

Demo-Warnmeldungen

+

Schaltet gelbe Banner ein oder aus.

+
+ +
+
+ + {/* Partnerverwaltung */} +
+
+
+ +

Partner-Management

+
+ +
+ + {/* Partner Liste */} + {loadingPartners ? ( +
+ + Lade Partner... +
+ ) : partners.length === 0 ? ( +
+ Keine Partner registriert. +
+ ) : ( +
+ + + + + + + + + + + {partners.map((partner) => ( + + + + + + + ))} + +
Partner E-MailStatusErstellt amAktionen
+
+ + {partner.email} +
+
+ + Aktiv + + + {partner.created_at ? new Date(partner.created_at).toLocaleDateString('de-DE') : '–'} + + +
+
+ )} +
+ + {/* MODAL 1: Neuen Partner erstellen */} + + + + Neuen Partner anlegen + + Erstellt einen Partner-Zugang. Dieser kann sich mit den Daten im Shop einloggen. + + + +
+
+ + setEmail(e.target.value)} + className="bg-slate-50 dark:bg-white/5 border-slate-200 dark:border-white/10" + /> +
+
+ + setPassword(e.target.value)} + className="bg-slate-50 dark:bg-white/5 border-slate-200 dark:border-white/10" + /> +
+ + {createError && ( +

+ {createError} +

+ )} + + + + + +
+
+
+ + {/* MODAL 2: Kundenliste eines Partners anzeigen */} + + + + Kunden von {selectedPartner?.email} + + Liste aller angelegten Endkunden dieses Partners. + + + +
+ {loadingCustomers ? ( +
+ + Lade Kunden... +
+ ) : partnerCustomers.length === 0 ? ( +
+ Dieser Partner hat noch keine Kunden angelegt. +
+ ) : ( +
+ + + + + + + + + + + {partnerCustomers.map((customer) => ( + + + + + + + ))} + +
UnternehmenAnsprechpartnerOrtStatus
+ {customer.company_name} + + {[customer.first_name, customer.last_name].filter(Boolean).join(' ') || '–'} + + {[customer.zip, customer.city].filter(Boolean).join(' ') || '–'} + + {customer.is_anonymized ? ( + + Anonymisiert + + ) : ( + + Aktiv + + )} +
+
+ )} +
+ + + + +
+
); } diff --git a/shop/lib/actions/end-customers.ts b/shop/lib/actions/end-customers.ts index 9fcd740..1c6d152 100644 --- a/shop/lib/actions/end-customers.ts +++ b/shop/lib/actions/end-customers.ts @@ -1,6 +1,7 @@ 'use server' import { createClient } from '@/lib/supabase/server' +import { createAdminClient } from '@/lib/supabase/admin' import { revalidatePath } from 'next/cache' import type { EndCustomer } from '@/lib/types' @@ -143,3 +144,15 @@ export async function anonymizeEndCustomer(id: string): Promise { revalidatePath('/my-customers') revalidatePath(`/my-customers/${id}`) } + +export async function getPartnerEndCustomers(partnerId: string): Promise { + const admin = createAdminClient() + const { data, error } = await admin + .from('end_customers') + .select('*') + .eq('partner_id', partnerId) + .order('company_name', { ascending: true }) + + if (error) throw error + return data as EndCustomer[] +} diff --git a/shop/lib/actions/users.ts b/shop/lib/actions/users.ts index 1d18664..b325a82 100644 --- a/shop/lib/actions/users.ts +++ b/shop/lib/actions/users.ts @@ -48,3 +48,18 @@ export async function resetPassword(email: string) { if (error) throw error // Usually you'd send this link or let Supabase handle it via regular auth.resetPasswordForEmail } + +export async function getPartners() { + const admin = createAdminClient() + const { data: { users }, error } = await admin.auth.admin.listUsers() + if (error) throw error + + const { data: dbUsers, error: dbError } = await admin + .from('users') + .select('id, role') + .eq('role', 'partner') + if (dbError) throw dbError + + const partnerIds = new Set(dbUsers.map(u => u.id)) + return users.filter(u => partnerIds.has(u.id)) +}