feat(hint): targets, automatic hints and formatting

Target fields on the admin hints page. The dashboard shows the four
automatic hints after admin hints when their condition is true, using
data Core already serves. HintText renders **bold** and safe links (app
path or https) as React elements, never HTML, in hints and news.
Auto-close without tick hides for this session, X for 7 days, tick + X
for good.
This commit is contained in:
lobo committed 2026-10-02 14:36:49 +02:00
1 parent ae904d946a
commit 8598024bce
14 files changed
+578 -53

No files matched your search

+41 -2
View File
@@ -3,8 +3,9 @@ import {apiFetch} from './api.js';
const AUTH = () => import.meta.env.VITE_API_URL;
/**
* GET /api/hints: {results: [{id, textEn, textDe, kind, startsAt, endsAt}]}, the running hints the user has not
* closed for good, newest first.
* GET /api/hints: {results: [{id, textEn, textDe, kind, startsAt, endsAt, emptyLibraryOnly}], automatic: [keys]}.
* results: the running hints for this user that are not closed for good, newest first (emptyLibraryOnly: show only
* with an empty library, checked here). automatic: the automatic hints that are on and not hidden for this user.
*/
export const fetchHints = () => apiFetch(`${AUTH()}/api/hints`);
@@ -31,3 +32,41 @@ export const createHint = (hint) =>
export const endHint = (hintId) => apiFetch(`${AUTH()}/api/admin/hints/${hintId}/end`, {method: 'PUT'});
export const hintText = (hint, language) => (language?.startsWith('de') && hint.textDe ? hint.textDe : hint.textEn);
/**
* The automatic hints in the order they show.
*/
export const AUTO_HINTS = ['NO_STEAM', 'EMPTY_LIBRARY', 'LINK_QUEUE', 'PILE_GREW'];
/**
* 204 or 404 HINT_NOT_FOUND: hide the automatic hint for 7 days.
*/
export const snoozeAutoHint = (key) => apiFetch(`${AUTH()}/api/hints/auto/${key}/snooze`, {method: 'PUT'});
/**
* 204 or 404 HINT_NOT_FOUND: hide the automatic hint for good.
*/
export const hideAutoHint = (key) => apiFetch(`${AUTH()}/api/hints/auto/${key}/done`, {method: 'PUT'});
/**
* PLATFORM_ADMIN only: 204, or 404 HINT_NOT_FOUND.
*/
export const setAutoHintEnabled = (key, enabled) =>
apiFetch(`${AUTH()}/api/admin/hints/auto/${key}`, {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({enabled}),
});
/**
* How much the pile grew since the end of last month (else since the first day of this month), from the TREND group of the stats. Uses Core's
* dates, not the browser clock.
*/
export const pileGrowth = (trend) => {
const days = trend?.days ?? [];
const last = days.at(-1);
const month = last?.day.slice(0, 7);
// The snapshot of a day is written at its end, so only last month's last row excludes the first day's additions.
const first = days.findLast((d) => d.day.slice(0, 7) < month) ?? days.find((d) => d.day.slice(0, 7) === month);
return last && first !== last ? last.backlogGames - first.backlogGames : 0;
};