make admin page mobile friendly
All checks were successful
deploy / build-and-deploy (push) Successful in 23s
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:
@@ -28,8 +28,8 @@
|
||||
data={admins}
|
||||
count={true}
|
||||
keys={[
|
||||
{ key: 'username', label: 'Username', width: 30 },
|
||||
{ key: 'permissions', label: 'Berechtigungen', width: 60, transform: permissionsBadge }
|
||||
{ key: 'username', label: 'Username' },
|
||||
{ key: 'permissions', label: 'Berechtigungen', transform: permissionsBadge }
|
||||
]}
|
||||
onEdit={(admin) => (editPopupAdmin = admin)}
|
||||
onDelete={onAdminDelete}
|
||||
|
||||
@@ -32,8 +32,8 @@
|
||||
data={blockedUsers}
|
||||
count={true}
|
||||
keys={[
|
||||
{ key: 'uuid', label: 'UUID', width: 20, sortable: true },
|
||||
{ key: 'comment', label: 'Kommentar', width: 70 }
|
||||
{ key: 'uuid', label: 'UUID', sortable: true },
|
||||
{ key: 'comment', label: 'Kommentar' }
|
||||
]}
|
||||
onEdit={(blockedUser) => (blockedUserEditPopupBlockedUser = blockedUser)}
|
||||
onDelete={onBlockedUserDelete}
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
data={directInvitations}
|
||||
count={true}
|
||||
keys={[
|
||||
{ key: 'url', label: 'Link', width: 50 },
|
||||
{ key: 'user.username', label: 'Registrierter Nutzer', width: 40, sortable: true }
|
||||
{ key: 'url', label: 'Link' },
|
||||
{ key: 'user.username', label: 'Registrierter Nutzer', sortable: true }
|
||||
]}
|
||||
onDelete={onDirectInvitationDelete}
|
||||
/>
|
||||
|
||||
@@ -12,15 +12,18 @@
|
||||
let { feedback }: Props = $props();
|
||||
</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>
|
||||
<div class="w-96">
|
||||
<div class="w-full lg:w-96">
|
||||
<Input value={feedback?.event} label="Event" readonly />
|
||||
<Input value={feedback?.title} label="Titel" readonly />
|
||||
<Input value={feedback?.username} label="Nutzer" readonly />
|
||||
</div>
|
||||
<div class="divider divider-horizontal"></div>
|
||||
<div class="divider lg:divider-horizontal"></div>
|
||||
<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>
|
||||
|
||||
@@ -20,15 +20,15 @@
|
||||
{dateFormat.format(new Date(value))}
|
||||
{/snippet}
|
||||
|
||||
<div class="h-screen flex flex-col justify-between">
|
||||
<div class="h-full flex flex-col justify-between">
|
||||
<DataTable
|
||||
data={feedbacks}
|
||||
count={true}
|
||||
keys={[
|
||||
{ key: 'event', label: 'Event', width: 10, sortable: true },
|
||||
{ key: 'username', label: 'Nutzer', width: 10, sortable: true },
|
||||
{ key: 'lastChanged', label: 'Datum', width: 10, sortable: true, transform: date },
|
||||
{ key: 'content', label: 'Inhalt', width: 10 }
|
||||
{ key: 'event', label: 'Event', sortable: true },
|
||||
{ key: 'username', label: 'Nutzer', sortable: true },
|
||||
{ key: 'lastChanged', label: 'Datum', sortable: true, transform: date },
|
||||
{ key: 'content', label: 'Inhalt' }
|
||||
]}
|
||||
onClick={(feedback) => (activeFeedback = feedback)}
|
||||
/>
|
||||
|
||||
@@ -89,7 +89,10 @@
|
||||
}
|
||||
</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="dropdown dropdown-end">
|
||||
<div tabindex="0" role="button" class="btn btn-sm btn-circle btn-ghost">
|
||||
@@ -102,35 +105,38 @@
|
||||
</div>
|
||||
<button class="btn btn-sm btn-circle btn-ghost" onclick={() => (report = null)}>✕</button>
|
||||
</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?.reported?.username}
|
||||
label="Reporteter Spieler"
|
||||
onSubmit={(user) => (reportedUser = user)}
|
||||
/>
|
||||
<Textarea bind:value={notice} label="Interne Notizen" rows={10} />
|
||||
<Textarea bind:value={notice} label="Interne Notizen" rows={6} />
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<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">
|
||||
<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)}
|
||||
<div>
|
||||
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
||||
<!-- 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'}
|
||||
<img
|
||||
src={location.pathname + '/attachment/' + reportAttachment.hash}
|
||||
alt={reportAttachment.hash}
|
||||
class="w-16 h-16"
|
||||
class="w-16 h-16 object-cover rounded"
|
||||
/>
|
||||
{:else if reportAttachment.type === 'video'}
|
||||
<!-- 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}
|
||||
</div>
|
||||
</div>
|
||||
@@ -138,9 +144,9 @@
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="divider divider-horizontal"></div>
|
||||
<div class="flex flex-col w-[42rem]">
|
||||
<Textarea bind:value={statement} label="Öffentliche Report Antwort" dynamicWidth rows={7} />
|
||||
<div class="divider lg:divider-horizontal"></div>
|
||||
<div class="flex flex-col w-full lg:w-[42rem]">
|
||||
<Textarea bind:value={statement} label="Öffentliche Report Antwort" dynamicWidth rows={6} />
|
||||
<Select
|
||||
bind:value={status}
|
||||
values={{ open: 'In Bearbeitung', closed: 'Bearbeitet' }}
|
||||
@@ -150,7 +156,7 @@
|
||||
/>
|
||||
<Select bind:value={strikeReason} values={strikeReasonValues} label="Vergehen" dynamicWidth></Select>
|
||||
<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>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
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 { type StrikeReasons, getStrikeReasons, reports } from '@app/admin/reports/reports.ts';
|
||||
|
||||
@@ -49,16 +49,16 @@
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
<div class="h-screen flex flex-col justify-between">
|
||||
<div class="h-full flex flex-col justify-between">
|
||||
<DataTable
|
||||
data={reports}
|
||||
count={true}
|
||||
keys={[
|
||||
{ key: 'reason', label: 'Grund', width: 35 },
|
||||
{ key: 'reporter.username', label: 'Report Ersteller', width: 15 },
|
||||
{ key: 'reported.username', label: 'Reporteter Spieler', width: 15 },
|
||||
{ key: 'createdAt', label: 'Datum', width: 15, sortable: true, transform: date },
|
||||
{ key: 'status.status', label: 'Bearbeitungsstatus', width: 10, sortable: true, transform: status }
|
||||
{ key: 'reason', label: 'Grund' },
|
||||
{ key: 'reporter.username', label: 'Report Ersteller' },
|
||||
{ key: 'reported.username', label: 'Reporteter Spieler' },
|
||||
{ key: 'createdAt', label: 'Datum', sortable: true, transform: date },
|
||||
{ key: 'status.status', label: 'Bearbeitungsstatus', sortable: true, transform: status }
|
||||
]}
|
||||
onClick={(report) => (activeReport = report)}
|
||||
/>
|
||||
|
||||
@@ -99,16 +99,16 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="h-full flex flex-col items-center justify-between">
|
||||
<div class="grid grid-cols-2 w-full">
|
||||
<div class="min-h-full flex flex-col items-center justify-between">
|
||||
<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)}
|
||||
<div class="mx-12">
|
||||
<div class="divider">{setting.name}</div>
|
||||
<div class="flex flex-col gap-5">
|
||||
<div class="flex flex-col">
|
||||
<div class="divider font-bold">{setting.name}</div>
|
||||
<div class="flex flex-col gap-6">
|
||||
{#each setting.entries as entry (entry.name)}
|
||||
<label class="flex justify-between">
|
||||
<span class="mt-[.125rem] text-sm w-1/2">{entry.name}</span>
|
||||
<div class="w-1/2">
|
||||
<label class="flex flex-col sm:flex-row justify-between gap-2">
|
||||
<span class="text-sm font-medium sm:w-1/2">{entry.name}</span>
|
||||
<div class="sm:w-1/2 flex sm:justify-end">
|
||||
{#if entry.type === 'checkbox'}
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -122,7 +122,7 @@
|
||||
{:else if entry.type === 'text'}
|
||||
<input
|
||||
type="text"
|
||||
class="input input-bordered"
|
||||
class="input input-bordered w-full sm:max-w-xs"
|
||||
onchange={(e) => {
|
||||
entry.onChange(e.currentTarget.value);
|
||||
changes = dynamicSettings.getChanges();
|
||||
@@ -131,7 +131,7 @@
|
||||
/>
|
||||
{:else if entry.type === 'textarea'}
|
||||
<textarea
|
||||
class="textarea"
|
||||
class="textarea textarea-bordered w-full sm:max-w-xs min-h-24"
|
||||
value={entry.value}
|
||||
onchange={(e) => {
|
||||
entry.onChange(e.currentTarget.value);
|
||||
@@ -146,9 +146,9 @@
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
<div>
|
||||
<div class="py-12">
|
||||
<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}
|
||||
onclick={onSaveSettingsClick}>Speichern</button
|
||||
>
|
||||
|
||||
@@ -32,9 +32,9 @@
|
||||
data={strikeReasons}
|
||||
count={true}
|
||||
keys={[
|
||||
{ key: 'name', label: 'Name', width: 20 },
|
||||
{ key: 'weight', label: 'Gewichtung', width: 50, sortable: true },
|
||||
{ key: 'id', label: 'Id', width: 20 }
|
||||
{ key: 'name', label: 'Name' },
|
||||
{ key: 'weight', label: 'Gewichtung', sortable: true },
|
||||
{ key: 'id', label: 'Id' }
|
||||
]}
|
||||
onDelete={onBlockedUserDelete}
|
||||
onEdit={(strikeReason) => (editPopupStrikeReason = strikeReason)}
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<fieldset class="fieldset border border-base-200 rounded-box px-4">
|
||||
<legend class="fieldset-legend">Account UUID finder</legend>
|
||||
<div>
|
||||
<div class="flex gap-3">
|
||||
<Input bind:value={username} />
|
||||
<Select bind:value={edition} values={{ java: 'Java', bedrock: 'Bedrock' }} />
|
||||
<div class="flex flex-col sm:flex-row gap-3">
|
||||
<Input bind:value={username} dynamicWidth />
|
||||
<Select bind:value={edition} values={{ java: 'Java', bedrock: 'Bedrock' }} dynamicWidth />
|
||||
</div>
|
||||
<div class="flex justify-center">
|
||||
<button class="btn w-4/6" class:disabled={!username} onclick={onSubmit}>UUID finden</button>
|
||||
|
||||
@@ -47,13 +47,13 @@
|
||||
data={users}
|
||||
count={true}
|
||||
keys={[
|
||||
{ key: 'firstname', label: 'Vorname', width: 15, sortable: true },
|
||||
{ key: 'lastname', label: 'Nachname', width: 15, sortable: true },
|
||||
{ key: 'birthday', label: 'Geburtstag', width: 5, sortable: true },
|
||||
{ key: 'telephone', label: 'Telefon', width: 12, sortable: true },
|
||||
{ key: 'username', label: 'Username', width: 15, sortable: true },
|
||||
{ key: 'edition', label: 'Edition', width: 5, sortable: true, transform: edition },
|
||||
{ key: 'uuid', label: 'UUID', width: 23 }
|
||||
{ key: 'firstname', label: 'Vorname', sortable: true },
|
||||
{ key: 'lastname', label: 'Nachname', sortable: true },
|
||||
{ key: 'birthday', label: 'Geburtstag', sortable: true },
|
||||
{ key: 'telephone', label: 'Telefon', sortable: true },
|
||||
{ key: 'username', label: 'Username', sortable: true },
|
||||
{ key: 'edition', label: 'Edition', sortable: true, transform: edition },
|
||||
{ key: 'uuid', label: 'UUID' }
|
||||
]}
|
||||
extraActions={blockAction}
|
||||
onEdit={(user) => (editPopupUser = user)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<script lang="ts">
|
||||
<script lang="ts" generics="T">
|
||||
import Input from '@components/input/Input.svelte';
|
||||
import { confirmPopupState } from '@components/popup/ConfirmPopup.ts';
|
||||
import Textarea from '@components/input/Textarea.svelte';
|
||||
@@ -14,7 +14,7 @@
|
||||
let modalForm: HTMLFormElement;
|
||||
|
||||
// types
|
||||
interface Props<T> {
|
||||
interface Props {
|
||||
texts: {
|
||||
title: string;
|
||||
submitButtonTitle: string;
|
||||
@@ -75,7 +75,7 @@
|
||||
['number']: {};
|
||||
['password']: {};
|
||||
['select']: {
|
||||
options: Record<string, string>;
|
||||
values: Record<string, string>;
|
||||
};
|
||||
['tel']: {};
|
||||
['text']: {};
|
||||
@@ -93,7 +93,7 @@
|
||||
};
|
||||
|
||||
// input
|
||||
let { texts, target, keys = [], onSubmit, onClose, open = $bindable() }: Props<any> = $props();
|
||||
let { texts, target, keys = [], onSubmit, onClose, open = $bindable() }: Props = $props();
|
||||
|
||||
onInit();
|
||||
|
||||
@@ -101,10 +101,12 @@
|
||||
let submitEnabled = $state(false);
|
||||
|
||||
$effect(() => {
|
||||
if (!open) return;
|
||||
|
||||
if (open) {
|
||||
onInit();
|
||||
modal.show();
|
||||
modal?.show();
|
||||
} else {
|
||||
modal?.close();
|
||||
}
|
||||
});
|
||||
|
||||
$effect.pre(() => {
|
||||
@@ -160,27 +162,30 @@
|
||||
}
|
||||
|
||||
function onModalClose() {
|
||||
setTimeout(() => {
|
||||
open = false;
|
||||
target = null;
|
||||
modalForm.reset();
|
||||
onClose?.();
|
||||
}, 300);
|
||||
}
|
||||
|
||||
function portal(node: HTMLElement) {
|
||||
document.body.appendChild(node);
|
||||
return {
|
||||
destroy() {
|
||||
if (node.parentNode) node.parentNode.removeChild(node);
|
||||
}
|
||||
};
|
||||
}
|
||||
</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}>
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick={onCancelButtonClick}>✕</button>
|
||||
<div class="space-y-5">
|
||||
<h3 class="text-xl font-geist font-bold">{texts.title}</h3>
|
||||
<div class="w-full flex flex-col">
|
||||
{#each keys as key (key)}
|
||||
<div
|
||||
class="grid grid-flow-col gap-4"
|
||||
class:grid-cols-1={key.length === 1}
|
||||
class:grid-cols-2={key.length === 2}
|
||||
>
|
||||
<div class="grid grid-cols-1 gap-x-4" class:md:grid-cols-2={key.length === 2}>
|
||||
{#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'}
|
||||
<Input
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
keys: {
|
||||
key: string;
|
||||
label: string;
|
||||
width?: number;
|
||||
sortable?: boolean;
|
||||
transform?: Snippet<[T]>;
|
||||
}[];
|
||||
@@ -28,20 +27,20 @@
|
||||
let { data, count, keys, onClick, onEdit, onDelete, extraActions }: Props<any> = $props();
|
||||
</script>
|
||||
|
||||
<div class="max-h-screen overflow-x-auto">
|
||||
<table class="table table-pin-rows">
|
||||
<div class="max-h-full overflow-x-auto">
|
||||
<table class="table table-pin-rows w-full table-auto lg:w-fit lg:min-w-full mx-auto">
|
||||
<thead>
|
||||
<SortableTr {data}>
|
||||
{#if count}
|
||||
<SortableTh style="width: 5%">#</SortableTh>
|
||||
<SortableTh style="width: 1%" class="text-left">#</SortableTh>
|
||||
{/if}
|
||||
{#each keys as key (key.key)}
|
||||
<SortableTh style={key.width ? `width: ${key.width}%` : undefined} key={key.sortable ? key.key : undefined}
|
||||
>{key.label}</SortableTh
|
||||
>
|
||||
<SortableTh class="text-left whitespace-nowrap" key={key.sortable ? key.key : undefined}>
|
||||
{key.label}
|
||||
</SortableTh>
|
||||
{/each}
|
||||
{#if onEdit || onDelete || extraActions}
|
||||
<SortableTh style="width: 5%"></SortableTh>
|
||||
<SortableTh style="width: 1%" class="text-left"></SortableTh>
|
||||
{/if}
|
||||
</SortableTr>
|
||||
</thead>
|
||||
@@ -49,10 +48,10 @@
|
||||
{#each $data as d, i (d)}
|
||||
<tr class="hover:bg-base-200" onclick={() => onClick?.(d)}>
|
||||
{#if count}
|
||||
<td>{i + 1}</td>
|
||||
<td class="text-left">{i + 1}</td>
|
||||
{/if}
|
||||
{#each keys as key (key.key)}
|
||||
<td>
|
||||
<td class="text-left whitespace-nowrap">
|
||||
{#if key.transform}
|
||||
{@render key.transform(getObjectEntryByKey(key.key, d))}
|
||||
{:else}
|
||||
@@ -61,7 +60,7 @@
|
||||
</td>
|
||||
{/each}
|
||||
{#if onEdit || onDelete || extraActions}
|
||||
<td class="px-3 whitespace-nowrap">
|
||||
<td class="px-3 whitespace-nowrap text-left">
|
||||
{#if extraActions}
|
||||
{@render extraActions(d)}
|
||||
{/if}
|
||||
|
||||
@@ -31,9 +31,9 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<th {...restProps}>
|
||||
<th {...restProps} class:text-left={true}>
|
||||
{#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>
|
||||
{#if $headerKey === key && asc}
|
||||
<span class="iconify iconify-[heroicons--chevron-up-16-solid]"></span>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
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>
|
||||
|
||||
<fieldset class="fieldset">
|
||||
@@ -24,7 +24,7 @@
|
||||
</legend>
|
||||
<textarea
|
||||
{id}
|
||||
class="textarea"
|
||||
class="textarea min-h-16"
|
||||
class:textarea-sm={size === 'sm'}
|
||||
class:w-full={dynamicWidth}
|
||||
class:validator={required}
|
||||
|
||||
@@ -82,9 +82,41 @@ const adminTabs = [
|
||||
|
||||
<BaseLayout title={title}>
|
||||
<ClientRouter />
|
||||
<div class="flex flex-row max-h-screen h-screen overflow-hidden">
|
||||
<div class="bg-base-200 w-72 flex flex-col justify-between overflow-scroll">
|
||||
<ul class="menu">
|
||||
<div class="drawer lg:drawer-open">
|
||||
<input id="admin-drawer" type="checkbox" class="drawer-toggle" />
|
||||
<div class="drawer-content flex flex-col h-screen overflow-hidden">
|
||||
<!-- Mobile Navbar -->
|
||||
<div class="navbar bg-base-200 lg:hidden border-b border-base-300 shrink-0">
|
||||
<div class="navbar-start">
|
||||
<label for="admin-drawer" class="btn btn-square btn-ghost drawer-button">
|
||||
<span class="iconify iconify-[heroicons--bars-3]"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="navbar-center">
|
||||
<a class="text-xl font-bold">{title}</a>
|
||||
</div>
|
||||
<div class="navbar-end">
|
||||
<!-- Optional: Profile or logout shortcut -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 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>
|
||||
@@ -122,26 +154,29 @@ const adminTabs = [
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
<ul class="menu w-full">
|
||||
</div>
|
||||
<div class="flex flex-col w-0 min-w-full">
|
||||
<ul class="menu w-full p-2">
|
||||
{
|
||||
Astro.slots.has('actions') && (
|
||||
<div class="mb-2">
|
||||
<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" />
|
||||
</fieldset>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div class="divider mx-1 my-0"></div>
|
||||
<li>
|
||||
<button id="logout">
|
||||
<button id="logout" class="text-error whitespace-nowrap">
|
||||
<span class="iconify iconify-[heroicons--arrow-left-end-on-rectangle]"></span>
|
||||
<span>Ausloggen</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="w-full overflow-scroll relative">
|
||||
<slot />
|
||||
</aside>
|
||||
</div>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
@@ -7,7 +7,7 @@ import Popup from '@components/popup/Popup.svelte';
|
||||
|
||||
<AdminLoginLayout title="Login">
|
||||
<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>
|
||||
<div class="divider"></div>
|
||||
<form id="login" class="flex flex-col items-center">
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import WebsiteLayout from '@layouts/website/WebsiteLayout.astro';
|
||||
import ConfirmPopup from '@components/popup/ConfirmPopup.svelte';
|
||||
import Popup from '@components/popup/Popup.svelte';
|
||||
import Select from '@components/input/Select.svelte';
|
||||
import Textarea from '@components/input/Textarea.svelte';
|
||||
import Input from '@components/input/Input.svelte';
|
||||
---
|
||||
|
||||
@@ -79,9 +79,9 @@ const information = [
|
||||
<div>
|
||||
<h2 class="text-3xl text-black dark:text-white mb-8">Über uns</h2>
|
||||
<p>
|
||||
Wir sind ein kleines <a class="link" href="admins">Team</a> von Minecraft-Enthusiasten, das bereits im 8. Jahr
|
||||
in Folge Minecraft CraftAttack organisiert. Jahr für Jahr arbeiten wir daran, das Spielerlebnis zu verbessern und
|
||||
steigeren die Teilnehmerzahl.
|
||||
Wir sind ein kleines <a class="link" href="admins">Team</a> von Minecraft-Enthusiasten, das bereits im 8. Jahr in
|
||||
Folge Minecraft CraftAttack organisiert. Jahr für Jahr arbeiten wir daran, das Spielerlebnis zu verbessern und steigeren
|
||||
die Teilnehmerzahl.
|
||||
</p>
|
||||
<p>
|
||||
Unser Ziel bei diesem ab dem <span class="italic"
|
||||
|
||||
@@ -20,5 +20,4 @@ const signupDisabledSubMessage = signupSetting[SettingKey.SignupDisabledSubMessa
|
||||
message: signupDisabledMessage,
|
||||
subMessage: signupDisabledSubMessage
|
||||
}}
|
||||
}
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user