Browse Source

fix: address PR feedback (security & UX)

handoff-20260429-1057
CatoPaus 7 months ago
parent
commit
5560aecf94
  1. 21
      src/app/api/open-directory/route.ts
  2. 49
      src/components/Header.tsx

21
src/app/api/open-directory/route.ts

@ -1,10 +1,10 @@
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import { exec } from "child_process"; import { execFile } from "child_process";
import { promisify } from "util"; import { promisify } from "util";
import os from "os"; import os from "os";
const execAsync = promisify(exec); const execFileAsync = promisify(execFile);
export async function POST(req: NextRequest) { export async function POST(req: NextRequest) {
try { try {
@ -19,24 +19,29 @@ export async function POST(req: NextRequest) {
} }
let command = ""; let command = "";
let args: string[] = [];
const platform = os.platform(); const platform = os.platform();
switch (platform) { switch (platform) {
case "darwin": case "darwin":
command = `open "${path}"`; command = "open";
args = [path];
break; break;
case "win32": case "win32":
command = `explorer "${path}"`; command = "explorer";
args = [path];
break; break;
case "linux": case "linux":
command = `xdg-open "${path}"`; command = "xdg-open";
args = [path];
break; break;
default: default:
// Fallback for other Unix-like systems, might not work everywhere // Fallback for other Unix-like systems
command = `xdg-open "${path}"`; command = "xdg-open";
args = [path];
} }
await execAsync(command); await execFileAsync(command, args);
return NextResponse.json({ success: true }); return NextResponse.json({ success: true });
} catch (error) { } catch (error) {

49
src/components/Header.tsx

@ -81,15 +81,24 @@ export function Header() {
if (!saveDirectoryPath) return; if (!saveDirectoryPath) return;
try { try {
await fetch("/api/open-directory", { const response = await fetch("/api/open-directory", {
method: "POST", method: "POST",
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
}, },
body: JSON.stringify({ path: saveDirectoryPath }), body: JSON.stringify({ path: saveDirectoryPath }),
}); });
const result = await response.json();
if (!response.ok || !result.success) {
console.error("Failed to open directory:", result.error);
alert(`Failed to open project folder: ${result.error || "Unknown error"}`);
return;
}
} catch (error) { } catch (error) {
console.error("Failed to open directory:", error); console.error("Failed to open directory:", error);
alert("Failed to open project folder. Please try again.");
} }
}; };
@ -142,25 +151,27 @@ export function Header() {
<span className="absolute -top-0.5 -right-0.5 w-2 h-2 rounded-full bg-red-500" /> <span className="absolute -top-0.5 -right-0.5 w-2 h-2 rounded-full bg-red-500" />
)} )}
</button> </button>
<button {saveDirectoryPath && (
onClick={handleOpenDirectory} <button
className="p-1 text-neutral-400 hover:text-neutral-200 transition-colors" onClick={handleOpenDirectory}
title="Open Project Folder" className="p-1 text-neutral-400 hover:text-neutral-200 transition-colors"
> title="Open Project Folder"
<svg
className="w-4 h-4"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
> >
<path <svg
strokeLinecap="round" className="w-4 h-4"
strokeLinejoin="round" fill="none"
d="M2.25 12.75V12A2.25 2.25 0 0 1 4.5 9.75h15A2.25 2.25 0 0 1 21.75 12v.75m-8.69-6.44-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z" viewBox="0 0 24 24"
/> stroke="currentColor"
</svg> strokeWidth={2}
</button> >
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M2.25 12.75V12A2.25 2.25 0 0 1 4.5 9.75h15A2.25 2.25 0 0 1 21.75 12v.75m-8.69-6.44-2.12-2.12a1.5 1.5 0 0 0-1.061-.44H4.5A2.25 2.25 0 0 0 2.25 6v12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 18V9a2.25 2.25 0 0 0-2.25-2.25h-5.379a1.5 1.5 0 0 1-1.06-.44Z"
/>
</svg>
</button>
)}
<button <button
onClick={handleOpenSettings} onClick={handleOpenSettings}
className="p-1 text-neutral-400 hover:text-neutral-200 transition-colors" className="p-1 text-neutral-400 hover:text-neutral-200 transition-colors"

Loading…
Cancel
Save