From dbaa61318e2603ee3dd5e56b2f3283c697ff55c0 Mon Sep 17 00:00:00 2001 From: AlexBesios Date: Tue, 7 Apr 2026 14:29:06 +0300 Subject: [PATCH] - Update the newbox to fetch news from the api - Fix the comment recoms and the minor issues --- resources/lang/en.json | 7 +++ src/client/Api.ts | 25 ++++++++++ src/client/components/NewsBox.ts | 62 +++++++++++++++++-------- src/client/components/PlayPage.ts | 4 +- src/core/ApiSchemas.ts | 9 ++++ tests/client/components/NewsBox.test.ts | 21 +++++---- 6 files changed, 96 insertions(+), 32 deletions(-) diff --git a/resources/lang/en.json b/resources/lang/en.json index bf01844e0..a2b87cbae 100644 --- a/resources/lang/en.json +++ b/resources/lang/en.json @@ -1043,5 +1043,12 @@ "description": "(ALPHA)", "login_required": "Login to play ranked!", "must_login": "You must be logged in to play ranked matchmaking." + }, + "news_box": { + "tournament": "TOURNAMENT", + "tutorial": "TUTORIAL", + "news": "NEWS", + "dismiss": "Dismiss", + "go_to_item": "Go to item {num}" } } diff --git a/src/client/Api.ts b/src/client/Api.ts index c28f59b20..f48f719ca 100644 --- a/src/client/Api.ts +++ b/src/client/Api.ts @@ -1,7 +1,10 @@ +import newsItemsFallback from "resources/news.json"; import { z } from "zod"; +import type { NewsItem } from "../core/ApiSchemas"; import { ClanLeaderboardResponse, ClanLeaderboardResponseSchema, + NewsItemSchema, PlayerProfile, PlayerProfileSchema, RankedLeaderboardResponse, @@ -263,3 +266,25 @@ export async function fetchPlayerLeaderboard( return false; } } + +export async function getNews(): Promise { + try { + const res = await fetch(`${getApiBase()}/news`, { + headers: { Accept: "application/json" }, + }); + if (res.status !== 200) { + console.warn("getNews: unexpected status", res.status); + return newsItemsFallback as NewsItem[]; + } + const json = await res.json(); + const parsed = z.array(NewsItemSchema).safeParse(json); + if (!parsed.success) { + console.warn("getNews: Zod validation failed", parsed.error); + return newsItemsFallback as NewsItem[]; + } + return parsed.data; + } catch (err) { + console.warn("getNews: request failed, using fallback", err); + return newsItemsFallback as NewsItem[]; + } +} diff --git a/src/client/components/NewsBox.ts b/src/client/components/NewsBox.ts index 7a9b76b05..41b1b08ab 100644 --- a/src/client/components/NewsBox.ts +++ b/src/client/components/NewsBox.ts @@ -1,14 +1,10 @@ import { LitElement, html, nothing } from "lit"; import { customElement, state } from "lit/decorators.js"; -import newsItems from "resources/news.json"; +import type { NewsItem } from "../../core/ApiSchemas"; +import { getNews } from "../Api"; +import { translateText } from "../Utils"; -export interface NewsItem { - id: string; - title: string; - description: string; - url?: string; - type: "tournament" | "tutorial" | "announcement"; -} +export type { NewsItem }; const DISMISSED_NEWS_KEY = "dismissedNewsItems"; const CYCLE_INTERVAL_MS = 5000; @@ -24,18 +20,22 @@ function getDismissedIds(): Set { } function saveDismissedIds(ids: Set): void { - localStorage.setItem(DISMISSED_NEWS_KEY, JSON.stringify([...ids])); + try { + localStorage.setItem(DISMISSED_NEWS_KEY, JSON.stringify([...ids])); + } catch { + // Ignore storage errors — dismiss still works for this session + } } -export function getVisibleNewsItems(): NewsItem[] { +export function getVisibleNewsItems(items: NewsItem[]): NewsItem[] { const dismissed = getDismissedIds(); - return (newsItems as NewsItem[]).filter((item) => !dismissed.has(item.id)); + return items.filter((item) => !dismissed.has(item.id)); } -const typeLabels: Record = { - tournament: "TOURNAMENT", - tutorial: "TUTORIAL", - announcement: "NEWS", +const typeLabelKeys: Record = { + tournament: "news_box.tournament", + tutorial: "news_box.tutorial", + announcement: "news_box.news", }; const typeLabelColors: Record = { @@ -56,8 +56,28 @@ export class NewsBox extends LitElement { connectedCallback() { super.connectedCallback(); - this.items = getVisibleNewsItems(); - this.startCycle(); + this.loadNews(); + } + + private async loadNews() { + try { + const allItems = await getNews(); + // Reset stale dismissed list when all items would be hidden + const visible = getVisibleNewsItems(allItems); + if (visible.length === 0 && allItems.length > 0) { + try { + localStorage.removeItem(DISMISSED_NEWS_KEY); + } catch { + // ignore + } + this.items = allItems; + } else { + this.items = visible; + } + this.startCycle(); + } catch { + // Silently fail — component simply won't render + } } disconnectedCallback() { @@ -111,7 +131,7 @@ export class NewsBox extends LitElement { class="shrink-0 text-[10px] font-bold tracking-wider px-2 py-0.5 rounded ${typeLabelColors[ item.type ]}" - >${typeLabels[item.type]}${translateText(typeLabelKeys[item.type])}
${item.url @@ -140,7 +160,9 @@ export class NewsBox extends LitElement { this.activeIndex ? "bg-white/60" : "bg-white/20 hover:bg-white/40"}" - aria-label="Go to item ${i + 1}" + aria-label="${translateText("news_box.go_to_item", { + num: i + 1, + })}" > `, )} @@ -150,7 +172,7 @@ export class NewsBox extends LitElement {