This commit is contained in:
parent
abffa440a1
commit
95968148a6
@ -1,13 +0,0 @@
|
|||||||
.DS_Store
|
|
||||||
node_modules
|
|
||||||
/build
|
|
||||||
/.svelte-kit
|
|
||||||
/package
|
|
||||||
.env
|
|
||||||
.env.*
|
|
||||||
!.env.example
|
|
||||||
|
|
||||||
# Ignore files for PNPM, NPM and YARN
|
|
||||||
pnpm-lock.yaml
|
|
||||||
package-lock.json
|
|
||||||
yarn.lock
|
|
@ -1,30 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
root: true,
|
|
||||||
extends: [
|
|
||||||
'eslint:recommended',
|
|
||||||
'plugin:@typescript-eslint/recommended',
|
|
||||||
'plugin:svelte/recommended',
|
|
||||||
'prettier'
|
|
||||||
],
|
|
||||||
parser: '@typescript-eslint/parser',
|
|
||||||
plugins: ['@typescript-eslint'],
|
|
||||||
parserOptions: {
|
|
||||||
sourceType: 'module',
|
|
||||||
ecmaVersion: 2020,
|
|
||||||
extraFileExtensions: ['.svelte']
|
|
||||||
},
|
|
||||||
env: {
|
|
||||||
browser: true,
|
|
||||||
es2017: true,
|
|
||||||
node: true
|
|
||||||
},
|
|
||||||
overrides: [
|
|
||||||
{
|
|
||||||
files: ['*.svelte'],
|
|
||||||
parser: 'svelte-eslint-parser',
|
|
||||||
parserOptions: {
|
|
||||||
parser: '@typescript-eslint/parser'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
};
|
|
40
eslint.config.mjs
Normal file
40
eslint.config.mjs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import prettier from 'eslint-config-prettier';
|
||||||
|
import js from '@eslint/js';
|
||||||
|
import svelte from 'eslint-plugin-svelte';
|
||||||
|
import globals from 'globals';
|
||||||
|
import ts from 'typescript-eslint';
|
||||||
|
|
||||||
|
export default ts.config(
|
||||||
|
js.configs.recommended,
|
||||||
|
...ts.configs.recommended,
|
||||||
|
...svelte.configs['flat/recommended'],
|
||||||
|
prettier,
|
||||||
|
...svelte.configs['flat/prettier'],
|
||||||
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
...globals.node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
files: ['**/*.svelte'],
|
||||||
|
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
parser: ts.parser
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ignores: ['build/', '.svelte-kit/', 'dist/']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
// expressions are often used to check if a props function is defined before calling it
|
||||||
|
'@typescript-eslint/no-unused-expressions': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
2746
package-lock.json
generated
2746
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
43
package.json
43
package.json
@ -15,33 +15,36 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@fontsource/nunito": "^5.1.0",
|
"@fontsource/nunito": "^5.1.0",
|
||||||
"@fontsource/roboto": "^5.1.0",
|
"@fontsource/roboto": "^5.1.0",
|
||||||
"@sveltejs/adapter-node": "^5.2.8",
|
"@sveltejs/adapter-node": "^5.2.9",
|
||||||
"@sveltejs/kit": "^2.7.1",
|
"@sveltejs/kit": "^2.9.0",
|
||||||
|
"@sveltejs/vite-plugin-svelte": "^5.0.1",
|
||||||
"@types/bcrypt": "^5.0.2",
|
"@types/bcrypt": "^5.0.2",
|
||||||
"@types/node": "^22.7.7",
|
"@types/node": "^22.10.1",
|
||||||
"@types/validator": "^13.12.2",
|
"@types/validator": "^13.12.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^8.10.0",
|
|
||||||
"@typescript-eslint/parser": "^8.10.0",
|
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"daisyui": "^4.12.13",
|
"daisyui": "^4.12.14",
|
||||||
"eslint": "^9.13.0",
|
"eslint": "^9.16.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-plugin-svelte": "^2.45.1",
|
"eslint-plugin-svelte": "^2.46.1",
|
||||||
"postcss": "^8.4.47",
|
"globals": "^15.13.0",
|
||||||
"prettier": "^3.3.3",
|
"postcss": "^8.4.49",
|
||||||
"prettier-plugin-svelte": "^3.2.7",
|
"prettier": "^3.4.1",
|
||||||
"sass": "^1.80.3",
|
"prettier-plugin-svelte": "^3.3.2",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.6.9",
|
||||||
|
"publint": "^0.2.12",
|
||||||
|
"sass": "^1.81.0",
|
||||||
"skinview3d": "^3.1.0",
|
"skinview3d": "^3.1.0",
|
||||||
"svelte": "^4.2.19",
|
"svelte": "^5.3.0",
|
||||||
"svelte-check": "^4.0.5",
|
"svelte-check": "^4.1.0",
|
||||||
"svelte-heros-v2": "^1.3.0",
|
"svelte-heros-v2": "^2.0.1",
|
||||||
"svelte-multicssclass": "^2.1.1",
|
"svelte-multicssclass": "^2.1.1",
|
||||||
"svelte-preprocess": "^6.0.3",
|
"svelte-preprocess": "^6.0.3",
|
||||||
"tailwindcss": "^3.4.14",
|
"tailwindcss": "^3.4.15",
|
||||||
"tslib": "^2.8.0",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^5.6.3",
|
"typescript": "^5.7.2",
|
||||||
"vite": "^5.4.9",
|
"typescript-eslint": "^8.16.0",
|
||||||
"vitest": "^2.1.3",
|
"vite": "^6.0.1",
|
||||||
|
"vitest": "^2.1.6",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
@ -2,10 +2,7 @@
|
|||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
import { env } from '$env/dynamic/public';
|
import { env } from '$env/dynamic/public';
|
||||||
|
|
||||||
// start date in milliseconds. if undefined, start will be Date.now
|
let { start, end }: { start?: number; end: number } = $props();
|
||||||
export let start: number | undefined = undefined;
|
|
||||||
// end date in milliseconds
|
|
||||||
export let end: number;
|
|
||||||
|
|
||||||
let title = `Spielstart ist am ${new Date(env.PUBLIC_START_DATE).toLocaleString('de-DE', {
|
let title = `Spielstart ist am ${new Date(env.PUBLIC_START_DATE).toLocaleString('de-DE', {
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
@ -26,7 +23,7 @@
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
let [days, hours, minutes, seconds] = getUntil();
|
let [days, hours, minutes, seconds] = $state(getUntil());
|
||||||
let intervalId = setInterval(() => {
|
let intervalId = setInterval(() => {
|
||||||
[days, hours, minutes, seconds] = getUntil();
|
[days, hours, minutes, seconds] = getUntil();
|
||||||
if (start) start += 1000;
|
if (start) start += 1000;
|
||||||
@ -41,25 +38,25 @@
|
|||||||
>
|
>
|
||||||
<div class="flex flex-col p-2 bg-gray-200 rounded-box bg-opacity-5 backdrop-blur-sm" {title}>
|
<div class="flex flex-col p-2 bg-gray-200 rounded-box bg-opacity-5 backdrop-blur-sm" {title}>
|
||||||
<span class="countdown font-mono text-3xl sm:text-6xl">
|
<span class="countdown font-mono text-3xl sm:text-6xl">
|
||||||
<span class="m-auto" style="--value:{days};" />
|
<span class="m-auto" style="--value:{days};"></span>
|
||||||
</span>
|
</span>
|
||||||
Tage
|
Tage
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col p-2 bg-gray-200 rounded-box bg-opacity-5 backdrop-blur-sm" {title}>
|
<div class="flex flex-col p-2 bg-gray-200 rounded-box bg-opacity-5 backdrop-blur-sm" {title}>
|
||||||
<span class="countdown font-mono text-3xl sm:text-6xl">
|
<span class="countdown font-mono text-3xl sm:text-6xl">
|
||||||
<span class="m-auto" style="--value:{hours};" />
|
<span class="m-auto" style="--value:{hours};"></span>
|
||||||
</span>
|
</span>
|
||||||
Stunden
|
Stunden
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col p-2 bg-gray-200 rounded-box bg-opacity-5 backdrop-blur-sm" {title}>
|
<div class="flex flex-col p-2 bg-gray-200 rounded-box bg-opacity-5 backdrop-blur-sm" {title}>
|
||||||
<span class="countdown font-mono text-3xl sm:text-6xl">
|
<span class="countdown font-mono text-3xl sm:text-6xl">
|
||||||
<span class="m-auto" style="--value:{minutes};" />
|
<span class="m-auto" style="--value:{minutes};"></span>
|
||||||
</span>
|
</span>
|
||||||
Minuten
|
Minuten
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col p-2 bg-gray-200 rounded-box bg-opacity-5 backdrop-blur-sm" {title}>
|
<div class="flex flex-col p-2 bg-gray-200 rounded-box bg-opacity-5 backdrop-blur-sm" {title}>
|
||||||
<span class="countdown font-mono text-3xl sm:text-6xl">
|
<span class="countdown font-mono text-3xl sm:text-6xl">
|
||||||
<span class="m-auto" style="--value:{seconds};" />
|
<span class="m-auto" style="--value:{seconds};"></span>
|
||||||
</span>
|
</span>
|
||||||
Sekunden
|
Sekunden
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let size = '24';
|
let { size = '24', fill = 'currentColor' } = $props();
|
||||||
export let fill = 'currentColor';
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height={size} width={size} {fill} viewBox="0 0 512 512"
|
<svg xmlns="http://www.w3.org/2000/svg" height={size} width={size} {fill} viewBox="0 0 512 512"
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export let size = '24';
|
let { size = '24', fill = 'currentColor' } = $props();
|
||||||
export let fill = 'currentColor';
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" height={size} width={size} {fill} viewBox="0 0 512 512"
|
<svg xmlns="http://www.w3.org/2000/svg" height={size} width={size} {fill} viewBox="0 0 512 512"
|
||||||
|
@ -1,12 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
// eslint-disable-next-line no-undef
|
let {
|
||||||
type T = $$Generic;
|
id,
|
||||||
|
name,
|
||||||
export let id: string | null = null;
|
disabled = false,
|
||||||
export let name: string | null = null;
|
available = {},
|
||||||
export let disabled = false;
|
value = $bindable([])
|
||||||
export let available: string[] | { [key: string]: T } = {};
|
}: {
|
||||||
export let value: T[] = [];
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
disabled?: boolean;
|
||||||
|
available?: string[] | { [key: string]: any };
|
||||||
|
value: any[];
|
||||||
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
@ -15,7 +20,7 @@
|
|||||||
{name}
|
{name}
|
||||||
class="select select-bordered select-xs"
|
class="select select-bordered select-xs"
|
||||||
disabled={disabled || available.length === 0}
|
disabled={disabled || available.length === 0}
|
||||||
on:change={(e) => {
|
onchange={(e) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
value.push(Object.values(available)[Object.keys(available).indexOf(e.target.value)]);
|
value.push(Object.values(available)[Object.keys(available).indexOf(e.target.value)]);
|
||||||
@ -42,7 +47,7 @@
|
|||||||
<button
|
<button
|
||||||
{disabled}
|
{disabled}
|
||||||
class:pointer-events-none={disabled}
|
class:pointer-events-none={disabled}
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
value.splice(i, 1);
|
value.splice(i, 1);
|
||||||
value = value;
|
value = value;
|
||||||
}}>✕</button
|
}}>✕</button
|
||||||
|
@ -1,30 +1,46 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Eye, EyeSlash } from 'svelte-heros-v2';
|
import { Eye, EyeSlash } from 'svelte-heros-v2';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
export let id: string | null = null;
|
let {
|
||||||
export let name: string | null = null;
|
label,
|
||||||
export let type = 'text';
|
notice,
|
||||||
export let value: string | null = null;
|
id,
|
||||||
export let placeholder: string | null = null;
|
name,
|
||||||
export let pattern: RegExp | null = null;
|
type = 'text',
|
||||||
export let required = false;
|
value = $bindable(),
|
||||||
export let disabled = false;
|
placeholder,
|
||||||
export let readonly = false;
|
pattern,
|
||||||
export let checked = false;
|
required = false,
|
||||||
export let size: 'xs' | 'sm' | 'md' | 'lg' = 'md';
|
disabled = false,
|
||||||
export let pickyWidth = true;
|
readonly = false,
|
||||||
export let containerClass = '';
|
checked = false,
|
||||||
|
size = 'md',
|
||||||
export let inputElement: HTMLInputElement | undefined = undefined;
|
pickyWidth = true,
|
||||||
|
containerClass = '',
|
||||||
const dispatch = createEventDispatcher();
|
inputElement = $bindable(),
|
||||||
function input(e: Event & { currentTarget: EventTarget & HTMLInputElement }) {
|
oninput,
|
||||||
dispatch('input', e);
|
onclick
|
||||||
}
|
}: {
|
||||||
function click(e: Event) {
|
label?: Snippet;
|
||||||
dispatch('click', e);
|
notice?: Snippet;
|
||||||
}
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
type?: string;
|
||||||
|
value?: string;
|
||||||
|
placeholder?: string;
|
||||||
|
pattern?: RegExp;
|
||||||
|
required?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
readonly?: boolean;
|
||||||
|
checked?: boolean;
|
||||||
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
||||||
|
pickyWidth?: boolean;
|
||||||
|
containerClass?: string;
|
||||||
|
inputElement?: HTMLInputElement;
|
||||||
|
oninput?: (e: Event & { currentTarget: EventTarget & HTMLInputElement }) => void;
|
||||||
|
onclick?: (e: Event) => void;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
let initialType = type;
|
let initialType = type;
|
||||||
|
|
||||||
@ -50,15 +66,15 @@
|
|||||||
{disabled}
|
{disabled}
|
||||||
bind:value
|
bind:value
|
||||||
bind:this={inputElement}
|
bind:this={inputElement}
|
||||||
on:input={input}
|
{oninput}
|
||||||
on:click={click}
|
{onclick}
|
||||||
/>
|
/>
|
||||||
{:else}
|
{:else}
|
||||||
<div>
|
<div>
|
||||||
{#if $$slots.label}
|
{#if label}
|
||||||
<label class="label" for={id}>
|
<label class="label" for={id}>
|
||||||
<span class="label-text">
|
<span class="label-text">
|
||||||
<slot name="label" />
|
{@render label()}
|
||||||
{#if required}
|
{#if required}
|
||||||
<span class="text-red-700">*</span>
|
<span class="text-red-700">*</span>
|
||||||
{/if}
|
{/if}
|
||||||
@ -93,31 +109,23 @@
|
|||||||
{readonly}
|
{readonly}
|
||||||
bind:this={inputElement}
|
bind:this={inputElement}
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
on:input={(e) => {
|
oninput={(e: Event & { currentTarget: EventTarget & HTMLInputElement }) => {
|
||||||
value = e.target?.value;
|
value = e.currentTarget.value;
|
||||||
if (pattern && !pattern.test(e.target?.value)) {
|
if (pattern && !pattern.test(value)) return;
|
||||||
if (inputElement?.value.endsWith(e.data)) {
|
oninput && oninput(e);
|
||||||
value = e.target?.value.substring(0, e.target?.value.length - e.data.length);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return input(e);
|
|
||||||
}}
|
}}
|
||||||
on:paste={(e) => {
|
onpaste={(e) => {
|
||||||
if (
|
if (pattern && e.clipboardData && !pattern.test(e.clipboardData.getData('text'))) {
|
||||||
pattern &&
|
|
||||||
!pattern.test((e.clipboardData || window.clipboardData).getData('text'))
|
|
||||||
) {
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
on:click={click}
|
{onclick}
|
||||||
/>
|
/>
|
||||||
{#if initialType === 'password'}
|
{#if initialType === 'password'}
|
||||||
<button
|
<button
|
||||||
class="absolute right-3"
|
class="absolute right-3"
|
||||||
type="button"
|
type="button"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
type = type === 'password' ? 'text' : 'password';
|
type = type === 'password' ? 'text' : 'password';
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -129,9 +137,11 @@
|
|||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if $$slots.notice}
|
{#if notice}
|
||||||
<label class="label" for={id}>
|
<label class="label" for={id}>
|
||||||
<span class="label-text-alt"><slot name="notice" /></span>
|
<span class="label-text-alt">
|
||||||
|
{@render notice()}
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,23 +1,34 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
let {
|
||||||
|
id,
|
||||||
|
value = $bindable(),
|
||||||
|
inputValue = $bindable(),
|
||||||
|
suggestionRequired = false,
|
||||||
|
emptyAllowed = false,
|
||||||
|
searchSuggestionFunc = () => Promise.resolve([]),
|
||||||
|
invalidMessage,
|
||||||
|
size = 'md',
|
||||||
|
label,
|
||||||
|
required = false,
|
||||||
|
onsubmit
|
||||||
|
}: {
|
||||||
|
id?: string;
|
||||||
|
value?: string;
|
||||||
|
inputValue?: string;
|
||||||
|
suggestionRequired?: boolean;
|
||||||
|
emptyAllowed?: boolean;
|
||||||
|
searchSuggestionFunc?: (input: string) => Promise<{ name: string; value: string }[]>;
|
||||||
|
invalidMessage?: string;
|
||||||
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
||||||
|
label?: string;
|
||||||
|
required?: boolean;
|
||||||
|
onsubmit?: (event: Event & { input: string; value: string }) => void;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
export let id: string | null = null;
|
let searchSuggestions: { name: string; value: string }[] = $state([]);
|
||||||
export let value = '';
|
$effect(() => {
|
||||||
export let inputValue = '';
|
if (!suggestionRequired) value = inputValue;
|
||||||
export let suggestionRequired = false;
|
});
|
||||||
export let emptyAllowed = false;
|
|
||||||
export let searchSuggestionFunc: (
|
|
||||||
input: string
|
|
||||||
) => Promise<{ name: string; value: string }[]> = () => Promise.resolve([]);
|
|
||||||
export let invalidMessage: string | null = null;
|
|
||||||
export let size: 'xs' | 'sm' | 'md' | 'lg' = 'md';
|
|
||||||
export let label: string | null = null;
|
|
||||||
export let required = false;
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
let searchSuggestions: { name: string; value: string }[] = [];
|
|
||||||
$: if (!suggestionRequired) value = inputValue;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="relative">
|
<div class="relative">
|
||||||
@ -42,7 +53,7 @@
|
|||||||
{id}
|
{id}
|
||||||
{required}
|
{required}
|
||||||
bind:value={inputValue}
|
bind:value={inputValue}
|
||||||
on:input={(e) => {
|
oninput={(e: Event & { currentTarget: EventTarget & HTMLInputElement }) => {
|
||||||
value = '';
|
value = '';
|
||||||
searchSuggestionFunc(inputValue).then((v) => {
|
searchSuggestionFunc(inputValue).then((v) => {
|
||||||
searchSuggestions = v;
|
searchSuggestions = v;
|
||||||
@ -51,15 +62,15 @@
|
|||||||
inputValue = searchSuggestion.name;
|
inputValue = searchSuggestion.name;
|
||||||
value = searchSuggestion.value;
|
value = searchSuggestion.value;
|
||||||
searchSuggestions = [];
|
searchSuggestions = [];
|
||||||
e.target?.setCustomValidity('');
|
e.currentTarget.setCustomValidity('');
|
||||||
dispatch('submit', { input: inputValue, value: value });
|
onsubmit && onsubmit(Object.assign(e, { input: inputValue, value: value }));
|
||||||
} else if (inputValue === '' && emptyAllowed) {
|
} else if (inputValue === '' && emptyAllowed) {
|
||||||
dispatch('submit', { input: '', value: '' });
|
onsubmit && onsubmit(Object.assign(e, { input: '', value: '' }));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
on:invalid={(e) => {
|
oninvalid={(e: Event & { currentTarget: EventTarget & HTMLInputElement }) => {
|
||||||
if (invalidMessage != null) e.target?.setCustomValidity(invalidMessage);
|
invalidMessage && e.currentTarget.setCustomValidity(invalidMessage);
|
||||||
}}
|
}}
|
||||||
pattern={suggestionRequired
|
pattern={suggestionRequired
|
||||||
? `${value ? inputValue : 'a^' + (emptyAllowed ? '|$^' : '')}`
|
? `${value ? inputValue : 'a^' + (emptyAllowed ? '|$^' : '')}`
|
||||||
@ -74,11 +85,11 @@
|
|||||||
<button
|
<button
|
||||||
class="block w-full overflow-hidden text-ellipsis whitespace-nowrap"
|
class="block w-full overflow-hidden text-ellipsis whitespace-nowrap"
|
||||||
title="{searchSuggestion.name} ({searchSuggestion.value})"
|
title="{searchSuggestion.name} ({searchSuggestion.value})"
|
||||||
on:click|preventDefault={() => {
|
onclick={(e) => {
|
||||||
inputValue = searchSuggestion.name;
|
inputValue = searchSuggestion.name;
|
||||||
value = searchSuggestion.value;
|
value = searchSuggestion.value;
|
||||||
searchSuggestions = [];
|
searchSuggestions = [];
|
||||||
dispatch('submit', { input: inputValue, value: value });
|
onsubmit && onsubmit(Object.assign(e, { input: inputValue, value: value }));
|
||||||
}}>{searchSuggestion.name}</button
|
}}>{searchSuggestion.name}</button
|
||||||
>
|
>
|
||||||
</li>
|
</li>
|
||||||
@ -90,7 +101,8 @@
|
|||||||
<!-- close the search suggestions box when clicking outside -->
|
<!-- close the search suggestions box when clicking outside -->
|
||||||
{#if inputValue && searchSuggestions.length !== 0}
|
{#if inputValue && searchSuggestions.length !== 0}
|
||||||
<button
|
<button
|
||||||
|
aria-label=" "
|
||||||
class="absolute top-0 left-0 z-10 w-full h-full cursor-default"
|
class="absolute top-0 left-0 z-10 w-full h-full cursor-default"
|
||||||
on:click={() => (searchSuggestions = [])}
|
onclick={() => (searchSuggestions = [])}
|
||||||
/>
|
></button>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,20 +1,31 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
// eslint-disable-next-line no-undef
|
import type { Snippet } from 'svelte';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
|
|
||||||
type T = $$Generic;
|
let {
|
||||||
|
children,
|
||||||
export let id: string | null = null;
|
id,
|
||||||
export let name: string | null = null;
|
name,
|
||||||
export let value: T | null = null;
|
value = $bindable(),
|
||||||
export let label: string | null = null;
|
label,
|
||||||
export let notice: string | null = null;
|
notice,
|
||||||
export let required = false;
|
required = false,
|
||||||
export let disabled = false;
|
disabled = false,
|
||||||
export let size: 'xs' | 'sm' | 'md' | 'lg' = 'md';
|
size = 'md',
|
||||||
export let pickyWidth = true;
|
pickyWidth = true,
|
||||||
|
onChange
|
||||||
let dispatch = createEventDispatcher();
|
}: {
|
||||||
|
children: Snippet;
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
value?: any;
|
||||||
|
label?: string;
|
||||||
|
notice?: string;
|
||||||
|
required?: boolean;
|
||||||
|
disabled?: boolean;
|
||||||
|
size?: 'xs' | 'sm' | 'md' | 'lg';
|
||||||
|
pickyWidth?: boolean;
|
||||||
|
onChange?: ({ value }: { value: any }) => void;
|
||||||
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@ -40,9 +51,9 @@
|
|||||||
{required}
|
{required}
|
||||||
{disabled}
|
{disabled}
|
||||||
bind:value
|
bind:value
|
||||||
on:change={(e) => dispatch('change', { value: value })}
|
onchange={() => onChange && onChange({ value: value })}
|
||||||
>
|
>
|
||||||
<slot />
|
{@render children()}
|
||||||
</select>
|
</select>
|
||||||
{#if notice}
|
{#if notice}
|
||||||
<label class="label" for={id}>
|
<label class="label" for={id}>
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
{rows}
|
{rows}
|
||||||
bind:value
|
bind:value
|
||||||
on:click={(e) => dispatch('click', e)}
|
on:click={(e) => dispatch('click', e)}
|
||||||
/>
|
></textarea>
|
||||||
{#if notice}
|
{#if notice}
|
||||||
<label class="label" for={id}>
|
<label class="label" for={id}>
|
||||||
<span class="label-text-alt">{notice}</span>
|
<span class="label-text-alt">{notice}</span>
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { onMount, tick } from 'svelte';
|
import { onMount, type Snippet, tick } from 'svelte';
|
||||||
|
|
||||||
export let onUpdate: () => Promise<any> = Promise.resolve;
|
let { children, onUpdate }: { children: Snippet; onUpdate: () => Promise<any> } = $props();
|
||||||
|
|
||||||
let bodyElem: HTMLTableSectionElement;
|
let bodyElem: HTMLTableSectionElement;
|
||||||
|
let intersectionElem: HTMLElement;
|
||||||
|
|
||||||
let intersectionObserver: IntersectionObserver;
|
|
||||||
|
|
||||||
let intersectionElement: HTMLElement;
|
|
||||||
async function getIntersectionElement(): Promise<HTMLElement> {
|
async function getIntersectionElement(): Promise<HTMLElement> {
|
||||||
if (!bodyElem.lastElementChild) {
|
if (!bodyElem.lastElementChild) {
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>((resolve) => {
|
||||||
@ -26,10 +24,10 @@
|
|||||||
await onUpdate();
|
await onUpdate();
|
||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
intersectionObserver = new IntersectionObserver(
|
const intersectionObserver = new IntersectionObserver(
|
||||||
async (entries, observer) => {
|
async (entries, observer) => {
|
||||||
if (entries.filter((e) => e.isIntersecting).length === 0 || !entries) return;
|
if (entries.filter((e) => e.isIntersecting).length === 0 || !entries) return;
|
||||||
observer.unobserve(intersectionElement);
|
observer.unobserve(intersectionElem);
|
||||||
|
|
||||||
const rows = bodyElem.rows.length;
|
const rows = bodyElem.rows.length;
|
||||||
|
|
||||||
@ -37,7 +35,7 @@
|
|||||||
await tick();
|
await tick();
|
||||||
|
|
||||||
if (rows === bodyElem.rows.length) return;
|
if (rows === bodyElem.rows.length) return;
|
||||||
observer.observe((intersectionElement = await getIntersectionElement()));
|
observer.observe((intersectionElem = await getIntersectionElement()));
|
||||||
},
|
},
|
||||||
{ threshold: 0.25 }
|
{ threshold: 0.25 }
|
||||||
);
|
);
|
||||||
@ -51,14 +49,14 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (intersectionElement) intersectionObserver.unobserve(intersectionElement);
|
if (intersectionElem) intersectionObserver.unobserve(intersectionElem);
|
||||||
intersectionObserver.observe((intersectionElement = await getIntersectionElement()));
|
intersectionObserver.observe((intersectionElem = await getIntersectionElement()));
|
||||||
}).observe(bodyElem, { childList: true });
|
}).observe(bodyElem, { childList: true });
|
||||||
|
|
||||||
intersectionObserver.observe((intersectionElement = await getIntersectionElement()));
|
intersectionObserver.observe((intersectionElem = await getIntersectionElement()));
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<tbody bind:this={bodyElem}>
|
<tbody bind:this={bodyElem}>
|
||||||
<slot />
|
{@render children()}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, getContext, onDestroy } from 'svelte';
|
import { getContext, onDestroy, type Snippet } from 'svelte';
|
||||||
import type { Writable } from 'svelte/store';
|
import type { Writable } from 'svelte/store';
|
||||||
import { ChevronDown, ChevronUp } from 'svelte-heros-v2';
|
import { ChevronDown, ChevronUp } from 'svelte-heros-v2';
|
||||||
|
|
||||||
|
let { children, onSort }: { children: Snippet; onSort: ({ asc }: { asc: boolean }) => void } =
|
||||||
|
$props();
|
||||||
|
|
||||||
let id = crypto.randomUUID();
|
let id = crypto.randomUUID();
|
||||||
let asc = false;
|
|
||||||
|
let asc = $state(false);
|
||||||
|
|
||||||
let { ascHeader } = getContext('sortableTr') as { ascHeader: Writable<null | string> };
|
let { ascHeader } = getContext('sortableTr') as { ascHeader: Writable<null | string> };
|
||||||
ascHeader.subscribe((v) => {
|
ascHeader.subscribe((v) => {
|
||||||
if (v !== id) asc = false;
|
if (v !== id) asc = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
let dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
if ($ascHeader === id) $ascHeader = null;
|
if ($ascHeader === id) $ascHeader = null;
|
||||||
});
|
});
|
||||||
@ -20,12 +23,14 @@
|
|||||||
<th>
|
<th>
|
||||||
<button
|
<button
|
||||||
class="flex flex-center"
|
class="flex flex-center"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
dispatch('sort', { asc: (asc = !asc) });
|
onSort({ asc: (asc = !asc) });
|
||||||
$ascHeader = id;
|
$ascHeader = id;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span class="mr-1"><slot /></span>
|
<span class="mr-1">
|
||||||
|
{@render children()}
|
||||||
|
</span>
|
||||||
{#if $ascHeader === id && asc}
|
{#if $ascHeader === id && asc}
|
||||||
<ChevronUp variation="solid" />
|
<ChevronUp variation="solid" />
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { setContext } from 'svelte';
|
import { setContext, type Snippet } from 'svelte';
|
||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
|
|
||||||
|
let { children, ...restProps }: { children: Snippet; [x: string]: unknown } = $props();
|
||||||
|
|
||||||
setContext('sortableTr', {
|
setContext('sortableTr', {
|
||||||
ascHeader: writable(null)
|
ascHeader: writable(null)
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<tr {...$$restProps}>
|
<tr {...restProps}>
|
||||||
<slot />
|
{@render children()}
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -3,15 +3,10 @@
|
|||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import { onDestroy } from 'svelte';
|
import { onDestroy } from 'svelte';
|
||||||
|
|
||||||
export let timeout = 2000;
|
let { children, timeout = 2000, show = false } = $props();
|
||||||
export let show = false;
|
|
||||||
|
|
||||||
export function reset() {
|
let progressValue = $state(100);
|
||||||
progressValue = 1;
|
let intervalClear: ReturnType<typeof setInterval> | undefined = $state();
|
||||||
}
|
|
||||||
|
|
||||||
let progressValue = 100;
|
|
||||||
let intervalClear: ReturnType<typeof setInterval> | undefined;
|
|
||||||
|
|
||||||
function startTimout() {
|
function startTimout() {
|
||||||
intervalClear = setInterval(() => {
|
intervalClear = setInterval(() => {
|
||||||
@ -23,10 +18,11 @@
|
|||||||
}, timeout / 100);
|
}, timeout / 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (show) {
|
$effect(() => {
|
||||||
|
if (!show) return;
|
||||||
progressValue = 0;
|
progressValue = 0;
|
||||||
startTimout();
|
startTimout();
|
||||||
}
|
});
|
||||||
|
|
||||||
onDestroy(() => clearInterval(intervalClear));
|
onDestroy(() => clearInterval(intervalClear));
|
||||||
</script>
|
</script>
|
||||||
@ -36,23 +32,23 @@
|
|||||||
in:fly={{ x: 0, duration: 200 }}
|
in:fly={{ x: 0, duration: 200 }}
|
||||||
out:fly={{ x: 400, duration: 400 }}
|
out:fly={{ x: 400, duration: 400 }}
|
||||||
class="toast"
|
class="toast"
|
||||||
on:mouseenter={() => {
|
onmouseenter={() => {
|
||||||
clearInterval(intervalClear);
|
clearInterval(intervalClear);
|
||||||
progressValue = 1;
|
progressValue = 1;
|
||||||
}}
|
}}
|
||||||
on:mouseleave={startTimout}
|
onmouseleave={startTimout}
|
||||||
role="alert"
|
role="alert"
|
||||||
>
|
>
|
||||||
<div class="alert alert-error border-none relative text-gray-900 overflow-hidden">
|
<div class="alert alert-error border-none relative text-gray-900 overflow-hidden">
|
||||||
<div class="flex gap-2 z-10">
|
<div class="flex gap-2 z-10">
|
||||||
<ExclamationCircle />
|
<ExclamationCircle />
|
||||||
<slot />
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
<progress
|
<progress
|
||||||
class="progress progress-error absolute bottom-0 h-[3px] w-full bg-[rgba(0,0,0,0.6)]"
|
class="progress progress-error absolute bottom-0 h-[3px] w-full bg-[rgba(0,0,0,0.6)]"
|
||||||
value={progressValue}
|
value={progressValue}
|
||||||
max="100"
|
max="100"
|
||||||
/>
|
></progress>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { DataTypes, Op } from 'sequelize';
|
import { DataTypes } from 'sequelize';
|
||||||
import { env } from '$env/dynamic/private';
|
import { env } from '$env/dynamic/private';
|
||||||
import { building, dev } from '$app/environment';
|
import { building, dev } from '$app/environment';
|
||||||
import * as bcrypt from 'bcrypt';
|
import * as bcrypt from 'bcrypt';
|
||||||
@ -53,6 +53,8 @@ export class Report extends Model {
|
|||||||
declare notice: string;
|
declare notice: string;
|
||||||
@Column({ type: DataTypes.STRING })
|
@Column({ type: DataTypes.STRING })
|
||||||
declare statement: string;
|
declare statement: string;
|
||||||
|
@Column({ type: DataTypes.DATE })
|
||||||
|
declare striked_at: Date | null;
|
||||||
@Column({ type: DataTypes.INTEGER, allowNull: false })
|
@Column({ type: DataTypes.INTEGER, allowNull: false })
|
||||||
@ForeignKey(() => User)
|
@ForeignKey(() => User)
|
||||||
declare reporter_id: number;
|
declare reporter_id: number;
|
||||||
@ -66,17 +68,26 @@ export class Report extends Model {
|
|||||||
@ForeignKey(() => StrikeReason)
|
@ForeignKey(() => StrikeReason)
|
||||||
declare strike_reason_id: number | null;
|
declare strike_reason_id: number | null;
|
||||||
|
|
||||||
@BelongsTo(() => User, 'reporter_id')
|
@BelongsTo(() => User, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
foreignKey: 'reporter_id'
|
||||||
|
})
|
||||||
declare reporter: User;
|
declare reporter: User;
|
||||||
@BelongsTo(() => User, 'reported_id')
|
@BelongsTo(() => User, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
foreignKey: 'reported_id'
|
||||||
|
})
|
||||||
declare reported: User;
|
declare reported: User;
|
||||||
@BelongsTo(() => Admin, 'auditor_id')
|
@BelongsTo(() => Admin, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
foreignKey: 'auditor_id'
|
||||||
|
})
|
||||||
declare auditor: Admin;
|
declare auditor: Admin;
|
||||||
@BelongsTo(() => StrikeReason, 'strike_reason_id')
|
@BelongsTo(() => StrikeReason, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
foreignKey: 'strike_reason_id'
|
||||||
|
})
|
||||||
declare strike_reason: StrikeReason;
|
declare strike_reason: StrikeReason;
|
||||||
|
|
||||||
@Column({ type: DataTypes.DATE })
|
|
||||||
declare striked_at: Date | null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Table({ modelName: 'strike_reason', underscored: true, createdAt: false, updatedAt: false })
|
@Table({ modelName: 'strike_reason', underscored: true, createdAt: false, updatedAt: false })
|
||||||
@ -110,7 +121,10 @@ export class Feedback extends Model {
|
|||||||
@ForeignKey(() => User)
|
@ForeignKey(() => User)
|
||||||
declare user_id: number;
|
declare user_id: number;
|
||||||
|
|
||||||
@BelongsTo(() => User, 'user_id')
|
@BelongsTo(() => User, {
|
||||||
|
onDelete: 'CASCADE',
|
||||||
|
foreignKey: 'user_id'
|
||||||
|
})
|
||||||
declare user: User;
|
declare user: User;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +167,6 @@ export class Settings extends Model {
|
|||||||
@Column({
|
@Column({
|
||||||
type: DataTypes.STRING,
|
type: DataTypes.STRING,
|
||||||
allowNull: false,
|
allowNull: false,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
get(this: Settings): any {
|
get(this: Settings): any {
|
||||||
const value = this.getDataValue('value');
|
const value = this.getDataValue('value');
|
||||||
return value != null ? JSON.parse(value) : null;
|
return value != null ? JSON.parse(value) : null;
|
||||||
|
@ -4,7 +4,9 @@
|
|||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
let navPaths = [
|
let { children } = $props();
|
||||||
|
|
||||||
|
let navPaths = $state([
|
||||||
{
|
{
|
||||||
name: 'Startseite',
|
name: 'Startseite',
|
||||||
sprite: `${env.PUBLIC_BASE_PATH}/img/menu-home.png`,
|
sprite: `${env.PUBLIC_BASE_PATH}/img/menu-home.png`,
|
||||||
@ -41,24 +43,24 @@
|
|||||||
href: `${env.PUBLIC_BASE_PATH}/team`,
|
href: `${env.PUBLIC_BASE_PATH}/team`,
|
||||||
active: false
|
active: false
|
||||||
}
|
}
|
||||||
];
|
]);
|
||||||
|
let showMenuPermanent = $state(false);
|
||||||
|
let onAdminPage = $state(false);
|
||||||
|
let isTouch = $state(false);
|
||||||
|
let windowHeight = $state(0);
|
||||||
|
|
||||||
let showMenuPermanent = false;
|
$effect(() => {
|
||||||
|
onAdminPage =
|
||||||
let onAdminPage = false;
|
$page.url.pathname.startsWith(`${env.PUBLIC_BASE_PATH}/admin`) &&
|
||||||
$: onAdminPage =
|
$page.url.pathname !== `${env.PUBLIC_BASE_PATH}/admin/login`;
|
||||||
$page.url.pathname.startsWith(`${env.PUBLIC_BASE_PATH}/admin`) &&
|
});
|
||||||
$page.url.pathname !== `${env.PUBLIC_BASE_PATH}/admin/login`;
|
$effect(() => {
|
||||||
$: {
|
|
||||||
for (let i = 0; i < navPaths.length; i++) {
|
for (let i = 0; i < navPaths.length; i++) {
|
||||||
navPaths[i].active = navPaths[i].href === $page.url.pathname;
|
navPaths[i].active = navPaths[i].href === $page.url.pathname;
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
let isTouch = false;
|
let navElem: HTMLDivElement;
|
||||||
let nav: HTMLDivElement;
|
|
||||||
|
|
||||||
$: windowHeight = 0;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window bind:innerHeight={windowHeight} />
|
<svelte:window bind:innerHeight={windowHeight} />
|
||||||
@ -67,7 +69,7 @@
|
|||||||
on:touchend={(e) => {
|
on:touchend={(e) => {
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
if (isTouch && !nav.contains(e.target)) showMenuPermanent = false;
|
if (isTouch && !navElem.contains(e.target)) showMenuPermanent = false;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
@ -81,20 +83,20 @@
|
|||||||
|
|
||||||
<main>
|
<main>
|
||||||
<div class="min-h-[calc(100vh-3.5rem)] h-full w-full" class:min-h-screen={onAdminPage}>
|
<div class="min-h-[calc(100vh-3.5rem)] h-full w-full" class:min-h-screen={onAdminPage}>
|
||||||
<slot />
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<nav>
|
<nav>
|
||||||
<div
|
<div
|
||||||
class="fixed bottom-4 right-4 sm:left-4 sm:right-[initial] group/menu-bar flex flex-col-reverse justify-center items-center z-50 main-menu"
|
class="fixed bottom-4 right-4 sm:left-4 sm:right-[initial] group/menu-bar flex flex-col-reverse justify-center items-center z-50 main-menu"
|
||||||
class:hidden={onAdminPage}
|
class:hidden={onAdminPage}
|
||||||
bind:this={nav}
|
bind:this={navElem}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
class={isTouch
|
class={isTouch
|
||||||
? 'btn btn-square relative w-16 h-16'
|
? 'btn btn-square relative w-16 h-16'
|
||||||
: 'btn btn-square group/menu-button relative w-16 h-16'}
|
: 'btn btn-square group/menu-button relative w-16 h-16'}
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
if (!isTouch) {
|
if (!isTouch) {
|
||||||
let activePath = navPaths.find((path) => path.active);
|
let activePath = navPaths.find((path) => path.active);
|
||||||
if (activePath !== undefined) {
|
if (activePath !== undefined) {
|
||||||
@ -103,7 +105,7 @@
|
|||||||
showMenuPermanent = !showMenuPermanent;
|
showMenuPermanent = !showMenuPermanent;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
on:touchend={() => {
|
ontouchend={() => {
|
||||||
isTouch = true;
|
isTouch = true;
|
||||||
showMenuPermanent = !showMenuPermanent;
|
showMenuPermanent = !showMenuPermanent;
|
||||||
}}
|
}}
|
||||||
@ -137,13 +139,13 @@
|
|||||||
<a
|
<a
|
||||||
class="btn btn-square border-none group/menu-item relative w-[3.5rem] h-[3.5rem] flex justify-center items-center"
|
class="btn btn-square border-none group/menu-item relative w-[3.5rem] h-[3.5rem] flex justify-center items-center"
|
||||||
href={navPath.href}
|
href={navPath.href}
|
||||||
on:click={() => goto(navPath.href)}
|
onclick={() => goto(navPath.href)}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style="background-image: url('{env.PUBLIC_BASE_PATH}/img/menu-inventory-bar.png'); background-position: -{i *
|
style="background-image: url('{env.PUBLIC_BASE_PATH}/img/menu-inventory-bar.png'); background-position: -{i *
|
||||||
3.5}rem 0;"
|
3.5}rem 0;"
|
||||||
class="block w-full h-full bg-no-repeat bg-horizontal-sprite pixelated"
|
class="block w-full h-full bg-no-repeat bg-horizontal-sprite pixelated"
|
||||||
/>
|
></div>
|
||||||
<div class="absolute flex justify-center items-center w-full h-full">
|
<div class="absolute flex justify-center items-center w-full h-full">
|
||||||
<img class="w-1/2 h-1/2 pixelated" src={navPath.sprite} alt={navPath.name} />
|
<img class="w-1/2 h-1/2 pixelated" src={navPath.sprite} alt={navPath.name} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,7 +4,8 @@
|
|||||||
import { Clock, User, WrenchScrewdriver } from 'svelte-heros-v2';
|
import { Clock, User, WrenchScrewdriver } from 'svelte-heros-v2';
|
||||||
import Crosshairs from '$lib/components/CustomIcons/Crosshairs.svelte';
|
import Crosshairs from '$lib/components/CustomIcons/Crosshairs.svelte';
|
||||||
import Skull from '$lib/components/CustomIcons/Skull.svelte';
|
import Skull from '$lib/components/CustomIcons/Skull.svelte';
|
||||||
import type { PageData } from './$types';
|
|
||||||
|
let { data } = $props();
|
||||||
|
|
||||||
let information = [
|
let information = [
|
||||||
{
|
{
|
||||||
@ -23,8 +24,6 @@
|
|||||||
'Jeder ist willkommen und kann mitspielen. Dazu benötigst Du nur einen Minecraft-Account und schon bist Du Teil unser Community :)'
|
'Jeder ist willkommen und kann mitspielen. Dazu benötigst Du nur einen Minecraft-Account und schon bist Du Teil unser Community :)'
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export let data: PageData;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@ -39,7 +38,7 @@
|
|||||||
<img src="{env.PUBLIC_BASE_PATH}/img/craftattack.webp" alt="Craftattack 7" />
|
<img src="{env.PUBLIC_BASE_PATH}/img/craftattack.webp" alt="Craftattack 7" />
|
||||||
<div class="flex flex-col gap-5 lg:gap-14 w-full mt-2 lg:mt-5 lg:w-10/12 h-full">
|
<div class="flex flex-col gap-5 lg:gap-14 w-full mt-2 lg:mt-5 lg:w-10/12 h-full">
|
||||||
<div>
|
<div>
|
||||||
<div class="divider" />
|
<div class="divider"></div>
|
||||||
<div class="flex flex-col md:flex-row xl:flex-col gap-5">
|
<div class="flex flex-col md:flex-row xl:flex-col gap-5">
|
||||||
{#each information as info}
|
{#each information as info}
|
||||||
<div>
|
<div>
|
||||||
@ -48,7 +47,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<div class="divider" />
|
<div class="divider"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<a
|
<a
|
||||||
|
@ -11,10 +11,11 @@
|
|||||||
} from 'svelte-heros-v2';
|
} from 'svelte-heros-v2';
|
||||||
import { buttonTriggeredRequest } from '$lib/components/utils';
|
import { buttonTriggeredRequest } from '$lib/components/utils';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import type { LayoutData } from './$types';
|
|
||||||
import { adminCount, errorMessage, reportCount, feedbackCount } from '$lib/stores';
|
import { adminCount, errorMessage, reportCount, feedbackCount } from '$lib/stores';
|
||||||
import ErrorToast from '$lib/components/Toast/ErrorToast.svelte';
|
import ErrorToast from '$lib/components/Toast/ErrorToast.svelte';
|
||||||
|
|
||||||
|
let { children, data } = $props();
|
||||||
|
|
||||||
async function logout() {
|
async function logout() {
|
||||||
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/logout`, {
|
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/logout`, {
|
||||||
method: 'POST'
|
method: 'POST'
|
||||||
@ -26,7 +27,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export let data: LayoutData;
|
|
||||||
if (data.reportCount) $reportCount = data.reportCount;
|
if (data.reportCount) $reportCount = data.reportCount;
|
||||||
if (data.feedbackCount) $feedbackCount = data.feedbackCount;
|
if (data.feedbackCount) $feedbackCount = data.feedbackCount;
|
||||||
if (data.adminCount) $adminCount = data.adminCount;
|
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">
|
<ul class="menu menu-horizontal h-10 p-0 flex items-center bg-base-300 rounded-lg">
|
||||||
{#each tabs as tab}
|
{#each tabs as tab}
|
||||||
{#if tab.enabled}
|
{#if tab.enabled}
|
||||||
|
{@const Icon = tab.icon}
|
||||||
<li>
|
<li>
|
||||||
<a href={tab.path}>
|
<a href={tab.path}>
|
||||||
<svelte:component this={tab.icon} />
|
<Icon />
|
||||||
<span class="mr-1" class:underline={$page.url.pathname === tab.path}>{tab.name}</span>
|
<span class="mr-1" class:underline={$page.url.pathname === tab.path}>{tab.name}</span>
|
||||||
{#if tab.badge != null}
|
{#if tab.badge != null}
|
||||||
<div class="badge">{tab.badge}</div>
|
<div class="badge">{tab.badge}</div>
|
||||||
@ -100,7 +101,7 @@
|
|||||||
<div class="absolute top-0 right-0 flex items-center h-full">
|
<div class="absolute top-0 right-0 flex items-center h-full">
|
||||||
<ul class="menu menu-vertical">
|
<ul class="menu menu-vertical">
|
||||||
<li>
|
<li>
|
||||||
<button on:click={(e) => buttonTriggeredRequest(e, logout())}>
|
<button onclick={(e) => buttonTriggeredRequest(e, logout())}>
|
||||||
<ArrowLeftOnRectangle />
|
<ArrowLeftOnRectangle />
|
||||||
<span>Ausloggen</span>
|
<span>Ausloggen</span>
|
||||||
</button>
|
</button>
|
||||||
@ -109,11 +110,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-full w-full -mt-12 pt-12 overflow-y-scroll overflow-x-hidden">
|
<div class="h-full w-full -mt-12 pt-12 overflow-y-scroll overflow-x-hidden">
|
||||||
<slot />
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="h-full w-full">
|
<div class="h-full w-full">
|
||||||
<slot />
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types';
|
|
||||||
import { env } from '$env/dynamic/public';
|
import { env } from '$env/dynamic/public';
|
||||||
import { BookOpen, Cog6Tooth, Flag, UserGroup, Users } from 'svelte-heros-v2';
|
import { BookOpen, Cog6Tooth, Flag, UserGroup, Users } from 'svelte-heros-v2';
|
||||||
|
|
||||||
export let data: PageData;
|
let { data } = $props();
|
||||||
|
|
||||||
let tabs = [
|
let tabs = [
|
||||||
{
|
{
|
||||||
@ -42,13 +41,14 @@
|
|||||||
<div class="flex justify-around items-center h-screen">
|
<div class="flex justify-around items-center h-screen">
|
||||||
{#each tabs as tab}
|
{#each tabs as tab}
|
||||||
{#if tab.enabled}
|
{#if tab.enabled}
|
||||||
|
{@const Icon = tab.icon}
|
||||||
<div class="flex flex-col gap-4 justify-center items-center">
|
<div class="flex flex-col gap-4 justify-center items-center">
|
||||||
<a
|
<a
|
||||||
class="h-48 w-48 border flex justify-center items-center rounded-xl duration-100 hover:bg-base-200"
|
class="h-48 w-48 border flex justify-center items-center rounded-xl duration-100 hover:bg-base-200"
|
||||||
href={tab.path}
|
href={tab.path}
|
||||||
title={tab.name}
|
title={tab.name}
|
||||||
>
|
>
|
||||||
<svelte:component this={tab.icon} width="10rem" height="10rem" />
|
<Icon />
|
||||||
</a>
|
</a>
|
||||||
<span>{tab.name}</span>
|
<span>{tab.name}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types';
|
|
||||||
import Badges from '$lib/components/Input/Badges.svelte';
|
import Badges from '$lib/components/Input/Badges.svelte';
|
||||||
import { Check, NoSymbol, PencilSquare, Trash, UserPlus } from 'svelte-heros-v2';
|
import { Check, NoSymbol, PencilSquare, Trash, UserPlus } from 'svelte-heros-v2';
|
||||||
import Input from '$lib/components/Input/Input.svelte';
|
import Input from '$lib/components/Input/Input.svelte';
|
||||||
import { Permissions } from '$lib/permissions';
|
import { Permissions } from '$lib/permissions';
|
||||||
import { env } from '$env/dynamic/public';
|
import { env } from '$env/dynamic/public';
|
||||||
import ErrorToast from '$lib/components/Toast/ErrorToast.svelte';
|
|
||||||
import { buttonTriggeredRequest } from '$lib/components/utils';
|
import { buttonTriggeredRequest } from '$lib/components/utils';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
import { adminCount } from '$lib/stores';
|
import { adminCount } from '$lib/stores';
|
||||||
|
|
||||||
|
let { data } = $props();
|
||||||
|
|
||||||
|
let admins = $state(data.admins);
|
||||||
|
|
||||||
let allPermissionBadges = {
|
let allPermissionBadges = {
|
||||||
Admin: Permissions.Admin,
|
Admin: Permissions.Admin,
|
||||||
Users: Permissions.Users,
|
Users: Permissions.Users,
|
||||||
@ -19,9 +21,9 @@
|
|||||||
Settings: Permissions.Settings
|
Settings: Permissions.Settings
|
||||||
};
|
};
|
||||||
|
|
||||||
let newAdminUsername: string;
|
let newAdminUsername = $state('');
|
||||||
let newAdminPassword: string;
|
let newAdminPassword = $state('');
|
||||||
let newAdminPermissions: number[];
|
let newAdminPermissions = $state([]);
|
||||||
|
|
||||||
async function addAdmin(username: string, password: string, permissions: Permissions) {
|
async function addAdmin(username: string, password: string, permissions: Permissions) {
|
||||||
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/admin`, {
|
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/admin`, {
|
||||||
@ -36,8 +38,11 @@
|
|||||||
let res = await response.json();
|
let res = await response.json();
|
||||||
$adminCount += 1;
|
$adminCount += 1;
|
||||||
res.permissions = new Permissions(res.permissions).asArray();
|
res.permissions = new Permissions(res.permissions).asArray();
|
||||||
data.admins.push(res);
|
admins.push(res);
|
||||||
data.admins = data.admins;
|
|
||||||
|
newAdminUsername = '';
|
||||||
|
newAdminPassword = '';
|
||||||
|
newAdminPermissions = [];
|
||||||
} else {
|
} else {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
@ -79,35 +84,32 @@
|
|||||||
await goto(`${env.PUBLIC_BASE_PATH}/`);
|
await goto(`${env.PUBLIC_BASE_PATH}/`);
|
||||||
} else {
|
} else {
|
||||||
$adminCount -= 1;
|
$adminCount -= 1;
|
||||||
data.admins.splice(
|
admins.splice(
|
||||||
data.admins.findIndex((v) => v.id == id),
|
admins.findIndex((v) => v.id == id),
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
data.admins = data.admins;
|
admins = admins;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error();
|
throw new Error();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let errorMessage = '';
|
let permissions = $state(new Permissions(data.permissions));
|
||||||
|
|
||||||
export let data: PageData;
|
|
||||||
let permissions = new Permissions(data.permissions);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<table class="table table-zebra w-full">
|
<table class="table table-zebra w-full">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th />
|
<th></th>
|
||||||
<th>Benutzername</th>
|
<th>Benutzername</th>
|
||||||
<th>Passwort</th>
|
<th>Passwort</th>
|
||||||
<th>Berechtigungen</th>
|
<th>Berechtigungen</th>
|
||||||
<th />
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{#each data.admins as admin, i}
|
{#each admins as admin, i}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{i + 1}</td>
|
<td>{i + 1}</td>
|
||||||
<td><Input type="text" bind:value={admin.username} disabled={!admin.edit} size="sm" /></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()}>
|
<span class="w-min" class:cursor-not-allowed={!permissions.admin()}>
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-square"
|
class="btn btn-sm btn-square"
|
||||||
on:click={async (e) => {
|
onclick={async (e) => {
|
||||||
await buttonTriggeredRequest(
|
await buttonTriggeredRequest(
|
||||||
e,
|
e,
|
||||||
updateAdmin(
|
updateAdmin(
|
||||||
@ -153,9 +155,9 @@
|
|||||||
<span class="w-min" class:cursor-not-allowed={!permissions.admin()}>
|
<span class="w-min" class:cursor-not-allowed={!permissions.admin()}>
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-square"
|
class="btn btn-sm btn-square"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
admin.edit = false;
|
admin.edit = false;
|
||||||
admin = admin.before;
|
admins[i] = admin.before;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<NoSymbol size="18" />
|
<NoSymbol size="18" />
|
||||||
@ -165,9 +167,9 @@
|
|||||||
<span class="w-min" class:cursor-not-allowed={!permissions.admin()}>
|
<span class="w-min" class:cursor-not-allowed={!permissions.admin()}>
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-square"
|
class="btn btn-sm btn-square"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
|
admin.before = $state.snapshot(admin);
|
||||||
admin.edit = true;
|
admin.edit = true;
|
||||||
admin.before = structuredClone(admin);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<PencilSquare size="18" />
|
<PencilSquare size="18" />
|
||||||
@ -176,7 +178,7 @@
|
|||||||
<span class="w-min" class:cursor-not-allowed={!permissions.admin()}>
|
<span class="w-min" class:cursor-not-allowed={!permissions.admin()}>
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-square"
|
class="btn btn-sm btn-square"
|
||||||
on:click={(e) => buttonTriggeredRequest(e, deleteAdmin(admin.id))}
|
onclick={(e) => buttonTriggeredRequest(e, deleteAdmin(admin.id))}
|
||||||
>
|
>
|
||||||
<Trash size="18" />
|
<Trash size="18" />
|
||||||
</button>
|
</button>
|
||||||
@ -187,7 +189,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
<tr>
|
<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="text" bind:value={newAdminUsername} size="sm" /></td>
|
||||||
<td><Input type="password" bind:value={newAdminPassword} size="sm" /></td>
|
<td><Input type="password" bind:value={newAdminPassword} size="sm" /></td>
|
||||||
<td><Badges bind:value={newAdminPermissions} available={allPermissionBadges} /></td>
|
<td><Badges bind:value={newAdminPermissions} available={allPermissionBadges} /></td>
|
||||||
@ -196,7 +198,7 @@
|
|||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-square"
|
class="btn btn-sm btn-square"
|
||||||
disabled={!newAdminUsername || !newAdminPassword}
|
disabled={!newAdminUsername || !newAdminPassword}
|
||||||
on:click={async (e) => {
|
onclick={async (e) => {
|
||||||
await buttonTriggeredRequest(
|
await buttonTriggeredRequest(
|
||||||
e,
|
e,
|
||||||
addAdmin(newAdminUsername, newAdminPassword, new Permissions(newAdminPermissions))
|
addAdmin(newAdminUsername, newAdminPassword, new Permissions(newAdminPermissions))
|
||||||
@ -213,7 +215,3 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<ErrorToast show={errorMessage !== ''}>
|
|
||||||
<span />
|
|
||||||
</ErrorToast>
|
|
||||||
|
@ -11,10 +11,10 @@
|
|||||||
import Textarea from '$lib/components/Input/Textarea.svelte';
|
import Textarea from '$lib/components/Input/Textarea.svelte';
|
||||||
import { onDestroy, onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
|
|
||||||
let feedbacks: (typeof Feedback.prototype.dataValues)[] = [];
|
let feedbacks: (typeof Feedback.prototype.dataValues)[] = $state([]);
|
||||||
let feedbacksPerRequest = 25;
|
let feedbacksPerRequest = 25;
|
||||||
let feedbackFilter = { event: null, content: null, username: null };
|
let feedbackFilter = $state({ event: null, content: null, username: null });
|
||||||
let activeFeedback: typeof Feedback.prototype.dataValues | null = null;
|
let activeFeedback: typeof Feedback.prototype.dataValues | null = $state(null);
|
||||||
|
|
||||||
async function fetchFeedback(extendedFilter?: {
|
async function fetchFeedback(extendedFilter?: {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
@ -57,13 +57,14 @@
|
|||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
if (browser) window.removeEventListener('hashchange', openHashReport);
|
if (browser) window.removeEventListener('hashchange', openHashReport);
|
||||||
});
|
});
|
||||||
|
|
||||||
$: if (feedbackFilter) fetchFeedback({ from: 0 }).then((r) => (feedbacks = r));
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="h-full flex flex-row">
|
<div class="h-full flex flex-row">
|
||||||
<div class="w-full flex flex-col overflow-hidden">
|
<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" />
|
<hr class="divider my-1 mx-8 border-none" />
|
||||||
<table class="table table-fixed h-fit">
|
<table class="table table-fixed h-fit">
|
||||||
<thead>
|
<thead>
|
||||||
@ -82,7 +83,7 @@
|
|||||||
<tr
|
<tr
|
||||||
class="hover [&>*]:text-sm cursor-pointer"
|
class="hover [&>*]:text-sm cursor-pointer"
|
||||||
class:bg-base-200={activeFeedback?.url_hash === feedback.url_hash}
|
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}`, {
|
await goto(`${window.location.href.split('#')[0]}#${feedback.url_hash}`, {
|
||||||
replaceState: true
|
replaceState: true
|
||||||
});
|
});
|
||||||
@ -96,8 +97,10 @@
|
|||||||
<button
|
<button
|
||||||
class="pl-1"
|
class="pl-1"
|
||||||
title="Nach Ersteller filtern"
|
title="Nach Ersteller filtern"
|
||||||
on:click|stopPropagation={() =>
|
onclick={(e) => {
|
||||||
(feedbackFilter.username = feedback.user?.username)}
|
e.stopPropagation();
|
||||||
|
feedbackFilter.username = feedback.user.username;
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<MagnifyingGlass size="14" />
|
<MagnifyingGlass size="14" />
|
||||||
</button>
|
</button>
|
||||||
@ -125,18 +128,18 @@
|
|||||||
>
|
>
|
||||||
<div class="absolute right-2 top-2 flex justify-center">
|
<div class="absolute right-2 top-2 flex justify-center">
|
||||||
<form class="dropdown dropdown-end">
|
<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">
|
<label tabindex="0" class="btn btn-sm btn-circle btn-ghost text-center">
|
||||||
<Share size="1rem" />
|
<Share size="1rem" />
|
||||||
</label>
|
</label>
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
||||||
<ul
|
<ul
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-max"
|
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-max"
|
||||||
>
|
>
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard.writeText(
|
||||||
`${window.location.protocol}//${window.location.host}${env.PUBLIC_BASE_PATH}/admin/reports#${activeFeedback.url_hash}`
|
`${window.location.protocol}//${window.location.host}${env.PUBLIC_BASE_PATH}/admin/reports#${activeFeedback.url_hash}`
|
||||||
);
|
);
|
||||||
@ -145,7 +148,7 @@
|
|||||||
Internen Link kopieren
|
Internen Link kopieren
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
on:click={() =>
|
onclick={() =>
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard.writeText(
|
||||||
`${window.location.protocol}//${window.location.host}${env.PUBLIC_BASE_PATH}/report/${activeFeedback.url_hash}`
|
`${window.location.protocol}//${window.location.host}${env.PUBLIC_BASE_PATH}/report/${activeFeedback.url_hash}`
|
||||||
)}>Öffentlichen Link kopieren</button
|
)}>Öffentlichen Link kopieren</button
|
||||||
@ -155,7 +158,7 @@
|
|||||||
</form>
|
</form>
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-circle btn-ghost"
|
class="btn btn-sm btn-circle btn-ghost"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
activeFeedback = null;
|
activeFeedback = null;
|
||||||
goto(window.location.href.split('#')[0], { replaceState: true });
|
goto(window.location.href.split('#')[0], { replaceState: true });
|
||||||
}}>✕</button
|
}}>✕</button
|
||||||
@ -169,7 +172,9 @@
|
|||||||
value={activeFeedback.user?.username || ''}
|
value={activeFeedback.user?.username || ''}
|
||||||
pickyWidth={false}
|
pickyWidth={false}
|
||||||
>
|
>
|
||||||
<span slot="label">Nutzer</span>
|
{#snippet label()}
|
||||||
|
<span>Nutzer</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
<Textarea readonly={true} rows={4} label="Inhalt" value={activeFeedback.content} />
|
<Textarea readonly={true} rows={4} label="Inhalt" value={activeFeedback.content} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -20,7 +20,7 @@ export const POST = (async ({ request, cookies }) => {
|
|||||||
}
|
}
|
||||||
const data = parseResult.data;
|
const data = parseResult.data;
|
||||||
|
|
||||||
let feedbackFindOptions: Attributes<Feedback> = {
|
const feedbackFindOptions: Attributes<Feedback> = {
|
||||||
content: { [Op.not]: null }
|
content: { [Op.not]: null }
|
||||||
};
|
};
|
||||||
if (data.event) Object.assign(feedbackFindOptions, { event: { [Op.like]: `%${data.event}%` } });
|
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 });
|
if (data.hash) Object.assign(feedbackFindOptions, { url_hash: data.hash });
|
||||||
|
|
||||||
let feedback = await Feedback.findAll({
|
const feedback = await Feedback.findAll({
|
||||||
where: feedbackFindOptions,
|
where: feedbackFindOptions,
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
@ -1,21 +1,26 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Input from '$lib/components/Input/Input.svelte';
|
import Input from '$lib/components/Input/Input.svelte';
|
||||||
|
|
||||||
export let feedbackFilter: { [k: string]: any } = {
|
let {
|
||||||
event: null,
|
feedbackFilter = $bindable({ event: null, content: null, username: null }),
|
||||||
content: null,
|
onUpdate
|
||||||
username: null
|
}: { feedbackFilter: { [k: string]: any }; onUpdate: () => void } = $props();
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form class="flex flex-row justify-center space-x-4 mx-4 my-2">
|
<form class="flex flex-row justify-center space-x-4 mx-4 my-2">
|
||||||
<Input size="sm" placeholder="Alle" bind:value={feedbackFilter.username}>
|
<Input size="sm" placeholder="Alle" bind:value={feedbackFilter.username} oninput={onUpdate}>
|
||||||
<span slot="label">Nutzer</span>
|
{#snippet label()}
|
||||||
|
<span>Nutzer</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
<Input size="sm" placeholder="Alle" bind:value={feedbackFilter.event}>
|
<Input size="sm" placeholder="Alle" bind:value={feedbackFilter.event} oninput={onUpdate}>
|
||||||
<span slot="label">Event</span>
|
{#snippet label()}
|
||||||
|
<span>Event</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
<Input size="sm" placeholder="Alle" bind:value={feedbackFilter.content}>
|
<Input size="sm" placeholder="Alle" bind:value={feedbackFilter.content} oninput={onUpdate}>
|
||||||
<span slot="label">Inhalt</span>
|
{#snippet label()}
|
||||||
|
<span>Inhalt</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
</form>
|
</form>
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
import { env } from '$env/dynamic/public';
|
import { env } from '$env/dynamic/public';
|
||||||
import { errorMessage } from '$lib/stores';
|
import { errorMessage } from '$lib/stores';
|
||||||
|
|
||||||
let passwordValue: string;
|
let password = $state('');
|
||||||
|
|
||||||
async function login() {
|
async function login() {
|
||||||
// eslint-disable-next-line no-async-promise-executor
|
// eslint-disable-next-line no-async-promise-executor
|
||||||
loginRequest = new Promise(async (resolve) => {
|
loginRequest = new Promise(async (resolve) => {
|
||||||
@ -12,10 +13,10 @@
|
|||||||
body: JSON.stringify(Object.fromEntries(new FormData(document.forms[0])))
|
body: JSON.stringify(Object.fromEntries(new FormData(document.forms[0])))
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
window.location = `${env.PUBLIC_BASE_PATH}/admin`;
|
window.location.href = `${env.PUBLIC_BASE_PATH}/admin`;
|
||||||
resolve();
|
resolve();
|
||||||
} else if (response.status == 401) {
|
} else if (response.status == 401) {
|
||||||
passwordValue = '';
|
password = '';
|
||||||
$errorMessage = 'Nutzername oder Passwort falsch';
|
$errorMessage = 'Nutzername oder Passwort falsch';
|
||||||
resolve();
|
resolve();
|
||||||
} else {
|
} else {
|
||||||
@ -25,25 +26,29 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let loginRequest: Promise<void> | null = null;
|
let loginRequest: Promise<void> | null = $state(null);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="card px-14 py-6 shadow-lg">
|
<div class="card px-14 py-6 shadow-lg">
|
||||||
<h1 class="text-center text-4xl mt-2 mb-4">Craftattack Admin Login</h1>
|
<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="flex flex-col justify-center items-center">
|
||||||
<div class="grid gap-4">
|
<div class="grid gap-4">
|
||||||
<Input id="username" name="username" type="text" required={true}>
|
<Input id="username" name="username" type="text" required={true}>
|
||||||
<span slot="label">Nutzername</span>
|
{#snippet label()}
|
||||||
|
<span>Nutzername</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
<Input
|
<Input id="password" name="password" type="password" required={true} bind:value={password}>
|
||||||
id="password"
|
{#snippet label()}
|
||||||
name="password"
|
<span>Passwort</span>
|
||||||
type="password"
|
{/snippet}
|
||||||
required={true}
|
|
||||||
bind:value={passwordValue}
|
|
||||||
>
|
|
||||||
<span slot="label">Passwort</span>
|
|
||||||
</Input>
|
</Input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -56,22 +61,7 @@
|
|||||||
{#await loginRequest}
|
{#await loginRequest}
|
||||||
<span
|
<span
|
||||||
class="relative top-[calc(50%-12px)] left-[calc(50%-12px)] row-[1] col-[1] loading loading-ring"
|
class="relative top-[calc(50%-12px)] left-[calc(50%-12px)] row-[1] col-[1] loading loading-ring"
|
||||||
/>
|
></span>
|
||||||
{: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>
|
|
||||||
{/await}
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import type { PageData } from './$types';
|
|
||||||
import type { Report } from '$lib/server/database';
|
import type { Report } from '$lib/server/database';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import { env } from '$env/dynamic/public';
|
import { env } from '$env/dynamic/public';
|
||||||
@ -18,12 +17,12 @@
|
|||||||
import { usernameSuggestions } from '$lib/utils';
|
import { usernameSuggestions } from '$lib/utils';
|
||||||
import PaginationTableBody from '$lib/components/PaginationTable/PaginationTableBody.svelte';
|
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 reportsPerRequest = 25;
|
||||||
let reportFilter = { draft: false, status: null, reporter: null, reported: null };
|
let reportFilter = $state({ draft: false, status: null, reporter: null, reported: null });
|
||||||
let activeReport: typeof Report.prototype.dataValues | null = null;
|
let activeReport: typeof Report.prototype.dataValues | null = $state(null);
|
||||||
|
|
||||||
async function fetchReports(extendedFilter?: {
|
async function fetchReports(extendedFilter?: {
|
||||||
hash?: string;
|
hash?: string;
|
||||||
@ -94,19 +93,20 @@
|
|||||||
|
|
||||||
let saveActiveReportChangesModal: HTMLDialogElement;
|
let saveActiveReportChangesModal: HTMLDialogElement;
|
||||||
let newReportModal: HTMLDialogElement;
|
let newReportModal: HTMLDialogElement;
|
||||||
|
|
||||||
$: if (reportFilter) fetchReports({ from: 0 }).then((r) => (reports = r.reports));
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="h-full flex flex-row">
|
<div class="h-full flex flex-row">
|
||||||
<div class="w-full flex flex-col overflow-scroll">
|
<div class="w-full flex flex-col overflow-scroll">
|
||||||
<div class="grid grid-cols-[5fr_1fr_10fr_1fr_5fr]">
|
<div class="grid grid-cols-[5fr_1fr_10fr_1fr_5fr]">
|
||||||
<div />
|
<div></div>
|
||||||
<div />
|
<div></div>
|
||||||
<HeaderBar bind:reportFilter />
|
<HeaderBar
|
||||||
<div class="divider divider-horizontal my-auto h-3/4" />
|
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">
|
<div class="flex items-center">
|
||||||
<button class="btn" on:click={() => newReportModal.show()}>
|
<button class="btn" onclick={() => newReportModal.show()}>
|
||||||
<Plus />
|
<Plus />
|
||||||
<span>Neuer Report</span>
|
<span>Neuer Report</span>
|
||||||
</button>
|
</button>
|
||||||
@ -140,7 +140,7 @@
|
|||||||
<tr
|
<tr
|
||||||
class="hover [&>*]:text-sm cursor-pointer"
|
class="hover [&>*]:text-sm cursor-pointer"
|
||||||
class:bg-base-200={activeReport?.url_hash === report.url_hash}
|
class:bg-base-200={activeReport?.url_hash === report.url_hash}
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
goto(`${window.location.href.split('#')[0]}#${report.url_hash}`, {
|
goto(`${window.location.href.split('#')[0]}#${report.url_hash}`, {
|
||||||
replaceState: true
|
replaceState: true
|
||||||
});
|
});
|
||||||
@ -154,7 +154,10 @@
|
|||||||
<button
|
<button
|
||||||
class="pl-1"
|
class="pl-1"
|
||||||
title="Nach Ersteller filtern"
|
title="Nach Ersteller filtern"
|
||||||
on:click|stopPropagation={() => (reportFilter.reporter = report.reporter.username)}
|
onclick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
reportFilter.reporter = report.reporter.username;
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<MagnifyingGlass size="14" />
|
<MagnifyingGlass size="14" />
|
||||||
</button>
|
</button>
|
||||||
@ -165,8 +168,10 @@
|
|||||||
<button
|
<button
|
||||||
class="pl-1"
|
class="pl-1"
|
||||||
title="Nach Reportetem Spieler filtern"
|
title="Nach Reportetem Spieler filtern"
|
||||||
on:click|stopPropagation={() =>
|
onclick={(e) => {
|
||||||
(reportFilter.reported = report.reported.username)}
|
e.stopPropagation();
|
||||||
|
reportFilter.reported = report.reported.username;
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<MagnifyingGlass size="14" />
|
<MagnifyingGlass size="14" />
|
||||||
</button>
|
</button>
|
||||||
@ -203,18 +208,18 @@
|
|||||||
>
|
>
|
||||||
<div class="absolute right-2 top-2 flex justify-center">
|
<div class="absolute right-2 top-2 flex justify-center">
|
||||||
<form class="dropdown dropdown-end">
|
<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">
|
<label tabindex="0" class="btn btn-sm btn-circle btn-ghost text-center">
|
||||||
<Share size="1rem" />
|
<Share size="1rem" />
|
||||||
</label>
|
</label>
|
||||||
<!-- svelte-ignore a11y-no-noninteractive-tabindex -->
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
||||||
<ul
|
<ul
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-max"
|
class="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-max"
|
||||||
>
|
>
|
||||||
<li>
|
<li>
|
||||||
<button
|
<button
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard.writeText(
|
||||||
`${window.location.protocol}//${window.location.host}${env.PUBLIC_BASE_PATH}/admin/reports#${activeReport.url_hash}`
|
`${window.location.protocol}//${window.location.host}${env.PUBLIC_BASE_PATH}/admin/reports#${activeReport.url_hash}`
|
||||||
);
|
);
|
||||||
@ -223,7 +228,7 @@
|
|||||||
Internen Link kopieren
|
Internen Link kopieren
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
on:click={() =>
|
onclick={() =>
|
||||||
navigator.clipboard.writeText(
|
navigator.clipboard.writeText(
|
||||||
`${window.location.protocol}//${window.location.host}${env.PUBLIC_BASE_PATH}/report/${activeReport.url_hash}`
|
`${window.location.protocol}//${window.location.host}${env.PUBLIC_BASE_PATH}/report/${activeReport.url_hash}`
|
||||||
)}>Öffentlichen Link kopieren</button
|
)}>Öffentlichen Link kopieren</button
|
||||||
@ -233,7 +238,7 @@
|
|||||||
</form>
|
</form>
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-circle btn-ghost"
|
class="btn btn-sm btn-circle btn-ghost"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
activeReport = null;
|
activeReport = null;
|
||||||
goto(window.location.href.split('#')[0], { replaceState: true });
|
goto(window.location.href.split('#')[0], { replaceState: true });
|
||||||
}}>✕</button
|
}}>✕</button
|
||||||
@ -242,7 +247,9 @@
|
|||||||
<h3 class="font-roboto font-semibold text-2xl mb-2">Report</h3>
|
<h3 class="font-roboto font-semibold text-2xl mb-2">Report</h3>
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<Input readonly={true} size="sm" value={activeReport.reporter.username} pickyWidth={false}>
|
<Input readonly={true} size="sm" value={activeReport.reporter.username} pickyWidth={false}>
|
||||||
<span slot="label">Reporter</span>
|
{#snippet label()}
|
||||||
|
<span>Reporter</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
<Search
|
<Search
|
||||||
size="sm"
|
size="sm"
|
||||||
@ -252,17 +259,17 @@
|
|||||||
invalidMessage="Es können nur registrierte Spieler reportet werden"
|
invalidMessage="Es können nur registrierte Spieler reportet werden"
|
||||||
label="Reporteter User"
|
label="Reporteter User"
|
||||||
inputValue={activeReport.reported?.username || ''}
|
inputValue={activeReport.reported?.username || ''}
|
||||||
on:submit={(e) =>
|
onsubmit={(e) =>
|
||||||
(activeReport.reported = {
|
(activeReport.reported = {
|
||||||
...activeReport.reported,
|
...activeReport.reported,
|
||||||
username: e.detail.input,
|
username: e.input,
|
||||||
uuid: e.detail.value
|
uuid: e.value
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
<Textarea readonly={true} rows={1} label="Report Grund" value={activeReport.subject} />
|
<Textarea readonly={true} rows={1} label="Report Grund" value={activeReport.subject} />
|
||||||
<Textarea readonly={true} rows={4} label="Report Details" value={activeReport.body} />
|
<Textarea readonly={true} rows={4} label="Report Details" value={activeReport.body} />
|
||||||
</div>
|
</div>
|
||||||
<div class="divider mx-4" />
|
<div class="divider mx-4"></div>
|
||||||
<div>
|
<div>
|
||||||
<div
|
<div
|
||||||
class="w-full"
|
class="w-full"
|
||||||
@ -318,7 +325,7 @@
|
|||||||
<Input
|
<Input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Speichern"
|
value="Speichern"
|
||||||
on:click={() => saveActiveReportChangesModal.show()}
|
onclick={() => saveActiveReportChangesModal.show()}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -333,7 +340,7 @@
|
|||||||
<Input
|
<Input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Speichern"
|
value="Speichern"
|
||||||
on:click={async () => {
|
onclick={async () => {
|
||||||
await updateActiveReport();
|
await updateActiveReport();
|
||||||
if (activeReport.reported?.username) {
|
if (activeReport.reported?.username) {
|
||||||
if (activeReport.reported?.id === undefined) {
|
if (activeReport.reported?.id === undefined) {
|
||||||
@ -363,9 +370,9 @@
|
|||||||
|
|
||||||
<dialog class="modal" bind:this={newReportModal}>
|
<dialog class="modal" bind:this={newReportModal}>
|
||||||
<NewReportModal
|
<NewReportModal
|
||||||
on:submit={(e) => {
|
onSubmit={(e) => {
|
||||||
if (!e.detail.draft) $reportCount += 1;
|
if (!e.draft) $reportCount += 1;
|
||||||
reports = [e.detail, ...reports];
|
reports = [e, ...reports];
|
||||||
activeReport = reports[0];
|
activeReport = reports[0];
|
||||||
newReportModal.close();
|
newReportModal.close();
|
||||||
}}
|
}}
|
||||||
|
@ -2,28 +2,38 @@
|
|||||||
import Select from '$lib/components/Input/Select.svelte';
|
import Select from '$lib/components/Input/Select.svelte';
|
||||||
import Input from '$lib/components/Input/Input.svelte';
|
import Input from '$lib/components/Input/Input.svelte';
|
||||||
|
|
||||||
export let reportFilter = {
|
let {
|
||||||
reporter: null,
|
reportFilter = $bindable({
|
||||||
reported: null,
|
reporter: undefined,
|
||||||
status: null,
|
reported: undefined,
|
||||||
draft: false
|
status: undefined,
|
||||||
};
|
draft: false
|
||||||
|
}),
|
||||||
|
onUpdate
|
||||||
|
}: {
|
||||||
|
reportFilter: { reporter?: string; reported?: string; status?: string; draft: false };
|
||||||
|
onUpdate: () => void;
|
||||||
|
} = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form class="flex flex-row justify-center space-x-4 mx-4 my-2">
|
<form class="flex flex-row justify-center space-x-4 mx-4 my-2">
|
||||||
<Input size="sm" placeholder="Alle" bind:value={reportFilter.reporter}>
|
<Input size="sm" placeholder="Alle" bind:value={reportFilter.reporter} oninput={onUpdate}>
|
||||||
<span slot="label">Report Ersteller</span>
|
{#snippet label()}
|
||||||
|
<span>Report Ersteller</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
<Input size="sm" placeholder="Alle" bind:value={reportFilter.reported}>
|
<Input size="sm" placeholder="Alle" bind:value={reportFilter.reported} oninput={onUpdate}>
|
||||||
<span slot="label">Reportete Spieler</span>
|
{#snippet label()}
|
||||||
|
<span>Reporteter Spieler</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</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="none">Unbearbeitet</option>
|
||||||
<option value="review">In Bearbeitung</option>
|
<option value="review">In Bearbeitung</option>
|
||||||
<option value={null}>Unbearbeitet & In Bearbeitung</option>
|
<option value={null}>Unbearbeitet & In Bearbeitung</option>
|
||||||
<option value="reviewed">Bearbeitet</option>
|
<option value="reviewed">Bearbeitet</option>
|
||||||
</Select>
|
</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={false}>Erstellt</option>
|
||||||
<option value={true}>Entwurf</option>
|
<option value={true}>Entwurf</option>
|
||||||
</Select>
|
</Select>
|
||||||
|
@ -3,15 +3,15 @@
|
|||||||
import { env } from '$env/dynamic/public';
|
import { env } from '$env/dynamic/public';
|
||||||
import Textarea from '$lib/components/Input/Textarea.svelte';
|
import Textarea from '$lib/components/Input/Textarea.svelte';
|
||||||
import Search from '$lib/components/Input/Search.svelte';
|
import Search from '$lib/components/Input/Search.svelte';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { usernameSuggestions } from '$lib/utils';
|
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 reporter: string | undefined = $state();
|
||||||
let reported: string;
|
let reported: string | undefined = $state();
|
||||||
let reason = '';
|
let reason = $state('');
|
||||||
let body = '';
|
let body = $state('');
|
||||||
|
|
||||||
async function newReport() {
|
async function newReport() {
|
||||||
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/reports`, {
|
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/reports`, {
|
||||||
@ -23,7 +23,7 @@
|
|||||||
body: body || null
|
body: body || null
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
if (response.ok) dispatch('submit', await response.json());
|
if (response.ok) onSubmit(await response.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
let globalCloseForm: HTMLFormElement;
|
let globalCloseForm: HTMLFormElement;
|
||||||
@ -35,7 +35,10 @@
|
|||||||
<form method="dialog" class="modal-box" bind:this={reportForm}>
|
<form method="dialog" class="modal-box" bind:this={reportForm}>
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
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>
|
<h3 class="font-roboto text-3xl">Neuer Report</h3>
|
||||||
<div class="space-y-2 mt-2 px-1 max-h-[70vh] overflow-y-scroll">
|
<div class="space-y-2 mt-2 px-1 max-h-[70vh] overflow-y-scroll">
|
||||||
@ -59,9 +62,11 @@
|
|||||||
bind:value={reported}
|
bind:value={reported}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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}>
|
<Input type="text" bind:value={reason} required={true} pickyWidth={false}>
|
||||||
<span slot="label">Report Grund</span>
|
{#snippet label()}
|
||||||
|
<span>Report Grund</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
<div>
|
<div>
|
||||||
<Textarea rows={4} label="Details über den Report Grund" bind:value={body} />
|
<Textarea rows={4} label="Details über den Report Grund" bind:value={body} />
|
||||||
@ -71,9 +76,9 @@
|
|||||||
<Input
|
<Input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Erstellen"
|
value="Erstellen"
|
||||||
on:click={(e) => {
|
onclick={(e) => {
|
||||||
if (reportForm.checkValidity()) {
|
if (reportForm.checkValidity()) {
|
||||||
e.detail.preventDefault();
|
e.preventDefault();
|
||||||
confirmDialog.show();
|
confirmDialog.show();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -81,8 +86,8 @@
|
|||||||
<Input
|
<Input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Abbrechen"
|
value="Abbrechen"
|
||||||
on:click={(e) => {
|
onclick={(e) => {
|
||||||
e.detail.preventDefault();
|
e.preventDefault();
|
||||||
globalCloseForm.submit();
|
globalCloseForm.submit();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -111,7 +116,7 @@
|
|||||||
<Input
|
<Input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Erstellen"
|
value="Erstellen"
|
||||||
on:click={async () => {
|
onclick={async () => {
|
||||||
await newReport();
|
await newReport();
|
||||||
globalCloseForm.submit();
|
globalCloseForm.submit();
|
||||||
}}
|
}}
|
||||||
|
@ -16,7 +16,6 @@ export const load: PageServerLoad = async ({ parent, cookies }) => {
|
|||||||
(prev, curr) => {
|
(prev, curr) => {
|
||||||
return { ...prev, [curr.key]: curr.value };
|
return { ...prev, [curr.key]: curr.value };
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
{} as { [key: string]: any }
|
{} as { [key: string]: any }
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types';
|
import type { PageData } from './$types';
|
||||||
import { env } from '$env/dynamic/public';
|
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() {
|
async function change() {
|
||||||
await fetch(`${env.PUBLIC_BASE_PATH}/admin/settings`, {
|
await fetch(`${env.PUBLIC_BASE_PATH}/admin/settings`, {
|
||||||
@ -23,7 +25,7 @@
|
|||||||
} as PageData['settings'])
|
} as PageData['settings'])
|
||||||
});
|
});
|
||||||
data.settings = settings;
|
data.settings = settings;
|
||||||
settings = structuredClone(data.settings);
|
settings = $state.snapshot(data.settings);
|
||||||
}
|
}
|
||||||
|
|
||||||
function returnIfNoDup<T>(value: T, original: T): T | undefined {
|
function returnIfNoDup<T>(value: T, original: T): T | undefined {
|
||||||
@ -68,7 +70,7 @@
|
|||||||
<button
|
<button
|
||||||
class="btn btn-success mt-auto"
|
class="btn btn-success mt-auto"
|
||||||
class:btn-disabled={JSON.stringify(data.settings) === JSON.stringify(settings)}
|
class:btn-disabled={JSON.stringify(data.settings) === JSON.stringify(settings)}
|
||||||
on:click={change}>Speichern</button
|
onclick={change}>Speichern</button
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types';
|
|
||||||
import { Check, NoSymbol, PencilSquare, Plus, Trash } from 'svelte-heros-v2';
|
import { Check, NoSymbol, PencilSquare, Plus, Trash } from 'svelte-heros-v2';
|
||||||
import Input from '$lib/components/Input/Input.svelte';
|
import Input from '$lib/components/Input/Input.svelte';
|
||||||
import Select from '$lib/components/Input/Select.svelte';
|
import Select from '$lib/components/Input/Select.svelte';
|
||||||
import { env } from '$env/dynamic/public';
|
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 { buttonTriggeredRequest } from '$lib/components/utils';
|
||||||
import { browser } from '$app/environment';
|
import { browser } from '$app/environment';
|
||||||
import HeaderBar from './HeaderBar.svelte';
|
import HeaderBar from './HeaderBar.svelte';
|
||||||
@ -13,11 +12,9 @@
|
|||||||
import NewUserModal from './NewUserModal.svelte';
|
import NewUserModal from './NewUserModal.svelte';
|
||||||
import PaginationTableBody from '$lib/components/PaginationTable/PaginationTableBody.svelte';
|
import PaginationTableBody from '$lib/components/PaginationTable/PaginationTableBody.svelte';
|
||||||
|
|
||||||
export let data: PageData;
|
let users: (typeof User.prototype.dataValues)[] = $state([]);
|
||||||
|
|
||||||
let users: (typeof User.prototype.dataValues)[] = [];
|
|
||||||
let usersPerRequest = 25;
|
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 userTableContainerElement: HTMLDivElement;
|
||||||
let newUserModal: HTMLDialogElement;
|
let newUserModal: HTMLDialogElement;
|
||||||
@ -64,18 +61,16 @@
|
|||||||
users = users;
|
users = users;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: if (userFilter) fetchUsers({ from: 0 }).then((u) => (users = u));
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="h-full flex flex-col overflow-hidden">
|
<div class="h-full flex flex-col overflow-hidden">
|
||||||
<div class="grid grid-cols-[10fr_1fr_10fr_1fr_10fr]">
|
<div class="grid grid-cols-[10fr_1fr_10fr_1fr_10fr]">
|
||||||
<div />
|
<div></div>
|
||||||
<div />
|
<div></div>
|
||||||
<HeaderBar bind:userFilter />
|
<HeaderBar bind:userFilter onUpdate={() => fetchUsers({ from: 0 }).then((u) => (users = u))} />
|
||||||
<div class="divider divider-horizontal my-auto h-3/4" />
|
<div class="divider divider-horizontal my-auto h-3/4"></div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<button class="btn" on:click={() => newUserModal.show()}>
|
<button class="btn" onclick={() => newUserModal.show()}>
|
||||||
<Plus />
|
<Plus />
|
||||||
<span>Neuer Spieler</span>
|
<span>Neuer Spieler</span>
|
||||||
</button>
|
</button>
|
||||||
@ -87,16 +82,16 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<SortableTr class="[&>th]:bg-base-100 [&>th]:z-[1] [&>th]:sticky [&>th]:top-0">
|
<SortableTr class="[&>th]:bg-base-100 [&>th]:z-[1] [&>th]:sticky [&>th]:top-0">
|
||||||
<th />
|
<th></th>
|
||||||
<SortableTh on:sort={(e) => userFilter = {...userFilter, sort: {key: 'firstname', asc: e.detail.asc}}}>Vorname</SortableTh>
|
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'firstname', asc: e.asc}}}>Vorname</SortableTh>
|
||||||
<SortableTh on:sort={(e) => userFilter = {...userFilter, sort: {key: 'lastname', asc: e.detail.asc}}}>Nachname</SortableTh>
|
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'lastname', asc: e.asc}}}>Nachname</SortableTh>
|
||||||
<SortableTh on:sort={(e) => userFilter = {...userFilter, sort: {key: 'birthday', asc: e.detail.asc}}}>Geburtstag</SortableTh>
|
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'birthday', asc: e.asc}}}>Geburtstag</SortableTh>
|
||||||
<SortableTh on:sort={(e) => userFilter = {...userFilter, sort: {key: 'telephone', asc: e.detail.asc}}}>Telefon</SortableTh>
|
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'telephone', asc: e.asc}}}>Telefon</SortableTh>
|
||||||
<SortableTh on:sort={(e) => userFilter = {...userFilter, sort: {key: 'username', asc: e.detail.asc}}}>Username</SortableTh>
|
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'username', asc: e.asc}}}>Username</SortableTh>
|
||||||
<SortableTh on:sort={(e) => userFilter = {...userFilter, sort: {key: 'playertype', asc: e.detail.asc}}}>Minecraft Edition</SortableTh>
|
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'playertype', asc: e.asc}}}>Minecraft Edition</SortableTh>
|
||||||
<SortableTh on:sort={(e) => userFilter = {...userFilter, sort: {key: 'password', asc: e.detail.asc}}}>Passwort</SortableTh>
|
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'password', asc: e.asc}}}>Passwort</SortableTh>
|
||||||
<SortableTh on:sort={(e) => userFilter = {...userFilter, sort: {key: 'uuid', asc: e.detail.asc}}}>UUID</SortableTh>
|
<SortableTh onSort={(e) => userFilter = {...userFilter, sort: {key: 'uuid', asc: e.asc}}}>UUID</SortableTh>
|
||||||
<th />
|
<th></th>
|
||||||
</SortableTr>
|
</SortableTr>
|
||||||
</thead>
|
</thead>
|
||||||
<PaginationTableBody
|
<PaginationTableBody
|
||||||
@ -117,7 +112,7 @@
|
|||||||
<Input
|
<Input
|
||||||
type="date"
|
type="date"
|
||||||
value={new Date(user.birthday).toISOString().split('T')[0]}
|
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}
|
disabled={!user.edit}
|
||||||
size="sm"
|
size="sm"
|
||||||
/>
|
/>
|
||||||
@ -146,7 +141,7 @@
|
|||||||
{#if user.edit}
|
{#if user.edit}
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-square"
|
class="btn btn-sm btn-square"
|
||||||
on:click={async (e) => {
|
onclick={async (e) => {
|
||||||
await buttonTriggeredRequest(e, updateUser(user));
|
await buttonTriggeredRequest(e, updateUser(user));
|
||||||
user.edit = false;
|
user.edit = false;
|
||||||
}}
|
}}
|
||||||
@ -155,9 +150,9 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-square"
|
class="btn btn-sm btn-square"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
user.edit = false;
|
user.edit = false;
|
||||||
user = user.before;
|
users[i] = user.before;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<NoSymbol size="18" />
|
<NoSymbol size="18" />
|
||||||
@ -165,8 +160,8 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-square"
|
class="btn btn-sm btn-square"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
user.before = structuredClone(user);
|
user.before = $state.snapshot(user);
|
||||||
user.edit = true;
|
user.edit = true;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -174,7 +169,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-square"
|
class="btn btn-sm btn-square"
|
||||||
on:click={(e) => buttonTriggeredRequest(e, deleteUser(user.id))}
|
onclick={(e) => buttonTriggeredRequest(e, deleteUser(user.id))}
|
||||||
>
|
>
|
||||||
<Trash size="18" />
|
<Trash size="18" />
|
||||||
</button>
|
</button>
|
||||||
@ -190,8 +185,8 @@
|
|||||||
|
|
||||||
<dialog class="modal" bind:this={newUserModal}>
|
<dialog class="modal" bind:this={newUserModal}>
|
||||||
<NewUserModal
|
<NewUserModal
|
||||||
on:submit={(e) => {
|
onSubmit={(e) => {
|
||||||
users = [...users, e.detail];
|
users = [...users, e];
|
||||||
newUserModal.close();
|
newUserModal.close();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -3,7 +3,13 @@ import { Permissions } from '$lib/permissions';
|
|||||||
import { error, type RequestHandler } from '@sveltejs/kit';
|
import { error, type RequestHandler } from '@sveltejs/kit';
|
||||||
import { User } from '$lib/server/database';
|
import { User } from '$lib/server/database';
|
||||||
import { type Attributes, Op } from 'sequelize';
|
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';
|
import { UserAddSchema, UserDeleteSchema, UserEditSchema, UserListSchema } from './schema';
|
||||||
|
|
||||||
export const POST = (async ({ request, cookies }) => {
|
export const POST = (async ({ request, cookies }) => {
|
||||||
|
@ -2,20 +2,34 @@
|
|||||||
import Select from '$lib/components/Input/Select.svelte';
|
import Select from '$lib/components/Input/Select.svelte';
|
||||||
import Input from '$lib/components/Input/Input.svelte';
|
import Input from '$lib/components/Input/Input.svelte';
|
||||||
|
|
||||||
export let userFilter: { [k: string]: any } = {
|
let {
|
||||||
name: null,
|
userFilter = $bindable({ name: null, playertype: null }),
|
||||||
playertype: null
|
onUpdate
|
||||||
};
|
}: { userFilter: { [k: string]: any }; onUpdate: () => void } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<form class="flex flex-row justify-center items-center space-x-4 my-2 w-full">
|
<form class="flex flex-row justify-center items-center space-x-4 my-2 w-full">
|
||||||
<div class="w-full">
|
<div class="w-full">
|
||||||
<Input size="sm" placeholder="..." bind:value={userFilter.name} pickyWidth={false}>
|
<Input
|
||||||
<span slot="label">Username</span>
|
size="sm"
|
||||||
|
placeholder="..."
|
||||||
|
bind:value={userFilter.name}
|
||||||
|
pickyWidth={false}
|
||||||
|
oninput={onUpdate}
|
||||||
|
>
|
||||||
|
{#snippet label()}
|
||||||
|
<span>Username</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full">
|
<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={null}>Alle</option>
|
||||||
<option value="java">Java</option>
|
<option value="java">Java</option>
|
||||||
<option value="bedrock">Bedrock</option>
|
<option value="bedrock">Bedrock</option>
|
||||||
|
@ -3,16 +3,33 @@
|
|||||||
import { env } from '$env/dynamic/public';
|
import { env } from '$env/dynamic/public';
|
||||||
import Select from '$lib/components/Input/Select.svelte';
|
import Select from '$lib/components/Input/Select.svelte';
|
||||||
import { errorMessage } from '$lib/stores';
|
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 firstname: string = $state('');
|
||||||
let lastname: string;
|
let lastname: string = $state('');
|
||||||
let birthday: string;
|
let birthday: string = $state('');
|
||||||
let phone: string;
|
let phone: string = $state('');
|
||||||
let username: string;
|
let username: string = $state('');
|
||||||
let playertype = 'java';
|
let playertype = $state('java');
|
||||||
|
|
||||||
async function newUser() {
|
async function newUser() {
|
||||||
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/users`, {
|
const response = await fetch(`${env.PUBLIC_BASE_PATH}/admin/users`, {
|
||||||
@ -27,7 +44,7 @@
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
dispatch('submit', {
|
onSubmit({
|
||||||
firstname: firstname,
|
firstname: firstname,
|
||||||
lastname: lastname,
|
lastname: lastname,
|
||||||
birthday: birthday,
|
birthday: birthday,
|
||||||
@ -50,24 +67,37 @@
|
|||||||
<form method="dialog" class="modal-box" bind:this={reportForm}>
|
<form method="dialog" class="modal-box" bind:this={reportForm}>
|
||||||
<button
|
<button
|
||||||
class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"
|
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>
|
<h3 class="font-roboto text-3xl">Neuer Spieler</h3>
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<Input type="text" required bind:value={firstname}>
|
<Input type="text" required bind:value={firstname}>
|
||||||
<span slot="label">Vorname</span>
|
{#snippet label()}
|
||||||
|
<span>Vorname</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
<Input type="text" required bind:value={lastname}>
|
<Input type="text" required bind:value={lastname}>
|
||||||
<span slot="label">Nachname</span>
|
{#snippet label()}
|
||||||
|
<span>Nachname</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
<Input type="date" required bind:value={birthday}>
|
<Input type="date" required bind:value={birthday}>
|
||||||
<span slot="label">Geburtstag</span>
|
{#snippet label()}
|
||||||
|
<span>Geburtstag</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
<Input type="tel" bind:value={phone}>
|
<Input type="tel" bind:value={phone}>
|
||||||
<span slot="label">Telefonnummer</span>
|
{#snippet label()}
|
||||||
|
<span>Telefonnummer</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
<Input type="text" required bind:value={username}>
|
<Input type="text" required bind:value={username}>
|
||||||
<span slot="label">Minecraft-Spielername</span>
|
{#snippet label()}
|
||||||
|
<span>Minecraft-Spielername</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
<Select required label="Edition" bind:value={playertype}>
|
<Select required label="Edition" bind:value={playertype}>
|
||||||
<option value="java">Java Edition</option>
|
<option value="java">Java Edition</option>
|
||||||
@ -78,9 +108,9 @@
|
|||||||
<Input
|
<Input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Hinzufügen"
|
value="Hinzufügen"
|
||||||
on:click={(e) => {
|
onclick={(e) => {
|
||||||
if (reportForm.checkValidity()) {
|
if (reportForm.checkValidity()) {
|
||||||
e.detail.preventDefault();
|
e.preventDefault();
|
||||||
confirmDialog.show();
|
confirmDialog.show();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@ -88,8 +118,8 @@
|
|||||||
<Input
|
<Input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Abbrechen"
|
value="Abbrechen"
|
||||||
on:click={(e) => {
|
onclick={(e) => {
|
||||||
e.detail.preventDefault();
|
e.preventDefault();
|
||||||
globalCloseForm.submit();
|
globalCloseForm.submit();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -104,7 +134,7 @@
|
|||||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
<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>
|
<h3 class="font-roboto text-xl mb-2">Spieler hinzufügen?</h3>
|
||||||
<div class="flex flex-row space-x-2 mt-6">
|
<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" />
|
<Input type="submit" value="Abbrechen" />
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
@ -31,8 +31,6 @@ export const POST = (async ({ request, url }) => {
|
|||||||
|
|
||||||
await Feedback.bulkCreate(Object.values(feedback));
|
await Feedback.bulkCreate(Object.values(feedback));
|
||||||
|
|
||||||
console.log(Object.entries(feedback));
|
|
||||||
|
|
||||||
return new Response(
|
return new Response(
|
||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
Object.entries(feedback).reduce(
|
Object.entries(feedback).reduce(
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { PageData } from './$types';
|
|
||||||
import { env } from '$env/dynamic/public';
|
import { env } from '$env/dynamic/public';
|
||||||
|
|
||||||
export let data: PageData;
|
|
||||||
|
|
||||||
let faq = [
|
let faq = [
|
||||||
{
|
{
|
||||||
section: 'Allgemein',
|
section: 'Allgemein',
|
||||||
@ -204,10 +201,11 @@ sind nicht gestattet.</p>`
|
|||||||
<input type="checkbox" autocomplete="off" />
|
<input type="checkbox" autocomplete="off" />
|
||||||
<div class="collapse-title">{question.title}</div>
|
<div class="collapse-title">{question.title}</div>
|
||||||
<div class="collapse-content">
|
<div class="collapse-content">
|
||||||
|
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
|
||||||
<div class="ml-2">{@html question.content}</div>
|
<div class="ml-2">{@html question.content}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="block w-full h-[1px] mx-auto mb-1 bg-gray-600" />
|
<span class="block w-full h-[1px] mx-auto mb-1 bg-gray-600"></span>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
import { env } from '$env/dynamic/public';
|
import { env } from '$env/dynamic/public';
|
||||||
import Select from '$lib/components/Input/Select.svelte';
|
import Select from '$lib/components/Input/Select.svelte';
|
||||||
|
|
||||||
let content = '';
|
let content = $state('');
|
||||||
let type = 'feedback';
|
let type = $state('feedback');
|
||||||
|
|
||||||
let submitModal: HTMLDialogElement;
|
let submitModal: HTMLDialogElement;
|
||||||
let sentModal: HTMLDialogElement & { type?: string } = {};
|
let sentModal: HTMLDialogElement;
|
||||||
|
|
||||||
async function submitFeedback() {
|
async function submitFeedback() {
|
||||||
await fetch(`${env.PUBLIC_BASE_PATH}/feedback`, {
|
await fetch(`${env.PUBLIC_BASE_PATH}/feedback`, {
|
||||||
@ -27,7 +27,12 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-3xl text-center">Feedback & Kontakt</h2>
|
<h2 class="text-3xl text-center">Feedback & Kontakt</h2>
|
||||||
<form on:submit|preventDefault={() => submitModal.show()}>
|
<form
|
||||||
|
onsubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
submitModal.show();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div class="space-y-4 mt-6 mb-4">
|
<div class="space-y-4 mt-6 mb-4">
|
||||||
<Select size="sm" bind:value={type}>
|
<Select size="sm" bind:value={type}>
|
||||||
<option value="feedback">Feedback</option>
|
<option value="feedback">Feedback</option>
|
||||||
@ -67,9 +72,8 @@
|
|||||||
<Input
|
<Input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Abschicken"
|
value="Abschicken"
|
||||||
on:click={async () => {
|
onclick={async () => {
|
||||||
await submitFeedback();
|
await submitFeedback();
|
||||||
sentModal.type = type;
|
|
||||||
sentModal.show();
|
sentModal.show();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -86,7 +90,7 @@
|
|||||||
<form
|
<form
|
||||||
method="dialog"
|
method="dialog"
|
||||||
class="modal-box"
|
class="modal-box"
|
||||||
on:submit={() => {
|
onsubmit={() => {
|
||||||
content = '';
|
content = '';
|
||||||
type = 'feedback';
|
type = 'feedback';
|
||||||
}}
|
}}
|
||||||
@ -94,7 +98,7 @@
|
|||||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-roboto font-medium text-xl">
|
<h3 class="font-roboto font-medium text-xl">
|
||||||
{sentModal.type === 'feedback' ? 'Feedback' : 'Kontaktanfrage'} abgeschickt
|
{type === 'feedback' ? 'Feedback' : 'Kontaktanfrage'} abgeschickt
|
||||||
</h3>
|
</h3>
|
||||||
<div class="my-4">
|
<div class="my-4">
|
||||||
{#if type === 'feedback'}
|
{#if type === 'feedback'}
|
||||||
@ -112,7 +116,7 @@
|
|||||||
<form
|
<form
|
||||||
method="dialog"
|
method="dialog"
|
||||||
class="modal-backdrop bg-[rgba(0,0,0,.3)]"
|
class="modal-backdrop bg-[rgba(0,0,0,.3)]"
|
||||||
on:submit={() => {
|
onsubmit={() => {
|
||||||
content = '';
|
content = '';
|
||||||
type = 'feedback';
|
type = 'feedback';
|
||||||
}}
|
}}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import type { PageData } from './$types';
|
|
||||||
import FeedbackDraft from './FeedbackDraft.svelte';
|
import FeedbackDraft from './FeedbackDraft.svelte';
|
||||||
import FeedbackSent from './FeedbackSent.svelte';
|
import FeedbackSent from './FeedbackSent.svelte';
|
||||||
|
|
||||||
export let data: PageData;
|
let { data } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -1,17 +1,14 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Input from '$lib/components/Input/Input.svelte';
|
import Input from '$lib/components/Input/Input.svelte';
|
||||||
import Textarea from '$lib/components/Input/Textarea.svelte';
|
import Textarea from '$lib/components/Input/Textarea.svelte';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import { env } from '$env/dynamic/public';
|
import { env } from '$env/dynamic/public';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
|
|
||||||
export let event: string;
|
let { event, anonymous, onSubmit }: { event: string; anonymous: false; onSubmit?: () => void } =
|
||||||
export let anonymous: boolean;
|
$props();
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
let content = $state('');
|
||||||
|
let sendAnonymous = $state(true);
|
||||||
let content = '';
|
|
||||||
let sendAnonymous = true;
|
|
||||||
|
|
||||||
let submitModal: HTMLDialogElement;
|
let submitModal: HTMLDialogElement;
|
||||||
|
|
||||||
@ -28,10 +25,17 @@
|
|||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h2 class="text-3xl text-center">Feedback</h2>
|
<h2 class="text-3xl text-center">Feedback</h2>
|
||||||
<form on:submit|preventDefault={() => submitModal.show()}>
|
<form
|
||||||
|
onsubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
submitModal.show();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div class="space-y-4 my-4">
|
<div class="space-y-4 my-4">
|
||||||
<Input size="sm" pickyWidth={false} disabled value={event}>
|
<Input size="sm" pickyWidth={false} disabled value={event}>
|
||||||
<span slot="label">Event</span>
|
{#snippet label()}
|
||||||
|
<span>Event</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
<Textarea required={true} rows={4} label="Feedback" bind:value={content} />
|
<Textarea required={true} rows={4} label="Feedback" bind:value={content} />
|
||||||
<div>
|
<div>
|
||||||
@ -63,9 +67,9 @@
|
|||||||
<Input
|
<Input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Abschicken"
|
value="Abschicken"
|
||||||
on:click={async () => {
|
onclick={async () => {
|
||||||
await submitFeedback();
|
await submitFeedback();
|
||||||
dispatch('submit');
|
onSubmit && onSubmit();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Input type="submit" value="Abbrechen" />
|
<Input type="submit" value="Abbrechen" />
|
||||||
|
@ -2,17 +2,16 @@
|
|||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import RegistrationComplete from './RegistrationComplete.svelte';
|
import RegistrationComplete from './RegistrationComplete.svelte';
|
||||||
import Register from './Register.svelte';
|
import Register from './Register.svelte';
|
||||||
import type { PageData } from './$types';
|
|
||||||
|
|
||||||
let registered = false;
|
let { data } = $props();
|
||||||
let firstname: string | null = null;
|
|
||||||
let lastname: string | null = null;
|
|
||||||
let birthday: Date | null = null;
|
|
||||||
let phone: string | null = null;
|
|
||||||
let username: string | null = null;
|
|
||||||
let edition: string | null = null;
|
|
||||||
|
|
||||||
export let data: PageData;
|
let registered = $state(false);
|
||||||
|
let firstname: string | null = $state(null);
|
||||||
|
let lastname: string | null = $state(null);
|
||||||
|
let birthday: Date | null = $state(null);
|
||||||
|
let phone: string | null = $state(null);
|
||||||
|
let username: string | null = $state(null);
|
||||||
|
let edition: string | null = $state(null);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@ -36,15 +35,15 @@
|
|||||||
{#if !registered}
|
{#if !registered}
|
||||||
<div class="col-[1] row-[1]" transition:fly={{ x: -200, duration: 300 }}>
|
<div class="col-[1] row-[1]" transition:fly={{ x: -200, duration: 300 }}>
|
||||||
<Register
|
<Register
|
||||||
on:submit={(e) => {
|
submit={(e) => {
|
||||||
registered = true;
|
registered = true;
|
||||||
firstname = e.detail.firstname;
|
firstname = e.firstname;
|
||||||
lastname = e.detail.lastname;
|
lastname = e.lastname;
|
||||||
birthday = e.detail.birthday;
|
birthday = e.birthday;
|
||||||
phone = e.detail.phone;
|
phone = e.phone;
|
||||||
phone = e.detail.phone;
|
phone = e.phone;
|
||||||
username = e.detail.username;
|
username = e.username;
|
||||||
edition = e.detail.edition;
|
edition = e.edition;
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -57,7 +56,7 @@
|
|||||||
{phone}
|
{phone}
|
||||||
{username}
|
{username}
|
||||||
{edition}
|
{edition}
|
||||||
on:close={() => (registered = false)}
|
close={() => (registered = false)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -17,6 +17,7 @@ export const POST = (async ({ request }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// eslint-disable-next-line no-var
|
||||||
var data = RegisterSchema.parse(await request.json());
|
var data = RegisterSchema.parse(await request.json());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
|
@ -1,27 +1,54 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import Select from '$lib/components/Input/Select.svelte';
|
import Select from '$lib/components/Input/Select.svelte';
|
||||||
import Input from '$lib/components/Input/Input.svelte';
|
import Input from '$lib/components/Input/Input.svelte';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { env } from '$env/dynamic/public';
|
import { env } from '$env/dynamic/public';
|
||||||
import { rulesShort } from '$lib/rules';
|
import { rulesShort } from '$lib/rules';
|
||||||
import { RegisterSchema } from './schema';
|
import { RegisterSchema } from './schema';
|
||||||
import { dev } from '$app/environment';
|
import { dev } from '$app/environment';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
let {
|
||||||
|
submit
|
||||||
|
}: {
|
||||||
|
submit: ({
|
||||||
|
firstname,
|
||||||
|
lastname,
|
||||||
|
birthday,
|
||||||
|
phone,
|
||||||
|
username,
|
||||||
|
edition
|
||||||
|
}: {
|
||||||
|
firstname: string;
|
||||||
|
lastname: string;
|
||||||
|
birthday: Date;
|
||||||
|
phone: string;
|
||||||
|
username: string;
|
||||||
|
edition: string;
|
||||||
|
}) => void;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
const modalTimeoutSeconds = dev ? 0 : 30;
|
const modalTimeoutSeconds = dev ? 0 : 30;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
let checkInputs = $state(() => {});
|
||||||
let checkInputs = () => {};
|
let playertype = $state('java');
|
||||||
let playertype = 'java';
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||||
let firstnameInput: HTMLInputElement;
|
// @ts-ignore
|
||||||
let lastnameInput: HTMLInputElement;
|
let firstnameInput: HTMLInputElement = $state();
|
||||||
let birthdayInput: HTMLInputElement;
|
// @ts-ignore
|
||||||
let phoneInput: HTMLInputElement;
|
let lastnameInput: HTMLInputElement = $state();
|
||||||
let usernameInput: HTMLInputElement;
|
// @ts-ignore
|
||||||
let privacyInput: HTMLInputElement;
|
let birthdayInput: HTMLInputElement = $state();
|
||||||
let logsInput: HTMLInputElement;
|
// @ts-ignore
|
||||||
let rulesInput: HTMLInputElement;
|
let phoneInput: HTMLInputElement = $state();
|
||||||
|
// @ts-ignore
|
||||||
|
let usernameInput: HTMLInputElement = $state();
|
||||||
|
// @ts-ignore
|
||||||
|
let privacyInput: HTMLInputElement = $state();
|
||||||
|
// @ts-ignore
|
||||||
|
let logsInput: HTMLInputElement = $state();
|
||||||
|
// @ts-ignore
|
||||||
|
let rulesInput: HTMLInputElement = $state();
|
||||||
|
/* eslint-enable @typescript-eslint/ban-ts-comment */
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
checkInputs = () => {
|
checkInputs = () => {
|
||||||
let allInputs = [
|
let allInputs = [
|
||||||
@ -58,10 +85,10 @@
|
|||||||
body: JSON.stringify(Object.fromEntries(new FormData(document.forms[0])))
|
body: JSON.stringify(Object.fromEntries(new FormData(document.forms[0])))
|
||||||
});
|
});
|
||||||
if (response.ok) {
|
if (response.ok) {
|
||||||
dispatch('submit', {
|
submit({
|
||||||
firstname: firstnameInput.value,
|
firstname: firstnameInput.value,
|
||||||
lastname: lastnameInput.value,
|
lastname: lastnameInput.value,
|
||||||
birthday: birthdayInput.valueAsDate,
|
birthday: birthdayInput.valueAsDate!,
|
||||||
phone: phoneInput.value,
|
phone: phoneInput.value,
|
||||||
username: usernameInput.value,
|
username: usernameInput.value,
|
||||||
edition: playertype == 'java' ? 'Java (PC)' : 'Bedrock (Konsolen und Handys)'
|
edition: playertype == 'java' ? 'Java (PC)' : 'Bedrock (Konsolen und Handys)'
|
||||||
@ -80,17 +107,24 @@
|
|||||||
|
|
||||||
let rulesAccepted = false;
|
let rulesAccepted = false;
|
||||||
let rulesModal: HTMLDialogElement;
|
let rulesModal: HTMLDialogElement;
|
||||||
let rulesModalSecondsOpened = 0;
|
let rulesModalSecondsOpened = $state(0);
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
let rulesModalTimer: number | NodeJS.Timeout | undefined = undefined;
|
let rulesModalTimer: number | NodeJS.Timeout | undefined = undefined;
|
||||||
|
|
||||||
let inputsInvalidMessage: string | null = 'Bitte fülle alle erforderlichen Felder aus';
|
let inputsInvalidMessage: string | null = $state('Bitte fülle alle erforderlichen Felder aus');
|
||||||
let registerRequest: Promise<void> | null = null;
|
let registerRequest: Promise<void> | null = $state(null);
|
||||||
|
let errorMessage: string = $state('');
|
||||||
let errorMessage: string = '';
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h1 class="text-center text-3xl lg:text-5xl">Anmeldung</h1>
|
<h1 class="text-center text-3xl lg:text-5xl">Anmeldung</h1>
|
||||||
<form id="form" on:input={checkInputs} on:submit|preventDefault={sendRegister}>
|
<form
|
||||||
|
id="form"
|
||||||
|
oninput={checkInputs}
|
||||||
|
onsubmit={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
sendRegister();
|
||||||
|
}}
|
||||||
|
>
|
||||||
<div class="divider">Persönliche Angaben</div>
|
<div class="divider">Persönliche Angaben</div>
|
||||||
<div class="mx-2 grid grid-cols-1 sm:grid-cols-2 gap-y-4">
|
<div class="mx-2 grid grid-cols-1 sm:grid-cols-2 gap-y-4">
|
||||||
<Input
|
<Input
|
||||||
@ -100,7 +134,9 @@
|
|||||||
required={true}
|
required={true}
|
||||||
bind:inputElement={firstnameInput}
|
bind:inputElement={firstnameInput}
|
||||||
>
|
>
|
||||||
<span slot="label">Vorname</span>
|
{#snippet label()}
|
||||||
|
<span>Vorname</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
<Input
|
<Input
|
||||||
id="lastname"
|
id="lastname"
|
||||||
@ -109,7 +145,9 @@
|
|||||||
required={true}
|
required={true}
|
||||||
bind:inputElement={lastnameInput}
|
bind:inputElement={lastnameInput}
|
||||||
>
|
>
|
||||||
<span slot="label">Nachname</span>
|
{#snippet label()}
|
||||||
|
<span>Nachname</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
<Input
|
<Input
|
||||||
id="birthday"
|
id="birthday"
|
||||||
@ -118,8 +156,12 @@
|
|||||||
required={true}
|
required={true}
|
||||||
bind:inputElement={birthdayInput}
|
bind:inputElement={birthdayInput}
|
||||||
>
|
>
|
||||||
<span slot="label">Geburtstag</span>
|
{#snippet label()}
|
||||||
<span slot="notice">Die Angabe hat keine Auswirkungen auf das Spielgeschehen</span>
|
<span>Geburtstag</span>
|
||||||
|
{/snippet}
|
||||||
|
{#snippet notice()}
|
||||||
|
<span>Die Angabe hat keine Auswirkungen auf das Spielgeschehen</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
<Input
|
<Input
|
||||||
id="telephone"
|
id="telephone"
|
||||||
@ -128,12 +170,16 @@
|
|||||||
bind:inputElement={phoneInput}
|
bind:inputElement={phoneInput}
|
||||||
pattern={new RegExp(/^[+()\s/\d]+$/)}
|
pattern={new RegExp(/^[+()\s/\d]+$/)}
|
||||||
>
|
>
|
||||||
<span slot="label">Telefonnummer</span>
|
{#snippet label()}
|
||||||
<p slot="notice">
|
<span>Telefonnummer</span>
|
||||||
Diese nutzen wir, um Dich in der Whatsapp-Gruppe zuzuordnen und kontaktieren zu können.
|
{/snippet}
|
||||||
<br />
|
{#snippet notice()}
|
||||||
<b>Die Angabe ist freiwillig, hilft den Administratoren jedoch sehr!</b>
|
<p>
|
||||||
</p>
|
Diese nutzen wir, um Dich in der Whatsapp-Gruppe zuzuordnen und kontaktieren zu können.
|
||||||
|
<br />
|
||||||
|
<b>Die Angabe ist freiwillig, hilft den Administratoren jedoch sehr!</b>
|
||||||
|
</p>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider">Spiel</div>
|
<div class="divider">Spiel</div>
|
||||||
@ -145,7 +191,9 @@
|
|||||||
required={true}
|
required={true}
|
||||||
bind:inputElement={usernameInput}
|
bind:inputElement={usernameInput}
|
||||||
>
|
>
|
||||||
<span slot="label">Minecraft-Spielername</span>
|
{#snippet label()}
|
||||||
|
<span>Minecraft-Spielername</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
<Select
|
<Select
|
||||||
id="playertype"
|
id="playertype"
|
||||||
@ -158,7 +206,7 @@
|
|||||||
<option value="bedrock">Bedrock Edition (Konsolen und Handys)</option>
|
<option value="bedrock">Bedrock Edition (Konsolen und Handys)</option>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider" />
|
<div class="divider"></div>
|
||||||
<div class="mx-2 grid gap-y-3 mb-6">
|
<div class="mx-2 grid gap-y-3 mb-6">
|
||||||
<div class="flex gap-4">
|
<div class="flex gap-4">
|
||||||
<Input
|
<Input
|
||||||
@ -199,9 +247,9 @@
|
|||||||
name="rules"
|
name="rules"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
required={true}
|
required={true}
|
||||||
on:input={(e) => {
|
oninput={(e) => {
|
||||||
if (!rulesAccepted) {
|
if (!rulesAccepted) {
|
||||||
e.detail.target.checked = false;
|
e.currentTarget.checked = false;
|
||||||
rulesModal.show();
|
rulesModal.show();
|
||||||
rulesModalTimer = setInterval(() => rulesModalSecondsOpened++, 1000);
|
rulesModalTimer = setInterval(() => rulesModalSecondsOpened++, 1000);
|
||||||
}
|
}
|
||||||
@ -211,7 +259,8 @@
|
|||||||
<label for="rules">
|
<label for="rules">
|
||||||
Ich bin mit den <button
|
Ich bin mit den <button
|
||||||
class="link"
|
class="link"
|
||||||
on:click|preventDefault={() => {
|
onclick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
rulesModal.show();
|
rulesModal.show();
|
||||||
rulesModalTimer = setInterval(() => rulesModalSecondsOpened++, 1000);
|
rulesModalTimer = setInterval(() => rulesModalSecondsOpened++, 1000);
|
||||||
}}>Regeln</button
|
}}>Regeln</button
|
||||||
@ -238,7 +287,7 @@
|
|||||||
{#await registerRequest}
|
{#await registerRequest}
|
||||||
<span
|
<span
|
||||||
class="relative top-[calc(50%-12px)] left-[calc(50%-12px)] row-[1] col-[1] loading loading-ring"
|
class="relative top-[calc(50%-12px)] left-[calc(50%-12px)] row-[1] col-[1] loading loading-ring"
|
||||||
/>
|
></span>
|
||||||
{/await}
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
{/key}
|
{/key}
|
||||||
@ -247,7 +296,7 @@
|
|||||||
|
|
||||||
<dialog
|
<dialog
|
||||||
class="modal"
|
class="modal"
|
||||||
on:close={() => {
|
onclose={() => {
|
||||||
clearInterval(rulesModalTimer);
|
clearInterval(rulesModalTimer);
|
||||||
rulesModalTimer = undefined;
|
rulesModalTimer = undefined;
|
||||||
}}
|
}}
|
||||||
@ -266,7 +315,7 @@
|
|||||||
<p>{rulesShort.header}</p>
|
<p>{rulesShort.header}</p>
|
||||||
<p class="mt-1 text-[.75rem]">{rulesShort.footer}</p>
|
<p class="mt-1 text-[.75rem]">{rulesShort.footer}</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="block w-full h-[1px] mx-auto mb-1 bg-gray-600" />
|
<span class="block w-full h-[1px] mx-auto mb-1 bg-gray-600"></span>
|
||||||
</div>
|
</div>
|
||||||
{#each rulesShort.sections as section, i}
|
{#each rulesShort.sections as section, i}
|
||||||
<div class="collapse collapse-arrow">
|
<div class="collapse collapse-arrow">
|
||||||
@ -278,10 +327,10 @@
|
|||||||
<p>{section.content}</p>
|
<p>{section.content}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="block w-full h-[1px] mx-auto mb-1 bg-gray-600" />
|
<span class="block w-full h-[1px] mx-auto mb-1 bg-gray-600"></span>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<!-- svelte-ignore a11y-no-static-element-interactions a11y-click-events-have-key-events -->
|
<!-- svelte-ignore a11y_no_static_element_interactions a11y_click_events_have_key_events -->
|
||||||
<div
|
<div
|
||||||
class="relative w-min"
|
class="relative w-min"
|
||||||
title={rulesModalSecondsOpened < modalTimeoutSeconds
|
title={rulesModalSecondsOpened < modalTimeoutSeconds
|
||||||
@ -290,7 +339,7 @@
|
|||||||
0
|
0
|
||||||
)} Sekunden akzeptiert werden`
|
)} Sekunden akzeptiert werden`
|
||||||
: ''}
|
: ''}
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
if (rulesModalSecondsOpened < modalTimeoutSeconds) {
|
if (rulesModalSecondsOpened < modalTimeoutSeconds) {
|
||||||
errorMessage =
|
errorMessage =
|
||||||
'Bitte lies die Regeln aufmerksam durch. Du kannst erst in einigen Sekunden fortfahren.';
|
'Bitte lies die Regeln aufmerksam durch. Du kannst erst in einigen Sekunden fortfahren.';
|
||||||
@ -301,7 +350,7 @@
|
|||||||
<div
|
<div
|
||||||
style="width: {Math.min((rulesModalSecondsOpened / modalTimeoutSeconds) * 100, 100)}%"
|
style="width: {Math.min((rulesModalSecondsOpened / modalTimeoutSeconds) * 100, 100)}%"
|
||||||
class="h-full bg-base-300"
|
class="h-full bg-base-300"
|
||||||
/>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<Input
|
<Input
|
||||||
id="rules-accept"
|
id="rules-accept"
|
||||||
@ -309,7 +358,7 @@
|
|||||||
value="Akzeptieren"
|
value="Akzeptieren"
|
||||||
disabled={rulesModalSecondsOpened < modalTimeoutSeconds}
|
disabled={rulesModalSecondsOpened < modalTimeoutSeconds}
|
||||||
containerClass="bg-transparent z-[1] relative"
|
containerClass="bg-transparent z-[1] relative"
|
||||||
on:click={() => {
|
onclick={() => {
|
||||||
rulesAccepted = true;
|
rulesAccepted = true;
|
||||||
rulesInput.checked = true;
|
rulesInput.checked = true;
|
||||||
checkInputs();
|
checkInputs();
|
||||||
@ -324,7 +373,7 @@
|
|||||||
</dialog>
|
</dialog>
|
||||||
|
|
||||||
{#if errorMessage}
|
{#if errorMessage}
|
||||||
<dialog class="modal" on:close={() => setTimeout(() => (errorMessage = ''), 200)} open>
|
<dialog class="modal" onclose={() => setTimeout(() => (errorMessage = ''), 200)} open>
|
||||||
<form method="dialog" class="modal-box z-50">
|
<form method="dialog" class="modal-box z-50">
|
||||||
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2">✕</button>
|
||||||
<h3 class="font-bold text-2xl">Achtung</h3>
|
<h3 class="font-bold text-2xl">Achtung</h3>
|
||||||
|
@ -1,17 +1,26 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import { env } from '$env/dynamic/public';
|
import { env } from '$env/dynamic/public';
|
||||||
import Input from '$lib/components/Input/Input.svelte';
|
import Input from '$lib/components/Input/Input.svelte';
|
||||||
import Select from '$lib/components/Input/Select.svelte';
|
import Select from '$lib/components/Input/Select.svelte';
|
||||||
|
|
||||||
export let firstname: string;
|
let {
|
||||||
export let lastname: string;
|
firstname,
|
||||||
export let birthday: Date;
|
lastname,
|
||||||
export let phone: string | null;
|
birthday,
|
||||||
export let username: string;
|
phone,
|
||||||
export let edition: string;
|
username,
|
||||||
|
edition,
|
||||||
const dispatch = createEventDispatcher();
|
close
|
||||||
|
}: {
|
||||||
|
firstname: string;
|
||||||
|
lastname: string;
|
||||||
|
birthday: Date;
|
||||||
|
phone?: string;
|
||||||
|
username: string;
|
||||||
|
edition: string;
|
||||||
|
close: () => void;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
let startDayOptions: Intl.DateTimeFormatOptions = {
|
let startDayOptions: Intl.DateTimeFormatOptions = {
|
||||||
day: '2-digit',
|
day: '2-digit',
|
||||||
@ -22,7 +31,7 @@
|
|||||||
hour: '2-digit',
|
hour: '2-digit',
|
||||||
minute: '2-digit'
|
minute: '2-digit'
|
||||||
};
|
};
|
||||||
let skin: string | null = null;
|
let skin: string | null = $state(null);
|
||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
let skinview3d = await import('skinview3d');
|
let skinview3d = await import('skinview3d');
|
||||||
@ -70,16 +79,34 @@
|
|||||||
uns, dich auf unserem <a class="link" href={env.PUBLIC_TS_LINK} target="_blank">TeamSpeak</a> oder
|
uns, dich auf unserem <a class="link" href={env.PUBLIC_TS_LINK} target="_blank">TeamSpeak</a> oder
|
||||||
in unserem <a class="link" href={env.PUBLIC_DISCORD_LINK} target="_blank">Discord</a> begrüßen zu dürfen!
|
in unserem <a class="link" href={env.PUBLIC_DISCORD_LINK} target="_blank">Discord</a> begrüßen zu dürfen!
|
||||||
</p>
|
</p>
|
||||||
<div class="divider" />
|
<div class="divider"></div>
|
||||||
<div class="flex justify-around mt-2 mb-4">
|
<div class="flex justify-around mt-2 mb-4">
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 w-full sm:w-fit gap-x-4 gap-y-2">
|
<div class="grid grid-cols-1 sm:grid-cols-2 w-full sm:w-fit gap-x-4 gap-y-2">
|
||||||
<Input value={firstname} size="sm" readonly><span slot="label">Vorname</span></Input>
|
<Input value={firstname} size="sm" readonly>
|
||||||
<Input value={lastname} size="sm" readonly><span slot="label">Nachname</span></Input>
|
{#snippet label()}
|
||||||
<Input value={birthday.toISOString().substring(0, 10)} type="date" size="sm" readonly
|
<span>Vorname</span>
|
||||||
><span slot="label">Geburtstag</span></Input
|
{/snippet}
|
||||||
|
</Input>
|
||||||
|
<Input value={lastname} size="sm" readonly>
|
||||||
|
{#snippet label()}
|
||||||
|
<span>Nachname</span>
|
||||||
|
{/snippet}
|
||||||
|
</Input>
|
||||||
|
<Input value={birthday.toISOString().substring(0, 10)} type="date" size="sm" readonly>
|
||||||
|
{#snippet label()}
|
||||||
|
<span>Geburtstag</span>
|
||||||
|
{/snippet}</Input
|
||||||
>
|
>
|
||||||
<Input value={phone} size="sm" readonly><span slot="label">Telefonnummer</span></Input>
|
<Input value={phone} size="sm" readonly>
|
||||||
<Input value={username} size="sm" readonly><span slot="label">Spielername</span></Input>
|
{#snippet label()}
|
||||||
|
<span>Telefonnummer</span>
|
||||||
|
{/snippet}
|
||||||
|
</Input>
|
||||||
|
<Input value={username} size="sm" readonly>
|
||||||
|
{#snippet label()}
|
||||||
|
<span>Spielername</span>
|
||||||
|
{/snippet}
|
||||||
|
</Input>
|
||||||
<Select value="edition" size="sm" disabled label="Edition">
|
<Select value="edition" size="sm" disabled label="Edition">
|
||||||
<option value="edition">{edition}</option>
|
<option value="edition">{edition}</option>
|
||||||
</Select>
|
</Select>
|
||||||
@ -88,11 +115,11 @@
|
|||||||
{#if skin}
|
{#if skin}
|
||||||
<img class="absolute" src={skin} alt="" />
|
<img class="absolute" src={skin} alt="" />
|
||||||
{:else}
|
{:else}
|
||||||
<span class="loading loading-spinner loading-lg" />
|
<span class="loading loading-spinner loading-lg"></span>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="divider" />
|
<div class="divider"></div>
|
||||||
<div class="flex justify-center gap-8">
|
<div class="flex justify-center gap-8">
|
||||||
<button class="btn" on:click={() => dispatch('close')}>Weitere Person anmelden</button>
|
<button class="btn" onclick={close}>Weitere Person anmelden</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { children } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
<div class="flex justify-center items-center w-full">
|
<div class="flex justify-center items-center w-full">
|
||||||
<slot />
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { fly } from 'svelte/transition';
|
import { fly } from 'svelte/transition';
|
||||||
import type { PageData } from './$types';
|
|
||||||
import ReportDraft from './ReportDraft.svelte';
|
import ReportDraft from './ReportDraft.svelte';
|
||||||
import ReportCompleted from './ReportCompleted.svelte';
|
import ReportCompleted from './ReportCompleted.svelte';
|
||||||
import ReportSubmitted from './ReportSubmitted.svelte';
|
import ReportSubmitted from './ReportSubmitted.svelte';
|
||||||
|
|
||||||
export let data: PageData;
|
let { data } = $props();
|
||||||
|
|
||||||
|
let completed = $state(!data.draft);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@ -15,14 +16,14 @@
|
|||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
<div class="mt-12 grid card w-11/12 xl:w-2/3 2xl:w-1/2 p-6 shadow-lg">
|
<div class="mt-12 grid card w-11/12 xl:w-2/3 2xl:w-1/2 p-6 shadow-lg">
|
||||||
{#if data.draft}
|
{#if !completed}
|
||||||
<div class="col-[1] row-[1]" transition:fly={{ x: -200, duration: 300 }}>
|
<div class="col-[1] row-[1]" transition:fly={{ x: -200, duration: 300 }}>
|
||||||
<ReportDraft
|
<ReportDraft
|
||||||
reason={data.reason}
|
reason={data.reason}
|
||||||
reporterName={data.reporter.name}
|
reporterName={data.reporter.name}
|
||||||
reportedName={data.reported.name ?? undefined}
|
reportedName={data.reported.name ?? undefined}
|
||||||
users={data.users ?? []}
|
users={data.users ?? []}
|
||||||
on:submit={() => (data.draft = false)}
|
onsubmit={() => (completed = true)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{:else if data.status === 'reviewed'}
|
{:else if data.status === 'reviewed'}
|
||||||
|
@ -3,31 +3,36 @@
|
|||||||
import Input from '$lib/components/Input/Input.svelte';
|
import Input from '$lib/components/Input/Input.svelte';
|
||||||
import { env } from '$env/dynamic/public';
|
import { env } from '$env/dynamic/public';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
import Search from '$lib/components/Input/Search.svelte';
|
import Search from '$lib/components/Input/Search.svelte';
|
||||||
import Select from '$lib/components/Input/Select.svelte';
|
import Select from '$lib/components/Input/Select.svelte';
|
||||||
|
|
||||||
export let reporterName: string;
|
let {
|
||||||
export let reportedName: string | null;
|
reporterName,
|
||||||
export let reason: string;
|
reportedName = null,
|
||||||
export let users: string[];
|
reason,
|
||||||
|
users,
|
||||||
|
onsubmit
|
||||||
|
}: {
|
||||||
|
reporterName: string;
|
||||||
|
reportedName: string | null;
|
||||||
|
reason: string;
|
||||||
|
users: string[];
|
||||||
|
onsubmit: () => void;
|
||||||
|
} = $props();
|
||||||
|
|
||||||
let oldReported = reportedName;
|
let reported = $state(reportedName);
|
||||||
$: reportedName = oldReported;
|
let content = $state('');
|
||||||
|
|
||||||
let body: string;
|
|
||||||
let userErrorModal: HTMLDialogElement;
|
let userErrorModal: HTMLDialogElement;
|
||||||
let submitModal: HTMLDialogElement;
|
let submitModal: HTMLDialogElement;
|
||||||
|
|
||||||
let dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
async function submitReport() {
|
async function submitReport() {
|
||||||
await fetch(`${env.PUBLIC_BASE_PATH}/report/${$page.params.url_hash}`, {
|
await fetch(`${env.PUBLIC_BASE_PATH}/report/${$page.params.url_hash}`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
reported: reportedName || null,
|
reported: reported || null,
|
||||||
subject: reason,
|
subject: reason,
|
||||||
body: body
|
body: content
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -45,11 +50,12 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2 class="text-3xl text-center">
|
<h2 class="text-3xl text-center">
|
||||||
Report von <span class="underline">{reporterName}</span> gegen
|
Report von <span class="underline">{reporterName}</span> gegen
|
||||||
<span class="underline">{(reportedName ?? 'unbekannt') || oldReported}</span>
|
<span class="underline">{reported !== null ? reported : 'unbekannt'}</span>
|
||||||
</h2>
|
</h2>
|
||||||
<form
|
<form
|
||||||
on:submit|preventDefault={() => {
|
onsubmit={(e) => {
|
||||||
if (reportedName != null && users.findIndex((u) => u === reportedName) === -1) {
|
e.preventDefault();
|
||||||
|
if (reported != null && users.findIndex((u) => u === reported) === -1) {
|
||||||
userErrorModal.show();
|
userErrorModal.show();
|
||||||
} else {
|
} else {
|
||||||
submitModal.show();
|
submitModal.show();
|
||||||
@ -59,21 +65,25 @@
|
|||||||
<div class="space-y-4 my-4">
|
<div class="space-y-4 my-4">
|
||||||
<div class="flex justify-center gap-4">
|
<div class="flex justify-center gap-4">
|
||||||
<Select
|
<Select
|
||||||
value={+(reportedName === null)}
|
value={+(reported === null)}
|
||||||
size="sm"
|
size="sm"
|
||||||
pickyWidth={false}
|
pickyWidth={false}
|
||||||
on:change={(e) => (reportedName = e.detail.value === 0 ? '' : null)}
|
onChange={(e) => {
|
||||||
|
reported = e.value === 0 ? reportedName || '' : null;
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<option value={0}>Ich möchte einen bestimmten Spieler reporten</option>
|
<option value={0}>Ich möchte einen bestimmten Spieler reporten</option>
|
||||||
<option value={1}>Ich möchte einen unbekannten Spieler reporten</option>
|
<option value={1}>Ich möchte einen unbekannten Spieler reporten</option>
|
||||||
</Select>
|
</Select>
|
||||||
{#if reportedName !== null}
|
{#if reported !== null}
|
||||||
<Search size="sm" bind:value={oldReported} searchSuggestionFunc={suggestNames} />
|
<Search size="sm" bind:value={reported} searchSuggestionFunc={suggestNames} />
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Input type="text" bind:value={reason} required={true} pickyWidth={false}>
|
<Input type="text" bind:value={reason} required={true} pickyWidth={false}>
|
||||||
<span slot="label">Report Grund</span>
|
{#snippet label()}
|
||||||
|
<span>Report Grund</span>
|
||||||
|
{/snippet}
|
||||||
</Input>
|
</Input>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@ -81,7 +91,7 @@
|
|||||||
required={true}
|
required={true}
|
||||||
rows={4}
|
rows={4}
|
||||||
label="Details über den Report Grund"
|
label="Details über den Report Grund"
|
||||||
bind:value={body}
|
bind:value={content}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -121,9 +131,9 @@
|
|||||||
<Input
|
<Input
|
||||||
type="submit"
|
type="submit"
|
||||||
value="Abschicken"
|
value="Abschicken"
|
||||||
on:click={async () => {
|
onclick={async () => {
|
||||||
await submitReport();
|
await submitReport();
|
||||||
dispatch('submit');
|
onsubmit();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Input type="submit" value="Abbrechen" />
|
<Input type="submit" value="Abbrechen" />
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { children } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
<div class="mx-4 my-6 sm:mx-48 sm:my-12">
|
<div class="mx-4 my-6 sm:mx-48 sm:my-12">
|
||||||
<slot />
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { rulesLong, rulesShort } from '$lib/rules';
|
import { rulesLong } from '$lib/rules';
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
@ -14,12 +14,12 @@
|
|||||||
<p>0. Vorwort</p>
|
<p>0. Vorwort</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="collapse-content">
|
<div class="collapse-content">
|
||||||
<p>{rulesShort.header}</p>
|
<p>{rulesLong.header}</p>
|
||||||
<p class="mt-1 text-[.75rem]">{rulesShort.footer}</p>
|
<p class="mt-1 text-[.75rem]">{rulesLong.footer}</p>
|
||||||
</div>
|
</div>
|
||||||
<span class="block w-full h-[1px] mx-auto mb-1 bg-gray-600" />
|
<span class="block w-full h-[1px] mx-auto mb-1 bg-gray-600"></span>
|
||||||
</div>
|
</div>
|
||||||
{#each rulesShort.sections as section, i}
|
{#each rulesLong.sections as section, i}
|
||||||
<div class="collapse collapse-arrow">
|
<div class="collapse collapse-arrow">
|
||||||
<input type="checkbox" autocomplete="off" />
|
<input type="checkbox" autocomplete="off" />
|
||||||
<div class="collapse-title">
|
<div class="collapse-title">
|
||||||
@ -29,5 +29,5 @@
|
|||||||
<p>{section.content}</p>
|
<p>{section.content}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span class="block w-full h-[1px] mx-auto mb-1 bg-gray-600" />
|
<span class="block w-full h-[1px] mx-auto mb-1 bg-gray-600"></span>
|
||||||
{/each}
|
{/each}
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let { children } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
<div class="m-auto flex flex-col justify-center items-center px-4 py-6 2xl:px-48 sm:py-12">
|
<div class="m-auto flex flex-col justify-center items-center px-4 py-6 2xl:px-48 sm:py-12">
|
||||||
<slot />
|
{@render children()}
|
||||||
</div>
|
</div>
|
||||||
|
@ -61,7 +61,7 @@
|
|||||||
<p class="text-center text-sm font-light">{member.roles.join(' · ')}</p>
|
<p class="text-center text-sm font-light">{member.roles.join(' · ')}</p>
|
||||||
{#if member.links}
|
{#if member.links}
|
||||||
<div class="w-full flex items-center flex-col">
|
<div class="w-full flex items-center flex-col">
|
||||||
<div class="w-1/2 h-[1px] my-3 rounded bg-base-content" />
|
<div class="w-1/2 h-[1px] my-3 rounded bg-base-content"></div>
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
{#each member.links as link}
|
{#each member.links as link}
|
||||||
<a
|
<a
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
|
import type { Config } from 'tailwindcss';
|
||||||
import daisyui from 'daisyui';
|
import daisyui from 'daisyui';
|
||||||
|
|
||||||
/** @type {import('tailwindcss').Config} */
|
|
||||||
export default {
|
export default {
|
||||||
content: ['./src/**/*.{html,js,svelte,ts}'],
|
content: ['./src/**/*.{html,js,svelte,ts}'],
|
||||||
theme: {
|
theme: {
|
||||||
@ -16,4 +16,4 @@ export default {
|
|||||||
daisyui: {
|
daisyui: {
|
||||||
logs: false
|
logs: false
|
||||||
}
|
}
|
||||||
};
|
} satisfies Config;
|
Loading…
x
Reference in New Issue
Block a user