Files
frontend/src/routes/(app)/+layout.svelte
Edwin Eames 7ac2c7c59e feat: add admin panel, stats API, and url support across frontend
- Add adminApi with full CRUD for users, companies, listings, oil-prices
- Add statsApi for fetching latest market price aggregates
- Add AdminTable component and /admin page for site management
- Add StatsPrice, UpdateUserRequest, UpdateOilPriceRequest types
- Add url field support in listing create/edit forms
- Update state SVG data for all 6 New England states
- Update county page to display richer listing info (phone, url, bio%)
- Misc layout and style updates across vendor and public routes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 11:34:31 -05:00

105 lines
4.2 KiB
Svelte
Executable File

<script lang="ts">
import { onMount } from "svelte";
import { writable } from "svelte/store";
import type { Writable } from 'svelte/store';
import '../../app.postcss'; // Import Tailwind CSS
import { user, darkMode, type User } from '$lib/states';
import { authApi } from '$lib/api';
// Initialize dark mode on mount to ensure data-theme is set
onMount(() => {
const isDark = $darkMode;
if (isDark) {
document.documentElement.setAttribute('data-theme', 'dark');
} else {
document.documentElement.setAttribute('data-theme', 'light');
}
});
// Placeholder for user store - in a real app, this would be managed by an auth library or context
let storedUser: User | null = null;
// Check for user session on mount
onMount(() => {
const storedUserString = localStorage.getItem('user');
if (storedUserString) {
storedUser = JSON.parse(storedUserString);
user.set(storedUser);
} else {
// No user stored
localStorage.removeItem('user');
user.set(null);
}
});
user.subscribe((newUser) => {
if (newUser) {
storedUser = newUser;
} else {
storedUser = null;
}
});
// Logout function - now async to call API to clear httpOnly cookie
const logout = async () => {
await authApi.logout();
user.set(null);
window.location.href = '/';
};
// Toggle dark mode
const toggleDarkMode = () => {
darkMode.update((value: boolean) => !value);
};
</script>
<div class="min-h-screen flex flex-col">
<header class="navbar bg-primary text-primary-content shadow-lg">
<div class="flex-1">
<a href="/" class="btn btn-ghost normal-case text-xl">Biz Hero</a>
</div>
<div class="flex-none flex items-center gap-2">
<button type="button" class="btn btn-ghost" on:click={toggleDarkMode}>
{#if !$darkMode}
<!-- Moon icon for dark mode -->
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z" clip-rule="evenodd" />
</svg>
{:else}
<!-- Sun icon for light mode -->
<svg class="w-5 h-5" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z" clip-rule="evenodd" />
</svg>
{/if}
</button>
{#if $user}
<div class="dropdown dropdown-end">
<button type="button" class="btn btn-ghost normal-case text-lg">
{$user.username}
</button>
<ul class="dropdown-content menu p-2 shadow bg-base-100 rounded-box w-52 text-black dark:text-gray-200">
<li><a href="/vendor">Dashboard</a></li>
{#if $user.username === 'Anekdotin'}
<li><a href="/admin">Admin Dashboard</a></li>
{/if}
<li><button type="button" on:click={logout}>Logout</button></li>
</ul>
</div>
{/if}
</div>
</header>
<main class="flex-grow container mx-auto p-4">
<slot />
</main>
<footer class="footer footer-center p-4 bg-base-300 text-base-content">
<div>
<p>Copyright © {new Date().getFullYear()} - All right reserved</p>
{#if !$user}
<a href="/login" class="link link-primary">Vendor Login</a>
{/if}
</div>
</footer>
</div>