add public feedback/contact option
All checks were successful
delpoy / build-and-deploy (push) Successful in 1m5s

This commit is contained in:
bytedream 2024-11-30 03:00:46 +01:00
parent ceaf006dd5
commit 55798fd294
10 changed files with 175 additions and 28 deletions

View File

@ -99,13 +99,13 @@ export class StrikePunishment extends Model {
@Table({ modelName: 'feedback', underscored: true })
export class Feedback extends Model {
@Column({ type: DataTypes.STRING, allowNull: false, unique: true })
@Index
declare url_hash: string;
@Column({ type: DataTypes.STRING, allowNull: false })
declare event: string;
@Column({ type: DataTypes.STRING })
declare content: string;
@Column({ type: DataTypes.STRING, allowNull: false, unique: true })
@Index
declare url_hash: string;
@Column({ type: DataTypes.INTEGER })
@ForeignKey(() => User)
declare user_id: number;

View File

@ -29,6 +29,12 @@
href: `${env.PUBLIC_BASE_PATH}/faq`,
active: false
},
{
name: 'Feedback & Kontakt',
sprite: `${env.PUBLIC_BASE_PATH}/img/menu-feedback.png`,
href: `${env.PUBLIC_BASE_PATH}/feedback`,
active: false
},
{
name: 'Team',
sprite: `${env.PUBLIC_BASE_PATH}/img/menu-team.png`,

View File

@ -0,0 +1,5 @@
<div class="flex justify-center items-center w-full min-h-screen h-full">
<div class="absolute top-12 grid card w-11/12 xl:w-2/3 2xl:w-1/2 p-6 shadow-lg">
<slot />
</div>
</div>

View File

@ -0,0 +1,122 @@
<script lang="ts">
import Input from '$lib/components/Input/Input.svelte';
import Textarea from '$lib/components/Input/Textarea.svelte';
import { env } from '$env/dynamic/public';
import Select from '$lib/components/Input/Select.svelte';
let content = '';
let type = 'feedback';
let submitModal: HTMLDialogElement;
let sentModal: HTMLDialogElement & { type?: string } = {};
async function submitFeedback() {
await fetch(`${env.PUBLIC_BASE_PATH}/feedback`, {
method: 'POST',
body: JSON.stringify({
content: content,
type: type
})
});
}
</script>
<svelte:head>
<title>Feedback & Kontakt</title>
</svelte:head>
<div>
<h2 class="text-3xl text-center">Feedback & Kontakt</h2>
<form on:submit|preventDefault={() => submitModal.show()}>
<div class="space-y-4 mt-6 mb-4">
<Select size="sm" bind:value={type}>
<option value="feedback">Feedback</option>
<option value="contact">Kontakt</option>
</Select>
<Textarea
required={true}
rows={4}
label={type === 'feedback' ? 'Feedback' : 'Anfrage'}
bind:value={content}
/>
<div>
<Input type="submit" disabled={type === '' || content === ''} value="Senden" />
</div>
</div>
</form>
</div>
<dialog class="modal" bind:this={submitModal}>
<form method="dialog" class="modal-box">
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"></button>
<div>
<h3 class="font-roboto font-medium text-xl">
{type === 'feedback' ? 'Feedback' : 'Kontaktanfrage'} abschicken?
</h3>
<div class="my-4">
{#if type === 'feedback'}
<p>Nach dem Abschicken des Feedbacks lässt es sich nicht mehr bearbeiten.</p>
{:else}
<p>
Bitte hinterlege eine Rückmeldemöglichkeit in deiner Anfrage. Nachdem sie abgeschickt
wurde, kannst du die Nachricht nicht mehr bearbeiten.
</p>
{/if}
</div>
<div class="flex flex-row space-x-1">
<Input
type="submit"
value="Abschicken"
on:click={async () => {
await submitFeedback();
sentModal.type = type;
sentModal.show();
}}
/>
<Input type="submit" value="Abbrechen" />
</div>
</div>
</form>
<form method="dialog" class="modal-backdrop bg-[rgba(0,0,0,.3)]">
<button>close</button>
</form>
</dialog>
<dialog class="modal" bind:this={sentModal}>
<form
method="dialog"
class="modal-box"
on:submit={() => {
content = '';
type = 'feedback';
}}
>
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2"></button>
<div>
<h3 class="font-roboto font-medium text-xl">
{sentModal.type === 'feedback' ? 'Feedback' : 'Kontaktanfrage'} abgeschickt
</h3>
<div class="my-4">
{#if type === 'feedback'}
<p>Dein Feedback wurde abgeschickt.</p>
{:else}
<p>
Deine Kontaktanfrage wurde abgeschickt. Jemand aus dem Team wird sich nächstmöglich bei
Dir melden.
</p>
{/if}
</div>
<Input type="submit" value="Schließen" />
</div>
</form>
<form
method="dialog"
class="modal-backdrop bg-[rgba(0,0,0,.3)]"
on:submit={() => {
content = '';
type = 'feedback';
}}
>
<button>close</button>
</form>
</dialog>

View File

@ -1,7 +0,0 @@
import type { PageLoad } from './$types';
import { redirect } from '@sveltejs/kit';
import { env } from '$env/dynamic/public';
export const load: PageLoad = async () => {
throw redirect(302, `${env.PUBLIC_BASE_PATH}/`);
};

View File

@ -0,0 +1,20 @@
import type { RequestHandler } from '@sveltejs/kit';
import { Feedback } from '$lib/server/database';
import { FeedbackSubmitSchema } from './schema';
import crypto from 'crypto';
export const POST = (async ({ request }) => {
const parseResult = await FeedbackSubmitSchema.safeParseAsync(await request.json());
if (!parseResult.success) {
return new Response(null, { status: 400 });
}
const data = parseResult.data;
await Feedback.create({
event: `website-${data.type}`,
content: data.content,
url_hash: crypto.randomBytes(18).toString('hex')
});
return new Response(null, { status: 200 });
}) satisfies RequestHandler;

View File

@ -1,3 +0,0 @@
<div class="flex justify-center items-center w-full min-h-screen h-full">
<slot />
</div>

View File

@ -13,18 +13,16 @@
<meta name="robots" content="noindex" />
</svelte:head>
<div class="absolute top-12 grid card w-11/12 xl:w-2/3 2xl:w-1/2 p-6 shadow-lg">
{#if data.draft}
<div class="col-[1] row-[1]" transition:fly={{ x: -200, duration: 300 }}>
<FeedbackDraft
event={data.event}
anonymous={data.anonymous}
on:submit={() => (data.draft = false)}
/>
</div>
{:else}
<div class="col-[1] row-[1]" transition:fly={{ x: 200, duration: 300 }}>
<FeedbackSent />
</div>
{/if}
</div>
{#if data.draft}
<div class="col-[1] row-[1]" transition:fly={{ x: -200, duration: 300 }}>
<FeedbackDraft
event={data.event}
anonymous={data.anonymous}
on:submit={() => (data.draft = false)}
/>
</div>
{:else}
<div class="col-[1] row-[1]" transition:fly={{ x: 200, duration: 300 }}>
<FeedbackSent />
</div>
{/if}

View File

@ -0,0 +1,6 @@
import { z } from 'zod';
export const FeedbackSubmitSchema = z.object({
content: z.string(),
type: z.enum(['feedback', 'contact'])
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 B