132 lines
3.6 KiB
Svelte
132 lines
3.6 KiB
Svelte
<script lang="ts">
|
|
import { enhance } from '$app/forms';
|
|
import { invalidateAll } from '$app/navigation';
|
|
import { resolve } from '$app/paths';
|
|
|
|
let { data } = $props();
|
|
|
|
let createNewArticlePopup = $state(false);
|
|
let loginPopup = $state(false);
|
|
let password = $state('');
|
|
let loginError = $state('');
|
|
let loginLoading = $state(false);
|
|
|
|
async function login() {
|
|
loginLoading = true;
|
|
loginError = '';
|
|
const res = await fetch('/admin/login', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ password })
|
|
});
|
|
if (res.ok) {
|
|
loginPopup = false;
|
|
password = '';
|
|
await invalidateAll();
|
|
} else {
|
|
loginError = 'Wrong password';
|
|
}
|
|
loginLoading = false;
|
|
}
|
|
</script>
|
|
|
|
<div class="text-center py-8 gap-2 flex align-center justify-center">
|
|
<h1 class="text-2xl">Blog (Stan's yapping corner)</h1>
|
|
{#if data.isAdmin}
|
|
<button
|
|
onclick={() => (createNewArticlePopup = true)}
|
|
class="bg-gray-800 rounded px-4 py-2 text-white font-bold cursor-pointer"
|
|
>Write new article</button
|
|
>
|
|
{:else}
|
|
<button
|
|
onclick={() => (loginPopup = true)}
|
|
class="text-xs text-gray-400 underline cursor-pointer">Admin login</button
|
|
>
|
|
{/if}
|
|
</div>
|
|
|
|
{#each data.blogArticles as article (article.slug)}
|
|
<div class="flex gap-2 px-4">
|
|
<a href={resolve('/blog/[slug]', { slug: article.slug })}>{article.title}</a>
|
|
{#if data.isAdmin && article.status === 'draft'}
|
|
<span class="text-xs uppercase text-orange-500">draft</span>
|
|
<a href={resolve('/blog/[slug]/edit', { slug: article.slug })} class="text-xs underline"
|
|
>edit</a
|
|
>
|
|
{/if}
|
|
<p>{article.createdAt?.toDateString()}</p>
|
|
</div>
|
|
{/each}
|
|
|
|
{#if createNewArticlePopup}
|
|
<div class="fixed inset-0 flex items-center justify-center bg-black/40">
|
|
<form
|
|
method="POST"
|
|
use:enhance={() => {
|
|
return async ({ result }) => {
|
|
if (result.type !== 'redirect') {
|
|
createNewArticlePopup = false;
|
|
}
|
|
};
|
|
}}
|
|
class="w-80 rounded bg-white p-6 shadow-xl"
|
|
>
|
|
<h2 class="mb-4 text-lg font-bold">Create New Article</h2>
|
|
<input
|
|
name="title"
|
|
placeholder="Article title"
|
|
required
|
|
class="mb-4 w-full rounded border border-gray-300 px-3 py-2 text-sm outline-none"
|
|
/>
|
|
<div class="flex justify-end gap-2">
|
|
<button
|
|
type="button"
|
|
onclick={() => (createNewArticlePopup = false)}
|
|
class="rounded px-3 py-2 text-sm cursor-pointer">Cancel</button
|
|
>
|
|
<button
|
|
type="submit"
|
|
class="rounded bg-gray-800 px-3 py-2 text-sm font-bold text-white cursor-pointer"
|
|
>Create draft</button
|
|
>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
{/if}
|
|
|
|
{#if loginPopup}
|
|
<div class="fixed inset-0 flex items-center justify-center bg-black/40">
|
|
<div class="w-80 rounded bg-white p-6 shadow-xl">
|
|
<h2 class="mb-4 text-lg font-bold">Admin login</h2>
|
|
<input
|
|
type="password"
|
|
bind:value={password}
|
|
placeholder="Password"
|
|
onkeydown={(e) => e.key === 'Enter' && login()}
|
|
class="mb-3 w-full rounded border border-gray-300 px-3 py-2 text-sm outline-none"
|
|
/>
|
|
{#if loginError}
|
|
<p class="mb-3 text-xs text-red-500">{loginError}</p>
|
|
{/if}
|
|
<div class="flex justify-end gap-2">
|
|
<button
|
|
type="button"
|
|
onclick={() => {
|
|
loginPopup = false;
|
|
loginError = '';
|
|
}}
|
|
class="rounded px-3 py-2 text-sm cursor-pointer">Cancel</button
|
|
>
|
|
<button
|
|
onclick={login}
|
|
disabled={loginLoading}
|
|
class="rounded bg-gray-800 px-3 py-2 text-sm font-bold text-white disabled:opacity-50 cursor-pointer disabled:cursor-not-allowed"
|
|
>
|
|
{loginLoading ? 'Logging in…' : 'Log in'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/if}
|