You've already forked obsidian-visualiser
First reworks
This commit is contained in:
@@ -1,9 +1,3 @@
|
||||
<template>
|
||||
<Head>
|
||||
<Title>Inconnu</Title>
|
||||
</Head>
|
||||
<div class="h-100 w-100 flex flex-1 flex-col justify-center items-center">
|
||||
<div class="text-3xl font-extralight tracking-wide text-light-60 dark:text-dark-60">Introuvable</div>
|
||||
<div class="text-lg text-light-60 dark:text-dark-60">Cette page n'existe pas</div>
|
||||
</div>
|
||||
Page inconnue.
|
||||
</template>
|
||||
3
pages/admin/index.vue
Normal file
3
pages/admin/index.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
Administration
|
||||
</template>
|
||||
@@ -1,223 +0,0 @@
|
||||
<style>
|
||||
.editor-container
|
||||
{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.editor
|
||||
{
|
||||
width: 45%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<template>
|
||||
<Head>
|
||||
<Title>Live Editing</Title>
|
||||
</Head>
|
||||
<div class="flex flex-1 flex-col justify-center items-center">
|
||||
<h1 class="block flex-1 text-3xl">En cours de développement</h1>
|
||||
<div class="flex flex-1">
|
||||
<Suspense>
|
||||
<template #fallback>
|
||||
<div class="loading"></div>
|
||||
</template>
|
||||
<EditableMarkdown class="editor-preview" v-if="input.length > 0" v-model="input"></EditableMarkdown>
|
||||
</Suspense>
|
||||
<textarea class="caret-light-50 dark:caret-dark-50 text-light-100 dark:text-dark-100 placeholder:text-light-50 dark:placeholder:text-dark-50 bg-light-20 dark:bg-dark-20 appearance-none w-1/2 outline-none px-3 py-1 focus:shadow-raw transition-[box-shadow] focus:shadow-light-40 dark:focus:shadow-dark-40 border border-light-35 dark:border-dark-35" v-model="input"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const input = ref(`## Liste de sorts provisoire
|
||||
%% Equilibrage: Les sorts de dégâts plus cher ne doivent pas forcément proposer plus de dés de dégâts mais offrir plus d'options et avoir des dé de dégâts plus haut, pour synergiser avec les buffs de l'arbre de magie. %%
|
||||
|
||||
**Notation:**
|
||||
- Nom #element (coût, durée d'incantation, portée, prérequis d'incantation)
|
||||
> Effet
|
||||
### Rang 1
|
||||
- Trait de feu #element/feu (4 mana, tour, 12 cases, V/Ge/Gl)
|
||||
>Tire un faisceau de flamme, infligeant 2d8 dégâts de [[Les types de dégâts#Feu|feu]].
|
||||
|
||||
- Echauffement #element/feu (2 mana, tour, V/Gl)
|
||||
>Chauffe à blanc une arme ou un projectile. Jusqu'au début de votre prochain tour, les coups portés avec l'objet infligent 1d6 dégâts supplémentaire. Les dégâts de l'arme deviennent des dégâts de [[Les types de dégâts#Feu|feu]].
|
||||
|
||||
- #element/feu (mana, tour)
|
||||
>
|
||||
|
||||
- Corps ardent #element/feu(6 mana, tour, V/Ge/Gl/C)
|
||||
>Pendant 5 tours, toute personne terminant son tour à une case de vous subit 1d10 dégâts de [[Les types de dégâts#Feu|feu]].
|
||||
|
||||
- #element/feu(mana, tour)
|
||||
>
|
||||
|
||||
- Protection supérieure #element/glace (2 mana, réaction, V/Gl)
|
||||
>L'armure subit l'intégralité des dégâts sur le prochain coup.
|
||||
|
||||
- Lames de glace #element/glace (4 mana, tour, 12 cases)
|
||||
>Tire 2 projectiles infligeant 1d8 dégâts de [[Les types de dégâts#Glace|glace]]. Augmenter les dés de dégâts offre un projectile supplémentaire à la place. Chaque projectile demande un jet d'attaque séparé et peut viser une cible différente.
|
||||
|
||||
- #element/glace(mana, tour)
|
||||
>
|
||||
|
||||
- #element/glace(mana, tour)
|
||||
>
|
||||
|
||||
- #element/glace(mana, tour)
|
||||
>
|
||||
|
||||
- Chaine de foudre #element/foudre (4 mana, tour, 12 cases)
|
||||
>Frappe une cible visible puis rebondit sur jusqu'à 2 autres cibles à 1 case de la première. Inflige 1d8 dégâts de [[Les types de dégâts#Foudre|foudre]].
|
||||
|
||||
- Vitesse lumière #element/foudre (3 mana, tour)
|
||||
>Se téléporte à 6 cases tant que vous pouvez voir et courir vers la destination.
|
||||
|
||||
- Décharge de foudre #element/foudre(3 mana, tour)
|
||||
>Tire une décharge foudroyante d'énergie, infligeant 4d4[[Glossaire#Jet explosif|!]] dégâts de [[Les types de dégâts#Foudre|foudre]].
|
||||
|
||||
- Faisceau fulgurant #element/foudre(4 mana, tour)
|
||||
>Lance un faisceau électrique qui peut contourner les obstacles pour toucher une cible à couvert. Inflige 3d4[[Glossaire#Jet explosif|!]] dégâts de [[Les types de dégâts#Foudre|foudre]].
|
||||
>*Vous pouvez viser une case que vous ne voyez pas, mais le MJ ne doit pas vous informer si l'attaque pourrait toucher une cible.*
|
||||
|
||||
- #element/foudre(mana, tour)
|
||||
>
|
||||
|
||||
- #element/terre(2 mana, tour)
|
||||
>Un pilier de matière est extirpé du sol pour aller frapper la cible, qui est alors déplacée d'une case. Si la cible est propulsée contre un mur, elle subit alors 3d12 dégâts [[Les types de dégâts#Contondant|contondant]].
|
||||
|
||||
- #element/terre(3 mana, tour)
|
||||
>Propulse un projectile de matière sur la cible, infligeant 1d12 dégâts [[Les types de dégâts#Contondant|contondant]] et appliquant un [[Les effets#L'étourdissement|étourdissement]] (2/12).
|
||||
|
||||
- Bouclier tortue #element/terre(3 mana, tour)
|
||||
>Vous gagnez un bonus de 2 en blocage, mais subissez également un malus de 2 en esquive et perdez 2 cases de vitesse de course durant 1 min.
|
||||
|
||||
- #element/terre(2 mana, réaction)
|
||||
> Vous gagnez une résistance aux dégâts [[Les types de dégâts#Les dégâts physiques|physiques]] jusqu'à la fin de votre prochain tour.
|
||||
|
||||
- #element/terre(mana, tour)
|
||||
>
|
||||
|
||||
- Enchantement mineur #element/arcane(2 mana, tour, V/Gl)
|
||||
> Condense de l'énergie magique dans une arme ou un projectile. Vous faites une attaque immédiatement après avoir lancé ce sort sans dépenser d'action, infligeant 1d8 dégâts supplémentaire. Les dégâts de l'arme deviennent [[Les types de dégâts#Neutre|magique]].
|
||||
|
||||
- Rupture de force #element/arcane(3 mana, tour, V/Ge/Gl)
|
||||
> Vous condensez une puissante énergie magique qui est propulsée directement sur votre cible. Vous lancez 2d20 et prenez le plus haut résultat pour infliger des dégâts [[Les types de dégâts#Neutre|magique]]. *Avoir un #avantage aux dégâts permet de lancer un autre d20.* *Augmenter les dégâts de ce sort permet d'infliger 5 dégâts [[Les types de dégâts#Neutre|magique]] supplémentaire.*
|
||||
|
||||
- #element/arcane(mana, tour)
|
||||
>
|
||||
|
||||
- #element/arcane(mana, tour)
|
||||
>
|
||||
|
||||
- #element/arcane(mana, tour)
|
||||
>
|
||||
|
||||
- Foulée aérienne #element/air(3 mana, tour, 12 cases)
|
||||
>La vitesse de course de votre cible augmente de 2 cases pendant 1 minute. Vous gagnez également un bonus de +1 à l'esquive.
|
||||
|
||||
- Pression forcée #element/air(5 mana, tour, 18 cases)
|
||||
>Crée une imposante colonne d'air descendent de 3 cases de rayon sur 12 cases de haut. Les créatures à l'intérieur ont un malus de 1 à l'esquive. Les créatures volantes chutent de 3 cases par tour.
|
||||
|
||||
- #element/air(mana, tour)
|
||||
>
|
||||
|
||||
- #element/air(mana, tour)
|
||||
>
|
||||
|
||||
- #element/air(mana, tour)
|
||||
>
|
||||
|
||||
- Conservation #element/nature (2 mana, 1 minute)
|
||||
>Permet à jusqu'à 5 herbes ou préparations médicinales de se conserver 1 jour de plus. *Ne peux être utilisé qu'une seule fois par herbe/préparation.*
|
||||
|
||||
- #element/nature(mana, tour)
|
||||
>
|
||||
|
||||
- #element/nature(mana, tour)
|
||||
>
|
||||
|
||||
- #element/nature(mana, tour)
|
||||
>
|
||||
|
||||
- #element/nature(mana, tour)
|
||||
>
|
||||
|
||||
- Absorption radieuse #element/lumiere (3 mana, tour)
|
||||
> Absorbe la lumière d'une zone de 4 cases de rayon, la faisant apparaitre comme plus sombre. #todo
|
||||
|
||||
- #element/lumiere (mana, tour)
|
||||
>
|
||||
|
||||
- #element/lumiere (mana, tour)
|
||||
>
|
||||
|
||||
- #element/lumiere (mana, tour)
|
||||
>
|
||||
|
||||
- #element/lumiere (mana, tour)
|
||||
>
|
||||
|
||||
- #element/psy(6 mana, tour)
|
||||
>Envenime l'esprit de la cible, brouillant sa perception de la réalité et lui faisant voir des images subliminales de chaos. Applique un effet de [[Les effets#La peur|peur]] (4/12).
|
||||
### Rang 2
|
||||
- Trait de feu 2 #element/feu (5 mana, tour, 15 cases, V/Ge/Gl)
|
||||
>Tire un faisceau de flamme, infligeant 3d8 de dégâts de feu.
|
||||
|
||||
- Lames de glace 2 #element/glace (5 mana, tour, 15 cases)
|
||||
>Tire 3 projectiles à 1d8 de glace. Augmenter les dés de dégâts offre un projectile supplémentaire à la place. Chaque projectile demande un jet d'attaque séparé et peut viser une cible différente.
|
||||
|
||||
- Chaine de foudre 2 #element/foudre (5 mana, tour, 15 cases)
|
||||
>Frappe une cible visible puis rebondit sur jusqu'à 3 autres cibles à 2 cases de la première. 1d8+3 de foudre.
|
||||
|
||||
- Décharge de foudre 2 #element/foudre(3 mana, tour)
|
||||
>Tire une décharge foudroyante d'énergie, infligeant 6d4[[Glossaire#Jet explosif|!]] de dégâts de foudre.
|
||||
|
||||
- Conservation 2 #element/nature (4 mana, 1 minute)
|
||||
>Permet à jusqu'à 8 herbes ou préparations médicinales de se conserver 3 jours de plus. *Ne peux être utilisé qu'une seule fois par herbe/préparation.*
|
||||
|
||||
- Boule de feu #element/feu (8 mana, tour, 12 cases)
|
||||
>Projette une imposante boule de flamme explosant au contact d'une surface, infligeant ainsi 4d10 de feu sur 3 cases de rayon.
|
||||
|
||||
- Détonation #element/feu (4 mana, tour, 8 cases)
|
||||
>Pointe un lieu visible. Une explosion de flamme jaillit subitement, infligeant 2d10 de feu sur 2 cases de rayon.
|
||||
|
||||
- Lance de givre #element/glace(4 mana, tour)
|
||||
>Une lame de glace vient grandir le long de votre arme. Augmente votre portée d'une case. L'arme inflige des dégâts tranchants. Dure 1 min, casse après 8 coups réussis.
|
||||
|
||||
- Téléportation #element/foudre (4 mana, tour)
|
||||
>Se téléporte à un point visible à 9 cases max.
|
||||
|
||||
- Apaisement #element/psy (3 mana, tour)
|
||||
>En touchant la cible, vous pouvez faire un jet d'intelligence. Guérit l'influence, le charme et la peur, mais augmente les chances de ces effet de 1 niveau pendant 3 tours.
|
||||
|
||||
- Painshock #element/psy (6 mana, tour)
|
||||
>*Ne fonctionne que si la cible touchée à subit des dégâts depuis votre dernier tour.* Vous touchez une plaie et intensifiez la douleur à l'extrême. Applique un effet d'[[Les effets#L'étourdissement|étourdissement]]. La difficulté est égale à 2/12 + 1 niveau pour chaque 10% de vie max retiré.
|
||||
|
||||
- Perturbateur #element/psy (4 mana, réaction, 9 cases, V/Ge)
|
||||
>Lorsqu'un lanceur de sort termine son incantation, vous pouvez perturber les flux magiques pour lui imposer un malus de 3 au jet.
|
||||
### Rang 3
|
||||
- Rejet pur #divin (spécial, tour, 3 cases, Ge)
|
||||
>Vous propulsez une énergie magique pure condensée sur votre adversaire avec une puissance absolue. Vous infligez 1d6!+4 dégâts [[Les types de dégâts#Neutre|magique]] tous les 3 mana dépensé. Vous pouvez dépenser jusqu'à 30 mana. Après avoir lancé ce sort, vous subissez un malus de 4 au lancer de sort pendant 1 tour.
|
||||
### Sorts unique
|
||||
Les sorts uniques sont des sorts obtenus uniquement avec des objets magiques ou en progressant dans l'arbre d'entrainement. Il n'existe **aucun** autre moyen d'obtenir des sorts.
|
||||
|
||||
- Dévastation #element/feu + #element/glace + #element/foudre (10 mana, tour, 12 cases)
|
||||
>Inflige 10+3d10 dégâts. Vous pouvez choisir le type de dégâts entre feu, glace et foudre. Ignore les résistances et réduit les immunités en résistance. ^484fc3
|
||||
|
||||
- Soin #element/nature (8 mana, tour, toucher)
|
||||
>Soigne 10+1d10 PV et guérit l'[[Les effets#L'étourdissement|étourdissement]], le [[Les effets#Le saignement|saignement]] et les [[Les effets#L'empoisonnement|poisons]]. ^068b55
|
||||
|
||||
- Contresort #element/arcane (4 mana, réaction, 12 cases)
|
||||
>Perturbe les flux magique pour interrompre une canalisation en cours. Vous pouvez augmenter le coût du sort pour augmenter les chances de réussite. La difficulté est égale à 6 - le cout du sort à interrompre + le cout du contresort. ^a8f46f
|
||||
|
||||
- Focalisation destructrice #element/arcane (12 mana, tour)
|
||||
>Vous focalisez les énergies magiques sur vous, rendant l'utilisation de sort plus complexe pour les autres. La densité d'énergie anormale vous fait subir 5 points de dégâts par tour. Pendant une minute, toute personne à 18 cases de vous essayant de lancer un sort ou de [[1.Règles/6.Les Aspects/index#Transformations|se transformer]] subit un malus de 4. ^73b8bd
|
||||
|
||||
- Domination mentale #element/psy (10 mana, tour, toucher)
|
||||
>Applique un effet de [[Les effets#La possession|possession]] (6/12). ^5b38b6
|
||||
### Sorts spéciaux
|
||||
Les sorts spéciaux sont une liste de sorts que les joueurs peuvent obtenir durant certaines aventures. Selon les cas, un joueur peut demander au maitre du jeu de commencer avec un sort spécial si ça correspond à son passé. Les sorts spéciaux peuvent aussi être des sorts que les PNJ ont et qu'ils peuvent apprendre aux joueurs.`);
|
||||
</script>
|
||||
3
pages/explore/[...path].vue
Normal file
3
pages/explore/[...path].vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
Current path: {{ $route.params.path }}
|
||||
</template>
|
||||
3
pages/explore/index.vue
Normal file
3
pages/explore/index.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
Index
|
||||
</template>
|
||||
@@ -1,33 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: "explorer",
|
||||
})
|
||||
|
||||
const route = useRoute();
|
||||
const { data, status } = await useLazyFetch(`/api/project/${route.params.projectId}/file/${encodeURIComponent(unifySlug(route.params.slug))}`);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head>
|
||||
<Title v-if="data">{{ data.title }}</Title>
|
||||
</Head>
|
||||
<div class="flex-1 flex overflow-hidden">
|
||||
<template v-if="status === 'pending'"><div class="loading"></div></template>
|
||||
<template v-else-if="!!data && data.type == 'Markdown' && !!data.content">
|
||||
<div class="md:px-24 ps-8 my-1 whitespace-break-spaces">
|
||||
<ProseH1>{{ data.title }}</ProseH1>
|
||||
<Markdown :content="data.content"></Markdown>
|
||||
</div>
|
||||
<CommentSide class="hidden" :comments="data.comments"></CommentSide>
|
||||
</template>
|
||||
<CanvasRenderer v-else-if="!!data && data.type == 'Canvas' && !!data.content" :canvas="JSON.parse(data.content)"></CanvasRenderer>
|
||||
<div v-else-if="!!data && data" class="h-100 w-100 flex flex-1 flex-col justify-center items-center">
|
||||
<div class="text-3xl font-extralight tracking-wide text-light-60 dark:text-dark-60">Impossible d'afficher (ou vide)</div>
|
||||
<div class="text-lg text-light-60 dark:text-dark-60">Cette page est actuellement vide ou impossible à traiter</div>
|
||||
</div>
|
||||
<div v-else class="h-100 w-100 flex flex-1 flex-col justify-center items-center">
|
||||
<div class="text-3xl font-extralight tracking-wide text-light-60 dark:text-dark-60">Introuvable</div>
|
||||
<div class="text-lg text-light-60 dark:text-dark-60">Cette page n'existe pas</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,26 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
const { id: project } = useProject();
|
||||
|
||||
const { data: projects, status, error } = await useFetch('/api/project');
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head>
|
||||
<Title>Liste des projets</Title>
|
||||
</Head>
|
||||
<div v-if="status === 'success'" class="grid grid-cols-2 gap-4">
|
||||
<div v-for="p of projects" class="border border-light-35 dark:border-dark-35 px-4 py-2">
|
||||
<div class="flex justify-between">
|
||||
<div>
|
||||
<NuxtLink class="text-accent-blue text-xl font-semibold hover:text-opacity-75" :to="{ path: `/explorer/${p.id}/${p.home}`, force: true }">{{ p.name }}</NuxtLink>
|
||||
<div class="italic">Par <NuxtLink class="font-semibold hover:underline" :to="{ path: `/users/${p.owner}`, force: true }">{{ p.username }}</NuxtLink></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="">{{ p.pages }} pages</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="">{{ p.summary ?? "Sans contenu" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="status === 'pending'" class="loading"></div>
|
||||
</template>
|
||||
@@ -1,9 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
const open = ref(false);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head>
|
||||
<Title>Accueil</Title>
|
||||
</Head>
|
||||
<div class="h-100 w-100 flex flex-1 flex-col justify-center items-center">
|
||||
<ThemeIcon icon="logo" :width=128 :height=128 />
|
||||
<div class="text-3xl font-extralight tracking-wide text-light-60 dark:text-dark-60">Coucou :) :) :) :)</div>
|
||||
<div class="text-3xl font-extralight tracking-wide text-light-60 dark:text-dark-60">
|
||||
<Tooltip message="Ajouter" side="top"><button @click="open = !open">Test</button></Tooltip>
|
||||
<Toast v-model="open">
|
||||
<template v-slot:title><h4>Titre</h4></template>
|
||||
<span>Test</span>
|
||||
</Toast>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
3
pages/legal.vue
Normal file
3
pages/legal.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
|
||||
</template>
|
||||
@@ -1,33 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
let data = `### Librairies, framework et outils libres utilisés:
|
||||
- [vue.js](https://vuejs.org/) - MIT License - Copyright (c) 2018-present, Yuxi (Evan) You and Vue contributors
|
||||
- [vue-router](https://router.vuejs.org/) - MIT License - Copyright (c) 2019-present Eduardo San Martin Morote
|
||||
- [nuxt](https://nuxt.com/) - MIT License - Copyright (c) 2016-present - Nuxt Team
|
||||
- [@nuxtjs/color-mode](https://color-mode.nuxtjs.org/) - MIT License - Copyright (c) Nuxt Team
|
||||
- [@nuxtjs/tailwindcss](https://tailwindcss.nuxtjs.org/) - MIT License - Copyright (c) Nuxt Community
|
||||
- [@vueuse/nuxt](https://vueuse.org/) - MIT License - Copyright (c) 2019-PRESENT Anthony Fu https://github.com/antfu
|
||||
- [nuxt-security](https://nuxt-security.vercel.app/) - MIT License - Copyright (c) 2023 Baroshem jakub.andrzejewski.dev@gmail.com
|
||||
- [zod](https://zod.dev/) - MIT License - Copyright (c) 2020 Colin McDonnell
|
||||
- [unified](https://unifiedjs.com/) - MIT License - Copyright (c) 2015 Titus Wormer <tituswormer@gmail.com>
|
||||
|
||||
Le logo a été créé grace aux icones de [Game Icons](https://game-icons.net).`;
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head>
|
||||
<Title>Tierces parties et crédits</Title>
|
||||
</Head>
|
||||
<div style="margin-left: var(--sidebar-left-width)" class="site-body-center-column">
|
||||
<div class="render-container">
|
||||
<div class="render-container-inner">
|
||||
<div class="publish-renderer">
|
||||
<div class="markdown-preview-view markdown-rendered node-insert-event hide-title">
|
||||
<div class="markdown-preview-sizer markdown-preview-section" style="padding-bottom: 0px;">
|
||||
<Markdown :content="data" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
3
pages/user/[id].vue
Normal file
3
pages/user/[id].vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
User profile: {{ $route.params.id }}
|
||||
</template>
|
||||
3
pages/user/edit.vue
Normal file
3
pages/user/edit.vue
Normal file
@@ -0,0 +1,3 @@
|
||||
<template>
|
||||
Edit your profile
|
||||
</template>
|
||||
@@ -1,97 +1,3 @@
|
||||
<script setup lang="ts">
|
||||
import { ZodError } from 'zod';
|
||||
import { schema, type Login } from '~/schemas/login';
|
||||
|
||||
definePageMeta({
|
||||
usersGoesTo: '/user/profile'
|
||||
});
|
||||
|
||||
const state = reactive<Login>({
|
||||
usernameOrEmail: '',
|
||||
password: ''
|
||||
});
|
||||
|
||||
const { data: result, status, error, refresh } = await useFetch('/api/auth/login', {
|
||||
body: state,
|
||||
immediate: false,
|
||||
method: 'POST',
|
||||
watch: false,
|
||||
ignoreResponseError: true,
|
||||
})
|
||||
|
||||
const usernameError = ref("");
|
||||
const passwordError = ref("");
|
||||
const generalError = ref("");
|
||||
|
||||
async function submit()
|
||||
{
|
||||
if(state.password === "")
|
||||
return;
|
||||
|
||||
const data = schema.safeParse(state);
|
||||
|
||||
if(data.success)
|
||||
{
|
||||
await refresh()
|
||||
|
||||
const login = result.value;
|
||||
if(!login || !login.success)
|
||||
{
|
||||
handleErrors(login?.error ?? error.value!);
|
||||
}
|
||||
else if(status.value === 'success' && login.success)
|
||||
{
|
||||
await navigateTo('/user/profile');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
handleErrors(data.error);
|
||||
}
|
||||
}
|
||||
function handleErrors(error: Error | ZodError)
|
||||
{
|
||||
if(error.hasOwnProperty('issues'))
|
||||
{
|
||||
for(const err of (error as ZodError).issues)
|
||||
{
|
||||
if(err.path.includes('username'))
|
||||
{
|
||||
usernameError.value = err.message;
|
||||
}
|
||||
if(err.path.includes('password'))
|
||||
{
|
||||
passwordError.value = err.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
generalError.value = error?.message ?? 'Erreur inconnue.';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head>
|
||||
<Title>Se connecter</Title>
|
||||
</Head>
|
||||
<div class="flex flex-1 justify-center items-center">
|
||||
<div class="p-8 w-[48em] border border-light-35 dark:border-dark-35">
|
||||
<form @submit.prevent="submit" class="p-4 bg-light-20 dark:bg-dark-20">
|
||||
<h1 class="text-2xl font-bold tracking-wider pb-4">Connexion</h1>
|
||||
<InputField type="text" autocomplete="username" v-model="state.usernameOrEmail"
|
||||
placeholder="" title="Nom d'utilisateur ou adresse mail" :error="usernameError"/>
|
||||
<InputField type="password" autocomplete="current-password" v-model="state.password"
|
||||
placeholder="" title="Mot de passe"
|
||||
:error="passwordError" class="w-[24em]"/>
|
||||
<span v-if="generalError" class="text-light-red dark:text-dark-red">{{ generalError }}</span>
|
||||
<button class="m-auto block px-4 py-1 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40 hover:border-light-50 dark:hover:border-dark-50 active:relative active:top-[1px]">
|
||||
<div class="loading before:w-6 before:h-6" v-if="status === 'pending'"></div>
|
||||
<template v-else>Se connecter</template>
|
||||
</button>
|
||||
<NuxtLink class="mt-4 text-center block text-sm font-semibold tracking-wide hover:italic" :to="{ path: `/user/register`, force: true }">Pas de compte ?</NuxtLink>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
Login
|
||||
</template>
|
||||
@@ -1,74 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { format } from '#imports';
|
||||
|
||||
definePageMeta({
|
||||
guestsGoesTo: '/user/login'
|
||||
});
|
||||
|
||||
const { user, clear } = useUserSession();
|
||||
const { data: projects } = useFetch(`/api/users/${user.value?.id}/projects`);
|
||||
const { data: comments } = useFetch(`/api/users/${user.value?.id}/comments`);
|
||||
|
||||
const dialog = useTemplateRef('dialog');
|
||||
const files = ref<File[]>([]), imgURL = ref<string |null>(null);
|
||||
|
||||
async function loadImage(f: File[])
|
||||
{
|
||||
files.value = f;
|
||||
if(f.length > 0)
|
||||
{
|
||||
imgURL.value = URL.createObjectURL(f[0]);
|
||||
}
|
||||
}
|
||||
async function approveThumbnail()
|
||||
{
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head>
|
||||
<Title>Votre profil</Title>
|
||||
</Head>
|
||||
<div v-if="user" class="flex-1 grid gap-4 grid-cols-3">
|
||||
<div class="border border-light-35 dark:border-dark-35 p-4 flex gap-4 col-span-2 self-start">
|
||||
<Image @click="() => dialog?.show()" :src="`/users/${user?.id}/normal.jpg`" :width=128 :height=128 class="inline-flex hover:bg-light-70 hover:bg-opacity-50 hover:opacity-50 transition-all cursor-pointer" fallback-icon="user-unknown" />
|
||||
<Dialog :close-icon="true" :focused="false" ref="dialog">
|
||||
<div class="w-[640px] h-[320px] flex justify-center gap-4 items-center mb-6">
|
||||
<div class="w-[256px] h-[256px]">
|
||||
<Image :src="imgURL" :width="256" :height="256" fallback-icon="user-unknown" class="inline-block select-none" :style="{ filter: `saturate(0.3) contrast(0.5)` }"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="border-t border-light-35 dark:border-dark-35 absolute bottom-0 left-0 right-0 p-4 flex justify-between">
|
||||
<div class="flex gap-3">
|
||||
<Upload accept="image/png,image/jpeg,image/webp" @change="loadImage">
|
||||
<span class="py-1 px-2 cursor-pointer text-light-100 dark:text-dark-100 transition-all duration-200 mb-4 border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 hover:border-light-50 dark:hover:border-dark-50 hover:bg-light-30 dark:hover:bg-dark-30">Importer un fichier</span>
|
||||
</Upload>
|
||||
<span class="text-sm text-light-60 dark:text-dark-60 italic">5 Mo max. 128 x 128 min recommandé.</span>
|
||||
</div>
|
||||
<div>
|
||||
<span @click="approveThumbnail" class="py-1 px-2 cursor-pointer text-light-100 dark:text-dark-100 transition-all duration-200 mb-4 border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 hover:border-light-50 dark:hover:border-dark-50 hover:bg-light-30 dark:hover:bg-dark-30">Valider</span>
|
||||
</div>
|
||||
</div>
|
||||
</Dialog>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xl font-semibold">{{ user?.username }}</span>
|
||||
<span>Inscrit depuis le {{ format(new Date(user.signin_timestamp), 'dd/MM/yyyy') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 py-2 flex gap-4 flex-col">
|
||||
<button @click="clear()" class="py-1 transition-all duration-200 mb-3 border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 hover:border-light-50 dark:hover:border-dark-50 hover:bg-light-30 dark:hover:bg-dark-30">Se déconnecter</button>
|
||||
<button @click="" class="py-1 transition-all duration-200 border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 hover:border-light-50 dark:hover:border-dark-50 hover:bg-light-30 dark:hover:bg-dark-30">Mettre à jour le profil</button>
|
||||
<button @click="" class="py-1 transition-all duration-200 border border-light-35 dark:border-dark-35 bg-light-20 dark:bg-dark-20 hover:border-light-red dark:hover:border-dark-red hover:bg-light-30 dark:hover:bg-dark-30 text-light-red dark:text-dark-red">Supprimer le compte</button>
|
||||
</div>
|
||||
<div class="border border-light-35 dark:border-dark-35 p-4 flex flex-col">
|
||||
<div class="text-2xl">Mes projets</div>
|
||||
<div class="flex flex-col">
|
||||
<div v-for="project of projects" class="flex justify-between items-center py-1">
|
||||
<NuxtLink :to="{ path: `/explorer/${project.id}/${project.home}`, force: true }" class="text-accent-blue font-bold cursor-pointer hover:text-opacity-80 text-lg">{{ project.name }}</NuxtLink>
|
||||
<span>{{ project.pages }} pages</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,124 +1,3 @@
|
||||
<script setup lang="ts">
|
||||
import { ZodError } from 'zod';
|
||||
import { schema, type Registration } from '~/schemas/registration';
|
||||
|
||||
definePageMeta({
|
||||
usersGoesTo: '/user/profile'
|
||||
});
|
||||
|
||||
const state = reactive<Registration>({
|
||||
username: '',
|
||||
email: '',
|
||||
password: ''
|
||||
});
|
||||
|
||||
const confirmPassword = ref("");
|
||||
|
||||
const checkedLength = computed(() => state.password.length >= 8 && state.password.length <= 128);
|
||||
const checkedLowerUpper = computed(() => state.password.toLowerCase() !== state.password && state.password.toUpperCase() !== state.password);
|
||||
const checkedDigit = computed(() => /[0-9]/.test(state.password));
|
||||
const checkedSymbol = computed(() => " !\"#$%&'()*+,-./:;<=>?@[]^_`{|}~".split("").some(e => state.password.includes(e)));
|
||||
|
||||
const usernameError = ref("");
|
||||
const emailError = ref("");
|
||||
const generalError = ref("");
|
||||
|
||||
const { data: result, status, error, execute } = await useFetch('/api/auth/register', {
|
||||
body: state,
|
||||
immediate: false,
|
||||
method: 'POST',
|
||||
watch: false,
|
||||
ignoreResponseError: true,
|
||||
})
|
||||
|
||||
async function submit()
|
||||
{
|
||||
if(state.password === "" || state.password !== confirmPassword.value)
|
||||
return;
|
||||
|
||||
const data = schema.safeParse(state);
|
||||
|
||||
if(data.success)
|
||||
{
|
||||
await execute()
|
||||
|
||||
const login = result.value;
|
||||
if(!login || !login.success)
|
||||
{
|
||||
handleErrors(login?.error ?? error.value!);
|
||||
}
|
||||
else if(status.value === 'success' && login.success)
|
||||
{
|
||||
await navigateTo('/user/profile');
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
handleErrors(data.error);
|
||||
}
|
||||
}
|
||||
function handleErrors(error: Error | ZodError)
|
||||
{
|
||||
if(error.hasOwnProperty('issues'))
|
||||
{
|
||||
for(const err of (error as ZodError).issues)
|
||||
{
|
||||
if(err.path.includes('username'))
|
||||
{
|
||||
usernameError.value = err.message;
|
||||
}
|
||||
if(err.path.includes('email'))
|
||||
{
|
||||
emailError.value = err.message;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
generalError.value = error?.message ?? 'Erreur inconnue.';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<Head>
|
||||
<Title>S'inscrire</Title>
|
||||
</Head>
|
||||
<div class="flex flex-1 justify-center items-center">
|
||||
<div class="p-8 w-[48em] border border-light-35 dark:border-dark-35">
|
||||
<form @submit.prevent="submit" class="p-4 bg-light-20 dark:bg-dark-20">
|
||||
<h1 class="text-2xl font-bold tracking-wider pb-4">Inscription</h1>
|
||||
<InputField type="text" autocomplete="username" v-model="state.username"
|
||||
placeholder="Entrez un nom d'utilisateur" title="Nom d'utilisateur" :error="usernameError"/>
|
||||
<InputField type="text" autocomplete="email" v-model="state.email" placeholder="Entrez une addresse mail"
|
||||
title="Adresse mail" :error="emailError"/>
|
||||
<InputField type="password" autocomplete="new-password" v-model="state.password"
|
||||
placeholder="Entrez un mot de passe" title="Mot de passe"
|
||||
:error="!(checkedLength && checkedLowerUpper && checkedDigit && checkedSymbol)"/>
|
||||
<div class="flex flex-col font-light">
|
||||
<span class="">Votre mot de passe doit respecter les critères de sécurité suivants
|
||||
:</span>
|
||||
<span class="px-4" :class="{'text-light-red dark:text-dark-red': !checkedLength}">Entre 8 et 128
|
||||
caractères</span>
|
||||
<span class="px-4" :class="{'text-light-red dark:text-dark-red': !checkedLowerUpper}">Au moins
|
||||
une minuscule et une majuscule</span>
|
||||
<span class="px-4" :class="{'text-light-red dark:text-dark-red': !checkedDigit}">Au moins un
|
||||
chiffre</span>
|
||||
<span class="px-4" :class="{'text-light-red dark:text-dark-red': !checkedSymbol}">Au moins un
|
||||
caractère spécial parmis la liste suivante:
|
||||
<pre>! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ ] ^ _ ` { | } ~</pre>
|
||||
</span>
|
||||
</div>
|
||||
<InputField type="password" v-model="confirmPassword" placeholder="Confirmer le mot de passe" title="Confirmer le mot de passe" autocomplete="new-password"
|
||||
:error="confirmPassword === '' || confirmPassword === state.password ? '' : 'Les mots de passe saisies ne sont pas identique'"/>
|
||||
<span v-if="generalError" class="text-light-red dark:text-dark-red">{{ generalError }}</span>
|
||||
<button class="m-auto block px-4 py-1 bg-light-20 dark:bg-dark-20 border border-light-40 dark:border-dark-40 hover:border-light-50 dark:hover:border-dark-50 active:relative active:top-[1px]">
|
||||
<div v-if="status === 'pending'" class="loading"></div>
|
||||
<template v-else>S'inscrire</template>
|
||||
</button>
|
||||
<NuxtLink class="mt-4 text-center block text-sm font-semibold tracking-wide hover:italic" :to="{ path: `/user/login`, force: true }">Vous avez déjà un compte ? Se connecter</NuxtLink>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
Register
|
||||
</template>
|
||||
@@ -1,31 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
const route = useRoute();
|
||||
const { data: user } = useFetch(`/api/users/${route.params.id}`);
|
||||
const { data: projects } = useFetch(`/api/users/${route.params.id}/projects`);
|
||||
const { data: comments } = useFetch(`/api/users/${route.params.id}/comments`);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head>
|
||||
<Title>{{ user?.username ?? "Inconnu" }}</Title>
|
||||
</Head>
|
||||
<div v-if="user" class="border border-light-35 dark:border-dark-35 p-4 flex gap-4">
|
||||
<div>
|
||||
<picture :width=128 :height=128 class="flex" >
|
||||
<source :src="`/users/${user?.id}/normal.jpg`" :width=128 :height=128 />
|
||||
<Icon :icon="`users/unknown`" :width=128 :height=128 ></Icon>
|
||||
</picture>
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
<span class="text-xl font-semibold">{{ user?.username }}</span>
|
||||
<span>Inscrit depuis le {{ format(new Date(user.signin_timestamp), 'dd/MM/yyyy') }}</span>
|
||||
<br/>
|
||||
<span>A créé {{ projects?.length ?? 0 }} projet(s)</span>
|
||||
<span>A créé {{ comments?.length ?? 0 }} commentaire(s)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="h-100 w-100 flex flex-1 flex-col justify-center items-center">
|
||||
<div class="text-3xl font-extralight tracking-wide text-light-60 dark:text-dark-60">Introuvable</div>
|
||||
<div class="text-lg text-light-60 dark:text-dark-60">Cette page n'existe pas</div>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user