add feedback and report things
All checks were successful
deploy / build-and-deploy (/testvaro, /opt/website-test, website-test) (push) Successful in 22s
deploy / build-and-deploy (/varo, /opt/website, website) (push) Successful in 21s

This commit is contained in:
2025-06-21 14:45:39 +02:00
parent 9c49585873
commit ee8f595ecc
25 changed files with 898 additions and 57 deletions

View File

@@ -0,0 +1,66 @@
---
import WebsiteLayout from '@layouts/website/WebsiteLayout.astro';
import Input from '@components/input/Input.svelte';
import Textarea from '@components/input/Textarea.svelte';
import { db } from '@db/database.ts';
const { urlHash } = Astro.params;
const feedback = urlHash ? await db.getFeedbackByUrlHash({ urlHash: urlHash }) : null;
if (!feedback) {
return new Response(null, { status: 404 });
}
---
<WebsiteLayout title="Feedback">
<div class="flex justify-center items-center">
<div class="mt-12 grid card w-11/12 xl:w-2/3 2xl:w-1/2 p-6 shadow-lg">
<h2 class="text-3xl text-center">Feedback</h2>
<form id="feedback" data-url-hash={urlHash}>
<div class="space-y-4 mt-6 mb-4">
<Input value={feedback.title} label="Event" dynamicWidth readonly />
<Textarea
id="content"
value={feedback.content}
label="Feedback"
rows={10}
dynamicWidth
required
readonly={feedback.content !== null}
/>
</div>
<button id="send" class="btn" disabled>Feedback senden</button>
</form>
</div>
</div>
</WebsiteLayout>
<script>
import { actions } from 'astro:actions';
import { actionErrorPopup } from '@util/action';
document.addEventListener('astro:page-load', () => {
const form = document.getElementById('feedback') as HTMLFormElement;
const content = document.getElementById('content') as HTMLTextAreaElement;
const sendButton = document.getElementById('send') as HTMLButtonElement;
form.addEventListener('submit', async (e) => {
e.preventDefault();
const { error } = await actions.feedback.submitFeedback({
urlHash: form.dataset.urlHash!,
content: content.value
});
if (error) {
actionErrorPopup(error);
return;
}
content.readOnly = true;
sendButton.disabled = true;
});
content.addEventListener('input', () => (sendButton.disabled = content.value === '' || content.readOnly));
});
</script>

View File

@@ -0,0 +1,23 @@
---
import WebsiteLayout from '@layouts/website/WebsiteLayout.astro';
import { db } from '@db/database.ts';
import Draft from './_draft.astro';
import Submitted from './_submitted.astro';
import Popup from '@components/popup/Popup.svelte';
import ConfirmPopup from '@components/popup/ConfirmPopup.svelte';
const { urlHash } = Astro.params;
const report = urlHash ? await db.getReportByUrlHash({ urlHash: urlHash }) : null;
if (!report) {
return new Response(null, { status: 404 });
}
---
<WebsiteLayout title="Report">
{report.createdAt === null ? <Draft report={report} /> : <Submitted report={report} />}
</WebsiteLayout>
<Popup client:idle />
<ConfirmPopup client:idle />

View File

@@ -0,0 +1,105 @@
---
import type { db } from '@db/database.ts';
import AdversarySearch from '@app/website/report/AdversarySearch.svelte';
import Dropzone from '@app/website/report/Dropzone.svelte';
import Input from '@components/input/Input.svelte';
import Textarea from '@components/input/Textarea.svelte';
import { MAX_UPLOAD_BYTES } from 'astro:env/server';
interface Props {
report: Awaited<ReturnType<db.getReportByUrlHash>>;
}
const { report } = Astro.props;
---
<div class="flex justify-center items-center">
<div class="mt-12 grid card w-11/12 xl:w-2/3 2xl:w-1/2 p-6 shadow-lg">
<h2 class="text-3xl text-center">
Report von Team <span class="underline">A</span> gegen Team <span id="adversary-team-name" class="underline"
>B</span
>
</h2>
<form id="report" data-url-hash={report.urlHash} data-adversary-team={report.reported?.name}>
<div class="space-y-4 my-4">
<div class="flex flex-col gap-4">
<AdversarySearch
adversary={{ type: report.reported ? 'team' : 'unknown', name: report.reported?.name }}
client:load
/>
<Input id="reason" value={report.reason} label="Report Grund" dynamicWidth />
<Textarea id="body" value={report.body} label="Details" rows={10} dynamicWidth required />
<Dropzone maxFilesBytes={MAX_UPLOAD_BYTES} client:load />
</div>
<button id="send" class="btn" disabled={report.body}>Report senden</button>
</div>
</form>
</div>
</div>
<script>
import { actions } from 'astro:actions';
import { actionErrorPopup } from '@util/action';
import { popupState } from '@components/popup/Popup';
document.addEventListener('astro:page-load', () => {
const eventCancelController = new AbortController();
document.addEventListener('astro:after-swap', () => eventCancelController.abort());
const adversary = document.getElementById('adversary-team-name') as HTMLSpanElement;
const form = document.getElementById('report') as HTMLFormElement;
const reason = document.getElementById('reason') as HTMLInputElement;
const body = document.getElementById('body') as HTMLTextAreaElement;
const sendButton = document.getElementById('send') as HTMLButtonElement;
let attachments: File[] = [];
body.addEventListener('change', () => {
sendButton.disabled = !body.value;
});
document.addEventListener(
'adversaryInput',
(e: any & { detail: { adversaryTeamName: string } }) => {
adversary.textContent = e.detail.adversaryTeamName;
},
{ signal: eventCancelController.signal }
);
document.addEventListener(
'dropzoneInput',
(e: any & { detail: { files: File[] } }) => {
attachments = e.detail.files;
},
{ signal: eventCancelController.signal }
);
form.addEventListener(
'submit',
async (e) => {
e.preventDefault();
const formData = new FormData();
formData.set('urlHash', form.dataset.urlHash!);
formData.set('reason', reason.value);
formData.set('body', body.value);
for (const attachment of attachments) {
formData.append('files', attachment);
}
const { error } = await actions.report.submitReport(formData);
if (error) {
actionErrorPopup(error);
return;
}
popupState.set({
type: 'info',
title: 'Report abgeschickt',
message: 'Der Report wurde abgeschickt. Ein Admin wird sich schnellstmöglich darum kümmern.',
onClose: () => location.reload()
});
},
{ signal: eventCancelController.signal }
);
});
</script>

View File

@@ -0,0 +1,33 @@
---
import type { db } from '@db/database.ts';
import Textarea from '@components/input/Textarea.svelte';
interface Props {
report: Awaited<ReturnType<db.getReportByUrlHash>>;
}
const { report } = Astro.props;
---
<div class="flex justify-center items-center">
<div class="mt-12 grid card w-11/12 xl:w-2/3 2xl:w-1/2 p-6 shadow-lg">
{
report.status.status === null ? (
<p>Dein Report wird in kürze bearbeitet</p>
) : report.status.status === 'open' ? (
<p>Dein Report befindet sich in Bearbeitung</p>
) : (
<>
<p>Dein Report wurde bearbeitet</p>
<Textarea
value={report.status.statement}
label="Antwort vom Admin Team (optional)"
rows={5}
dynamicWidth
readonly
/>
</>
)
}
</div>
</div>