chore: migrate to v3 and adopt Doppler for secrets management #3

Open
kamil wants to merge 574 commits from v3-cleanup into main
3 changed files with 65 additions and 8 deletions
Showing only changes of commit 60a9540ef7 - Show all commits

View File

@@ -199,7 +199,7 @@
<div class="min-h-screen flex flex-col" class:pb-24={audioStore.active}>
<!-- Navigation progress bar — shown while SSR is running for any page transition -->
{#if navigating}
<div class="fixed top-0 left-0 right-0 z-[100] h-0.5 bg-zinc-800">
<div class="fixed top-0 left-0 right-0 z-[100] h-1 bg-zinc-800">
<div class="h-full bg-amber-400 animate-progress-bar"></div>
</div>
{/if}
@@ -251,8 +251,26 @@
{/key}
</main>
<footer class="border-t border-zinc-800 text-zinc-600 text-xs text-center py-4">
libnovel
<footer class="border-t border-zinc-800 mt-auto">
<div class="max-w-6xl mx-auto px-4 py-5 flex flex-col sm:flex-row items-center justify-between gap-3">
<span class="text-zinc-500 text-sm font-semibold tracking-tight">libnovel</span>
<nav class="flex items-center gap-5 text-xs text-zinc-600">
<a href="/books" class="hover:text-zinc-400 transition-colors">Library</a>
<a href="/browse" class="hover:text-zinc-400 transition-colors">Discover</a>
<a
href="https://novelfire.net"
target="_blank"
rel="noopener noreferrer"
class="hover:text-zinc-400 transition-colors flex items-center gap-1"
>
novelfire.net
<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
</svg>
</a>
</nav>
</div>
</footer>
</div>

View File

@@ -152,7 +152,7 @@
<div class="flex items-center gap-2 flex-wrap">
<h1 class="text-2xl font-bold text-zinc-100 leading-tight">{data.book.title}</h1>
{#if !data.inLib}
<span class="text-xs px-2 py-0.5 rounded-full bg-zinc-700 text-zinc-400 border border-zinc-600 shrink-0">
<span class="text-xs px-2 py-0.5 rounded-full bg-zinc-700 text-zinc-400 border border-zinc-600 shrink-0" title="This book was fetched live from the source and is not yet in your library">
not in library
</span>
{/if}

View File

@@ -1,10 +1,23 @@
<script lang="ts">
import { enhance } from '$app/forms';
import { navigating } from '$app/state';
import type { PageData, ActionData } from './$types';
import type { NovelListing } from './+page.server';
let { data, form }: { data: PageData; form: ActionData } = $props();
// Track which novel card is currently being navigated to
let loadingSlug = $state<string | null>(null);
// Clear loading state when navigation ends (success or failure)
$effect(() => {
if (!navigating) loadingSlug = null;
});
function handleNovelClick(slug: string) {
loadingSlug = slug;
}
// Filter options
const genres = [
{ value: 'all', label: 'All Genres' },
@@ -282,9 +295,12 @@
<!-- ── Grid view ─────────────────────────────────────────────────────── -->
<div class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4">
{#each data.novels as novel}
{@const isLoading = loadingSlug === novel.slug}
<a
href="/books/{novel.slug}"
class="group flex flex-col rounded-lg overflow-hidden bg-zinc-800 border border-zinc-700 hover:border-zinc-500 transition-colors relative"
onclick={() => handleNovelClick(novel.slug)}
class="group flex flex-col rounded-lg overflow-hidden bg-zinc-800 border transition-colors relative
{isLoading ? 'border-amber-400/60' : 'border-zinc-700 hover:border-zinc-500'}"
>
<!-- Cover -->
<div class="aspect-[2/3] bg-zinc-900 overflow-hidden relative">
@@ -313,6 +329,15 @@
{novel.rating}
</span>
{/if}
<!-- Loading overlay -->
{#if isLoading}
<div class="absolute inset-0 bg-zinc-900/70 flex items-center justify-center">
<svg class="w-8 h-8 animate-spin text-amber-400" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
</div>
{/if}
</div>
<!-- Info -->
@@ -355,14 +380,18 @@
<!-- ── List view ─────────────────────────────────────────────────────── -->
<div class="flex flex-col gap-2">
{#each data.novels as novel}
<div class="flex items-center gap-4 bg-zinc-800 border border-zinc-700 rounded-lg px-4 py-3 hover:border-zinc-500 transition-colors">
{@const isLoading = loadingSlug === novel.slug}
<div
class="flex items-center gap-4 bg-zinc-800 border rounded-lg px-4 py-3 transition-colors
{isLoading ? 'border-amber-400/60' : 'border-zinc-700 hover:border-zinc-500'}"
>
<!-- Rank / index -->
{#if novel.rank}
<span class="text-amber-400 font-bold text-sm w-8 shrink-0 text-right">{novel.rank}</span>
{/if}
<!-- Cover thumbnail -->
<div class="w-10 h-14 shrink-0 rounded overflow-hidden bg-zinc-900">
<div class="w-10 h-14 shrink-0 rounded overflow-hidden bg-zinc-900 relative">
{#if novel.cover}
<img src={novel.cover} alt={novel.title} class="w-full h-full object-cover" loading="lazy" />
{:else}
@@ -373,6 +402,14 @@
</svg>
</div>
{/if}
{#if isLoading}
<div class="absolute inset-0 bg-zinc-900/70 flex items-center justify-center">
<svg class="w-4 h-4 animate-spin text-amber-400" fill="none" viewBox="0 0 24 24">
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
</svg>
</div>
{/if}
</div>
<!-- Title + meta -->
@@ -380,7 +417,9 @@
{#if novel.slug}
<a
href="/books/{novel.slug}"
class="text-sm font-semibold text-zinc-100 hover:text-amber-400 transition-colors line-clamp-1"
onclick={() => handleNovelClick(novel.slug)}
class="text-sm font-semibold transition-colors line-clamp-1
{isLoading ? 'text-amber-400' : 'text-zinc-100 hover:text-amber-400'}"
>
{novel.title}
</a>