initial commit

This commit is contained in:
2026-02-26 21:29:34 +01:00
commit d9d84634e8
72 changed files with 18491 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
<template>
<div v-if="authStore.user != null">
<img class='avatar' :src="authStore.user.avatar"/>
<ProfileModulesDisplayName/>
</div>
</template>
<script setup lang="ts">
const authStore = useAuthStore()
onMounted(() => {
if (authStore.user){
authStore.user.sudo_token = null
}
})
</script>
<style lang="scss">
img.avatar {
display: block;
margin-top:0.75em;
margin-inline: auto;
border-radius: 50%;
//border: 5px solid blueviolet;
}
.modale-btns{
display: flex;
gap:1em;
}
</style>

View File

@@ -0,0 +1,31 @@
<template>
<UiModale :modalActive="modelValue"
@close="emit('update:modelValue', false)"
title="Confirmer le changement demail">
<div class="modale-content">
<p>
Un email de validation va être envoyé à la nouvelle adresse.
Le changement ne sera effectif quaprès confirmation.
</p>
<div class="actions">
<button class="btn danger" @click="confirm">Confirmer</button>
<button class="btn" @click="emit('update:modelValue', false)">Annuler</button>
</div>
</div>
</UiModale>
</template>
<script setup lang="ts">
const props = defineProps<{
modelValue: boolean
}>()
const emit = defineEmits(['update:modelValue', 'confirm'])
function confirm() {
emit('confirm')
emit('update:modelValue', false)
}
</script>

View File

@@ -0,0 +1,74 @@
<template>
<div>
<button class="deleteBtn" @click="openModal">
{{ $t('profile.delete_account') }}
</button>
</div>
<UiModale @close="closeModal" :modalActive="modalActive">
<div class="modal-content">
<h1>{{ $t('delete.modale.title') }}</h1>
<p>{{ $t('delete.modale.text1') }}</p>
<!-- <p>{{ $t('emailUpdate.modale.text2') }}</p> -->
<div class="modale-btns">
<ButtonBase ref=ConfirmBtn @click="confirm" :loading="awaiting">{{ $t('ui.yes') }}</ButtonBase>
<ButtonBase class="btn" @click="closeModal">{{ $t('ui.no') }}</ButtonBase>
</div>
</div>
</UiModale>
<UiModale @close="closeModalInfo" :modalActive="modalInfoActive">
<div class="modal-content">
<h1>{{ $t('delete.modale2.title') }}</h1>
<p>{{ $t('delete.modale2.text1') }}</p>
<div class="modale-btns">
<ButtonBase ref=btnCloseInfo @click="closeModalInfo">{{ $t('ui.ok') }}</ButtonBase>
</div>
</div>
</UiModale>
</template>
<script setup lang="ts">
const {locale} = useI18n()
const authStore = useAuthStore();
const modalActive = ref(false);
const modalInfoActive = ref(false)
const awaiting = ref(false)
const openModal = () => {
modalActive.value = true;
}
const closeModal = () => {
modalActive.value = false;
}
const confirm = async (locale:string) => {
awaiting.value = true;
await authStore.deleteRequest(locale)
awaiting.value=false
modalActive.value = false;
modalInfoActive.value = true
}
const closeModalInfo = () => {
modalInfoActive.value = false
}
</script>
<style scoped lang="scss">
.deleteBtn{
color:red;
font-weight: bold;
margin:0;
border:none;
text-align: left;
background-color: none;
&:hover{
color:rgb(83, 0, 0)
}
}
button{
text-align: center;
}
</style>

View File

@@ -0,0 +1,74 @@
<template>
<div>
<button class="deleteBtn" @click="openModal">
{{ $t('profile.delete_account') }}
</button>
</div>
<UiModale @close="closeModal" :modalActive="modalActive">
<div class="modal-content">
<h1>{{ $t('delete.modale.title') }}</h1>
<p>{{ $t('delete.modale.text1') }}</p>
<!-- <p>{{ $t('emailUpdate.modale.text2') }}</p> -->
<div class="modale-btns">
<ButtonBase ref=ConfirmBtn @click="confirm" :loading="awaiting">{{ $t('ui.yes') }}</ButtonBase>
<ButtonBase class="btn" @click="closeModal">{{ $t('ui.no') }}</ButtonBase>
</div>
</div>
</UiModale>
<UiModale @close="closeModalInfo" :modalActive="modalInfoActive">
<div class="modal-content">
<h1>{{ $t('delete.modale2.title') }}</h1>
<p>{{ $t('delete.modale2.text1') }}</p>
<div class="modale-btns">
<ButtonBase ref=btnCloseInfo @click="closeModalInfo">{{ $t('ui.ok') }}</ButtonBase>
</div>
</div>
</UiModale>
</template>
<script setup lang="ts">
const {locale} = useI18n()
const authStore = useAuthStore();
const modalActive = ref(false);
const modalInfoActive = ref(false)
const awaiting = ref(false)
const openModal = () => {
modalActive.value = true;
}
const closeModal = () => {
modalActive.value = false;
}
const confirm = async (locale:string) => {
awaiting.value = true;
await authStore.deleteRequest(locale)
awaiting.value=false
modalActive.value = false;
modalInfoActive.value = true
}
const closeModalInfo = () => {
modalInfoActive.value = false
}
</script>
<style scoped lang="scss">
.deleteBtn{
color:red;
font-weight: bold;
margin:0;
border:none;
text-align: left;
background-color: none;
&:hover{
color:rgb(83, 0, 0)
}
}
button{
text-align: center;
}
</style>

View File

@@ -0,0 +1,105 @@
<template>
<div class="field-container">
<label>{{ $t('profile.name_label') }}</label>
<div v-if="authStore.user?.is_google" class="value-locked">
<p>{{ authStore.user.display_name }}</p>
</div>
<div v-else class="editable-wrapper">
<button
v-if="!isEditing"
@click="startEditing"
class="value-btn"
:aria-label="$t('profile.edit_name')"
>
{{ authStore.user?.display_name }}
</button>
<input
v-else
ref="inputRef"
v-model="newName"
@blur="handleUpdate"
@keydown.enter="handleUpdate"
@keydown.esc="cancelEditing"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, nextTick } from 'vue'
import { useAuthStore } from '@/stores/auth'
const authStore = useAuthStore()
const isEditing = ref(false)
const newName = ref(authStore.user?.display_name || '')
const inputRef = ref<HTMLInputElement | null>(null)
const startEditing = async () => {
isEditing.value = true
// On attend que l'input soit rendu pour lui donner le focus
await nextTick()
inputRef.value?.focus()
}
const cancelEditing = () => {
isEditing.value = false
newName.value = authStore.user?.display_name || ''
}
const handleUpdate = () => {
if (!isEditing.value) return
// Si le nom a changé et n'est pas vide
if (newName.value && newName.value !== authStore.user?.display_name) {
authStore.updateDisplayName(newName.value)
}
isEditing.value = false
}
</script>
<style scoped lang="scss">
div.field-container {
margin-block: 1rem;
display:block
}
div.editable-wrapper{
display:inline;
}
.value-locked::after {
content:" 🔒";
}
.value-locked {
display:inline;
& p {
font-weight: 500;
color: #666;
display: inline;
align-items: center;
gap: 8px;
}
}
label{
font-weight:bold;
display:inline;
}
.value-btn {
background: none;
border: none;
padding: 0;
cursor: pointer;
text-align: left;
font-size:1em;
&:hover{
color : blueviolet;
}
}
</style>

View File

@@ -0,0 +1,126 @@
<template>
<div class="field-container">
<label>{{ $t('profile.email_label') }}</label>
<div v-if="authStore.user?.is_google" class="value-locked">
<p>{{ authStore.user.email }}</p>
</div>
<div v-else class="editable-wrapper">
<button
v-if="!email_toggle"
@click="toggle_to_input"
>
{{ authStore.user?.email }}
</button>
<input v-else
ref="emailInput"
v-model="email"
@blur="handleUpdate"
@keydown.enter="handleUpdate"
@keydown.esc ="cancelEditing"
/>
</div>
</div>
<UiModale @close="closeEmailModal" :modalActive="emailModalActive">
<div class="modal-content">
<h1>{{ $t('emailUpdate.modale.title') }}</h1>
<p>{{ $t('emailUpdate.modale.text1') }}</p>
<p>{{ $t('emailUpdate.modale.text2') }}</p>
<div class="modale-btns">
<ButtonBase ref=ConfirmBtn @click="confirmEmailChange">{{ $t('emailUpdate.modale.confBtn') }}</ButtonBase>
<ButtonBase class="btn" @click="closeEmailModal">{{ $t('emailUpdate.modale.cancelBtn') }}</ButtonBase>
</div>
</div>
</UiModale>
</template>
<script setup lang="ts">
const {locale} = useI18n()
const authStore = useAuthStore()
const email_toggle = ref(false)
const email = ref(authStore.user?.email)
const emailModalActive = ref(false)
const emailInput = ref<HTMLInputElement | null>(null)
const ConfirmBtn = ref<HTMLInputElement | null>(null)
const toggle_to_input = async ()=>{
email_toggle.value = true
// On attend que Vue passe email_toggle à true et affiche l'input dans le DOM
await nextTick()
// Maintenant l'input existe, on peut lui donner le focus
emailInput.value?.focus()
}
const handleUpdate = async ()=>{
if (email.value !== authStore.user?.email){
emailModalActive.value = true
await nextTick()
ConfirmBtn.value?.focus()
}
email_toggle.value = false
}
const cancelEditing = () => {
email_toggle.value = false
email.value = authStore.user?.email || ''
}
//* Fonctions de taitement email */
const closeEmailModal = () =>{
emailModalActive.value = false
email.value = authStore.user?.email
}
const confirmEmailChange = () => {
emailModalActive.value = false
if (email.value){
authStore.emailChange(email.value, locale.value)
}
}
</script>
<style lang="scss">
div.field-container {
margin-block: 1rem;
display:block
}
div.editable-wrapper{
display:inline;
}
.value-locked::after {
content:" 🔒";
}
.value-locked {
display:inline;
& p {
font-weight: 500;
color: #666;
display: inline;
align-items: center;
gap: 8px;
}
}
label{
font-weight:bold;
display:inline;
}
button{
background: none;
border: none;
padding: 0;
cursor: pointer;
text-align: left;
font-size:1em;
&:hover{
color:blueviolet;
}
}
</style>

View File

@@ -0,0 +1,57 @@
<template>
<form @submit.prevent="handleFormSubmit">
<InputPassword
name="password"
label=""
:placeholder="$t('profile.pwd_challenge_input_label')"
v-model="password"
>
<p>{{ password }}</p>
<template #message>
<p v-if="errors.passwordEmpty" class="error">{{ $t('ui.errorPwdEmpty') }}</p>
<p v-if="errors.wrongPassword" class="error">{{ $t('ui.errorWrongPwd') }}</p>
</template>
</InputPassword>
<ButtonBase
:disabled="password === '' || awaiting"
:loading="awaiting"
>
{{ $t('loginFormBtn') }}
</ButtonBase>
</form>
</template>
<script setup lang="ts">
const authStore=useAuthStore()
const password = ref('');
const errors = ref({
"passwordEmpty":false,
"wrongPassword":false
})
const awaiting = ref(false);
const handleFormSubmit = async() => {
awaiting.value = true
errors.value.wrongPassword = false;
errors.value.passwordEmpty = false;
if (!password.value){
errors.value.passwordEmpty = true;
awaiting.value = false;
return false
}
// Envoie de la requette à l'endpoint JWT et récupération d'un token de connexion
const success = await authStore.pwdChallenge(password.value)
if (success) {
awaiting.value = false
}
else{
errors.value.wrongPassword = true;
awaiting.value = false
}
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,46 @@
<template>
<div v-if="!authStore.user?.sudo_token" class="auth-challenge-screen">
<h3 class="danger">--- {{ $t('profile.danger_zone') }} ---</h3>
<p>Cette zone contient des actions sensibles. Veuillez confirmer votre identité.</p>
<div v-if="authStore.user?.is_google" class="reauth-box">
<p>Compte Google détecté : veuillez confirmer votre session.</p>
<ButtonGoogleChallenge />
</div>
<div v-else class="reauth-box">
<ProfileModulesPasswordChallenge />
</div>
</div>
<div v-else class="red-zone-content">
<div class="module-wrapper">
<ProfileModulesEmailUpdate />
</div>
<div class="danger-section">
<h3 class="danger">--- {{ $t('profile.danger_zone') }} ---</h3>
<ProfileModulesGlobalLogout />
<ProfileModulesDeleteAccount />
</div>
</div>
</template>
<script setup lang="ts">
const authStore = useAuthStore();
onUnmounted( () => {
if (authStore.user) authStore.user.sudo_token = null
}
)
</script>
<style scoped lang="scss">
h3.danger{
color : red;
margin-bottom: 0.5em;
}
.reauth-box{
margin-top: 1em;
}
</style>