feat: some fixes to the ui, imlemented user and guest user, ans used the dahboard

This commit is contained in:
2026-08-16 16:24:25 +02:00
parent 4639baa9ae
commit 50e9fbd0b0
37 changed files with 1657 additions and 194 deletions
+1 -2
View File
@@ -1,11 +1,10 @@
import { createInertiaApp } from '@inertiajs/vue3';
import { initializeTheme } from '@/composables/useAppearance';
import PublicLayout from './layouts/PublicLayout.vue';
import { initializeFlashToast } from '@/lib/flashToast';
import PublicLayout from './layouts/PublicLayout.vue';
const appName = import.meta.env.VITE_APP_NAME || 'Laravel';
createInertiaApp({
title: (title) => (title ? `${title} - ${appName}` : appName),
+2 -2
View File
@@ -36,7 +36,7 @@ import { useCurrentUrl } from '@/composables/useCurrentUrl';
import { getInitials } from '@/composables/useInitials';
import { toUrl } from '@/lib/utils';
import { dashboard } from '@/routes';
import type { BreadcrumbItem, NavItem } from '@/types';
import type { BreadcrumbItem, NavItem, User } from '@/types';
type Props = {
breadcrumbs?: BreadcrumbItem[];
@@ -47,7 +47,7 @@ const props = withDefaults(defineProps<Props>(), {
});
const page = usePage();
const auth = computed(() => page.props.auth);
const auth = computed(() => page.props.auth as { user: User });
const { isCurrentUrl, whenCurrentUrl } = useCurrentUrl();
const activeItemStyles =
+2 -1
View File
@@ -15,9 +15,10 @@ import {
} from '@/components/ui/sidebar';
import UserInfo from '@/components/UserInfo.vue';
import UserMenuContent from '@/components/UserMenuContent.vue';
import type { User } from '@/types';
const page = usePage();
const user = computed(() => page.props.auth.user);
const user = computed(() => page.props.auth.user as User);
const { isMobile, state } = useSidebar();
</script>
+49 -29
View File
@@ -1,35 +1,56 @@
<script setup>
import { ref } from 'vue'
<script setup lang="ts">
import { Link, usePage } from '@inertiajs/vue3';
import { computed } from 'vue';
import { dashboard, home, login, logout, register } from '@/routes';
const navigation = [
]
const mobileMenuOpen = ref(false)
const page = usePage();
const user = computed(() => page.props.auth.user);
</script>
<template>
<div class="min-h-svh bg-gray-900">
<div class="min-h-svh bg-gray-900 text-white">
<header class="border-b border-white/10">
<nav class="mx-auto flex h-16 max-w-7xl items-center justify-between px-6" aria-label="Global">
<nav class="flex items-center w-full justify-between p-6 lg:px-8" aria-label="Global">
<div class="flex lg:flex-1">
<a href="#" class="-m-1.5 p-1.5">
<span class="sr-only">Your Company</span> </a>
</div>
<div class="flex lg:hidden">
<button type="button"
class="-m-2.5 inline-flex items-center justify-center rounded-md p-2.5 text-gray-200"
@click="mobileMenuOpen = true">
<span class="sr-only">Open main menu</span>
</button>
</div>
<div class="hidden lg:flex lg:gap-x-12">
<h1 class="font-extrabold text-2xl">Scrum Poker</h1>
</div>
<div class="hidden lg:flex lg:flex-1 lg:justify-end">
<a href="#" class="text-sm/6 font-semibold text-white">Log in <span
aria-hidden="true">&rarr;</span></a>
</div>
</nav>
<nav
class="mx-auto flex h-16 max-w-7xl items-center justify-between px-6"
aria-label="Hauptnavigation"
>
<Link :href="home()" class="text-xl font-extrabold">
Scrum Poker
</Link>
<div class="flex items-center gap-4 text-sm font-semibold">
<template v-if="user">
<Link
:href="dashboard()"
class="text-gray-200 transition hover:text-white"
>
Meine Räume
</Link>
<Link
:href="logout()"
method="post"
as="button"
class="rounded-lg border border-white/15 px-3 py-2 text-gray-200 transition hover:bg-white/10 hover:text-white"
>
Abmelden
</Link>
</template>
<template v-else>
<Link
:href="login()"
class="text-gray-200 transition hover:text-white"
>
Anmelden
</Link>
<Link
:href="register()"
class="rounded-lg bg-white px-3 py-2 text-gray-900 transition hover:bg-gray-100"
>
Registrieren
</Link>
</template>
</div>
</nav>
</header>
@@ -38,4 +59,3 @@
</main>
</div>
</template>
+215 -24
View File
@@ -1,47 +1,238 @@
<script setup lang="ts">
import { Head } from '@inertiajs/vue3';
import PlaceholderPattern from '@/components/PlaceholderPattern.vue';
import { Head, Link, useForm } from '@inertiajs/vue3';
import { CalendarClock, DoorOpen, Plus, Trash2, Users } from '@lucide/vue';
import { ref } from 'vue';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { dashboard } from '@/routes';
import { create, destroy, reopen, show } from '@/routes/room';
type OwnedRoom = {
id: string;
title: string | null;
join_code: string;
current_round: number;
players_count: number;
last_activity_at: string | null;
closed_at: string | null;
};
defineOptions({
layout: {
breadcrumbs: [
{
title: 'Dashboard',
title: 'Meine Räume',
href: dashboard(),
},
],
},
});
defineProps<{
rooms: OwnedRoom[];
}>();
const selectedRoom = ref<OwnedRoom | null>(null);
const deleteForm = useForm({});
const dateFormatter = new Intl.DateTimeFormat('de-DE', {
dateStyle: 'medium',
timeStyle: 'short',
});
function formatDate(value: string | null): string {
return value ? dateFormatter.format(new Date(value)) : 'Keine Aktivität';
}
function setDeleteDialogOpen(open: boolean): void {
if (!open && !deleteForm.processing) {
selectedRoom.value = null;
}
}
function deleteRoom(): void {
if (selectedRoom.value === null || deleteForm.processing) {
return;
}
deleteForm.delete(destroy(selectedRoom.value.id).url, {
preserveScroll: true,
onSuccess: () => {
selectedRoom.value = null;
},
});
}
</script>
<template>
<Head title="Dashboard" />
<Head title="Meine Räume" />
<div
class="flex h-full flex-1 flex-col gap-4 overflow-x-auto rounded-xl p-4"
class="flex min-h-[calc(100svh-4rem)] flex-1 flex-col gap-6 overflow-x-auto bg-slate-900 p-4 text-slate-100"
>
<div class="grid auto-rows-min gap-4 md:grid-cols-3">
<div
class="relative aspect-video overflow-hidden rounded-xl border border-sidebar-border/70 dark:border-sidebar-border"
>
<PlaceholderPattern />
</div>
<div
class="relative aspect-video overflow-hidden rounded-xl border border-sidebar-border/70 dark:border-sidebar-border"
>
<PlaceholderPattern />
</div>
<div
class="relative aspect-video overflow-hidden rounded-xl border border-sidebar-border/70 dark:border-sidebar-border"
>
<PlaceholderPattern />
</div>
</div>
<div
class="relative min-h-[100vh] flex-1 rounded-xl border border-sidebar-border/70 md:min-h-min dark:border-sidebar-border"
class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
<PlaceholderPattern />
<div>
<h1 class="text-2xl font-bold tracking-tight">Meine Räume</h1>
<p class="mt-1 text-sm text-muted-foreground">
Deine erstellten Scrum-Poker-Räume auf einen Blick.
</p>
</div>
<Link
:href="create()"
class="inline-flex w-fit items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground transition hover:bg-primary/90"
>
<Plus class="size-4" />
Raum erstellen
</Link>
</div>
<div
v-if="rooms.length > 0"
class="grid gap-4 md:grid-cols-2 xl:grid-cols-3"
>
<article
v-for="room in rooms"
:key="room.id"
class="flex flex-col rounded-md border border-slate-700 bg-slate-800 p-5 text-slate-100"
>
<div class="flex items-start justify-between gap-4">
<div class="min-w-0">
<h2 class="text-lg font-semibold break-words">
{{ room.title ?? 'Scrum Poker' }}
</h2>
<p
class="mt-1 font-mono text-sm tracking-[0.18em] text-muted-foreground"
>
{{ room.join_code }}
</p>
</div>
<span
class="shrink-0 text-sm font-semibold normal-case"
:class="
room.closed_at
? 'text-muted-foreground'
: 'text-emerald-600 dark:text-emerald-400'
"
>
{{ room.closed_at ? 'Geschlossen' : 'Aktiv' }}
</span>
</div>
<div
class="mt-5 grid grid-cols-2 gap-3 text-sm text-muted-foreground"
>
<span class="inline-flex items-center gap-2">
<Users class="size-4" />
{{ room.players_count }} Spieler
</span>
<span>Runde {{ room.current_round }}</span>
<span class="col-span-2 inline-flex items-center gap-2">
<CalendarClock class="size-4" />
{{ formatDate(room.last_activity_at) }}
</span>
</div>
<div class="mt-6 flex flex-wrap items-center gap-3">
<Link
v-if="!room.closed_at"
:href="show(room.id)"
class="inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-semibold transition hover:bg-blue-500/80"
>
<DoorOpen class="size-4" />
Raum beitreten
</Link>
<Link
v-else
:href="reopen(room.id)"
method="post"
as="button"
class="inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-semibold text-primary-foreground transition hover:bg-primary/90"
>
<DoorOpen class="size-4" />
Raum öffnen
</Link>
<button
type="button"
class="inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-semibold text-rose-300 transition hover:bg-rose-500/80"
@click="selectedRoom = room"
>
<Trash2 class="size-4" />
Raum löschen
</button>
</div>
</article>
</div>
<div
v-else
class="grid min-h-72 place-items-center rounded-md border border-dashed border-sidebar-border p-8 text-center"
>
<div>
<h2 class="text-lg font-semibold">Noch keine eigenen Räume</h2>
<p class="mt-2 max-w-md text-sm text-muted-foreground">
Erstelle deinen ersten Raum. Er wird automatisch in diesem
Dashboard gespeichert.
</p>
<Link
:href="create()"
class="mt-5 inline-flex items-center gap-2 rounded-md bg-primary px-4 py-2 text-sm font-semibold text-primary-foreground"
>
<Plus class="size-4" />
Ersten Raum erstellen
</Link>
</div>
</div>
<Dialog
:open="selectedRoom !== null"
@update:open="setDeleteDialogOpen"
>
<DialogContent
class="border-slate-700 bg-slate-800 text-slate-100 sm:max-w-md"
>
<DialogHeader>
<DialogTitle>Raum wirklich löschen?</DialogTitle>
<DialogDescription class="text-slate-300">
„{{ selectedRoom?.title ?? 'Scrum Poker' }}“ wird aus
deinem Dashboard entfernt. Alle Spieler, die den Raum
gerade geöffnet haben, werden automatisch
hinausgeleitet.
</DialogDescription>
</DialogHeader>
<DialogFooter class="gap-6 space-x-4 sm:gap-0">
<button
type="button"
class="rounded-md border border-slate-600 px-4 py-2 text-sm font-semibold text-slate-200 transition hover:bg-slate-700"
:disabled="deleteForm.processing"
@click="selectedRoom = null"
>
Abbrechen
</button>
<button
type="button"
class="rounded-md bg-rose-600 px-4 py-2 text-sm font-semibold text-white transition hover:bg-rose-500 disabled:opacity-50"
:disabled="deleteForm.processing"
@click="deleteRoom"
>
{{
deleteForm.processing
? 'Wird gelöscht …'
: 'Raum löschen'
}}
</button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
</template>
+20 -19
View File
@@ -1,35 +1,36 @@
<script setup lang="ts">
import { create, join } from '@/routes/room';
import { Link } from '@inertiajs/vue3';
import { create, join } from '@/routes/room';
</script>
<template>
<main class="p-6">
<div class="flex flex-col gap-6 justify-start items-center min-h-screen">
<div
class="flex min-h-screen flex-col items-center justify-start gap-6"
>
<h1 class="mt-10 text-4xl font-extrabold">Welcome to ScrumPoker</h1>
<h1 class="text-4xl font-extrabold mt-10"> Welcome to ScrumPoker</h1>
<div class="h-full mt-[6%] space-y-8 flex justify-center flex-col items-center">
<p class="font-bold"> Create or Join a Room? </p>
<div class=" flex justify-between gap-6 font-extrabold mt-6">
<div
class="mt-[6%] flex h-full flex-col items-center justify-center space-y-8"
>
<p class="font-bold">Create or Join a Room?</p>
<div class="mt-6 flex justify-between gap-6 font-extrabold">
<Link :href="join()">
<p class="bg-gray-600 rounded-2xl min-w-30 px-4 py-2 text-center"> Join</p>
<p
class="min-w-30 rounded-2xl bg-gray-600 px-4 py-2 text-center"
>
Join
</p>
</Link>
<Link :href="create()">
<p class="bg-gray-600 rounded-2xl min-w-30 px-4 py-2 text-center"> Create</p>
<p
class="min-w-30 rounded-2xl bg-gray-600 px-4 py-2 text-center"
>
Create
</p>
</Link>
</div>
</div>
</div>
</main>
</template>
+53 -39
View File
@@ -1,21 +1,20 @@
<script setup lang="ts">
import { store } from '@/routes/room';
import { Head, useForm } from '@inertiajs/vue3';
import { store } from '@/routes/room';
type Deck = {
id: number,
name: string,
id: number;
name: string;
cards: Array<{
key: string,
label: string,
key: string;
label: string;
}>;
};
const props = defineProps<{
decks: Deck[];
defaultPlayerName: string | null;
}>()
}>();
const form = useForm({
title: '',
@@ -29,26 +28,31 @@ function submit(): void {
</script>
<template>
<Head title="Raum erstellen" />
<main class="mx-auto max-w-lg p-6">
<h1 class="mb-6 text-2xl font-bold">
Scrum Poker Raum erstellen
</h1>
<h1 class="mb-6 text-2xl font-bold">Scrum Poker Raum erstellen</h1>
<form class="space-y-5" @submit.prevent="submit">
<div>
<label for="player_name" class="mb-1 block font-medium">
Dein Name
</label>
<input id="player_name" v-model="form.player_name" type="text" maxlength="50" required
class="w-full rounded border p-2" placeholder="Eric">
<p v-if="form.errors.player_name" class="mt-1 text-sm text-red-600">
<input
id="player_name"
v-model="form.player_name"
type="text"
maxlength="50"
required
class="w-full rounded border p-2"
placeholder="Eric"
/>
<p
v-if="form.errors.player_name"
class="mt-1 text-sm text-red-600"
>
{{ form.errors.player_name }}
</p>
</div>
<div>
@@ -56,14 +60,18 @@ function submit(): void {
Raumtitle
</label>
<input id="title" v-model="form.title" type="text" maxlength="100" class="w-full rounded border p-2"
placeholder="Sprint Planning" />
<input
id="title"
v-model="form.title"
type="text"
maxlength="100"
class="w-full rounded border p-2"
placeholder="Sprint Planning"
/>
<p v-if="form.errors.title" class="mt-1 text-sm text-red-600">
{{ form.errors.title }}
</p>
</div>
<div>
@@ -71,49 +79,55 @@ function submit(): void {
Kartendeck
</label>
<select id="deck_id" v-model.number="form.deck_id" required class="w-full rounded border p-2">
<option v-for="deck in decks" :key="deck.id" :value="deck.id">
<select
id="deck_id"
v-model.number="form.deck_id"
required
class="w-full rounded border p-2"
>
<option
v-for="deck in decks"
:key="deck.id"
:value="deck.id"
>
{{ deck.name }}
</option>
</select>
<p v-if="form.errors.deck_id" class="mt-1 text-sm text-red-600">
{{ form.errors.deck_id }}
</p>
</div>
<div v-if="form.deck_id" class="rounded border p-3">
<p class="mb-2 text-sm font-medium">
Karten:
</p>
<p class="mb-2 text-sm font-medium">Karten:</p>
<div class="flex flex-wrap gap-2">
<template v-for="deck in decks" :key="deck.id">
<span v-for="card in deck.id === form.deck_id ? deck.cards : []" :key="card.key"
class="rounded border px-3 py-1">
<span
v-for="card in deck.id === form.deck_id
? deck.cards
: []"
:key="card.key"
class="rounded border px-3 py-1"
>
{{ card.label }}
</span>
</template>
</div>
</div>
<button type="submit" :disabled="form.processing || decks.length === 0"
class="w-full rounded bg-blue-600 px-4 py-2 text-white disabled:opacity-50">
<button
type="submit"
:disabled="form.processing || decks.length === 0"
class="w-full rounded bg-blue-600 px-4 py-2 text-white disabled:opacity-50"
>
{{ form.processing ? 'Raum wird erstellt' : 'Raum erstellen' }}
</button>
</form>
<p v-if="decks.length === 0" class="mt-4 text-red-600">
Es wurden noch keine Kartendecks angelegt.
</p>
</main>
</template>
</template>
+95 -40
View File
@@ -1,69 +1,124 @@
<script setup lang="ts">
import { code} from '@/routes/room';
import { useForm } from '@inertiajs/vue3';
import { Head, Link, useForm, usePage } from '@inertiajs/vue3';
import { computed } from 'vue';
import { dashboard } from '@/routes';
import { code } from '@/routes/room';
const props = defineProps <{
join_code?: string,
}>()
const props = defineProps<{
join_code?: string;
defaultPlayerName: string | null;
closedRoom: {
title: string | null;
join_code: string;
} | null;
}>();
const page = usePage();
const isAuthenticated = computed(() => page.props.auth.user !== null);
const form = useForm({
player_name: '',
player_name: props.defaultPlayerName ?? '',
join_code: props.join_code ?? '',
});
function submit(): void {
form.submit(code());
}
</script>
<template>
<main>
<div class="flex flex-col gap-6 justify-start items-center min-h-screen">
<Head title="Raum beitreten" />
<h1 class="text-4xl font-extrabold mt-10"> Tritt einer Runde bei </h1>
<main
class="mx-auto flex min-h-[calc(100svh-8rem)] max-w-lg items-start justify-center pt-10"
>
<section
class="w-full rounded-xl border border-white/10 bg-gray-800 p-6"
>
<template v-if="closedRoom">
<p class="text-sm font-semibold text-amber-300">
Raum geschlossen
</p>
<h1 class="mt-2 text-2xl font-bold">
{{ closedRoom.title ?? 'Scrum Poker' }}
</h1>
<p class="mt-3 text-gray-300">
Der Raum mit dem Code
<strong class="font-mono tracking-wider">{{
closedRoom.join_code
}}</strong>
ist geschlossen und kann nicht mehr betreten werden.
</p>
<Link
v-if="isAuthenticated"
:href="dashboard()"
class="mt-6 inline-flex rounded-lg bg-white px-4 py-2 font-semibold text-gray-900"
>
Zu meinen Räumen
</Link>
</template>
<div class="p-6 flex justify-center items-center flex-col gap-8">
<template v-else>
<h1 class="text-2xl font-bold">Einem Raum beitreten</h1>
<p class="mt-2 text-sm text-gray-400">
Gib deinen Namen und den sechsstelligen Raumcode ein.
</p>
<form class="space-y-5" @submit.prevent="submit">
<form class="mt-6 space-y-5" @submit.prevent="submit">
<div>
<label for="player_name" class="mb-1 block font-medium">
Player Name
</label>
<input id="title" v-model="form.player_name" type="text" maxlength="100"
class="w-full rounded border p-2" placeholder="Max Mustermann" />
<p v-if="form.errors.player_name" class="mt-1 text-sm text-red-600">
<label for="player_name" class="mb-1 block font-medium"
>Dein Name</label
>
<input
id="player_name"
v-model="form.player_name"
type="text"
maxlength="50"
required
class="w-full rounded-lg border border-gray-600 bg-gray-900 p-2.5 text-white"
placeholder="Max Mustermann"
/>
<p
v-if="form.errors.player_name"
class="mt-1 text-sm text-red-400"
>
{{ form.errors.player_name }}
</p>
</div>
<div>
<label for="join_code" class="mb-1 block font-medium">
Raum Code
</label>
<input id="title" v-model="form.join_code" type="text" maxlength="100"
class="w-full rounded border p-2" />
<p v-if="form.errors.join_code" class="mt-1 text-sm text-red-600">
<label for="join_code" class="mb-1 block font-medium"
>Raumcode</label
>
<input
id="join_code"
v-model="form.join_code"
type="text"
maxlength="6"
required
class="w-full rounded-lg border border-gray-600 bg-gray-900 p-2.5 font-mono tracking-[0.2em] text-white uppercase"
/>
<p
v-if="form.errors.join_code"
class="mt-1 text-sm text-red-400"
>
{{ form.errors.join_code }}
</p>
</div>
<button type="submit" :disabled="form.processing"
class="w-full rounded bg-blue-600 px-4 py-2 text-white disabled:opacity-50">
{{ form.processing ? 'Raum wird beigetreten' : 'Raum beitreten' }}
<button
type="submit"
:disabled="form.processing"
class="w-full rounded-lg bg-blue-600 px-4 py-2.5 font-semibold text-white transition hover:bg-blue-500 disabled:opacity-50"
>
{{
form.processing
? 'Raum wird geöffnet'
: 'Raum beitreten'
}}
</button>
</form>
</div>
</div>
</template>
</section>
</main>
</template>
</template>
+74 -5
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { Head, useForm, usePoll } from '@inertiajs/vue3';
import { Head, Link, useForm, usePage, usePoll } from '@inertiajs/vue3';
import {
Check,
CircleUserRound,
@@ -8,9 +8,11 @@ import {
Eye,
RotateCcw,
Trash2,
X,
} from '@lucide/vue';
import { computed, ref, watch } from 'vue';
import { login, register } from '@/routes';
import { nextRound, reset as resetRound, reveal } from '@/routes/room';
import { store as storeVote } from '@/routes/room/vote';
import type { PokerCard, RoomPlayer, SeatStyle } from '@/types/room';
@@ -23,6 +25,10 @@ const props = defineProps<{
current_round: number;
revealed: boolean;
state_version: number;
statistics: {
average: string | null;
median: string | null;
} | null;
deck: {
name: string;
cards: PokerCard[];
@@ -37,6 +43,15 @@ const props = defineProps<{
};
}>();
const page = usePage();
const roomNotice = computed(
() =>
page.props.roomNotice as {
guest_retention_days: number;
account_retention_days: number;
} | null,
);
const voteForm = useForm({
value: props.currentPlayer.current_vote ?? '',
});
@@ -44,6 +59,7 @@ const revealForm = useForm<{ room?: string }>({});
const nextRoundForm = useForm<{ room?: string }>({});
const resetForm = useForm<{ room?: string }>({});
const copied = ref(false);
const roomNoticeDismissed = ref(false);
usePoll(2_000, {
only: ['room', 'currentPlayer'],
@@ -168,6 +184,44 @@ function playerVote(player: RoomPlayer): string | null {
<main
class="-m-6 min-h-[calc(100svh-4rem)] bg-slate-900 px-3 py-6 text-white sm:px-6 lg:px-10"
>
<aside
v-if="roomNotice && !roomNoticeDismissed"
class="mx-auto mb-5 flex max-w-6xl flex-col gap-4 rounded-lg border border-dashed border-slate-500 bg-slate-800 p-4 text-sm text-slate-100 sm:flex-row sm:items-center"
role="status"
>
<div class="min-w-0 flex-1">
<p class="font-semibold">
Speichere diesen Raum in deinem Konto
</p>
<p class="mt-1 text-slate-300">
Als Gast wird der Raum nach
{{ roomNotice.guest_retention_days }} Tagen Inaktivität
geschlossen. Nach der Anmeldung bleibt er in deinem
Dashboard gespeichert und die Frist beträgt
{{ roomNotice.account_retention_days }} Tage.
</p>
</div>
<div
class="flex shrink-0 items-center gap-3 font-semibold sm:ml-auto"
>
<Link :href="login()" class="underline underline-offset-4">
Anmelden
</Link>
<Link :href="register()" class="underline underline-offset-4">
Registrieren
</Link>
<button
type="button"
class="ml-1 grid size-8 place-items-center rounded-md text-slate-300 transition hover:bg-slate-700 hover:text-white"
aria-label="Hinweis schließen"
@click="roomNoticeDismissed = true"
>
<X class="size-4" />
</button>
</div>
</aside>
<header
class="mx-auto flex max-w-6xl flex-col gap-4 sm:flex-row sm:items-center sm:justify-between"
>
@@ -213,12 +267,27 @@ function playerVote(player: RoomPlayer): string | null {
}}
von {{ room.players.length }} haben gewählt
</span>
<span
<div
v-else
class="text-sm font-semibold text-amber-200"
class="grid grid-cols-2 gap-8 text-emerald-50"
>
Karten aufgedeckt
</span>
<div class="flex flex-col gap-1">
<span class="text-xs text-emerald-100/65">
Durchschnitt
</span>
<strong class="text-xl">
{{ room.statistics?.average ?? '–' }}
</strong>
</div>
<div class="flex flex-col gap-1">
<span class="text-xs text-emerald-100/65">
Median
</span>
<strong class="text-xl">
{{ room.statistics?.median ?? '–' }}
</strong>
</div>
</div>
</div>
</div>
+2 -1
View File
@@ -11,6 +11,7 @@ import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { edit } from '@/routes/profile';
import { send } from '@/routes/verification';
import type { User } from '@/types';
defineOptions({
layout: {
@@ -24,7 +25,7 @@ defineOptions({
});
const page = usePage();
const user = computed(() => page.props.auth.user);
const user = computed(() => page.props.auth.user as User);
</script>
<template>
+4
View File
@@ -18,6 +18,10 @@ declare module '@inertiajs/core' {
sharedPageProps: {
name: string;
auth: Auth;
roomNotice: {
guest_retention_days: number;
account_retention_days: number;
} | null;
sidebarOpen: boolean;
[key: string]: unknown;
};