feat: update styles and enhance pet purchase flow in marketplace components
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/** Couleurs assignées de façon déterministe à chaque adresse IP */
|
||||
const PALETTE = ['#666688', '#00ddff', '#ff00cc', '#00ee77', '#ff8844'] as const;
|
||||
const PALETTE = ['#7777aa', '#4499bb', '#aa4499', '#338866', '#aa6633'] as const;
|
||||
|
||||
export function getIpColor(ip: string): string {
|
||||
// djb2 hash
|
||||
@@ -11,7 +11,7 @@ export function getIpColor(ip: string): string {
|
||||
}
|
||||
|
||||
export function getIpGlow(color: string): string {
|
||||
return color === '#666688' ? 'none' : `0 0 8px ${color}80`;
|
||||
return 'none';
|
||||
}
|
||||
|
||||
/** Gold skin (Style Doré) — overrides the djb2 palette for owners. */
|
||||
@@ -28,6 +28,5 @@ export function getIpColorWithPerks(ip: string, perks?: PerkLike | null): string
|
||||
}
|
||||
|
||||
export function getIpGlowWithPerks(ip: string, perks?: PerkLike | null): string {
|
||||
if (perks?.skin === 'gold') return `0 0 10px ${GOLD}cc`;
|
||||
return getIpGlow(getIpColor(ip));
|
||||
return 'none';
|
||||
}
|
||||
|
||||
@@ -39,6 +39,37 @@ export interface Message extends Reply {
|
||||
|
||||
const API_URL = import.meta.env.VITE_API_URL ?? 'http://localhost:3000';
|
||||
|
||||
/** Refresh the viewer's own perks from the server (callable from anywhere). */
|
||||
export async function refreshMyPerks(): Promise<void> {
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/shop/me`);
|
||||
if (!res.ok) return;
|
||||
const { entitlements } = (await res.json()) as {
|
||||
entitlements: { kind: string; metaJson?: string | null }[];
|
||||
};
|
||||
const p: Perks = {};
|
||||
const pets: { char: string; position: 'left' | 'right' | 'both' }[] = [];
|
||||
for (const e of entitlements) {
|
||||
let meta: any = {};
|
||||
try { meta = e.metaJson ? JSON.parse(e.metaJson) : {}; } catch { /* */ }
|
||||
if (e.kind === 'noads') { p.noads = true; if (meta.plan === 'annual') p.badge = true; }
|
||||
if (e.kind === 'style-dore') p.skin = 'gold';
|
||||
if (e.kind === 'pet' && meta.char) pets.push({ char: meta.char, position: meta.position ?? 'left' });
|
||||
if (e.kind === 'element-skin') p.elementSkin = true;
|
||||
if (e.kind === 'rich-htmlcss') p.richHtmlcss = true;
|
||||
if (e.kind === 'rich-js') p.richJs = true;
|
||||
if (e.kind === 'no-file-limit') p.noFileLimit = true;
|
||||
if (e.kind === 'audio-alert') p.audioAlert = true;
|
||||
}
|
||||
if (pets.length) p.pets = pets;
|
||||
myPerks.value = p;
|
||||
const { ip } = useWallet();
|
||||
if (ip.value) setPerks(ip.value, p);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
export function useMessages() {
|
||||
const messages = ref<Message[]>([]);
|
||||
const loading = ref(false);
|
||||
@@ -102,32 +133,7 @@ export function useMessages() {
|
||||
// myPerks is module-level; this ref is the same reference.
|
||||
|
||||
async function fetchMyPerks(): Promise<void> {
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/shop/me`);
|
||||
if (!res.ok) return;
|
||||
const { entitlements } = (await res.json()) as {
|
||||
entitlements: { kind: string; metaJson?: string | null }[];
|
||||
};
|
||||
const p: Perks = {};
|
||||
const pets: { char: string; position: 'left' | 'right' | 'both' }[] = [];
|
||||
for (const e of entitlements) {
|
||||
let meta: any = {};
|
||||
try { meta = e.metaJson ? JSON.parse(e.metaJson) : {}; } catch { /* */ }
|
||||
if (e.kind === 'noads') { p.noads = true; if (meta.plan === 'annual') p.badge = true; }
|
||||
if (e.kind === 'style-dore') p.skin = 'gold';
|
||||
if (e.kind === 'pet' && meta.char) pets.push({ char: meta.char, position: meta.position ?? 'left' });
|
||||
if (e.kind === 'element-skin') p.elementSkin = true;
|
||||
if (e.kind === 'rich-htmlcss') p.richHtmlcss = true;
|
||||
if (e.kind === 'rich-js') p.richJs = true;
|
||||
if (e.kind === 'no-file-limit') p.noFileLimit = true;
|
||||
if (e.kind === 'audio-alert') p.audioAlert = true;
|
||||
}
|
||||
if (pets.length) p.pets = pets.slice(0, 3);
|
||||
myPerks.value = p;
|
||||
if (myIp.value) setPerks(myIp.value, p);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return refreshMyPerks();
|
||||
}
|
||||
|
||||
const { stats, connected, sendTyping } = useRealtime({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ref } from 'vue';
|
||||
import { useWallet } from './useWallet';
|
||||
import { refreshMyPerks } from './useMessages';
|
||||
|
||||
/** Marketplace client: catalogue, my entitlements, purchase flow. */
|
||||
|
||||
@@ -96,8 +97,8 @@ export function useShop() {
|
||||
return false;
|
||||
}
|
||||
lastSuccess.value = `Acheté : ${productId}`;
|
||||
// Refresh wallet + my entitlements (WS also pushes wallet, this is belt-and-braces).
|
||||
await Promise.all([fetchWallet(), fetchMe(), fetchProducts()]);
|
||||
// Refresh wallet + my entitlements + myPerks (WS also pushes wallet, this is belt-and-braces).
|
||||
await Promise.all([fetchWallet(), fetchMe(), fetchProducts(), refreshMyPerks()]);
|
||||
return true;
|
||||
} catch {
|
||||
lastError.value = 'Réseau indisponible';
|
||||
|
||||
Reference in New Issue
Block a user