Files
DAV/pages/index.vue
Do-raa 0e23d7f455
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
feat: build main page for DAV project and integrate database with Supabase and Prisma
2024-12-20 11:38:11 +01:00

455 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<v-container fluid class="lg:mx-12 mx-2">
<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">
<h1 class="my-8 lg:text-xl text-l text-black">{{ introduction.title }}</h1>
<p class="text-xs text-black mb-8 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 deux 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 deux 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"> <!-- Make it take more space on large screens -->
<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="d-flex align-center mb-2">
<span class="mr-2">
<v-icon class="m-1 text-primary hover:text-green-500 text-xs"
@click.stop="likeArticle(article.id)">
mdi-thumb-up
</v-icon>
<p class="ml-2 lg:ml-0 text-primary">{{ article.likes }}</p>
</span>
<span class="mr-3">
<v-icon class="m-1 text-tertiary hover:text-red-500 text-xs"
@click.stop="dislikeArticle(article.id)">
mdi-thumb-down
</v-icon>
<p class="ml-2 lg:ml-0 text-tertiary">{{ article.dislikes }}</p>
</span>
<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-expand-transition>
<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="primary"
size="x-small">
<div class="mb-0">
<p class="text-sm text-black">{{ comment }}</p>
</div>
</v-timeline-item>
<v-timeline-item v-else dot-color="secondary" size="x-small">
<p class="text-sm text-black">Aucune suggestion pour cet article</p>
</v-timeline-item>
</v-timeline>
</v-expand-transition>
</v-expansion-panel-text>
</v-expansion-panel>
</v-expansion-panels>
</v-col>
</v-row>
<!-- Signature Section -->
<v-row class="justify-center my-8">
<v-col cols="12" md="8" lg="6">
<v-card elevation="3" class="pa-6 bg-gray-100" color="secondary">
<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 complet" 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 :disabled="!isFormValid" 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>
<!-- Success Overlay (Dynamic Content) -->
<v-overlay v-model="overlay" 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="overlayIconColor" size="48" class="mb-4">{{ overlayIcon }}</v-icon>
<p class="overlay-msg text-sm font-semibold text-black text-uppercase">{{ overlayMessage }}</p>
<VBtnValid class="mt-6" @click="overlay = false">
{{ overlayButtonText }}
</VBtnValid>
</v-card>
</v-overlay>
</v-container>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useAsyncData } from 'nuxt/app';
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 overlay = ref(false);
const overlayIcon = ref('mdi-check-circle'); // Default icon
const overlayMessage = ref('Votre suggestion à été ajoutée avec succès'); // Default message
const overlayButtonText = ref('Continuer'); // Default button text
const overlayIconColor = ref('success');
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 { 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") {
try {
// Add default values (likes, dislikes, comments) if missing
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,
comments: article.comments,
},
});
} catch (err) {
console.error('Error creating article:', err);
}
} else if (file.type === "summary") {
summary.value = file;
}
}
// Initialize newComments object based on article IDs
articles.value.forEach(article => {
newComments.value[article.id] = '';
});
// 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);
}
onMounted(async () => {
try {
const response = await $fetch(`/api/articles`, {
method: 'GET',
});
if (response.success && response.data) {
// Update the articles array by merging data from the response
articles.value = articles.value.map((article) => {
const updatedArticle = response.data.find((data) => data.id === article.id);
return updatedArticle
? {
...article,
likes: updatedArticle.likes,
dislikes: updatedArticle.dislikes,
comments: updatedArticle.comments,
}
: article;
});
} else {
console.error('Failed to fetch articles:', response.message || 'Unknown error');
}
} catch (error) {
console.error('Error fetching articles:', error);
}
});
const likeArticle = async (articleId) => {
const article = articles.value.find((a) => a.id === articleId);
if (!article) return;
article.likes += 1;
try {
await $fetch(`/api/articles/${articleId}`, {
method: 'PUT',
body: { likes: article.likes },
});
console.log('Article updated successfully');
} catch (error) {
console.error('Error in likeArticle:', error);
}
};;
const dislikeArticle = async (articleId) => {
const article = articles.value.find((a) => a.id === articleId);
if (!article) return;
article.dislikes += 1;
try {
await $fetch(`/api/articles/${articleId}`, {
method: 'PUT',
body: { dislikes: article.dislikes },
});
console.log('Article updated successfully');
} catch (error) {
console.error('Error in dislikeArticle:', error);
}
};
const addComment = async (articleId) => {
const commentText = newComments.value[articleId]?.trim();
if (!commentText) return;
const article = articles.value.find((a) => a.id === articleId);
article.comments.push(commentText);
newComments.value[articleId] = '';
overlayIcon.value = 'mdi-check-circle';
overlayMessage.value = `Merci pour votre suggestion. Veuillez la consulter tout en descendant vers le bas de la page`;
overlayButtonText.value = 'Continuer';
overlayIconColor.value = 'success';
overlay.value = true;
try {
await $fetch(`/api/articles/${article.id}`, {
method: 'PUT',
body: { comments: article.comments },
});
console.log('Comment added successfully');
} catch (error) {
console.error('Error in addComment:', error);
}
};
const submitSignature = async () => {
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)
})
console.log('Charte is submitted successfully');
} catch (error) {
console.error('Error in submit signature:', error);
}
overlayIcon.value = 'mdi-check-circle';
overlayMessage.value = `Merci pour votre signature ${name.value}`
overlayButtonText.value = 'Continuer';
overlayIconColor.value = 'success';
overlay.value = true;
name.value = '';
email.value = '';
comment.value = '';
};
</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;
}
</style>