simplify heroui layouts and add sonarqube workflow
Some checks failed
Deploy Discord Bot / deploy (push) Has been cancelled
SonarQube / sonar (push) Has been cancelled

This commit is contained in:
Pepe44DEV
2026-07-01 15:46:04 +02:00
parent da72f49255
commit a604fb494f
10 changed files with 237 additions and 227 deletions

View File

@@ -0,0 +1,22 @@
name: SonarQube
on:
push:
branches:
- main
- master
pull_request:
jobs:
sonar:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: sonarsource/sonarqube-scan-action@v5
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: http://10.0.0.15:9001

View File

@@ -1,19 +1,20 @@
import { useState } from 'react';
import { Avatar, Button, Chip, ScrollShadow, Tooltip } from '@heroui/react';
import {
ChevronLeft, ChevronRight, LogOut, Server, PanelLeftClose, PanelLeft,
Activity, AudioLines, CalendarDays, ClipboardList, Home,
LogIn, Music, Puzzle, RadioTower, Settings, Shield, Sparkles,
Tag, Ticket, Wrench, Bot
Avatar, Button, Card, CardContent, ScrollShadow, Tooltip,
Select, SelectTrigger, SelectValue, SelectPopover, ListBox, ListBoxItem
} from '@heroui/react';
import {
LogOut, PanelLeftClose, PanelLeft, Activity, AudioLines, CalendarDays,
ClipboardList, Home, LogIn, Music, Puzzle, RadioTower, Settings,
Shield, Sparkles, Tag, Ticket, Wrench
} from 'lucide-react';
import { useApp } from '../../context/AppContext';
import { guildIconUrl } from '../../utils/formatters';
const navGroups = [
{
label: 'Dashboard',
items: [
{ key: 'overview', label: 'Übersicht', icon: <Home size={18} /> },
{ key: 'overview', label: 'Uebersicht', icon: <Home size={18} /> },
]
},
{
@@ -61,44 +62,41 @@ export function Sidebar() {
const { user, guilds, currentGuildId, section, setCurrentGuildId, setSection, handleLogout } = useApp();
const [collapsed, setCollapsed] = useState(false);
const selectedGuild = guilds.find((g) => g.id === currentGuildId);
return (
<aside className={`flex h-full flex-col border-r border-default-100 bg-gradient-to-b from-default-50/40 to-background transition-all duration-200 ${collapsed ? 'w-16' : 'w-64'}`}>
<aside className={`flex h-full flex-col transition-all duration-200 ${collapsed ? 'w-20' : 'w-72'}`}>
<div className={`flex items-center gap-3 px-4 pt-4 pb-3 ${collapsed ? 'justify-center' : ''}`}>
<Avatar name="Papo" radius="lg" />
{!collapsed && (
<>
<div className="flex size-10 shrink-0 items-center justify-center rounded-xl bg-gradient-to-br from-primary-400 to-primary-600 text-lg font-bold text-white shadow-lg shadow-primary-500/25">
P
</div>
<div className="min-w-0">
<div className="text-base font-bold">Papo</div>
<div className="text-[10px] uppercase tracking-widest text-default-400">Dashboard</div>
</div>
</>
)}
{collapsed && (
<div className="flex size-10 items-center justify-center rounded-xl bg-gradient-to-br from-primary-400 to-primary-600 text-lg font-bold text-white shadow-lg shadow-primary-500/25">
P
<div className="min-w-0">
<div className="text-base font-bold">Papo</div>
<div className="text-[10px] uppercase tracking-widest text-default-400">Dashboard</div>
</div>
)}
</div>
<div className={`px-3 pb-2 ${collapsed ? 'px-2' : ''}`}>
<select
className={`w-full rounded-xl border border-default-200 bg-default-50 text-sm text-foreground outline-none transition-colors focus:border-primary-400 ${collapsed ? 'px-1 py-2 text-center text-[10px]' : 'px-3 py-2'}`}
value={currentGuildId}
onChange={(e) => setCurrentGuildId(e.target.value)}
title={selectedGuild?.name}
<div className="px-3 pb-2">
<Select
aria-label="Guild auswaehlen"
selectedKey={currentGuildId}
onSelectionChange={(key) => {
if (typeof key === 'string') setCurrentGuildId(key);
}}
>
{guilds.map((g) => (
<option key={g.id} value={g.id}>{collapsed ? g.name.slice(0, 2) : g.name}</option>
))}
</select>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectPopover>
<ListBox>
{guilds.map((g) => (
<ListBoxItem key={g.id} id={g.id} textValue={g.name}>
{collapsed ? g.name.slice(0, 2) : g.name}
</ListBoxItem>
))}
</ListBox>
</SelectPopover>
</Select>
</div>
<div className="h-px bg-default-200 mx-3" />
<ScrollShadow className="flex-1 px-2 py-2" hideScrollBar>
<nav className="flex flex-col gap-4">
{navGroups.map((group) => (
@@ -116,9 +114,10 @@ export function Sidebar() {
return (
<Tooltip key={item.key} content={collapsed ? item.label : ''} placement="right" offset={8}>
<Button
className={`h-9 justify-start gap-3 px-2 ${isActive ? 'bg-primary-500/15 text-primary-400 font-semibold' : 'text-default-500 hover:text-foreground hover:bg-default-100/40'}`}
className="h-9 justify-start gap-3 px-2"
color={isActive ? 'primary' : 'default'}
radius="lg"
variant="light"
variant={isActive ? 'flat' : 'light'}
size="sm"
startContent={item.icon}
onPress={() => setSection(item.key)}
@@ -140,9 +139,10 @@ export function Sidebar() {
)}
<Tooltip content={collapsed ? 'Admin' : ''} placement="right" offset={8}>
<Button
className={`h-9 justify-start gap-3 px-2 ${section === 'admin' ? 'bg-warning-500/15 text-warning-400 font-semibold' : 'text-default-500 hover:text-foreground hover:bg-default-100/40'}`}
className="h-9 justify-start gap-3 px-2"
color={section === 'admin' ? 'warning' : 'default'}
radius="lg"
variant="light"
variant={section === 'admin' ? 'flat' : 'light'}
size="sm"
startContent={<Wrench size={18} />}
onPress={() => setSection('admin')}
@@ -155,8 +155,6 @@ export function Sidebar() {
</nav>
</ScrollShadow>
<div className="h-px bg-default-200 mx-3" />
<div className="p-3 flex flex-col gap-2">
<Button
isIconOnly
@@ -169,25 +167,24 @@ export function Sidebar() {
{collapsed ? <PanelLeft size={16} /> : <PanelLeftClose size={16} />}
</Button>
<div className={`flex items-center gap-3 rounded-xl border border-default-100 bg-default-50/20 p-2 ${collapsed ? 'justify-center' : ''}`}>
<Avatar name={user?.username} size="sm" className="shrink-0" />
{!collapsed && (
<>
<div className="flex-1 min-w-0">
<div className="truncate text-xs font-semibold">{user?.username}</div>
<div className="flex items-center gap-1 text-[10px] text-success-400">
<div className="size-1.5 rounded-full bg-success-400" />
Online
<Card>
<CardContent className={`flex items-center gap-3 p-2 ${collapsed ? 'justify-center' : ''}`}>
<Avatar name={user?.username} size="sm" className="shrink-0" />
{!collapsed && (
<>
<div className="flex-1 min-w-0">
<div className="truncate text-xs font-semibold">{user?.username}</div>
<div className="text-[10px] text-default-400">Angemeldet</div>
</div>
</div>
<Tooltip content="Abmelden" placement="top">
<Button isIconOnly color="danger" radius="lg" size="sm" variant="light" onPress={handleLogout}>
<LogOut size={14} />
</Button>
</Tooltip>
</>
)}
</div>
<Tooltip content="Abmelden" placement="top">
<Button isIconOnly color="danger" radius="lg" size="sm" variant="light" onPress={handleLogout}>
<LogOut size={14} />
</Button>
</Tooltip>
</>
)}
</CardContent>
</Card>
</div>
</aside>
);

View File

@@ -1,4 +1,4 @@
import { Card, CardContent } from '@heroui/react';
import { Card, CardContent, Chip } from '@heroui/react';
import type { ReactNode } from 'react';
type Props = {
@@ -9,14 +9,13 @@ type Props = {
export function ActivityTile({ icon, label, value }: Props) {
return (
<Card className="border border-default-100 bg-default-50/20">
<Card>
<CardContent className="flex flex-row items-center justify-between gap-4 p-4">
<div className="flex items-center gap-3">
<div className="flex size-10 items-center justify-center rounded-xl bg-primary-500/10 text-primary-400">
{icon}
</div>
<Chip color="primary" size="sm" variant="flat" startContent={icon}>
{label}
</Chip>
<div>
<div className="text-tiny uppercase tracking-widest text-default-500">{label}</div>
<div className="text-2xl font-black">{value}</div>
</div>
</div>

View File

@@ -1,4 +1,4 @@
import { Card, CardHeader, CardContent } from '@heroui/react';
import { Card, CardHeader, CardContent, CardTitle } from '@heroui/react';
import type { ReactNode } from 'react';
type Props = {
@@ -8,11 +8,11 @@ type Props = {
export function FormPanel({ title, children }: Props) {
return (
<Card className="border border-default-100 bg-default-50/20">
<CardHeader className="px-5 pt-5 pb-0">
<h3 className="text-base font-semibold">{title}</h3>
<Card>
<CardHeader>
<CardTitle>{title}</CardTitle>
</CardHeader>
<CardContent className="flex flex-col gap-4 px-5 py-5">{children}</CardContent>
<CardContent className="flex flex-col gap-4">{children}</CardContent>
</Card>
);
}

View File

@@ -1,4 +1,4 @@
import { Card, CardHeader, CardContent } from '@heroui/react';
import { Card, CardHeader, CardContent, CardTitle } from '@heroui/react';
import type { ReactNode } from 'react';
type Props = {
@@ -9,11 +9,11 @@ type Props = {
export function ListPanel({ title, children, className }: Props) {
return (
<Card className={`border border-default-100 bg-default-50/20 ${className ?? ''}`}>
<CardHeader className="px-5 pt-5 pb-0">
<h3 className="text-base font-semibold">{title}</h3>
<Card className={className ?? ''}>
<CardHeader>
<CardTitle>{title}</CardTitle>
</CardHeader>
<CardContent className="px-5 py-5">
<CardContent>
{children}
</CardContent>
</Card>

View File

@@ -10,15 +10,15 @@ type Props = {
export function SectionCard({ title, subtitle, children, action }: Props) {
return (
<Card className="border border-default-100 bg-gradient-to-b from-default-50/40 to-background">
<CardHeader className="flex items-start justify-between gap-4 px-6 pt-6 pb-0">
<Card>
<CardHeader className="flex items-start justify-between gap-4">
<div className="min-w-0 flex flex-col gap-1">
<CardTitle>{title}</CardTitle>
{subtitle && <CardDescription>{subtitle}</CardDescription>}
</div>
{action && <div className="shrink-0">{action}</div>}
</CardHeader>
<CardContent className="px-6 py-5">{children}</CardContent>
<CardContent>{children}</CardContent>
</Card>
);
}

View File

@@ -1,4 +1,4 @@
import { Card, CardContent } from '@heroui/react';
import { Card, CardContent, Chip } from '@heroui/react';
import type { ReactNode } from 'react';
type Props = {
@@ -10,13 +10,15 @@ type Props = {
};
export function StatCard({ icon, label, value, trend, color = 'primary' }: Props) {
const chipColor = color === 'default' ? 'default' : color;
return (
<Card className="border border-default-100 bg-gradient-to-br from-default-50/20 to-background hover:border-default-200 transition-all">
<Card>
<CardContent className="flex flex-col gap-2 p-4">
<div className="flex items-center justify-between">
<div className={`flex size-9 items-center justify-center rounded-xl bg-${color}-500/10 text-${color}-400`}>
{icon}
</div>
<Chip color={chipColor} size="sm" variant="flat" startContent={icon}>
{label}
</Chip>
{trend && (
<span className={`text-tiny font-medium ${trend.startsWith('+') ? 'text-success-400' : trend.startsWith('-') ? 'text-danger-400' : 'text-default-400'}`}>
{trend}
@@ -24,7 +26,7 @@ export function StatCard({ icon, label, value, trend, color = 'primary' }: Props
)}
</div>
<div className="text-2xl font-bold tracking-tight">{value}</div>
<div className="text-tiny uppercase tracking-widest text-default-500">{label}</div>
<div className="text-tiny text-default-500">{label}</div>
</CardContent>
</Card>
);

View File

@@ -1,15 +1,15 @@
import { Card, CardContent, CardHeader, Avatar, Chip, Button, Separator, ScrollShadow } from '@heroui/react';
import { Card, CardContent, CardHeader, Avatar, Chip, Button, ScrollShadow } from '@heroui/react';
import {
Bot, UserRound, CalendarDays, Users, Cable, Ticket, Shield, MessageSquare,
Command, ChevronRight, Activity, Clock, ArrowUpRight, RefreshCw, Send,
Settings, Trash2, Sparkles, Home, Server, Hash, Gauge, Zap, Bell, Tag
Bot, CalendarDays, Users, Ticket, Shield, MessageSquare,
ChevronRight, Activity, Clock, ArrowUpRight, RefreshCw, Send,
Settings, Sparkles, Hash, Gauge, Zap, Bell, Tag, Command
} from 'lucide-react';
import { useApp } from '../context/AppContext';
import { formatDate, guildIconUrl } from '../utils/formatters';
import { StatCard } from '../components/shared/StatCard';
export function Dashboard() {
const { guildInfo, guilds, currentGuildId, overview, activity, logs, setSection, section } = useApp();
const { guildInfo, guilds, currentGuildId, overview, activity, logs, setSection } = useApp();
const selectedGuild = guilds.find((g) => g.id === currentGuildId);
const moduleFlags = guildInfo?.modules || {};
@@ -17,13 +17,13 @@ export function Dashboard() {
{ key: 'tickets', label: 'Ticket Panel senden', icon: <Send size={16} />, color: 'primary' as const },
{ key: 'serverstats', label: 'Sync starten', icon: <RefreshCw size={16} />, color: 'success' as const },
{ key: 'modules', label: 'Module aktualisieren', icon: <Zap size={16} />, color: 'warning' as const },
{ key: 'settings', label: 'Einstellungen öffnen', icon: <Settings size={16} />, color: 'default' as const },
{ key: 'settings', label: 'Einstellungen oeffnen', icon: <Settings size={16} />, color: 'default' as const },
];
return (
<div className="space-y-6">
<Card className="border border-default-100 bg-gradient-to-r from-primary-500/5 via-default-50/40 to-background overflow-hidden">
<CardContent className="flex flex-col gap-6 p-6 xl:flex-row xl:items-center xl:justify-between">
<Card>
<CardContent className="flex flex-col gap-6 xl:flex-row xl:items-center xl:justify-between">
<div className="flex min-w-0 items-center gap-5">
<Avatar className="size-20 shrink-0" radius="lg" src={guildIconUrl(selectedGuild)} />
<div className="min-w-0">
@@ -65,49 +65,23 @@ export function Dashboard() {
</div>
<div className="grid gap-5 xl:grid-cols-2 2xl:grid-cols-3">
<Card className="border border-default-100 bg-gradient-to-b from-default-50/40 to-background">
<CardHeader className="flex items-center justify-between px-5 pt-5 pb-0">
<Card>
<CardHeader>
<div>
<h2 className="text-lg font-bold">Activity Bereich</h2>
<p className="mt-0.5 text-tiny text-default-400">Live Statistiken</p>
</div>
</CardHeader>
<CardContent className="flex flex-col gap-3 p-5">
<div className="grid grid-cols-2 gap-3">
<div className="rounded-xl border border-default-100 bg-default-50/20 p-4">
<div className="flex items-center gap-2 text-tiny uppercase tracking-widest text-default-500">
<MessageSquare size={14} className="text-primary-400" /> Nachrichten
</div>
<div className="mt-1 text-3xl font-black">{activity?.messages24h ?? 0}</div>
<div className="text-tiny text-default-400">in den letzten 24h</div>
</div>
<div className="rounded-xl border border-default-100 bg-default-50/20 p-4">
<div className="flex items-center gap-2 text-tiny uppercase tracking-widest text-default-500">
<Command size={14} className="text-success-400" /> Commands
</div>
<div className="mt-1 text-3xl font-black">{activity?.commands24h ?? 0}</div>
<div className="text-tiny text-default-400">in den letzten 24h</div>
</div>
<div className="rounded-xl border border-default-100 bg-default-50/20 p-4">
<div className="flex items-center gap-2 text-tiny uppercase tracking-widest text-default-500">
<Shield size={14} className="text-warning-400" /> Automod
</div>
<div className="mt-1 text-3xl font-black">{activity?.automod24h ?? 0}</div>
<div className="text-tiny text-default-400">Aktionen (24h)</div>
</div>
<div className="rounded-xl border border-default-100 bg-default-50/20 p-4">
<div className="flex items-center gap-2 text-tiny uppercase tracking-widest text-default-500">
<Users size={14} className="text-secondary-400" /> Neue User
</div>
<div className="mt-1 text-3xl font-black">{activity?.newUsers24h ?? 0}</div>
<div className="text-tiny text-default-400">Beitritte (24h)</div>
</div>
</div>
<CardContent className="grid grid-cols-1 gap-3 sm:grid-cols-2">
<StatCard icon={<MessageSquare size={16} />} label="Nachrichten" value={activity?.messages24h ?? 0} />
<StatCard icon={<Command size={16} />} label="Commands" value={activity?.commands24h ?? 0} color="success" />
<StatCard icon={<Shield size={16} />} label="Automod" value={activity?.automod24h ?? 0} color="warning" />
<StatCard icon={<Users size={16} />} label="Neue User" value={activity?.newUsers24h ?? 0} />
</CardContent>
</Card>
<Card className="border border-default-100 bg-gradient-to-b from-default-50/40 to-background">
<CardHeader className="flex items-center justify-between px-5 pt-5 pb-0">
<Card>
<CardHeader className="flex items-center justify-between">
<div>
<h2 className="text-lg font-bold">Guild Logs</h2>
<p className="mt-0.5 text-tiny text-default-400">Letzte Ereignisse</p>
@@ -116,36 +90,37 @@ export function Dashboard() {
Alle
</Button>
</CardHeader>
<CardContent className="p-5">
<CardContent>
<ScrollShadow className="max-h-[320px] space-y-2 pr-1" hideScrollBar>
{logs.length ? logs.slice(0, 15).map((log, i) => (
<div key={`${log.timestamp}-${i}`} className="flex items-start gap-3 rounded-xl border border-default-100 bg-default-50/20 p-3">
<div className={`mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-lg ${
log.level === 'error' ? 'bg-danger-500/10 text-danger-400' :
log.level === 'warn' ? 'bg-warning-500/10 text-warning-400' :
'bg-primary-500/10 text-primary-400'
}`}>
{log.level === 'error' ? <Shield size={12} /> :
log.level === 'warn' ? <Bell size={12} /> :
<Activity size={12} />}
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<Chip
color={log.level === 'error' ? 'danger' : log.level === 'warn' ? 'warning' : 'default'}
size="sm"
variant="flat"
className="h-5"
>
{(log.level || 'info').toUpperCase()}
</Chip>
<span className="text-tiny text-default-400">{formatDate(log.timestamp)}</span>
<Card key={`${log.timestamp}-${i}`}>
<CardContent className="flex items-start gap-3 p-3">
<div className={`mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-lg ${
log.level === 'error' ? 'bg-danger-500/10 text-danger-400' :
log.level === 'warn' ? 'bg-warning-500/10 text-warning-400' :
'bg-primary-500/10 text-primary-400'
}`}>
{log.level === 'error' ? <Shield size={12} /> :
log.level === 'warn' ? <Bell size={12} /> :
<Activity size={12} />}
</div>
<p className="mt-1 text-small text-foreground/80">
{log.category ? <span className="text-default-500">[{log.category}] </span> : ''}{log.message || '-'}
</p>
</div>
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<Chip
color={log.level === 'error' ? 'danger' : log.level === 'warn' ? 'warning' : 'default'}
size="sm"
variant="flat"
>
{(log.level || 'info').toUpperCase()}
</Chip>
<span className="text-tiny text-default-400">{formatDate(log.timestamp)}</span>
</div>
<p className="mt-1 text-small text-foreground/80">
{log.category ? <span className="text-default-500">[{log.category}] </span> : ''}{log.message || '-'}
</p>
</div>
</CardContent>
</Card>
)) : (
<div className="flex flex-col items-center gap-2 py-6 text-center text-small text-default-400">
<Activity size={20} />
@@ -156,12 +131,12 @@ export function Dashboard() {
</CardContent>
</Card>
<Card className="border border-default-100 bg-gradient-to-b from-default-50/40 to-background">
<CardHeader className="px-5 pt-5 pb-0">
<Card>
<CardHeader>
<h2 className="text-lg font-bold">Quick Actions</h2>
<p className="mt-0.5 text-tiny text-default-400">Schnellzugriff</p>
</CardHeader>
<CardContent className="flex flex-col gap-2 p-5">
<CardContent className="flex flex-col gap-2">
{quickActions.map((action) => (
<Button
key={action.key}
@@ -182,11 +157,11 @@ export function Dashboard() {
{(['tickets', 'supportlogin', 'automod', 'welcome', 'birthday', 'reactionroles'] as const).map((key) => {
const item = navItemMap[key];
return (
<Card key={key} isPressable className="border border-default-100 bg-default-50/20 transition-all hover:border-primary-300 hover:shadow-md" onPress={() => setSection(key)}>
<CardContent className="flex flex-col items-start gap-2 p-4">
<div className="flex size-10 items-center justify-center rounded-xl bg-primary-500/10 text-primary-400">
{item.icon}
</div>
<Card key={key} isPressable onPress={() => setSection(key)}>
<CardContent className="flex flex-col items-start gap-2">
<Chip color="primary" size="sm" variant="flat" startContent={item.icon}>
{item.label}
</Chip>
<div className="font-semibold text-sm">{item.label}</div>
<div className="text-tiny text-default-400">Modul verwalten</div>
</CardContent>

View File

@@ -1,5 +1,5 @@
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Switch, Separator, TextField, Label } from '@heroui/react';
import { LogIn, UserRound, Eye, Save, Send, RefreshCw } from 'lucide-react';
import { Avatar, Card, CardContent, CardDescription, CardHeader, CardTitle, Input, TextArea, Button, Chip, Switch, Separator, TextField, Label } from '@heroui/react';
import { LogIn, UserRound, Save, Send } from 'lucide-react';
import { useApp } from '../context/AppContext';
import { SectionCard } from '../components/shared/SectionCard';
@@ -7,13 +7,16 @@ export function SupportLogin() {
const { supportLogin, setSupportLogin, saveSupportLogin } = useApp();
return (
<SectionCard title="Support Login" subtitle="Login-Panel f<EFBFBD>r Supporter konfigurieren">
<SectionCard title="Support Login" subtitle="Login-Panel fuer Supporter konfigurieren">
<div className="grid gap-5 xl:grid-cols-[1fr_400px]">
<Card className="border border-default-100 bg-default-50/20">
<CardHeader className="px-5 pt-5 pb-0">
<h3 className="text-base font-semibold">Panel-Konfiguration</h3>
<Card>
<CardHeader>
<div>
<CardTitle>Panel-Konfiguration</CardTitle>
<CardDescription>Texte und Zielkanal mit normalen HeroUI-Feldern pflegen.</CardDescription>
</div>
</CardHeader>
<CardContent className="flex flex-col gap-4 p-5">
<CardContent className="flex flex-col gap-4">
<Switch
isSelected={supportLogin?.config?.autoRefresh !== false}
onChange={(v) => setSupportLogin((s) => s ? { ...s, config: { ...s.config, autoRefresh: v } } : s)}
@@ -76,41 +79,48 @@ export function SupportLogin() {
</Card>
<div className="space-y-4">
<Card className="border border-default-100 bg-default-50/20">
<CardHeader className="px-5 pt-5 pb-0">
<h3 className="text-base font-semibold">Live Vorschau</h3>
<Card>
<CardHeader>
<div>
<CardTitle>Live Vorschau</CardTitle>
<CardDescription>Panel in einer normalen HeroUI-Karte.</CardDescription>
</div>
</CardHeader>
<CardContent className="p-5">
<div className="rounded-xl border border-default-100 bg-gradient-to-b from-default-50/40 to-background p-4">
<div className="flex items-center gap-3 mb-3">
<div className="flex size-10 items-center justify-center rounded-xl bg-primary-500/10 text-primary-400">
<LogIn size={20} />
<CardContent>
<Card>
<CardHeader>
<div className="flex items-center gap-2">
<LogIn size={16} />
<div>
<CardTitle>{supportLogin?.config?.title || 'Support Login'}</CardTitle>
<CardDescription>{supportLogin?.config?.description || 'Melde dich als Support an/ab.'}</CardDescription>
</div>
</div>
<div>
<div className="font-semibold">{supportLogin?.config?.title || 'Support Login'}</div>
<div className="text-tiny text-default-400">{supportLogin?.config?.description || 'Melde dich als Support an/ab.'}</div>
</div>
</div>
<div className="flex gap-2">
</CardHeader>
<CardContent className="flex gap-2">
<Button size="sm" color="primary" variant="flat">{supportLogin?.config?.loginLabel || 'Login'}</Button>
<Button size="sm" variant="flat">{supportLogin?.config?.logoutLabel || 'Logout'}</Button>
</div>
</div>
</CardContent>
</Card>
</CardContent>
</Card>
<Card className="border border-default-100 bg-default-50/20">
<CardHeader className="px-5 pt-5 pb-0">
<h3 className="text-base font-semibold">Aktive Supporter</h3>
<Card>
<CardHeader>
<div>
<CardTitle>Aktive Supporter</CardTitle>
<CardDescription>Aktueller Status aus der Guild.</CardDescription>
</div>
</CardHeader>
<CardContent className="flex flex-col gap-2 p-5">
<CardContent className="flex flex-col gap-2">
{supportLogin?.status?.active?.length ? supportLogin.status.active.map((s, i) => (
<div key={i} className="flex items-center gap-3 rounded-xl border border-default-100 bg-default-50/30 px-4 py-3 text-small">
<div className="size-2 rounded-full bg-success-400" />
<Avatar name={s.username?.[0]} size="sm" className="size-6" />
<span className="font-medium">{s.username || s.userId}</span>
<Chip size="sm" variant="flat" color="success" className="ml-auto">Online</Chip>
</div>
<Card key={i}>
<CardContent className="flex items-center gap-3">
<Avatar name={s.username?.[0]} size="sm" className="size-6" />
<span className="font-medium">{s.username || s.userId}</span>
<Chip size="sm" variant="flat" color="success" className="ml-auto">Online</Chip>
</CardContent>
</Card>
)) : (
<div className="flex flex-col items-center gap-2 py-4 text-center text-tiny text-default-400">
<UserRound size={20} />

View File

@@ -1,5 +1,5 @@
import { Card, CardContent, CardHeader, Input, TextArea, Button, Chip, Switch, Separator, TextField, Label } from '@heroui/react';
import { Sparkles, Save, Eye } from 'lucide-react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle, Input, TextArea, Button, Switch, Separator, TextField, Label } from '@heroui/react';
import { Sparkles, Save } from 'lucide-react';
import { useApp } from '../context/AppContext';
import { SectionCard } from '../components/shared/SectionCard';
@@ -9,11 +9,14 @@ export function Welcome() {
return (
<SectionCard title="Willkommen" subtitle="Welcome-Embeds und Join-Nachrichten">
<div className="grid gap-5 xl:grid-cols-2">
<Card className="border border-default-100 bg-default-50/20">
<CardHeader className="px-5 pt-5 pb-0">
<h3 className="text-base font-semibold">Welcome konfigurieren</h3>
<Card>
<CardHeader>
<div>
<CardTitle>Welcome konfigurieren</CardTitle>
<CardDescription>Standardfelder fuer das Welcome-Embed.</CardDescription>
</div>
</CardHeader>
<CardContent className="flex flex-col gap-4 p-5">
<CardContent className="flex flex-col gap-4">
<Switch isSelected={settings.welcomeConfig?.enabled !== false} onChange={(v) => setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), enabled: v } }))}>
<div className="flex items-center gap-2"><Sparkles size={16} /> Welcome aktiv</div>
</Switch>
@@ -21,7 +24,7 @@ export function Welcome() {
<TextField>
<Label>Channel ID</Label>
<Input
placeholder="Channel ID f<EFBFBD>r Willkommensnachrichten"
placeholder="Channel ID fuer Willkommensnachrichten"
value={settings.welcomeConfig?.channelId || settings.welcomeChannelId || ''}
onChange={(e) => setSettings((s) => ({ ...s, welcomeConfig: { ...(s.welcomeConfig || {}), channelId: e.target.value } }))}
/>
@@ -62,32 +65,34 @@ export function Welcome() {
</CardContent>
</Card>
<div className="flex flex-col gap-4">
<Card className="border border-default-100 bg-default-50/20">
<CardHeader className="px-5 pt-5 pb-0">
<h3 className="text-base font-semibold">Live Vorschau</h3>
</CardHeader>
<CardContent className="p-5">
<div className="rounded-xl border border-default-100 bg-gradient-to-b from-default-50/40 to-background p-4">
<div className="flex items-start gap-3">
<div className="flex size-10 items-center justify-center rounded-xl bg-primary-500/10 text-primary-400">
<Sparkles size={20} />
</div>
<div className="min-w-0 flex-1">
<div className="font-semibold text-lg">{settings.welcomeConfig?.embedTitle || 'Willkommen!'}</div>
<p className="mt-1 text-small text-default-500">{settings.welcomeConfig?.embedDescription || 'Willkommen auf dem Server!'}</p>
{settings.welcomeConfig?.embedFooter && (
<p className="mt-2 text-tiny text-default-400">{settings.welcomeConfig.embedFooter}</p>
)}
</div>
<Card>
<CardHeader>
<div>
<CardTitle>Live Vorschau</CardTitle>
<CardDescription>Normale HeroUI-Karten ohne zusaetzliche Huelle.</CardDescription>
</div>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<Card>
<CardHeader>
<div className="flex items-center gap-2">
<Sparkles size={16} />
<CardTitle>{settings.welcomeConfig?.embedTitle || 'Willkommen!'}</CardTitle>
</div>
</div>
<p className="mt-3 text-tiny text-default-400">
Nutze {'{user}'} f<EFBFBD>r den Benutzernamen und {'{server}'} f<EFBFBD>r den Servernamen.
</p>
</CardContent>
</Card>
</div>
</CardHeader>
<CardContent>
<p>{settings.welcomeConfig?.embedDescription || 'Willkommen auf dem Server!'}</p>
{settings.welcomeConfig?.embedFooter && (
<p className="text-small text-default-500">{settings.welcomeConfig.embedFooter}</p>
)}
</CardContent>
</Card>
<p className="text-small text-default-500">
Nutze {'{user}'} fuer den Benutzernamen und {'{server}'} fuer den Servernamen.
</p>
</CardContent>
</Card>
</div>
</SectionCard>
);