1
0
Fork 0

feat(palette): add account and display quick actions

Add provisioning a personal bank account, toggling background texture and callsign display, and copying the page link; the layout now passes user preferences and personal-account state so the palette can hide or handle them.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
Jason Fraley 2026-09-07 04:37:14 -04:00
parent c4a24483f6
commit 6bcd38aff6
3 changed files with 146 additions and 59 deletions

View file

@ -72,40 +72,50 @@ export default async function RootLayout(props: { children: React.ReactNode }) {
let isIntelligence = false; let isIntelligence = false;
let isLogistics = false; let isLogistics = false;
let currentRank = ""; let currentRank = "";
let hasPersonalBankAccount = false;
const shimsGlobal = await payload.findGlobal({ slug: "shims" }); const shimsGlobal = await payload.findGlobal({ slug: "shims" });
const shimEntries = (shimsGlobal.shims ?? []) as NonNullable<Shim["shims"]>; const shimEntries = (shimsGlobal.shims ?? []) as NonNullable<Shim["shims"]>;
if (user) { if (user) {
[isIntelligence, isLogistics] = await Promise.all([ const [intelligenceQual, logisticsQual, profileRes, experienceRes, personalAccountRes] =
hasIntelligenceQualification(payload, user).catch(() => false), await Promise.all([
hasLogisticsQualification(payload, user).catch(() => false), hasIntelligenceQualification(payload, user).catch(() => false),
]); hasLogisticsQualification(payload, user).catch(() => false),
const [profileRes, experienceRes] = await Promise.all([ payload.find({
payload.find({ collection: "profiles",
collection: "profiles", where: { user: { equals: user.id } },
where: { user: { equals: user.id } }, select: {
select: { rank: true,
rank: true, progression: {
progression: { awards: true,
awards: true, scoreboard: true,
scoreboard: true, xp: true,
xp: true, minigames: true,
minigames: true, },
}, },
}, }),
}), payload.find({
payload.find({ collection: "experience",
collection: "experience", sort: "requiredPoints",
sort: "requiredPoints", limit: 100,
limit: 100, select: {
select: { name: true,
name: true, description: true,
description: true, requiredPoints: true,
requiredPoints: true, },
}, }),
}), payload.find({
]); collection: "bank-accounts",
where: {
and: [{ accountType: { equals: "personal" } }, { ownerUser: { equals: user.id } }],
},
limit: 1,
}),
]);
isIntelligence = intelligenceQual;
isLogistics = logisticsQual;
hasPersonalBankAccount = personalAccountRes.docs.length > 0;
const userXp = profileRes.docs[0]?.progression?.xp ?? 0; const userXp = profileRes.docs[0]?.progression?.xp ?? 0;
xpLevel = resolveLevel(userXp, experienceRes.docs); xpLevel = resolveLevel(userXp, experienceRes.docs);
@ -155,6 +165,8 @@ export default async function RootLayout(props: { children: React.ReactNode }) {
hasIntelligence={isIntelligence} hasIntelligence={isIntelligence}
hasLogistics={isLogistics} hasLogistics={isLogistics}
username={user.username} username={user.username}
preferences={user.preferences}
hasPersonalBankAccount={hasPersonalBankAccount}
/> />
</CommandPaletteProvider> </CommandPaletteProvider>
</KeyboardShortcutsProvider> </KeyboardShortcutsProvider>

View file

@ -24,12 +24,17 @@ import {
} from "./CommandPaletteContext"; } from "./CommandPaletteContext";
import { markAllNotificationsRead } from "@/app/(frontend)/notifications/actions"; import { markAllNotificationsRead } from "@/app/(frontend)/notifications/actions";
import { autoArrangeLocker } from "@/app/(frontend)/locker/actions"; import { autoArrangeLocker } from "@/app/(frontend)/locker/actions";
import { ensureMyAccount } from "@/app/(frontend)/logistics/banking/actions";
import { updatePreferences } from "@/app/(frontend)/account/actions";
import type { User as PayloadUser } from "@/payload-types";
interface CommandPaletteProps { interface CommandPaletteProps {
userRoles: string[]; userRoles: string[];
hasIntelligence: boolean; hasIntelligence: boolean;
hasLogistics: boolean; hasLogistics: boolean;
username?: string; username?: string;
preferences?: NonNullable<PayloadUser["preferences"]>;
hasPersonalBankAccount?: boolean;
} }
export function CommandPalette({ export function CommandPalette({
@ -37,6 +42,8 @@ export function CommandPalette({
hasIntelligence, hasIntelligence,
hasLogistics, hasLogistics,
username, username,
preferences,
hasPersonalBankAccount,
}: CommandPaletteProps) { }: CommandPaletteProps) {
const router = useRouter(); const router = useRouter();
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
@ -90,6 +97,64 @@ export function CommandPalette({
} }
break; break;
} }
case "ensureBankAccount": {
const result = await ensureMyAccount();
if (result.success) {
toast.success("Personal bank account ready");
} else {
toast.error(result.error ?? "Failed to provision bank account");
}
break;
}
case "toggleBackgroundTexture": {
const result = await updatePreferences({
mutedAll: preferences?.notifications?.mutedAll ?? false,
mutedTypes: preferences?.notifications?.mutedTypes ?? [],
showCallsign: preferences?.display?.showCallsign ?? false,
showVersionOverlay: preferences?.display?.showVersionOverlay ?? true,
showBackgroundTexture: !(preferences?.display?.showBackgroundTexture ?? true),
leadOrActual: preferences?.display?.leadOrActual ?? "lead",
discordEnabled: preferences?.discord?.enabled ?? false,
discordMutedTypes: preferences?.discord?.mutedTypes ?? [],
xpDisplayMini: preferences?.display?.xpDisplayMini ?? false,
});
if (result.success) {
toast.success("Background texture toggled");
router.refresh();
} else {
toast.error(result.error ?? "Failed to update preferences");
}
break;
}
case "toggleCallsign": {
const result = await updatePreferences({
mutedAll: preferences?.notifications?.mutedAll ?? false,
mutedTypes: preferences?.notifications?.mutedTypes ?? [],
showCallsign: !(preferences?.display?.showCallsign ?? false),
showVersionOverlay: preferences?.display?.showVersionOverlay ?? true,
showBackgroundTexture: preferences?.display?.showBackgroundTexture ?? true,
leadOrActual: preferences?.display?.leadOrActual ?? "lead",
discordEnabled: preferences?.discord?.enabled ?? false,
discordMutedTypes: preferences?.discord?.mutedTypes ?? [],
xpDisplayMini: preferences?.display?.xpDisplayMini ?? false,
});
if (result.success) {
toast.success("Callsign display toggled");
router.refresh();
} else {
toast.error(result.error ?? "Failed to update preferences");
}
break;
}
case "copyPageLink": {
try {
await navigator.clipboard.writeText(window.location.href);
toast.success("Page link copied to clipboard");
} catch {
toast.error("Failed to copy page link");
}
break;
}
case "refreshPage": { case "refreshPage": {
router.refresh(); router.refresh();
toast.success("Page refreshed"); toast.success("Page refreshed");
@ -105,7 +170,7 @@ export function CommandPalette({
setLoading(null); setLoading(null);
} }
}, },
[router], [router, preferences],
); );
const filteredNavCommands = React.useMemo<NavigationCommand[]>(() => { const filteredNavCommands = React.useMemo<NavigationCommand[]>(() => {
@ -154,9 +219,12 @@ export function CommandPalette({
if (cmd.requires === "logistics" && !hasLogistics && !userRoles.includes("developer")) { if (cmd.requires === "logistics" && !hasLogistics && !userRoles.includes("developer")) {
return false; return false;
} }
if (cmd.actionKey === "ensureBankAccount" && hasPersonalBankAccount) {
return false;
}
return true; return true;
}); });
}, [userRoles, hasIntelligence, hasLogistics]); }, [userRoles, hasIntelligence, hasLogistics, hasPersonalBankAccount]);
const groupedContextual = React.useMemo(() => { const groupedContextual = React.useMemo(() => {
const groups = new Map<string, ContextualCommand[]>(); const groups = new Map<string, ContextualCommand[]>();

View file

@ -3,10 +3,11 @@ import {
Bell, Bell,
Backpack, Backpack,
LogOut, LogOut,
PlaneTakeoff,
User,
RefreshCw, RefreshCw,
Shield, Wallet,
Wallpaper,
Tag,
Link2,
} from "lucide-react"; } from "lucide-react";
export interface ActionCommand { export interface ActionCommand {
@ -28,39 +29,37 @@ export interface ActionCommand {
export const actionCommands: ActionCommand[] = [ export const actionCommands: ActionCommand[] = [
{ {
id: "action:flight-simulator", id: "action:provision-bank-account",
label: "Flight Simulator", label: "Provision Personal Bank Account",
icon: PlaneTakeoff, icon: Wallet,
keywords: ["flappy", "game", "minigame", "fly"], keywords: ["banking", "wallet", "money", "create", "open"],
actionType: "navigate", actionType: "callback",
href: "/flappy", actionKey: "ensureBankAccount",
}, },
{ {
id: "action:view-notifications", id: "action:toggle-background-texture",
label: "View Notifications", label: "Toggle Background Texture",
icon: Bell, icon: Wallpaper,
keywords: ["alerts", "messages", "inbox"], keywords: ["display", "appearance", "theme", "texture", "background"],
actionType: "navigate", actionType: "callback",
href: "/notifications", actionKey: "toggleBackgroundTexture",
}, },
{ {
id: "action:create-ticket", id: "action:toggle-callsign",
label: "Create Support Ticket", label: "Toggle Callsign Display",
icon: UserPlus, icon: Tag,
keywords: ["help", "support", "report"], keywords: ["display", "callsign", "name", "sidebar"],
actionType: "navigate", actionType: "callback",
href: "/helpdesk", actionKey: "toggleCallsign",
}, },
{ {
id: "action:admin-panel", id: "action:copy-page-link",
label: "Open Admin Panel", label: "Copy Page Link",
icon: Shield, icon: Link2,
keywords: ["cms", "manage", "backend"], keywords: ["share", "url", "clipboard", "copy", "link"],
actionType: "navigate", actionType: "callback",
href: "/admin", actionKey: "copyPageLink",
roles: ["admin", "developer"],
}, },
{ {
id: "action:mark-all-read", id: "action:mark-all-read",
label: "Mark All Notifications Read", label: "Mark All Notifications Read",
@ -93,4 +92,12 @@ export const actionCommands: ActionCommand[] = [
actionType: "callback", actionType: "callback",
actionKey: "logout", actionKey: "logout",
}, },
{
id: "action:create-ticket",
label: "Create Support Ticket",
icon: UserPlus,
keywords: ["help", "support", "report"],
actionType: "navigate",
href: "/helpdesk",
},
]; ];