Close remaining web gaps: rich profile, login history, lightbox/slider, flash client, admin chatlog/DJ/chart/WYSIWYG, niche API

- Rich profile (/u/[username]): wallet (credits/duckets/diamonds), friends
  grid (messenger_friendships), and owned rooms sections.
- Login history: new website_login_logs table (model + migration 0007),
  recorded on every successful sign-in (ip + user-agent), surfaced on a new
  /settings/sessions page (with failed-attempt list from failed_logins).
- Photos lightbox + home article slider (client components, no Swiper dep).
- /client/flash launcher (SSO ticket like the Nitro page).
- Admin: private chatlogs section in /admin/logs, /admin/radio/moderation
  (shout moderation), a "users by rank" inline bar chart on the dashboard,
  and a TinyMCE rich-text editor on the article admin forms.
- Niche API: /api/values/[id], /api/guilds(+/[id]), /api/radio/auto-play.

Verified live (prod, amx_test): login recorded → /settings/sessions shows
it with device; profile renders wallet/friends/rooms; dashboard chart +
private-chat logs + /client/flash + /api/guilds all OK. Reverted test data.
tsc 0, vitest 49/49, next build 0.
This commit is contained in:
Simo committed 2026-06-29 18:26:34 +02:00
1 parent f7b3845131
commit 4dfe698009
25 files changed
+1463 -64

No files matched your search

+2 -1
View File
@@ -1,6 +1,7 @@
import Link from "next/link";
import { notFound } from "next/navigation";
import { deleteArticle, updateArticle } from "@/actions/admin-articles";
import { RichText } from "@/components/admin/rich-text";
import { prisma } from "@/lib/prisma";
export const dynamic = "force-dynamic";
@@ -30,7 +31,7 @@ export default async function EditArticle({
<input name="title" defaultValue={article.title} required />
<input name="image" defaultValue={article.image} placeholder="Image URL" />
<textarea name="shortStory" defaultValue={article.shortStory} rows={2} />
<textarea name="fullStory" defaultValue={article.fullStory} rows={10} />
<RichText name="fullStory" defaultValue={article.fullStory} rows={10} />
<button type="submit">Save</button>
</form>
<form action={deleteArticle} style={{ marginTop: "1rem" }}>
+2 -1
View File
@@ -1,5 +1,6 @@
import Link from "next/link";
import { createArticle } from "@/actions/admin-articles";
import { RichText } from "@/components/admin/rich-text";
export default function NewArticle() {
return (
@@ -12,7 +13,7 @@ export default function NewArticle() {
<input name="title" placeholder="Title" required />
<input name="image" placeholder="Image URL" />
<textarea name="shortStory" placeholder="Short story (summary)" rows={2} />
<textarea name="fullStory" placeholder="Full story" rows={10} />
<RichText name="fullStory" placeholder="Full story" rows={10} />
<button type="submit">Create</button>
</form>
</main>