155 lines
6.3 KiB
Vue
155 lines
6.3 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onMounted, ref } from 'vue'
|
|
import Select from 'primevue/select'
|
|
import { useAwardsStore } from '../stores/awards'
|
|
import { useAuthStore } from '../stores/auth'
|
|
import Button from '../components/ui/Button.vue'
|
|
import Card from '../components/ui/Card.vue'
|
|
|
|
const store = useAwardsStore()
|
|
const authStore = useAuthStore()
|
|
const selectedCategoryId = ref<number | null>(null)
|
|
const nomineeName = ref('')
|
|
const nominees = ref<string[]>(['Hoshimi Miyu', 'Kurainu'])
|
|
const submitting = ref(false)
|
|
const submitMessage = ref('')
|
|
const submitError = ref('')
|
|
|
|
onMounted(async () => {
|
|
await store.loadHomeData()
|
|
selectedCategoryId.value = store.categories.categories[0]?.id ?? null
|
|
})
|
|
|
|
const categories = computed(() =>
|
|
store.categories.categories.map((category) => ({
|
|
label: category.name,
|
|
value: category.id,
|
|
})),
|
|
)
|
|
|
|
const selectedCategory = computed(() =>
|
|
store.categories.categories.find((category) => category.id === selectedCategoryId.value),
|
|
)
|
|
|
|
function addNominee() {
|
|
const value = nomineeName.value.trim()
|
|
if (!value || nominees.value.includes(value) || nominees.value.length >= 3) return
|
|
nominees.value = [...nominees.value, value]
|
|
nomineeName.value = ''
|
|
}
|
|
|
|
function removeNominee(name: string) {
|
|
nominees.value = nominees.value.filter((entry) => entry !== name)
|
|
}
|
|
|
|
async function submitNomination() {
|
|
if (!selectedCategoryId.value || nominees.value.length === 0) return
|
|
|
|
submitting.value = true
|
|
submitMessage.value = ''
|
|
submitError.value = ''
|
|
|
|
try {
|
|
const response = await store.submitNomination({
|
|
year: store.categories.year,
|
|
categoryId: selectedCategoryId.value,
|
|
twitchUserId: authStore.session?.twitchUserId ?? '',
|
|
nominees: nominees.value,
|
|
})
|
|
|
|
submitMessage.value = `${response.saved} Nominierungen fuer ${response.category} gespeichert.`
|
|
} catch (error) {
|
|
submitError.value = error instanceof Error ? error.message : 'Nominierung konnte nicht gespeichert werden.'
|
|
} finally {
|
|
submitting.value = false
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="space-y-10 pb-14">
|
|
<div class="space-y-4">
|
|
<p class="text-xs font-semibold uppercase tracking-[0.35em] text-amber-500">Nominierungs-Flow</p>
|
|
<h1 class="max-w-[12ch] font-[Cormorant_Garamond] text-6xl leading-[0.92] text-violet-800">Kategorien waehlen, Regeln live pruefen</h1>
|
|
<p class="max-w-3xl text-lg leading-8 text-slate-600">
|
|
Nur Twitch Login, kein separates Konto. Das Team pflegt Kategorien pro Jahr, waehrend die UI sofort Limits, Dubletten und editierbare Entwuerfe abbildet.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="grid gap-6 lg:grid-cols-[0.68fr_1.32fr]">
|
|
<Card class="p-7">
|
|
<p class="text-xs font-semibold uppercase tracking-[0.25em] text-violet-500">Regeln</p>
|
|
<ul class="mt-5 space-y-4 text-slate-600">
|
|
<li>Pro Kategorie nur eine Nominierung derselben Person.</li>
|
|
<li>Insgesamt maximal drei Nominierungen in diesem Draft.</li>
|
|
<li>Freitext-Ideen landen spaeter in der Review-Liste.</li>
|
|
<li>Bereits gespeicherte Entwuerfe koennen bis zur Deadline bearbeitet werden.</li>
|
|
</ul>
|
|
</Card>
|
|
|
|
<Card class="p-7">
|
|
<div class="grid gap-6 lg:grid-cols-[0.8fr_1.2fr]">
|
|
<div class="space-y-5">
|
|
<p v-if="!authStore.isLoggedIn" class="rounded-[26px] border border-amber-200 bg-amber-50 px-5 py-4 text-sm text-amber-700">
|
|
Bitte zuerst ueber den Kopfbereich mit einem Twitch-Account einloggen, damit die Nominierung gespeichert werden kann.
|
|
</p>
|
|
|
|
<label class="text-sm font-semibold text-slate-600">Kategorie</label>
|
|
<Select
|
|
v-model="selectedCategoryId"
|
|
:options="categories"
|
|
option-label="label"
|
|
option-value="value"
|
|
class="w-full"
|
|
/>
|
|
|
|
<div v-if="selectedCategory" class="rounded-[28px] bg-violet-50/70 p-6">
|
|
<p class="text-xs font-semibold uppercase tracking-[0.25em] text-violet-500">{{ selectedCategory.groupName }}</p>
|
|
<h2 class="mt-2 font-[Cormorant_Garamond] text-4xl text-violet-800">{{ selectedCategory.name }}</h2>
|
|
<p class="mt-2 text-slate-600">{{ selectedCategory.description }}</p>
|
|
</div>
|
|
<div class="space-y-3 rounded-[28px] border border-violet-100 bg-white/70 p-5">
|
|
<label class="text-sm font-semibold text-slate-600">Neuen Namen hinzufuegen</label>
|
|
<input
|
|
v-model="nomineeName"
|
|
type="text"
|
|
class="w-full rounded-2xl border border-violet-200 bg-white px-4 py-3"
|
|
placeholder="z. B. Shiro Ch."
|
|
/>
|
|
<Button @click="addNominee">Nominierung hinzufuegen</Button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="space-y-4">
|
|
<h3 class="font-[Cormorant_Garamond] text-4xl text-violet-800">Dein Entwurf</h3>
|
|
<div
|
|
v-for="name in nominees"
|
|
:key="name"
|
|
class="flex items-center justify-between rounded-[26px] border border-violet-100 bg-white/85 px-5 py-5"
|
|
>
|
|
<div>
|
|
<p class="font-semibold text-slate-800">{{ name }}</p>
|
|
<p class="text-sm text-slate-500">@{{ name.toLowerCase().replace(/\s+/g, '') }}</p>
|
|
</div>
|
|
<button class="text-sm font-semibold text-rose-500" @click="removeNominee(name)">Entfernen</button>
|
|
</div>
|
|
<p class="rounded-[26px] border border-dashed border-violet-200 bg-violet-50/60 px-5 py-5 text-sm text-slate-600">
|
|
Live-Status: {{ nominees.length }}/3 Slots belegt.
|
|
</p>
|
|
|
|
<p v-if="submitMessage" class="rounded-[26px] border border-emerald-200 bg-emerald-50 px-5 py-4 text-sm text-emerald-700">
|
|
{{ submitMessage }}
|
|
</p>
|
|
<p v-if="submitError" class="rounded-[26px] border border-rose-200 bg-rose-50 px-5 py-4 text-sm text-rose-700">
|
|
{{ submitError }}
|
|
</p>
|
|
<Button :disabled="submitting || !authStore.isLoggedIn || !selectedCategoryId || nominees.length === 0" @click="submitNomination">
|
|
{{ submitting ? 'Speichert ...' : 'Nominierung speichern' }}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
</div>
|
|
</template>
|