feat: add LoginHandler component for executing login actions

- Created LoginHandler component to process /login command results
- Handles three action types:
  - add-account: Adds account to AccountManager and sets as active
  - error: Shows error toast with descriptive message
  - open-dialog: Shows info toast (dialog UI coming in next phase)
- Uses sonner for toast notifications with success/error feedback
- Integrated with WindowRenderer routing system
- Component auto-executes action on mount via useEffect

Files:
- src/components/LoginHandler.tsx (new)
- src/components/WindowRenderer.tsx (updated)
This commit is contained in:
Claude
2026-01-04 19:22:57 +00:00
parent ab0aa7fadf
commit ade643f61c
2 changed files with 105 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
import { useEffect } from "react";
import { toast } from "sonner";
import { Check, X, Info } from "lucide-react";
import type { ReadOnlyAccount } from "@/lib/account-types";
import accountManager from "@/services/accounts";
interface LoginHandlerProps {
action: "add-account" | "error" | "open-dialog";
account?: ReadOnlyAccount;
message?: string;
}
/**
* LoginHandler - Executes login command actions
*
* This component handles the result of the /login command:
* - add-account: Adds account to AccountManager and sets as active
* - error: Shows error toast
* - open-dialog: Shows info about opening login dialog (UI coming soon)
*/
export default function LoginHandler({
action,
account,
message,
}: LoginHandlerProps) {
useEffect(() => {
const handleAction = async () => {
switch (action) {
case "add-account":
if (!account) {
toast.error("Failed to add account", {
description: "No account provided",
icon: <X className="h-4 w-4" />,
});
return;
}
try {
// Add account to manager
accountManager.addAccount(account);
// Set as active account
accountManager.setActive(account.id);
// Show success toast
toast.success("Account added successfully", {
description: `Logged in as ${account.pubkey.slice(0, 8)}...${account.pubkey.slice(-8)}`,
icon: <Check className="h-4 w-4" />,
});
} catch (error) {
toast.error("Failed to add account", {
description:
error instanceof Error ? error.message : "Unknown error",
icon: <X className="h-4 w-4" />,
});
}
break;
case "error":
toast.error("Login failed", {
description: message || "Unknown error occurred",
icon: <X className="h-4 w-4" />,
});
break;
case "open-dialog":
toast.info("Login dialog", {
description:
"Login dialog UI coming soon. For now, use: login <npub|nip-05|hex|nprofile>",
icon: <Info className="h-4 w-4" />,
duration: 5000,
});
break;
default:
toast.error("Unknown action", {
description: `Unhandled action: ${action}`,
icon: <X className="h-4 w-4" />,
});
}
};
handleAction();
}, [action, account, message]);
// This component doesn't render anything visible - it just executes the action
return (
<div className="flex items-center justify-center h-full w-full p-8">
<div className="text-center text-muted-foreground space-y-2">
<p className="text-sm">Processing login...</p>
<p className="text-xs">This window can be closed.</p>
</div>
</div>
);
}

View File

@@ -33,6 +33,7 @@ const SpellsViewer = lazy(() =>
const SpellbooksViewer = lazy(() =>
import("./SpellbooksViewer").then((m) => ({ default: m.SpellbooksViewer })),
);
const LoginHandler = lazy(() => import("./LoginHandler"));
// Loading fallback component
function ViewerLoading() {
@@ -175,6 +176,15 @@ export function WindowRenderer({ window, onClose }: WindowRendererProps) {
case "spellbooks":
content = <SpellbooksViewer />;
break;
case "login-handler":
content = (
<LoginHandler
action={window.props.action}
account={window.props.account}
message={window.props.message}
/>
);
break;
default:
content = (
<div className="p-4 text-muted-foreground">