This commit is contained in:
@ -11,10 +11,11 @@
|
||||
} from 'svelte-heros-v2';
|
||||
import { buttonTriggeredRequest } from '$lib/components/utils';
|
||||
import { goto } from '$app/navigation';
|
||||
import type { LayoutData } from './$types';
|
||||
import { adminCount, errorMessage, reportCount, feedbackCount } from '$lib/stores';
|
||||
import ErrorToast from '$lib/components/Toast/ErrorToast.svelte';
|
||||
|
||||
let { children, data } = $props();
|
||||
|
||||
async function logout() {
|
||||
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/logout`, {
|
||||
method: 'POST'
|
||||
@ -26,7 +27,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
export let data: LayoutData;
|
||||
if (data.reportCount) $reportCount = data.reportCount;
|
||||
if (data.feedbackCount) $feedbackCount = data.feedbackCount;
|
||||
if (data.adminCount) $adminCount = data.adminCount;
|
||||
@ -85,9 +85,10 @@
|
||||
<ul class="menu menu-horizontal h-10 p-0 flex items-center bg-base-300 rounded-lg">
|
||||
{#each tabs as tab}
|
||||
{#if tab.enabled}
|
||||
{@const Icon = tab.icon}
|
||||
<li>
|
||||
<a href={tab.path}>
|
||||
<svelte:component this={tab.icon} />
|
||||
<Icon />
|
||||
<span class="mr-1" class:underline={$page.url.pathname === tab.path}>{tab.name}</span>
|
||||
{#if tab.badge != null}
|
||||
<div class="badge">{tab.badge}</div>
|
||||
@ -100,7 +101,7 @@
|
||||
<div class="absolute top-0 right-0 flex items-center h-full">
|
||||
<ul class="menu menu-vertical">
|
||||
<li>
|
||||
<button on:click={(e) => buttonTriggeredRequest(e, logout())}>
|
||||
<button onclick={(e) => buttonTriggeredRequest(e, logout())}>
|
||||
<ArrowLeftOnRectangle />
|
||||
<span>Ausloggen</span>
|
||||
</button>
|
||||
@ -109,11 +110,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="h-full w-full -mt-12 pt-12 overflow-y-scroll overflow-x-hidden">
|
||||
<slot />
|
||||
{@render children()}
|
||||
</div>
|
||||
{:else}
|
||||
<div class="h-full w-full">
|
||||
<slot />
|
||||
{@render children()}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
@ -1,9 +1,8 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import { BookOpen, Cog6Tooth, Flag, UserGroup, Users } from 'svelte-heros-v2';
|
||||
|
||||
export let data: PageData;
|
||||
let { data } = $props();
|
||||
|
||||
let tabs = [
|
||||
{
|
||||
@ -42,13 +41,14 @@
|
||||
<div class="flex justify-around items-center h-screen">
|
||||
{#each tabs as tab}
|
||||
{#if tab.enabled}
|
||||
{@const Icon = tab.icon}
|
||||
<div class="flex flex-col gap-4 justify-center items-center">
|
||||
<a
|
||||
class="h-48 w-48 border flex justify-center items-center rounded-xl duration-100 hover:bg-base-200"
|
||||
href={tab.path}
|
||||
title={tab.name}
|
||||
>
|
||||
<svelte:component this={tab.icon} width="10rem" height="10rem" />
|
||||
<Icon />
|
||||
</a>
|
||||
<span>{tab.name}</span>
|
||||
</div>
|
||||
|
@ -1,16 +1,18 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import Badges from '$lib/components/Input/Badges.svelte';
|
||||
import { Check, NoSymbol, PencilSquare, Trash, UserPlus } from 'svelte-heros-v2';
|
||||
import Input from '$lib/components/Input/Input.svelte';
|
||||
import { Permissions } from '$lib/permissions';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import ErrorToast from '$lib/components/Toast/ErrorToast.svelte';
|
||||
import { buttonTriggeredRequest } from '$lib/components/utils';
|
||||
import { goto } from '$app/navigation';
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import { adminCount } from '$lib/stores';
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
let admins = $state(data.admins);
|
||||
|
||||
let allPermissionBadges = {
|
||||
Admin: Permissions.Admin,
|
||||
Users: Permissions.Users,
|
||||
@ -19,9 +21,9 @@
|
||||
Settings: Permissions.Settings
|
||||
};
|
||||
|
||||
let newAdminUsername: string;
|
||||
let newAdminPassword: string;
|
||||
let newAdminPermissions: number[];
|
||||
let newAdminUsername = $state('');
|
||||
let newAdminPassword = $state('');
|
||||
let newAdminPermissions = $state([]);
|
||||
|
||||
async function addAdmin(username: string, password: string, permissions: Permissions) {
|
||||
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/admin`, {
|
||||
@ -36,8 +38,11 @@
|
||||
let res = await response.json();
|
||||
$adminCount += 1;
|
||||
res.permissions = new Permissions(res.permissions).asArray();
|
||||
data.admins.push(res);
|
||||
data.admins = data.admins;
|
||||
admins.push(res);
|
||||
|
||||
newAdminUsername = '';
|
||||
newAdminPassword = '';
|
||||
newAdminPermissions = [];
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
@ -79,35 +84,32 @@
|
||||
await goto(`${env.PUBLIC_BASE_PATH}/`);
|
||||
} else {
|
||||
$adminCount -= 1;
|
||||
data.admins.splice(
|
||||
data.admins.findIndex((v) => v.id == id),
|
||||
admins.splice(
|
||||
admins.findIndex((v) => v.id == id),
|
||||
1
|
||||
);
|
||||
data.admins = data.admins;
|
||||
admins = admins;
|
||||
}
|
||||
} else {
|
||||
throw new Error();
|
||||
}
|
||||
}
|
||||
|
||||
let errorMessage = '';
|
||||
|
||||
export let data: PageData;
|
||||
let permissions = new Permissions(data.permissions);
|
||||
let permissions = $state(new Permissions(data.permissions));
|
||||
</script>
|
||||
|
||||
<table class="table table-zebra w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th></th>
|
||||
<th>Benutzername</th>
|
||||
<th>Passwort</th>
|
||||
<th>Berechtigungen</th>
|
||||
<th />
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{#each data.admins as admin, i}
|
||||
{#each admins as admin, i}
|
||||
<tr>
|
||||
<td>{i + 1}</td>
|
||||
<td><Input type="text" bind:value={admin.username} disabled={!admin.edit} size="sm" /></td>
|
||||
@ -133,7 +135,7 @@
|
||||
<span class="w-min" class:cursor-not-allowed={!permissions.admin()}>
|
||||
<button
|
||||
class="btn btn-sm btn-square"
|
||||
on:click={async (e) => {
|
||||
onclick={async (e) => {
|
||||
await buttonTriggeredRequest(
|
||||
e,
|
||||
updateAdmin(
|
||||
@ -153,9 +155,9 @@
|
||||
<span class="w-min" class:cursor-not-allowed={!permissions.admin()}>
|
||||
<button
|
||||
class="btn btn-sm btn-square"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
admin.edit = false;
|
||||
admin = admin.before;
|
||||
admins[i] = admin.before;
|
||||
}}
|
||||
>
|
||||
<NoSymbol size="18" />
|
||||
@ -165,9 +167,9 @@
|
||||
<span class="w-min" class:cursor-not-allowed={!permissions.admin()}>
|
||||
<button
|
||||
class="btn btn-sm btn-square"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
admin.before = $state.snapshot(admin);
|
||||
admin.edit = true;
|
||||
admin.before = structuredClone(admin);
|
||||
}}
|
||||
>
|
||||
<PencilSquare size="18" />
|
||||
@ -176,7 +178,7 @@
|
||||
<span class="w-min" class:cursor-not-allowed={!permissions.admin()}>
|
||||
<button
|
||||
class="btn btn-sm btn-square"
|
||||
on:click={(e) => buttonTriggeredRequest(e, deleteAdmin(admin.id))}
|
||||
onclick={(e) => buttonTriggeredRequest(e, deleteAdmin(admin.id))}
|
||||
>
|
||||
<Trash size="18" />
|
||||
</button>
|
||||
@ -187,7 +189,7 @@
|
||||
</tr>
|
||||
{/each}
|
||||
<tr>
|
||||
<td>{data.admins.length + 1}</td>
|
||||
<td>{admins.length + 1}</td>
|
||||
<td><Input type="text" bind:value={newAdminUsername} size="sm" /></td>
|
||||
<td><Input type="password" bind:value={newAdminPassword} size="sm" /></td>
|
||||
<td><Badges bind:value={newAdminPermissions} available={allPermissionBadges} /></td>
|
||||
@ -196,7 +198,7 @@
|
||||
<button
|
||||
class="btn btn-sm btn-square"
|
||||
disabled={!newAdminUsername || !newAdminPassword}
|
||||
on:click={async (e) => {
|
||||
onclick={async (e) => {
|
||||
await buttonTriggeredRequest(
|
||||
e,
|
||||
addAdmin(newAdminUsername, newAdminPassword, new Permissions(newAdminPermissions))
|
||||
@ -213,7 +215,3 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<ErrorToast show={errorMessage !== ''}>
|
||||
<span />
|
||||
</ErrorToast>
|
||||
|
@ -11,10 +11,10 @@
|
||||
import Textarea from '$lib/components/Input/Textarea.svelte';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
|
||||
let feedbacks: (typeof Feedback.prototype.dataValues)[] = [];
|
||||
let feedbacks: (typeof Feedback.prototype.dataValues)[] = $state([]);
|
||||
let feedbacksPerRequest = 25;
|
||||
let feedbackFilter = { event: null, content: null, username: null };
|
||||
let activeFeedback: typeof Feedback.prototype.dataValues | null = null;
|
||||
let feedbackFilter = $state({ event: null, content: null, username: null });
|
||||
let activeFeedback: typeof Feedback.prototype.dataValues | null = $state(null);
|
||||
|
||||
async function fetchFeedback(extendedFilter?: {
|
||||
limit?: number;
|
||||
@ -57,13 +57,14 @@
|
||||
onDestroy(() => {
|
||||
if (browser) window.removeEventListener('hashchange', openHashReport);
|
||||
});
|
||||
|
||||
$: if (feedbackFilter) fetchFeedback({ from: 0 }).then((r) => (feedbacks = r));
|
||||
</script>
|
||||
|
||||
<div class="h-full flex flex-row">
|
||||
<div class="w-full flex flex-col overflow-hidden">
|
||||
<HeaderBar bind:feedbackFilter />
|
||||
<HeaderBar
|
||||
bind:feedbackFilter
|
||||
onUpdate={() => fetchFeedback({ from: 0 }).then((r) => (feedbacks = r))}
|
||||
/>
|
||||
<hr class="divider my-1 mx-8 border-none" />
|
||||
<table class="table table-fixed h-fit">
|
||||
<thead>
|
||||
@ -82,7 +83,7 @@
|
||||
<tr
|
||||
class="hover [&>*]:text-sm cursor-pointer"
|
||||
class:bg-base-200={activeFeedback?.url_hash === feedback.url_hash}
|
||||
on:click={async () => {
|
||||
onclick={async () => {
|
||||
await goto(`${window.location.href.split('#')[0]}#${feedback.url_hash}`, {
|
||||
replaceState: true
|
||||
});
|
||||
@ -96,8 +97,10 @@
|
||||
<button
|
||||
class="pl-1"
|
||||
title="Nach Ersteller filtern"
|
||||
on:click|stopPropagation={() =>
|
||||
(feedbackFilter.username = feedback.user?.username)}
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
feedbackFilter.username = feedback.user.username;
|
||||
}}
|
||||
>
|
||||
<MagnifyingGlass size="14" />
|
||||
</button>
|
||||
@ -125,18 +128,18 @@
|
||||
>
|
||||
<div class="absolute right-2 top-2 flex justify-center">
|
||||
<form class="dropdown dropdown-end">
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
||||
<label tabindex="0" class="btn btn-sm btn-circle btn-ghost text-center">
|
||||
<Share size="1rem" />
|
||||
</label>
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-max"
|
||||
>
|
||||
<li>
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
`${window.location.protocol}//${window.location.host}${env.PUBLIC_BASE_PATH}/admin/reports#${activeFeedback.url_hash}`
|
||||
);
|
||||
@ -145,7 +148,7 @@
|
||||
Internen Link kopieren
|
||||
</button>
|
||||
<button
|
||||
on:click={() =>
|
||||
onclick={() =>
|
||||
navigator.clipboard.writeText(
|
||||
`${window.location.protocol}//${window.location.host}${env.PUBLIC_BASE_PATH}/report/${activeFeedback.url_hash}`
|
||||
)}>Öffentlichen Link kopieren</button
|
||||
@ -155,7 +158,7 @@
|
||||
</form>
|
||||
<button
|
||||
class="btn btn-sm btn-circle btn-ghost"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
activeFeedback = null;
|
||||
goto(window.location.href.split('#')[0], { replaceState: true });
|
||||
}}>✕</button
|
||||
@ -169,7 +172,9 @@
|
||||
value={activeFeedback.user?.username || ''}
|
||||
pickyWidth={false}
|
||||
>
|
||||
<span slot="label">Nutzer</span>
|
||||
{#snippet label()}
|
||||
<span>Nutzer</span>
|
||||
{/snippet}
|
||||
</Input>
|
||||
<Textarea readonly={true} rows={4} label="Inhalt" value={activeFeedback.content} />
|
||||
</div>
|
||||
|
@ -20,7 +20,7 @@ export const POST = (async ({ request, cookies }) => {
|
||||
}
|
||||
const data = parseResult.data;
|
||||
|
||||
let feedbackFindOptions: Attributes<Feedback> = {
|
||||
const feedbackFindOptions: Attributes<Feedback> = {
|
||||
content: { [Op.not]: null }
|
||||
};
|
||||
if (data.event) Object.assign(feedbackFindOptions, { event: { [Op.like]: `%${data.event}%` } });
|
||||
@ -35,7 +35,7 @@ export const POST = (async ({ request, cookies }) => {
|
||||
});
|
||||
if (data.hash) Object.assign(feedbackFindOptions, { url_hash: data.hash });
|
||||
|
||||
let feedback = await Feedback.findAll({
|
||||
const feedback = await Feedback.findAll({
|
||||
where: feedbackFindOptions,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
|
@ -1,21 +1,26 @@
|
||||
<script lang="ts">
|
||||
import Input from '$lib/components/Input/Input.svelte';
|
||||
|
||||
export let feedbackFilter: { [k: string]: any } = {
|
||||
event: null,
|
||||
content: null,
|
||||
username: null
|
||||
};
|
||||
let {
|
||||
feedbackFilter = $bindable({ event: null, content: null, username: null }),
|
||||
onUpdate
|
||||
}: { feedbackFilter: { [k: string]: any }; onUpdate: () => void } = $props();
|
||||
</script>
|
||||
|
||||
<form class="flex flex-row justify-center space-x-4 mx-4 my-2">
|
||||
<Input size="sm" placeholder="Alle" bind:value={feedbackFilter.username}>
|
||||
<span slot="label">Nutzer</span>
|
||||
<Input size="sm" placeholder="Alle" bind:value={feedbackFilter.username} oninput={onUpdate}>
|
||||
{#snippet label()}
|
||||
<span>Nutzer</span>
|
||||
{/snippet}
|
||||
</Input>
|
||||
<Input size="sm" placeholder="Alle" bind:value={feedbackFilter.event}>
|
||||
<span slot="label">Event</span>
|
||||
<Input size="sm" placeholder="Alle" bind:value={feedbackFilter.event} oninput={onUpdate}>
|
||||
{#snippet label()}
|
||||
<span>Event</span>
|
||||
{/snippet}
|
||||
</Input>
|
||||
<Input size="sm" placeholder="Alle" bind:value={feedbackFilter.content}>
|
||||
<span slot="label">Inhalt</span>
|
||||
<Input size="sm" placeholder="Alle" bind:value={feedbackFilter.content} oninput={onUpdate}>
|
||||
{#snippet label()}
|
||||
<span>Inhalt</span>
|
||||
{/snippet}
|
||||
</Input>
|
||||
</form>
|
||||
|
@ -3,7 +3,8 @@
|
||||
import { env } from '$env/dynamic/public';
|
||||
import { errorMessage } from '$lib/stores';
|
||||
|
||||
let passwordValue: string;
|
||||
let password = $state('');
|
||||
|
||||
async function login() {
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
loginRequest = new Promise(async (resolve) => {
|
||||
@ -12,10 +13,10 @@
|
||||
body: JSON.stringify(Object.fromEntries(new FormData(document.forms[0])))
|
||||
});
|
||||
if (response.ok) {
|
||||
window.location = `${env.PUBLIC_BASE_PATH}/admin`;
|
||||
window.location.href = `${env.PUBLIC_BASE_PATH}/admin`;
|
||||
resolve();
|
||||
} else if (response.status == 401) {
|
||||
passwordValue = '';
|
||||
password = '';
|
||||
$errorMessage = 'Nutzername oder Passwort falsch';
|
||||
resolve();
|
||||
} else {
|
||||
@ -25,25 +26,29 @@
|
||||
});
|
||||
}
|
||||
|
||||
let loginRequest: Promise<void> | null = null;
|
||||
let loginRequest: Promise<void> | null = $state(null);
|
||||
</script>
|
||||
|
||||
<div class="card px-14 py-6 shadow-lg">
|
||||
<h1 class="text-center text-4xl mt-2 mb-4">Craftattack Admin Login</h1>
|
||||
<form class="flex flex-col items-center" on:submit|preventDefault={login}>
|
||||
<form
|
||||
class="flex flex-col items-center"
|
||||
onsubmit={(e) => {
|
||||
e.preventDefault();
|
||||
login();
|
||||
}}
|
||||
>
|
||||
<div class="flex flex-col justify-center items-center">
|
||||
<div class="grid gap-4">
|
||||
<Input id="username" name="username" type="text" required={true}>
|
||||
<span slot="label">Nutzername</span>
|
||||
{#snippet label()}
|
||||
<span>Nutzername</span>
|
||||
{/snippet}
|
||||
</Input>
|
||||
<Input
|
||||
id="password"
|
||||
name="password"
|
||||
type="password"
|
||||
required={true}
|
||||
bind:value={passwordValue}
|
||||
>
|
||||
<span slot="label">Passwort</span>
|
||||
<Input id="password" name="password" type="password" required={true} bind:value={password}>
|
||||
{#snippet label()}
|
||||
<span>Passwort</span>
|
||||
{/snippet}
|
||||
</Input>
|
||||
</div>
|
||||
</div>
|
||||
@ -56,22 +61,7 @@
|
||||
{#await loginRequest}
|
||||
<span
|
||||
class="relative top-[calc(50%-12px)] left-[calc(50%-12px)] row-[1] col-[1] loading loading-ring"
|
||||
/>
|
||||
{:catch error}
|
||||
<dialog
|
||||
class="modal"
|
||||
on:close={() => setTimeout(() => (loginRequest = null), 200)}
|
||||
open
|
||||
>
|
||||
<form method="dialog" class="modal-box">
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||
<h3 class="font-bold text-lg">Error</h3>
|
||||
<p class="py-4">{error.message}</p>
|
||||
</form>
|
||||
<form method="dialog" class="modal-backdrop bg-[rgba(0,0,0,.2)]">
|
||||
<button>close</button>
|
||||
</form>
|
||||
</dialog>
|
||||
></span>
|
||||
{/await}
|
||||
{/if}
|
||||
{/key}
|
||||
|
@ -1,6 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { fly } from 'svelte/transition';
|
||||
import type { PageData } from './$types';
|
||||
import type { Report } from '$lib/server/database';
|
||||
import { browser } from '$app/environment';
|
||||
import { env } from '$env/dynamic/public';
|
||||
@ -18,12 +17,12 @@
|
||||
import { usernameSuggestions } from '$lib/utils';
|
||||
import PaginationTableBody from '$lib/components/PaginationTable/PaginationTableBody.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
let { data } = $props();
|
||||
|
||||
let reports: (typeof Report.prototype.dataValues)[] = [];
|
||||
let reports: (typeof Report.prototype.dataValues)[] = $state([]);
|
||||
let reportsPerRequest = 25;
|
||||
let reportFilter = { draft: false, status: null, reporter: null, reported: null };
|
||||
let activeReport: typeof Report.prototype.dataValues | null = null;
|
||||
let reportFilter = $state({ draft: false, status: null, reporter: null, reported: null });
|
||||
let activeReport: typeof Report.prototype.dataValues | null = $state(null);
|
||||
|
||||
async function fetchReports(extendedFilter?: {
|
||||
hash?: string;
|
||||
@ -94,19 +93,20 @@
|
||||
|
||||
let saveActiveReportChangesModal: HTMLDialogElement;
|
||||
let newReportModal: HTMLDialogElement;
|
||||
|
||||
$: if (reportFilter) fetchReports({ from: 0 }).then((r) => (reports = r.reports));
|
||||
</script>
|
||||
|
||||
<div class="h-full flex flex-row">
|
||||
<div class="w-full flex flex-col overflow-scroll">
|
||||
<div class="grid grid-cols-[5fr_1fr_10fr_1fr_5fr]">
|
||||
<div />
|
||||
<div />
|
||||
<HeaderBar bind:reportFilter />
|
||||
<div class="divider divider-horizontal my-auto h-3/4" />
|
||||
<div></div>
|
||||
<div></div>
|
||||
<HeaderBar
|
||||
bind:reportFilter
|
||||
onUpdate={() => fetchReports({ from: 0 }).then((r) => (reports = r.reports))}
|
||||
/>
|
||||
<div class="divider divider-horizontal my-auto h-3/4"></div>
|
||||
<div class="flex items-center">
|
||||
<button class="btn" on:click={() => newReportModal.show()}>
|
||||
<button class="btn" onclick={() => newReportModal.show()}>
|
||||
<Plus />
|
||||
<span>Neuer Report</span>
|
||||
</button>
|
||||
@ -140,7 +140,7 @@
|
||||
<tr
|
||||
class="hover [&>*]:text-sm cursor-pointer"
|
||||
class:bg-base-200={activeReport?.url_hash === report.url_hash}
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
goto(`${window.location.href.split('#')[0]}#${report.url_hash}`, {
|
||||
replaceState: true
|
||||
});
|
||||
@ -154,7 +154,10 @@
|
||||
<button
|
||||
class="pl-1"
|
||||
title="Nach Ersteller filtern"
|
||||
on:click|stopPropagation={() => (reportFilter.reporter = report.reporter.username)}
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
reportFilter.reporter = report.reporter.username;
|
||||
}}
|
||||
>
|
||||
<MagnifyingGlass size="14" />
|
||||
</button>
|
||||
@ -165,8 +168,10 @@
|
||||
<button
|
||||
class="pl-1"
|
||||
title="Nach Reportetem Spieler filtern"
|
||||
on:click|stopPropagation={() =>
|
||||
(reportFilter.reported = report.reported.username)}
|
||||
onclick={(e) => {
|
||||
e.stopPropagation();
|
||||
reportFilter.reported = report.reported.username;
|
||||
}}
|
||||
>
|
||||
<MagnifyingGlass size="14" />
|
||||
</button>
|
||||
@ -203,18 +208,18 @@
|
||||
>
|
||||
<div class="absolute right-2 top-2 flex justify-center">
|
||||
<form class="dropdown dropdown-end">
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex a11y-label-has-associated-control -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
||||
<label tabindex="0" class="btn btn-sm btn-circle btn-ghost text-center">
|
||||
<Share size="1rem" />
|
||||
</label>
|
||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
||||
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
||||
<ul
|
||||
tabindex="0"
|
||||
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-max"
|
||||
>
|
||||
<li>
|
||||
<button
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
navigator.clipboard.writeText(
|
||||
`${window.location.protocol}//${window.location.host}${env.PUBLIC_BASE_PATH}/admin/reports#${activeReport.url_hash}`
|
||||
);
|
||||
@ -223,7 +228,7 @@
|
||||
Internen Link kopieren
|
||||
</button>
|
||||
<button
|
||||
on:click={() =>
|
||||
onclick={() =>
|
||||
navigator.clipboard.writeText(
|
||||
`${window.location.protocol}//${window.location.host}${env.PUBLIC_BASE_PATH}/report/${activeReport.url_hash}`
|
||||
)}>Öffentlichen Link kopieren</button
|
||||
@ -233,7 +238,7 @@
|
||||
</form>
|
||||
<button
|
||||
class="btn btn-sm btn-circle btn-ghost"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
activeReport = null;
|
||||
goto(window.location.href.split('#')[0], { replaceState: true });
|
||||
}}>✕</button
|
||||
@ -242,7 +247,9 @@
|
||||
<h3 class="font-roboto font-semibold text-2xl mb-2">Report</h3>
|
||||
<div class="w-full">
|
||||
<Input readonly={true} size="sm" value={activeReport.reporter.username} pickyWidth={false}>
|
||||
<span slot="label">Reporter</span>
|
||||
{#snippet label()}
|
||||
<span>Reporter</span>
|
||||
{/snippet}
|
||||
</Input>
|
||||
<Search
|
||||
size="sm"
|
||||
@ -252,17 +259,17 @@
|
||||
invalidMessage="Es können nur registrierte Spieler reportet werden"
|
||||
label="Reporteter User"
|
||||
inputValue={activeReport.reported?.username || ''}
|
||||
on:submit={(e) =>
|
||||
onsubmit={(e) =>
|
||||
(activeReport.reported = {
|
||||
...activeReport.reported,
|
||||
username: e.detail.input,
|
||||
uuid: e.detail.value
|
||||
username: e.input,
|
||||
uuid: e.value
|
||||
})}
|
||||
/>
|
||||
<Textarea readonly={true} rows={1} label="Report Grund" value={activeReport.subject} />
|
||||
<Textarea readonly={true} rows={4} label="Report Details" value={activeReport.body} />
|
||||
</div>
|
||||
<div class="divider mx-4" />
|
||||
<div class="divider mx-4"></div>
|
||||
<div>
|
||||
<div
|
||||
class="w-full"
|
||||
@ -318,7 +325,7 @@
|
||||
<Input
|
||||
type="submit"
|
||||
value="Speichern"
|
||||
on:click={() => saveActiveReportChangesModal.show()}
|
||||
onclick={() => saveActiveReportChangesModal.show()}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@ -333,7 +340,7 @@
|
||||
<Input
|
||||
type="submit"
|
||||
value="Speichern"
|
||||
on:click={async () => {
|
||||
onclick={async () => {
|
||||
await updateActiveReport();
|
||||
if (activeReport.reported?.username) {
|
||||
if (activeReport.reported?.id === undefined) {
|
||||
@ -363,9 +370,9 @@
|
||||
|
||||
<dialog class="modal" bind:this={newReportModal}>
|
||||
<NewReportModal
|
||||
on:submit={(e) => {
|
||||
if (!e.detail.draft) $reportCount += 1;
|
||||
reports = [e.detail, ...reports];
|
||||
onSubmit={(e) => {
|
||||
if (!e.draft) $reportCount += 1;
|
||||
reports = [e, ...reports];
|
||||
activeReport = reports[0];
|
||||
newReportModal.close();
|
||||
}}
|
||||
|
@ -2,28 +2,38 @@
|
||||
import Select from '$lib/components/Input/Select.svelte';
|
||||
import Input from '$lib/components/Input/Input.svelte';
|
||||
|
||||
export let reportFilter = {
|
||||
reporter: null,
|
||||
reported: null,
|
||||
status: null,
|
||||
draft: false
|
||||
};
|
||||
let {
|
||||
reportFilter = $bindable({
|
||||
reporter: undefined,
|
||||
reported: undefined,
|
||||
status: undefined,
|
||||
draft: false
|
||||
}),
|
||||
onUpdate
|
||||
}: {
|
||||
reportFilter: { reporter?: string; reported?: string; status?: string; draft: false };
|
||||
onUpdate: () => void;
|
||||
} = $props();
|
||||
</script>
|
||||
|
||||
<form class="flex flex-row justify-center space-x-4 mx-4 my-2">
|
||||
<Input size="sm" placeholder="Alle" bind:value={reportFilter.reporter}>
|
||||
<span slot="label">Report Ersteller</span>
|
||||
<Input size="sm" placeholder="Alle" bind:value={reportFilter.reporter} oninput={onUpdate}>
|
||||
{#snippet label()}
|
||||
<span>Report Ersteller</span>
|
||||
{/snippet}
|
||||
</Input>
|
||||
<Input size="sm" placeholder="Alle" bind:value={reportFilter.reported}>
|
||||
<span slot="label">Reportete Spieler</span>
|
||||
<Input size="sm" placeholder="Alle" bind:value={reportFilter.reported} oninput={onUpdate}>
|
||||
{#snippet label()}
|
||||
<span>Reporteter Spieler</span>
|
||||
{/snippet}
|
||||
</Input>
|
||||
<Select label="Bearbeitungsstatus" size="sm" bind:value={reportFilter.status}>
|
||||
<Select label="Bearbeitungsstatus" size="sm" bind:value={reportFilter.status} onChange={onUpdate}>
|
||||
<option value="none">Unbearbeitet</option>
|
||||
<option value="review">In Bearbeitung</option>
|
||||
<option value={null}>Unbearbeitet & In Bearbeitung</option>
|
||||
<option value="reviewed">Bearbeitet</option>
|
||||
</Select>
|
||||
<Select label="Reportstatus" size="sm" bind:value={reportFilter.draft}>
|
||||
<Select label="Reportstatus" size="sm" bind:value={reportFilter.draft} onChange={onUpdate}>
|
||||
<option value={false}>Erstellt</option>
|
||||
<option value={true}>Entwurf</option>
|
||||
</Select>
|
||||
|
@ -3,15 +3,15 @@
|
||||
import { env } from '$env/dynamic/public';
|
||||
import Textarea from '$lib/components/Input/Textarea.svelte';
|
||||
import Search from '$lib/components/Input/Search.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { usernameSuggestions } from '$lib/utils';
|
||||
import type { Report } from '$lib/server/database';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let { onSubmit }: { onSubmit: (data: typeof Report.prototype.dataValues) => void } = $props();
|
||||
|
||||
let reporter: string;
|
||||
let reported: string;
|
||||
let reason = '';
|
||||
let body = '';
|
||||
let reporter: string | undefined = $state();
|
||||
let reported: string | undefined = $state();
|
||||
let reason = $state('');
|
||||
let body = $state('');
|
||||
|
||||
async function newReport() {
|
||||
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/reports`, {
|
||||
@ -23,7 +23,7 @@
|
||||
body: body || null
|
||||
})
|
||||
});
|
||||
if (response.ok) dispatch('submit', await response.json());
|
||||
if (response.ok) onSubmit(await response.json());
|
||||
}
|
||||
|
||||
let globalCloseForm: HTMLFormElement;
|
||||
@ -35,7 +35,10 @@
|
||||
<form method="dialog" class="modal-box" bind:this={reportForm}>
|
||||
<button
|
||||
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||
on:click|preventDefault={() => globalCloseForm.submit()}>✕</button
|
||||
onclick={(e) => {
|
||||
e.preventDefault();
|
||||
globalCloseForm.submit();
|
||||
}}>✕</button
|
||||
>
|
||||
<h3 class="font-roboto text-3xl">Neuer Report</h3>
|
||||
<div class="space-y-2 mt-2 px-1 max-h-[70vh] overflow-y-scroll">
|
||||
@ -59,9 +62,11 @@
|
||||
bind:value={reported}
|
||||
/>
|
||||
</div>
|
||||
<div class="divider mx-4 pt-3" />
|
||||
<div class="divider mx-4 pt-3"></div>
|
||||
<Input type="text" bind:value={reason} required={true} pickyWidth={false}>
|
||||
<span slot="label">Report Grund</span>
|
||||
{#snippet label()}
|
||||
<span>Report Grund</span>
|
||||
{/snippet}
|
||||
</Input>
|
||||
<div>
|
||||
<Textarea rows={4} label="Details über den Report Grund" bind:value={body} />
|
||||
@ -71,9 +76,9 @@
|
||||
<Input
|
||||
type="submit"
|
||||
value="Erstellen"
|
||||
on:click={(e) => {
|
||||
onclick={(e) => {
|
||||
if (reportForm.checkValidity()) {
|
||||
e.detail.preventDefault();
|
||||
e.preventDefault();
|
||||
confirmDialog.show();
|
||||
}
|
||||
}}
|
||||
@ -81,8 +86,8 @@
|
||||
<Input
|
||||
type="submit"
|
||||
value="Abbrechen"
|
||||
on:click={(e) => {
|
||||
e.detail.preventDefault();
|
||||
onclick={(e) => {
|
||||
e.preventDefault();
|
||||
globalCloseForm.submit();
|
||||
}}
|
||||
/>
|
||||
@ -111,7 +116,7 @@
|
||||
<Input
|
||||
type="submit"
|
||||
value="Erstellen"
|
||||
on:click={async () => {
|
||||
onclick={async () => {
|
||||
await newReport();
|
||||
globalCloseForm.submit();
|
||||
}}
|
||||
|
@ -16,7 +16,6 @@ export const load: PageServerLoad = async ({ parent, cookies }) => {
|
||||
(prev, curr) => {
|
||||
return { ...prev, [curr.key]: curr.value };
|
||||
},
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
{} as { [key: string]: any }
|
||||
);
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import { env } from '$env/dynamic/public';
|
||||
export let data: PageData;
|
||||
let settings = structuredClone(data.settings);
|
||||
|
||||
let { data } = $props();
|
||||
|
||||
let settings = $state($state.snapshot(data.settings));
|
||||
|
||||
async function change() {
|
||||
await fetch(`${env.PUBLIC_BASE_PATH}/admin/settings`, {
|
||||
@ -23,7 +25,7 @@
|
||||
} as PageData['settings'])
|
||||
});
|
||||
data.settings = settings;
|
||||
settings = structuredClone(data.settings);
|
||||
settings = $state.snapshot(data.settings);
|
||||
}
|
||||
|
||||
function returnIfNoDup<T>(value: T, original: T): T | undefined {
|
||||
@ -68,7 +70,7 @@
|
||||
<button
|
||||
class="btn btn-success mt-auto"
|
||||
class:btn-disabled={JSON.stringify(data.settings) === JSON.stringify(settings)}
|
||||
on:click={change}>Speichern</button
|
||||
onclick={change}>Speichern</button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,10 +1,9 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import { Check, NoSymbol, PencilSquare, Plus, Trash } from 'svelte-heros-v2';
|
||||
import Input from '$lib/components/Input/Input.svelte';
|
||||
import Select from '$lib/components/Input/Select.svelte';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import type { Report, User } from '$lib/server/database';
|
||||
import type { User } from '$lib/server/database';
|
||||
import { buttonTriggeredRequest } from '$lib/components/utils';
|
||||
import { browser } from '$app/environment';
|
||||
import HeaderBar from './HeaderBar.svelte';
|
||||
@ -13,11 +12,9 @@
|
||||
import NewUserModal from './NewUserModal.svelte';
|
||||
import PaginationTableBody from '$lib/components/PaginationTable/PaginationTableBody.svelte';
|
||||
|
||||
export let data: PageData;
|
||||
|
||||
let users: (typeof User.prototype.dataValues)[] = [];
|
||||
let users: (typeof User.prototype.dataValues)[] = $state([]);
|
||||
let usersPerRequest = 25;
|
||||
let userFilter: { [k: string]: any } = { name: null, playertype: null };
|
||||
let userFilter: { [k: string]: any } = $state({ name: null, playertype: null });
|
||||
|
||||
let userTableContainerElement: HTMLDivElement;
|
||||
let newUserModal: HTMLDialogElement;
|
||||
@ -64,18 +61,16 @@
|
||||
users = users;
|
||||
}
|
||||
}
|
||||
|
||||
$: if (userFilter) fetchUsers({ from: 0 }).then((u) => (users = u));
|
||||
</script>
|
||||
|
||||
<div class="h-full flex flex-col overflow-hidden">
|
||||
<div class="grid grid-cols-[10fr_1fr_10fr_1fr_10fr]">
|
||||
<div />
|
||||
<div />
|
||||
<HeaderBar bind:userFilter />
|
||||
<div class="divider divider-horizontal my-auto h-3/4" />
|
||||
<div></div>
|
||||
<div></div>
|
||||
<HeaderBar bind:userFilter onUpdate={() => fetchUsers({ from: 0 }).then((u) => (users = u))} />
|
||||
<div class="divider divider-horizontal my-auto h-3/4"></div>
|
||||
<div class="flex items-center">
|
||||
<button class="btn" on:click={() => newUserModal.show()}>
|
||||
<button class="btn" onclick={() => newUserModal.show()}>
|
||||
<Plus />
|
||||
<span>Neuer Spieler</span>
|
||||
</button>
|
||||
@ -87,16 +82,16 @@
|
||||
<thead>
|
||||
<!-- prettier-ignore -->
|
||||
<SortableTr class="[&>th]:bg-base-100 [&>th]:z-[1] [&>th]:sticky [&>th]:top-0">
|
||||
<th />
|
||||
<SortableTh on:sort={(e) => userFilter = {...userFilter, sort: {key: 'firstname', asc: e.detail.asc}}}>Vorname</SortableTh>
|
||||
<SortableTh on:sort={(e) => userFilter = {...userFilter, sort: {key: 'lastname', asc: e.detail.asc}}}>Nachname</SortableTh>
|
||||
<SortableTh on:sort={(e) => userFilter = {...userFilter, sort: {key: 'birthday', asc: e.detail.asc}}}>Geburtstag</SortableTh>
|
||||
<SortableTh on:sort={(e) => userFilter = {...userFilter, sort: {key: 'telephone', asc: e.detail.asc}}}>Telefon</SortableTh>
|
||||
<SortableTh on:sort={(e) => userFilter = {...userFilter, sort: {key: 'username', asc: e.detail.asc}}}>Username</SortableTh>
|
||||
<SortableTh on:sort={(e) => userFilter = {...userFilter, sort: {key: 'playertype', asc: e.detail.asc}}}>Minecraft Edition</SortableTh>
|
||||
<SortableTh on:sort={(e) => userFilter = {...userFilter, sort: {key: 'password', asc: e.detail.asc}}}>Passwort</SortableTh>
|
||||
<SortableTh on:sort={(e) => userFilter = {...userFilter, sort: {key: 'uuid', asc: e.detail.asc}}}>UUID</SortableTh>
|
||||
<th />
|
||||
<th></th>
|
||||
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'firstname', asc: e.asc}}}>Vorname</SortableTh>
|
||||
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'lastname', asc: e.asc}}}>Nachname</SortableTh>
|
||||
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'birthday', asc: e.asc}}}>Geburtstag</SortableTh>
|
||||
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'telephone', asc: e.asc}}}>Telefon</SortableTh>
|
||||
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'username', asc: e.asc}}}>Username</SortableTh>
|
||||
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'playertype', asc: e.asc}}}>Minecraft Edition</SortableTh>
|
||||
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'password', asc: e.asc}}}>Passwort</SortableTh>
|
||||
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'uuid', asc: e.asc}}}>UUID</SortableTh>
|
||||
<th></th>
|
||||
</SortableTr>
|
||||
</thead>
|
||||
<PaginationTableBody
|
||||
@ -117,7 +112,7 @@
|
||||
<Input
|
||||
type="date"
|
||||
value={new Date(user.birthday).toISOString().split('T')[0]}
|
||||
on:input={(e) => (user.birthday = e.detail.target.valueAsDate.toISOString())}
|
||||
oninput={(e) => (user.birthday = e.currentTarget.valueAsDate.toISOString())}
|
||||
disabled={!user.edit}
|
||||
size="sm"
|
||||
/>
|
||||
@ -146,7 +141,7 @@
|
||||
{#if user.edit}
|
||||
<button
|
||||
class="btn btn-sm btn-square"
|
||||
on:click={async (e) => {
|
||||
onclick={async (e) => {
|
||||
await buttonTriggeredRequest(e, updateUser(user));
|
||||
user.edit = false;
|
||||
}}
|
||||
@ -155,9 +150,9 @@
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-square"
|
||||
on:click={() => {
|
||||
onclick={() => {
|
||||
user.edit = false;
|
||||
user = user.before;
|
||||
users[i] = user.before;
|
||||
}}
|
||||
>
|
||||
<NoSymbol size="18" />
|
||||
@ -165,8 +160,8 @@
|
||||
{:else}
|
||||
<button
|
||||
class="btn btn-sm btn-square"
|
||||
on:click={() => {
|
||||
user.before = structuredClone(user);
|
||||
onclick={() => {
|
||||
user.before = $state.snapshot(user);
|
||||
user.edit = true;
|
||||
}}
|
||||
>
|
||||
@ -174,7 +169,7 @@
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-sm btn-square"
|
||||
on:click={(e) => buttonTriggeredRequest(e, deleteUser(user.id))}
|
||||
onclick={(e) => buttonTriggeredRequest(e, deleteUser(user.id))}
|
||||
>
|
||||
<Trash size="18" />
|
||||
</button>
|
||||
@ -190,8 +185,8 @@
|
||||
|
||||
<dialog class="modal" bind:this={newUserModal}>
|
||||
<NewUserModal
|
||||
on:submit={(e) => {
|
||||
users = [...users, e.detail];
|
||||
onSubmit={(e) => {
|
||||
users = [...users, e];
|
||||
newUserModal.close();
|
||||
}}
|
||||
/>
|
||||
|
@ -3,7 +3,13 @@ import { Permissions } from '$lib/permissions';
|
||||
import { error, type RequestHandler } from '@sveltejs/kit';
|
||||
import { User } from '$lib/server/database';
|
||||
import { type Attributes, Op } from 'sequelize';
|
||||
import { ApiError, getJavaUuid, getNoAuthUuid, RateLimitError, UserNotFoundError } from '$lib/server/minecraft';
|
||||
import {
|
||||
ApiError,
|
||||
getJavaUuid,
|
||||
getNoAuthUuid,
|
||||
RateLimitError,
|
||||
UserNotFoundError
|
||||
} from '$lib/server/minecraft';
|
||||
import { UserAddSchema, UserDeleteSchema, UserEditSchema, UserListSchema } from './schema';
|
||||
|
||||
export const POST = (async ({ request, cookies }) => {
|
||||
|
@ -2,20 +2,34 @@
|
||||
import Select from '$lib/components/Input/Select.svelte';
|
||||
import Input from '$lib/components/Input/Input.svelte';
|
||||
|
||||
export let userFilter: { [k: string]: any } = {
|
||||
name: null,
|
||||
playertype: null
|
||||
};
|
||||
let {
|
||||
userFilter = $bindable({ name: null, playertype: null }),
|
||||
onUpdate
|
||||
}: { userFilter: { [k: string]: any }; onUpdate: () => void } = $props();
|
||||
</script>
|
||||
|
||||
<form class="flex flex-row justify-center items-center space-x-4 my-2 w-full">
|
||||
<div class="w-full">
|
||||
<Input size="sm" placeholder="..." bind:value={userFilter.name} pickyWidth={false}>
|
||||
<span slot="label">Username</span>
|
||||
<Input
|
||||
size="sm"
|
||||
placeholder="..."
|
||||
bind:value={userFilter.name}
|
||||
pickyWidth={false}
|
||||
oninput={onUpdate}
|
||||
>
|
||||
{#snippet label()}
|
||||
<span>Username</span>
|
||||
{/snippet}
|
||||
</Input>
|
||||
</div>
|
||||
<div class="w-full">
|
||||
<Select label="Edition" size="sm" bind:value={userFilter.playertype} pickyWidth={false}>
|
||||
<Select
|
||||
label="Edition"
|
||||
size="sm"
|
||||
bind:value={userFilter.playertype}
|
||||
pickyWidth={false}
|
||||
onChange={onUpdate}
|
||||
>
|
||||
<option value={null}>Alle</option>
|
||||
<option value="java">Java</option>
|
||||
<option value="bedrock">Bedrock</option>
|
||||
|
@ -3,16 +3,33 @@
|
||||
import { env } from '$env/dynamic/public';
|
||||
import Select from '$lib/components/Input/Select.svelte';
|
||||
import { errorMessage } from '$lib/stores';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let {
|
||||
onSubmit
|
||||
}: {
|
||||
onSubmit: ({
|
||||
firstname,
|
||||
lastname,
|
||||
birthday,
|
||||
telephone,
|
||||
username,
|
||||
playertype
|
||||
}: {
|
||||
firstname: string;
|
||||
lastname: string;
|
||||
birthday: string;
|
||||
telephone: string;
|
||||
username: string;
|
||||
playertype: string;
|
||||
}) => void;
|
||||
} = $props();
|
||||
|
||||
let firstname: string;
|
||||
let lastname: string;
|
||||
let birthday: string;
|
||||
let phone: string;
|
||||
let username: string;
|
||||
let playertype = 'java';
|
||||
let firstname: string = $state('');
|
||||
let lastname: string = $state('');
|
||||
let birthday: string = $state('');
|
||||
let phone: string = $state('');
|
||||
let username: string = $state('');
|
||||
let playertype = $state('java');
|
||||
|
||||
async function newUser() {
|
||||
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/users`, {
|
||||
@ -27,7 +44,7 @@
|
||||
})
|
||||
});
|
||||
if (response.ok) {
|
||||
dispatch('submit', {
|
||||
onSubmit({
|
||||
firstname: firstname,
|
||||
lastname: lastname,
|
||||
birthday: birthday,
|
||||
@ -50,24 +67,37 @@
|
||||
<form method="dialog" class="modal-box" bind:this={reportForm}>
|
||||
<button
|
||||
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
||||
on:click|preventDefault={() => globalCloseForm.submit()}>✕</button
|
||||
onclick={(e) => {
|
||||
e.preventDefault();
|
||||
globalCloseForm.submit();
|
||||
}}>✕</button
|
||||
>
|
||||
<h3 class="font-roboto text-3xl">Neuer Spieler</h3>
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<Input type="text" required bind:value={firstname}>
|
||||
<span slot="label">Vorname</span>
|
||||
{#snippet label()}
|
||||
<span>Vorname</span>
|
||||
{/snippet}
|
||||
</Input>
|
||||
<Input type="text" required bind:value={lastname}>
|
||||
<span slot="label">Nachname</span>
|
||||
{#snippet label()}
|
||||
<span>Nachname</span>
|
||||
{/snippet}
|
||||
</Input>
|
||||
<Input type="date" required bind:value={birthday}>
|
||||
<span slot="label">Geburtstag</span>
|
||||
{#snippet label()}
|
||||
<span>Geburtstag</span>
|
||||
{/snippet}
|
||||
</Input>
|
||||
<Input type="tel" bind:value={phone}>
|
||||
<span slot="label">Telefonnummer</span>
|
||||
{#snippet label()}
|
||||
<span>Telefonnummer</span>
|
||||
{/snippet}
|
||||
</Input>
|
||||
<Input type="text" required bind:value={username}>
|
||||
<span slot="label">Minecraft-Spielername</span>
|
||||
{#snippet label()}
|
||||
<span>Minecraft-Spielername</span>
|
||||
{/snippet}
|
||||
</Input>
|
||||
<Select required label="Edition" bind:value={playertype}>
|
||||
<option value="java">Java Edition</option>
|
||||
@ -78,9 +108,9 @@
|
||||
<Input
|
||||
type="submit"
|
||||
value="Hinzufügen"
|
||||
on:click={(e) => {
|
||||
onclick={(e) => {
|
||||
if (reportForm.checkValidity()) {
|
||||
e.detail.preventDefault();
|
||||
e.preventDefault();
|
||||
confirmDialog.show();
|
||||
}
|
||||
}}
|
||||
@ -88,8 +118,8 @@
|
||||
<Input
|
||||
type="submit"
|
||||
value="Abbrechen"
|
||||
on:click={(e) => {
|
||||
e.detail.preventDefault();
|
||||
onclick={(e) => {
|
||||
e.preventDefault();
|
||||
globalCloseForm.submit();
|
||||
}}
|
||||
/>
|
||||
@ -104,7 +134,7 @@
|
||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||
<h3 class="font-roboto text-xl mb-2">Spieler hinzufügen?</h3>
|
||||
<div class="flex flex-row space-x-2 mt-6">
|
||||
<Input type="submit" value="Hinzufügen" on:click={newUser} />
|
||||
<Input type="submit" value="Hinzufügen" onclick={newUser} />
|
||||
<Input type="submit" value="Abbrechen" />
|
||||
</div>
|
||||
</form>
|
||||
|
Reference in New Issue
Block a user