From 6bcd38aff6ca549f56340911ccf670db4304f495 Mon Sep 17 00:00:00 2001 From: Z8MB1E Date: Mon, 7 Sep 2026 04:37:14 -0400 Subject: [PATCH] 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 --- src/app/(frontend)/layout.tsx | 68 ++++++++++-------- .../command-palette/CommandPalette.tsx | 72 ++++++++++++++++++- .../command-palette/commands/actions.ts | 65 +++++++++-------- 3 files changed, 146 insertions(+), 59 deletions(-) diff --git a/src/app/(frontend)/layout.tsx b/src/app/(frontend)/layout.tsx index 11f907c..0802264 100644 --- a/src/app/(frontend)/layout.tsx +++ b/src/app/(frontend)/layout.tsx @@ -72,40 +72,50 @@ export default async function RootLayout(props: { children: React.ReactNode }) { let isIntelligence = false; let isLogistics = false; let currentRank = ""; + let hasPersonalBankAccount = false; const shimsGlobal = await payload.findGlobal({ slug: "shims" }); const shimEntries = (shimsGlobal.shims ?? []) as NonNullable; if (user) { - [isIntelligence, isLogistics] = await Promise.all([ - hasIntelligenceQualification(payload, user).catch(() => false), - hasLogisticsQualification(payload, user).catch(() => false), - ]); - const [profileRes, experienceRes] = await Promise.all([ - payload.find({ - collection: "profiles", - where: { user: { equals: user.id } }, - select: { - rank: true, - progression: { - awards: true, - scoreboard: true, - xp: true, - minigames: true, + const [intelligenceQual, logisticsQual, profileRes, experienceRes, personalAccountRes] = + await Promise.all([ + hasIntelligenceQualification(payload, user).catch(() => false), + hasLogisticsQualification(payload, user).catch(() => false), + payload.find({ + collection: "profiles", + where: { user: { equals: user.id } }, + select: { + rank: true, + progression: { + awards: true, + scoreboard: true, + xp: true, + minigames: true, + }, }, - }, - }), - payload.find({ - collection: "experience", - sort: "requiredPoints", - limit: 100, - select: { - name: true, - description: true, - requiredPoints: true, - }, - }), - ]); + }), + payload.find({ + collection: "experience", + sort: "requiredPoints", + limit: 100, + select: { + name: true, + description: 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; xpLevel = resolveLevel(userXp, experienceRes.docs); @@ -155,6 +165,8 @@ export default async function RootLayout(props: { children: React.ReactNode }) { hasIntelligence={isIntelligence} hasLogistics={isLogistics} username={user.username} + preferences={user.preferences} + hasPersonalBankAccount={hasPersonalBankAccount} /> diff --git a/src/components/command-palette/CommandPalette.tsx b/src/components/command-palette/CommandPalette.tsx index 3ffd409..0a8483a 100644 --- a/src/components/command-palette/CommandPalette.tsx +++ b/src/components/command-palette/CommandPalette.tsx @@ -24,12 +24,17 @@ import { } from "./CommandPaletteContext"; import { markAllNotificationsRead } from "@/app/(frontend)/notifications/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 { userRoles: string[]; hasIntelligence: boolean; hasLogistics: boolean; username?: string; + preferences?: NonNullable; + hasPersonalBankAccount?: boolean; } export function CommandPalette({ @@ -37,6 +42,8 @@ export function CommandPalette({ hasIntelligence, hasLogistics, username, + preferences, + hasPersonalBankAccount, }: CommandPaletteProps) { const router = useRouter(); const [open, setOpen] = React.useState(false); @@ -90,6 +97,64 @@ export function CommandPalette({ } 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": { router.refresh(); toast.success("Page refreshed"); @@ -105,7 +170,7 @@ export function CommandPalette({ setLoading(null); } }, - [router], + [router, preferences], ); const filteredNavCommands = React.useMemo(() => { @@ -154,9 +219,12 @@ export function CommandPalette({ if (cmd.requires === "logistics" && !hasLogistics && !userRoles.includes("developer")) { return false; } + if (cmd.actionKey === "ensureBankAccount" && hasPersonalBankAccount) { + return false; + } return true; }); - }, [userRoles, hasIntelligence, hasLogistics]); + }, [userRoles, hasIntelligence, hasLogistics, hasPersonalBankAccount]); const groupedContextual = React.useMemo(() => { const groups = new Map(); diff --git a/src/components/command-palette/commands/actions.ts b/src/components/command-palette/commands/actions.ts index 5f5d927..c2ec073 100644 --- a/src/components/command-palette/commands/actions.ts +++ b/src/components/command-palette/commands/actions.ts @@ -3,10 +3,11 @@ import { Bell, Backpack, LogOut, - PlaneTakeoff, - User, RefreshCw, - Shield, + Wallet, + Wallpaper, + Tag, + Link2, } from "lucide-react"; export interface ActionCommand { @@ -28,39 +29,37 @@ export interface ActionCommand { export const actionCommands: ActionCommand[] = [ { - id: "action:flight-simulator", - label: "Flight Simulator", - icon: PlaneTakeoff, - keywords: ["flappy", "game", "minigame", "fly"], - actionType: "navigate", - href: "/flappy", + id: "action:provision-bank-account", + label: "Provision Personal Bank Account", + icon: Wallet, + keywords: ["banking", "wallet", "money", "create", "open"], + actionType: "callback", + actionKey: "ensureBankAccount", }, { - id: "action:view-notifications", - label: "View Notifications", - icon: Bell, - keywords: ["alerts", "messages", "inbox"], - actionType: "navigate", - href: "/notifications", + id: "action:toggle-background-texture", + label: "Toggle Background Texture", + icon: Wallpaper, + keywords: ["display", "appearance", "theme", "texture", "background"], + actionType: "callback", + actionKey: "toggleBackgroundTexture", }, { - id: "action:create-ticket", - label: "Create Support Ticket", - icon: UserPlus, - keywords: ["help", "support", "report"], - actionType: "navigate", - href: "/helpdesk", + id: "action:toggle-callsign", + label: "Toggle Callsign Display", + icon: Tag, + keywords: ["display", "callsign", "name", "sidebar"], + actionType: "callback", + actionKey: "toggleCallsign", }, { - id: "action:admin-panel", - label: "Open Admin Panel", - icon: Shield, - keywords: ["cms", "manage", "backend"], - actionType: "navigate", - href: "/admin", - roles: ["admin", "developer"], + id: "action:copy-page-link", + label: "Copy Page Link", + icon: Link2, + keywords: ["share", "url", "clipboard", "copy", "link"], + actionType: "callback", + actionKey: "copyPageLink", }, - { id: "action:mark-all-read", label: "Mark All Notifications Read", @@ -93,4 +92,12 @@ export const actionCommands: ActionCommand[] = [ actionType: "callback", actionKey: "logout", }, + { + id: "action:create-ticket", + label: "Create Support Ticket", + icon: UserPlus, + keywords: ["help", "support", "report"], + actionType: "navigate", + href: "/helpdesk", + }, ];