initial commit of v5

This commit is contained in:
Amruth Pillai
2026-01-19 23:31:54 +01:00
parent 55bdfd0067
commit cad390fa13
1132 changed files with 200807 additions and 165288 deletions
+125
View File
@@ -0,0 +1,125 @@
import { Trans } from "@lingui/react/macro";
import { useRef } from "react";
import { useHotkeys } from "react-hotkeys-hook";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from "@/components/animate-ui/components/radix/dialog";
import { Command, CommandEmpty, CommandInput, CommandList } from "../ui/command";
import { NavigationCommandGroup } from "./pages/navigation";
import { PreferencesCommandGroup } from "./pages/preferences";
import { ResumesCommandGroup } from "./pages/resumes";
import { useCommandPaletteStore } from "./store";
export function CommandPalette() {
const inputRef = useRef<HTMLInputElement>(null);
const { open, search, pages, setOpen, setSearch, goBack } = useCommandPaletteStore();
const isFirstPage = pages.length === 0;
const currentPage = pages[pages.length - 1];
// Toggle command palette with Cmd+K / Ctrl+K
useHotkeys(
["meta+k", "ctrl+k"],
(e) => {
e.preventDefault();
setOpen(!open);
},
{ preventDefault: true, enableOnFormTags: true },
[open],
);
// Handle backspace: delete text if input has text, go back if empty, close if first page
useHotkeys(
"backspace",
(e) => {
// Only handle if the command palette is open
if (!open) return;
const input = inputRef.current;
if (!input) return;
// Only handle if input is focused
if (document.activeElement !== input) return;
// If input has text, let the default behavior handle it (delete character)
if (search.length > 0) return;
// If input is empty, prevent default and go back
e.preventDefault();
goBack();
},
{
preventDefault: false, // We'll prevent it conditionally
enableOnFormTags: true,
},
[open, search],
);
// Close with Escape
useHotkeys(
"escape",
() => {
if (!open) return;
setOpen(false);
},
{
preventDefault: true,
enableOnFormTags: true,
},
[open],
);
const handleOpenChange = (newOpen: boolean) => {
setOpen(newOpen);
};
const handleSearchChange = (value: string) => {
setSearch(value);
};
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogHeader className="sr-only">
<DialogTitle>
<Trans>Builder Command Palette</Trans>
</DialogTitle>
<DialogDescription>
<Trans>Type a command or search...</Trans>
</DialogDescription>
</DialogHeader>
<DialogContent
className="overflow-hidden p-0"
aria-label={isFirstPage ? "Command Palette" : `Command Palette - ${currentPage}`}
>
<Command
loop
aria-label="Command Palette"
className="[&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5 **:[[cmdk-group-heading]]:px-2 **:[[cmdk-group-heading]]:font-medium **:[[cmdk-group-heading]]:text-muted-foreground **:[[cmdk-group]]:px-2 **:[[cmdk-input]]:h-12 **:[[cmdk-item]]:px-2 **:[[cmdk-item]]:py-3"
>
<CommandInput
ref={inputRef}
value={search}
onValueChange={handleSearchChange}
placeholder={isFirstPage ? "Type a command or search..." : "Search..."}
aria-label="Search commands"
/>
<CommandList>
<CommandEmpty>
<Trans>The command you're looking for doesn't exist.</Trans>
</CommandEmpty>
<ResumesCommandGroup />
<PreferencesCommandGroup />
<NavigationCommandGroup />
</CommandList>
</Command>
</DialogContent>
</Dialog>
);
}
@@ -0,0 +1,22 @@
import { useMemo } from "react";
import { CommandGroup } from "@/components/ui/command";
import { useCommandPaletteStore } from "../store";
type Props = {
page?: string;
heading: React.ReactNode;
children: React.ReactNode;
};
export const BaseCommandGroup = ({ page, heading, children }: Props) => {
const pages = useCommandPaletteStore((state) => state.pages);
const currentPage = pages[pages.length - 1];
const isEnabled = useMemo(() => {
return currentPage === page;
}, [currentPage, page]);
if (!isEnabled) return null;
return <CommandGroup heading={heading}>{children}</CommandGroup>;
};
@@ -0,0 +1,115 @@
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import {
GearIcon,
HouseSimpleIcon,
KeyIcon,
OpenAiLogoIcon,
ReadCvLogoIcon,
ShieldCheckIcon,
UserCircleIcon,
WarningIcon,
} from "@phosphor-icons/react";
import { useNavigate, useRouteContext } from "@tanstack/react-router";
import { CommandItem } from "@/components/ui/command";
import { useCommandPaletteStore } from "../store";
import { BaseCommandGroup } from "./base";
export function NavigationCommandGroup() {
const navigate = useNavigate();
const { session } = useRouteContext({ strict: false });
const reset = useCommandPaletteStore((state) => state.reset);
const pushPage = useCommandPaletteStore((state) => state.pushPage);
function onNavigate(path: string) {
navigate({ to: path });
reset();
}
return (
<>
<BaseCommandGroup heading={<Trans>Go to...</Trans>}>
<CommandItem keywords={[t`Home`]} value="navigation.home" onSelect={() => onNavigate("/")}>
<HouseSimpleIcon />
<Trans>Home</Trans>
</CommandItem>
<CommandItem
disabled={!session}
keywords={[t`Resumes`]}
value="navigation.resumes"
onSelect={() => onNavigate("/dashboard/resumes")}
>
<ReadCvLogoIcon />
<Trans>Resumes</Trans>
</CommandItem>
<CommandItem
disabled={!session}
keywords={[t`Settings`]}
value="navigation.settings"
onSelect={() => pushPage("settings")}
>
<GearIcon />
<Trans>Settings</Trans>
</CommandItem>
</BaseCommandGroup>
<BaseCommandGroup page="settings" heading={<Trans>Settings</Trans>}>
<CommandItem
keywords={[t`Profile`]}
value="navigation.settings.profile"
onSelect={() => onNavigate("/dashboard/settings/profile")}
>
<UserCircleIcon />
<Trans>Profile</Trans>
</CommandItem>
<CommandItem
keywords={[t`Preferences`]}
value="navigation.settings.preferences"
onSelect={() => onNavigate("/dashboard/settings/preferences")}
>
<GearIcon />
<Trans>Preferences</Trans>
</CommandItem>
<CommandItem
keywords={[t`Authentication`]}
value="navigation.settings.authentication"
onSelect={() => onNavigate("/dashboard/settings/authentication")}
>
<ShieldCheckIcon />
<Trans>Authentication</Trans>
</CommandItem>
<CommandItem
keywords={[t`API Keys`]}
value="navigation.settings.api-keys"
onSelect={() => onNavigate("/dashboard/settings/api-keys")}
>
<KeyIcon />
<Trans>API Keys</Trans>
</CommandItem>
<CommandItem
keywords={[t`Artificial Intelligence`]}
value="navigation.settings.ai"
onSelect={() => onNavigate("/dashboard/settings/ai")}
>
<OpenAiLogoIcon />
<Trans>Artificial Intelligence</Trans>
</CommandItem>
<CommandItem
keywords={[t`Danger Zone`]}
value="navigation.settings.danger-zone"
onSelect={() => onNavigate("/dashboard/settings/danger-zone")}
>
<WarningIcon />
<Trans>Danger Zone</Trans>
</CommandItem>
</BaseCommandGroup>
</>
);
}
@@ -0,0 +1,30 @@
import { Trans } from "@lingui/react/macro";
import { PaletteIcon, TranslateIcon } from "@phosphor-icons/react";
import { CommandItem } from "@/components/ui/command";
import { useCommandPaletteStore } from "../../store";
import { BaseCommandGroup } from "../base";
import { LanguageCommandPage } from "./language";
import { ThemeCommandPage } from "./theme";
export function PreferencesCommandGroup() {
const pushPage = useCommandPaletteStore((state) => state.pushPage);
return (
<>
<BaseCommandGroup heading={<Trans>Preferences</Trans>}>
<CommandItem onSelect={() => pushPage("theme")}>
<PaletteIcon />
<Trans>Change theme to...</Trans>
</CommandItem>
<CommandItem onSelect={() => pushPage("language")}>
<TranslateIcon />
<Trans>Change language to...</Trans>
</CommandItem>
</BaseCommandGroup>
<ThemeCommandPage />
<LanguageCommandPage />
</>
);
}
@@ -0,0 +1,26 @@
import { useLingui } from "@lingui/react";
import { Trans } from "@lingui/react/macro";
import { CommandItem } from "@/components/ui/command";
import { isLocale, loadLocale, localeMap, setLocaleServerFn } from "@/utils/locale";
import { BaseCommandGroup } from "../base";
export function LanguageCommandPage() {
const { i18n } = useLingui();
const handleLocaleChange = async (value: string) => {
if (!value || !isLocale(value)) return;
await Promise.all([loadLocale(value), setLocaleServerFn({ data: value })]);
window.location.reload();
};
return (
<BaseCommandGroup page="language" heading={<Trans>Language</Trans>}>
{Object.entries(localeMap).map(([value, label]) => (
<CommandItem key={value} onSelect={() => handleLocaleChange(value)}>
<span className="font-mono text-muted-foreground text-xs">{value}</span>
{i18n.t(label)}
</CommandItem>
))}
</BaseCommandGroup>
);
}
@@ -0,0 +1,30 @@
import { Trans } from "@lingui/react/macro";
import { MoonIcon, SunIcon } from "@phosphor-icons/react";
import { useTheme } from "@/components/theme/provider";
import { CommandItem } from "@/components/ui/command";
import { useCommandPaletteStore } from "../../store";
import { BaseCommandGroup } from "../base";
export function ThemeCommandPage() {
const { setTheme } = useTheme();
const setOpen = useCommandPaletteStore((state) => state.setOpen);
const handleThemeChange = (theme: "light" | "dark") => {
setTheme(theme, { playSound: false });
setOpen(false);
};
return (
<BaseCommandGroup page="theme" heading={<Trans>Theme</Trans>}>
<CommandItem value="light" onSelect={() => handleThemeChange("light")}>
<SunIcon />
<Trans>Light theme</Trans>
</CommandItem>
<CommandItem value="dark" onSelect={() => handleThemeChange("dark")}>
<MoonIcon />
<Trans>Dark theme</Trans>
</CommandItem>
</BaseCommandGroup>
);
}
@@ -0,0 +1,82 @@
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { PlusIcon, ReadCvLogoIcon } from "@phosphor-icons/react";
import { useQuery } from "@tanstack/react-query";
import { useNavigate, useRouteContext } from "@tanstack/react-router";
import { CommandLoading } from "cmdk";
import { CommandItem, CommandShortcut } from "@/components/ui/command";
import { Kbd } from "@/components/ui/kbd";
import { useDialogStore } from "@/dialogs/store";
import { orpc } from "@/integrations/orpc/client";
import { useCommandPaletteStore } from "../store";
import { BaseCommandGroup } from "./base";
export function ResumesCommandGroup() {
const navigate = useNavigate();
const { openDialog } = useDialogStore();
const { session } = useRouteContext({ strict: false });
const reset = useCommandPaletteStore((state) => state.reset);
const peekPage = useCommandPaletteStore((state) => state.peekPage);
const pushPage = useCommandPaletteStore((state) => state.pushPage);
const isResumesPage = peekPage() === "resumes";
const { data: resumes, isLoading } = useQuery(
orpc.resume.list.queryOptions({
enabled: !!session && isResumesPage,
}),
);
const onCreate = () => {
navigate({ to: "/dashboard/resumes" });
openDialog("resume.create", undefined);
reset();
};
const onNavigate = (path: string) => {
navigate({ to: path });
reset();
};
if (!session) return null;
return (
<>
<BaseCommandGroup heading={<Trans>Search for...</Trans>}>
<CommandItem keywords={[t`Resumes`]} value="search.resumes" onSelect={() => pushPage("resumes")}>
<ReadCvLogoIcon />
<Trans>Resumes</Trans>
</CommandItem>
</BaseCommandGroup>
<BaseCommandGroup page="resumes" heading={<Trans>Resumes</Trans>}>
<CommandItem onSelect={onCreate}>
<PlusIcon />
<Trans>Create a new resume</Trans>
</CommandItem>
{isLoading ? (
<CommandLoading>
<Trans>Loading resumes...</Trans>
</CommandLoading>
) : (
resumes?.map((resume) => (
<CommandItem
key={resume.id}
value={resume.id}
keywords={[resume.name]}
onSelect={() => onNavigate(`/builder/${resume.id}`)}
>
<ReadCvLogoIcon />
{resume.name}
<CommandShortcut className="opacity-0 transition-opacity group-data-[selected=true]/command-item:opacity-100">
Press <Kbd>Enter</Kbd> to open
</CommandShortcut>
</CommandItem>
))
)}
</BaseCommandGroup>
</>
);
}
+59
View File
@@ -0,0 +1,59 @@
import { create } from "zustand/react";
type CommandPaletteState = {
open: boolean;
search: string;
pages: string[];
};
type CommandPaletteActions = {
setOpen: (open: boolean) => void;
setSearch: (search: string) => void;
pushPage: (page: string) => void;
peekPage: () => string | undefined;
popPage: () => void;
reset: () => void;
goBack: () => void;
};
type CommandPaletteStore = CommandPaletteState & CommandPaletteActions;
const initialState: CommandPaletteState = {
open: false,
search: "",
pages: [],
};
export const useCommandPaletteStore = create<CommandPaletteStore>((set, get) => ({
...initialState,
setOpen: (open) => {
set({ open });
if (!open) set(initialState);
},
setSearch: (search) => set({ search }),
peekPage: () => get().pages[get().pages.length - 1],
pushPage: (page) => set((state) => ({ pages: [...state.pages, page], search: "" })),
popPage: () => set((state) => ({ pages: state.pages.slice(0, -1), search: "" })),
reset: () => set(initialState),
goBack: () => {
set((state) => {
if (state.search.length > 0) {
// If there's text, just clear the search
return { search: "" };
}
if (state.pages.length > 0) {
// If on a sub-page, go back
return { pages: state.pages.slice(0, -1), search: "" };
}
// If on first page, close
return { open: false, search: "", pages: [] };
});
},
}));