530 lines
22 KiB
Vue
530 lines
22 KiB
Vue
<template>
|
||
<v-container fluid>
|
||
<!-- Loading Section -->
|
||
<v-row v-if="isLoading" class="justify-center">
|
||
<Loader />
|
||
</v-row>
|
||
|
||
<!-- Main Content Section -->
|
||
<v-row v-else class="mx-5">
|
||
<v-row class="video-section">
|
||
<video autoplay loop muted playsinline class="video-background">
|
||
<source src="/videos/video.mp4" type="video/mp4" />
|
||
Your browser does not support the video tag.
|
||
</video>
|
||
</v-row>
|
||
<!-- Introduction section -->
|
||
<v-row v-if="introduction" class="d-flex justify-center align-center text-center mx-4">
|
||
<h1 class="my-8 lg:text-xl text-l text-black">{{ introduction.title }}</h1>
|
||
<p class="text-xs text-black sm:mb-24 mr-2 lg:mr-8 text-justify">{{ introduction.description }}</p>
|
||
</v-row>
|
||
<!-- Articles Section -->
|
||
<v-row>
|
||
<!-- Left Column -->
|
||
<v-col cols="12" md="6">
|
||
<div v-for="(article, index) in leftColumnArticles" :key="article.id || index" class="article">
|
||
<v-card class="mb-12" elevation="0">
|
||
<v-card-title>
|
||
<h2 class="lg:text-sm text-xs text-wrap text-black font-bold">{{ article.title }}</h2>
|
||
</v-card-title>
|
||
<v-card-text>
|
||
<p class="text-xs text-black">{{ article.description }}</p>
|
||
</v-card-text>
|
||
<v-card-actions>
|
||
<v-btn icon="mdi-play" @click="toggleShow(article.id)">
|
||
</v-btn>
|
||
<p class="text-gray-700 lg:text-xs text-xs">Suggérer une modification de texte</p>
|
||
</v-card-actions>
|
||
|
||
<v-expand-transition>
|
||
<div v-show="showStates[article.id]">
|
||
<v-divider></v-divider>
|
||
<p class="text-gray-500 text-xs mx-5"> La constitution des DAV ne se fera pas
|
||
seule. Chacun et
|
||
chacune sont
|
||
invité(e)s à donner le meilleur d’eux même pour assurer sa constitution,
|
||
son respect, ainsi que sa mise en application.
|
||
Soyez courtois . Tout commentaire désobligeant irrespectueux ou agressif sera
|
||
systématiquement supprimé. </p>
|
||
|
||
<!-- Comments Section -->
|
||
<v-card-text>
|
||
<v-textarea v-model="newComments[article.id]" outlined rows="2"
|
||
auto-grow></v-textarea>
|
||
<VBtnValid @click="addComment(article.id)">
|
||
Valider
|
||
</VBtnValid>
|
||
</v-card-text>
|
||
</div>
|
||
</v-expand-transition>
|
||
</v-card>
|
||
</div>
|
||
</v-col>
|
||
|
||
<!-- Right Column -->
|
||
<v-col cols="12" md="6">
|
||
<div v-for="(article, index) in rightColumnArticles" :key="article.id || index" class="article">
|
||
<v-card class="mb-12" elevation="0">
|
||
<v-card-title>
|
||
<h2 class="lg:text-sm text-xs text-wrap text-black font-bold">{{ article.title }}</h2>
|
||
</v-card-title>
|
||
<v-card-text>
|
||
<p class="text-xs text-black">{{ article.description }}</p>
|
||
</v-card-text>
|
||
<v-card-actions>
|
||
<v-btn icon="mdi-play" @click="toggleShow(article.id)">
|
||
</v-btn>
|
||
<p class="text-gray-700 lg:text-xs text-xs">Suggérer une modification de texte</p>
|
||
</v-card-actions>
|
||
|
||
<v-expand-transition>
|
||
<div v-show="showStates[article.id]">
|
||
<v-divider></v-divider>
|
||
<p class="text-gray-500 text-xs mx-5"> La constitution des DAV ne se fera pas
|
||
seule. Chacun et
|
||
chacune sont
|
||
invité(e)s à donner le meilleur d’eux même pour assurer sa constitution,
|
||
son respect, ainsi que sa mise en application.
|
||
Soyez courtois . Tout commentaire désobligeant irrespectueux ou agressif sera
|
||
systématiquement supprimé. </p>
|
||
|
||
<!-- Comments Section -->
|
||
<v-card-text>
|
||
<v-textarea v-model="newComments[article.id]" outlined rows="2"
|
||
auto-grow></v-textarea>
|
||
<VBtnValid @click="addComment(article.id)">
|
||
Valider
|
||
</VBtnValid>
|
||
</v-card-text>
|
||
</div>
|
||
</v-expand-transition>
|
||
</v-card>
|
||
</div>
|
||
</v-col>
|
||
</v-row>
|
||
<v-row v-if="summary" class="d-flex justify-center align-center text-center">
|
||
<p class="text-xs text-black mb-8 px-1 text-center">{{ summary.description }}</p>
|
||
</v-row>
|
||
<v-row class="justify-center">
|
||
<v-col cols="12" md="6" lg="8">
|
||
<v-expansion-panels variant="accordion">
|
||
<v-expansion-panel v-for="(article, index) in articles" :key="article.id" class="article">
|
||
<v-expansion-panel-title class="d-flex flex-column align-start" expand-icon=""
|
||
collapse-icon="">
|
||
<div class="flex items-center mb-0">
|
||
<!-- Like/Dislike Section -->
|
||
<div class="mr-3 flex items-center">
|
||
<v-icon class="mr-1 text-xs" size="18" color="secondary"
|
||
@click.stop="likeArticle(article.id)">
|
||
mdi-thumb-up
|
||
</v-icon>
|
||
<p class="text-gray-400 text-sm">{{ article.likes }}</p>
|
||
|
||
<v-icon class="mr-1 ml-2 text-xs" size="18" color="tertiary"
|
||
@click.stop="dislikeArticle(article.id)">
|
||
mdi-thumb-down
|
||
</v-icon>
|
||
<p class="text-gray-400 text-sm">{{ article.dislikes }}</p>
|
||
</div>
|
||
<h2 class="text-sm text-wrap text-black font-bold m-0">
|
||
{{ article.title }}
|
||
</h2>
|
||
</div>
|
||
<div class="d-flex align-center mt-2">
|
||
<!-- Control the panel opening -->
|
||
<v-icon class="mr-1">
|
||
mdi-play
|
||
</v-icon>
|
||
<p class="text-gray-700 text-xs ml-2">Voir les suggestions</p>
|
||
</div>
|
||
</v-expansion-panel-title>
|
||
<v-expansion-panel-text>
|
||
<v-timeline align="start" density="compact" class="ml-3">
|
||
<v-timeline-item v-if="article.comments.length"
|
||
v-for="(comment, cIndex) in article.comments" :key="cIndex"
|
||
dot-color="secondary" size="x-small">
|
||
<div class="flex items-center mb-0">
|
||
<!-- Like/Dislike Section -->
|
||
<div class="mr-3 flex items-center">
|
||
<v-icon color="secondary" size="14"
|
||
@click="likeComment(article.id, comment.id)">mdi-thumb-up</v-icon>
|
||
<span class="text-xs text-gray-400 ml-1 mr-2">{{ comment.likes }}</span>
|
||
<v-icon color="tertiary" size="14"
|
||
@click="dislikeComment(article.id, comment.id)">mdi-thumb-down</v-icon>
|
||
<span class="text-xs text-gray-400 ml-1">{{ comment.dislikes }}</span>
|
||
</div>
|
||
<!-- Comment Content -->
|
||
<p class="text-sm text-black">{{ comment.content }}</p>
|
||
</div>
|
||
</v-timeline-item>
|
||
|
||
<!-- No Comments Placeholder -->
|
||
<v-timeline-item v-else dot-color="accent" size="x-small">
|
||
<p class="text-sm text-black">Aucune suggestion pour cet article</p>
|
||
</v-timeline-item>
|
||
</v-timeline>
|
||
</v-expansion-panel-text>
|
||
|
||
</v-expansion-panel>
|
||
</v-expansion-panels>
|
||
</v-col>
|
||
</v-row>
|
||
|
||
<!-- Signature Section -->
|
||
<v-row class="justify-center w-full">
|
||
<v-col cols="12" md="6" lg="8">
|
||
<v-card elevation="3" class="pa-6 bg-gray-100" color="accent">
|
||
<v-card-title class="text-center">
|
||
<h2 class="text-uppercase text-md font-semibold font-sans">Signer la Charte</h2>
|
||
</v-card-title>
|
||
<v-divider></v-divider>
|
||
<v-card-text>
|
||
<v-form @submit.prevent="submitSignature" lazy-validation class="text-xs font-sans">
|
||
<v-text-field v-model="name" label="Nom / prénom / pseudonyme" outlined dense
|
||
hide-details :rules="[rules.required]" class="mb-2"
|
||
prepend-inner-icon="mdi-account"></v-text-field>
|
||
<v-text-field v-model="email" type="email" label="Email"
|
||
placeholder="Entrez votre email" outlined dense hide-details
|
||
:rules="[rules.required, rules.email]" class="mb-2"
|
||
prepend-inner-icon="mdi-email"></v-text-field>
|
||
<v-textarea v-model="comment" label="Ajouter un commentaire"
|
||
placeholder="Partagez vos réflexions ou suggestions" outlined dense hide-details
|
||
rows="2" class="mb-2" prepend-inner-icon="mdi-message-text"></v-textarea>
|
||
<VBtnValid type="submit" block>
|
||
<v-icon size="16" left>mdi-check-circle</v-icon>
|
||
Signer
|
||
</VBtnValid>
|
||
</v-form>
|
||
</v-card-text>
|
||
</v-card>
|
||
</v-col>
|
||
</v-row>
|
||
|
||
<!-- Signature Display Section -->
|
||
<v-row class="justify-center">
|
||
<v-col cols="12" md="6" lg="8">
|
||
<v-expansion-panels variant="accordion">
|
||
<v-expansion-panel>
|
||
<v-expansion-panel-title class="d-flex flex-column align-start" expand-icon=""
|
||
color="primary" collapse-icon="">
|
||
<div class="text-uppercase text-l font-semibold font-sans">voir les signatures</div>
|
||
</v-expansion-panel-title>
|
||
<v-expansion-panel-text>
|
||
<v-timeline align="start" density="compact">
|
||
<v-timeline-item v-for="signature in signatures" :key="signature.id"
|
||
dot-color="accent" size="x-small">
|
||
<div>
|
||
<div class="font-weight-normal font-sans text-sm">
|
||
<strong>{{ signature.name }}</strong> @{{ signature.createdAt }}
|
||
</div>
|
||
<div class="font-weight-normal font-sans text-sm">{{ signature.comment }}
|
||
</div>
|
||
</div>
|
||
</v-timeline-item>
|
||
</v-timeline>
|
||
</v-expansion-panel-text>
|
||
</v-expansion-panel>
|
||
</v-expansion-panels>
|
||
</v-col>
|
||
</v-row>
|
||
|
||
<!-- Success Overlay (Dynamic Content) -->
|
||
<v-overlay v-model="overlay.visible" class="d-flex align-center justify-center"
|
||
:style="{ background: 'rgba(0, 0, 0, 0.5)', backdropFilter: 'blur(4px)' }">
|
||
<v-card class="py-10 px-8 text-center" elevation="4" rounded="lg" style="max-width: 400px;">
|
||
<v-icon :color="overlay.iconColor" size="48" class="mb-4">{{ overlay.icon }}</v-icon>
|
||
<p class="overlay-msg text-sm font-semibold text-black text-uppercase">{{ overlay.message }}</p>
|
||
<VBtnValid class="mt-6" @click="overlay.visible = false">
|
||
{{ overlay.buttonText }}
|
||
</VBtnValid>
|
||
</v-card>
|
||
</v-overlay>
|
||
</v-row>
|
||
</v-container>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted } from 'vue';
|
||
import { useAsyncData } from 'nuxt/app';
|
||
|
||
import Loader from '~/components/Loader.vue';
|
||
import { updateLikeDislike } from '~/lib/likes.ts';
|
||
import { fetchArticlesData, fetchCommentsData, fetchSignatures } from '~/lib/fetchers';
|
||
import { showOverlay } from '~/lib/overlayHandler';
|
||
|
||
definePageMeta({
|
||
layout: 'default',
|
||
});
|
||
|
||
const articles = ref([]);
|
||
const newComments = ref([]);
|
||
const leftColumnArticles = ref([]);
|
||
const rightColumnArticles = ref([]);
|
||
const showStates = ref({});
|
||
const introduction = ref(null);
|
||
const summary = ref(null);
|
||
const signatures = ref([]);
|
||
// Form fields
|
||
const name = ref('');
|
||
const email = ref('');
|
||
const comment = ref('');
|
||
|
||
const isFormValid = computed(() => name.value && email.value);
|
||
const isLoading = ref(true);
|
||
|
||
const overlay = ref({
|
||
icon: '',
|
||
message: '',
|
||
buttonText: '',
|
||
iconColor: '',
|
||
visible: false,
|
||
})
|
||
|
||
const rules = {
|
||
required: (value) => !!value || "Ce champ est obligatoire.",
|
||
email: (value) => /.+@.+\..+/.test(value) || "L'email doit être valide.",
|
||
};
|
||
|
||
const toggleShow = (articleId) => {
|
||
showStates.value[articleId] = !showStates.value[articleId];
|
||
};
|
||
|
||
// Fetch articles data
|
||
const fetchContent = async () => {
|
||
try {
|
||
const { data: contentData, error } = await useAsyncData('content', () =>
|
||
queryContent().find()
|
||
);
|
||
|
||
// Process fetched content
|
||
if (!error.value && contentData.value) {
|
||
for (const file of contentData.value) {
|
||
if (file.type === "intro") {
|
||
introduction.value = file;
|
||
} else if (file.type === "article") {
|
||
const article = {
|
||
id: file.id,
|
||
title: file.title,
|
||
description: file.description,
|
||
likes: file.likes || 0,
|
||
dislikes: file.dislikes || 0,
|
||
comments: file.comments || [],
|
||
};
|
||
|
||
// Add the article locally
|
||
articles.value.push(article);
|
||
|
||
await $fetch('/api/articles', {
|
||
method: 'POST',
|
||
body: {
|
||
id: article.id,
|
||
title: article.title,
|
||
description: article.description,
|
||
likes: article.likes,
|
||
dislikes: article.dislikes
|
||
},
|
||
});
|
||
} else if (file.type === "summary") {
|
||
summary.value = file;
|
||
}
|
||
}
|
||
}
|
||
// Split articles into left and right columns
|
||
// leftColumnArticles.value = articles.value.filter((_, index) => index % 2 === 0);
|
||
// rightColumnArticles.value = articles.value.filter((_, index) => index % 2 !== 0);
|
||
// Split articles into two columns (assuming a 50% split)
|
||
const midIndex = Math.ceil(articles.value.length / 2);
|
||
leftColumnArticles.value = articles.value.slice(0, midIndex);
|
||
rightColumnArticles.value = articles.value.slice(midIndex);
|
||
// Initialize newComments object based on article IDs
|
||
articles.value.forEach(article => {
|
||
newComments.value[article.id] = '';
|
||
});
|
||
}
|
||
catch (err) {
|
||
console.error('Error fetching content:', err);
|
||
} finally {
|
||
isLoading.value = false;
|
||
}
|
||
};
|
||
|
||
onMounted(async () => {
|
||
await fetchContent()
|
||
await fetchArticlesData(articles);
|
||
await fetchCommentsData(articles);
|
||
await fetchSignatures(signatures);
|
||
});
|
||
|
||
const likeArticle = (articleId) => {
|
||
updateLikeDislike({
|
||
type: 'article',
|
||
action: 'like',
|
||
id: articleId,
|
||
articles,
|
||
});
|
||
};
|
||
|
||
const dislikeArticle = (articleId) => {
|
||
updateLikeDislike({
|
||
type: 'article',
|
||
action: 'dislike',
|
||
id: articleId,
|
||
articles,
|
||
});
|
||
};
|
||
|
||
const likeComment = (articleId, commentId) => {
|
||
updateLikeDislike({
|
||
type: 'comment',
|
||
action: 'like',
|
||
id: commentId,
|
||
articleId,
|
||
articles,
|
||
});
|
||
};
|
||
|
||
const dislikeComment = (articleId, commentId) => {
|
||
updateLikeDislike({
|
||
type: 'comment',
|
||
action: 'dislike',
|
||
id: commentId,
|
||
articleId,
|
||
articles,
|
||
});
|
||
};
|
||
|
||
const addComment = async (articleId) => {
|
||
const commentText = newComments.value[articleId]?.trim();
|
||
if (!commentText) return;
|
||
|
||
try {
|
||
const response = await $fetch('/api/comments', {
|
||
method: 'POST',
|
||
body: {
|
||
content: commentText,
|
||
articleId,
|
||
likes: 0,
|
||
dislikes: 0,
|
||
},
|
||
});
|
||
|
||
if (response.success) {
|
||
const article = articles.value.find((a) => a.id === articleId);
|
||
if (article) {
|
||
article.comments.push({
|
||
id: response.data.id,
|
||
content: commentText,
|
||
likes: 0,
|
||
dislikes: 0,
|
||
});
|
||
}
|
||
|
||
// Reset the input field for the current article
|
||
newComments.value[articleId] = '';
|
||
|
||
// Display success overlay
|
||
showOverlay(
|
||
{
|
||
icon: 'mdi-check-circle',
|
||
message: `Merci pour votre suggestion. Veuillez la consulter tout en descendant vers le bas de la page.`,
|
||
buttonText: 'Continuer',
|
||
iconColor: 'success',
|
||
},
|
||
overlay
|
||
);
|
||
console.log('Comment added successfully');
|
||
} else {
|
||
console.error('Failed to add comment:', response.message);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error in addComment:', error);
|
||
}
|
||
};
|
||
|
||
const submitSignature = async () => {
|
||
if (isFormValid.value) {
|
||
const signatureData = {
|
||
name: name.value,
|
||
email: email.value,
|
||
comment: comment.value,
|
||
};
|
||
signatures.value.push({ ...signatureData });
|
||
try {
|
||
await $fetch('/api/charte', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
body: JSON.stringify(signatureData)
|
||
})
|
||
// Display success overlay using the reusable function
|
||
showOverlay(
|
||
{
|
||
icon: 'mdi-check-circle',
|
||
message: `Merci pour votre signature ${name.value}`,
|
||
buttonText: 'Continuer',
|
||
iconColor: 'success',
|
||
},
|
||
overlay
|
||
)
|
||
name.value = '';
|
||
email.value = '';
|
||
comment.value = '';
|
||
console.log('Charte is submitted successfully');
|
||
} catch (error) {
|
||
console.error('Error in submit signature:', error);
|
||
}
|
||
} return;
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.video-section {
|
||
position: relative;
|
||
width: 100%;
|
||
height: 50vh;
|
||
overflow: hidden;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.video-background {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
}
|
||
|
||
h1 {
|
||
font-weight: bold;
|
||
font-family: "Times New Roman", Times, serif;
|
||
}
|
||
|
||
p {
|
||
font-family: "Times New Roman", Times, serif;
|
||
}
|
||
|
||
.v-expansion-panel-title {
|
||
font-family: Georgia;
|
||
}
|
||
|
||
.v-card {
|
||
font-family: Georgia;
|
||
}
|
||
|
||
.overlay-msg {
|
||
font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
|
||
}
|
||
|
||
.custom-btn {
|
||
font-size: 12px;
|
||
font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif;
|
||
padding: 4px 8px;
|
||
min-height: 24px;
|
||
min-width: 40px;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.v-icon:hover {
|
||
opacity: 0.5;
|
||
}
|
||
</style>
|