Fix registration email and add no character friendly messages

This commit is contained in:
Clément Pons 2025-10-15 14:58:59 +02:00
parent 443612cc58
commit 72843f2425
8 changed files with 81 additions and 59 deletions

Binary file not shown.

Binary file not shown.

View File

@ -35,46 +35,59 @@ async function duplicateCharacter(id: number)
<div v-if="status === 'pending'" class="flex flex-1 justify-center align-center"> <div v-if="status === 'pending'" class="flex flex-1 justify-center align-center">
<Loading size="large" /> <Loading size="large" />
</div> </div>
<div v-else-if="status === 'success'" class="grid p-6 2xl:grid-cols-3 lg:grid-cols-2 grid-cols-1 gap-4 w-full"> <template v-else-if="status === 'success'">
<div class="flex flex-col w-[360px] border border-light-35 dark:border-dark-35" v-for="character of characters"> <div v-if="characters && characters.length > 0" class="grid p-6 2xl:grid-cols-3 lg:grid-cols-2 grid-cols-1 gap-4 w-full">
<NuxtLink :to="{ name: 'character-id', params: { id: character.id } }" class="group bg-light-10 dark:bg-dark-10 border-b border-light-35 dark:border-dark-35 p-2 flex flex-col gap-2"> <div class="flex flex-col w-[360px] border border-light-35 dark:border-dark-35" v-for="character of characters">
<div class="flex flex-row gap-8 ps-4 items-center"> <NuxtLink :to="{ name: 'character-id', params: { id: character.id } }" class="group bg-light-10 dark:bg-dark-10 border-b border-light-35 dark:border-dark-35 p-2 flex flex-col gap-2">
<div class="flex flex-1 flex-col gap-2 justify-center"> <div class="flex flex-row gap-8 ps-4 items-center">
<span class="text-lg font-bold group-hover:text-accent-blue">{{ character.name }}</span> <div class="flex flex-1 flex-col gap-2 justify-center">
<span class="border-b w-full border-light-50 dark:border-dark-50"></span> <span class="text-lg font-bold group-hover:text-accent-blue">{{ character.name }}</span>
<div class="flex flex-row flex-1 items-stretch gap-4"> <span class="border-b w-full border-light-50 dark:border-dark-50"></span>
<span class="text-sm">Niveau {{ character.level }}</span> <div class="flex flex-row flex-1 items-stretch gap-4">
<span class="w-px h-full bg-light-50 dark:bg-dark-50"></span> <span class="text-sm">Niveau {{ character.level }}</span>
<span class="text-sm italic">{{ config.peoples[character.people!]?.name }}</span> <span class="w-px h-full bg-light-50 dark:bg-dark-50"></span>
</div> <span class="text-sm italic">{{ config.peoples[character.people!]?.name }}</span>
</div>
<div class="rounded-full w-[96px] h-[96px] border border-light-50 dark:border-dark-50 bg-light-100 dark:bg-dark-100 !bg-opacity-10"></div>
</div>
</NuxtLink>
<div class="flex justify-around items-center py-2 px-4 gap-4">
<NuxtLink :to="{ name: 'character-id-edit', params: { id: character.id } }" class="text-sm font-bold cursor-pointer hover:text-accent-blue">Editer</NuxtLink>
<span class="w-px h-full bg-light-50 dark:bg-dark-50"></span>
<NuxtLink @click="duplicateCharacter(character.id)" class="text-sm font-bold cursor-pointer hover:text-accent-blue">Dupliquer</NuxtLink>
<span class="w-px h-full bg-light-50 dark:bg-dark-50"></span>
<AlertDialogRoot>
<AlertDialogTrigger>
<span class="text-sm font-bold text-light-red dark:text-dark-red">Supprimer</span>
</AlertDialogTrigger>
<AlertDialogPortal>
<AlertDialogOverlay class="bg-light-0 dark:bg-dark-0 opacity-70 fixed inset-0 z-40" />
<AlertDialogContent
class="data-[state=open]:animate-contentShow fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[800px] translate-x-[-50%] translate-y-[-50%] bg-light-10 dark:bg-dark-10 border border-light-30 dark:border-dark-30 p-6 z-50 text-light-100 dark:text-dark-100">
<AlertDialogTitle class="text-3xl font-light relative -top-2">Supprimer {{ character.name }} ?</AlertDialogTitle>
<div class="flex flex-1 justify-end gap-4">
<AlertDialogCancel asChild><Button>Non</Button></AlertDialogCancel>
<AlertDialogAction asChild><Button @click="() => deleteCharacter(character.id)" class="border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red hover:bg-light-redBack dark:hover:bg-dark-redBack text-light-red dark:text-dark-red focus:shadow-light-red dark:focus:shadow-dark-red">Oui</Button></AlertDialogAction>
</div> </div>
</AlertDialogContent> </div>
</AlertDialogPortal> <div class="rounded-full w-[96px] h-[96px] border border-light-50 dark:border-dark-50 bg-light-100 dark:bg-dark-100 !bg-opacity-10"></div>
</AlertDialogRoot> </div>
</NuxtLink>
<div class="flex justify-around items-center py-2 px-4 gap-4">
<NuxtLink :to="{ name: 'character-id-edit', params: { id: character.id } }" class="text-sm font-bold cursor-pointer hover:text-accent-blue">Editer</NuxtLink>
<span class="w-px h-full bg-light-50 dark:bg-dark-50"></span>
<NuxtLink @click="duplicateCharacter(character.id)" class="text-sm font-bold cursor-pointer hover:text-accent-blue">Dupliquer</NuxtLink>
<span class="w-px h-full bg-light-50 dark:bg-dark-50"></span>
<AlertDialogRoot>
<AlertDialogTrigger>
<span class="text-sm font-bold text-light-red dark:text-dark-red">Supprimer</span>
</AlertDialogTrigger>
<AlertDialogPortal>
<AlertDialogOverlay class="bg-light-0 dark:bg-dark-0 opacity-70 fixed inset-0 z-40" />
<AlertDialogContent
class="data-[state=open]:animate-contentShow fixed top-[50%] left-[50%] max-h-[85vh] w-[90vw] max-w-[800px] translate-x-[-50%] translate-y-[-50%] bg-light-10 dark:bg-dark-10 border border-light-30 dark:border-dark-30 p-6 z-50 text-light-100 dark:text-dark-100">
<AlertDialogTitle class="text-3xl font-light relative -top-2">Supprimer {{ character.name }} ?</AlertDialogTitle>
<div class="flex flex-1 justify-end gap-4">
<AlertDialogCancel asChild><Button>Non</Button></AlertDialogCancel>
<AlertDialogAction asChild><Button @click="() => deleteCharacter(character.id)" class="border-light-red dark:border-dark-red hover:border-light-red dark:hover:border-dark-red hover:bg-light-redBack dark:hover:bg-dark-redBack text-light-red dark:text-dark-red focus:shadow-light-red dark:focus:shadow-dark-red">Oui</Button></AlertDialogAction>
</div>
</AlertDialogContent>
</AlertDialogPortal>
</AlertDialogRoot>
</div>
</div> </div>
</div> </div>
</div> <div v-else class="flex flex-col gap-2 items-center flex-1">
<span class="text-lg font-bold">Vous n'avez pas encore de personnage</span>
<NuxtLink class="inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
hover:bg-light-25 dark:hover:bg-dark-25 hover:border-light-50 dark:hover:border-dark-50
focus:bg-light-30 dark:focus:bg-dark-30 focus:border-light-50 dark:focus:border-dark-50 focus:shadow-raw focus:shadow-light-50 dark:focus:shadow-dark-50 py-2 px-4" :to="{ name: 'character-id-edit', params: { id: 'new' } }">Nouveau personnage</NuxtLink>
<NuxtLink class="inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
hover:bg-light-25 dark:hover:bg-dark-25 hover:border-light-50 dark:hover:border-dark-50
focus:bg-light-30 dark:focus:bg-dark-30 focus:border-light-50 dark:focus:border-dark-50 focus:shadow-raw focus:shadow-light-50 dark:focus:shadow-dark-50 py-2 px-4" :to="{ name: 'character-list' }">Qu'ont fait les autres ?</NuxtLink>
</div>
</template>
<div v-else> <div v-else>
<span>Erreur de chargement</span> <span>Erreur de chargement</span>
<span>{{ error?.message }}</span> <span>{{ error?.message }}</span>

View File

@ -13,24 +13,34 @@ const config = characterConfig as CharacterConfig;
<div v-if="status === 'pending'" class="flex flex-1 justify-center align-center"> <div v-if="status === 'pending'" class="flex flex-1 justify-center align-center">
<Loading size="large" /> <Loading size="large" />
</div> </div>
<div v-else-if="status === 'success'" class="grid p-6 2xl:grid-cols-3 lg:grid-cols-2 grid-cols-1 gap-4 w-full"> <template v-else-if="status === 'success'">
<div class="flex flex-col w-[360px] border border-light-35 dark:border-dark-35" v-for="character of characters"> <div v-if="characters && characters.length > 0" class="grid p-6 2xl:grid-cols-3 lg:grid-cols-2 grid-cols-1 gap-4 w-full">
<NuxtLink :to="{ name: 'character-id', params: { id: character.id } }" class="group bg-light-10 dark:bg-dark-10 p-2 flex flex-col gap-2"> <div class="flex flex-col w-[360px] border border-light-35 dark:border-dark-35" v-for="character of characters">
<div class="flex flex-row gap-8 ps-4 items-center"> <NuxtLink :to="{ name: 'character-id', params: { id: character.id } }" class="group bg-light-10 dark:bg-dark-10 p-2 flex flex-col gap-2">
<div class="flex flex-1 flex-col gap-2 justify-center"> <div class="flex flex-row gap-8 ps-4 items-center">
<span class="text-lg font-bold group-hover:text-accent-blue">{{ character.name }}</span> <div class="flex flex-1 flex-col gap-2 justify-center">
<span class="border-b w-full border-light-50 dark:border-dark-50"></span> <span class="text-lg font-bold group-hover:text-accent-blue">{{ character.name }}</span>
<div class="flex flex-row flex-1 items-stretch gap-4"> <span class="border-b w-full border-light-50 dark:border-dark-50"></span>
<span class="text-sm">Niveau {{ character.level }}</span> <div class="flex flex-row flex-1 items-stretch gap-4">
<span class="w-px h-full bg-light-50 dark:bg-dark-50"></span> <span class="text-sm">Niveau {{ character.level }}</span>
<span class="text-sm italic">{{ config.peoples[character.people!]?.name }}</span> <span class="w-px h-full bg-light-50 dark:bg-dark-50"></span>
<span class="text-sm italic">{{ config.peoples[character.people!]?.name }}</span>
</div>
</div> </div>
<div class="rounded-full w-[96px] h-[96px] border border-light-50 dark:border-dark-50 bg-light-100 dark:bg-dark-100 !bg-opacity-10"></div>
</div> </div>
<div class="rounded-full w-[96px] h-[96px] border border-light-50 dark:border-dark-50 bg-light-100 dark:bg-dark-100 !bg-opacity-10"></div> </NuxtLink>
</div> </div>
</NuxtLink>
</div> </div>
</div> <div v-else class="flex flex-col gap-2 items-center flex-1">
<span class="text-lg font-bold">Il n'existe pas encore de personnage public</span>
Soyez le premier à partager vos créations !
<NuxtLink class="inline-flex justify-center items-center outline-none leading-none transition-[box-shadow]
text-light-100 dark:text-dark-100 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40
hover:bg-light-25 dark:hover:bg-dark-25 hover:border-light-50 dark:hover:border-dark-50
focus:bg-light-30 dark:focus:bg-dark-30 focus:border-light-50 dark:focus:border-dark-50 focus:shadow-raw focus:shadow-light-50 dark:focus:shadow-dark-50 py-2 px-4" :to="{ name: 'character-id-edit', params: { id: 'new' } }">Nouveau personnage</NuxtLink>
</div>
</template>
<div v-else> <div v-else>
<span>Erreur de chargement</span> <span>Erreur de chargement</span>
<span>{{ error?.message }}</span> <span>{{ error?.message }}</span>

View File

@ -5,7 +5,6 @@ import { usersDataTable, usersTable } from '~/db/schema';
import { schema } from '~/schemas/registration'; import { schema } from '~/schemas/registration';
import { checkSession, logSession } from '~/server/utils/user'; import { checkSession, logSession } from '~/server/utils/user';
import type { UserSession, UserSessionRequired } from '~/types/auth'; import type { UserSession, UserSessionRequired } from '~/types/auth';
import sendMail from '~/server/tasks/mail';
import type { $ZodIssue } from 'zod/v4/core'; import type { $ZodIssue } from 'zod/v4/core';
interface SuccessHandler interface SuccessHandler
@ -84,7 +83,7 @@ export default defineEventHandler(async (e): Promise<Return> => {
id: emailId, timestamp, id: emailId, timestamp,
} }
}); });
await sendMail({ await runTask('mail', {
payload: { payload: {
type: 'mail', type: 'mail',
to: [body.data.email], to: [body.data.email],

View File

@ -16,9 +16,9 @@ export default defineEventHandler(async (e) => {
let where: ((character: typeof characterTable._.config.columns, sql: Operators) => SQL | undefined) | undefined = undefined; let where: ((character: typeof characterTable._.config.columns, sql: Operators) => SQL | undefined) | undefined = undefined;
const db = useDatabase(); const db = useDatabase();
const session = await getUserSession(e);
if(visibility === "own") if(visibility === "own")
{ {
const session = await getUserSession(e);
if(!session.user) if(!session.user)
{ {
setResponseStatus(e, 401); setResponseStatus(e, 401);
@ -33,7 +33,6 @@ export default defineEventHandler(async (e) => {
} }
else if(visibility === 'admin') else if(visibility === 'admin')
{ {
const session = await getUserSession(e);
if(!session.user) if(!session.user)
{ {
setResponseStatus(e, 401); setResponseStatus(e, 401);
@ -73,7 +72,7 @@ export default defineEventHandler(async (e) => {
people: character.people, people: character.people,
level: character.level, level: character.level,
aspect: character.aspect, aspect: character.aspect,
notes: character.notes, notes: { public: character.public_notes, private: session.user?.id === character.owner ? character.private_notes : undefined },
variables: character.variables, variables: character.variables,
training: character.training.reduce((p, v) => { p[v.stat] ??= {}; p[v.stat][v.level as TrainingLevel] = v.choice; return p; }, {} as Record<MainStat, Partial<Record<TrainingLevel, number>>>), training: character.training.reduce((p, v) => { p[v.stat] ??= {}; p[v.stat][v.level as TrainingLevel] = v.choice; return p; }, {} as Record<MainStat, Partial<Record<TrainingLevel, number>>>),

View File

@ -31,7 +31,8 @@ export default defineEventHandler(async (e) => {
people: body.data.people!, people: body.data.people!,
level: body.data.level, level: body.data.level,
aspect: body.data.aspect, aspect: body.data.aspect,
notes: body.data.notes, public_notes: body.data.notes.public,
private_notes: body.data.notes.private,
variables: body.data.variables, variables: body.data.variables,
visibility: body.data.visibility, visibility: body.data.visibility,
thumbnail: body.data.thumbnail, thumbnail: body.data.thumbnail,

View File

@ -43,7 +43,7 @@ export default defineEventHandler(async (e) => {
people: character.people, people: character.people,
level: character.level, level: character.level,
aspect: character.aspect, aspect: character.aspect,
notes: { public: character.public_notes, private: character.private_notes }, notes: { public: character.public_notes, private: session.user?.id === character.owner ? character.private_notes : undefined },
variables: character.variables, variables: character.variables,
training: character.training.reduce((p, v) => { p[v.stat] ??= {}; p[v.stat][v.level as TrainingLevel] = v.choice; return p; }, {} as Record<MainStat, Partial<Record<TrainingLevel, number>>>), training: character.training.reduce((p, v) => { p[v.stat] ??= {}; p[v.stat][v.level as TrainingLevel] = v.choice; return p; }, {} as Record<MainStat, Partial<Record<TrainingLevel, number>>>),