Files
XIP/frontend/src/views/HomePage.vue
Kerboul cfa2eadec9
Some checks failed
Deploy XIP / deploy (push) Failing after 37s
feat: conformite enonce - explorer, favoris, stats perso, tests, slots
Fonctionnel
- Backend messages : GET /api/messages/:id (detail) + recherche (q),
  pagination par curseur (before/limit) avec enveloppe { items, nextCursor,
  hasMore } ; le flux temps reel garde l'ancien format quand aucun parametre.
- Explorer (/explorer) : catalogue distant, recherche debouncee + annulable
  (AbortController), filtre, defilement infini, etat garde (keep-alive).
- Details par id : /message/:id et /shop/p/:id (consomment route.params).
- Favoris (/favoris) : liste perso persistee en localStorage, notation
  (note/rating/statut) via modale, refletee partout (bouton favori).
- Mes stats (/mes-stats) : agregats derives des favoris (note moyenne, top
  pays/auteurs, statuts), auto-mis a jour, route gardee si liste vide.
- Routeur : pages secondaires en lazy-load + repli, garde beforeEnter.

Technique
- Slots : PrefSection (slot defaut + slot nomme) enveloppe les 5 sections
  "Mes Persos" ; Modal (Teleport + slots).
- v-model custom : SearchBox (defineModel + debounce).
- Directive custom : v-click-outside.
- Tests Vitest : 25 tests (etat, fonctions, composants), ~86% du code metier.
- Retrait d'Ionic (inutilise). Script typecheck backend ; tsconfig @types/bun.
- Correctif type : garde stockLimit nullable dans l'achat (catalog.ts).
- README complet (URL, stack, run, tests, secrets, deploiement, mention IA).
2026-05-31 23:59:34 +02:00

104 lines
3.1 KiB
Vue

<template>
<div class="xip-app" :data-theme="theme">
<!-- Bandeau de stats temps réel, toujours visible -->
<StatsTicker :stats="stats" :connected="connected" />
<div class="xip-root">
<!-- Zone chat centrale -->
<div class="xip-center" :style="chatBgStyle">
<ChatHeader :connected-count="stats?.connectedTabs ?? 0" />
<MessageList :messages="messages" :hide-ads="!!myPerks.noads" :my-ip="myIp" @reply="startReply" />
<!-- Bannière de réponse -->
<div v-if="replyingTo" class="reply-banner">
<span class="reply-banner-txt">
En réponse à <span class="reply-ip">{{ replyingTo.authorIp }}</span>
</span>
<button class="reply-cancel" @click="cancelReply" type="button"></button>
</div>
<!-- Composer (texte / riche / pièces jointes / envoi) -->
<ChatComposer :replying-to="replyingTo" @clear-reply="cancelReply" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import ChatHeader from '@/components/ChatHeader.vue';
import MessageList from '@/components/MessageList.vue';
import ChatComposer from '@/components/ChatComposer.vue';
import StatsTicker from '@/components/StatsTicker.vue';
import { useMessages, useMyPerks } from '@/composables/useMessages';
import { provideTheme } from '@/composables/useTheme';
import { useCustomStyles } from '@/composables/useCustomStyles';
const { theme } = provideTheme();
const { messages, stats, connected, myIp } = useMessages();
const { myPerks } = useMyPerks();
const { prefs: stylePrefs } = useCustomStyles();
const chatBgStyle = computed(() => {
if (!stylePrefs.chatBgUrl) return {};
return {
backgroundImage: `url(${stylePrefs.chatBgUrl})`,
backgroundSize: 'cover',
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat',
};
});
// ── Réponse (la bannière vit ici ; le composer envoie avec parentId) ──
const replyingTo = ref<{ id: string; authorIp: string } | null>(null);
function startReply(payload: { id: string; authorIp: string }): void {
replyingTo.value = payload;
}
function cancelReply(): void {
replyingTo.value = null;
}
</script>
<style scoped>
.xip-app {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
background: var(--xip-app-bg);
overflow: hidden;
}
.xip-root {
flex: 1;
min-height: 0;
display: flex;
overflow: hidden;
}
.xip-center {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
background: var(--xip-bg);
overflow: hidden;
}
/* ── Bannière de réponse ── */
.reply-banner {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
background: #0c1622;
border-top: 1px solid #16324a;
padding: 6px 20px;
}
.reply-banner-txt { font-family: Arial, sans-serif; font-size: 11px; color: #6688aa; }
.reply-ip { font-family: 'Courier New', monospace; color: #00ccff; font-weight: bold; }
.reply-cancel { background: none; border: none; color: #557; cursor: pointer; font-size: 13px; }
.reply-cancel:hover { color: #aac; }
</style>