feat: update shortcut icons and descriptions for improved clarity

- Replaced icons for "General Assist," "Quick Assist," and "Extreme Assist" shortcuts to better represent their functionalities.
- Updated descriptions for each shortcut to enhance user understanding of their actions.
- Refactored the layout of the shortcut recorder for a more streamlined user experience.
This commit is contained in:
DESKTOP-RTLN3BA\$punk 2026-04-07 04:22:22 -07:00
parent a4a4deeda0
commit b74ac8a608
3 changed files with 182 additions and 157 deletions

View file

@ -1,6 +1,6 @@
"use client"; "use client";
import { AppWindow, Clipboard, Sparkles } from "lucide-react"; import { BrainCog, Rocket, Zap } from "lucide-react";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
import { toast } from "sonner"; import { toast } from "sonner";
import { DEFAULT_SHORTCUTS, ShortcutRecorder } from "@/components/desktop/shortcut-recorder"; import { DEFAULT_SHORTCUTS, ShortcutRecorder } from "@/components/desktop/shortcut-recorder";
@ -103,27 +103,27 @@ export function DesktopContent() {
onReset={() => resetShortcut("generalAssist")} onReset={() => resetShortcut("generalAssist")}
defaultValue={DEFAULT_SHORTCUTS.generalAssist} defaultValue={DEFAULT_SHORTCUTS.generalAssist}
label="General Assist" label="General Assist"
description="Open SurfSense from anywhere" description="Launch SurfSense instantly from any application"
icon={AppWindow} icon={Rocket}
/> />
<ShortcutRecorder <ShortcutRecorder
value={shortcuts.quickAsk} value={shortcuts.quickAsk}
onChange={(accel) => updateShortcut("quickAsk", accel)} onChange={(accel) => updateShortcut("quickAsk", accel)}
onReset={() => resetShortcut("quickAsk")} onReset={() => resetShortcut("quickAsk")}
defaultValue={DEFAULT_SHORTCUTS.quickAsk} defaultValue={DEFAULT_SHORTCUTS.quickAsk}
label="Quick Assist" label="Quick Assist"
description="Copy selected text and ask AI about it" description="Select text anywhere, then ask AI to explain, rewrite, or act on it"
icon={Clipboard} icon={Zap}
/>
<ShortcutRecorder
value={shortcuts.autocomplete}
onChange={(accel) => updateShortcut("autocomplete", accel)}
onReset={() => resetShortcut("autocomplete")}
defaultValue={DEFAULT_SHORTCUTS.autocomplete}
label="Extreme Assist"
description="AI drafts text using your screen context and knowledge base"
icon={BrainCog}
/> />
<ShortcutRecorder
value={shortcuts.autocomplete}
onChange={(accel) => updateShortcut("autocomplete", accel)}
onReset={() => resetShortcut("autocomplete")}
defaultValue={DEFAULT_SHORTCUTS.autocomplete}
label="Extreme Assist"
description="AI writing powered by your screen and knowledge base"
icon={Sparkles}
/>
<p className="text-[11px] text-muted-foreground"> <p className="text-[11px] text-muted-foreground">
Click a shortcut and press a new key combination to change it. Click a shortcut and press a new key combination to change it.
</p> </p>

View file

@ -2,7 +2,7 @@
import { IconBrandGoogleFilled } from "@tabler/icons-react"; import { IconBrandGoogleFilled } from "@tabler/icons-react";
import { useAtom } from "jotai"; import { useAtom } from "jotai";
import { AppWindow, Clipboard, Eye, EyeOff, Keyboard, Sparkles } from "lucide-react"; import { BrainCog, Eye, EyeOff, Rocket, Zap } from "lucide-react";
import Image from "next/image"; import Image from "next/image";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { useCallback, useEffect, useState } from "react"; import { useCallback, useEffect, useState } from "react";
@ -10,7 +10,6 @@ import { toast } from "sonner";
import { loginMutationAtom } from "@/atoms/auth/auth-mutation.atoms"; import { loginMutationAtom } from "@/atoms/auth/auth-mutation.atoms";
import { DEFAULT_SHORTCUTS, ShortcutRecorder } from "@/components/desktop/shortcut-recorder"; import { DEFAULT_SHORTCUTS, ShortcutRecorder } from "@/components/desktop/shortcut-recorder";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
@ -100,8 +99,9 @@ export default function DesktopLoginPage() {
}; };
return ( return (
<div className="relative flex min-h-screen items-center justify-center bg-background p-4"> <div className="relative flex min-h-svh items-center justify-center bg-background p-4 sm:p-6">
<div className="pointer-events-none absolute inset-0 overflow-hidden"> {/* Subtle radial glow */}
<div className="pointer-events-none fixed inset-0 overflow-hidden">
<div <div
className="absolute -top-1/2 left-1/2 size-[800px] -translate-x-1/2 rounded-full opacity-[0.03]" className="absolute -top-1/2 left-1/2 size-[800px] -translate-x-1/2 rounded-full opacity-[0.03]"
style={{ style={{
@ -110,134 +110,157 @@ export default function DesktopLoginPage() {
/> />
</div> </div>
<Card className="relative w-full max-w-md shadow-lg"> <div className="relative flex w-full max-w-md flex-col overflow-hidden rounded-xl border bg-card shadow-lg">
<CardHeader className="items-center text-center pb-4"> {/* Header */}
<div className="flex flex-col items-center px-6 pt-6 pb-2 text-center">
<Image <Image
src="/icon-128.svg" src="/icon-128.svg"
className="select-none dark:invert size-14 rounded-md mb-2" className="select-none dark:invert size-12 rounded-lg mb-3"
alt="SurfSense" alt="SurfSense"
width={56} width={48}
height={56} height={48}
priority priority
/> />
<CardTitle className="text-xl">Welcome to SurfSense Desktop App</CardTitle> <h1 className="text-lg font-semibold tracking-tight">
<CardDescription>Configure your shortcuts, then sign in to get started.</CardDescription> Welcome to SurfSense Desktop
</CardHeader> </h1>
<p className="mt-1 text-sm text-muted-foreground">
<CardContent className="flex flex-col gap-6"> Configure shortcuts, then sign in to get started.
{/* ---- Shortcuts Section (first) ---- */} </p>
{shortcutsLoaded ? ( </div>
<div className="flex flex-col gap-3">
<div className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground mb-1">
<Keyboard className="size-3" />
Keyboard Shortcuts
</div>
<ShortcutRecorder
value={shortcuts.generalAssist}
onChange={(accel) => updateShortcut("generalAssist", accel)}
onReset={() => resetShortcut("generalAssist")}
defaultValue={DEFAULT_SHORTCUTS.generalAssist}
label="General Assist"
description="Open SurfSense from anywhere"
icon={AppWindow}
/>
<ShortcutRecorder
value={shortcuts.quickAsk}
onChange={(accel) => updateShortcut("quickAsk", accel)}
onReset={() => resetShortcut("quickAsk")}
defaultValue={DEFAULT_SHORTCUTS.quickAsk}
label="Quick Assist"
description="Copy selected text and ask AI about it"
icon={Clipboard}
/>
<ShortcutRecorder
value={shortcuts.autocomplete}
onChange={(accel) => updateShortcut("autocomplete", accel)}
onReset={() => resetShortcut("autocomplete")}
defaultValue={DEFAULT_SHORTCUTS.autocomplete}
label="Extreme Assist"
description="AI writing powered by your screen and knowledge base"
icon={Sparkles}
/>
<p className="text-[11px] text-muted-foreground text-center">
Click a shortcut and press a new key combination to change it.
</p>
</div>
) : (
<div className="flex justify-center py-4">
<Spinner size="sm" />
</div>
)}
{/* ---- Divider ---- */}
<Separator />
{/* ---- Auth Section (second) ---- */}
{isGoogleAuth ? (
<Button variant="outline" className="w-full gap-2 py-5" onClick={handleGoogleLogin}>
<IconBrandGoogleFilled className="size-5" />
Continue with Google
</Button>
) : (
<form onSubmit={handleLocalLogin} className="flex flex-col gap-4">
{loginError && (
<div className="rounded-md border border-destructive/20 bg-destructive/10 px-3 py-2 text-sm text-destructive">
{loginError}
</div>
)}
{/* Scrollable content */}
<div className="flex-1 overflow-y-auto px-6 py-4">
<div className="flex flex-col gap-5">
{/* ---- Shortcuts ---- */}
{shortcutsLoaded ? (
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<Label htmlFor="email">Email</Label> <p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
<Input Keyboard Shortcuts
id="email" </p>
type="email" <div className="flex flex-col gap-1.5">
placeholder="you@example.com" <ShortcutRecorder
required value={shortcuts.generalAssist}
value={email} onChange={(accel) => updateShortcut("generalAssist", accel)}
onChange={(e) => setEmail(e.target.value)} onReset={() => resetShortcut("generalAssist")}
disabled={isLoggingIn} defaultValue={DEFAULT_SHORTCUTS.generalAssist}
autoFocus label="General Assist"
/> description="Launch SurfSense instantly from any application"
</div> icon={Rocket}
/>
<div className="flex flex-col gap-2"> <ShortcutRecorder
<Label htmlFor="password">Password</Label> value={shortcuts.quickAsk}
<div className="relative"> onChange={(accel) => updateShortcut("quickAsk", accel)}
<Input onReset={() => resetShortcut("quickAsk")}
id="password" defaultValue={DEFAULT_SHORTCUTS.quickAsk}
type={showPassword ? "text" : "password"} label="Quick Assist"
placeholder="Enter your password" description="Select text anywhere, then ask AI to explain, rewrite, or act on it"
required icon={Zap}
value={password} />
onChange={(e) => setPassword(e.target.value)} <ShortcutRecorder
disabled={isLoggingIn} value={shortcuts.autocomplete}
className="pr-10" onChange={(accel) => updateShortcut("autocomplete", accel)}
onReset={() => resetShortcut("autocomplete")}
defaultValue={DEFAULT_SHORTCUTS.autocomplete}
label="Extreme Assist"
description="AI drafts text using your screen context and knowledge base"
icon={BrainCog}
/> />
<button
type="button"
onClick={() => setShowPassword((v) => !v)}
className="absolute inset-y-0 right-0 flex items-center pr-3 text-muted-foreground hover:text-foreground"
tabIndex={-1}
>
{showPassword ? <EyeOff className="size-4" /> : <Eye className="size-4" />}
</button>
</div> </div>
<p className="text-[11px] text-muted-foreground text-center mt-1">
Click a shortcut and press a new key combination to change it.
</p>
</div> </div>
) : (
<div className="flex justify-center py-6">
<Spinner size="sm" />
</div>
)}
<Button type="submit" disabled={isLoggingIn} className="mt-1"> <Separator />
{isLoggingIn ? (
<> {/* ---- Auth ---- */}
<Spinner size="sm" className="text-primary-foreground" /> <div className="flex flex-col gap-3">
Signing in... <p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
</> Sign In
) : ( </p>
"Sign in"
)} {isGoogleAuth ? (
</Button> <Button variant="outline" className="w-full gap-2 h-10" onClick={handleGoogleLogin}>
</form> <IconBrandGoogleFilled className="size-4" />
)} Continue with Google
</CardContent> </Button>
</Card> ) : (
<form onSubmit={handleLocalLogin} className="flex flex-col gap-3">
{loginError && (
<div className="rounded-md border border-destructive/20 bg-destructive/10 px-3 py-2 text-sm text-destructive">
{loginError}
</div>
)}
<div className="flex flex-col gap-1.5">
<Label htmlFor="email" className="text-xs">
Email
</Label>
<Input
id="email"
type="email"
placeholder="you@example.com"
required
value={email}
onChange={(e) => setEmail(e.target.value)}
disabled={isLoggingIn}
autoFocus
className="h-9"
/>
</div>
<div className="flex flex-col gap-1.5">
<Label htmlFor="password" className="text-xs">
Password
</Label>
<div className="relative">
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="Enter your password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
disabled={isLoggingIn}
className="h-9 pr-9"
/>
<button
type="button"
onClick={() => setShowPassword((v) => !v)}
className="absolute inset-y-0 right-0 flex items-center pr-2.5 text-muted-foreground hover:text-foreground"
tabIndex={-1}
>
{showPassword ? (
<EyeOff className="size-3.5" />
) : (
<Eye className="size-3.5" />
)}
</button>
</div>
</div>
<Button type="submit" disabled={isLoggingIn} className="h-9 mt-1">
{isLoggingIn ? (
<>
<Spinner size="sm" className="text-primary-foreground" />
Signing in
</>
) : (
"Sign in"
)}
</Button>
</form>
)}
</div>
</div>
</div>
</div>
</div> </div>
); );
} }

View file

@ -6,7 +6,7 @@ import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Accelerator display helpers // Accelerator <-> display helpers
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
export function keyEventToAccelerator(e: React.KeyboardEvent): string | null { export function keyEventToAccelerator(e: React.KeyboardEvent): string | null {
@ -47,13 +47,13 @@ export const DEFAULT_SHORTCUTS = {
export function Kbd({ keys, className }: { keys: string[]; className?: string }) { export function Kbd({ keys, className }: { keys: string[]; className?: string }) {
return ( return (
<span className={cn("inline-flex items-center gap-1", className)}> <span className={cn("inline-flex items-center gap-0.5", className)}>
{keys.map((key) => ( {keys.map((key, i) => (
<kbd <kbd
key={key} key={`${key}-${i}`}
className={cn( className={cn(
"inline-flex h-7 min-w-7 items-center justify-center rounded-md border bg-muted px-1.5 font-mono text-xs font-medium text-muted-foreground shadow-sm", "inline-flex h-6 min-w-6 items-center justify-center rounded border bg-muted px-1 font-mono text-[11px] font-medium text-muted-foreground",
key.length > 3 && "px-2" key.length > 3 && "px-1.5"
)} )}
> >
{key} {key}
@ -111,27 +111,29 @@ export function ShortcutRecorder({
const isDefault = value === defaultValue; const isDefault = value === defaultValue;
return ( return (
<div className="flex items-center justify-between gap-4 rounded-lg border bg-background p-3"> <div className="group flex items-center gap-3 rounded-lg border border-border/60 bg-card px-3 py-2.5 transition-colors hover:border-border">
<div className="flex items-center gap-3 min-w-0"> {/* Icon */}
<div className="flex size-9 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary"> <div className="flex size-8 shrink-0 items-center justify-center rounded-md bg-primary/10 text-primary">
<Icon className="size-4" /> <Icon className="size-4" />
</div>
<div className="min-w-0">
<p className="text-sm font-medium leading-none">{label}</p>
<p className="mt-1 text-xs text-muted-foreground truncate">{description}</p>
</div>
</div> </div>
<div className="flex items-center gap-2 shrink-0"> {/* Label + description */}
<div className="min-w-0 flex-1">
<p className="text-[13px] font-medium leading-none">{label}</p>
<p className="mt-1 text-[11px] leading-snug text-muted-foreground">{description}</p>
</div>
{/* Actions */}
<div className="flex shrink-0 items-center gap-1">
{!isDefault && ( {!isDefault && (
<Button <Button
variant="ghost" variant="ghost"
size="icon" size="icon"
className="size-7" className="size-6 opacity-0 group-hover:opacity-100 transition-opacity"
onClick={onReset} onClick={onReset}
title="Reset to default" title="Reset to default"
> >
<RotateCcw /> <RotateCcw className="size-3" />
</Button> </Button>
)} )}
<button <button
@ -141,14 +143,14 @@ export function ShortcutRecorder({
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
onBlur={() => setRecording(false)} onBlur={() => setRecording(false)}
className={cn( className={cn(
"flex h-9 items-center gap-1 rounded-md border px-3 text-sm transition-all focus:outline-none", "flex h-7 items-center gap-0.5 rounded-md border px-2 transition-all focus:outline-none",
recording recording
? "border-primary bg-primary/5 ring-2 ring-primary/20" ? "border-primary bg-primary/5 ring-2 ring-primary/20"
: "border-input bg-muted/50 hover:bg-muted" : "border-input bg-muted/40 hover:bg-muted"
)} )}
> >
{recording ? ( {recording ? (
<span className="text-xs text-primary animate-pulse">Press keys...</span> <span className="text-[11px] text-primary animate-pulse whitespace-nowrap">Press keys</span>
) : ( ) : (
<Kbd keys={displayKeys} /> <Kbd keys={displayKeys} />
)} )}