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 isLogistics = false;
let currentRank = "";
let hasPersonalBankAccount = false;
const shimsGlobal = await payload.findGlobal({ slug: "shims" });
const shimEntries = (shimsGlobal.shims ?? []) as NonNullable<Shim["shims"]>;
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}
/>
</CommandPaletteProvider>
</KeyboardShortcutsProvider>

View file

@ -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<PayloadUser["preferences"]>;
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<NavigationCommand[]>(() => {
@ -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<string, ContextualCommand[]>();

View file

@ -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",
},
];