Skip to content

Commit 6dbe6d3

Browse files
committed
Add site settings management and admin config pages
Introduces a new site settings model, Svelte store, and API endpoints for managing site configuration and modules. Adds admin pages for site configuration and module management, updates Navbar and layout to use dynamic site name and favicon, and enhances Sidebar with nested admin navigation. Removes the old admin landing page and redirects /admin to /admin/users.
1 parent 4b96e6e commit 6dbe6d3

File tree

11 files changed

+773
-31
lines changed

11 files changed

+773
-31
lines changed

src/lib/components/Navbar.svelte

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,29 @@
1-
<script>
2-
// @ts-nocheck
1+
<script lang="ts">
32
import UserMenu from '../../routes/utils/widgets/UserMenu.svelte';
43
import { Avatar, Button, DarkMode, NavBrand, NavHamburger, Navbar, P, Dropdown, DropdownItem } from 'flowbite-svelte';
54
import { steamStore } from '$lib/stores/steamStore';
65
import { goto } from '$app/navigation';
76
import { onMount } from 'svelte';
87
import { regionStore } from '$lib/stores/regionStore';
8+
import { siteSettingsStore, loadSiteSettings } from '$lib/stores/siteSettingsStore';
99
import { ChevronDownOutline } from 'flowbite-svelte-icons';
1010
11-
/** @type {{fluid?: boolean, drawerHidden?: boolean, list?: boolean}} */
12-
let { fluid = true, drawerHidden = $bindable(false), list = false } = $props();
11+
interface Props {
12+
fluid?: boolean;
13+
drawerHidden?: boolean;
14+
list?: boolean;
15+
}
16+
17+
let { fluid = true, drawerHidden = $bindable(false), list = false }: Props = $props();
1318
1419
let loading = $state(true);
1520
let currentRegion = $state('ar');
1621
let regionDropOpen = $state(false);
1722
1823
onMount(() => {
24+
// Load site settings
25+
loadSiteSettings();
26+
1927
const unsubscribe = steamStore.subscribe((value) => {
2028
if (value !== undefined) {
2129
loading = false;
@@ -40,8 +48,8 @@
4048
<NavContainer class="mb-px mt-px px-1" {fluid}>
4149
<NavHamburger onClick={() => (drawerHidden = !drawerHidden)} class="m-0 me-3 md:block lg:hidden" />
4250
<NavBrand href="/" class={list ? 'w-40' : 'lg:w-60'}>
43-
<img src="/images/favicon.png" class="me-2.5 h-6 sm:h-8" alt="Flowbite Logo" />
44-
<span class="ml-px self-center whitespace-nowrap text-xl font-semibold dark:text-white sm:text-2xl"> Electric Panel </span>
51+
<img src={$siteSettingsStore?.faviconPath || '/images/favicon.png'} class="me-2.5 h-6 sm:h-8" alt="Site Logo" />
52+
<span class="ml-px self-center whitespace-nowrap text-xl font-semibold dark:text-white sm:text-2xl"> {$siteSettingsStore?.siteName || 'Electric Panel'} </span>
4553
</NavBrand>
4654
<!-- Region selector -->
4755
<div class="ms-auto flex items-center gap-2 text-gray-500 dark:text-gray-400 sm:order-2">

src/lib/components/Sidebar.svelte

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script lang="ts">
22
import { page } from '$app/stores';
33
import { Sidebar, SidebarDropdownWrapper, SidebarGroup, SidebarItem, SidebarWrapper } from 'flowbite-svelte';
4-
import { AngleDownOutline, AngleUpOutline, ChartOutline, SearchOutline, CogOutline, HomeOutline } from 'flowbite-svelte-icons';
4+
import { AngleDownOutline, AngleUpOutline, ChartOutline, SearchOutline, CogOutline, HomeOutline, UsersGroupOutline, GridOutline } from 'flowbite-svelte-icons';
55
import MgeOutline from '$lib/components/icons/MgeOutline.svelte';
66
import LeaderboardOutline from '$lib/components/icons/LeaderboardOutline.svelte';
77
import GamesOutline from '$lib/components/icons/GamesOutline.svelte';
@@ -43,7 +43,16 @@
4343
href: '/whois'
4444
};
4545
46-
const adminItem: NavItem = { name: 'Admin', href: '/admin', icon: CogOutline };
46+
const adminItem: NavItem = {
47+
name: 'Admin',
48+
icon: CogOutline,
49+
href: '#',
50+
children: [
51+
{ name: 'Users', href: '/admin/users', icon: UsersGroupOutline },
52+
{ name: 'Site Configuration', href: '/admin/config', icon: CogOutline },
53+
{ name: 'Modules', href: '/admin/modules', icon: GridOutline }
54+
]
55+
};
4756
4857
let mgeItem = $derived({
4958
name: 'MGE',
@@ -104,11 +113,24 @@
104113
{/each}
105114
{#if showAdmin}
106115
<li class="my-2 border-t border-gray-200 dark:border-gray-700"></li>
107-
<SidebarItem label={adminItem.name} href={adminItem.href} spanClass="ml-3" class={styles.item}>
116+
<SidebarDropdownWrapper label={adminItem.name} class="pr-3">
108117
<svelte:fragment slot="icon">
109118
<adminItem.icon class={styles.icon} />
110119
</svelte:fragment>
111-
</SidebarItem>
120+
<svelte:fragment slot="arrowdown">
121+
<AngleDownOutline strokeWidth="3.3" size="sm" />
122+
</svelte:fragment>
123+
<svelte:fragment slot="arrowup">
124+
<AngleUpOutline strokeWidth="3.3" size="sm" />
125+
</svelte:fragment>
126+
{#each adminItem.children ?? [] as child}
127+
<SidebarItem label={child.name} href={child.href} spanClass="ml-3" class={`${styles.item} pl-9`}>
128+
<svelte:fragment slot="icon">
129+
<child.icon class={styles.icon} />
130+
</svelte:fragment>
131+
</SidebarItem>
132+
{/each}
133+
</SidebarDropdownWrapper>
112134
{/if}
113135
</SidebarGroup>
114136
</nav>

src/lib/models/siteSettings.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import mongoose from 'mongoose';
2+
3+
interface ISiteSettings {
4+
siteName: string;
5+
siteDescription?: string;
6+
faviconPath?: string;
7+
enabledRegions: string[];
8+
enabledModules: {
9+
mge: boolean;
10+
whois: boolean;
11+
tf2pickup: boolean;
12+
};
13+
createdAt: Date;
14+
updatedAt: Date;
15+
}
16+
17+
const SiteSettingsSchema = new mongoose.Schema<ISiteSettings>({
18+
siteName: {
19+
type: String,
20+
required: true,
21+
default: 'Electric Panel'
22+
},
23+
siteDescription: {
24+
type: String,
25+
default: ''
26+
},
27+
faviconPath: {
28+
type: String,
29+
default: '/images/favicon.png'
30+
},
31+
enabledRegions: {
32+
type: [String],
33+
default: ['ar', 'br']
34+
},
35+
enabledModules: {
36+
mge: {
37+
type: Boolean,
38+
default: true
39+
},
40+
whois: {
41+
type: Boolean,
42+
default: true
43+
},
44+
tf2pickup: {
45+
type: Boolean,
46+
default: false
47+
}
48+
}
49+
}, {
50+
timestamps: true
51+
});
52+
53+
// Ensure only one settings document exists
54+
SiteSettingsSchema.index({}, { unique: true });
55+
56+
export const SiteSettings = mongoose.model<ISiteSettings>('SiteSettings', SiteSettingsSchema);
57+
58+
export type { ISiteSettings };
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { writable } from 'svelte/store';
2+
3+
interface SiteSettings {
4+
siteName: string;
5+
siteDescription: string;
6+
faviconPath: string;
7+
enabledRegions: string[];
8+
enabledModules: {
9+
mge: boolean;
10+
whois: boolean;
11+
tf2pickup: boolean;
12+
};
13+
}
14+
15+
const defaultSettings: SiteSettings = {
16+
siteName: 'Electric Panel',
17+
siteDescription: '',
18+
faviconPath: '/images/favicon.png',
19+
enabledRegions: ['ar', 'br'],
20+
enabledModules: {
21+
mge: true,
22+
whois: true,
23+
tf2pickup: false
24+
}
25+
};
26+
27+
export const siteSettingsStore = writable<SiteSettings>(defaultSettings);
28+
29+
// Function to load settings from API
30+
export async function loadSiteSettings() {
31+
try {
32+
const response = await fetch('/api/admin/settings');
33+
const result = await response.json();
34+
35+
if (response.ok) {
36+
siteSettingsStore.set(result);
37+
}
38+
} catch (error) {
39+
console.error('Error loading site settings:', error);
40+
}
41+
}
42+
43+
// Function to update settings
44+
export function updateSiteSettings(settings: Partial<SiteSettings>) {
45+
siteSettingsStore.update(current => ({
46+
...current,
47+
...settings
48+
}));
49+
}

src/routes/+layout.svelte

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,43 @@
1-
<script>
1+
<script lang="ts">
22
import '../app.pcss';
33
import '/node_modules/flag-icons/css/flag-icons.min.css';
44
import Navbar from '../lib/components/Navbar.svelte';
55
import Sidebar from '../lib/components/Sidebar.svelte';
66
let drawerHidden = $state(false);
77
import { onMount } from 'svelte';
88
import { steamStore } from '$lib/stores/steamStore';
9+
import { siteSettingsStore, loadSiteSettings } from '$lib/stores/siteSettingsStore';
910
10-
/** @type {{data: any, children?: import('svelte').Snippet}} */
11-
let { data, children } = $props();
11+
interface Props {
12+
data: any;
13+
children?: import('svelte').Snippet;
14+
}
15+
16+
let { data, children }: Props = $props();
1217
1318
onMount(() => {
1419
if (data.user) {
1520
steamStore.set(data.user);
1621
}
22+
// Load site settings
23+
loadSiteSettings();
24+
});
25+
26+
// Update favicon when settings change
27+
$effect(() => {
28+
if (typeof window !== 'undefined' && $siteSettingsStore?.faviconPath) {
29+
const link = document.querySelector('link[rel="icon"]') as HTMLLinkElement;
30+
if (link) {
31+
link.href = $siteSettingsStore.faviconPath;
32+
}
33+
}
34+
});
35+
36+
// Update page title when settings change
37+
$effect(() => {
38+
if (typeof window !== 'undefined' && $siteSettingsStore?.siteName) {
39+
document.title = $siteSettingsStore.siteName;
40+
}
1741
});
1842
</script>
1943

src/routes/admin/+page.server.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { redirect } from '@sveltejs/kit';
2+
import type { PageServerLoad } from './$types';
3+
4+
export const load: PageServerLoad = async () => {
5+
throw redirect(302, '/admin/users');
6+
};

src/routes/admin/+page.svelte

Lines changed: 0 additions & 18 deletions
This file was deleted.

0 commit comments

Comments
 (0)