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 || [];
+}