Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import { prerenderRoutes } from 'nuxt/app'
import { useSecretFeature } from '~/secrets/useSecretFeature'

prerenderRoutes(['/api/opass.json'])
prerenderRoutes(['/api/opass.json', '/api/session'])
useSecretFeature()

const route = useRoute()
Expand Down
21 changes: 21 additions & 0 deletions app/components/feature/CpSessionLoadingSkeleton.vue
Comment thread
pan93412 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<template>
<div class="flex flex-col">
<!-- DaySelector -->
<div class="px-6 pb-4 pt-3 flex w-[var(--viewport-width,100vw)] justify-center order-last sm:order-none">
<div class="rounded-full bg-gray-200 h-12 w-1/2 animate-pulse" />
</div>

<!-- FilterBar -->
<div class="p-4 flex flex-col gap-3 w-[var(--viewport-width,100vw)] items-stretch sm:flex-row sm:items-center sm:left-0 sm:justify-between sm:sticky sm:z-sticky">
<div class="flex shrink-0 gap-3 items-center justify-center sm:justify-start">
<div class="rounded-md bg-gray-200 h-12 w-18 animate-pulse sm:h-9" />
<div class="rounded-md bg-gray-200 h-12 w-18 animate-pulse sm:h-9" />
</div>

<div class="rounded-md bg-gray-200 h-12 w-full animate-pulse sm:flex-none sm:h-9 sm:w-80" />
</div>

<!-- Session -->
<div class="rounded-xl bg-gray-200 h-screen w-[var(--viewport-width,100vw)] animate-pulse" />
</div>
</template>
64 changes: 38 additions & 26 deletions app/pages/session.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
<script setup lang="ts">
import type { SessionSummary } from '#shared/types/session'
import type { TableViewMode } from '~/components/feature/CpSessionFilterBar.vue'
import { useStorage } from '@vueuse/core'
import { prerenderRoutes } from 'nuxt/app'
import { useI18n } from 'vue-i18n'
import { z } from 'zod'
import { useRoute, useRouter } from '#imports'
import { SessionSummarySchema } from '#shared/types/session'
import CpFavoriteImportBanner from '~/components/feature/CpFavoriteImportBanner.vue'
import CpSessionDaySelector from '~/components/feature/CpSessionDaySelector.vue'
import CpSessionEmptyBanner from '~/components/feature/CpSessionEmptyBanner.vue'
import CpSessionFilterBar from '~/components/feature/CpSessionFilterBar.vue'
import CpSessionList from '~/components/feature/CpSessionList.vue'
import CpSessionLoadingSkeleton from '~/components/feature/CpSessionLoadingSkeleton.vue'
import CpSessionShareButton from '~/components/feature/CpSessionShareButton.vue'
import CpSessionTable from '~/components/feature/CpSessionTable.vue'
import CpSessionTrackTable from '~/components/feature/CpSessionTrackTable.vue'
Expand All @@ -21,7 +25,24 @@ const { locale, t } = useI18n()
const route = useRoute()
const router = useRouter()

const { data } = await useFetch('/api/session')
type SessionsByDay = Record<string, SessionSummary[]>

const SessionsByDaySchema = z.record(z.string(), z.array(SessionSummarySchema))

async function parseSessionsByDay(value: unknown): Promise<SessionsByDay> {
const response = value instanceof Blob ? await value.text() : value
const parsed = typeof response === 'string' ? JSON.parse(response) as unknown : response
return SessionsByDaySchema.parse(parsed)
}

const { data, status } = useFetch('/api/session', {
server: false,
Comment thread
Link1515 marked this conversation as resolved.
transform: parseSessionsByDay,
default: (): SessionsByDay => ({}),
lazy: true,
})
const isSessionLoading = computed(() => status.value === 'idle' || status.value === 'pending')
const isSessionLoaded = computed(() => status.value === 'success')
const { isFavorite, setFavorites, favorites } = provideFavorites()

const days = computed(() => Object.keys(data?.value ?? {}).sort())
Expand Down Expand Up @@ -83,6 +104,10 @@ const selectedDay = computed({
})

watchEffect(() => {
if (!isSessionLoaded.value) {
return
}

if (route.query.day && !queryDay.value) {
const nextQuery = { ...route.query }
delete nextQuery.day
Expand Down Expand Up @@ -156,11 +181,14 @@ function importShared() {
view.value = 'favorite'
}

prerenderRoutes(
Object.values(data.value ?? {})
.flat()
.map((s) => `/session/${s.id}`),
)
if (import.meta.server && !route.params.id) {
const sessionsByDay = await $fetch<Record<string, SessionSummary[]>>('/api/session')
prerenderRoutes(
Object.values(sessionsByDay)
.flat()
.map((s) => `/session/${s.id}`),
)
}

useSeoMeta({
title: () => t('meta.title'),
Expand All @@ -182,28 +210,12 @@ definePageMeta({

<ClientOnly>
<template #fallback>
<div class="flex flex-col">
<!-- DaySelector -->
<div class="px-6 pb-4 pt-3 flex w-[var(--viewport-width,100vw)] justify-center order-last sm:order-none">
<div class="rounded-full bg-gray-200 h-12 w-1/2 animate-pulse" />
</div>

<!-- FilterBar -->
<div class="p-4 flex flex-col gap-3 w-[var(--viewport-width,100vw)] items-stretch sm:flex-row sm:items-center sm:left-0 sm:justify-between sm:sticky sm:z-sticky">
<div class="flex shrink-0 gap-3 items-center justify-center sm:justify-start">
<div class="rounded-md bg-gray-200 h-12 w-18 animate-pulse sm:h-9" />
<div class="rounded-md bg-gray-200 h-12 w-18 animate-pulse sm:h-9" />
</div>

<div class="rounded-md bg-gray-200 h-12 w-full animate-pulse sm:flex-none sm:h-9 sm:w-80" />
</div>

<!-- Session -->
<div class="rounded-xl bg-gray-200 h-screen w-[var(--viewport-width,100vw)] animate-pulse" />
</div>
<CpSessionLoadingSkeleton />
</template>

<template v-if="selectedDay">
<CpSessionLoadingSkeleton v-if="isSessionLoading" />

<template v-else-if="selectedDay">
<div class="flex flex-col">
<!-- A `?filter=` link carries shared favorites: preview them and offer import. -->
<div
Expand Down