mirror of
https://github.com/ericasch6/scrum-poker-internal.git
synced 2026-10-15 17:46:09 +00:00
feat: some fixes to the ui, imlemented user and guest user, ans used the dahboard
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user