diff --git a/DorkNet.Server/admin-ui/src/pages/Content.tsx b/DorkNet.Server/admin-ui/src/pages/Content.tsx
new file mode 100644
index 0000000..ed39b3b
--- /dev/null
+++ b/DorkNet.Server/admin-ui/src/pages/Content.tsx
@@ -0,0 +1,26 @@
+import { CommunityBoard } from './CommunityBoard';
+import { LoadingTips } from './LoadingTips';
+import { PageHeader } from '../components/PageHeader';
+import { Tabs, useTabParam } from '../components/Tabs';
+
+// The two player-facing content surfaces the watch pulls on dorm load —
+// the community board and the loading-screen tips — share one page.
+// Old /community and /loading-tips routes redirect here.
+export function Content() {
+ const [tab, setTab] = useTabParam<'community' | 'tips'>('community', ['community', 'tips']);
+ return (
+
+
+
+ {tab === 'community' &&
}
+ {tab === 'tips' &&
}
+
+ );
+}
diff --git a/DorkNet.Server/admin-ui/src/pages/Gift.tsx b/DorkNet.Server/admin-ui/src/pages/Gift.tsx
index 5ad11bd..a8b8e93 100644
--- a/DorkNet.Server/admin-ui/src/pages/Gift.tsx
+++ b/DorkNet.Server/admin-ui/src/pages/Gift.tsx
@@ -1,8 +1,6 @@
import { useEffect, useMemo, useState } from 'react';
import { api, get } from '../lib/api';
-import type { Player } from '../lib/types';
import { useApi } from '../lib/useApi';
-import { PageHeader } from '../components/PageHeader';
import { useToast } from '../components/Toast';
import { Confirm } from '../components/Confirm';
import { RefreshCw, Search, Trash } from '../components/Icons';
@@ -41,10 +39,11 @@ interface AvatarItem {
// and the consume endpoint writes the avatar item into their inventory.
// This is the only flow that triggers the popup — directly poking
// InventoryJson skips it entirely.
-export function Gift() {
- const { data: players } = useApi
('/players?take=500');
- const [playerId, setPlayerId] = useState(null);
-
+// Per-player gift composer, rendered as the "Gift" tab of the player
+// detail modal. The recipient is fixed to the open player, so there's
+// no picker and no page chrome — just the reward builder, presentation
+// options, and that player's pending-gift inbox.
+export function GiftPanel({ playerId }: { playerId: number }) {
const [includeItem, setIncludeItem] = useState(true);
const [includeCurrency, setIncludeCurrency] = useState(false);
const [includeXp, setIncludeXp] = useState(false);
@@ -66,7 +65,6 @@ export function Gift() {
const toast = useToast();
const send = async () => {
- if (!playerId) return toast.push('Pick a recipient first', 'error');
if (!includeItem && !includeCurrency && !includeXp) {
return toast.push('Pick at least one reward (item / currency / xp)', 'error');
}
@@ -101,23 +99,14 @@ export function Gift() {
return (
-
+
+ Drops a wrapped gift box on this player's HUD — they tap to open it and the rewards land in their inventory / wallet.
+
- {playerId !== null &&
}
+
-
+
-
-
-