316 lines
9.3 KiB
Vue
316 lines
9.3 KiB
Vue
<script setup lang="ts">
|
||
import { sluggy } from '#imports';
|
||
import type { DataTableColumnSource } from 'vuestic-ui';
|
||
import NumberCard from '~/components/showcase/NumberCard.vue';
|
||
import { fullDate } from '~~/server/utils/date';
|
||
|
||
definePageMeta({
|
||
layout: 'full'
|
||
});
|
||
const { t, d } = useI18n();
|
||
const localePath = useLocalePath();
|
||
const { tKey, hasKey, tSluggy } = useI18nKey();
|
||
|
||
const app = useNuxtApp();
|
||
const { name, id } = app.$slugData.huntSlug!;
|
||
const { data, error } = await useFetch(`/api/hunt/${id}/showcase`);
|
||
|
||
useHead({
|
||
title: t('layouts.two_title', {
|
||
title: tKey(data.value ?? { name_de: name, name_en: name }, 'name').value,
|
||
subtitle: t('page.showcase.title')
|
||
})
|
||
});
|
||
|
||
const { toggle, isFullscreen, isSupported } = useFullscreen();
|
||
|
||
const leaderboardColumns: DataTableColumnSource[] = [
|
||
'place',
|
||
'name',
|
||
'description',
|
||
{
|
||
key: 'score',
|
||
displayFormatFn: (data: number) => Number(data).toLocaleString('de')
|
||
},
|
||
'answers'
|
||
];
|
||
const columns = leaderboardColumns.map((c) =>
|
||
typeof c === 'string' ? c : c.key
|
||
);
|
||
// TODO: colors
|
||
</script>
|
||
|
||
<template>
|
||
<div
|
||
v-if="isSupported"
|
||
class="fixed left-2 top-2 opacity-25 transition-opacity hover:opacity-100"
|
||
>
|
||
<VaButton
|
||
round
|
||
border-color="primary"
|
||
color="onPrimary"
|
||
:icon="isFullscreen ? 'fullscreen_exit' : 'fullscreen'"
|
||
@click="toggle"
|
||
/>
|
||
</div>
|
||
<!-- eslint-disable-next-line vue/no-multiple-template-root -->
|
||
<article
|
||
v-if="data"
|
||
class="h-dvh w-full snap-y snap-mandatory overflow-y-scroll scroll-smooth"
|
||
>
|
||
<section id="title">
|
||
<div
|
||
class="flex flex-col items-center justify-center gap-8 bg-green-300 text-center"
|
||
>
|
||
<h1 class="text-7xl">{{ t('page.showcase.showcase_for') }}</h1>
|
||
<h1 class="text-9xl font-bold">
|
||
{{ hasKey(data, 'name') ? tKey(data, 'name') : name }}
|
||
</h1>
|
||
<p v-if="data.start || data.end" class="flex flex-row gap-4 text-4xl">
|
||
<span v-if="data.start">{{ d(data.start, fullDate) }}</span
|
||
><span v-if="data.start && data.end"> – </span
|
||
><span v-if="data.end">{{ d(data.end, fullDate) }}</span>
|
||
</p>
|
||
</div>
|
||
</section>
|
||
<section id="numbers">
|
||
<div class="grid grid-cols-3 gap-4 bg-red-100 p-4">
|
||
<NumberCard
|
||
:title="t('page.showcase.crew')"
|
||
:count="data._count.members + 1"
|
||
icon="support_agent"
|
||
/>
|
||
<NumberCard
|
||
:title="t('page.showcase.quests')"
|
||
:count="data._count.quests"
|
||
icon="home"
|
||
/>
|
||
<NumberCard
|
||
:title="t('page.showcase.users')"
|
||
:count="data.userCount"
|
||
icon="person"
|
||
/>
|
||
<NumberCard
|
||
:title="t('page.showcase.teams')"
|
||
:count="data._count.teams"
|
||
icon="groups"
|
||
/>
|
||
<NumberCard
|
||
:title="t('page.showcase.total_points')"
|
||
:count="data.totalScore"
|
||
icon="star"
|
||
/>
|
||
<NumberCard
|
||
:title="t('page.showcase.pictures')"
|
||
:count="data.pictureCount"
|
||
icon="photo"
|
||
/>
|
||
</div>
|
||
</section>
|
||
<section
|
||
v-for="quest in data.quests"
|
||
:id="tSluggy(quest).value"
|
||
:key="quest.id"
|
||
>
|
||
<div class="flex flex-col gap-4 bg-yellow-100 p-4">
|
||
<div class="my-8 flex w-full flex-row justify-around">
|
||
<h3 class="text-3xl">{{ tKey(quest, 'title') }}</h3>
|
||
<p v-if="hasKey(quest, 'question')" class="text-xl font-bold">
|
||
{{ tKey(quest, 'question') }}
|
||
</p>
|
||
<p v-if="quest.textSolution" class="text-3xl">
|
||
{{ quest.textSolution }}
|
||
</p>
|
||
</div>
|
||
<ClientOnly>
|
||
<VaCarousel
|
||
stateful
|
||
:items="quest.answers"
|
||
arrows
|
||
indicators
|
||
lazy
|
||
color="onPrimary"
|
||
indicator-trigger="hover"
|
||
class="w-full flex-grow"
|
||
style="--va-carousel-background: none"
|
||
>
|
||
<template #default="{ item }"
|
||
><VaImage :src="item.picture.url" fit="contain" lazy />
|
||
<p
|
||
class="absolute top-2 mx-auto rounded bg-gray-200/75 px-2 py-1 text-center text-xl"
|
||
>
|
||
{{ item.team.name }} — "{{ item.text }}" —
|
||
{{ item.picture.creator.name }}
|
||
</p></template
|
||
></VaCarousel
|
||
></ClientOnly
|
||
>
|
||
</div>
|
||
</section>
|
||
<section v-if="data.leaderboard.length > 3" id="leaderboard">
|
||
<div class="flex flex-col gap-4 bg-indigo-100">
|
||
<h1 class="mt-8 text-center text-6xl">
|
||
{{ t('page.showcase.leaderboard') }}
|
||
</h1>
|
||
<VaDataTable
|
||
class="h-full text-2xl"
|
||
virtual-scroller
|
||
striped
|
||
sticky-header
|
||
:items="data.leaderboard.slice(3)"
|
||
:columns="leaderboardColumns"
|
||
>
|
||
<template v-for="col in columns" #[col] :key="col"
|
||
><span>{{ t(`page.showcase.table.${col}`) }}</span></template
|
||
>
|
||
<template #cell(place)="{ rowIndex }">
|
||
<strong>{{ rowIndex + 4 }}</strong>
|
||
</template>
|
||
</VaDataTable>
|
||
</div>
|
||
</section>
|
||
<section v-if="data.leaderboard[2]" id="leaderboard-third">
|
||
<div class="place bg-[#AD8A56]">
|
||
<div class="place-left">
|
||
<VaIcon name="looks_3" size="10rem" />
|
||
<h2>{{ t('page.showcase.third_place') }}</h2>
|
||
</div>
|
||
<div class="place-right">
|
||
<h3>{{ data.leaderboard[2].name }}</h3>
|
||
<p>{{ data.leaderboard[2].description }}</p>
|
||
<NumberCard
|
||
:title="t('page.showcase.points')"
|
||
:count="data.leaderboard[2].score"
|
||
icon="star"
|
||
/>
|
||
<NumberCard
|
||
:title="t('page.showcase.answers')"
|
||
:count="data.leaderboard[2].answers"
|
||
icon="question_mark"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section v-if="data.leaderboard[1]" id="leaderboard-second">
|
||
<div class="place bg-[#B4B4B4]">
|
||
<div class="place-left">
|
||
<VaIcon name="looks_two" size="10rem" />
|
||
<h2>{{ t('page.showcase.second_place') }}</h2>
|
||
</div>
|
||
<div class="place-right">
|
||
<h3>{{ data.leaderboard[1].name }}</h3>
|
||
<p>{{ data.leaderboard[1].description }}</p>
|
||
<NumberCard
|
||
:title="t('page.showcase.points')"
|
||
:count="data.leaderboard[1].score"
|
||
icon="star"
|
||
/>
|
||
<NumberCard
|
||
:title="t('page.showcase.answers')"
|
||
:count="data.leaderboard[1].answers"
|
||
icon="question_mark"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section v-if="data.leaderboard[0]" id="leaderboard-winner-teaser">
|
||
<div class="place bg-[#C9B037]">
|
||
<div class="place-left">
|
||
<VaIcon name="looks_one" size="10rem" />
|
||
<h2>{{ t('page.showcase.first_place') }}</h2>
|
||
</div>
|
||
<div class="place-right">
|
||
<h3 class="bg-white text-transparent">
|
||
{{ data.leaderboard[0].name }}
|
||
</h3>
|
||
<p class="bg-white text-transparent">
|
||
{{ data.leaderboard[0].description }}
|
||
</p>
|
||
<NumberCard
|
||
:title="t('page.showcase.points')"
|
||
:count="data.leaderboard[0].score"
|
||
icon="star"
|
||
/>
|
||
<NumberCard
|
||
:title="t('page.showcase.answers')"
|
||
:count="data.leaderboard[0].answers"
|
||
icon="question_mark"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
<section v-if="data.leaderboard[0]" id="leaderboard-winner">
|
||
<div class="place bg-[#C9B037]">
|
||
<div class="place-left">
|
||
<VaIcon name="workspace_premium" size="10rem" />
|
||
<h2>{{ t('page.showcase.first_place') }}</h2>
|
||
</div>
|
||
<div class="place-right">
|
||
<h3>{{ data.leaderboard[0].name }}</h3>
|
||
<p>{{ data.leaderboard[0].description }}</p>
|
||
<NumberCard
|
||
:title="t('page.showcase.points')"
|
||
:count="data.leaderboard[0].score"
|
||
icon="star"
|
||
/>
|
||
<NumberCard
|
||
:title="t('page.showcase.answers')"
|
||
:count="data.leaderboard[0].answers"
|
||
icon="question_mark"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</article>
|
||
|
||
<article
|
||
v-else-if="error"
|
||
class="flex h-dvh w-full flex-col items-center justify-center text-center text-2xl"
|
||
>
|
||
<VaAlert color="danger" class="w-[90%]">{{
|
||
t('page.common.not_revealed')
|
||
}}</VaAlert>
|
||
<VaAlert v-if="error.statusCode !== 403" color="danger" class="w-[90%]">{{
|
||
error
|
||
}}</VaAlert
|
||
><VaButton
|
||
icon="arrow_back"
|
||
:to="localePath(`/hunt/${sluggy($nuxt.$slugData.huntSlug!)}`)"
|
||
>{{ t('page.common.back_to_hunt') }}</VaButton
|
||
>
|
||
</article>
|
||
</template>
|
||
|
||
<style scoped>
|
||
section {
|
||
@apply mt-6 h-dvh snap-center p-6 first:mt-0;
|
||
}
|
||
|
||
section > div {
|
||
@apply h-full w-full rounded-lg;
|
||
}
|
||
|
||
.place {
|
||
@apply grid h-full w-full grid-cols-4 items-center p-4;
|
||
}
|
||
|
||
.place-left {
|
||
@apply flex h-full flex-col justify-center gap-4 text-center text-white;
|
||
}
|
||
|
||
.place-right {
|
||
@apply col-span-3 grid h-full grid-cols-2 gap-4 text-center text-white;
|
||
}
|
||
|
||
.place-right h3 {
|
||
@apply mt-auto text-6xl font-bold;
|
||
}
|
||
.place-right p {
|
||
@apply mt-auto text-3xl font-bold;
|
||
}
|
||
|
||
.place h2 {
|
||
@apply text-5xl font-bold;
|
||
}
|
||
</style>
|