diff --git a/frontend/src/App.vue b/frontend/src/App.vue index c9462b9..7c2aa3f 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -1,9 +1,3 @@ - - diff --git a/frontend/src/components/AdBand.vue b/frontend/src/components/AdBand.vue new file mode 100644 index 0000000..f540445 --- /dev/null +++ b/frontend/src/components/AdBand.vue @@ -0,0 +1,128 @@ + + + + diff --git a/frontend/src/components/ChatHeader.vue b/frontend/src/components/ChatHeader.vue new file mode 100644 index 0000000..ee498c8 --- /dev/null +++ b/frontend/src/components/ChatHeader.vue @@ -0,0 +1,74 @@ + + + + + + diff --git a/frontend/src/components/InlineCasinoAd.vue b/frontend/src/components/InlineCasinoAd.vue new file mode 100644 index 0000000..6146b49 --- /dev/null +++ b/frontend/src/components/InlineCasinoAd.vue @@ -0,0 +1,135 @@ + + + + diff --git a/frontend/src/components/MenuToggle.vue b/frontend/src/components/MenuToggle.vue new file mode 100644 index 0000000..830b8a5 --- /dev/null +++ b/frontend/src/components/MenuToggle.vue @@ -0,0 +1,49 @@ + + + + + + diff --git a/frontend/src/components/MessageItem.vue b/frontend/src/components/MessageItem.vue new file mode 100644 index 0000000..df57c8e --- /dev/null +++ b/frontend/src/components/MessageItem.vue @@ -0,0 +1,107 @@ + + + + + + diff --git a/frontend/src/components/MessageList.vue b/frontend/src/components/MessageList.vue new file mode 100644 index 0000000..8cb1e26 --- /dev/null +++ b/frontend/src/components/MessageList.vue @@ -0,0 +1,79 @@ + + + + + + diff --git a/frontend/src/components/SendButton.vue b/frontend/src/components/SendButton.vue new file mode 100644 index 0000000..eb16fcf --- /dev/null +++ b/frontend/src/components/SendButton.vue @@ -0,0 +1,51 @@ + + + + + + diff --git a/frontend/src/composables/ipColor.ts b/frontend/src/composables/ipColor.ts new file mode 100644 index 0000000..83c6a33 --- /dev/null +++ b/frontend/src/composables/ipColor.ts @@ -0,0 +1,15 @@ +/** Couleurs assignées de façon déterministe à chaque adresse IP */ +const PALETTE = ['#666688', '#00ddff', '#ff00cc', '#00ee77', '#ff8844'] as const; + +export function getIpColor(ip: string): string { + // djb2 hash + let hash = 5381; + for (let i = 0; i < ip.length; i++) { + hash = ((hash << 5) + hash + ip.charCodeAt(i)) & 0xffffffff; + } + return PALETTE[Math.abs(hash) % PALETTE.length]; +} + +export function getIpGlow(color: string): string { + return color === '#666688' ? 'none' : `0 0 8px ${color}80`; +} diff --git a/frontend/src/composables/useMessages.ts b/frontend/src/composables/useMessages.ts new file mode 100644 index 0000000..a30719c --- /dev/null +++ b/frontend/src/composables/useMessages.ts @@ -0,0 +1,55 @@ +import { ref, onMounted } from 'vue'; + +export interface Reply { + id: string; + content: string; + authorIp: string; + createdAt: string; +} + +export interface Message extends Reply { + parentId: string | null; + replies: Reply[]; +} + +const API_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:3000'; + +export function useMessages() { + const messages = ref([]); + const loading = ref(false); + const sending = ref(false); + + async function fetchMessages(): Promise { + loading.value = true; + try { + const res = await fetch(`${API_URL}/api/messages`); + if (res.ok) { + // L'API renvoie du plus récent au plus ancien ; on inverse pour affichage chronologique + messages.value = ((await res.json()) as Message[]).reverse(); + } + } finally { + loading.value = false; + } + } + + async function postMessage(content: string): Promise { + if (!content.trim()) return false; + sending.value = true; + try { + const res = await fetch(`${API_URL}/api/messages`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ content: content.trim() }), + }); + if (!res.ok) return false; + await fetchMessages(); + return true; + } finally { + sending.value = false; + } + } + + onMounted(fetchMessages); + + return { messages, loading, sending, postMessage }; +} diff --git a/frontend/src/main.ts b/frontend/src/main.ts index c51bda0..f7624f9 100644 --- a/frontend/src/main.ts +++ b/frontend/src/main.ts @@ -1,35 +1,12 @@ -import { createApp } from "vue"; -import { IonicVue } from "@ionic/vue"; -import { createRouter, createWebHistory } from "@ionic/vue-router"; -import type { RouteRecordRaw } from "vue-router"; -import App from "./App.vue"; -import HomePage from "./views/HomePage.vue"; - -/* Ionic core CSS */ -import "@ionic/vue/css/core.css"; -import "@ionic/vue/css/normalize.css"; -import "@ionic/vue/css/structure.css"; -import "@ionic/vue/css/typography.css"; - -/* Optional utilities */ -import "@ionic/vue/css/padding.css"; -import "@ionic/vue/css/float-elements.css"; -import "@ionic/vue/css/text-alignment.css"; -import "@ionic/vue/css/text-transformation.css"; -import "@ionic/vue/css/flex-utils.css"; -import "@ionic/vue/css/display.css"; - -const routes: RouteRecordRaw[] = [ - { path: "/", component: HomePage }, -]; +import { createApp } from 'vue'; +import { createRouter, createWebHistory } from 'vue-router'; +import App from './App.vue'; +import HomePage from './views/HomePage.vue'; +import './style.css'; const router = createRouter({ history: createWebHistory(), - routes, + routes: [{ path: '/', component: HomePage }], }); -const app = createApp(App).use(IonicVue).use(router); - -router.isReady().then(() => { - app.mount("#app"); -}); +createApp(App).use(router).mount('#app'); diff --git a/frontend/src/style.css b/frontend/src/style.css new file mode 100644 index 0000000..713d9ac --- /dev/null +++ b/frontend/src/style.css @@ -0,0 +1,13 @@ +*, *::before, *::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html, +body, +#app { + height: 100%; + overflow: hidden; + background: #080808; +} diff --git a/frontend/src/views/HomePage.vue b/frontend/src/views/HomePage.vue index 899db72..3d8c964 100644 --- a/frontend/src/views/HomePage.vue +++ b/frontend/src/views/HomePage.vue @@ -1,174 +1,96 @@ diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +///