feat: start dashboard
This commit is contained in:
parent
bacbc6db0e
commit
ec7aa9faba
50 changed files with 3624 additions and 18 deletions
55
Catalogger.Frontend/src/lib/api.ts
Normal file
55
Catalogger.Frontend/src/lib/api.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
export type User = {
|
||||
id: string;
|
||||
tag: string;
|
||||
avatar_url: string;
|
||||
};
|
||||
|
||||
export type PartialGuild = {
|
||||
id: string;
|
||||
name: string;
|
||||
icon_url: string;
|
||||
bot_in_guild: boolean;
|
||||
};
|
||||
|
||||
export type CurrentUser = {
|
||||
user: User;
|
||||
guilds: PartialGuild[];
|
||||
};
|
||||
|
||||
export type AuthCallback = CurrentUser & { token: string };
|
||||
|
||||
export type ApiError = {
|
||||
error_code: string;
|
||||
message: string;
|
||||
};
|
||||
|
||||
export const TOKEN_KEY = "catalogger-token";
|
||||
|
||||
export default async function apiFetch<T>(
|
||||
method: "GET" | "POST" | "PATCH" | "DELETE",
|
||||
path: string,
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
body: any = null,
|
||||
) {
|
||||
const token = localStorage.getItem(TOKEN_KEY);
|
||||
const headers = {
|
||||
...(body != null
|
||||
? { "Content-Type": "application/json; charset=utf-8" }
|
||||
: {}),
|
||||
...(token ? { Authorization: token } : {}),
|
||||
};
|
||||
|
||||
const reqBody = body ? JSON.stringify(body) : undefined;
|
||||
|
||||
console.debug("Sending", method, "request to", path, "with body", reqBody);
|
||||
|
||||
const resp = await fetch(path, {
|
||||
method,
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
headers,
|
||||
});
|
||||
if (resp.status < 200 || resp.status > 299)
|
||||
throw (await resp.json()) as ApiError;
|
||||
|
||||
return (await resp.json()) as T;
|
||||
}
|
||||
51
Catalogger.Frontend/src/lib/components/Navbar.svelte
Normal file
51
Catalogger.Frontend/src/lib/components/Navbar.svelte
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
import { TOKEN_KEY, type User } from "$lib/api";
|
||||
import { addToast } from "$lib/toast";
|
||||
import {
|
||||
Button,
|
||||
Navbar,
|
||||
NavbarBrand,
|
||||
NavbarToggler,
|
||||
Collapse,
|
||||
Nav,
|
||||
NavItem,
|
||||
NavLink,
|
||||
} from "@sveltestrap/sveltestrap";
|
||||
|
||||
export let user: User | null;
|
||||
|
||||
let isOpen = false;
|
||||
|
||||
const logOut = async () => {
|
||||
localStorage.removeItem(TOKEN_KEY);
|
||||
|
||||
addToast({ header: "Logged out", body: "Successfully logged out." });
|
||||
|
||||
await goto("/", { invalidateAll: true });
|
||||
};
|
||||
</script>
|
||||
|
||||
<Navbar expand="lg">
|
||||
<NavbarBrand href="/">Catalogger</NavbarBrand>
|
||||
<NavbarToggler on:click={() => (isOpen = !isOpen)} />
|
||||
<Collapse {isOpen} navbar expand="lg">
|
||||
<Nav class="ms-auto" navbar>
|
||||
<NavItem>
|
||||
<NavLink href="/">Home</NavLink>
|
||||
</NavItem>
|
||||
{#if user}
|
||||
<NavItem>
|
||||
<NavLink href="/dash">Dashboard</NavLink>
|
||||
</NavItem>
|
||||
<NavItem>
|
||||
<NavLink on:click={logOut}>Log out</NavLink>
|
||||
</NavItem>
|
||||
{:else}
|
||||
<NavItem>
|
||||
<NavLink href="/api/authorize">Log in with Discord</NavLink>
|
||||
</NavItem>
|
||||
{/if}
|
||||
</Nav>
|
||||
</Collapse>
|
||||
</Navbar>
|
||||
1
Catalogger.Frontend/src/lib/index.ts
Normal file
1
Catalogger.Frontend/src/lib/index.ts
Normal file
|
|
@ -0,0 +1 @@
|
|||
// place files you want to import through the `$lib` alias in this folder.
|
||||
37
Catalogger.Frontend/src/lib/toast.ts
Normal file
37
Catalogger.Frontend/src/lib/toast.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { writable } from "svelte/store";
|
||||
|
||||
export interface ToastData {
|
||||
header?: string;
|
||||
body: string;
|
||||
duration?: number;
|
||||
}
|
||||
|
||||
interface IdToastData extends ToastData {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export const toastStore = writable<IdToastData[]>([]);
|
||||
|
||||
let maxId = 0;
|
||||
|
||||
export const addToast = (data: ToastData) => {
|
||||
const id = maxId++;
|
||||
|
||||
toastStore.update((toasts) => (toasts = [...toasts, { ...data, id }]));
|
||||
|
||||
if (data.duration !== -1) {
|
||||
setTimeout(() => {
|
||||
toastStore.update(
|
||||
(toasts) => (toasts = toasts.filter((toast) => toast.id !== id)),
|
||||
);
|
||||
}, data.duration ?? 5000);
|
||||
}
|
||||
|
||||
return id;
|
||||
};
|
||||
|
||||
export const delToast = (id: number) => {
|
||||
toastStore.update(
|
||||
(toasts) => (toasts = toasts.filter((toast) => toast.id !== id)),
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue