Use a pinia store to manage session state
Replace the convoluted useAccountSession composable with a pinia store that in addition allows for the consolidation of all session related functions to grouped into one module.
This commit is contained in:
parent
c47452a8b4
commit
fae8b4e2e4
21 changed files with 181 additions and 118 deletions
5
app.vue
5
app.vue
|
@ -5,4 +5,9 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import "~/assets/global.css";
|
import "~/assets/global.css";
|
||||||
|
const event = useRequestEvent();
|
||||||
|
const sessionStore = useSessionStore();
|
||||||
|
await callOnce("fetch-session", async () => {
|
||||||
|
await sessionStore.fetch(event);
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<p v-if="event.interested">
|
<p v-if="event.interested">
|
||||||
{{ event.interested }} interested
|
{{ event.interested }} interested
|
||||||
</p>
|
</p>
|
||||||
<p v-if="session">
|
<p v-if="sessionStore.account">
|
||||||
<button
|
<button
|
||||||
class="interested"
|
class="interested"
|
||||||
:class="{ active: interestedIds.has(event.id) }"
|
:class="{ active: interestedIds.has(event.id) }"
|
||||||
|
@ -20,7 +20,7 @@
|
||||||
<li v-for="slot in event.slots" :key="slot.id">
|
<li v-for="slot in event.slots" :key="slot.id">
|
||||||
{{ formatTime(slot.start) }} - {{ formatTime(slot.end) }}
|
{{ formatTime(slot.start) }} - {{ formatTime(slot.end) }}
|
||||||
<button
|
<button
|
||||||
v-if="session && event.slots.length > 1"
|
v-if="sessionStore.account && event.slots.length > 1"
|
||||||
class="interested"
|
class="interested"
|
||||||
:disabled="interestedIds.has(event.id)"
|
:disabled="interestedIds.has(event.id)"
|
||||||
:class="{ active: interestedIds.has(event.id) || interestedIds.has(slot.id) }"
|
:class="{ active: interestedIds.has(event.id) || interestedIds.has(slot.id) }"
|
||||||
|
@ -49,9 +49,9 @@ defineProps<{
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const runtimeConfig = useRuntimeConfig();
|
const runtimeConfig = useRuntimeConfig();
|
||||||
const { data: session, refresh: refreshSession } = await useAccountSession();
|
const sessionStore = useSessionStore();
|
||||||
const interestedIds = computed(() => new Set(session.value?.account.interestedIds ?? []));
|
const interestedIds = computed(() => new Set(sessionStore.account?.interestedIds ?? []));
|
||||||
const timezone = computed(() => session.value?.account?.timezone ?? runtimeConfig.public.defaultTimezone);
|
const timezone = computed(() => sessionStore.account?.timezone ?? runtimeConfig.public.defaultTimezone);
|
||||||
const { data: accounts } = await useAccounts();
|
const { data: accounts } = await useAccounts();
|
||||||
const idToAccount = computed(() => new Map(accounts.value?.map(a => [a.id, a])));
|
const idToAccount = computed(() => new Map(accounts.value?.map(a => [a.id, a])));
|
||||||
|
|
||||||
|
@ -60,7 +60,7 @@ function formatTime(time: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggle(id: string, slotIds?: string[]) {
|
async function toggle(id: string, slotIds?: string[]) {
|
||||||
let newIds = [...session.value!.account.interestedIds ?? []];
|
let newIds = [...sessionStore.account!.interestedIds ?? []];
|
||||||
if (interestedIds.value.has(id)) {
|
if (interestedIds.value.has(id)) {
|
||||||
newIds = newIds.filter(newId => newId !== id);
|
newIds = newIds.filter(newId => newId !== id);
|
||||||
} else {
|
} else {
|
||||||
|
@ -74,7 +74,7 @@ async function toggle(id: string, slotIds?: string[]) {
|
||||||
method: "PATCH",
|
method: "PATCH",
|
||||||
body: { interestedIds: newIds },
|
body: { interestedIds: newIds },
|
||||||
})
|
})
|
||||||
await refreshSession();
|
await sessionStore.fetch();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -135,8 +135,8 @@ defineProps<{
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const schedule = await useSchedule();
|
const schedule = await useSchedule();
|
||||||
const { data: session } = await useAccountSession();
|
const sessionStore = useSessionStore();
|
||||||
const canEditPublic = computed(() => session.value?.account.type === "admin");
|
const canEditPublic = computed(() => sessionStore.account?.type === "admin");
|
||||||
|
|
||||||
function canEdit(event: ScheduleEvent) {
|
function canEdit(event: ScheduleEvent) {
|
||||||
return event.crew || canEditPublic.value;
|
return event.crew || canEditPublic.value;
|
||||||
|
|
|
@ -8,18 +8,18 @@
|
||||||
<li>
|
<li>
|
||||||
<NuxtLink to="/schedule">Schedule</NuxtLink>
|
<NuxtLink to="/schedule">Schedule</NuxtLink>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="session?.account?.type === 'admin' || session?.account?.type === 'crew'">
|
<li v-if="sessionStore.account?.type === 'admin' || sessionStore.account?.type === 'crew'">
|
||||||
<NuxtLink to="/edit">Edit</NuxtLink>
|
<NuxtLink to="/edit">Edit</NuxtLink>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
<div class="account">
|
<div class="account">
|
||||||
<template v-if="session?.account">
|
<template v-if="sessionStore.account">
|
||||||
{{ session.account.name }}
|
{{ sessionStore.account.name }}
|
||||||
(s:{{ session.id }} a:{{ session.account.id }}{{ session.push ? " push" : null }})
|
(s:{{ sessionStore.id }} a:{{ sessionStore.account.id }}{{ sessionStore.push ? " push" : null }})
|
||||||
{{ session.account.type }}
|
{{ sessionStore.account.type }}
|
||||||
<NuxtLink to="/account/settings">Settings</NuxtLink>
|
<NuxtLink to="/account/settings">Settings</NuxtLink>
|
||||||
<LogOutButton v-if="session.account.type !== 'anonymous'"/>
|
<LogOutButton v-if="sessionStore.account.type !== 'anonymous'"/>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<NuxtLink to="/login">Log In</NuxtLink>
|
<NuxtLink to="/login">Log In</NuxtLink>
|
||||||
|
@ -29,7 +29,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
const { data: session } = await useAccountSession();
|
const sessionStore = useSessionStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
|
@ -1,19 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<button type="button" @click="logOut">Log out</button>
|
<button type="button" @click="sessionStore.logOut">Log out</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { refresh: sessionRefresh } = useAccountSession();
|
const sessionStore = useSessionStore();
|
||||||
|
|
||||||
async function logOut() {
|
|
||||||
try {
|
|
||||||
await $fetch.raw("/api/auth/session", {
|
|
||||||
method: "DELETE",
|
|
||||||
});
|
|
||||||
await sessionRefresh();
|
|
||||||
|
|
||||||
} catch (err: any) {
|
|
||||||
alert(`Log out failed: ${err.statusCode} ${err.statusMessage}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -18,15 +18,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
const { data: session, refresh: refreshSession } = await useAccountSession();
|
const sessionStore = useSessionStore();
|
||||||
const { supported, subscription, getSubscription, subscribe, unsubscribe } = usePushNotification();
|
const { supported, subscription, getSubscription, subscribe, unsubscribe } = usePushNotification();
|
||||||
const subscribed = computed(() => Boolean(subscription.value && session.value?.push))
|
const subscribed = computed(() => Boolean(subscription.value && sessionStore.push));
|
||||||
async function onClick() {
|
async function onClick() {
|
||||||
if (!subscribed.value)
|
if (!subscribed.value)
|
||||||
await subscribe();
|
await subscribe();
|
||||||
else
|
else
|
||||||
await unsubscribe();
|
await unsubscribe();
|
||||||
await refreshSession();
|
await sessionStore.fetch();
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
|
|
@ -424,11 +424,11 @@ function removeSlot(eventChanges: ChangeRecord<ScheduleEvent>[], event: Schedule
|
||||||
return eventChanges;
|
return eventChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: session } = await useAccountSession();
|
const sessionStore = useSessionStore();
|
||||||
const schedule = await useSchedule();
|
const schedule = await useSchedule();
|
||||||
const runtimeConfig = useRuntimeConfig();
|
const runtimeConfig = useRuntimeConfig();
|
||||||
const timezone = computed(
|
const timezone = computed(
|
||||||
() => session.value?.account?.timezone ?? runtimeConfig.public.defaultTimezone
|
() => sessionStore.account?.timezone ?? runtimeConfig.public.defaultTimezone
|
||||||
);
|
);
|
||||||
|
|
||||||
type EventSlotChange = { op: "set" | "del", data: EventSlot } ;
|
type EventSlotChange = { op: "set" | "del", data: EventSlot } ;
|
||||||
|
|
|
@ -394,11 +394,11 @@ function removeSlot(eventChanges: ChangeRecord<Shift>[], shift: Shift, shiftSlot
|
||||||
return eventChanges;
|
return eventChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data: session } = await useAccountSession();
|
const sessionStore = useSessionStore();
|
||||||
const schedule = await useSchedule();
|
const schedule = await useSchedule();
|
||||||
const runtimeConfig = useRuntimeConfig();
|
const runtimeConfig = useRuntimeConfig();
|
||||||
const timezone = computed(
|
const timezone = computed(
|
||||||
() => session.value?.account?.timezone ?? runtimeConfig.public.defaultTimezone
|
() => sessionStore.account?.timezone ?? runtimeConfig.public.defaultTimezone
|
||||||
);
|
);
|
||||||
|
|
||||||
type ShiftSlotChange = { op: "set" | "del", data: ShiftSlot } ;
|
type ShiftSlotChange = { op: "set" | "del", data: ShiftSlot } ;
|
||||||
|
|
|
@ -504,10 +504,10 @@ const stretches = computed(() => [
|
||||||
])
|
])
|
||||||
|
|
||||||
const runtimeConfig = useRuntimeConfig();
|
const runtimeConfig = useRuntimeConfig();
|
||||||
const { data: session } = await useAccountSession();
|
const sessionStore = useSessionStore();
|
||||||
const debugTimezone = ref<undefined | string>();
|
const debugTimezone = ref<undefined | string>();
|
||||||
const timezone = computed({
|
const timezone = computed({
|
||||||
get: () => debugTimezone.value ?? session.value?.account?.timezone ?? runtimeConfig.public.defaultTimezone,
|
get: () => debugTimezone.value ?? sessionStore.account?.timezone ?? runtimeConfig.public.defaultTimezone,
|
||||||
set: (value: string) => { debugTimezone.value = value },
|
set: (value: string) => { debugTimezone.value = value },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ let source: EventSource | null = null;
|
||||||
let sourceRefs = 0;
|
let sourceRefs = 0;
|
||||||
let sourceSessionId: number | undefined = undefined;
|
let sourceSessionId: number | undefined = undefined;
|
||||||
export const useSchedule = () => {
|
export const useSchedule = () => {
|
||||||
const { data: session } = useAccountSession();
|
const sessionStore = useSessionStore();
|
||||||
const requestFetch = useRequestFetch();
|
const requestFetch = useRequestFetch();
|
||||||
const asyncData = useAsyncData<Schedule>(
|
const asyncData = useAsyncData<Schedule>(
|
||||||
'schedule',
|
'schedule',
|
||||||
|
@ -14,8 +14,8 @@ export const useSchedule = () => {
|
||||||
const { data: schedule } = asyncData;
|
const { data: schedule } = asyncData;
|
||||||
|
|
||||||
function connect() {
|
function connect() {
|
||||||
console.log("Opening event source sid:", session.value?.id);
|
console.log("Opening event source sid:", sessionStore.id);
|
||||||
sourceSessionId = session.value?.id;
|
sourceSessionId = sessionStore.id;
|
||||||
source = new EventSource("/api/events");
|
source = new EventSource("/api/events");
|
||||||
source.addEventListener("message", (message) => {
|
source.addEventListener("message", (message) => {
|
||||||
console.log("Message", message.data);
|
console.log("Message", message.data);
|
||||||
|
@ -43,11 +43,11 @@ export const useSchedule = () => {
|
||||||
connect();
|
connect();
|
||||||
})
|
})
|
||||||
|
|
||||||
watch(() => session.value?.id, () => {
|
watch(() => sessionStore.id, () => {
|
||||||
if (sourceSessionId === session.value?.id) {
|
if (sourceSessionId === sessionStore.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sourceSessionId = session.value?.id;
|
sourceSessionId = sessionStore.id;
|
||||||
console.log("Session changed, refetching schedule")
|
console.log("Session changed, refetching schedule")
|
||||||
$fetch("/api/schedule").then(
|
$fetch("/api/schedule").then(
|
||||||
data => { schedule.value = data; },
|
data => { schedule.value = data; },
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
import { appendResponseHeader } from "h3";
|
|
||||||
import type { H3Event } from "h3";
|
|
||||||
|
|
||||||
const fetchWithCookie = async (url: string, event?: H3Event) => {
|
|
||||||
if (!event) {
|
|
||||||
return $fetch(url);
|
|
||||||
}
|
|
||||||
|
|
||||||
const cookie = useRequestHeader("cookie");
|
|
||||||
const res = await $fetch.raw(url, {
|
|
||||||
headers: cookie ? { cookie } : undefined
|
|
||||||
});
|
|
||||||
for (const cookie of res.headers.getSetCookie()) {
|
|
||||||
appendResponseHeader(event, "set-cookie", cookie);
|
|
||||||
}
|
|
||||||
return res._data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useAccountSession = () => {
|
|
||||||
const event = useRequestEvent();
|
|
||||||
return useAsyncData(
|
|
||||||
"session",
|
|
||||||
async () => await fetchWithCookie("/api/auth/session", event),
|
|
||||||
{
|
|
||||||
transform: (input) => input === undefined ? false as any as null: input,
|
|
||||||
getCachedData(key, nuxtApp, context) {
|
|
||||||
if (context.cause === "refresh:manual")
|
|
||||||
return undefined
|
|
||||||
return nuxtApp.payload.data[key];
|
|
||||||
},
|
|
||||||
dedupe: "defer",
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,14 +1,14 @@
|
||||||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||||
const { data: session } = await useAccountSession();
|
const sessionStore = useSessionStore();
|
||||||
|
|
||||||
if (!session.value) {
|
if (!sessionStore.account) {
|
||||||
console.log("Not logged in, redirecting to /login");
|
console.log("Not logged in, redirecting to /login");
|
||||||
return navigateTo("/login");
|
return navigateTo("/login");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
to.meta.allowedAccountTypes
|
to.meta.allowedAccountTypes
|
||||||
&& !to.meta.allowedAccountTypes.includes(session.value.account.type)
|
&& !to.meta.allowedAccountTypes.includes(sessionStore.account.type)
|
||||||
) {
|
) {
|
||||||
throw createError({
|
throw createError({
|
||||||
status: 403,
|
status: 403,
|
||||||
|
|
|
@ -10,5 +10,8 @@ export default defineNuxtConfig({
|
||||||
defaultTimezone: "Europe/Oslo",
|
defaultTimezone: "Europe/Oslo",
|
||||||
vapidPublicKey: "",
|
vapidPublicKey: "",
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
modules: [
|
||||||
|
"@pinia/nuxt",
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,8 +10,10 @@
|
||||||
"postinstall": "nuxt prepare"
|
"postinstall": "nuxt prepare"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@pinia/nuxt": "0.11.0",
|
||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
"nuxt": "^3.17.4",
|
"nuxt": "^3.17.4",
|
||||||
|
"pinia": "^3.0.2",
|
||||||
"vue": "latest",
|
"vue": "latest",
|
||||||
"vue-router": "latest",
|
"vue-router": "latest",
|
||||||
"web-push": "^3.6.7"
|
"web-push": "^3.6.7"
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
<h1>Account Settings</h1>
|
<h1>Account Settings</h1>
|
||||||
<p v-if="session?.account.type !== 'anonymous'">
|
<p v-if="sessionStore.account?.type !== 'anonymous'">
|
||||||
Name: {{ session?.account.name }}
|
Name: {{ sessionStore.account?.name }}
|
||||||
</p>
|
</p>
|
||||||
<p>Access: {{ session?.account.type }}</p>
|
<p>Access: {{ sessionStore.account?.type }}</p>
|
||||||
<form @submit.prevent="changeSettings">
|
<form @submit.prevent="changeSettings">
|
||||||
<label>
|
<label>
|
||||||
Timezone
|
Timezone
|
||||||
|
@ -34,9 +34,9 @@ definePageMeta({
|
||||||
middleware: ["authenticated"],
|
middleware: ["authenticated"],
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: session, refresh: sessionRefresh } = await useAccountSession();
|
const sessionStore = useSessionStore();
|
||||||
|
|
||||||
const timezone = ref(session.value?.account.timezone ?? "");
|
const timezone = ref(sessionStore.account?.timezone ?? "");
|
||||||
|
|
||||||
async function changeSettings() {
|
async function changeSettings() {
|
||||||
try {
|
try {
|
||||||
|
@ -46,7 +46,7 @@ async function changeSettings() {
|
||||||
timezone: timezone.value,
|
timezone: timezone.value,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
await sessionRefresh();
|
await sessionStore.fetch();
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
alert(err.data.message);
|
alert(err.data.message);
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ async function deleteAccount() {
|
||||||
await $fetch.raw("/api/account", {
|
await $fetch.raw("/api/account", {
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
});
|
});
|
||||||
await sessionRefresh();
|
await sessionStore.fetch();
|
||||||
await navigateTo("/");
|
await navigateTo("/");
|
||||||
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|
|
@ -77,6 +77,7 @@ definePageMeta({
|
||||||
|
|
||||||
const schedule = await useSchedule();
|
const schedule = await useSchedule();
|
||||||
const { data: accounts } = await useAccounts();
|
const { data: accounts } = await useAccounts();
|
||||||
|
const sessionStore = useSessionStore();
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const crewFilter = computed({
|
const crewFilter = computed({
|
||||||
|
@ -90,14 +91,14 @@ const crewFilter = computed({
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
const eventSlotFilter = computed(() => {
|
const eventSlotFilter = computed(() => {
|
||||||
if (crewFilter.value === undefined || !session.value) {
|
if (crewFilter.value === undefined || !sessionStore.account) {
|
||||||
return () => true;
|
return () => true;
|
||||||
}
|
}
|
||||||
const cid = parseInt(crewFilter.value);
|
const cid = parseInt(crewFilter.value);
|
||||||
return (slot: TimeSlot) => slot.assigned?.some(id => id === cid) || false;
|
return (slot: TimeSlot) => slot.assigned?.some(id => id === cid) || false;
|
||||||
});
|
});
|
||||||
const shiftSlotFilter = computed(() => {
|
const shiftSlotFilter = computed(() => {
|
||||||
if (crewFilter.value === undefined || !session.value) {
|
if (crewFilter.value === undefined || !sessionStore.account) {
|
||||||
return () => true;
|
return () => true;
|
||||||
}
|
}
|
||||||
const cid = parseInt(crewFilter.value);
|
const cid = parseInt(crewFilter.value);
|
||||||
|
@ -126,6 +127,5 @@ const roleFilter = computed({
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const { data: session } = await useAccountSession();
|
const isAdmin = computed(() => sessionStore.account?.type === "admin")
|
||||||
const isAdmin = computed(() => session.value?.account.type === "admin")
|
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -5,13 +5,13 @@
|
||||||
<li>
|
<li>
|
||||||
<NuxtLink to="/schedule">View Schedule</NuxtLink>
|
<NuxtLink to="/schedule">View Schedule</NuxtLink>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="session?.account?.type === 'admin' || session?.account?.type === 'crew'">
|
<li v-if="sessionStore.account?.type === 'admin' || sessionStore.account?.type === 'crew'">
|
||||||
<NuxtLink to="/edit">Edit Schedule</NuxtLink>
|
<NuxtLink to="/edit">Edit Schedule</NuxtLink>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="session">
|
<li v-if="sessionStore.account">
|
||||||
<NuxtLink to="/account/settings">Account Settings</NuxtLink>
|
<NuxtLink to="/account/settings">Account Settings</NuxtLink>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!session">
|
<li v-if="!sessionStore.account">
|
||||||
<NuxtLink to="/login">Log In / Create Account</NuxtLink>
|
<NuxtLink to="/login">Log In / Create Account</NuxtLink>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -19,5 +19,5 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
const { data: session } = await useAccountSession();
|
const sessionStore = useSessionStore();
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -25,29 +25,26 @@
|
||||||
<button type="button" @click="createAnonymousAccount">Create anonymous account</button>
|
<button type="button" @click="createAnonymousAccount">Create anonymous account</button>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<pre><code>{{ result }}</code></pre>
|
<pre><code>{{ result }}</code></pre>
|
||||||
<pre><code>Session: {{ session }}</code></pre>
|
<pre><code>Session: {{ ({ id: sessionStore.id, account: sessionStore.account, push: sessionStore.push }) }}</code></pre>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
const { data: session, refresh: sessionRefresh } = await useAccountSession();
|
const sessionStore = useSessionStore();
|
||||||
const { getSubscription, subscribe } = usePushNotification();
|
const { getSubscription, subscribe } = usePushNotification();
|
||||||
|
|
||||||
const name = ref("");
|
const name = ref("");
|
||||||
const result = ref("")
|
const result = ref("")
|
||||||
async function logIn() {
|
async function logIn() {
|
||||||
try {
|
try {
|
||||||
const res = await $fetch.raw("/api/auth/login", {
|
result.value = await sessionStore.logIn(name.value);
|
||||||
method: "POST",
|
|
||||||
body: { name: name.value },
|
|
||||||
});
|
|
||||||
result.value = `Server replied: ${res.status} ${res.statusText}`;
|
|
||||||
// Resubscribe push notifications if the user was subscribed before.
|
// Resubscribe push notifications if the user was subscribed before.
|
||||||
const subscription = await getSubscription();
|
const subscription = await getSubscription();
|
||||||
if (subscription) {
|
if (subscription) {
|
||||||
await subscribe();
|
await subscribe();
|
||||||
}
|
}
|
||||||
await sessionRefresh();
|
// XXX Remove the need for this.
|
||||||
|
await sessionStore.fetch();
|
||||||
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
@ -67,7 +64,7 @@ async function createAccount() {
|
||||||
body: new URLSearchParams({ name: createName.value })
|
body: new URLSearchParams({ name: createName.value })
|
||||||
});
|
});
|
||||||
result.value = `Server replied: ${res.status} ${res.statusText}`;
|
result.value = `Server replied: ${res.status} ${res.statusText}`;
|
||||||
await sessionRefresh();
|
await sessionStore.fetch();
|
||||||
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
@ -84,7 +81,7 @@ async function createAnonymousAccount() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
result.value = `Server replied: ${res.status} ${res.statusText}`;
|
result.value = `Server replied: ${res.status} ${res.statusText}`;
|
||||||
await sessionRefresh();
|
await sessionStore.fetch();
|
||||||
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<p>
|
<p>
|
||||||
Study carefully, we only hold these events once a year.
|
Study carefully, we only hold these events once a year.
|
||||||
</p>
|
</p>
|
||||||
<p v-if="!session">
|
<p v-if="!sessionStore.account">
|
||||||
<NuxtLink to="/login">Login</NuxtLink> or <NuxtLink to="/login#create-account">Create an account</NuxtLink>
|
<NuxtLink to="/login">Login</NuxtLink> or <NuxtLink to="/login#create-account">Create an account</NuxtLink>
|
||||||
to get notified about updates to the schedule.
|
to get notified about updates to the schedule.
|
||||||
</p>
|
</p>
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
Check out your <NuxtLink to="/account/settings">Account Setting</NuxtLink> to set up notifications for changes to schedule.
|
Check out your <NuxtLink to="/account/settings">Account Setting</NuxtLink> to set up notifications for changes to schedule.
|
||||||
</p>
|
</p>
|
||||||
<h2>Schedule</h2>
|
<h2>Schedule</h2>
|
||||||
<label v-if="session">
|
<label v-if="sessionStore.account">
|
||||||
Filter:
|
Filter:
|
||||||
<select
|
<select
|
||||||
v-model="filter"
|
v-model="filter"
|
||||||
|
@ -57,12 +57,12 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { ShiftSlot, TimeSlot } from '~/shared/types/schedule';
|
import type { ShiftSlot, TimeSlot } from '~/shared/types/schedule';
|
||||||
|
|
||||||
const { data: session } = await useAccountSession();
|
const sessionStore = useSessionStore();
|
||||||
const { data: accounts } = await useAccounts();
|
const { data: accounts } = await useAccounts();
|
||||||
const schedule = await useSchedule();
|
const schedule = await useSchedule();
|
||||||
const isCrew = computed(() => (
|
const isCrew = computed(() => (
|
||||||
session.value?.account?.type === "crew"
|
sessionStore.account?.type === "crew"
|
||||||
|| session.value?.account?.type === "admin"
|
|| sessionStore.account?.type === "admin"
|
||||||
));
|
));
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
@ -78,12 +78,12 @@ const filter = computed({
|
||||||
});
|
});
|
||||||
|
|
||||||
const eventSlotFilter = computed(() => {
|
const eventSlotFilter = computed(() => {
|
||||||
if (filter.value === undefined || !session.value) {
|
if (filter.value === undefined || !sessionStore.account) {
|
||||||
return () => true;
|
return () => true;
|
||||||
}
|
}
|
||||||
const aid = session.value?.account?.id;
|
const aid = sessionStore.account?.id;
|
||||||
if (filter.value === "my-schedule") {
|
if (filter.value === "my-schedule") {
|
||||||
const ids = new Set(session.value?.account?.interestedIds);
|
const ids = new Set(sessionStore.account?.interestedIds);
|
||||||
for (const event of schedule.value.events) {
|
for (const event of schedule.value.events) {
|
||||||
if (ids.has(event.id)) {
|
if (ids.has(event.id)) {
|
||||||
for (const slot of event.slots) {
|
for (const slot of event.slots) {
|
||||||
|
@ -103,11 +103,11 @@ const eventSlotFilter = computed(() => {
|
||||||
return () => false;
|
return () => false;
|
||||||
});
|
});
|
||||||
const shiftSlotFilter = computed(() => {
|
const shiftSlotFilter = computed(() => {
|
||||||
if (filter.value === undefined || !session.value) {
|
if (filter.value === undefined || !sessionStore.account) {
|
||||||
return () => true;
|
return () => true;
|
||||||
}
|
}
|
||||||
if (filter.value === "my-schedule" || filter.value === "assigned") {
|
if (filter.value === "my-schedule" || filter.value === "assigned") {
|
||||||
const aid = session.value?.account?.id;
|
const aid = sessionStore.account?.id;
|
||||||
return (slot: ShiftSlot) => slot.assigned?.some(id => id === aid) || false;
|
return (slot: ShiftSlot) => slot.assigned?.some(id => id === aid) || false;
|
||||||
}
|
}
|
||||||
if (filter.value.startsWith("crew-")) {
|
if (filter.value.startsWith("crew-")) {
|
||||||
|
|
41
pnpm-lock.yaml
generated
41
pnpm-lock.yaml
generated
|
@ -8,12 +8,18 @@ importers:
|
||||||
|
|
||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@pinia/nuxt':
|
||||||
|
specifier: 0.11.0
|
||||||
|
version: 0.11.0(magicast@0.3.5)(pinia@3.0.2(typescript@5.8.2)(vue@3.5.14(typescript@5.8.2)))
|
||||||
luxon:
|
luxon:
|
||||||
specifier: ^3.5.0
|
specifier: ^3.5.0
|
||||||
version: 3.5.0
|
version: 3.5.0
|
||||||
nuxt:
|
nuxt:
|
||||||
specifier: ^3.17.4
|
specifier: ^3.17.4
|
||||||
version: 3.17.4(@parcel/watcher@2.5.1)(@types/node@22.13.8)(db0@0.3.2)(ioredis@5.6.1)(magicast@0.3.5)(rollup@4.41.0)(terser@5.39.0)(typescript@5.8.2)(vite@6.3.5(@types/node@22.13.8)(jiti@2.4.2)(terser@5.39.0)(yaml@2.7.0))(yaml@2.7.0)
|
version: 3.17.4(@parcel/watcher@2.5.1)(@types/node@22.13.8)(db0@0.3.2)(ioredis@5.6.1)(magicast@0.3.5)(rollup@4.41.0)(terser@5.39.0)(typescript@5.8.2)(vite@6.3.5(@types/node@22.13.8)(jiti@2.4.2)(terser@5.39.0)(yaml@2.7.0))(yaml@2.7.0)
|
||||||
|
pinia:
|
||||||
|
specifier: ^3.0.2
|
||||||
|
version: 3.0.2(typescript@5.8.2)(vue@3.5.14(typescript@5.8.2))
|
||||||
vue:
|
vue:
|
||||||
specifier: latest
|
specifier: latest
|
||||||
version: 3.5.14(typescript@5.8.2)
|
version: 3.5.14(typescript@5.8.2)
|
||||||
|
@ -633,6 +639,11 @@ packages:
|
||||||
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
|
resolution: {integrity: sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==}
|
||||||
engines: {node: '>= 10.0.0'}
|
engines: {node: '>= 10.0.0'}
|
||||||
|
|
||||||
|
'@pinia/nuxt@0.11.0':
|
||||||
|
resolution: {integrity: sha512-QGFlUAkeVAhPCTXacrtNP4ti24sGEleVzmxcTALY9IkS6U5OUox7vmNL1pkqBeW39oSNq/UC5m40ofDEPHB1fg==}
|
||||||
|
peerDependencies:
|
||||||
|
pinia: ^3.0.2
|
||||||
|
|
||||||
'@pkgjs/parseargs@0.11.0':
|
'@pkgjs/parseargs@0.11.0':
|
||||||
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
|
@ -949,6 +960,9 @@ packages:
|
||||||
'@vue/devtools-api@6.6.4':
|
'@vue/devtools-api@6.6.4':
|
||||||
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
|
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
|
||||||
|
|
||||||
|
'@vue/devtools-api@7.7.6':
|
||||||
|
resolution: {integrity: sha512-b2Xx0KvXZObePpXPYHvBRRJLDQn5nhKjXh7vUhMEtWxz1AYNFOVIsh5+HLP8xDGL7sy+Q7hXeUxPHB/KgbtsPw==}
|
||||||
|
|
||||||
'@vue/devtools-core@7.7.6':
|
'@vue/devtools-core@7.7.6':
|
||||||
resolution: {integrity: sha512-ghVX3zjKPtSHu94Xs03giRIeIWlb9M+gvDRVpIZ/cRIxKHdW6HE/sm1PT3rUYS3aV92CazirT93ne+7IOvGUWg==}
|
resolution: {integrity: sha512-ghVX3zjKPtSHu94Xs03giRIeIWlb9M+gvDRVpIZ/cRIxKHdW6HE/sm1PT3rUYS3aV92CazirT93ne+7IOvGUWg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
|
@ -2478,6 +2492,15 @@ packages:
|
||||||
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
|
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
pinia@3.0.2:
|
||||||
|
resolution: {integrity: sha512-sH2JK3wNY809JOeiiURUR0wehJ9/gd9qFN2Y828jCbxEzKEmEt0pzCXwqiSTfuRsK9vQsOflSdnbdBOGrhtn+g==}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.4.4'
|
||||||
|
vue: ^2.7.0 || ^3.5.11
|
||||||
|
peerDependenciesMeta:
|
||||||
|
typescript:
|
||||||
|
optional: true
|
||||||
|
|
||||||
pkg-types@1.3.1:
|
pkg-types@1.3.1:
|
||||||
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
|
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
|
||||||
|
|
||||||
|
@ -4240,6 +4263,13 @@ snapshots:
|
||||||
'@parcel/watcher-win32-ia32': 2.5.1
|
'@parcel/watcher-win32-ia32': 2.5.1
|
||||||
'@parcel/watcher-win32-x64': 2.5.1
|
'@parcel/watcher-win32-x64': 2.5.1
|
||||||
|
|
||||||
|
'@pinia/nuxt@0.11.0(magicast@0.3.5)(pinia@3.0.2(typescript@5.8.2)(vue@3.5.14(typescript@5.8.2)))':
|
||||||
|
dependencies:
|
||||||
|
'@nuxt/kit': 3.17.4(magicast@0.3.5)
|
||||||
|
pinia: 3.0.2(typescript@5.8.2)(vue@3.5.14(typescript@5.8.2))
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- magicast
|
||||||
|
|
||||||
'@pkgjs/parseargs@0.11.0':
|
'@pkgjs/parseargs@0.11.0':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
@ -4554,6 +4584,10 @@ snapshots:
|
||||||
|
|
||||||
'@vue/devtools-api@6.6.4': {}
|
'@vue/devtools-api@6.6.4': {}
|
||||||
|
|
||||||
|
'@vue/devtools-api@7.7.6':
|
||||||
|
dependencies:
|
||||||
|
'@vue/devtools-kit': 7.7.6
|
||||||
|
|
||||||
'@vue/devtools-core@7.7.6(vite@6.3.5(@types/node@22.13.8)(jiti@2.4.2)(terser@5.39.0)(yaml@2.7.0))(vue@3.5.14(typescript@5.8.2))':
|
'@vue/devtools-core@7.7.6(vite@6.3.5(@types/node@22.13.8)(jiti@2.4.2)(terser@5.39.0)(yaml@2.7.0))(vue@3.5.14(typescript@5.8.2))':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vue/devtools-kit': 7.7.6
|
'@vue/devtools-kit': 7.7.6
|
||||||
|
@ -6256,6 +6290,13 @@ snapshots:
|
||||||
|
|
||||||
picomatch@4.0.2: {}
|
picomatch@4.0.2: {}
|
||||||
|
|
||||||
|
pinia@3.0.2(typescript@5.8.2)(vue@3.5.14(typescript@5.8.2)):
|
||||||
|
dependencies:
|
||||||
|
'@vue/devtools-api': 7.7.6
|
||||||
|
vue: 3.5.14(typescript@5.8.2)
|
||||||
|
optionalDependencies:
|
||||||
|
typescript: 5.8.2
|
||||||
|
|
||||||
pkg-types@1.3.1:
|
pkg-types@1.3.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
confbox: 0.1.8
|
confbox: 0.1.8
|
||||||
|
|
61
stores/session.ts
Normal file
61
stores/session.ts
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import { appendResponseHeader } from "h3";
|
||||||
|
import type { H3Event } from "h3";
|
||||||
|
import type { Account } from "~/shared/types/account";
|
||||||
|
|
||||||
|
const fetchSessionWithCookie = async (event?: H3Event) => {
|
||||||
|
// Client side
|
||||||
|
if (!event) {
|
||||||
|
return $fetch("/api/auth/session");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server side
|
||||||
|
const cookie = useRequestHeader("cookie");
|
||||||
|
const res = await $fetch.raw("/api/auth/session", {
|
||||||
|
headers: cookie ? { cookie } : undefined
|
||||||
|
});
|
||||||
|
for (const cookie of res.headers.getSetCookie()) {
|
||||||
|
appendResponseHeader(event, "set-cookie", cookie);
|
||||||
|
}
|
||||||
|
return res._data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useSessionStore = defineStore("session", () => {
|
||||||
|
const state = {
|
||||||
|
account: ref<Account>(),
|
||||||
|
id: ref<number>(),
|
||||||
|
push: ref<boolean>(false),
|
||||||
|
};
|
||||||
|
|
||||||
|
const actions = {
|
||||||
|
async fetch(event?: H3Event) {
|
||||||
|
const session = await fetchSessionWithCookie(event)
|
||||||
|
state.account.value = session?.account;
|
||||||
|
state.id.value = session?.id;
|
||||||
|
state.push.value = session?.push ?? false;
|
||||||
|
},
|
||||||
|
async logIn(name: string) {
|
||||||
|
const res = await $fetch.raw("/api/auth/login", {
|
||||||
|
method: "POST",
|
||||||
|
body: { name },
|
||||||
|
});
|
||||||
|
await actions.fetch();
|
||||||
|
return `/api/auth/login replied: ${res.status} ${res.statusText}`;
|
||||||
|
},
|
||||||
|
async logOut() {
|
||||||
|
try {
|
||||||
|
await $fetch.raw("/api/auth/session", {
|
||||||
|
method: "DELETE",
|
||||||
|
});
|
||||||
|
await actions.fetch();
|
||||||
|
|
||||||
|
} catch (err: any) {
|
||||||
|
alert(`Log out failed: ${err.statusCode} ${err.statusMessage}`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
...actions,
|
||||||
|
};
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue