Add WordPress integration as Headless CMS and enhance navigation with dynamic categories
This commit is contained in:
@@ -128,7 +128,7 @@ export default {
|
||||
const isPlaying = ref(false)
|
||||
const currentTime = ref(0)
|
||||
const duration = ref(0)
|
||||
const volume = ref(70)
|
||||
const volume = ref(100)
|
||||
const playbackRate = ref(1)
|
||||
|
||||
const hasAudio = computed(() => !!props.audioSrc)
|
||||
|
||||
@@ -35,18 +35,36 @@
|
||||
<span>Textes</span>
|
||||
</div>
|
||||
</router-link>
|
||||
<template v-if="!useDropdown">
|
||||
<router-link
|
||||
to="/actualites"
|
||||
v-for="category in dynamicCategories"
|
||||
:key="category.id"
|
||||
:to="getCategoryLink(category)"
|
||||
class="nav-link"
|
||||
:class="{ 'active': $route.name === 'News' || $route.name === 'NewsArticle' }"
|
||||
:class="{ 'active': isCategoryActive(category) }"
|
||||
>
|
||||
<div class="flex items-center space-x-2">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z" />
|
||||
</svg>
|
||||
<span>Actualités</span>
|
||||
</div>
|
||||
<span>{{ category.name }}</span>
|
||||
</router-link>
|
||||
</template>
|
||||
<div v-else class="relative group">
|
||||
<button
|
||||
class="nav-link"
|
||||
:class="{ 'active': isBlogActive }"
|
||||
type="button"
|
||||
>
|
||||
Blog
|
||||
</button>
|
||||
<div class="absolute left-0 mt-2 w-56 bg-white border border-gray-200 rounded-lg shadow-lg py-2 hidden group-hover:block">
|
||||
<router-link
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
:to="getCategoryLink(category)"
|
||||
class="dropdown-link"
|
||||
>
|
||||
{{ category.name }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Barre de recherche -->
|
||||
<div class="relative">
|
||||
@@ -83,7 +101,17 @@
|
||||
<div class="flex flex-col space-y-4">
|
||||
<router-link to="/" class="nav-link-mobile">Accueil</router-link>
|
||||
<router-link to="/textes" class="nav-link-mobile">Textes</router-link>
|
||||
<router-link to="/actualites" class="nav-link-mobile">Actualités</router-link>
|
||||
<div v-if="categories.length" class="pt-2 border-t border-gray-200">
|
||||
<div class="text-xs uppercase tracking-wide text-gray-500 mb-2">Blog</div>
|
||||
<router-link
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
:to="getCategoryLink(category)"
|
||||
class="nav-link-mobile"
|
||||
>
|
||||
{{ category.name }}
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="pt-2">
|
||||
<input
|
||||
v-model="searchQuery"
|
||||
@@ -100,15 +128,19 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import { wordpressService } from '../services/wordpressService.js'
|
||||
|
||||
export default {
|
||||
name: 'NavigationBar',
|
||||
setup() {
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const searchQuery = ref('')
|
||||
const mobileMenuOpen = ref(false)
|
||||
const categories = ref([])
|
||||
const categoriesLoading = ref(false)
|
||||
|
||||
const performSearch = () => {
|
||||
if (searchQuery.value.trim()) {
|
||||
@@ -120,9 +152,55 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
const loadCategories = async () => {
|
||||
categoriesLoading.value = true
|
||||
try {
|
||||
categories.value = await wordpressService.getCategories()
|
||||
} catch (error) {
|
||||
console.error('Erreur lors du chargement des catégories:', error)
|
||||
} finally {
|
||||
categoriesLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const dynamicCategories = computed(() => {
|
||||
return categories.value.slice(0, 4)
|
||||
})
|
||||
|
||||
const useDropdown = computed(() => {
|
||||
return categories.value.length > 4
|
||||
})
|
||||
|
||||
const getCategoryLink = (category) => {
|
||||
return category.slug === 'actualites' ? '/actualites' : `/blog/${category.slug}`
|
||||
}
|
||||
|
||||
const isCategoryActive = (category) => {
|
||||
if (category.slug === 'actualites') {
|
||||
return route.name === 'News' || route.name === 'NewsArticle'
|
||||
}
|
||||
|
||||
return route.name === 'BlogCategory' && route.params.slug === category.slug
|
||||
}
|
||||
|
||||
const isBlogActive = computed(() => {
|
||||
return route.name === 'News' || route.name === 'BlogCategory' || route.name === 'NewsArticle'
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
loadCategories()
|
||||
})
|
||||
|
||||
return {
|
||||
searchQuery,
|
||||
mobileMenuOpen,
|
||||
categories,
|
||||
categoriesLoading,
|
||||
dynamicCategories,
|
||||
useDropdown,
|
||||
isBlogActive,
|
||||
getCategoryLink,
|
||||
isCategoryActive,
|
||||
performSearch
|
||||
}
|
||||
}
|
||||
@@ -157,4 +235,16 @@ export default {
|
||||
.nav-link-mobile:hover {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.dropdown-link {
|
||||
display: block;
|
||||
padding: 0.5rem 1rem;
|
||||
color: #374151;
|
||||
transition: color 0.2s, background-color 0.2s;
|
||||
}
|
||||
|
||||
.dropdown-link:hover {
|
||||
color: black;
|
||||
background-color: #f9fafb;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -28,6 +28,11 @@ const routes = [
|
||||
name: 'News',
|
||||
component: News
|
||||
},
|
||||
{
|
||||
path: '/blog/:slug',
|
||||
name: 'BlogCategory',
|
||||
component: News
|
||||
},
|
||||
{
|
||||
path: '/actualite/:id',
|
||||
name: 'NewsArticle',
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* Service pour communiquer avec l'API backend des textes
|
||||
*/
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_TEXTS_API_URL || 'http://localhost:3000/api'
|
||||
const API_BASE_URL = import.meta.env.VITE_TEXTS_API_URL || 'https://patois.lagaudiere.uk/api' || 'http://localhost:3000/api'
|
||||
|
||||
export class TextService {
|
||||
constructor() {
|
||||
|
||||
@@ -5,6 +5,55 @@ const ACTUALITES_CATEGORY_ID = 3
|
||||
* Service pour interagir avec l'API WordPress (Headless CMS)
|
||||
*/
|
||||
export const wordpressService = {
|
||||
/**
|
||||
* Récupère les catégories principales (parent = 0)
|
||||
* @returns {Promise<Array>} - Liste des catégories
|
||||
*/
|
||||
async getCategories() {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE_URL}/categories?per_page=100&parent=0&hide_empty=true&orderby=name&order=asc`
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Erreur HTTP: ${response.status}`)
|
||||
}
|
||||
|
||||
const categories = await response.json()
|
||||
return categories
|
||||
.filter(category => category.slug !== 'uncategorized' && category.name.toLowerCase() !== 'uncategorized')
|
||||
.map(category => this.formatCategory(category))
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération des catégories:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Récupère une catégorie par son slug
|
||||
* @param {string} slug - Slug de la catégorie
|
||||
* @returns {Promise<Object|null>} - Catégorie formatée ou null
|
||||
*/
|
||||
async getCategoryBySlug(slug) {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/categories?slug=${slug}`)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Erreur HTTP: ${response.status}`)
|
||||
}
|
||||
|
||||
const categories = await response.json()
|
||||
if (!categories.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
return this.formatCategory(categories[0])
|
||||
} catch (error) {
|
||||
console.error(`Erreur lors de la récupération de la catégorie ${slug}:`, error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Récupère tous les posts de la catégorie "actualites"
|
||||
* @param {number} perPage - Nombre de posts par page (défaut: 10)
|
||||
@@ -12,9 +61,20 @@ export const wordpressService = {
|
||||
* @returns {Promise<Object>} - { posts: Array, totalPages: number, total: number }
|
||||
*/
|
||||
async getNews(perPage = 10, page = 1) {
|
||||
return this.getPostsByCategory(ACTUALITES_CATEGORY_ID, perPage, page)
|
||||
},
|
||||
|
||||
/**
|
||||
* Récupère les posts d'une catégorie
|
||||
* @param {number} categoryId - ID de la catégorie
|
||||
* @param {number} perPage - Nombre de posts par page (défaut: 10)
|
||||
* @param {number} page - Numéro de page (défaut: 1)
|
||||
* @returns {Promise<Object>} - { posts: Array, totalPages: number, total: number }
|
||||
*/
|
||||
async getPostsByCategory(categoryId, perPage = 10, page = 1) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`${API_BASE_URL}/posts?categories=${ACTUALITES_CATEGORY_ID}&per_page=${perPage}&page=${page}&_embed`
|
||||
`${API_BASE_URL}/posts?categories=${categoryId}&per_page=${perPage}&page=${page}&_embed`
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
@@ -31,7 +91,7 @@ export const wordpressService = {
|
||||
total
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Erreur lors de la récupération des actualités:', error)
|
||||
console.error('Erreur lors de la récupération des posts:', error)
|
||||
throw error
|
||||
}
|
||||
},
|
||||
@@ -42,6 +102,15 @@ export const wordpressService = {
|
||||
* @returns {Promise<Object>} - Post formaté
|
||||
*/
|
||||
async getNewsById(id) {
|
||||
return this.getPostById(id)
|
||||
},
|
||||
|
||||
/**
|
||||
* Récupère un post spécifique par son ID
|
||||
* @param {number} id - ID du post
|
||||
* @returns {Promise<Object>} - Post formaté
|
||||
*/
|
||||
async getPostById(id) {
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/posts/${id}?_embed`)
|
||||
|
||||
@@ -57,6 +126,22 @@ export const wordpressService = {
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Formate une catégorie WordPress
|
||||
* @param {Object} category - Catégorie brute de l'API WordPress
|
||||
* @returns {Object} - Catégorie formatée
|
||||
*/
|
||||
formatCategory(category) {
|
||||
return {
|
||||
id: category.id,
|
||||
name: category.name,
|
||||
slug: category.slug,
|
||||
description: category.description,
|
||||
count: category.count,
|
||||
parent: category.parent
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Formate un post WordPress pour l'utilisation dans l'application
|
||||
* @param {Object} post - Post brut de l'API WordPress
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
<div class="max-w-6xl mx-auto px-4">
|
||||
<!-- En-tête -->
|
||||
<div class="mb-8">
|
||||
<h1 class="text-4xl font-bold text-black mb-4">Actualités</h1>
|
||||
<h1 class="text-4xl font-bold text-black mb-4">
|
||||
{{ categoryTitle }}
|
||||
</h1>
|
||||
<p class="text-gray-600">
|
||||
Découvrez les dernières nouvelles et événements
|
||||
{{ categoryDescription }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -25,7 +27,7 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Liste des actualités -->
|
||||
<!-- Liste des articles -->
|
||||
<div v-else-if="newsList.length > 0" class="space-y-6">
|
||||
<article
|
||||
v-for="news in newsList"
|
||||
@@ -136,12 +138,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Aucune actualité -->
|
||||
<!-- Aucun article -->
|
||||
<div v-else class="text-center py-20">
|
||||
<svg class="w-16 h-16 mx-auto text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 20H5a2 2 0 01-2-2V6a2 2 0 012-2h10a2 2 0 012 2v1m2 13a2 2 0 01-2-2V7m2 13a2 2 0 002-2V9a2 2 0 00-2-2h-2m-4-3H9M7 16h6M7 8h6v4H7V8z" />
|
||||
</svg>
|
||||
<p class="text-gray-600 text-lg">Aucune actualité disponible pour le moment</p>
|
||||
<p class="text-gray-600 text-lg">Aucun article disponible pour le moment</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -162,20 +164,49 @@ const currentPage = ref(1)
|
||||
const totalPages = ref(1)
|
||||
const total = ref(0)
|
||||
const perPage = 10
|
||||
const category = ref(null)
|
||||
|
||||
// Charger les actualités
|
||||
const categorySlug = computed(() => {
|
||||
return route.params.slug || 'actualites'
|
||||
})
|
||||
|
||||
const categoryTitle = computed(() => {
|
||||
return category.value?.name || 'Actualités'
|
||||
})
|
||||
|
||||
const categoryDescription = computed(() => {
|
||||
return category.value?.description || 'Découvrez les derniers articles de cette catégorie'
|
||||
})
|
||||
|
||||
// Charger la catégorie
|
||||
const loadCategory = async () => {
|
||||
category.value = await wordpressService.getCategoryBySlug(categorySlug.value)
|
||||
if (!category.value) {
|
||||
throw new Error('Catégorie introuvable')
|
||||
}
|
||||
}
|
||||
|
||||
// Charger les articles
|
||||
const loadNews = async () => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const result = await wordpressService.getNews(perPage, currentPage.value)
|
||||
if (!category.value) {
|
||||
await loadCategory()
|
||||
}
|
||||
|
||||
const result = await wordpressService.getPostsByCategory(
|
||||
category.value.id,
|
||||
perPage,
|
||||
currentPage.value
|
||||
)
|
||||
newsList.value = result.posts
|
||||
totalPages.value = result.totalPages
|
||||
total.value = result.total
|
||||
} catch (err) {
|
||||
error.value = 'Impossible de charger les actualités. Veuillez réessayer plus tard.'
|
||||
console.error('Erreur lors du chargement des actualités:', err)
|
||||
error.value = 'Impossible de charger les articles. Veuillez réessayer plus tard.'
|
||||
console.error('Erreur lors du chargement des articles:', err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -183,7 +214,11 @@ const loadNews = async () => {
|
||||
|
||||
// Navigation vers une actualité
|
||||
const goToNews = (id) => {
|
||||
router.push({ name: 'NewsArticle', params: { id } })
|
||||
router.push({
|
||||
name: 'NewsArticle',
|
||||
params: { id },
|
||||
query: { category: category.value?.slug || categorySlug.value }
|
||||
})
|
||||
}
|
||||
|
||||
// Navigation entre les pages
|
||||
@@ -226,6 +261,13 @@ onMounted(() => {
|
||||
watch(currentPage, () => {
|
||||
loadNews()
|
||||
})
|
||||
|
||||
// Recharger lors du changement de catégorie
|
||||
watch(categorySlug, () => {
|
||||
category.value = null
|
||||
currentPage.value = 1
|
||||
loadNews()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -53,8 +53,8 @@
|
||||
</svg>
|
||||
</li>
|
||||
<li>
|
||||
<router-link to="/actualites" class="hover:text-black transition-colors">
|
||||
Actualités
|
||||
<router-link :to="categoryLink" class="hover:text-black transition-colors">
|
||||
{{ categoryTitle }}
|
||||
</router-link>
|
||||
</li>
|
||||
<li>
|
||||
@@ -131,13 +131,13 @@
|
||||
<!-- Navigation -->
|
||||
<div class="mt-12 pt-8 border-t border-gray-200">
|
||||
<router-link
|
||||
to="/actualites"
|
||||
:to="categoryLink"
|
||||
class="inline-flex items-center text-black hover:text-gray-700 font-medium transition-colors"
|
||||
>
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
||||
</svg>
|
||||
Retour aux actualités
|
||||
Retour aux articles
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
@@ -146,7 +146,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ref, onMounted, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { wordpressService } from '../services/wordpressService.js'
|
||||
|
||||
@@ -155,6 +155,21 @@ const route = useRoute()
|
||||
const article = ref(null)
|
||||
const loading = ref(true)
|
||||
const error = ref(null)
|
||||
const category = ref(null)
|
||||
|
||||
const categorySlug = computed(() => {
|
||||
return route.query.category || 'actualites'
|
||||
})
|
||||
|
||||
const categoryTitle = computed(() => {
|
||||
return category.value?.name || 'Actualités'
|
||||
})
|
||||
|
||||
const categoryLink = computed(() => {
|
||||
return categorySlug.value === 'actualites'
|
||||
? '/actualites'
|
||||
: `/blog/${categorySlug.value}`
|
||||
})
|
||||
|
||||
// Charger l'article
|
||||
const loadArticle = async () => {
|
||||
@@ -167,7 +182,9 @@ const loadArticle = async () => {
|
||||
throw new Error('ID d\'article invalide')
|
||||
}
|
||||
|
||||
article.value = await wordpressService.getNewsById(id)
|
||||
article.value = await wordpressService.getPostById(id)
|
||||
|
||||
category.value = await wordpressService.getCategoryBySlug(categorySlug.value)
|
||||
|
||||
// Mettre à jour le titre de la page
|
||||
if (article.value) {
|
||||
|
||||
Reference in New Issue
Block a user