Auth state rework

This commit is contained in:
Jakob Kordež 2024-12-10 19:06:49 +01:00
parent 5b9bce4796
commit dde9f5d4fb
29 changed files with 808 additions and 767 deletions

View File

@ -14,30 +14,30 @@
"format": "prettier --write ."
},
"devDependencies": {
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/free-solid-svg-icons": "^6.7.1",
"@sveltejs/adapter-auto": "^3.3.1",
"@sveltejs/kit": "^2.7.3",
"@sveltejs/vite-plugin-svelte": "^4.0.0",
"@sveltejs/kit": "^2.9.1",
"@sveltejs/vite-plugin-svelte": "^4.0.2",
"@types/async-lock": "^1.4.2",
"@types/eslint": "^9.6.1",
"async-lock": "^1.4.1",
"autoprefixer": "^10.4.20",
"axios": "^1.7.7",
"daisyui": "^4.12.13",
"eslint": "^9.13.0",
"axios": "^1.7.9",
"daisyui": "^4.12.20",
"eslint": "^9.16.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.46.0",
"globals": "^15.11.0",
"eslint-plugin-svelte": "^2.46.1",
"globals": "^15.13.0",
"jwt-decode": "^4.0.0",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.7",
"svelte": "^5.1.2",
"svelte-check": "^4.0.5",
"prettier": "^3.4.2",
"prettier-plugin-svelte": "^3.3.2",
"svelte": "^5.10.0",
"svelte-check": "^4.1.1",
"svelte-fa": "^4.0.3",
"tailwindcss": "^3.4.14",
"typescript": "^5.6.3",
"typescript-eslint": "^8.11.0",
"vite": "^5.4.10",
"vitest": "^2.1.3"
"tailwindcss": "^3.4.16",
"typescript": "^5.7.2",
"typescript-eslint": "^8.18.0",
"vite": "^5.4.11",
"vitest": "^2.1.8"
}
}

1072
svelte-app/pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@ -1,12 +1,13 @@
<script>
import { Role } from '$lib/enums/role.enum';
import { logout } from '$lib/stores/auth-store';
import { userStore } from '$lib/stores/user-store';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
import { faUserCircle } from '@fortawesome/free-solid-svg-icons';
import Fa from 'svelte-fa';
import Clock from './clock.svelte';
let userDropOpen = $state(false);
const auth = getAuthContext();
</script>
<div
@ -20,11 +21,11 @@
<!-- <ThemeToggle /> -->
<!-- User -->
{#if $userStore}
{#if auth.user}
<div class="relative">
<button class="btn btn-ghost" onclick={() => (userDropOpen = !userDropOpen)}>
<Fa icon={faUserCircle} class="h-5 w-5" />
<span>{$userStore.username}</span>
<span>{auth.user.username}</span>
</button>
<div class="absolute right-2 top-full z-[1] pt-1 {userDropOpen ? '' : 'hidden'}">
@ -40,7 +41,7 @@
<li>
<a href="/profile" onclick={() => (userDropOpen = false)}>Profil</a>
</li>
{#if $userStore.roles.includes(Role.Admin)}
{#if auth.user.roles.includes(Role.Admin)}
<li>
<a href="/admin" class="btn-warning" onclick={() => (userDropOpen = false)}>
Admin panel
@ -50,8 +51,8 @@
<li>
<button
onclick={() => {
logout();
window.location.reload();
auth.logout();
userDropOpen = false;
}}
>
Odjava

View File

@ -1,12 +1,14 @@
<script lang="ts">
import { Fa } from 'svelte-fa';
import { faWarning } from '@fortawesome/free-solid-svg-icons';
import { userStore } from '$lib/stores/user-store';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
const { class: className = '' }: { class?: string } = $props();
const auth = getAuthContext();
</script>
{#if $userStore?.passwordResetRequired === true}
{#if auth.user?.passwordResetRequired === true}
<div class="alert alert-warning {className}">
<Fa icon={faWarning} class="shrink-0 h-6 w-6" />
<span>Opozorilo: Prosim, zamenjaj geslo!</span>

View File

@ -0,0 +1,111 @@
import { apiFunctions } from '$lib/api';
import AsyncLock from 'async-lock';
import { isAxiosError } from 'axios';
import { getContext, setContext, tick } from 'svelte';
import { jwtDecode } from 'jwt-decode';
import { browser } from '$app/environment';
import { type User } from '$lib/interfaces/user.interface';
type AuthState = {
accessToken: string | null;
refreshToken: string | null;
};
function createAuthState() {
const lock = new AsyncLock();
let authState = $state<AuthState>(fetchStorage());
let user = $state<User | null>();
$effect(() => {
if (!browser) return;
localStorage.setItem(STORAGE_KEY, JSON.stringify(authState));
});
refreshUser();
function login(username: string, password: string) {
return apiFunctions
.login(username, password)
.then((res) => {
authState = res;
tick().then(refreshUser);
return true;
})
.catch(() => false);
}
function refreshUser() {
console.log('Refreshing user');
return getAccessToken().then((token) => {
if (!token) user = null;
else apiFunctions.getMe(token).then((res) => (user = res));
});
}
async function getAccessToken(): Promise<string | null> {
return await lock.acquire('isValid', async () => {
const { accessToken } = authState;
// Check access token validity
if (accessToken) {
const exp = jwtDecode(accessToken).exp ?? 0;
if (Date.now() < exp * 1000) {
return accessToken;
}
}
const { refreshToken } = fetchStorage();
// Check refresh token validity
if (refreshToken) {
const exp = jwtDecode(refreshToken).exp ?? 0;
if (Date.now() < exp * 1000) {
// Try to refresh
try {
authState = await apiFunctions.refresh(refreshToken);
console.log('Token refreshed');
return authState.accessToken;
} catch (e) {
if (isAxiosError(e) && e.response?.status === 401) authState.refreshToken = null;
}
}
}
return null;
});
}
return {
get user() {
return user;
},
login,
getAccessToken,
refreshUser,
async logout(): Promise<void> {
try {
await apiFunctions.logout((await getAccessToken())!);
} catch (e) {
console.error(e);
}
authState = { accessToken: null, refreshToken: null };
tick().then(refreshUser);
}
};
}
const STORAGE_KEY = 'auth-store';
function fetchStorage(): AuthState {
if (!browser) return { accessToken: null, refreshToken: null };
const value = localStorage.getItem(STORAGE_KEY) || '{}';
return JSON.parse(value) as AuthState;
}
const CONTEXT_KEY = 'AUTH_CONTEXT';
export function createAuthContext() {
return setContext(CONTEXT_KEY, createAuthState());
}
export function getAuthContext() {
return getContext<ReturnType<typeof createAuthContext>>(CONTEXT_KEY);
}

View File

@ -1,71 +0,0 @@
import { apiFunctions } from '$lib/api';
import AsyncLock from 'async-lock';
import { isAxiosError } from 'axios';
import { get, writable } from 'svelte/store';
import { jwtDecode } from 'jwt-decode';
import { browser } from '$app/environment';
const lock = new AsyncLock();
type AuthStore = {
accessToken: string | null;
refreshToken: string | null;
};
const state: AuthStore = (() => {
if (!browser) return { accessToken: null, refreshToken: null };
const value = localStorage.getItem('auth-store') || '{}';
return JSON.parse(value) as AuthStore;
})();
export const authStore = writable<AuthStore>(state);
authStore.subscribe((value) => {
if (!browser) return;
localStorage.setItem('auth-store', JSON.stringify(value));
});
export async function isAuthValid(): Promise<boolean> {
return lock.acquire<boolean>('isValid', async () => {
const { accessToken, refreshToken } = get(authStore);
// Check access token validity
if (accessToken) {
const exp = jwtDecode(accessToken).exp ?? 0;
if (Date.now() < exp * 1000) {
return true;
} else authStore.update((v) => ({ ...v, accessToken: null }));
}
// Check refresh token validity
if (refreshToken) {
const exp = jwtDecode(refreshToken).exp ?? 0;
if (Date.now() < exp * 1000) {
// Try to refresh
try {
authStore.set(await apiFunctions.refresh(refreshToken));
return true;
} catch (e) {
if (isAxiosError(e) && e.response?.status === 401)
authStore.update((v) => ({ ...v, refreshToken: null }));
}
}
}
return false;
});
}
export async function getAccessToken(): Promise<string | null> {
const isValid = await isAuthValid();
return isValid ? get(authStore).accessToken : null;
}
export function logout(): void {
try {
apiFunctions.logout(get(authStore).accessToken!);
} catch (e) {
console.error(e);
}
authStore.set({ accessToken: null, refreshToken: null });
}

View File

@ -1,19 +0,0 @@
import { apiFunctions } from '$lib/api';
import type { User } from '$lib/interfaces/user.interface';
import { get, writable } from 'svelte/store';
import { authStore, getAccessToken } from './auth-store';
export const userStore = writable<User | null>();
export const refreshUser = () =>
getAccessToken()
.then((token) => {
if (!token) return null;
return apiFunctions.getMe(token);
})
.then(userStore.set);
authStore.subscribe((auth) => {
if (!auth.refreshToken) return userStore.set(null);
if (!get(userStore)) refreshUser();
});

View File

@ -1,6 +1,10 @@
<script lang="ts">
import Header from '$lib/components/header.svelte';
import { createAuthContext } from '$lib/stores/auth-state.svelte';
import '../app.css';
createAuthContext();
let { children } = $props();
</script>

View File

@ -2,7 +2,9 @@
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { Role } from '$lib/enums/role.enum';
import { userStore } from '$lib/stores/user-store';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
const auth = getAuthContext();
const pathname = $derived($page.url.pathname);
@ -18,8 +20,8 @@
];
$effect(() => {
if ($userStore === undefined) return;
if (!$userStore?.roles.includes(Role.Admin)) goto('/');
if (auth.user === undefined) return;
if (!auth.user?.roles.includes(Role.Admin)) goto('/');
});
let { children } = $props();

View File

@ -2,11 +2,22 @@
import { faAdd } from '@fortawesome/free-solid-svg-icons';
import Fa from 'svelte-fa';
import { getUTCString } from '$lib/util/date.util';
import type { PageData } from './$types';
import PrivateTag from '$lib/components/private-tag.svelte';
import Loading from '$lib/components/loading.svelte';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
import { apiFunctions } from '$lib/api';
export let data: PageData;
const auth = getAuthContext();
const eventsP = auth
.getAccessToken()
.then((token) => {
if (!token) return [];
return apiFunctions.getAllEvents(token);
})
.then((events) => {
return events.sort((a, b) => b.createdAt.valueOf() - a.createdAt.valueOf());
});
</script>
<div>
@ -19,7 +30,7 @@
</a>
</div>
{#await data.events}
{#await eventsP}
<Loading />
{:then events}
<div class="overflow-x-auto">

View File

@ -1,16 +0,0 @@
import { apiFunctions } from '$lib/api';
import { getAccessToken } from '$lib/stores/auth-store';
import type { PageLoad } from './$types';
export const load: PageLoad = () => {
return {
events: getAccessToken()
.then((token) => {
if (!token) return [];
return apiFunctions.getAllEvents(token);
})
.then((events) => {
return events.sort((a, b) => b.createdAt.valueOf() - a.createdAt.valueOf());
})
};
};

View File

@ -1,23 +1,24 @@
<script lang="ts">
import { invalidateAll } from '$app/navigation';
import { apiFunctions } from '$lib/api';
import Loading from '$lib/components/loading.svelte';
import { uppercaseInput } from '$lib/input-helpers';
import type { Event } from '$lib/interfaces/event.interface';
import type { User } from '$lib/interfaces/user.interface';
import { getAccessToken } from '$lib/stores/auth-store';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
import { faPlus, faRemove } from '@fortawesome/free-solid-svg-icons';
import Fa from 'svelte-fa';
const { event }: { event: Event } = $props();
const auth = getAuthContext();
let users = $state<User[]>();
let error = $state<string>();
let usernameInput = $state('');
$effect(() => {
getAccessToken().then((token) => {
auth.getAccessToken().then((token) => {
if (!token) return;
apiFunctions
.getManyUsers(token, event.access)
@ -29,11 +30,11 @@
async function grantAccess() {
error = undefined;
try {
const token = await getAccessToken();
const token = await auth.getAccessToken();
if (!token) throw Error('Unauthenticated');
const user = await apiFunctions.findByUsername(token, usernameInput.toUpperCase());
await apiFunctions.grantEventAccess(token, event._id, user._id);
invalidateAll();
users?.push(user);
usernameInput = '';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@ -45,10 +46,10 @@
async function revokeAccess(userId: string) {
try {
const token = await getAccessToken();
const token = await auth.getAccessToken();
if (!token) throw Error('Unauthenticated');
await apiFunctions.revokeEventAccess(token, event._id, userId);
invalidateAll();
users = users?.filter((u) => u._id !== userId);
} catch (err) {
console.error(err);
}

View File

@ -4,13 +4,15 @@
import type { Event } from '$lib/interfaces/event.interface';
import type { Reservation } from '$lib/interfaces/reservation.interface';
import type { User } from '$lib/interfaces/user.interface';
import { getAccessToken } from '$lib/stores/auth-store';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
import { dayInMs, getUTCDateString, getUTCTimeString } from '$lib/util/date.util';
import { faFileCircleCheck, faFileCircleExclamation } from '@fortawesome/free-solid-svg-icons';
import Fa from 'svelte-fa';
const { event }: { event: Event } = $props();
const auth = getAuthContext();
let reservations = $state<Reservation[]>();
let users = $state<Map<string, User>>();
@ -20,7 +22,7 @@
.then((res) => {
reservations = res.sort((a, b) => b.startDateTime.valueOf() - a.startDateTime.valueOf());
const u = new Set<string>(res.map((r) => r.user));
getAccessToken().then((token) => {
auth.getAccessToken().then((token) => {
if (!token) return;
apiFunctions
.getManyUsers(token, Array.from(u))

View File

@ -4,17 +4,22 @@
import Loading from '$lib/components/loading.svelte';
import { Role } from '$lib/enums/role.enum';
import type { User } from '$lib/interfaces/user.interface';
import type { PageData } from './$types';
import { userStore } from '$lib/stores/user-store';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
import DeleteModal from './delete-modal.svelte';
import ResetPasswordModal from './reset-password-modal.svelte';
import { apiFunctions } from '$lib/api';
const { data }: { data: PageData } = $props();
const auth = getAuthContext();
let deleteUser = $state<User>();
let resetPassUser = $state<User>();
let me = $derived($userStore);
let me = $derived(auth.user);
const usersP = auth.getAccessToken().then((token) => {
if (!token) return [];
return apiFunctions.getAllUsers(token);
});
</script>
<div>
@ -27,7 +32,7 @@
</a>
</div>
{#await data.users}
{#await usersP}
<Loading />
{:then users}
<div class="overflow-x-auto">

View File

@ -1,12 +0,0 @@
import { apiFunctions } from '$lib/api';
import { getAccessToken } from '$lib/stores/auth-store';
import type { PageLoad } from './$types';
export const load: PageLoad = () => {
return {
users: getAccessToken().then((token) => {
if (!token) return [];
return apiFunctions.getAllUsers(token);
})
};
};

View File

@ -2,11 +2,13 @@
import { goto } from '$app/navigation';
import { apiFunctions } from '$lib/api';
import { uppercaseInput } from '$lib/input-helpers';
import { getAccessToken } from '$lib/stores/auth-store';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
import { faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
import Fa from 'svelte-fa';
import PasswordField from '../password-field.svelte';
const auth = getAuthContext();
let username = $state('');
let password = $state('');
let name = $state('');
@ -16,7 +18,7 @@
function submit() {
error = undefined;
getAccessToken().then((token) => {
auth.getAccessToken().then((token) => {
if (!token) return;
apiFunctions
.createUser(token, {

View File

@ -2,9 +2,11 @@
import { invalidateAll } from '$app/navigation';
import { apiFunctions } from '$lib/api';
import type { User } from '$lib/interfaces/user.interface';
import { getAccessToken } from '$lib/stores/auth-store';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
let { deleteUser = $bindable() }: { deleteUser: User | undefined } = $props();
const auth = getAuthContext();
</script>
<dialog class="modal {deleteUser ? 'modal-open' : ''}">
@ -19,7 +21,7 @@
class="btn btn-error"
onclick={async () => {
deleteUser = undefined;
const token = await getAccessToken();
const token = await auth.getAccessToken();
if (!token) return;
apiFunctions
.deleteUser(token, deleteUser!._id)

View File

@ -1,13 +1,13 @@
<script lang="ts">
import { invalidateAll } from '$app/navigation';
import { apiFunctions } from '$lib/api';
import type { User } from '$lib/interfaces/user.interface';
import { getAccessToken } from '$lib/stores/auth-store';
import { tick } from 'svelte';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
import PasswordField from './password-field.svelte';
let { user = $bindable() }: { user: User | undefined } = $props();
const auth = getAuthContext();
let password = $state('');
</script>
@ -23,7 +23,7 @@
type="button"
class="btn btn-primary"
onclick={async () => {
const token = await getAccessToken();
const token = await auth.getAccessToken();
if (!token) return;
apiFunctions
.updateOtherPassword(token, user!._id, password)

View File

@ -6,13 +6,15 @@
import Fa from 'svelte-fa';
import FreeDatesComponent from './free-dates-component.svelte';
import Time from './time.svelte';
import { userStore } from '$lib/stores/user-store';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
import ReserveComponent from './reserve/reserve-component.svelte';
import MyReservations from './reserve/my-reservations.svelte';
import { Role } from '$lib/enums/role.enum';
import PrivateTag from '$lib/components/private-tag.svelte';
const { event }: { event: Event } = $props();
const auth = getAuthContext();
</script>
<div class="flex flex-col gap-8">
@ -28,7 +30,7 @@
{/if}
</div>
{#if $userStore?.roles.includes(Role.Admin)}
{#if auth.user?.roles.includes(Role.Admin)}
<a href="/admin/events/{event._id}" class="btn btn-warning btn-sm">Uredi</a>
{/if}
</div>
@ -49,7 +51,7 @@
<FreeDatesComponent {event} />
</div>
{#if $userStore && event.access.includes($userStore._id)}
{#if auth.user && event.access.includes(auth.user._id)}
<div>
<h2 class="mb-4 text-2xl">Moje rezervacije</h2>
<MyReservations eventId={event._id} />
@ -63,7 +65,7 @@
</div>
<ReserveComponent {event} />
</div>
{:else if !$userStore}
{:else if !auth.user}
<div>
<a href="/login?redirect=/event/{event._id}" class="btn">Prijavi se za rezervacijo</a>
</div>

View File

@ -2,11 +2,13 @@
import { apiFunctions } from '$lib/api';
import Loading from '$lib/components/loading.svelte';
import ReservationsTable from '$lib/components/reservations-table.svelte';
import { getAccessToken } from '$lib/stores/auth-store';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
const { eventId }: { eventId: string } = $props();
const reservations = getAccessToken().then((token) => {
const auth = getAuthContext();
const reservations = auth.getAccessToken().then((token) => {
if (!token) return [];
return apiFunctions
.getReservationsForSelf(token, { event: eventId })

View File

@ -10,9 +10,11 @@
import BandSelector from './band-selector.svelte';
import ModeSelector from './mode-selector.svelte';
import { invalidateAll } from '$app/navigation';
import { getAccessToken } from '$lib/stores/auth-store';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
import { clampDate } from '..';
const auth = getAuthContext();
function floorHour(date: number) {
return new Date(Math.floor(date / hourInMs) * hourInMs);
}
@ -92,7 +94,7 @@
async function submit() {
error = undefined;
const token = await getAccessToken();
const token = await auth.getAccessToken();
if (!token) {
error = ['Napaka'];
return;

View File

@ -1,16 +1,15 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { page } from '$app/stores';
import { apiFunctions } from '$lib/api';
import { uppercaseInput } from '$lib/input-helpers';
import { authStore, isAuthValid } from '$lib/stores/auth-store';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
const redirect = $page.url.searchParams.get('redirect');
const auth = getAuthContext();
$effect(() => {
isAuthValid().then((r) => {
if (r) goto(redirect || '/');
});
if (auth.user) goto(redirect || '/');
});
let username = $state('');
@ -21,16 +20,9 @@
function login() {
error = undefined;
apiFunctions
.login(username, password)
.then((res) => {
authStore.set(res);
goto(redirect || '/');
})
.catch((e) => {
console.error(e);
error = 'Napačno uporabniško ime ali geslo';
});
auth.login(username, password).then((res) => {
if (!res) error = 'Napačno uporabniško ime ali geslo';
});
}
</script>

View File

@ -1,14 +1,25 @@
<script lang="ts">
import { apiFunctions } from '$lib/api';
import Loading from '$lib/components/loading.svelte';
import ReservationsTable from '$lib/components/reservations-table.svelte';
import ResetPasswordAlert from '$lib/components/reset-password-alert.svelte';
import type { PageData } from './$types';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
const { data }: { data: PageData } = $props();
const auth = getAuthContext();
const promise = auth.getAccessToken().then((token) => {
if (!token) throw Error('Unauthenticated');
return Promise.all([
apiFunctions.getMe(token),
apiFunctions.getReservationsForSelf(token).then((res) => {
return res.sort((a, b) => b.startDateTime.valueOf() - a.startDateTime.valueOf());
})
]);
});
</script>
<div class="container flex flex-col gap-8 py-10">
{#await Promise.all([data.user, data.reservations])}
{#await promise}
<Loading />
{:then [user, reservations]}
<ResetPasswordAlert />

View File

@ -1,17 +0,0 @@
import { apiFunctions } from '$lib/api';
import { getAccessToken } from '$lib/stores/auth-store';
import type { PageLoad } from './$types';
export const load: PageLoad = async () => {
const tokenP = getAccessToken().then((token) => {
if (!token) throw Error('Unauthenticated');
return token;
});
return {
user: tokenP.then(apiFunctions.getMe),
reservations: tokenP.then(apiFunctions.getReservationsForSelf).then((res) => {
return res.sort((a, b) => b.startDateTime.valueOf() - a.startDateTime.valueOf());
})
};
};

View File

@ -1,11 +1,11 @@
<script lang="ts">
import { apiFunctions } from '$lib/api';
import { getAccessToken } from '$lib/stores/auth-store';
import { refreshUser, userStore } from '$lib/stores/user-store';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
import { faCircleCheck, faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
import Fa from 'svelte-fa';
const user = $derived($userStore);
const auth = getAuthContext();
const user = $derived(auth.user);
let name = $state('');
let email = $state('');
@ -28,7 +28,7 @@
return;
}
const token = await getAccessToken();
const token = await auth.getAccessToken();
if (!token) return;
apiFunctions
.updateSelf(token, {
@ -38,7 +38,7 @@
})
.then(() => {
saved = true;
refreshUser();
auth.refreshUser();
})
.catch((e) => {
console.error(e);
@ -55,7 +55,7 @@
return;
}
const token = await getAccessToken();
const token = await auth.getAccessToken();
if (!token) return;
apiFunctions
.updateSelfPassword(token, oldPassword, newPassword)

View File

@ -11,9 +11,8 @@
import DownloadButton from './download-button.svelte';
import LogSummary from './log-summary.svelte';
import UploadLog from './upload-log.svelte';
import { userStore } from '$lib/stores/user-store';
import { Role } from '$lib/enums/role.enum';
import { getAccessToken } from '$lib/stores/auth-store';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
import { apiFunctions } from '$lib/api';
import ProgressBar from '$lib/components/progress-bar.svelte';
import { createTimeState } from '$lib/stores/time-state.svelte';
@ -21,11 +20,19 @@
const { data }: { data: PageData } = $props();
const auth = getAuthContext();
const promise = auth.getAccessToken().then((token) => {
if (!token) throw new Error('Unauthenticated');
const res = apiFunctions.getReservation(token, data.id);
return Promise.all([res, res.then((r) => apiFunctions.getEvent(r.event))]);
});
const now = createTimeState(10_000);
</script>
<div class="container py-10">
{#await Promise.all([data.reservation, data.event])}
{#await promise}
<Loading />
{:then [reservation, event]}
<div class="flex flex-col gap-10">
@ -36,7 +43,7 @@
<span>Nazaj na dogodek</span>
</a>
{#if $userStore?.roles.includes(Role.Admin)}
{#if auth.user?.roles.includes(Role.Admin)}
<a href="/admin/events/{event._id}" class="btn btn-warning btn-sm btn-outline">
Nazaj na dogodek
</a>
@ -44,7 +51,7 @@
<button
onclick={() => {
if (!confirm('Ali ste prepričani, da želite izbrisati rezervacijo?')) return;
getAccessToken().then((token) => {
auth.getAccessToken().then((token) => {
if (!token) return;
apiFunctions.deleteReservation(token, reservation._id).then(() => {
window.location.href = `/event/${event._id}`;

View File

@ -1,17 +1,7 @@
import { apiFunctions } from '$lib/api';
import { getAccessToken } from '$lib/stores/auth-store';
import type { PageLoad } from './$types';
export const load: PageLoad = ({ params }) => {
const tokenP = getAccessToken();
const res = tokenP.then((token) => {
if (!token) throw Error('Unauthenticated');
return apiFunctions.getReservation(token, params.id);
});
return {
reservation: res,
event: res.then((res) => apiFunctions.getEvent(res.event))
id: params.id
};
};

View File

@ -2,13 +2,15 @@
import { apiFunctions } from '$lib/api';
import type { Event } from '$lib/interfaces/event.interface';
import type { Reservation } from '$lib/interfaces/reservation.interface';
import { getAccessToken } from '$lib/stores/auth-store';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
import { faDownload } from '@fortawesome/free-solid-svg-icons';
import { tick } from 'svelte';
import Fa from 'svelte-fa';
const { event, reservation }: { event: Event; reservation: Reservation } = $props();
const auth = getAuthContext();
let aRef: HTMLAnchorElement;
let url = $state<string>();
@ -24,7 +26,7 @@
return;
}
const token = await getAccessToken();
const token = await auth.getAccessToken();
if (!token) return;
apiFunctions
.getLog(token, reservation._id)

View File

@ -1,11 +1,12 @@
<script lang="ts">
import { apiFunctions } from '$lib/api';
import type { Reservation } from '$lib/interfaces/reservation.interface';
import { getAccessToken } from '$lib/stores/auth-store';
import { userStore } from '$lib/stores/user-store';
import { getAuthContext } from '$lib/stores/auth-state.svelte';
const { reservation: res }: { reservation: Reservation } = $props();
const auth = getAuthContext();
let error = $state<string>();
let file = $state<File>();
@ -16,7 +17,7 @@
return;
}
const token = await getAccessToken();
const token = await auth.getAccessToken();
if (!token) return;
apiFunctions
.uploadLog(token, res._id, file)
@ -30,7 +31,7 @@
}
</script>
{#if $userStore?._id === res.user}
{#if auth.user?._id === res.user}
<div class="form-control">
<div class="label">
{res.logSummary ? 'Ponovno oddaj' : 'Oddaj'} dnevnik