svelte-website/src/components/misc/guestbook.svelte

183 lines
4.8 KiB
Svelte

<script lang="ts" context="module">
export type Comment = {
id: string;
name: string;
email: string;
website: string;
message: string;
time: string;
uuid: string;
};
</script>
<script lang="ts">
import Errormodal from './errormodal.svelte';
import { onMount } from 'svelte';
import { sha256 } from 'js-sha256';
import GuestbookEntry from './guestbook/guestbook-entry.svelte';
import { mode } from '$app/env';
//const ApiURL = dev ? 'https://api.quenten.nl/api/testing' : 'https://api.quenten.nl/api';
//const ApiURL = import.meta.env.APIURL;
const ApiURL = mode == 'production' ? 'https://api.quenten.nl/api' : 'https://api.quenten.nl/api/testing';
let showError = false;
let errorTitle = 'Error';
let errorMessage = 'Error message';
let name = '';
let email = '';
let website = '';
let message = '';
let DisplayComments: Comment[];
let commentPage = 0;
let pageSize = 10;
async function onSubmit(event: SubmitEvent) {
// Validation
if (name === '' || message === '') {
errorMessage = 'You must enter a name and a message!';
showError = true;
return;
}
if (
name.length >= 40 ||
email.length >= 100 ||
website.length >= 200 ||
message.length >= 360
) {
errorMessage =
'Your name must be < 40 characters. Email < 100, website < 200 and message < 360.';
showError = true;
return;
}
const data = {
name,
email,
website,
message
};
const jsonData = JSON.stringify(data);
const res = await fetch(ApiURL + '/comment', {
method: 'POST',
body: jsonData
});
if (res.status >= 300) {
errorMessage = await res.text();
showError = true;
return;
}
clear();
getComments();
}
function clear() {
name = '';
email = '';
website = '';
message = '';
}
async function getComments() {
// Check if in localstorage already
let commentLS = localStorage.getItem('comments');
let comments: Comment[];
if (commentLS === null) {
// Not yet in storage
comments = await updateComments();
DisplayComments = comments;
return;
}
let newHash = await fetch(ApiURL + '/commenthash')
.then((res) => res.text())
.then((t) => {
return t;
});
let oldHash = localStorage.getItem('commenthash');
if (oldHash != newHash) {
DisplayComments = await updateComments();
return;
}
DisplayComments = JSON.parse(decodeURIComponent(window.atob(commentLS)));
}
async function fetchComments() {
let comments: Comment[];
comments = await fetch(ApiURL + '/comment')
.then((res) => res.json())
.then((data) => {
return data;
});
return comments;
}
async function updateComments() {
let comments = await fetchComments();
localStorage.setItem('comments', window.btoa(encodeURIComponent(JSON.stringify(comments))));
localStorage.setItem('commenthash', sha256(JSON.stringify(comments)));
return comments;
}
onMount(() => {
getComments();
});
</script>
<Errormodal bind:show={showError} bind:title={errorTitle} bind:message={errorMessage} />
<div class="">
<h2 class="font-semibold">Guestbook</h2>
<p>You can leave any comment you want right here in this guestbook! No signing up necessary.</p>
<form on:submit|preventDefault={onSubmit} class="mt-2 w-full grid gap-2">
<input
class="p-1 rounded bg- border-2 focus:border-pink-400 dark:focus:border-pink-700 outline-none"
type="text"
name="name"
placeholder="Nickname (max. 40 chars)"
bind:value={name}
/>
<input
class="p-1 rounded bg- border-2 focus:border-pink-400 dark:focus:border-pink-700 outline-none"
type="text"
name="email"
placeholder="Email (optional)"
bind:value={email}
/>
<input
class="p-1 rounded bg- border-2 focus:border-pink-400 dark:focus:border-pink-700 outline-none"
type="text"
name="website"
placeholder="Website (optional)"
bind:value={website}
/>
<textarea
class="p-1 rounded bg- border-2 focus:border-pink-400 dark:focus:border-pink-700 outline-none"
type="text"
name="message"
placeholder="Message (max. 360 chars)"
bind:value={message}
/>
<button
class="bg-blue-100 dark:bg-slate-700 border-slate-400 border-2 rounded-full py-2 w-fit px-10"
type="submit">Submit</button
>
</form>
<div>
{#if DisplayComments != null}
<div class="mt-4">
<button class="px-5 py-2 bg-slate-400 dark:bg-slate-800 dark:ring-slate-400 dark:ring-2 rounded-xl mx-1" on:click={() => {if (commentPage > 0) commentPage -= 1}}>Previous</button>
<button class="px-5 py-2 bg-slate-400 dark:bg-slate-800 dark:ring-slate-400 dark:ring-2 rounded-xl mx-1" on:click={() => commentPage += 1}>Next Page</button>
<p class="ml-2 mt-1 w-full md:w-max">Current page: {commentPage + 1}</p>
</div>
{#each DisplayComments.slice(commentPage * pageSize, commentPage * pageSize + pageSize) as c}
<GuestbookEntry comment={c} />
{/each}
{/if}
</div>
</div>