make admin page mobile friendly
All checks were successful
deploy / build-and-deploy (push) Successful in 23s

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Gemini 3 <google-gemini-noreply@google.com>
This commit is contained in:
2026-04-20 22:05:31 +02:00
parent 31ad92a6e2
commit fdc9b24800
20 changed files with 199 additions and 153 deletions

View File

@@ -28,8 +28,8 @@
data={admins} data={admins}
count={true} count={true}
keys={[ keys={[
{ key: 'username', label: 'Username', width: 30 }, { key: 'username', label: 'Username' },
{ key: 'permissions', label: 'Berechtigungen', width: 60, transform: permissionsBadge } { key: 'permissions', label: 'Berechtigungen', transform: permissionsBadge }
]} ]}
onEdit={(admin) => (editPopupAdmin = admin)} onEdit={(admin) => (editPopupAdmin = admin)}
onDelete={onAdminDelete} onDelete={onAdminDelete}

View File

@@ -32,8 +32,8 @@
data={blockedUsers} data={blockedUsers}
count={true} count={true}
keys={[ keys={[
{ key: 'uuid', label: 'UUID', width: 20, sortable: true }, { key: 'uuid', label: 'UUID', sortable: true },
{ key: 'comment', label: 'Kommentar', width: 70 } { key: 'comment', label: 'Kommentar' }
]} ]}
onEdit={(blockedUser) => (blockedUserEditPopupBlockedUser = blockedUser)} onEdit={(blockedUser) => (blockedUserEditPopupBlockedUser = blockedUser)}
onDelete={onBlockedUserDelete} onDelete={onBlockedUserDelete}

View File

@@ -21,8 +21,8 @@
data={directInvitations} data={directInvitations}
count={true} count={true}
keys={[ keys={[
{ key: 'url', label: 'Link', width: 50 }, { key: 'url', label: 'Link' },
{ key: 'user.username', label: 'Registrierter Nutzer', width: 40, sortable: true } { key: 'user.username', label: 'Registrierter Nutzer', sortable: true }
]} ]}
onDelete={onDirectInvitationDelete} onDelete={onDirectInvitationDelete}
/> />

View File

@@ -12,15 +12,18 @@
let { feedback }: Props = $props(); let { feedback }: Props = $props();
</script> </script>
<div class="relative bg-base-200 rounded-lg w-[calc(100%-1rem)] m-2 flex px-6 py-4 gap-2" hidden={feedback === null}> <div
class="relative bg-base-200 rounded-lg w-[calc(100%-1rem)] m-2 flex flex-col lg:flex-row px-4 lg:px-6 py-4 gap-4"
hidden={feedback === null}
>
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick={() => (feedback = null)}>✕</button> <button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick={() => (feedback = null)}>✕</button>
<div class="w-96"> <div class="w-full lg:w-96">
<Input value={feedback?.event} label="Event" readonly /> <Input value={feedback?.event} label="Event" readonly />
<Input value={feedback?.title} label="Titel" readonly /> <Input value={feedback?.title} label="Titel" readonly />
<Input value={feedback?.username} label="Nutzer" readonly /> <Input value={feedback?.username} label="Nutzer" readonly />
</div> </div>
<div class="divider divider-horizontal"></div> <div class="divider lg:divider-horizontal"></div>
<div class="w-full"> <div class="w-full">
<Textarea value={feedback?.content} label="Inhalt" rows={9} readonly dynamicWidth /> <Textarea value={feedback?.content} label="Inhalt" rows={6} readonly dynamicWidth />
</div> </div>
</div> </div>

View File

@@ -20,15 +20,15 @@
{dateFormat.format(new Date(value))} {dateFormat.format(new Date(value))}
{/snippet} {/snippet}
<div class="h-screen flex flex-col justify-between"> <div class="h-full flex flex-col justify-between">
<DataTable <DataTable
data={feedbacks} data={feedbacks}
count={true} count={true}
keys={[ keys={[
{ key: 'event', label: 'Event', width: 10, sortable: true }, { key: 'event', label: 'Event', sortable: true },
{ key: 'username', label: 'Nutzer', width: 10, sortable: true }, { key: 'username', label: 'Nutzer', sortable: true },
{ key: 'lastChanged', label: 'Datum', width: 10, sortable: true, transform: date }, { key: 'lastChanged', label: 'Datum', sortable: true, transform: date },
{ key: 'content', label: 'Inhalt', width: 10 } { key: 'content', label: 'Inhalt' }
]} ]}
onClick={(feedback) => (activeFeedback = feedback)} onClick={(feedback) => (activeFeedback = feedback)}
/> />

View File

@@ -89,7 +89,10 @@
} }
</script> </script>
<div class="relative bg-base-200 rounded-lg w-[calc(100%-1rem)] m-2 flex px-6 py-4 gap-2" hidden={report === null}> <div
class="relative bg-base-200 rounded-lg w-[calc(100%-1rem)] m-2 flex flex-col lg:flex-row px-4 lg:px-6 py-4 gap-4"
hidden={report === null}
>
<div class="absolute right-2 top-2"> <div class="absolute right-2 top-2">
<div class="dropdown dropdown-end"> <div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn btn-sm btn-circle btn-ghost"> <div tabindex="0" role="button" class="btn btn-sm btn-circle btn-ghost">
@@ -102,35 +105,38 @@
</div> </div>
<button class="btn btn-sm btn-circle btn-ghost" onclick={() => (report = null)}>✕</button> <button class="btn btn-sm btn-circle btn-ghost" onclick={() => (report = null)}>✕</button>
</div> </div>
<div class="w-[34rem]"> <div class="w-full lg:w-[34rem]">
<UserSearch value={report?.reporter.username} label="Report Ersteller" readonly mustMatch /> <UserSearch value={report?.reporter.username} label="Report Ersteller" readonly mustMatch />
<UserSearch <UserSearch
value={report?.reported?.username} value={report?.reported?.username}
label="Reporteter Spieler" label="Reporteter Spieler"
onSubmit={(user) => (reportedUser = user)} onSubmit={(user) => (reportedUser = user)}
/> />
<Textarea bind:value={notice} label="Interne Notizen" rows={10} /> <Textarea bind:value={notice} label="Interne Notizen" rows={6} />
</div> </div>
<div class="w-full"> <div class="w-full">
<Input value={report?.reason} label="Grund" readonly dynamicWidth /> <Input value={report?.reason} label="Grund" readonly dynamicWidth />
<Textarea value={report?.body} label="Inhalt" readonly dynamicWidth rows={9} /> <Textarea value={report?.body} label="Inhalt" readonly dynamicWidth rows={6} />
<fieldset class="fieldset"> <fieldset class="fieldset">
<legend class="fieldset-legend">Anhänge</legend> <legend class="fieldset-legend">Anhänge</legend>
<div class="h-16.5 rounded border border-dashed flex"> <div class="min-h-16.5 rounded border border-dashed border-base-content/20 flex flex-wrap p-1">
{#each reportAttachments as reportAttachment (reportAttachment.hash)} {#each reportAttachments as reportAttachment (reportAttachment.hash)}
<div> <div>
<!-- svelte-ignore a11y_no_static_element_interactions --> <!-- svelte-ignore a11y_no_static_element_interactions -->
<!-- svelte-ignore a11y_click_events_have_key_events --> <!-- svelte-ignore a11y_click_events_have_key_events -->
<div class="cursor-zoom-in" onclick={() => (previewReportAttachment = reportAttachment)}> <div class="cursor-zoom-in p-1" onclick={() => (previewReportAttachment = reportAttachment)}>
{#if reportAttachment.type === 'image'} {#if reportAttachment.type === 'image'}
<img <img
src={location.pathname + '/attachment/' + reportAttachment.hash} src={location.pathname + '/attachment/' + reportAttachment.hash}
alt={reportAttachment.hash} alt={reportAttachment.hash}
class="w-16 h-16" class="w-16 h-16 object-cover rounded"
/> />
{:else if reportAttachment.type === 'video'} {:else if reportAttachment.type === 'video'}
<!-- svelte-ignore a11y_media_has_caption --> <!-- svelte-ignore a11y_media_has_caption -->
<video src={location.pathname + '/attachment/' + reportAttachment.hash} class="w-16 h-16"></video> <video
src={location.pathname + '/attachment/' + reportAttachment.hash}
class="w-16 h-16 object-cover rounded"
></video>
{/if} {/if}
</div> </div>
</div> </div>
@@ -138,9 +144,9 @@
</div> </div>
</fieldset> </fieldset>
</div> </div>
<div class="divider divider-horizontal"></div> <div class="divider lg:divider-horizontal"></div>
<div class="flex flex-col w-[42rem]"> <div class="flex flex-col w-full lg:w-[42rem]">
<Textarea bind:value={statement} label="Öffentliche Report Antwort" dynamicWidth rows={7} /> <Textarea bind:value={statement} label="Öffentliche Report Antwort" dynamicWidth rows={6} />
<Select <Select
bind:value={status} bind:value={status}
values={{ open: 'In Bearbeitung', closed: 'Bearbeitet' }} values={{ open: 'In Bearbeitung', closed: 'Bearbeitet' }}
@@ -150,7 +156,7 @@
/> />
<Select bind:value={strikeReason} values={strikeReasonValues} label="Vergehen" dynamicWidth></Select> <Select bind:value={strikeReason} values={strikeReasonValues} label="Vergehen" dynamicWidth></Select>
<div class="divider mt-0 mb-2"></div> <div class="divider mt-0 mb-2"></div>
<button class="btn mt-auto" onclick={onSaveButtonClick}>Speichern</button> <button class="btn btn-primary mt-auto" onclick={onSaveButtonClick}>Speichern</button>
</div> </div>
</div> </div>

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import BottomBar from '@app/admin/reports/BottomBar.svelte'; import BottomBar from '@app/admin/reports/BottomBar.svelte';
import { onMount, untrack } from 'svelte'; import { onMount } from 'svelte';
import DataTable from '@components/admin/table/DataTable.svelte'; import DataTable from '@components/admin/table/DataTable.svelte';
import { type StrikeReasons, getStrikeReasons, reports } from '@app/admin/reports/reports.ts'; import { type StrikeReasons, getStrikeReasons, reports } from '@app/admin/reports/reports.ts';
@@ -49,16 +49,16 @@
{/if} {/if}
{/snippet} {/snippet}
<div class="h-screen flex flex-col justify-between"> <div class="h-full flex flex-col justify-between">
<DataTable <DataTable
data={reports} data={reports}
count={true} count={true}
keys={[ keys={[
{ key: 'reason', label: 'Grund', width: 35 }, { key: 'reason', label: 'Grund' },
{ key: 'reporter.username', label: 'Report Ersteller', width: 15 }, { key: 'reporter.username', label: 'Report Ersteller' },
{ key: 'reported.username', label: 'Reporteter Spieler', width: 15 }, { key: 'reported.username', label: 'Reporteter Spieler' },
{ key: 'createdAt', label: 'Datum', width: 15, sortable: true, transform: date }, { key: 'createdAt', label: 'Datum', sortable: true, transform: date },
{ key: 'status.status', label: 'Bearbeitungsstatus', width: 10, sortable: true, transform: status } { key: 'status.status', label: 'Bearbeitungsstatus', sortable: true, transform: status }
]} ]}
onClick={(report) => (activeReport = report)} onClick={(report) => (activeReport = report)}
/> />

View File

@@ -99,16 +99,16 @@
} }
</script> </script>
<div class="h-full flex flex-col items-center justify-between"> <div class="min-h-full flex flex-col items-center justify-between">
<div class="grid grid-cols-2 w-full"> <div class="grid grid-cols-1 lg:grid-cols-2 w-full px-4 lg:px-12 gap-8">
{#each settingsInput as setting (setting.name)} {#each settingsInput as setting (setting.name)}
<div class="mx-12"> <div class="flex flex-col">
<div class="divider">{setting.name}</div> <div class="divider font-bold">{setting.name}</div>
<div class="flex flex-col gap-5"> <div class="flex flex-col gap-6">
{#each setting.entries as entry (entry.name)} {#each setting.entries as entry (entry.name)}
<label class="flex justify-between"> <label class="flex flex-col sm:flex-row justify-between gap-2">
<span class="mt-[.125rem] text-sm w-1/2">{entry.name}</span> <span class="text-sm font-medium sm:w-1/2">{entry.name}</span>
<div class="w-1/2"> <div class="sm:w-1/2 flex sm:justify-end">
{#if entry.type === 'checkbox'} {#if entry.type === 'checkbox'}
<input <input
type="checkbox" type="checkbox"
@@ -122,7 +122,7 @@
{:else if entry.type === 'text'} {:else if entry.type === 'text'}
<input <input
type="text" type="text"
class="input input-bordered" class="input input-bordered w-full sm:max-w-xs"
onchange={(e) => { onchange={(e) => {
entry.onChange(e.currentTarget.value); entry.onChange(e.currentTarget.value);
changes = dynamicSettings.getChanges(); changes = dynamicSettings.getChanges();
@@ -131,7 +131,7 @@
/> />
{:else if entry.type === 'textarea'} {:else if entry.type === 'textarea'}
<textarea <textarea
class="textarea" class="textarea textarea-bordered w-full sm:max-w-xs min-h-24"
value={entry.value} value={entry.value}
onchange={(e) => { onchange={(e) => {
entry.onChange(e.currentTarget.value); entry.onChange(e.currentTarget.value);
@@ -146,9 +146,9 @@
</div> </div>
{/each} {/each}
</div> </div>
<div> <div class="py-12">
<button <button
class="btn btn-success mt-auto mb-8" class="btn btn-success btn-lg px-12"
class:btn-disabled={Object.keys(changes).length === 0} class:btn-disabled={Object.keys(changes).length === 0}
onclick={onSaveSettingsClick}>Speichern</button onclick={onSaveSettingsClick}>Speichern</button
> >

View File

@@ -32,9 +32,9 @@
data={strikeReasons} data={strikeReasons}
count={true} count={true}
keys={[ keys={[
{ key: 'name', label: 'Name', width: 20 }, { key: 'name', label: 'Name' },
{ key: 'weight', label: 'Gewichtung', width: 50, sortable: true }, { key: 'weight', label: 'Gewichtung', sortable: true },
{ key: 'id', label: 'Id', width: 20 } { key: 'id', label: 'Id' }
]} ]}
onDelete={onBlockedUserDelete} onDelete={onBlockedUserDelete}
onEdit={(strikeReason) => (editPopupStrikeReason = strikeReason)} onEdit={(strikeReason) => (editPopupStrikeReason = strikeReason)}

View File

@@ -17,9 +17,9 @@
<fieldset class="fieldset border border-base-200 rounded-box px-4"> <fieldset class="fieldset border border-base-200 rounded-box px-4">
<legend class="fieldset-legend">Account UUID finder</legend> <legend class="fieldset-legend">Account UUID finder</legend>
<div> <div>
<div class="flex gap-3"> <div class="flex flex-col sm:flex-row gap-3">
<Input bind:value={username} /> <Input bind:value={username} dynamicWidth />
<Select bind:value={edition} values={{ java: 'Java', bedrock: 'Bedrock' }} /> <Select bind:value={edition} values={{ java: 'Java', bedrock: 'Bedrock' }} dynamicWidth />
</div> </div>
<div class="flex justify-center"> <div class="flex justify-center">
<button class="btn w-4/6" class:disabled={!username} onclick={onSubmit}>UUID finden</button> <button class="btn w-4/6" class:disabled={!username} onclick={onSubmit}>UUID finden</button>

View File

@@ -47,13 +47,13 @@
data={users} data={users}
count={true} count={true}
keys={[ keys={[
{ key: 'firstname', label: 'Vorname', width: 15, sortable: true }, { key: 'firstname', label: 'Vorname', sortable: true },
{ key: 'lastname', label: 'Nachname', width: 15, sortable: true }, { key: 'lastname', label: 'Nachname', sortable: true },
{ key: 'birthday', label: 'Geburtstag', width: 5, sortable: true }, { key: 'birthday', label: 'Geburtstag', sortable: true },
{ key: 'telephone', label: 'Telefon', width: 12, sortable: true }, { key: 'telephone', label: 'Telefon', sortable: true },
{ key: 'username', label: 'Username', width: 15, sortable: true }, { key: 'username', label: 'Username', sortable: true },
{ key: 'edition', label: 'Edition', width: 5, sortable: true, transform: edition }, { key: 'edition', label: 'Edition', sortable: true, transform: edition },
{ key: 'uuid', label: 'UUID', width: 23 } { key: 'uuid', label: 'UUID' }
]} ]}
extraActions={blockAction} extraActions={blockAction}
onEdit={(user) => (editPopupUser = user)} onEdit={(user) => (editPopupUser = user)}

View File

@@ -1,4 +1,4 @@
<script lang="ts"> <script lang="ts" generics="T">
import Input from '@components/input/Input.svelte'; import Input from '@components/input/Input.svelte';
import { confirmPopupState } from '@components/popup/ConfirmPopup.ts'; import { confirmPopupState } from '@components/popup/ConfirmPopup.ts';
import Textarea from '@components/input/Textarea.svelte'; import Textarea from '@components/input/Textarea.svelte';
@@ -14,7 +14,7 @@
let modalForm: HTMLFormElement; let modalForm: HTMLFormElement;
// types // types
interface Props<T> { interface Props {
texts: { texts: {
title: string; title: string;
submitButtonTitle: string; submitButtonTitle: string;
@@ -75,7 +75,7 @@
['number']: {}; ['number']: {};
['password']: {}; ['password']: {};
['select']: { ['select']: {
options: Record<string, string>; values: Record<string, string>;
}; };
['tel']: {}; ['tel']: {};
['text']: {}; ['text']: {};
@@ -93,7 +93,7 @@
}; };
// input // input
let { texts, target, keys = [], onSubmit, onClose, open = $bindable() }: Props<any> = $props(); let { texts, target, keys = [], onSubmit, onClose, open = $bindable() }: Props = $props();
onInit(); onInit();
@@ -101,10 +101,12 @@
let submitEnabled = $state(false); let submitEnabled = $state(false);
$effect(() => { $effect(() => {
if (!open) return; if (open) {
onInit();
onInit(); modal?.show();
modal.show(); } else {
modal?.close();
}
}); });
$effect.pre(() => { $effect.pre(() => {
@@ -160,27 +162,30 @@
} }
function onModalClose() { function onModalClose() {
setTimeout(() => { open = false;
open = false; target = null;
target = null; modalForm.reset();
modalForm.reset(); onClose?.();
onClose?.(); }
}, 300);
function portal(node: HTMLElement) {
document.body.appendChild(node);
return {
destroy() {
if (node.parentNode) node.parentNode.removeChild(node);
}
};
} }
</script> </script>
<dialog class="modal" bind:this={modal} onclose={onModalClose}> <dialog class="modal fixed inset-0 z-[9999]" use:portal bind:this={modal} onclose={onModalClose}>
<form method="dialog" class="modal-box overflow-visible" bind:this={modalForm}> <form method="dialog" class="modal-box overflow-visible" bind:this={modalForm}>
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick={onCancelButtonClick}>✕</button> <button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick={onCancelButtonClick}>✕</button>
<div class="space-y-5"> <div class="space-y-5">
<h3 class="text-xl font-geist font-bold">{texts.title}</h3> <h3 class="text-xl font-geist font-bold">{texts.title}</h3>
<div class="w-full flex flex-col"> <div class="w-full flex flex-col">
{#each keys as key (key)} {#each keys as key (key)}
<div <div class="grid grid-cols-1 gap-x-4" class:md:grid-cols-2={key.length === 2}>
class="grid grid-flow-col gap-4"
class:grid-cols-1={key.length === 1}
class:grid-cols-2={key.length === 2}
>
{#each key as k (k)} {#each key as k (k)}
{#if k.type === 'color' || k.type === 'date' || k.type === 'datetime-local' || k.type === 'number' || k.type === 'tel' || k.type === 'text'} {#if k.type === 'color' || k.type === 'date' || k.type === 'datetime-local' || k.type === 'number' || k.type === 'tel' || k.type === 'text'}
<Input <Input

View File

@@ -13,7 +13,6 @@
keys: { keys: {
key: string; key: string;
label: string; label: string;
width?: number;
sortable?: boolean; sortable?: boolean;
transform?: Snippet<[T]>; transform?: Snippet<[T]>;
}[]; }[];
@@ -28,20 +27,20 @@
let { data, count, keys, onClick, onEdit, onDelete, extraActions }: Props<any> = $props(); let { data, count, keys, onClick, onEdit, onDelete, extraActions }: Props<any> = $props();
</script> </script>
<div class="max-h-screen overflow-x-auto"> <div class="max-h-full overflow-x-auto">
<table class="table table-pin-rows"> <table class="table table-pin-rows w-full table-auto lg:w-fit lg:min-w-full mx-auto">
<thead> <thead>
<SortableTr {data}> <SortableTr {data}>
{#if count} {#if count}
<SortableTh style="width: 5%">#</SortableTh> <SortableTh style="width: 1%" class="text-left">#</SortableTh>
{/if} {/if}
{#each keys as key (key.key)} {#each keys as key (key.key)}
<SortableTh style={key.width ? `width: ${key.width}%` : undefined} key={key.sortable ? key.key : undefined} <SortableTh class="text-left whitespace-nowrap" key={key.sortable ? key.key : undefined}>
>{key.label}</SortableTh {key.label}
> </SortableTh>
{/each} {/each}
{#if onEdit || onDelete || extraActions} {#if onEdit || onDelete || extraActions}
<SortableTh style="width: 5%"></SortableTh> <SortableTh style="width: 1%" class="text-left"></SortableTh>
{/if} {/if}
</SortableTr> </SortableTr>
</thead> </thead>
@@ -49,10 +48,10 @@
{#each $data as d, i (d)} {#each $data as d, i (d)}
<tr class="hover:bg-base-200" onclick={() => onClick?.(d)}> <tr class="hover:bg-base-200" onclick={() => onClick?.(d)}>
{#if count} {#if count}
<td>{i + 1}</td> <td class="text-left">{i + 1}</td>
{/if} {/if}
{#each keys as key (key.key)} {#each keys as key (key.key)}
<td> <td class="text-left whitespace-nowrap">
{#if key.transform} {#if key.transform}
{@render key.transform(getObjectEntryByKey(key.key, d))} {@render key.transform(getObjectEntryByKey(key.key, d))}
{:else} {:else}
@@ -61,7 +60,7 @@
</td> </td>
{/each} {/each}
{#if onEdit || onDelete || extraActions} {#if onEdit || onDelete || extraActions}
<td class="px-3 whitespace-nowrap"> <td class="px-3 whitespace-nowrap text-left">
{#if extraActions} {#if extraActions}
{@render extraActions(d)} {@render extraActions(d)}
{/if} {/if}

View File

@@ -31,9 +31,9 @@
} }
</script> </script>
<th {...restProps}> <th {...restProps} class:text-left={true}>
{#if key} {#if key}
<button class="flex items-center gap-1" onclick={() => onButtonClick()}> <button class="flex items-center justify-start gap-1" onclick={() => onButtonClick()}>
<span>{@render children?.()}</span> <span>{@render children?.()}</span>
{#if $headerKey === key && asc} {#if $headerKey === key && asc}
<span class="iconify iconify-[heroicons--chevron-up-16-solid]"></span> <span class="iconify iconify-[heroicons--chevron-up-16-solid]"></span>

View File

@@ -12,7 +12,7 @@
rows?: number; rows?: number;
} }
let { id, value = $bindable(), label, required, readonly, size, dynamicWidth, rows }: Props = $props(); let { id, value = $bindable(), label, required, readonly, size, dynamicWidth, rows = 3 }: Props = $props();
</script> </script>
<fieldset class="fieldset"> <fieldset class="fieldset">
@@ -24,7 +24,7 @@
</legend> </legend>
<textarea <textarea
{id} {id}
class="textarea" class="textarea min-h-16"
class:textarea-sm={size === 'sm'} class:textarea-sm={size === 'sm'}
class:w-full={dynamicWidth} class:w-full={dynamicWidth}
class:validator={required} class:validator={required}

View File

@@ -82,66 +82,101 @@ const adminTabs = [
<BaseLayout title={title}> <BaseLayout title={title}>
<ClientRouter /> <ClientRouter />
<div class="flex flex-row max-h-screen h-screen overflow-hidden"> <div class="drawer lg:drawer-open">
<div class="bg-base-200 w-72 flex flex-col justify-between overflow-scroll"> <input id="admin-drawer" type="checkbox" class="drawer-toggle" />
<ul class="menu"> <div class="drawer-content flex flex-col h-screen overflow-hidden">
{ <!-- Mobile Navbar -->
preTabs.map((tab) => ( <div class="navbar bg-base-200 lg:hidden border-b border-base-300 shrink-0">
<li> <div class="navbar-start">
<a href={tab.href}> <label for="admin-drawer" class="btn btn-square btn-ghost drawer-button">
<span class="iconify" class:list={tab.iconClass} /> <span class="iconify iconify-[heroicons--bars-3]"></span>
<span>{tab.name}</span> </label>
</a> </div>
</li> <div class="navbar-center">
)) <a class="text-xl font-bold">{title}</a>
} </div>
<div class="divider mx-1 my-1"></div> <div class="navbar-end">
{ <!-- Optional: Profile or logout shortcut -->
adminTabs.map( </div>
(tab) => </div>
tab.enabled && (
<!-- Content Area -->
<div class="flex-1 overflow-auto relative p-4 lg:p-6">
<slot />
</div>
</div>
<!-- Sidebar / Drawer Side -->
<div class="drawer-side z-20">
<label for="admin-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
<aside
class="bg-base-200 w-max max-w-[85vw] h-full flex flex-col justify-between border-r border-base-300 overflow-y-auto"
>
<div class="flex flex-col min-w-max">
<div class="p-4 hidden lg:block border-b border-base-300">
<span class="text-xl font-bold">Admin Panel</span>
</div>
<ul class="menu p-2">
{
preTabs.map((tab) => (
<li> <li>
<a href={tab.href}> <a href={tab.href}>
<span class="iconify" class:list={tab.iconClass} /> <span class="iconify" class:list={tab.iconClass} />
<span>{tab.name}</span> <span>{tab.name}</span>
</a> </a>
{tab.subTabs && (
<ul>
{tab.subTabs.map((subTab) => (
<li>
<a href={subTab.href}>
<span class="iconify" class:list={subTab.iconClass} />
<span>{subTab.name}</span>
</a>
</li>
))}
</ul>
)}
</li> </li>
))
}
<div class="divider mx-1 my-1"></div>
{
adminTabs.map(
(tab) =>
tab.enabled && (
<li>
<a href={tab.href}>
<span class="iconify" class:list={tab.iconClass} />
<span>{tab.name}</span>
</a>
{tab.subTabs && (
<ul>
{tab.subTabs.map((subTab) => (
<li>
<a href={subTab.href}>
<span class="iconify" class:list={subTab.iconClass} />
<span>{subTab.name}</span>
</a>
</li>
))}
</ul>
)}
</li>
)
) )
) }
} </ul>
</ul> </div>
<ul class="menu w-full"> <div class="flex flex-col w-0 min-w-full">
{ <ul class="menu w-full p-2">
Astro.slots.has('actions') && ( {
<fieldset class="fieldset bg-base-300 border-base-100 rounded-box border p-2"> Astro.slots.has('actions') && (
<slot name="actions" /> <div class="mb-2">
</fieldset> <span class="menu-title px-2 uppercase text-xs font-semibold opacity-60">Actions</span>
) <fieldset class="fieldset bg-base-300 border-base-100 rounded-box border p-2">
} <slot name="actions" />
<div class="divider mx-1 my-0"></div> </fieldset>
<li> </div>
<button id="logout"> )
<span class="iconify iconify-[heroicons--arrow-left-end-on-rectangle]"></span> }
<span>Ausloggen</span> <div class="divider mx-1 my-0"></div>
</button> <li>
</li> <button id="logout" class="text-error whitespace-nowrap">
</ul> <span class="iconify iconify-[heroicons--arrow-left-end-on-rectangle]"></span>
</div> <span>Ausloggen</span>
</button>
<div class="w-full overflow-scroll relative"> </li>
<slot /> </ul>
</div>
</aside>
</div> </div>
</div> </div>
</BaseLayout> </BaseLayout>

View File

@@ -7,7 +7,7 @@ import Popup from '@components/popup/Popup.svelte';
<AdminLoginLayout title="Login"> <AdminLoginLayout title="Login">
<div class="flex justify-center items-center w-full h-screen"> <div class="flex justify-center items-center w-full h-screen">
<div class="card w-96 px-6 py-6 shadow-lg"> <div class="card w-full max-w-sm px-6 py-6 shadow-lg">
<h1 class="text-3xl text-center">Admin Login</h1> <h1 class="text-3xl text-center">Admin Login</h1>
<div class="divider"></div> <div class="divider"></div>
<form id="login" class="flex flex-col items-center"> <form id="login" class="flex flex-col items-center">

View File

@@ -2,7 +2,6 @@
import WebsiteLayout from '@layouts/website/WebsiteLayout.astro'; import WebsiteLayout from '@layouts/website/WebsiteLayout.astro';
import ConfirmPopup from '@components/popup/ConfirmPopup.svelte'; import ConfirmPopup from '@components/popup/ConfirmPopup.svelte';
import Popup from '@components/popup/Popup.svelte'; import Popup from '@components/popup/Popup.svelte';
import Select from '@components/input/Select.svelte';
import Textarea from '@components/input/Textarea.svelte'; import Textarea from '@components/input/Textarea.svelte';
import Input from '@components/input/Input.svelte'; import Input from '@components/input/Input.svelte';
--- ---

View File

@@ -79,9 +79,9 @@ const information = [
<div> <div>
<h2 class="text-3xl text-black dark:text-white mb-8">Über uns</h2> <h2 class="text-3xl text-black dark:text-white mb-8">Über uns</h2>
<p> <p>
Wir sind ein kleines <a class="link" href="admins">Team</a> von Minecraft-Enthusiasten, das bereits im 8. Jahr Wir sind ein kleines <a class="link" href="admins">Team</a> von Minecraft-Enthusiasten, das bereits im 8. Jahr in
in Folge Minecraft CraftAttack organisiert. Jahr für Jahr arbeiten wir daran, das Spielerlebnis zu verbessern und Folge Minecraft CraftAttack organisiert. Jahr für Jahr arbeiten wir daran, das Spielerlebnis zu verbessern und steigeren
steigeren die Teilnehmerzahl. die Teilnehmerzahl.
</p> </p>
<p> <p>
Unser Ziel bei diesem ab dem <span class="italic" Unser Ziel bei diesem ab dem <span class="italic"

View File

@@ -20,5 +20,4 @@ const signupDisabledSubMessage = signupSetting[SettingKey.SignupDisabledSubMessa
message: signupDisabledMessage, message: signupDisabledMessage,
subMessage: signupDisabledSubMessage subMessage: signupDisabledSubMessage
}} }}
}
/> />