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:
parent
c4a24483f6
commit
6bcd38aff6
3 changed files with 146 additions and 59 deletions
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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[]>();
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue