the universal sandbox runtime for agents and humans.
pocketenv.io
sandbox
openclaw
agent
claude-code
vercel-sandbox
deno-sandbox
cloudflare-sandbox
atproto
sprites
daytona
1import { useNavigate } from "@tanstack/react-router";
2import type { Sandbox } from "../../../types/sandbox";
3import _ from "lodash";
4import dayjs from "dayjs";
5import { useState } from "react";
6import {
7 useStartSandboxMutation,
8 useStopSandboxMutation,
9} from "../../../hooks/useSandbox";
10import { useQueryClient } from "@tanstack/react-query";
11import { useAtomValue } from "jotai";
12import { profileAtom } from "../../../atoms/profile";
13import TerminalModal from "./TerminalModal";
14import ContextMenu from "../../../components/contextmenu";
15
16export type ProjectProps = {
17 sandbox: Sandbox;
18};
19
20function Project({ sandbox }: ProjectProps) {
21 const navigate = useNavigate();
22 const queryClient = useQueryClient();
23 const [modalOpen, setModalOpen] = useState(false);
24 const profile = useAtomValue(profileAtom);
25 const { mutateAsync: stopSandbox } = useStopSandboxMutation();
26 const { mutateAsync: startSandbox } = useStartSandboxMutation();
27 const [displayLoading, setDisplayLoading] = useState(false);
28
29 const onPlay = async (e: React.MouseEvent) => {
30 e.stopPropagation();
31 setDisplayLoading(true);
32 await startSandbox(sandbox.id);
33 queryClient.invalidateQueries({
34 queryKey: ["actorSandboxes", profile?.did],
35 });
36 setDisplayLoading(false);
37 };
38
39 const onStop = async (e: React.MouseEvent) => {
40 e.stopPropagation();
41 setDisplayLoading(true);
42 await stopSandbox(sandbox.id);
43 queryClient.invalidateQueries({
44 queryKey: ["actorSandboxes", profile?.did],
45 });
46 setDisplayLoading(false);
47 };
48
49 const onOpenTerminal = (e: React.MouseEvent) => {
50 e.stopPropagation();
51 if (sandbox.status !== "RUNNING") return;
52 setModalOpen(true);
53 };
54
55 const onOpenProject = () => {
56 navigate({
57 to: `/${sandbox.uri.split("at://")[1].replace("io.pocketenv.", "")}`,
58 });
59 };
60
61 return (
62 <tr className="cursor-pointer" onClick={onOpenProject}>
63 <td>{sandbox.name}</td>
64 <td>{sandbox.baseSandbox}</td>
65 <td>
66 <span
67 className={`badge badge-soft ${sandbox?.status === "RUNNING" ? "badge-success" : ""} rounded-full ${sandbox.status === "RUNNING" ? "bg-green-400/10" : "bg-white/15 rounded"}`}
68 >
69 {_.upperFirst(_.camelCase(sandbox.status))}
70 </span>
71 </td>
72 <td>
73 <span className="badge badge-soft badge-primary bg-blue-400/10 rounded-full">
74 {sandbox?.vcpus} CPU
75 </span>
76 <span className="badge badge-soft badge-primary bg-blue-400/10 rounded-full ml-2">
77 {sandbox?.memory} GiB RAM
78 </span>
79 </td>
80 <td>{dayjs(sandbox.createdAt).format("M/D/YYYY, h:mm:ss A")}</td>
81 <td className="align-middle">
82 <div
83 className="flex items-center justify-center p-1"
84 onClick={(e) => e.stopPropagation()}
85 >
86 {!displayLoading && sandbox.status === "RUNNING" && (
87 <button
88 className="btn btn-circle btn-text btn-sm bg-transparent outline-0"
89 onClick={onStop}
90 >
91 <span className="icon-[tabler--player-stop] size-5 hover:text-white"></span>
92 </button>
93 )}
94 {!displayLoading && sandbox.status !== "RUNNING" && (
95 <button
96 className="btn btn-circle btn-text btn-sm bg-transparent outline-0"
97 onClick={onPlay}
98 >
99 <span className="icon-[tabler--player-play] size-5 hover:text-white"></span>
100 </button>
101 )}
102 {displayLoading && (
103 <span className="loading loading-spinner loading-sm btn-text mr-[10px]"></span>
104 )}
105 <button
106 className={`btn btn-circle btn-text btn-sm bg-transparent outline-0 ${sandbox.status !== "RUNNING" ? "opacity-50" : ""}`}
107 onClick={onOpenTerminal}
108 >
109 <span
110 className={`icon-[mingcute--terminal-fill] size-5 ${sandbox.status !== "RUNNING" ? "" : "hover:text-white"}`}
111 ></span>
112 </button>
113 <ContextMenu sandboxId={sandbox.id} uri={sandbox.uri} />
114 </div>
115
116 <TerminalModal
117 title={sandbox.name}
118 isOpen={modalOpen}
119 onClose={() => {
120 setModalOpen(false);
121 }}
122 sandboxId={sandbox.id}
123 isCloudflare={sandbox.provider === "cloudflare"}
124 isTty={["sprites", "vercel"].includes(sandbox.provider)}
125 pty={sandbox.provider === "vercel"}
126 worker={sandbox.baseSandbox}
127 />
128 </td>
129 </tr>
130 );
131}
132
133export default Project;