diff --git a/components/Sandboxes/SandboxCreateSection.tsx b/components/Sandboxes/SandboxCreateSection.tsx index 40b895335..724b04ad9 100644 --- a/components/Sandboxes/SandboxCreateSection.tsx +++ b/components/Sandboxes/SandboxCreateSection.tsx @@ -6,14 +6,13 @@ import { toast } from "sonner"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import useCreateSandbox from "@/hooks/useCreateSandbox"; -import type { Sandbox } from "@/lib/sandboxes/createSandbox"; interface SandboxCreateSectionProps { - onSandboxCreated: (sandboxes: Sandbox[]) => void; + onSuccess: () => void; } export default function SandboxCreateSection({ - onSandboxCreated, + onSuccess, }: SandboxCreateSectionProps) { const [prompt, setPrompt] = useState(""); const { createSandbox, isCreating } = useCreateSandbox(); @@ -25,10 +24,10 @@ export default function SandboxCreateSection({ } try { - const newSandboxes = await createSandbox(prompt); - onSandboxCreated(newSandboxes); + await createSandbox(prompt); toast.success("Sandbox created successfully"); setPrompt(""); + onSuccess(); } catch { // Error is handled by the hook } diff --git a/components/Sandboxes/SandboxList.tsx b/components/Sandboxes/SandboxList.tsx index 425c70d81..db13cfbd3 100644 --- a/components/Sandboxes/SandboxList.tsx +++ b/components/Sandboxes/SandboxList.tsx @@ -1,26 +1,25 @@ import type { Sandbox } from "@/lib/sandboxes/createSandbox"; +import SandboxListCard from "./SandboxListCard"; interface SandboxListProps { sandboxes: Sandbox[]; } export default function SandboxList({ sandboxes }: SandboxListProps) { - if (sandboxes.length === 0) return null; + if (sandboxes.length === 0) { + return ( +
+

No sandboxes yet. Create one above to get started.

+
+ ); + } return (
-

Created Sandboxes

+

Sandbox History

{sandboxes.map((sandbox) => ( -
-

{sandbox.sandboxId}

-

- Status: {sandbox.sandboxStatus} -

-
+ ))}
diff --git a/components/Sandboxes/SandboxListCard.tsx b/components/Sandboxes/SandboxListCard.tsx new file mode 100644 index 000000000..a40bddbd5 --- /dev/null +++ b/components/Sandboxes/SandboxListCard.tsx @@ -0,0 +1,34 @@ +import type { Sandbox } from "@/lib/sandboxes/createSandbox"; + +interface SandboxListCardProps { + sandbox: Sandbox; +} + +const statusColors: Record = { + pending: "bg-yellow-500", + running: "bg-green-500", + stopping: "bg-orange-500", + stopped: "bg-gray-500", + failed: "bg-red-500", +}; + +export default function SandboxListCard({ sandbox }: SandboxListCardProps) { + return ( +
+
+

{sandbox.sandboxId}

+
+ + + {sandbox.sandboxStatus} + +
+
+

+ Created: {new Date(sandbox.createdAt).toLocaleString()} +

+
+ ); +} diff --git a/components/Sandboxes/SandboxesPage.tsx b/components/Sandboxes/SandboxesPage.tsx index 7eec62324..013f7d743 100644 --- a/components/Sandboxes/SandboxesPage.tsx +++ b/components/Sandboxes/SandboxesPage.tsx @@ -1,22 +1,35 @@ "use client"; -import { useState } from "react"; +import { Loader } from "lucide-react"; import SandboxCreateSection from "@/components/Sandboxes/SandboxCreateSection"; import SandboxList from "@/components/Sandboxes/SandboxList"; -import type { Sandbox } from "@/lib/sandboxes/createSandbox"; +import useSandboxes from "@/hooks/useSandboxes"; export default function SandboxesPage() { - const [sandboxes, setSandboxes] = useState([]); - - const handleSandboxCreated = (newSandboxes: Sandbox[]) => { - setSandboxes((prev) => [...newSandboxes, ...prev]); - }; + const { sandboxes, isLoading, error, refetch } = useSandboxes(); return (

Sandboxes

- - + + {isLoading ? ( +
+ + Loading sandboxes... +
+ ) : error ? ( +
+

Failed to load sandboxes

+ +
+ ) : ( + + )}
); } diff --git a/hooks/useSandboxes.ts b/hooks/useSandboxes.ts new file mode 100644 index 000000000..fbe233da6 --- /dev/null +++ b/hooks/useSandboxes.ts @@ -0,0 +1,34 @@ +import { useQuery } from "@tanstack/react-query"; +import { usePrivy } from "@privy-io/react-auth"; +import { getSandboxes } from "@/lib/sandboxes/getSandboxes"; +import type { Sandbox } from "@/lib/sandboxes/createSandbox"; + +interface UseSandboxesReturn { + sandboxes: Sandbox[]; + isLoading: boolean; + error: Error | null; + refetch: () => void; +} + +export default function useSandboxes(): UseSandboxesReturn { + const { getAccessToken, authenticated } = usePrivy(); + + const query = useQuery({ + queryKey: ["sandboxes"], + queryFn: async () => { + const accessToken = await getAccessToken(); + if (!accessToken) { + throw new Error("Please sign in to view sandboxes"); + } + return getSandboxes(accessToken); + }, + enabled: authenticated, + }); + + return { + sandboxes: query.data || [], + isLoading: query.isLoading, + error: query.error, + refetch: query.refetch, + }; +} diff --git a/lib/sandboxes/getSandboxes.ts b/lib/sandboxes/getSandboxes.ts new file mode 100644 index 000000000..37057fbbd --- /dev/null +++ b/lib/sandboxes/getSandboxes.ts @@ -0,0 +1,25 @@ +import { NEW_API_BASE_URL } from "@/lib/consts"; +import type { Sandbox } from "./createSandbox"; + +interface GetSandboxesResponse { + status: "success" | "error"; + sandboxes?: Sandbox[]; + error?: string; +} + +export async function getSandboxes(accessToken: string): Promise { + const response = await fetch(`${NEW_API_BASE_URL}/api/sandboxes`, { + method: "GET", + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }); + + const data: GetSandboxesResponse = await response.json(); + + if (!response.ok || data.status === "error") { + throw new Error(data.error || "Failed to fetch sandboxes"); + } + + return data.sandboxes || []; +}