the universal sandbox runtime for agents and humans. pocketenv.io
sandbox openclaw agent claude-code vercel-sandbox deno-sandbox cloudflare-sandbox atproto sprites daytona
7
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add SignIn modal and login flow

Create a SignIn modal component and index. Replace the navbar
sign-in link with a button that opens the modal and add sign-in
state. Add a Login link on the home navbar and update the
/​signin page to redirect to API_URL/login?handle=… when submitting.
Remove an unused consola import.

+160 -8
+11 -3
apps/web/src/components/navbar/Navbar.tsx
··· 4 4 import { useState, useEffect, useRef } from "react"; 5 5 import NewProject from "../newproject"; 6 6 import Logo from "../../assets/logo.png"; 7 + import SignIn from "../signin"; 7 8 8 9 export type NavbarProps = { 9 10 title: string; ··· 14 15 function Navbar({ title, project, withLogo }: NavbarProps) { 15 16 const [open, setOpen] = useState(false); 16 17 const [modalOpen, setModalOpen] = useState(false); 18 + const [signInModalOpen, setSignInModalOpen] = useState(false); 17 19 const dropdownRef = useRef<HTMLDivElement>(null); 18 20 const navigate = useNavigate(); 19 21 ··· 175 177 </div> 176 178 )} 177 179 {true && ( 178 - <Link 179 - to="/signin" 180 + <button 181 + onClick={() => setSignInModalOpen(true)} 180 182 className="btn btn-block bg-blue-600/20 border-none text-blue-500 font-extrabold w-[100px]" 181 183 > 182 184 Sign in 183 - </Link> 185 + </button> 184 186 )} 185 187 </div> 186 188 <NewProject 187 189 isOpen={modalOpen} 188 190 onClose={() => { 189 191 setModalOpen(false); 192 + }} 193 + /> 194 + <SignIn 195 + isOpen={signInModalOpen} 196 + onClose={() => { 197 + setSignInModalOpen(false); 190 198 }} 191 199 /> 192 200 </nav>
-1
apps/web/src/components/newproject/NewProject.tsx
··· 4 4 useSandboxesQuery, 5 5 } from "../../hooks/useSandbox"; 6 6 import { useNavigate } from "@tanstack/react-router"; 7 - import consola from "consola"; 8 7 9 8 export type NewProjectProps = { 10 9 isOpen: boolean;
+124
apps/web/src/components/signin/SignIn.tsx
··· 1 + import { useEffect, useRef, useState } from "react"; 2 + import { API_URL } from "../../consts"; 3 + 4 + export type SignInProps = { 5 + isOpen: boolean; 6 + onClose: () => void; 7 + }; 8 + 9 + function SignIn({ isOpen, onClose }: SignInProps) { 10 + const [handle, setHandle] = useState(""); 11 + const inputRef = useRef<HTMLInputElement>(null); 12 + 13 + const onSignIn = () => { 14 + if (!handle) { 15 + return; 16 + } 17 + window.location.href = `${API_URL}/login?handle=${handle}`; 18 + onClose(); 19 + }; 20 + 21 + const handleBackdropClick = (e: React.MouseEvent<HTMLDivElement>) => { 22 + if (e.target === e.currentTarget) { 23 + onClose(); 24 + } 25 + }; 26 + 27 + useEffect(() => { 28 + if (isOpen) { 29 + inputRef.current?.focus(); 30 + } 31 + }, [isOpen]); 32 + 33 + useEffect(() => { 34 + const handleEscapeKey = (event: KeyboardEvent) => { 35 + if (event.key === "Escape" && isOpen) { 36 + setHandle(""); 37 + onClose(); 38 + } 39 + }; 40 + 41 + document.addEventListener("keydown", handleEscapeKey); 42 + return () => { 43 + document.removeEventListener("keydown", handleEscapeKey); 44 + }; 45 + }, [isOpen, onClose]); 46 + 47 + return ( 48 + <> 49 + <div 50 + className={`overlay modal modal-middle overlay-open:opacity-100 overlay-open:duration-300 open ${isOpen ? "opened" : "hidden"}`} 51 + role="dialog" 52 + style={{ outline: "none" }} 53 + onClick={handleBackdropClick} 54 + > 55 + <div className="overlay-animation-target modal-dialog overlay-open:mt-4 overlay-open:duration-300 mt-12 transition-all ease-out"> 56 + <div className="modal-content"> 57 + <div className="modal-header"></div> 58 + <div className="modal-body"> 59 + <div className="flex flex-col items-center gap-6 w-[400px]"> 60 + <div className="form-control w-full"> 61 + <label className="label"> 62 + <span className="label-text text-[15px]">Handle</span> 63 + </label> 64 + <div className="input input-bordered w-full input-lg text-[15px] font-semibold bg-transparent focus-within:border-pink-500! outline-none!"> 65 + <span className="label-text my-auto text-[16px] opacity-50 mr-[10px]"> 66 + @ 67 + </span> 68 + <input 69 + placeholder="alice.bsky.social" 70 + className="grow " 71 + ref={inputRef} 72 + value={handle} 73 + onChange={(e) => setHandle(e.target.value)} 74 + autoFocus 75 + /> 76 + </div> 77 + </div> 78 + 79 + <button 80 + className="btn btn-lg font-bold bg-pink-500 border-none w-full" 81 + onClick={onSignIn} 82 + > 83 + Sign In 84 + </button> 85 + <p className="text-center font-semibold text-white/70"> 86 + Don't have an atproto handle yet? You can create one at 87 + <a 88 + href={`${API_URL}/login?prompt=create`} 89 + className="text-pink-400" 90 + > 91 + selfhosted.social 92 + </a> 93 + ,{" "} 94 + <a 95 + href="https://bsky.app/" 96 + className="text-pink-400" 97 + target="_blank" 98 + > 99 + Bluesky 100 + </a>{" "} 101 + or any other AT Protocol service. 102 + </p> 103 + </div> 104 + </div> 105 + </div> 106 + </div> 107 + </div> 108 + 109 + {isOpen && ( 110 + <div 111 + id="slide-down-animated-modal-backdrop" 112 + data-overlay-backdrop-template="" 113 + style={{ zIndex: 79 }} 114 + className="overlay-backdrop transition duration-300 fixed inset-0 bg-base-300/60 overflow-y-auto opacity-75" 115 + onClick={() => { 116 + onClose(); 117 + }} 118 + ></div> 119 + )} 120 + </> 121 + ); 122 + } 123 + 124 + export default SignIn;
+3
apps/web/src/components/signin/index.tsx
··· 1 + import Signin from "./Signin"; 2 + 3 + export default Signin;
+5
apps/web/src/pages/home/Navbar/Navbar.tsx
··· 36 36 <span className="icon-[mdi--github] size-7 mt-[8px]"></span> 37 37 </a> 38 38 </div> 39 + <div className="ml-[10px]"> 40 + <Link to="/signin" className="text-[15px]"> 41 + Login 42 + </Link> 43 + </div> 39 44 <div> 40 45 <button 41 46 onClick={() => setModalOpen(true)}
+17 -4
apps/web/src/pages/signin/SignIn.tsx
··· 1 1 import { useNavigate } from "@tanstack/react-router"; 2 + import { useState } from "react"; 3 + import { API_URL } from "../../consts"; 2 4 3 5 function SignIn() { 4 - const navigate = useNavigate(); 6 + const [handle, setHandle] = useState(""); 5 7 6 8 const onSignIn = () => { 7 - navigate({ to: "/projects" }); 9 + if (!handle) { 10 + return; 11 + } 12 + window.location.href = `${API_URL}/login?handle=${handle}`; 8 13 }; 9 14 10 15 return ( ··· 15 20 <label className="label"> 16 21 <span className="label-text text-[15px]">Handle</span> 17 22 </label> 18 - <div className="input input-bordered w-full input-lg text-[15px] font-semibold bg-transparent focus-within:border-pink-500!"> 23 + <div className="input input-bordered w-full input-lg text-[15px] font-semibold bg-transparent focus-within:border-pink-500! outline-none!"> 19 24 <span className="label-text my-auto text-[16px] opacity-50 mr-[10px]"> 20 25 @ 21 26 </span> 22 27 <input 23 28 placeholder="alice.bsky.social" 24 29 className="grow " 30 + value={handle} 31 + onChange={(e) => setHandle(e.target.value)} 25 32 autoFocus 26 33 /> 27 34 </div> ··· 35 42 </button> 36 43 <p className="text-center text-white/70"> 37 44 Don't have an atproto handle yet? You can create one at 38 - <button className="text-pink-400">selfhosted.social</button>,{" "} 45 + <a 46 + href={`${API_URL}/login?prompt=create`} 47 + className="text-pink-400" 48 + > 49 + selfhosted.social 50 + </a> 51 + ,{" "} 39 52 <a 40 53 href="https://bsky.app/" 41 54 className="text-pink-400"