fix: enable auth nav, turnstile deployment, country dropdown, profile routes

- Add PUBLIC_TURNSTILE_SITE_KEY as Docker build arg and Woodpecker CI arg
- Uncomment auth nav in Header and MobileNav (login/logout/profile links)
- Move ThemeToggle from header to footer
- Expand country dropdown from DACH-only to all European countries
- Replace profile route redirect with requireAuth guard
- Set cookie secure flag based on environment (secure in prod)
- Add error handling to admin markets page (403 instead of 500)
This commit is contained in:
2026-02-27 14:12:23 +01:00
parent e4e5fbf9f5
commit 83264b4b41
9 changed files with 103 additions and 39 deletions

View File

@@ -29,6 +29,7 @@ steps:
from_secret: registry_password
build_args:
- PUBLIC_API_BASE_URL=https://api.marktvogt.de
- PUBLIC_TURNSTILE_SITE_KEY=0x4AAAAAAACjLCV-78Q1loTPz
when:
- event: push
branch: main

View File

@@ -20,6 +20,9 @@ COPY . .
ARG PUBLIC_API_BASE_URL=https://api.marktvogt.de
ENV PUBLIC_API_BASE_URL=$PUBLIC_API_BASE_URL
ARG PUBLIC_TURNSTILE_SITE_KEY=1x00000000000000000000AA
ENV PUBLIC_TURNSTILE_SITE_KEY=$PUBLIC_TURNSTILE_SITE_KEY
RUN bun run build
# ─────────────────────────────────────────────

View File

@@ -1,10 +1,11 @@
import { dev } from '$app/environment';
import type { Cookies } from '@sveltejs/kit';
import type { AuthData } from '$lib/api/types.js';
const COOKIE_OPTS = {
path: '/',
httpOnly: true,
secure: false, // TODO: set to true in production
secure: !dev,
sameSite: 'lax' as const
};

View File

@@ -1,13 +1,20 @@
<script lang="ts">
import ThemeToggle from '$lib/components/ui/ThemeToggle.svelte';
</script>
<footer class="border-primary-800 bg-primary-950 border-t">
<div class="mx-auto max-w-7xl px-4 py-8 sm:px-6 lg:px-8">
<div class="flex flex-col items-center justify-between gap-4 sm:flex-row">
<p class="text-primary-400 text-sm">&copy; {new Date().getFullYear()} Marktvogt</p>
<nav class="flex gap-6">
<a href="/impressum" class="text-primary-400 hover:text-primary-200 text-sm">Impressum</a>
<a href="/datenschutz" class="text-primary-400 hover:text-primary-200 text-sm"
>Datenschutz</a
>
</nav>
<div class="flex items-center gap-6">
<nav class="flex gap-6">
<a href="/impressum" class="text-primary-400 hover:text-primary-200 text-sm">Impressum</a>
<a href="/datenschutz" class="text-primary-400 hover:text-primary-200 text-sm"
>Datenschutz</a
>
</nav>
<ThemeToggle />
</div>
</div>
</div>
</footer>

View File

@@ -1,7 +1,6 @@
<script lang="ts">
import type { ProfileData } from '$lib/api/types.js';
import MobileNav from './MobileNav.svelte';
import ThemeToggle from '$lib/components/ui/ThemeToggle.svelte';
interface Props {
user: ProfileData | null;
@@ -45,27 +44,25 @@
Admin
</a>
{/if}
<!-- TODO: re-enable auth nav when login/signup is ready
{#if user}
<a href="/profile" class="text-sm font-medium text-primary-200 hover:text-white">Profil</a>
<a href="/profile" class="text-primary-200 text-sm font-medium hover:text-white">
Profil
</a>
<form method="POST" action="/auth/abmelden">
<button type="submit" class="text-sm font-medium text-primary-200 hover:text-white">
<button type="submit" class="text-primary-200 text-sm font-medium hover:text-white">
Abmelden
</button>
</form>
<span class="text-sm text-primary-300">{user.display_name}</span>
<span class="text-primary-300 text-sm">{user.display_name}</span>
{:else}
<a href="/auth/anmelden" class="text-sm font-medium text-accent-300 hover:text-accent-200">
<a href="/auth/anmelden" class="text-accent-300 hover:text-accent-200 text-sm font-medium">
Anmelden
</a>
{/if}
-->
<ThemeToggle />
</nav>
<!-- Mobile: theme toggle + menu button -->
<!-- Mobile: menu button -->
<div class="flex items-center gap-2 md:hidden">
<ThemeToggle />
<button
type="button"
class="text-primary-300 rounded-md p-2 hover:text-white"

View File

@@ -30,22 +30,28 @@
Admin
</a>
{/if}
<!-- TODO: re-enable auth nav when login/signup is ready
{#if user}
<a href="/profile" class="text-sm font-medium text-primary-200 hover:text-white" onclick={onclose}>
<a
href="/profile"
class="text-primary-200 text-sm font-medium hover:text-white"
onclick={onclose}
>
Profil
</a>
<form method="POST" action="/auth/abmelden">
<button type="submit" class="text-sm font-medium text-primary-200 hover:text-white">
<button type="submit" class="text-primary-200 text-sm font-medium hover:text-white">
Abmelden
</button>
</form>
<span class="text-sm text-primary-300">{user.display_name}</span>
<span class="text-primary-300 text-sm">{user.display_name}</span>
{:else}
<a href="/auth/anmelden" class="text-sm font-medium text-accent-300 hover:text-accent-200" onclick={onclose}>
<a
href="/auth/anmelden"
class="text-accent-300 hover:text-accent-200 text-sm font-medium"
onclick={onclose}
>
Anmelden
</a>
{/if}
-->
</div>
</nav>

View File

@@ -1,6 +1,8 @@
import { error } from '@sveltejs/kit';
import { serverFetch } from '$lib/api/client.server.js';
import type { AdminMarketSummary, PaginationMeta } from '$lib/api/types.js';
import { ApiClientError } from '$lib/api/client.js';
import { buildSearchQuery } from '$lib/api/client.js';
import type { AdminMarketSummary, PaginationMeta } from '$lib/api/types.js';
import type { PageServerLoad } from './$types.js';
export const load: PageServerLoad = async ({ url, cookies }) => {
@@ -9,11 +11,19 @@ export const load: PageServerLoad = async ({ url, cookies }) => {
const page = url.searchParams.get('page') ?? '1';
const query = buildSearchQuery({ status, q, page, per_page: '20' });
const res = await serverFetch<AdminMarketSummary[]>(`/admin/markets?${query}`, cookies);
return {
markets: res.data,
meta: res.meta as PaginationMeta,
filters: { status, q }
};
try {
const res = await serverFetch<AdminMarketSummary[]>(`/admin/markets?${query}`, cookies);
return {
markets: res.data,
meta: res.meta as PaginationMeta,
filters: { status, q }
};
} catch (err) {
if (err instanceof ApiClientError) {
error(err.status, err.message);
}
throw err;
}
};

View File

@@ -117,12 +117,52 @@
class="bg-vellum focus:border-primary-500 focus:ring-primary-500 w-full rounded-lg border border-stone-300 px-3 py-2
text-sm shadow-sm focus:ring-2 focus:outline-none
dark:border-stone-600 dark:bg-stone-800"
value={form?.country ?? 'DE'}
>
<option value="DE" selected={form?.country !== 'AT' && form?.country !== 'CH'}>
Deutschland
</option>
<option value="AT" selected={form?.country === 'AT'}>Österreich</option>
<option value="CH" selected={form?.country === 'CH'}>Schweiz</option>
<option value="DE">Deutschland</option>
<option value="AT">Österreich</option>
<option value="CH">Schweiz</option>
<option disabled>──────────</option>
<option value="AL">Albanien</option>
<option value="AD">Andorra</option>
<option value="BE">Belgien</option>
<option value="BA">Bosnien und Herzegowina</option>
<option value="BG">Bulgarien</option>
<option value="DK">Dänemark</option>
<option value="EE">Estland</option>
<option value="FI">Finnland</option>
<option value="FR">Frankreich</option>
<option value="GR">Griechenland</option>
<option value="IE">Irland</option>
<option value="IS">Island</option>
<option value="IT">Italien</option>
<option value="XK">Kosovo</option>
<option value="HR">Kroatien</option>
<option value="LV">Lettland</option>
<option value="LI">Liechtenstein</option>
<option value="LT">Litauen</option>
<option value="LU">Luxemburg</option>
<option value="MT">Malta</option>
<option value="MD">Moldawien</option>
<option value="MC">Monaco</option>
<option value="ME">Montenegro</option>
<option value="NL">Niederlande</option>
<option value="MK">Nordmazedonien</option>
<option value="NO">Norwegen</option>
<option value="PL">Polen</option>
<option value="PT">Portugal</option>
<option value="RO">Rumänien</option>
<option value="SM">San Marino</option>
<option value="SE">Schweden</option>
<option value="RS">Serbien</option>
<option value="SK">Slowakei</option>
<option value="SI">Slowenien</option>
<option value="ES">Spanien</option>
<option value="CZ">Tschechien</option>
<option value="UA">Ukraine</option>
<option value="HU">Ungarn</option>
<option value="VA">Vatikanstadt</option>
<option value="GB">Vereinigtes Königreich</option>
</select>
</div>
</div>

View File

@@ -1,7 +1,6 @@
// TODO: remove this redirect when login/signup is re-enabled
import { redirect } from '@sveltejs/kit';
import { requireAuth } from '$lib/auth/guard.js';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = () => {
redirect(302, '/');
export const load: LayoutServerLoad = (event) => {
requireAuth(event);
};