beatufitull front end for ozone modration ,, wit catpucoin and ebergarden !
ozone
moderation
1<script lang="ts">
2 import { session } from '$lib/stores/auth';
3 import { appTheme, type AppTheme } from '$lib/stores/ui';
4 import { createQuery } from '@tanstack/svelte-query';
5 import { Cat, LogOut, MenuIcon, Paintbrush, SearchIcon, TriangleAlert } from 'lucide-svelte';
6 import Button from './ui/Button.svelte';
7 import PopupMenu from './ui/PopupMenu.svelte';
8 import { navItems } from '$lib/nav';
9 import { ads } from '$lib/ads';
10
11 let { onSearch } = $props<{
12 onSearch: () => void;
13 }>();
14
15 async function handleLogout() {
16 if ($session) {
17 await $session.session.session.signOut();
18 }
19 localStorage.removeItem('meowzone-password-session');
20 session.set(null);
21 window.location.href = '/login';
22 }
23
24 function setAppTheme(theme: AppTheme) {
25 appTheme.set(theme);
26 }
27
28 const profileQuery = createQuery(() => ({
29 queryKey: ['profile'],
30 queryFn: async () => {
31 if (!$session) {
32 throw new Error('No active session');
33 }
34 const profile = await $session.agent.getProfile({ actor: $session.agent.did! });
35 return profile.data;
36 },
37 enabled: !!$session
38 }));
39
40 function handleChange(event: Event) {
41 const adsEnabled = (event.target as HTMLInputElement).checked;
42 ads.toggle(adsEnabled);
43 }
44</script>
45
46<div
47 class="mx-auto mt-5 mb-4 flex max-w-2xl items-center justify-between rounded-xl border border-ctp-surface1 px-2 py-2"
48>
49 <a class="ml-3 cursor-pointer" href="/">
50 <Cat class="text-ctp-subtext1 hover:text-ctp-text" />
51 </a>
52 <div class="flex items-center gap-2">
53 <Button
54 variant="ghost"
55 size="icon"
56 onclick={() => onSearch()}
57 className="items-center size-10"
58 aria-label="Menu"
59 >
60 <SearchIcon class="size-5" />
61 </Button>
62 <PopupMenu
63 originClass="origin-top-right"
64 wrapperClass="relative"
65 menuClass="absolute top-full right-0 z-50 w-48 rounded-md border border-ctp-surface1 bg-ctp-base p-1.5 shadow-lg"
66 >
67 <svelte:fragment slot="trigger" let:toggle>
68 <Button
69 variant="ghost"
70 size="icon"
71 onclick={() => toggle()}
72 className="items-center size-10"
73 aria-label="Menu"
74 >
75 <div class="size-8">
76 {#if profileQuery.isLoading}
77 <div class="size-8 rounded-full bg-ctp-surface1"></div>
78 {:else if profileQuery.isError || !profileQuery.data}
79 <div class="size-8 rounded-full bg-ctp-red/20">
80 <TriangleAlert class="size-full p-2 text-ctp-red" />
81 </div>
82 {:else if profileQuery.data && $session}
83 <img
84 src={profileQuery.data.avatar}
85 alt={profileQuery.data.displayName || profileQuery.data.handle}
86 class="size-8 rounded-full"
87 />
88 {/if}
89 </div>
90 </Button>
91 </svelte:fragment>
92
93 <svelte:fragment slot="content">
94 <div class="mb-1 flex items-center gap-2 px-2 py-1 text-xs text-ctp-subtext0">
95 <Paintbrush size={16} />
96 <span>Theme</span>
97 </div>
98 <div class="grid grid-cols-2 gap-2 px-2 py-1">
99 <button
100 type="button"
101 onclick={() => setAppTheme('catppuccin')}
102 class="cursor-pointer rounded border border-ctp-surface1 px-2 py-1 text-xs text-ctp-text hover:bg-ctp-surface0"
103 class:bg-ctp-surface0={$appTheme === 'catppuccin'}
104 >
105 Catppuccin
106 </button>
107 <button
108 type="button"
109 onclick={() => setAppTheme('evergarden')}
110 class="cursor-pointer rounded border border-ctp-surface1 px-2 py-1 text-xs text-ctp-text hover:bg-ctp-surface0"
111 class:bg-ctp-surface0={$appTheme === 'evergarden'}
112 >
113 Evergarden
114 </button>
115 </div>
116 <!-- <div class="my-2 border-t border-ctp-surface1"></div>
117 <div class="mb-1 px-2 py-1 text-xs text-ctp-subtext0">Accent</div>
118 <div class="flex gap-2 px-2 py-1">
119 {#each accentColours as colour}
120 <button
121 class="size-5 cursor-pointer rounded-full hover:opacity-80"
122 class:bg-ctp-sapphire={colour === 'sapphire'}
123 class:bg-ctp-mauve={colour === 'mauve'}
124 class:bg-ctp-lavender={colour === 'lavender'}
125 class:ring-2={$inputAccentColor === colour}
126 class:ring-ctp-overlay1={$inputAccentColor === colour}
127 class:text-ctp-subtext0={$inputAccentColor !== colour}
128 onclick={() => setInputAccentColor(colour)}
129 aria-label={`Set input accent color to ${colour}`}
130 >
131 </button>
132 {/each}
133 </div> -->
134 <div class="my-2 border-t border-ctp-surface1"></div>
135 <div>
136 <div class="flex items-center gap-2 px-2 py-1">
137 <input
138 type="checkbox"
139 id="ads-toggle"
140 class="cursor-pointer rounded accent-ctp-sapphire"
141 bind:checked={$ads}
142 onchange={handleChange}
143 />
144 <label for="ads-toggle" class="cursor-pointer text-ctp-text">Enable ads</label>
145 </div>
146 <div class="my-2 border-t border-ctp-surface1"></div>
147 <Button
148 variant="danger"
149 fullWidth={true}
150 size="sm"
151 onclick={handleLogout}
152 className="justify-start py-1.5 rounded-sm"
153 >
154 <LogOut size={18} class="mr-3" />
155 <span>Logout</span>
156 </Button>
157 </div></svelte:fragment
158 >
159 </PopupMenu>
160 <PopupMenu
161 originClass="origin-top-right"
162 wrapperClass="relative"
163 menuClass="absolute top-full right-0 z-50 w-40 rounded-md border border-ctp-surface1 bg-ctp-base p-1.5 shadow-lg"
164 >
165 <svelte:fragment slot="trigger" let:toggle>
166 <Button
167 variant="ghost"
168 size="icon"
169 onclick={() => toggle()}
170 className="items-center size-10"
171 aria-label="Menu"
172 >
173 <MenuIcon class="size-5" />
174 </Button>
175 </svelte:fragment>
176
177 <svelte:fragment slot="content">
178 {#each navItems as item (item.label)}
179 {@const Icon = item.icon}
180
181 <a href={item.href}>
182 <Button
183 variant="ghost"
184 fullWidth={true}
185 size="icon"
186 className="justify-start rounded-md hover:bg-ctp-surface0"
187 >
188 <span class="grid size-6 place-items-center">
189 <Icon size={16} />
190 </span>
191 <span>{item.label}</span>
192 </Button>
193 </a>
194 {/each}
195 </svelte:fragment>
196 </PopupMenu>
197 </div>
198</div>