Files
personal-website/src/routes/blog/+page.svelte
2026-08-31 16:39:56 +00:00

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}