Add eslint

This commit is contained in:
2025-08-20 23:40:00 +02:00
parent cc544322b2
commit 16956d12f4
31 changed files with 1214 additions and 913 deletions
+42 -38
View File
@@ -17,9 +17,7 @@ const { huntSlug } = app.$slugData;
const { name, id } = app.$slugData.questSlug!;
const titleStore = useTitleStore();
const { data, pending, refresh, error } = await useFetch(
`/api/admin/quest/${id}`
);
const { data, refresh } = await useFetch(`/api/admin/quest/${id}`);
useHead({
title: t('layouts.title', {
@@ -37,42 +35,48 @@ titleStore.title = data.value ? tKey(data.value, 'title').value : undefined;
</script>
<template>
<h1>quest admin</h1>
<VaButtonGroup>
<VaButton
icon="arrow_back"
:to="
$localePath(
`/hunt/${data ? tSluggy(data.hunt).value : sluggy(huntSlug!)}`
)
"
>Back to Hunt</VaButton
>
<VaButton icon="replay" color="success" @click="refresh">Refresh</VaButton>
</VaButtonGroup>
<div class="grid grid-cols-2 gap-2 p-2 md:flex md:flex-row">
<VaChip outline>In progress</VaChip>
<VaChip color="primary">Reviewable</VaChip>
<VaChip color="danger">Review or Score missing</VaChip>
<VaChip color="success">Reviewed</VaChip>
<div>
<h1>quest admin</h1>
<VaButtonGroup>
<VaButton
icon="arrow_back"
:to="
$localePath(
`/hunt/${data ? tSluggy(data.hunt).value : sluggy(huntSlug!)}`
)
"
>Back to Hunt</VaButton
>
<VaButton icon="replay" color="success" @click="refresh"
>Refresh</VaButton
>
</VaButtonGroup>
<div class="grid grid-cols-2 gap-2 p-2 md:flex md:flex-row">
<VaChip outline>In progress</VaChip>
<VaChip color="primary">Reviewable</VaChip>
<VaChip color="danger">Review or Score missing</VaChip>
<VaChip color="success">Reviewed</VaChip>
</div>
<div v-if="data">
<p>{{ data.points }} Points</p>
<p v-if="data.extraPoints">{{ data.extraPoints }} Extra points</p>
<p v-if="hasKey(data, 'extraText')">
Extra: {{ tKey(data, 'extraText') }}
</p>
</div>
<div v-if="data && data.answers.length > 0">
<VaAccordion multiple stateful>
<AdminAnswer
v-for="answer in data.answers"
:key="answer.id"
:answer="answer"
:quest="data"
@update="refresh"
/>
</VaAccordion>
</div>
<p v-else>No answers yet</p>
</div>
<div v-if="data">
<p>{{ data.points }} Points</p>
<p v-if="data.extraPoints">{{ data.extraPoints }} Extra points</p>
<p v-if="hasKey(data, 'extraText')">Extra: {{ tKey(data, 'extraText') }}</p>
</div>
<div v-if="data && data.answers.length > 0">
<VaAccordion multiple stateful>
<AdminAnswer
v-for="answer in data.answers"
:key="answer.id"
:answer="answer"
:quest="data"
@update="refresh"
/>
</VaAccordion>
</div>
<p v-else>No answers yet</p>
</template>
<style scoped></style>
+177 -175
View File
@@ -105,187 +105,189 @@ const createImageURL = (file: File) => URL.createObjectURL(file);
</script>
<template>
<VaButtonGroup>
<VaButton
icon="arrow_back"
:to="
$localePath(
`/hunt/${data ? tSluggy(data.hunt).value : sluggy(huntSlug!)}`
)
"
>Back to Hunt</VaButton
>
<VaButton
@click="
submitError = '';
refresh();
"
icon="refresh"
:loading="pending"
color="success"
>{{ t('layouts.refresh') }}</VaButton
>
<VaButton
v-if="user && user.role === 'ADMIN'"
color="danger"
:to="
$localePath(
`/hunt/${data?.hunt ? tSluggy(data.hunt).value : sluggy(huntSlug!)}/${data ? tSluggy(data).value : sluggy(questSlug!)}}/admin`
)
"
>Admin</VaButton
></VaButtonGroup
>
<div v-if="data">
<div class="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<VaCard
:stripe="data.answer !== null"
:stripe-color="
data.answer?.review?.score && data.answer?.review?.score > 0
? 'success'
: 'primary'
<div>
<VaButtonGroup>
<VaButton
icon="arrow_back"
:to="
$localePath(
`/hunt/${data ? tSluggy(data.hunt).value : sluggy(huntSlug!)}`
)
"
>Back to Hunt</VaButton
>
<VaCardTitle><QuestTags :quest="data" /></VaCardTitle>
<ClickableImage
v-if="data?.picture"
:src="data?.picture?.url"
:title="(data ? tKey(data, 'title').value : questSlug?.name) || ''"
/>
<VaCardContent>
<div class="flex flex-col gap-4">
<p class="text-lg">{{ tKey(data, 'description') }}</p>
<p v-if="hasKey(data, 'question')" class="text-xl font-bold">
{{ data.textRequired ? '❓' : '❔' }}:
{{ tKey(data, 'question') }}
</p>
<p v-if="hasKey(data, 'question')">
🖋: {{ t(`enums.text_type.${data.textType}`) }}
</p>
<p v-if="hasKey(data, 'extraText')">
<span class="uppercase">Extra points:</span>
{{ tKey(data, 'extraText') }}
</p>
</div>
</VaCardContent>
</VaCard>
<VaCard color="info" v-if="data.answer?.review">
<VaCardTitle>Review</VaCardTitle>
<VaCardContent>
<p v-if="data.answer.review?.score" class="mb-2">
Score:
<VaChip color="success">{{ data.answer.review.score }}</VaChip>
<span v-if="data.answer.review.reviewer" class="text-sm">
by {{ data.answer.review.reviewer.name }} at
{{ d(data.answer.review.updatedAt, fullDate) }}</span
>
</p>
<div
class="flex max-h-[60dvh] flex-col gap-2 overflow-y-auto pr-4"
v-if="data.answer.review.review"
>
<p
v-for="(line, i) in data.answer.review.review.split('\n')"
:key="i"
class="italic"
>
{{ line }}
</p>
</div>
</VaCardContent>
</VaCard>
<VaCard
stripe
:stripe-color="data.answer?.final ? 'success' : 'primary'"
v-if="user && user.role !== 'ADMIN'"
<VaButton
icon="refresh"
:loading="pending"
color="success"
@click="
submitError = '';
refresh();
"
>{{ t('layouts.refresh') }}</VaButton
>
<VaCardTitle>Submission</VaCardTitle>
<VaCardContent>
<div class="flex flex-col gap-4">
<VaInput
:label="tKey(data, 'question').value"
:disabled="uploading || data.answer?.final"
v-if="hasKey(data, 'question')"
placeholder="Your answer"
:required-mark="data.textRequired"
v-model="answer"
:clearable="!!data.answer?.text"
:clear-value="data.answer?.text || ''"
clearable-icon="replay"
:type="textType[data.textType]"
:messages="[
t(`enums.text_type.${data.textType}`),
data.textRequired ? 'Required Answer' : ''
]"
>
<template v-if="data.textUnit" #appendInner>
<span>{{ data.textUnit }}</span>
</template>
</VaInput>
<VaImage
v-if="newImage"
:src="createImageURL(newImage)"
class="max-h-60 w-full"
fit="contain"
lazy
/>
<VaImage
v-else-if="data.answer?.picture"
:src="data.answer?.picture?.url"
class="max-h-60 w-full"
fit="contain"
lazy
>
<template #loader> <VaProgressCircle indeterminate /> </template
></VaImage>
<VaFileUpload
v-model="newImage"
:disabled="uploading || data.answer?.final"
type="single"
file-types="jpg,png,jpeg"
/>
<p v-if="data.answer?.updatedAt">
Last update:
{{ d(data.answer.updatedAt, fullDate) }}
by {{ data.answer.member.name }}
<VaButton
v-if="user && user.role === 'ADMIN'"
color="danger"
:to="
$localePath(
`/hunt/${data?.hunt ? tSluggy(data.hunt).value : sluggy(huntSlug!)}/${data ? tSluggy(data).value : sluggy(questSlug!)}}/admin`
)
"
>Admin</VaButton
></VaButtonGroup
>
<div v-if="data">
<div class="mt-4 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<VaCard
:stripe="data.answer !== null"
:stripe-color="
data.answer?.review?.score && data.answer?.review?.score > 0
? 'success'
: 'primary'
"
>
<VaCardTitle><QuestTags :quest="data" /></VaCardTitle>
<ClickableImage
v-if="data?.picture"
:src="data?.picture?.url"
:title="(data ? tKey(data, 'title').value : questSlug?.name) || ''"
/>
<VaCardContent>
<div class="flex flex-col gap-4">
<p class="text-lg">{{ tKey(data, 'description') }}</p>
<p v-if="hasKey(data, 'question')" class="text-xl font-bold">
{{ data.textRequired ? '❓' : '❔' }}:
{{ tKey(data, 'question') }}
</p>
<p v-if="hasKey(data, 'question')">
🖋: {{ t(`enums.text_type.${data.textType}`) }}
</p>
<p v-if="hasKey(data, 'extraText')">
<span class="uppercase">Extra points:</span>
{{ tKey(data, 'extraText') }}
</p>
</div>
</VaCardContent>
</VaCard>
<VaCard v-if="data.answer?.review" color="info">
<VaCardTitle>Review</VaCardTitle>
<VaCardContent>
<p v-if="data.answer.review?.score" class="mb-2">
Score:
<VaChip color="success">{{ data.answer.review.score }}</VaChip>
<span v-if="data.answer.review.reviewer" class="text-sm">
by {{ data.answer.review.reviewer.name }} at
{{ d(data.answer.review.updatedAt, fullDate) }}</span
>
</p>
<VaCheckbox
:disabled="data.answer?.final"
label="Finalize answer"
messages="This allows the admins to review your answer and give you points. You cannot change your answer afterwards!"
v-model="final"
/>
<div
v-if="data.answer.review.review"
class="flex max-h-[60dvh] flex-col gap-2 overflow-y-auto pr-4"
>
<p
v-for="(line, i) in data.answer.review.review.split('\n')"
:key="i"
class="italic"
>
{{ line }}
</p>
</div>
</VaCardContent>
</VaCard>
<VaCard
v-if="user && user.role !== 'ADMIN'"
stripe
:stripe-color="data.answer?.final ? 'success' : 'primary'"
>
<VaCardTitle>Submission</VaCardTitle>
<VaCardContent>
<div class="flex flex-col gap-4">
<VaInput
v-if="hasKey(data, 'question')"
v-model="answer"
:label="tKey(data, 'question').value"
:disabled="uploading || data.answer?.final"
placeholder="Your answer"
:required-mark="data.textRequired"
:clearable="!!data.answer?.text"
:clear-value="data.answer?.text || ''"
clearable-icon="replay"
:type="textType[data.textType]"
:messages="[
t(`enums.text_type.${data.textType}`),
data.textRequired ? 'Required Answer' : ''
]"
>
<template v-if="data.textUnit" #appendInner>
<span>{{ data.textUnit }}</span>
</template>
</VaInput>
<VaAlert
v-if="submitError.length > 0"
color="danger"
class="w-full text-center"
>{{ t(submitError) }}</VaAlert
>
<VaButton
v-if="!data.answer?.final"
:disabled="
!(
newImage ||
data.answer?.text !== answer ||
data.answer?.final !== final
)
"
:loading="uploading"
@click="submit"
>Update</VaButton
>
<p v-else class="font-bold">
Your final answer has already been submitted!
</p>
</div>
</VaCardContent>
</VaCard>
<VaImage
v-if="newImage"
:src="createImageURL(newImage)"
class="max-h-60 w-full"
fit="contain"
lazy
/>
<VaImage
v-else-if="data.answer?.picture"
:src="data.answer?.picture?.url"
class="max-h-60 w-full"
fit="contain"
lazy
>
<template #loader> <VaProgressCircle indeterminate /> </template
></VaImage>
<VaFileUpload
v-model="newImage"
:disabled="uploading || data.answer?.final"
type="single"
file-types="jpg,png,jpeg"
/>
<p v-if="data.answer?.updatedAt">
Last update:
{{ d(data.answer.updatedAt, fullDate) }}
by {{ data.answer.member.name }}
</p>
<VaCheckbox
v-model="final"
:disabled="data.answer?.final"
label="Finalize answer"
messages="This allows the admins to review your answer and give you points. You cannot change your answer afterwards!"
/>
<VaAlert
v-if="submitError.length > 0"
color="danger"
class="w-full text-center"
>{{ t(submitError) }}</VaAlert
>
<VaButton
v-if="!data.answer?.final"
:disabled="
!(
newImage ||
data.answer?.text !== answer ||
data.answer?.final !== final
)
"
:loading="uploading"
@click="submit"
>Update</VaButton
>
<p v-else class="font-bold">
Your final answer has already been submitted!
</p>
</div>
</VaCardContent>
</VaCard>
</div>
</div>
<div v-if="error" class="mt-8 text-3xl">
{{ t(parseError(error)) }}
</div>
</div>
<div v-if="error" class="mt-8 text-3xl">
{{ t(parseError(error)) }}
</div>
</template>
+149 -144
View File
@@ -21,16 +21,13 @@ const { init } = useToast();
const { name, id } = app.$slugData.huntSlug!;
const titleStore = useTitleStore();
const { data, pending, refresh, error } = await useFetch(
`/api/admin/hunt/${id}`,
{
transform: (value) => {
return superjson.deserialize(
value as unknown as SuperJSONResult
) as unknown as typeof value;
}
const { data, pending, refresh } = await useFetch(`/api/admin/hunt/${id}`, {
transform: (value) => {
return superjson.deserialize(
value as unknown as SuperJSONResult
) as unknown as typeof value;
}
);
});
const formData = useCloned(data, {
clone: (source) => superjson.deserialize(superjson.serialize(source))
@@ -158,148 +155,156 @@ async function addUser() {
</script>
<template>
<h1>admin</h1>
<VaForm v-if="formData.cloned.value && data" class="flex flex-col gap-4">
<VaInput
label="Name DE"
v-model="formData.cloned.value.name_de"
clearable
:clear-value="data.name_de"
clearable-icon="replay"
/>
<VaInput
label="Name EN"
v-model="formData.cloned.value.name_en"
clearable
:clear-value="data.name_en"
clearable-icon="replay"
/>
<VaTextarea
label="Description DE"
v-model="formData.cloned.value.description_de"
clearable
:clear-value="data.description_de"
clearable-icon="replay"
/>
<VaTextarea
label="Description EN"
v-model="formData.cloned.value.description_en"
clearable
:clear-value="data.description_en"
clearable-icon="replay"
/>
<VaCheckbox label="Virtual" v-model="formData.cloned.value.virtual" />
<VaCheckbox label="Allow Join" v-model="formData.cloned.value.allowJoin" />
<VaCheckbox
label="Reveal Quests"
v-model="formData.cloned.value.revealQuests"
/>
<VaCheckbox
label="Reveal Answers"
v-model="formData.cloned.value.revealAnswers"
/>
<div class="flex flex-row gap-2">
<div>
<h1>admin</h1>
<VaForm v-if="formData.cloned.value && data" class="flex flex-col gap-4">
<VaInput
v-model="formData.cloned.value.name_de"
label="Name DE"
clearable
:clear-value="data.name_de"
clearable-icon="replay"
/>
<VaInput
v-model="formData.cloned.value.name_en"
label="Name EN"
clearable
:clear-value="data.name_en"
clearable-icon="replay"
/>
<VaTextarea
v-model="formData.cloned.value.description_de"
label="Description DE"
clearable
:clear-value="data.description_de"
clearable-icon="replay"
/>
<VaTextarea
v-model="formData.cloned.value.description_en"
label="Description EN"
clearable
:clear-value="data.description_en"
clearable-icon="replay"
/>
<VaCheckbox v-model="formData.cloned.value.virtual" label="Virtual" />
<VaCheckbox
label="Start?"
:model-value="!!formData.cloned.value.start"
@update:model-value="
(newVal) =>
(formData.cloned.value!.start = newVal ? new Date() : null)
"
v-model="formData.cloned.value.allowJoin"
label="Allow Join"
/>
<VaDateInput
v-if="formData.cloned.value.start"
label="Start"
v-model="formData.cloned.value.start"
manual-input
/>
<VaTimeInput
v-if="formData.cloned.value.start"
label="Start"
v-model="formData.cloned.value.start"
view="seconds"
manual-input
/>
</div>
<div class="flex flex-row gap-2">
<VaCheckbox
label="End?"
:model-value="!!formData.cloned.value.end"
@update:model-value="
(newVal) => (formData.cloned.value!.end = newVal ? new Date() : null)
"
v-model="formData.cloned.value.revealQuests"
label="Reveal Quests"
/>
<VaDateInput
v-if="formData.cloned.value.end"
label="End"
v-model="formData.cloned.value.end"
manual-input
<VaCheckbox
v-model="formData.cloned.value.revealAnswers"
label="Reveal Answers"
/>
<VaTimeInput
v-if="formData.cloned.value.end"
label="End"
v-model="formData.cloned.value.end"
view="seconds"
manual-input
/>
</div>
<p>
Last update:
{{ d(data.updatedAt, fullDate) }}
</p>
<VaButtonGroup :loading="pending" :disabled="pending">
<VaButton
icon="replay"
:disabled="!formData.isModified.value"
@click.prevent="formData.sync()"
>Reset all values</VaButton
>
<VaButton :disabled="formData.isModified.value" @click.prevent="refresh()"
>Refresh</VaButton
>
<VaButton :disabled="!formData.isModified.value" @click.prevent="submit"
>Save</VaButton
>
</VaButtonGroup>
</VaForm>
<div class="flex flex-row gap-2">
<VaCheckbox
label="Start?"
:model-value="!!formData.cloned.value.start"
@update:model-value="
(newVal) =>
(formData.cloned.value!.start = newVal ? new Date() : null)
"
/>
<VaDateInput
v-if="formData.cloned.value.start"
v-model="formData.cloned.value.start"
label="Start"
manual-input
/>
<VaTimeInput
v-if="formData.cloned.value.start"
v-model="formData.cloned.value.start"
label="Start"
view="seconds"
manual-input
/>
</div>
<div class="flex flex-row gap-2">
<VaCheckbox
label="End?"
:model-value="!!formData.cloned.value.end"
@update:model-value="
(newVal) =>
(formData.cloned.value!.end = newVal ? new Date() : null)
"
/>
<VaDateInput
v-if="formData.cloned.value.end"
v-model="formData.cloned.value.end"
label="End"
manual-input
/>
<VaTimeInput
v-if="formData.cloned.value.end"
v-model="formData.cloned.value.end"
label="End"
view="seconds"
manual-input
/>
</div>
<p>
Last update:
{{ d(data.updatedAt, fullDate) }}
</p>
<VaButtonGroup :loading="pending" :disabled="pending">
<VaButton
icon="replay"
:disabled="!formData.isModified.value"
@click.prevent="formData.sync()"
>Reset all values</VaButton
>
<VaButton
:disabled="formData.isModified.value"
@click.prevent="refresh()"
>Refresh</VaButton
>
<VaButton :disabled="!formData.isModified.value" @click.prevent="submit"
>Save</VaButton
>
</VaButtonGroup>
</VaForm>
<VaDivider />
<h2 class="text-2xl">Hunt admin members:</h2>
<div
class="mt-8 grid grid-cols-1 gap-2 md:grid-cols-3 lg:grid-cols-5"
v-if="data"
>
<VaButton
color="danger"
v-for="member in data.members"
:key="member.member.id"
:disabled="loading"
:loading="loading"
@click="kick(member)"
>Kick {{ member.member.name }} ({{ member.member.email }})</VaButton
>
<VaChip v-if="data.members.length <= 0">No members yet</VaChip>
</div>
<div class="mt-8 flex flex-col gap-2 md:flex-row" v-if="data">
<VaSelect
label="New Admin Member"
placeholder="Start typing their email..."
autocomplete
highlight-matched-text
:options="users"
v-model="newUser"
v-model:search="search"
:loading="searchPending"
track-by="id"
text-by="email"
/><VaButton
icon="add"
color="danger"
:disabled="!newUser || loading"
:loading="loading"
@click="addUser"
>Add admin</VaButton
<VaDivider />
<h2 class="text-2xl">Hunt admin members:</h2>
<div
v-if="data"
class="mt-8 grid grid-cols-1 gap-2 md:grid-cols-3 lg:grid-cols-5"
>
<VaButton
v-for="member in data.members"
:key="member.member.id"
color="danger"
:disabled="loading"
:loading="loading"
@click="kick(member)"
>Kick {{ member.member.name }} ({{ member.member.email }})</VaButton
>
<VaChip v-if="data.members.length <= 0">No members yet</VaChip>
</div>
<div v-if="data" class="mt-8 flex flex-col gap-2 md:flex-row">
<VaSelect
v-model="newUser"
v-model:search="search"
label="New Admin Member"
placeholder="Start typing their email..."
autocomplete
highlight-matched-text
:options="users"
:loading="searchPending"
track-by="id"
text-by="email"
/><VaButton
icon="add"
color="danger"
:disabled="!newUser || loading"
:loading="loading"
@click="addUser"
>Add admin</VaButton
>
</div>
</div>
</template>
+2 -2
View File
@@ -71,7 +71,7 @@ titleStore.title = data.value?.name;
height="100dvh"
autoscroll
:indicators="false"
fallbackIcon="broken_image"
fallback-icon="broken_image"
infinite
color="secondary"
:items="data.pictures"
@@ -95,7 +95,7 @@ titleStore.title = data.value?.name;
<VaAlert color="danger" class="w-[90%]"
>Answers are not revealed yet, please wait until the end!</VaAlert
>
<VaAlert color="danger" class="w-[90%]" v-if="error.statusCode !== 403">{{
<VaAlert v-if="error.statusCode !== 403" color="danger" class="w-[90%]">{{
error
}}</VaAlert
><VaButton
+182 -176
View File
@@ -42,192 +42,198 @@ async function invite() {
</script>
<template>
<h1 class="text-2xl">
{{ data ? tKey(data, 'name') : name }}
<div>
<h1 class="text-2xl">
{{ data ? tKey(data, 'name') : name }}
<VaChip v-if="data?.totalScore" class="float-right" color="success"
>Total points: {{ data!!.totalScore }}</VaChip
>
</h1>
<div class="grid w-full grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-6">
<VaButton icon="replay" @click="refresh" :disabled="pending" title="Refresh"
>Refresh</VaButton
>
<VaButton
v-if="(data?.revealAnswers && data?.revealQuests) || data?.isMember"
color="success"
icon="leaderboard"
:to="$localePath(`/hunt/${tSluggy(data).value}/showcase`)"
>Showcase</VaButton
>
<VaButton
v-if="(data?.revealAnswers && data?.revealQuests) || data?.isMember"
color="success"
icon="collections"
:to="$localePath(`/hunt/${tSluggy(data).value}/diashow`)"
>Diashow</VaButton
>
<VaButton
v-if="(data?.revealAnswers && data?.revealQuests) || data?.isMember"
color="success"
icon="auto_awesome_mosaic"
:to="$localePath(`/hunt/${tSluggy(data).value}/pictures`)"
>All pictures</VaButton
>
<VaButton
v-if="data?.isMember"
color="danger"
icon="edit"
:to="$localePath(`/hunt/${tSluggy(data).value}/admin`)"
>Admin</VaButton
><VaButton
v-if="data?.isMember"
color="danger"
icon="groups"
:to="$localePath(`/hunt/${tSluggy(data).value}/teams`)"
>Teams</VaButton
>
</div>
<div v-if="data">
<div class="flex flex-col gap-2">
<p
v-for="(line, i) in tKey(data, 'description').value.split('\n')"
:key="i"
<VaChip v-if="data?.totalScore" class="float-right" color="success"
>Total points: {{ data!!.totalScore }}</VaChip
>
{{ line }}
</h1>
<div class="grid w-full grid-cols-2 gap-2 sm:grid-cols-3 md:grid-cols-6">
<VaButton
icon="replay"
:disabled="pending"
title="Refresh"
@click="refresh"
>Refresh</VaButton
>
<VaButton
v-if="(data?.revealAnswers && data?.revealQuests) || data?.isMember"
color="success"
icon="leaderboard"
:to="$localePath(`/hunt/${tSluggy(data).value}/showcase`)"
>Showcase</VaButton
>
<VaButton
v-if="(data?.revealAnswers && data?.revealQuests) || data?.isMember"
color="success"
icon="collections"
:to="$localePath(`/hunt/${tSluggy(data).value}/diashow`)"
>Diashow</VaButton
>
<VaButton
v-if="(data?.revealAnswers && data?.revealQuests) || data?.isMember"
color="success"
icon="auto_awesome_mosaic"
:to="$localePath(`/hunt/${tSluggy(data).value}/pictures`)"
>All pictures</VaButton
>
<VaButton
v-if="data?.isMember"
color="danger"
icon="edit"
:to="$localePath(`/hunt/${tSluggy(data).value}/admin`)"
>Admin</VaButton
><VaButton
v-if="data?.isMember"
color="danger"
icon="groups"
:to="$localePath(`/hunt/${tSluggy(data).value}/teams`)"
>Teams</VaButton
>
</div>
<div v-if="data">
<div class="flex flex-col gap-2">
<p
v-for="(line, i) in tKey(data, 'description').value.split('\n')"
:key="i"
>
{{ line }}
</p>
</div>
<ClickableImage
v-if="data?.picture"
:src="data?.picture?.url"
class="h-48"
:title="data ? tKey(data, 'name').value : name"
/>
<p v-if="data.start">
Start:
{{ d(data.start, fullDate) }}
</p>
</div>
<ClickableImage
v-if="data?.picture"
:src="data?.picture?.url"
class="h-48"
:title="data ? tKey(data, 'name').value : name"
/>
<p v-if="data.start">
Start:
{{ d(data.start, fullDate) }}
</p>
<p v-if="data.end">
End:
{{ d(data.end, fullDate) }}
</p>
<p>
{{ data._count.teams }} teams joined{{
data.ownTeam !== null ? ', just like you' : ', unlike you'
}}!
</p>
<p v-if="data.ownTeam">
Your team: <span class="font-bold">{{ data.ownTeam.name }}</span>
<VaButton size="small" @click="invite" class="ml-2"
>Invite:
<span
class="font-mono font-bold text-white blur transition hover:blur-none active:blur-none"
>
{{ data.ownTeam.password }}</span
></VaButton
>
</p>
<VaCard
v-if="data.ownTeam === null && !data.isMember"
color="primary"
class="mb-16 mt-8"
>
<VaCardTitle>Join this hunt!</VaCardTitle>
<VaCardContent class="text-2xl"
>To join this hunt you need to join or create a team:</VaCardContent
>
<VaCardActions v-if="data.loggedIn">
<TeamJoinModal @update="refresh" :hunt-id="data.id" />
<TeamAddModal @update="refresh" :hunt-id="data.id" />
</VaCardActions>
<VaCardActions v-else>
<VaButton
color="info"
icon="login"
:to="$localePath(`/login?to=/hunt/${tSluggy(data).value}`)"
>Login first</VaButton
>
</VaCardActions>
</VaCard>
<div class="flex flex-row gap-2" v-else-if="!data.isMember">
<VaSwitch v-model="hideAnswers" icon="image">Hide answers</VaSwitch>
</div>
<VaDivider />
<div
v-if="data.revealQuests || data.isMember"
class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4"
>
<VaCard
v-for="quest in data.quests"
:key="`q-${quest.id}`"
stripe
:stripe-color="
(quest.answer?.review?.score && quest.answer?.review?.score > 0) ||
quest.answer?.final
? 'success'
: !!quest.answer
? 'warning'
: 'primary'
"
>
<VaCardTitle><QuestTags :quest="quest" /></VaCardTitle>
<VaImage
v-if="!hideAnswers"
:src="quest?.answer?.picture?.url ?? ''"
fallback-text="No picture uploaded"
class="h-32"
fit="cover"
lazy
>
<template #loader> <VaProgressCircle indeterminate /> </template
></VaImage>
<VaCardContent>
<p class="line-clamp-2 h-12">{{ tKey(quest, 'description') }}</p>
<div v-if="!hideAnswers && quest.answer?.text">
<VaDivider />
<p class="line-clamp-1">{{ quest.answer?.text }}</p>
</div>
</VaCardContent>
<VaCardActions>
<VaButton
:to="
$localePath(
`/hunt/${tSluggy(data).value}/${tSluggy(quest).value}`
)
"
color="primary"
icon="rate_review"
class="flex-grow"
>Open</VaButton
<p v-if="data.end">
End:
{{ d(data.end, fullDate) }}
</p>
<p>
{{ data._count.teams }} teams joined{{
data.ownTeam !== null ? ', just like you' : ', unlike you'
}}!
</p>
<p v-if="data.ownTeam">
Your team: <span class="font-bold">{{ data.ownTeam.name }}</span>
<VaButton size="small" class="ml-2" @click="invite"
>Invite:
<span
class="font-mono font-bold text-white blur transition hover:blur-none active:blur-none"
>
{{ data.ownTeam.password }}</span
></VaButton
>
</p>
<VaCard
v-if="data.ownTeam === null && !data.isMember"
color="primary"
class="mb-16 mt-8"
>
<VaCardTitle>Join this hunt!</VaCardTitle>
<VaCardContent class="text-2xl"
>To join this hunt you need to join or create a team:</VaCardContent
>
<VaCardActions v-if="data.loggedIn">
<TeamJoinModal :hunt-id="data.id" @update="refresh" />
<TeamAddModal :hunt-id="data.id" @update="refresh" />
</VaCardActions>
<VaCardActions v-else>
<VaButton
v-if="data.isMember"
color="danger"
:icon="(quest.unreviewed || 0) > 0 ? 'priority_high' : undefined"
:to="
$localePath(
`/hunt/${tSluggy(data).value}/${tSluggy(quest).value}/admin`
)
"
>Admin ({{ quest.unreviewed || 0 }} unreviewed)</VaButton
color="info"
icon="login"
:to="$localePath(`/login?to=/hunt/${tSluggy(data).value}`)"
>Login first</VaButton
>
</VaCardActions>
</VaCard>
</div>
<VaCard v-else color="warning" class="mt-4"
><VaCardTitle>Quests not revealed yet!</VaCardTitle>
<VaCardContent
>Soon all {{ data._count.quests }} quests will be
revealed!</VaCardContent
<div v-else-if="!data.isMember" class="flex flex-row gap-2">
<VaSwitch v-model="hideAnswers" icon="image">Hide answers</VaSwitch>
</div>
<VaDivider />
<div
v-if="data.revealQuests || data.isMember"
class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-4"
>
</VaCard>
</div>
<VaCard
v-for="quest in data.quests"
:key="`q-${quest.id}`"
stripe
:stripe-color="
(quest.answer?.review?.score && quest.answer?.review?.score > 0) ||
quest.answer?.final
? 'success'
: !!quest.answer
? 'warning'
: 'primary'
"
>
<VaCardTitle><QuestTags :quest="quest" /></VaCardTitle>
<VaImage
v-if="!hideAnswers"
:src="quest?.answer?.picture?.url ?? ''"
fallback-text="No picture uploaded"
class="h-32"
fit="cover"
lazy
>
<template #loader> <VaProgressCircle indeterminate /> </template
></VaImage>
<VaCardContent>
<p class="line-clamp-2 h-12">{{ tKey(quest, 'description') }}</p>
<div v-if="error">
<h1 class="mt-8 text-3xl">{{ t(parseError(error)) }}</h1>
<VaButton icon="arrow_back" :to="$localePath(`/`)">Back home</VaButton>
<div v-if="!hideAnswers && quest.answer?.text">
<VaDivider />
<p class="line-clamp-1">{{ quest.answer?.text }}</p>
</div>
</VaCardContent>
<VaCardActions>
<VaButton
:to="
$localePath(
`/hunt/${tSluggy(data).value}/${tSluggy(quest).value}`
)
"
color="primary"
icon="rate_review"
class="flex-grow"
>Open</VaButton
>
<VaButton
v-if="data.isMember"
color="danger"
:icon="(quest.unreviewed || 0) > 0 ? 'priority_high' : undefined"
:to="
$localePath(
`/hunt/${tSluggy(data).value}/${tSluggy(quest).value}/admin`
)
"
>Admin ({{ quest.unreviewed || 0 }} unreviewed)</VaButton
>
</VaCardActions>
</VaCard>
</div>
<VaCard v-else color="warning" class="mt-4"
><VaCardTitle>Quests not revealed yet!</VaCardTitle>
<VaCardContent
>Soon all {{ data._count.quests }} quests will be
revealed!</VaCardContent
>
</VaCard>
</div>
<div v-if="error">
<h1 class="mt-8 text-3xl">{{ t(parseError(error)) }}</h1>
<VaButton icon="arrow_back" :to="$localePath(`/`)">Back home</VaButton>
</div>
</div>
</template>
+2 -2
View File
@@ -86,8 +86,8 @@ titleStore.title = data.value ? tKey(data.value, 'name').value : undefined;
/>
</div>
<VaAlert
color="warning"
v-if="data.count <= 0"
color="warning"
class="sm:col-span-2 md:col-span-3 lg:col-span-4"
>No pictures has been submitted yet :(</VaAlert
>
@@ -99,7 +99,7 @@ titleStore.title = data.value ? tKey(data.value, 'name').value : undefined;
<VaAlert color="danger" class="w-[90%]"
>Answers are not revealed yet, please wait until the end!</VaAlert
>
<VaAlert color="danger" class="w-[90%]" v-if="error.statusCode !== 403">{{
<VaAlert v-if="error.statusCode !== 403" color="danger" class="w-[90%]">{{
error
}}</VaAlert
><VaButton
+11 -10
View File
@@ -44,9 +44,10 @@ const columns = leaderboardColumns.map((c) =>
@click="toggle"
/>
</div>
<!-- eslint-disable-next-line vue/no-multiple-template-root -->
<article
class="h-dvh w-full snap-y snap-mandatory overflow-y-scroll scroll-smooth"
v-if="data"
class="h-dvh w-full snap-y snap-mandatory overflow-y-scroll scroll-smooth"
>
<section id="title">
<div
@@ -99,13 +100,13 @@ const columns = leaderboardColumns.map((c) =>
</section>
<section
v-for="quest in data.quests"
:key="quest.id"
: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 class="text-xl font-bold" v-if="hasKey(quest, 'question')">
<p v-if="hasKey(quest, 'question')" class="text-xl font-bold">
{{ tKey(quest, 'question') }}
</p>
<p v-if="quest.textSolution" class="text-3xl">
@@ -137,7 +138,7 @@ const columns = leaderboardColumns.map((c) =>
>
</div>
</section>
<section id="leaderboard" v-if="data.leaderboard.length > 3">
<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') }}
@@ -150,7 +151,7 @@ const columns = leaderboardColumns.map((c) =>
:items="data.leaderboard.slice(3)"
:columns="leaderboardColumns"
>
<template v-for="col in columns" #[col]
<template v-for="col in columns" #[col] :key="col"
><span>{{ t(`page.showcase.table.${col}`) }}</span></template
>
<template #cell(place)="{ rowIndex }">
@@ -159,7 +160,7 @@ const columns = leaderboardColumns.map((c) =>
</VaDataTable>
</div>
</section>
<section id="leaderboard-third" v-if="data.leaderboard[2]">
<section v-if="data.leaderboard[2]" id="leaderboard-third">
<div class="place bg-[#AD8A56]">
<div class="place-left">
<VaIcon name="looks_3" size="10rem" />
@@ -181,7 +182,7 @@ const columns = leaderboardColumns.map((c) =>
</div>
</div>
</section>
<section id="leaderboard-second" v-if="data.leaderboard[1]">
<section v-if="data.leaderboard[1]" id="leaderboard-second">
<div class="place bg-[#B4B4B4]">
<div class="place-left">
<VaIcon name="looks_two" size="10rem" />
@@ -203,7 +204,7 @@ const columns = leaderboardColumns.map((c) =>
</div>
</div>
</section>
<section id="leaderboard-winner-teaser" v-if="data.leaderboard[0]">
<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" />
@@ -227,7 +228,7 @@ const columns = leaderboardColumns.map((c) =>
</div>
</div>
</section>
<section id="leaderboard-winner" v-if="data.leaderboard[0]">
<section v-if="data.leaderboard[0]" id="leaderboard-winner">
<div class="place bg-[#C9B037]">
<div class="place-left">
<VaIcon name="workspace_premium" size="10rem" />
@@ -258,7 +259,7 @@ const columns = leaderboardColumns.map((c) =>
<VaAlert color="danger" class="w-[90%]"
>Answers are not revealed yet, please wait until the end!</VaAlert
>
<VaAlert color="danger" class="w-[90%]" v-if="error.statusCode !== 403">{{
<VaAlert v-if="error.statusCode !== 403" color="danger" class="w-[90%]">{{
error
}}</VaAlert
><VaButton
+14 -14
View File
@@ -16,9 +16,7 @@ const router = useRouter();
const { name, id } = app.$slugData.huntSlug!;
const titleStore = useTitleStore();
const { data, pending, refresh } = await useFetch(
`/api/admin/hunt/${id}/teams`
);
const { data, refresh } = await useFetch(`/api/admin/hunt/${id}/teams`);
useHead({
title: t('layouts.title', {
@@ -39,17 +37,19 @@ export type Team = Data['teams'][number];
</script>
<template>
<h1>teams</h1>
<VaButton color="success" icon="replay" @click="refresh">Refresh</VaButton>
<VaAccordion v-if="data" stateful>
<Team
v-for="team in data.teams"
:key="team.id"
:team="team"
:huntId="id"
@refresh="refresh"
/>
</VaAccordion>
<div>
<h1>teams</h1>
<VaButton color="success" icon="replay" @click="refresh">Refresh</VaButton>
<VaAccordion v-if="data" stateful>
<Team
v-for="team in data.teams"
:key="team.id"
:team="team"
:hunt-id="id"
@refresh="refresh"
/>
</VaAccordion>
</div>
</template>
<style scoped></style>
+105 -103
View File
@@ -10,112 +10,114 @@ watch(loggedIn, () => refresh());
</script>
<template>
<h1 class="text-primary text-3xl">{{ t('hello') }}</h1>
<VaButton icon="refresh" @click="refresh" :disabled="pending">{{
t('layouts.refresh')
}}</VaButton>
<div>
<h1 class="text-primary text-3xl">{{ t('hello') }}</h1>
<VaButton icon="refresh" :disabled="pending" @click="refresh">{{
t('layouts.refresh')
}}</VaButton>
<div v-if="data">
<div v-if="data.own !== null">
<h2 class="my-4 text-2xl font-bold">
{{ t('page.home.joined_hunts') }}
</h2>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<VaCard
v-for="hunt in data.own"
:key="hunt.id"
stripe
:to="$localePath(`/hunt/${tSluggy(hunt).value}`)"
>
<VaCardTitle>{{ tKey(hunt, 'name') }}</VaCardTitle>
<div v-if="data">
<div v-if="data.own !== null">
<h2 class="my-4 text-2xl font-bold">
{{ t('page.home.joined_hunts') }}
</h2>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<VaCard
v-for="hunt in data.own"
:key="hunt.id"
stripe
:to="$localePath(`/hunt/${tSluggy(hunt).value}`)"
>
<VaCardTitle>{{ tKey(hunt, 'name') }}</VaCardTitle>
<ClickableImage
v-if="hunt.picture"
:src="hunt.picture?.url"
:title="tKey(hunt, 'name').value"
/>
<VaCardContent>
<h3 class="text-xl">{{ tKey(hunt, 'name') }}</h3>
{{ tKey(hunt, 'description') }}
<VaDivider />
<div class="flex flex-col gap-2">
<p>{{ t('page.home.n_quests', hunt._count.quests) }}</p>
<p v-if="hunt.start">
{{
t('page.home.start', {
start: d(hunt.start, fullDate)
})
}}
</p>
<p v-if="hunt.end">
{{
t('page.home.end', {
start: d(hunt.end, fullDate)
})
}}
</p>
<p>{{ t('page.home.n_teams_joined', hunt._count.teams) }}</p>
</div>
</VaCardContent>
</VaCard>
<VaCard v-if="data.own.length <= 0">
<VaCardTitle>{{ t('page.home.no_hunt_joined') }}</VaCardTitle>
<VaCardContent>{{
t('page.home.click_hunt_below_to_join')
}}</VaCardContent>
</VaCard>
<ClickableImage
v-if="hunt.picture"
:src="hunt.picture?.url"
:title="tKey(hunt, 'name').value"
/>
<VaCardContent>
<h3 class="text-xl">{{ tKey(hunt, 'name') }}</h3>
{{ tKey(hunt, 'description') }}
<VaDivider />
<div class="flex flex-col gap-2">
<p>{{ t('page.home.n_quests', hunt._count.quests) }}</p>
<p v-if="hunt.start">
{{
t('page.home.start', {
start: d(hunt.start, fullDate)
})
}}
</p>
<p v-if="hunt.end">
{{
t('page.home.end', {
start: d(hunt.end, fullDate)
})
}}
</p>
<p>{{ t('page.home.n_teams_joined', hunt._count.teams) }}</p>
</div>
</VaCardContent>
</VaCard>
<VaCard v-if="data.own.length <= 0">
<VaCardTitle>{{ t('page.home.no_hunt_joined') }}</VaCardTitle>
<VaCardContent>{{
t('page.home.click_hunt_below_to_join')
}}</VaCardContent>
</VaCard>
</div>
</div>
</div>
<div v-else>
<VaButton color="info" icon="login" :to="$localePath(`/login`)">{{
t('page.home.login_first')
}}</VaButton>
</div>
<div class="mt-8">
<VaDivider />
<h2 class="my-4 text-2xl font-bold">
{{ t('page.home.open_to_join') }}
</h2>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<VaCard
v-for="hunt in data.others"
:key="hunt.id"
:to="$localePath(`/hunt/${tSluggy(hunt).value}`)"
>
<VaCardTitle>{{ tKey(hunt, 'name') }}</VaCardTitle>
<ClickableImage
v-if="hunt.picture"
:src="hunt.picture?.url"
:title="tKey(hunt, 'name').value"
/>
<VaCardContent>
<h3 class="text-xl">{{ tKey(hunt, 'name') }}</h3>
{{ tKey(hunt, 'description') }}
<VaDivider />
<div class="flex flex-col gap-2">
<p>{{ t('page.home.n_quests', hunt._count.quests) }}</p>
<p v-if="hunt.start">
{{
t('page.home.start', {
start: d(hunt.start, fullDate)
})
}}
</p>
<p v-if="hunt.end">
{{
t('page.home.end', {
start: d(hunt.end, fullDate)
})
}}
</p>
<p>{{ t('page.home.n_teams_joined', hunt._count.teams) }}</p>
</div>
</VaCardContent>
</VaCard>
<VaCard v-if="data.others.length <= 0">
<VaCardTitle>{{ t('page.home.no_hunt_created') }}</VaCardTitle>
<VaCardContent>{{ t('page.home.new_hunts_soon') }}</VaCardContent>
</VaCard>
<div v-else>
<VaButton color="info" icon="login" :to="$localePath(`/login`)">{{
t('page.home.login_first')
}}</VaButton>
</div>
<div class="mt-8">
<VaDivider />
<h2 class="my-4 text-2xl font-bold">
{{ t('page.home.open_to_join') }}
</h2>
<div class="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<VaCard
v-for="hunt in data.others"
:key="hunt.id"
:to="$localePath(`/hunt/${tSluggy(hunt).value}`)"
>
<VaCardTitle>{{ tKey(hunt, 'name') }}</VaCardTitle>
<ClickableImage
v-if="hunt.picture"
:src="hunt.picture?.url"
:title="tKey(hunt, 'name').value"
/>
<VaCardContent>
<h3 class="text-xl">{{ tKey(hunt, 'name') }}</h3>
{{ tKey(hunt, 'description') }}
<VaDivider />
<div class="flex flex-col gap-2">
<p>{{ t('page.home.n_quests', hunt._count.quests) }}</p>
<p v-if="hunt.start">
{{
t('page.home.start', {
start: d(hunt.start, fullDate)
})
}}
</p>
<p v-if="hunt.end">
{{
t('page.home.end', {
start: d(hunt.end, fullDate)
})
}}
</p>
<p>{{ t('page.home.n_teams_joined', hunt._count.teams) }}</p>
</div>
</VaCardContent>
</VaCard>
<VaCard v-if="data.others.length <= 0">
<VaCardTitle>{{ t('page.home.no_hunt_created') }}</VaCardTitle>
<VaCardContent>{{ t('page.home.new_hunts_soon') }}</VaCardContent>
</VaCard>
</div>
</div>
</div>
</div>
+75 -73
View File
@@ -53,88 +53,90 @@ async function submit() {
</script>
<template>
<h1 class="mb-4 text-center text-3xl">{{ t('auth.login') }}</h1>
<div class="grid grid-cols-1 sm:grid-cols-3 lg:grid-cols-5">
<VaForm
ref="formRef"
tag="form"
class="flex flex-col items-baseline gap-6 sm:col-start-2 lg:col-start-3"
@submit.prevent="submit"
>
<VaInput
v-model="form.email"
class="w-full"
type="email"
:label="t('auth.email')"
autocomplete="email"
:rules="[validation.required(t('auth.email'))]"
:placeholder="t('auth.email')"
required
<div>
<h1 class="mb-4 text-center text-3xl">{{ t('auth.login') }}</h1>
<div class="grid grid-cols-1 sm:grid-cols-3 lg:grid-cols-5">
<VaForm
ref="formRef"
tag="form"
class="flex flex-col items-baseline gap-6 sm:col-start-2 lg:col-start-3"
@submit.prevent="submit"
>
<template #prependInner>
<VaIcon name="mail_outline" color="secondary" />
</template>
</VaInput>
<VaValue v-slot="isPasswordVisible" :default-value="false">
<VaInput
v-model="form.password"
v-model="form.email"
class="w-full"
:type="isPasswordVisible.value ? 'text' : 'password'"
:label="t('auth.password')"
:placeholder="t('auth.password')"
:rules="[
validation.required(t('auth.password')),
validation.min(t('auth.password'), 8)
]"
autocomplete="current-password"
:minlength="8"
type="email"
:label="t('auth.email')"
autocomplete="email"
:rules="[validation.required(t('auth.email'))]"
:placeholder="t('auth.email')"
required
>
<template #prependInner>
<VaIcon name="key" color="secondary" />
</template>
<template #appendInner>
<VaButton
v-if="form.password"
preset="plain"
:title="
t(
isPasswordVisible.value
? 'auth.hidePassword'
: 'auth.showPassword'
)
"
@click="isPasswordVisible.value = !isPasswordVisible.value"
>
<VaIcon
:name="
isPasswordVisible.value ? 'visibility_off' : 'visibility'
"
color="primary"
/>
</VaButton>
<VaIcon name="mail_outline" color="secondary" />
</template>
</VaInput>
</VaValue>
<VaAlert
v-if="error.length > 0"
color="danger"
class="w-full text-center"
>{{ t(error) }}</VaAlert
>
<VaButtonGroup>
<VaButton color="success" type="submit" :disabled="!isValid">{{
t('auth.login')
}}</VaButton>
<VaButton
:to="$localePath({ path: '/register', query: route.query })"
border-color="primary"
preset="secondary"
>{{ t('auth.register_instead') }}</VaButton
<VaValue v-slot="isPasswordVisible" :default-value="false">
<VaInput
v-model="form.password"
class="w-full"
:type="isPasswordVisible.value ? 'text' : 'password'"
:label="t('auth.password')"
:placeholder="t('auth.password')"
:rules="[
validation.required(t('auth.password')),
validation.min(t('auth.password'), 8)
]"
autocomplete="current-password"
:minlength="8"
required
>
<template #prependInner>
<VaIcon name="key" color="secondary" />
</template>
<template #appendInner>
<VaButton
v-if="form.password"
preset="plain"
:title="
t(
isPasswordVisible.value
? 'auth.hidePassword'
: 'auth.showPassword'
)
"
@click="isPasswordVisible.value = !isPasswordVisible.value"
>
<VaIcon
:name="
isPasswordVisible.value ? 'visibility_off' : 'visibility'
"
color="primary"
/>
</VaButton>
</template>
</VaInput>
</VaValue>
<VaAlert
v-if="error.length > 0"
color="danger"
class="w-full text-center"
>{{ t(error) }}</VaAlert
>
</VaButtonGroup>
</VaForm>
<VaButtonGroup>
<VaButton color="success" type="submit" :disabled="!isValid">{{
t('auth.login')
}}</VaButton>
<VaButton
:to="$localePath({ path: '/register', query: route.query })"
border-color="primary"
preset="secondary"
>{{ t('auth.register_instead') }}</VaButton
>
</VaButtonGroup>
</VaForm>
</div>
</div>
</template>
+2 -2
View File
@@ -90,8 +90,8 @@ async function submitPassword() {
<VaCardContent v-if="data">
<div class="flex flex-col gap-4">
<VaInput
label="Name"
v-model="form.name"
label="Name"
clearable
:clear-value="data.name"
clearable-icon="replay"
@@ -106,9 +106,9 @@ async function submitPassword() {
]"
/>
<VaInput
v-model="form.email"
label="Email"
type="email"
v-model="form.email"
clearable
:clear-value="data.email"
clearable-icon="replay"
+99 -97
View File
@@ -55,118 +55,120 @@ async function submit() {
</script>
<template>
<h1 class="mb-4 text-center text-3xl">{{ t('auth.login') }}</h1>
<div class="grid grid-cols-1 sm:grid-cols-3 lg:grid-cols-5">
<VaForm
ref="formRef"
tag="form"
class="flex flex-col items-baseline gap-6 sm:col-start-2 lg:col-start-3"
@submit.prevent="submit"
>
<VaInput
v-model="form.name"
class="w-full"
type="text"
autocomplete="name"
clearable
clear-value=""
:label="t('auth.name')"
:placeholder="t('auth.name')"
max-length="64"
min-length="5"
:rules="[
validation.required(t('auth.name')),
validation.max(t('auth.name'), 64),
validation.min(t('auth.name'), 5)
]"
required
<div>
<h1 class="mb-4 text-center text-3xl">{{ t('auth.login') }}</h1>
<div class="grid grid-cols-1 sm:grid-cols-3 lg:grid-cols-5">
<VaForm
ref="formRef"
tag="form"
class="flex flex-col items-baseline gap-6 sm:col-start-2 lg:col-start-3"
@submit.prevent="submit"
>
<template #prependInner>
<VaIcon name="person" color="secondary" />
</template>
<template #appendInner>
<VaButton
icon="refresh"
preset="plain"
@click="form.name = getRandomName()"
/>
</template>
</VaInput>
<VaInput
v-model="form.email"
class="w-full"
type="email"
:label="t('auth.email')"
autocomplete="email"
:rules="[validation.required(t('auth.email'))]"
:placeholder="t('auth.email')"
required
>
<template #prependInner>
<VaIcon name="mail_outline" color="secondary" />
</template>
</VaInput>
<VaValue v-slot="isPasswordVisible" :default-value="false">
<VaInput
v-model="form.password"
v-model="form.name"
class="w-full"
:type="isPasswordVisible.value ? 'text' : 'password'"
:label="t('auth.password')"
:placeholder="t('auth.password')"
type="text"
autocomplete="name"
clearable
clear-value=""
:label="t('auth.name')"
:placeholder="t('auth.name')"
max-length="64"
min-length="5"
:rules="[
validation.required(t('auth.password')),
validation.min(t('auth.password'), 8)
validation.required(t('auth.name')),
validation.max(t('auth.name'), 64),
validation.min(t('auth.name'), 5)
]"
autocomplete="current-password"
:minlength="8"
required
>
<template #prependInner>
<VaIcon name="key" color="secondary" />
<VaIcon name="person" color="secondary" />
</template>
<template #appendInner>
<VaButton
v-if="form.password"
icon="refresh"
preset="plain"
:title="
t(
isPasswordVisible.value
? 'auth.hidePassword'
: 'auth.showPassword'
)
"
@click="isPasswordVisible.value = !isPasswordVisible.value"
>
<VaIcon
:name="
isPasswordVisible.value ? 'visibility_off' : 'visibility'
"
color="primary"
/>
</VaButton>
@click="form.name = getRandomName()"
/>
</template>
</VaInput>
</VaValue>
<VaAlert
v-if="error.length > 0"
color="danger"
class="w-full text-center"
>{{ t(error) }}</VaAlert
>
<VaButtonGroup>
<VaButton color="success" type="submit" :disabled="!isValid">{{
t('auth.register')
}}</VaButton>
<VaButton
:to="$localePath({ path: '/login', query: route.query })"
border-color="primary"
preset="secondary"
>{{ t('auth.login_instead') }}</VaButton
<VaInput
v-model="form.email"
class="w-full"
type="email"
:label="t('auth.email')"
autocomplete="email"
:rules="[validation.required(t('auth.email'))]"
:placeholder="t('auth.email')"
required
>
</VaButtonGroup>
</VaForm>
<template #prependInner>
<VaIcon name="mail_outline" color="secondary" />
</template>
</VaInput>
<VaValue v-slot="isPasswordVisible" :default-value="false">
<VaInput
v-model="form.password"
class="w-full"
:type="isPasswordVisible.value ? 'text' : 'password'"
:label="t('auth.password')"
:placeholder="t('auth.password')"
:rules="[
validation.required(t('auth.password')),
validation.min(t('auth.password'), 8)
]"
autocomplete="current-password"
:minlength="8"
required
>
<template #prependInner>
<VaIcon name="key" color="secondary" />
</template>
<template #appendInner>
<VaButton
v-if="form.password"
preset="plain"
:title="
t(
isPasswordVisible.value
? 'auth.hidePassword'
: 'auth.showPassword'
)
"
@click="isPasswordVisible.value = !isPasswordVisible.value"
>
<VaIcon
:name="
isPasswordVisible.value ? 'visibility_off' : 'visibility'
"
color="primary"
/>
</VaButton>
</template>
</VaInput>
</VaValue>
<VaAlert
v-if="error.length > 0"
color="danger"
class="w-full text-center"
>{{ t(error) }}</VaAlert
>
<VaButtonGroup>
<VaButton color="success" type="submit" :disabled="!isValid">{{
t('auth.register')
}}</VaButton>
<VaButton
:to="$localePath({ path: '/login', query: route.query })"
border-color="primary"
preset="secondary"
>{{ t('auth.login_instead') }}</VaButton
>
</VaButtonGroup>
</VaForm>
</div>
</div>
</template>