feat: conformite enonce - explorer, favoris, stats perso, tests, slots
Some checks failed
Deploy XIP / deploy (push) Failing after 37s
Some checks failed
Deploy XIP / deploy (push) Failing after 37s
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).
This commit is contained in:
@@ -1,133 +1,133 @@
|
||||
<!-- En-tête du chat -->
|
||||
<template>
|
||||
<header class="chat-header">
|
||||
<div class="header-left">
|
||||
<span class="xip-title">XIP</span>
|
||||
<span class="chat-label">Chat</span>
|
||||
<span class="online-dot" aria-hidden="true" />
|
||||
<span class="online-count">{{ connectedCount }} connectés</span>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<ThemePicker v-model="theme" />
|
||||
<span v-if="ip" class="me-ip" :title="'Ton pseudo = ton IP'">{{ ip }}</span>
|
||||
<span class="balance" :class="{ 'balance--free': freeMode }" title="Tes crédits XIP">
|
||||
<span class="balance-coin">◈</span>
|
||||
<span class="balance-val">{{ displayBalance() }}</span>
|
||||
<span class="balance-unit">cr</span>
|
||||
</span>
|
||||
<router-link to="/shop" class="shop-link">🛒 Shop</router-link>
|
||||
<span class="channel-badge"># général</span>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useWallet } from '@/composables/useWallet';
|
||||
import { useTheme } from '@/composables/useTheme';
|
||||
import ThemePicker from './ThemePicker.vue';
|
||||
|
||||
defineProps<{ connectedCount: number }>();
|
||||
|
||||
const { ip, freeMode, displayBalance } = useWallet();
|
||||
const { theme } = useTheme();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chat-header {
|
||||
height: 52px;
|
||||
flex-shrink: 0;
|
||||
background: var(--xip-header-bg);
|
||||
border-bottom: 1px solid var(--xip-header-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px 0 20px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.xip-title {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #7ab8cc;
|
||||
}
|
||||
|
||||
.chat-label {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #aaaacc;
|
||||
}
|
||||
|
||||
.online-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #44aa66;
|
||||
}
|
||||
|
||||
.online-count {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 11px;
|
||||
color: #557766;
|
||||
}
|
||||
|
||||
.me-ip {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 11px;
|
||||
color: #5566aa;
|
||||
}
|
||||
|
||||
.balance {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 4px;
|
||||
background: #131322;
|
||||
border: 1px solid #2a2a44;
|
||||
border-radius: 12px;
|
||||
padding: 3px 10px;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
.balance-coin { color: #aa8833; font-size: 11px; }
|
||||
.balance-val { color: #ccaa44; font-size: 13px; font-weight: bold; }
|
||||
.balance-unit { color: #886633; font-size: 9px; }
|
||||
.balance--free .balance-val { color: #44aa77; }
|
||||
.balance--free .balance-coin { color: #44aa77; }
|
||||
|
||||
.shop-link {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #6699aa;
|
||||
text-decoration: none;
|
||||
border: 1px solid #33445566;
|
||||
border-radius: 12px;
|
||||
padding: 4px 12px;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.shop-link:hover {
|
||||
background: #1a2530;
|
||||
}
|
||||
|
||||
.channel-badge {
|
||||
background: #131320;
|
||||
border: 1px solid #222233;
|
||||
border-radius: 12px;
|
||||
padding: 4px 14px;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 10px;
|
||||
color: #5555aa;
|
||||
}
|
||||
</style>
|
||||
<!-- En-tête du chat -->
|
||||
<template>
|
||||
<header class="chat-header">
|
||||
<div class="header-left">
|
||||
<span class="xip-title">XIP</span>
|
||||
<span class="chat-label">Chat</span>
|
||||
<span class="online-dot" aria-hidden="true" />
|
||||
<span class="online-count">{{ connectedCount }} connectés</span>
|
||||
</div>
|
||||
|
||||
<div class="header-right">
|
||||
<ThemePicker v-model="theme" />
|
||||
<span v-if="ip" class="me-ip" :title="'Ton pseudo = ton IP'">{{ ip }}</span>
|
||||
<span class="balance" :class="{ 'balance--free': freeMode }" title="Tes crédits XIP">
|
||||
<span class="balance-coin">◈</span>
|
||||
<span class="balance-val">{{ displayBalance() }}</span>
|
||||
<span class="balance-unit">cr</span>
|
||||
</span>
|
||||
<router-link to="/shop" class="shop-link">🛒 Shop</router-link>
|
||||
<span class="channel-badge"># général</span>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useWallet } from '@/composables/useWallet';
|
||||
import { useTheme } from '@/composables/useTheme';
|
||||
import ThemePicker from './ThemePicker.vue';
|
||||
|
||||
defineProps<{ connectedCount: number }>();
|
||||
|
||||
const { ip, freeMode, displayBalance } = useWallet();
|
||||
const { theme } = useTheme();
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.chat-header {
|
||||
height: 52px;
|
||||
flex-shrink: 0;
|
||||
background: var(--xip-header-bg);
|
||||
border-bottom: 1px solid var(--xip-header-border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 16px 0 20px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.header-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.xip-title {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #7ab8cc;
|
||||
}
|
||||
|
||||
.chat-label {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: #aaaacc;
|
||||
}
|
||||
|
||||
.online-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: #44aa66;
|
||||
}
|
||||
|
||||
.online-count {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 11px;
|
||||
color: #557766;
|
||||
}
|
||||
|
||||
.me-ip {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 11px;
|
||||
color: #5566aa;
|
||||
}
|
||||
|
||||
.balance {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 4px;
|
||||
background: #131322;
|
||||
border: 1px solid #2a2a44;
|
||||
border-radius: 12px;
|
||||
padding: 3px 10px;
|
||||
font-family: 'Courier New', monospace;
|
||||
}
|
||||
.balance-coin { color: #aa8833; font-size: 11px; }
|
||||
.balance-val { color: #ccaa44; font-size: 13px; font-weight: bold; }
|
||||
.balance-unit { color: #886633; font-size: 9px; }
|
||||
.balance--free .balance-val { color: #44aa77; }
|
||||
.balance--free .balance-coin { color: #44aa77; }
|
||||
|
||||
.shop-link {
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
color: #6699aa;
|
||||
text-decoration: none;
|
||||
border: 1px solid #33445566;
|
||||
border-radius: 12px;
|
||||
padding: 4px 12px;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.shop-link:hover {
|
||||
background: #1a2530;
|
||||
}
|
||||
|
||||
.channel-badge {
|
||||
background: #131320;
|
||||
border: 1px solid #222233;
|
||||
border-radius: 12px;
|
||||
padding: 4px 14px;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 10px;
|
||||
color: #5555aa;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user