Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions app/app.vue
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
<template>
<UApp :locale="fr">
<HeaderItem />
<NuxtLayout>
<div class="pt-28 px-4">
<NuxtPage />
</div>
</NuxtLayout>
<div class="flex flex-col min-h-screen">
<HeaderItem />
<NuxtLayout>
<div class="pt-28 px-4 flex-1">
<NuxtPage />
</div>
</NuxtLayout>
<FooterItem />
</div>
</UApp>
</template>

<script setup="ts">
import HeaderItem from '~/components/HeaderItem.vue'
import FooterItem from '~/components/FooterItem.vue'
import { fr } from '@nuxt/ui/locale'
</script>
55 changes: 55 additions & 0 deletions app/components/EntitesItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<template>
<NuxtLink
v-for="entite in props.entites"
:key="entite.id"
:to="formateLink(entite.id)"
class="border rounded-lg p-4 hover:shadow-lg hover:bg-white transition flex gap-2"
>
<div v-if="entite.image_url" class="w-20 h-20 rounded-full overflow-hidden shrink-0">
<NuxtImg
format="webp"
provider="cloudinary"
:src="entite.image_url"
:alt="`Image de${entite.nom}`"
class="object-cover w-full h-full"
/>
</div>
<div class="flex w-full flex-col justify-between">
<div class="flex justify-between items-start mb-2">
<h3 class="font-bold text-lg">{{ entite.nom }}</h3>
<span class="text-xs bg-indigo-400 text-white px-2 py-1 rounded">
{{ entite.type }}
</span>
</div>

<div v-if="entite.description">
<p class="text-gray-600 text-sm mb-3">
{{ entite.description?.substring(0, 60) }}...
<a href="#" class="font-bold text-indigo-900 hover:text-indigo-400">Lire la suite</a>
</p>
</div>

<div class="flex flex-wrap gap-1">
<span
v-for="tag in entite.tags"
:key="tag"
class="text-xs bg-cyan-100 text-cyan-800 px-2 py-1 rounded"
>
{{ tag }}
</span>
</div>
</div>
</NuxtLink>
</template>

<script setup lang="ts">
import type { Entite } from '../types/database.types'

const props = defineProps<{
entites: Entite[]
}>()

const formateLink = (id: string) => {
return `/entites/${id}`
}
</script>
24 changes: 24 additions & 0 deletions app/components/FooterItem.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<template>
<footer v-if="data" class="w-full mx-auto bg-white/30 shadow-lg">
<nav class="flex items-center justify-between p-4">
<p class="text-sm text-gray-700">
{{ new Date().getFullYear() }} - Weena et la légende de Noor. Tous droits réservés.
</p>
<div class="flex gap-4">
<a
v-for="(link, index) in data.links"
:key="index"
:href="link.url"
rel="noopener noreferrer"
class="text-sm text-gray-700 hover:text-gray-900 transition-colors duration-200"
>
{{ link.text }}
</a>
</div>
</nav>
</footer>
</template>

<script setup lang="ts">
const { data } = await useAsyncData('footer', () => queryCollection('footer').first())
</script>
4 changes: 4 additions & 0 deletions app/components/HeroItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
>
<div class="absolute inset-0">
<NuxtImg
:provider="providerToUse(feature?.img)"
:src="feature?.img"
:alt="`Image de ${feature?.title}`"
class="w-full h-full object-cover"
Expand Down Expand Up @@ -63,4 +64,7 @@
}
return '#search'
}

//TODO remove this when all images will be on cloudinary
const providerToUse = (img: string) => (img.includes('/images/') ? undefined : 'cloudinary')
</script>
185 changes: 84 additions & 101 deletions app/components/SearchItem.vue
Original file line number Diff line number Diff line change
@@ -1,119 +1,96 @@
<template>
<div id="search" class="pt-30 pb-10 max-w-7xl mx-auto">
<h1 class="font-heading text-5xl mb-6">Encyclopédie</h1>

<div class="mb-6 space-y-4">
<input
v-model="searchValue"
type="text"
placeholder="Rechercher..."
class="border px-4 py-2 rounded w-full"
/>

<div class="flex gap-2">
<UButton
:color="typeFilter === 'tous' ? 'primary' : 'secondary'"
class="text-white px-4 py-2 rounded"
@click="addTypeFilter('tous')"
>
Tous
</UButton>
<UButton
:color="typeFilter === 'personnage' ? 'primary' : 'secondary'"
class="text-white px-4 py-2 rounded"
@click="addTypeFilter('personnage')"
>
Personnages
</UButton>
<UButton
:color="typeFilter === 'lieu' ? 'primary' : 'secondary'"
class="text-white px-4 py-2 rounded"
@click="addTypeFilter('lieu')"
>
Lieux
</UButton>
<UButton
:color="typeFilter === 'creature' ? 'primary' : 'secondary'"
class="text-white px-4 py-2 rounded"
@click="addTypeFilter('creature')"
>
Créatures
</UButton>
<UButton
:color="typeFilter === 'peuple' ? 'primary' : 'secondary'"
class="text-white px-4 py-2 rounded"
@click="addTypeFilter('peuple')"
>
Peuples
</UButton>
<div>
<div id="weekipedia" class="pt-30 mb-10 max-w-7xl mx-auto">
<h1 class="font-heading text-5xl mb-6">Encyclopédie</h1>
<div class="mb-6 space-y-4">
<input
v-model="searchValue"
type="text"
placeholder="Rechercher..."
class="border px-4 py-2 rounded w-full"
/>
<div class="flex gap-2">
<UButton
:color="typeFilter === 'tous' ? 'primary' : 'secondary'"
class="text-white px-4 py-2 rounded"
@click="addTypeFilter('tous')"
>
Tous
</UButton>
<UButton
:color="typeFilter === 'personnage' ? 'primary' : 'secondary'"
class="text-white px-4 py-2 rounded"
@click="addTypeFilter('personnage')"
>
Personnages
</UButton>
<UButton
:color="typeFilter === 'lieu' ? 'primary' : 'secondary'"
class="text-white px-4 py-2 rounded"
@click="addTypeFilter('lieu')"
>
Lieux
</UButton>
<UButton
:color="typeFilter === 'creature' ? 'primary' : 'secondary'"
class="text-white px-4 py-2 rounded"
@click="addTypeFilter('creature')"
>
Créatures
</UButton>
<UButton
:color="typeFilter === 'peuple' ? 'primary' : 'secondary'"
class="text-white px-4 py-2 rounded"
@click="addTypeFilter('peuple')"
>
Peuples
</UButton>
</div>
</div>
</div>

<div v-if="isError" class="border border-red-300 bg-red-50 rounded-lg p-4 mb-6">
<div class="flex items-start gap-3">
<UIcon name="i-heroicons-exclamation-triangle" class="w-6 h-6 text-red-600 shrink-0" />
<div class="flex-1">
<h3 class="font-bold text-red-800">Erreur de chargement</h3>
<p class="text-red-700 text-sm mt-1">{{ errorMessage }}</p>
<div v-if="isError" class="border border-red-300 bg-red-50 rounded-lg p-4 mb-6">
<div class="flex items-start gap-3">
<UIcon name="i-heroicons-exclamation-triangle" class="w-6 h-6 text-red-600 shrink-0" />
<div class="flex-1">
<h3 class="font-bold text-red-800">Erreur de chargement</h3>
<p class="text-red-700 text-sm mt-1">{{ errorMessage }}</p>
</div>
<button
class="bg-red-600 text-white px-3 py-1 rounded text-sm hover:bg-red-700"
@click="entitesStore.resetAndLoad()"
>
Réessayer
</button>
</div>
<button
class="bg-red-600 text-white px-3 py-1 rounded text-sm hover:bg-red-700"
@click="entitesStore.resetAndLoad()"
>
Réessayer
</button>
</div>
</div>

<div v-if="hasNoResults" class="flex flex-col items-center justify-center py-20">
<UIcon name="i-heroicons-magnifying-glass" class="w-16 h-16 text-gray-400 mb-4" />
<h3 class="text-xl font-semibold text-gray-700 mb-2">Aucun résultat</h3>
<p class="text-gray-500">Aucune entité ne correspond à vos critères de recherche.</p>
</div>
<div v-if="hasNoResults" class="flex flex-col items-center justify-center py-20">
<UIcon name="i-heroicons-magnifying-glass" class="w-16 h-16 text-gray-400 mb-4" />
<h3 class="text-xl font-semibold text-gray-700 mb-2">Aucun résultat</h3>
<p class="text-gray-500">Aucune entité ne correspond à vos critères de recherche.</p>
</div>

<div
v-if="entitesFilter.length > 0"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
>
<NuxtLink
v-for="entite in entitesFilter"
:key="entite.id"
:to="formateLink(entite.id)"
class="border rounded-lg p-4 hover:shadow-lg hover:bg-white transition"
<div
v-if="entitesFilter.length > 0"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4"
>
<div class="flex justify-between items-start mb-2">
<h3 class="font-bold text-lg">{{ entite.nom }}</h3>
<span class="text-xs bg-indigo-400 text-white px-2 py-1 rounded">
{{ entite.type }}
</span>
</div>

<p class="text-gray-600 text-sm mb-3">{{ entite.description }}</p>

<div class="flex flex-wrap gap-1">
<span
v-for="tag in entite.tags"
:key="tag"
class="text-xs bg-cyan-100 text-cyan-800 px-2 py-1 rounded"
>
{{ tag }}
</span>
</div>
</NuxtLink>
<EntitesItem :entites="entitesFilter" />
</div>
</div>

<div ref="loadMoreTrigger" class="h-10 flex items-center justify-center">
<div ref="loadMoreTrigger" class="flex items-center justify-center">
<UIcon
v-if="isLoading"
name="i-heroicons-arrow-path"
class="w-8 h-10 animate-spin text-primary"
class="w-8 mb-10 animate-spin text-primary"
/>
</div>
</div>
</template>

<script setup lang="ts">
import { ref, computed, nextTick, onMounted, onUnmounted, watch } from 'vue'
import { useEntitesStore } from '../stores/entites.store'
import EntitesItem from './EntitesItem.vue'
import type { typeFilter } from '../types/database.types'
import { storeToRefs } from 'pinia'

Expand All @@ -133,7 +110,6 @@
let observer: IntersectionObserver | null = null

onMounted(async () => {
entitesStore.resetFilters()
await entitesStore.getEntites()

observer = new IntersectionObserver((entries: IntersectionObserverEntry[]) => {
Expand All @@ -146,24 +122,31 @@
if (loadMoreTrigger.value) {
observer.observe(loadMoreTrigger.value)
}

await nextTick()
if (isVisible.value && entitesStore.hasMore) {
await entitesStore.loadMore()
}
})

onUnmounted(() => {
observer?.disconnect()
})

watch(isVisible, async (visible) => {
if (visible && !isLoading.value) {
if (visible && !isLoading.value && entitesStore.hasMore) {
await entitesStore.loadMore()
}
})

watch(typeFilter, async () => {
await entitesStore.resetAndLoad()
await nextTick()
if (isVisible.value && entitesStore.hasMore) {
await entitesStore.loadMore()
}
})

const formateLink = (id: string) => `/entites/${id}`

const hasNoResults = computed(() => {
return !isLoading.value && !isError.value && entitesFilter.value.length === 0
})
Expand Down
14 changes: 11 additions & 3 deletions app/pages/entites/[id].vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,18 @@
<div class="mx-auto w-[75%] max-w-7xl">
<p v-if="pending">Chargement...</p>
<div v-else-if="entite" class="flex flex-col gap-1.5">
<p>Nom : {{ entite.nom }}</p>
<p>Type : {{ entite.type }}</p>
<h1 class="text-4xl font-heading mb-4">{{ entite.nom }}</h1>
<div v-if="entite.gender === 'Féminin'" class="text-2xl font-bold flex items-center gap-1.5">
<UIcon name="gg:gender-female" />
</div>
<div
v-else-if="entite.gender === 'Masculin'"
class="text-2xl font-bold flex items-center gap-1.5"
>
<UIcon name="gg:gender-male" />
</div>
<p class="capitalize">{{ entite.type }}</p>
<p>Description : {{ entite.description }}</p>
<p v-if="entite.espece">Espèce : {{ entite.espece }}</p>
<p v-if="entite.vivant">Espèce vivante</p>
<p v-if="entite.dangereux">Espèce dangeureuse</p>
<p v-if="entite.hostile">Espèce hostile</p>
Expand Down
Loading