update
This commit is contained in:
@@ -1,13 +1,51 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { computed, reactive, ref } from 'vue'
|
||||
import FilmSuche from './FilmSuche.vue'
|
||||
import { posterUrl } from '../images'
|
||||
|
||||
defineProps({
|
||||
const props = defineProps({
|
||||
items: { type: Array, default: () => [] },
|
||||
// Der Host pflegt die ganze Runde, alle anderen nur den eigenen Zugang
|
||||
host: Boolean,
|
||||
ich: { type: Object, default: null },
|
||||
})
|
||||
|
||||
const liste = computed(() =>
|
||||
props.host ? props.items : props.items.filter((u) => u.id === props.ich?.id)
|
||||
)
|
||||
|
||||
const neuerName = ref('')
|
||||
const fehler = ref('')
|
||||
|
||||
// Der Anmeldefilm ist das Passwort: wer den Namen wählt, muss ihn in der
|
||||
// Suche finden. Welcher es ist, gibt der Server nicht heraus — hier steht
|
||||
// nur, ob überhaupt einer gesetzt ist.
|
||||
const setzt = ref(null)
|
||||
|
||||
async function schutzSetzen(user, movieId) {
|
||||
await fetch(`/api/users/${user.id}/schutz`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ movie_id: movieId }),
|
||||
})
|
||||
delete gezeigt[user.id]
|
||||
setzt.value = null
|
||||
}
|
||||
|
||||
// Wer den Film ändern darf, darf ihn auch nachschlagen — sonst weiß man
|
||||
// nicht, was man gerade überschreibt. Sichtbar wird er erst auf Klick.
|
||||
const gezeigt = reactive({})
|
||||
|
||||
async function schutzZeigen(user) {
|
||||
if (gezeigt[user.id]) {
|
||||
delete gezeigt[user.id]
|
||||
return
|
||||
}
|
||||
gezeigt[user.id] = { laden: true }
|
||||
const resp = await fetch(`/api/users/${user.id}/schutz`)
|
||||
gezeigt[user.id] = resp.ok ? await resp.json() : { fehler: true }
|
||||
}
|
||||
|
||||
async function anlegen() {
|
||||
fehler.value = ''
|
||||
const resp = await fetch('/api/users', {
|
||||
@@ -27,9 +65,14 @@ async function entfernen(user) {
|
||||
<template>
|
||||
<div class="verwaltung-tab">
|
||||
<section>
|
||||
<h3>Benutzer</h3>
|
||||
<h3>{{ host ? 'Benutzer' : 'Dein Zugang' }}</h3>
|
||||
|
||||
<div class="anlegen">
|
||||
<p v-if="!host" class="erklaerung">
|
||||
Hinterleg einen Film, dann fragt die Seite beim Wechsel auf deinen
|
||||
Namen danach. Ohne Film bleibt dein Name für alle offen.
|
||||
</p>
|
||||
|
||||
<div v-if="host" class="anlegen">
|
||||
<input
|
||||
v-model="neuerName"
|
||||
type="text"
|
||||
@@ -41,27 +84,101 @@ async function entfernen(user) {
|
||||
</div>
|
||||
<p v-if="fehler" class="fehler">{{ fehler }}</p>
|
||||
|
||||
<ul v-if="items.length" class="liste">
|
||||
<li v-for="u in items" :key="u.id">
|
||||
<span class="name">{{ u.name }}</span>
|
||||
<button
|
||||
type="button"
|
||||
class="weg"
|
||||
title="Aus der Runde nehmen"
|
||||
@click="entfernen(u)"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
<ul v-if="liste.length" class="liste">
|
||||
<li v-for="u in liste" :key="u.id">
|
||||
<div class="zeile">
|
||||
<span class="name">{{ u.name }}</span>
|
||||
<button
|
||||
v-if="u.geschuetzt"
|
||||
type="button"
|
||||
class="auge"
|
||||
:class="{ an: gezeigt[u.id] }"
|
||||
:title="gezeigt[u.id] ? 'Film verbergen' : 'Hinterlegten Film zeigen'"
|
||||
@click="schutzZeigen(u)"
|
||||
>
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<path d="M1 12s4-7.5 11-7.5S23 12 23 12s-4 7.5-11 7.5S1 12 1 12z" />
|
||||
<circle cx="12" cy="12" r="3.2" />
|
||||
<line v-if="!gezeigt[u.id]" x1="3" y1="21" x2="21" y2="3" />
|
||||
</svg>
|
||||
</button>
|
||||
<button type="button" class="klein" @click="setzt = u">
|
||||
{{ u.geschuetzt ? 'Ändern' : 'Anmeldefilm' }}
|
||||
</button>
|
||||
<button
|
||||
v-if="u.geschuetzt"
|
||||
type="button"
|
||||
class="klein"
|
||||
title="Namen wieder für alle öffnen"
|
||||
@click="schutzSetzen(u, null)"
|
||||
>
|
||||
Aufheben
|
||||
</button>
|
||||
<button
|
||||
v-if="host"
|
||||
type="button"
|
||||
class="weg"
|
||||
title="Aus der Runde nehmen"
|
||||
@click="entfernen(u)"
|
||||
>
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<p v-if="gezeigt[u.id]" class="verraten">
|
||||
<template v-if="gezeigt[u.id].laden">Moment…</template>
|
||||
<template v-else-if="gezeigt[u.id].film">
|
||||
<img
|
||||
v-if="posterUrl(gezeigt[u.id].film.poster_path, 'w92')"
|
||||
:src="posterUrl(gezeigt[u.id].film.poster_path, 'w92')"
|
||||
:alt="gezeigt[u.id].film.title"
|
||||
/>
|
||||
<span>
|
||||
{{ gezeigt[u.id].film.title }}
|
||||
<span v-if="gezeigt[u.id].film.year" class="jahr">
|
||||
{{ gezeigt[u.id].film.year }}
|
||||
</span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>Der Film ließ sich nicht laden.</template>
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
<p v-else class="leer">Noch niemand in der Runde.</p>
|
||||
</section>
|
||||
|
||||
<Teleport to="body">
|
||||
<div v-if="setzt" class="hintergrund" @click.self="setzt = null">
|
||||
<div class="fenster" role="dialog" aria-modal="true">
|
||||
<p class="label">Anmeldefilm für</p>
|
||||
<h2>{{ setzt.name }}</h2>
|
||||
<p class="frage">
|
||||
{{
|
||||
setzt.id === ich?.id
|
||||
? 'Diesen Film musst du beim Wechsel auf deinen Namen anklicken.'
|
||||
: `Diesen Film muss ${setzt.name} beim Wechsel anklicken.`
|
||||
}}
|
||||
</p>
|
||||
|
||||
<FilmSuche
|
||||
platzhalter="Film als Passwort …"
|
||||
@waehlen="schutzSetzen(setzt, $event.id)"
|
||||
/>
|
||||
|
||||
<div class="fuss">
|
||||
<button type="button" class="abbrechen" @click="setzt = null">
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.verwaltung-tab {
|
||||
max-width: 26rem;
|
||||
max-width: 34rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@@ -107,14 +224,66 @@ h3 {
|
||||
|
||||
.liste li {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
padding: 0.55rem 0.85rem;
|
||||
background: var(--bg-soft);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.zeile {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Der hinterlegte Film, aufgeklappt */
|
||||
.verraten {
|
||||
display: flex;
|
||||
gap: 0.6rem;
|
||||
align-items: center;
|
||||
padding-top: 0.5rem;
|
||||
color: var(--text);
|
||||
font-size: 0.9rem;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.verraten img {
|
||||
width: 40px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.jahr {
|
||||
color: var(--muted);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.auge {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
width: 1.8rem;
|
||||
height: 1.8rem;
|
||||
color: var(--muted);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.auge svg {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.8;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.auge:hover,
|
||||
.auge.an {
|
||||
color: var(--text);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.name {
|
||||
flex: 1;
|
||||
font-size: 0.95rem;
|
||||
@@ -132,13 +301,117 @@ h3 {
|
||||
}
|
||||
|
||||
.leer,
|
||||
.fehler {
|
||||
.fehler,
|
||||
.erklaerung {
|
||||
color: var(--muted);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
|
||||
.erklaerung {
|
||||
margin-bottom: 1rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.fehler {
|
||||
margin-bottom: 0.8rem;
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.marke {
|
||||
padding: 0.1rem 0.4rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.68rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.klein {
|
||||
padding: 0.25rem 0.55rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.78rem;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.klein:hover {
|
||||
color: var(--text);
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
/* ── Fenster zum Setzen ──────────────────────────────────────────── */
|
||||
.hintergrund {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 50;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 1rem;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
}
|
||||
|
||||
.fenster {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.6rem;
|
||||
width: min(38rem, 100%);
|
||||
max-height: min(42rem, 90vh);
|
||||
padding: 1.5rem 1.75rem;
|
||||
background: var(--bg-soft);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: var(--muted);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.09em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.fenster h2 {
|
||||
font-size: 1.35rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.frage {
|
||||
color: var(--muted);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.fuss {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.abbrechen {
|
||||
padding: 0.55rem 1.1rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.88rem;
|
||||
font-weight: 600;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.abbrechen:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.hintergrund {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.fenster {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: none;
|
||||
padding: 1rem;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user