aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCodeTorso <[email protected]>2024-07-01 06:10:13 +0530
committerGitHub <[email protected]>2024-07-01 06:10:13 +0530
commit38565f2ec45390c79101b752d366656bc70fd4d7 (patch)
tree20b3a9c9c43a109028b06b6cf3b3bc199a7c2771
parentfix typescript errors (diff)
parentcanvas (3/3) (diff)
downloadsupermemory-38565f2ec45390c79101b752d366656bc70fd4d7.tar.xz
supermemory-38565f2ec45390c79101b752d366656bc70fd4d7.zip
Merge pull request #90 from Dhravya/editor
canvas (3/3)
-rw-r--r--apps/web/app/(canvas)/canvas/page.tsx3
-rw-r--r--apps/web/app/(canvas)/canvas/search&create.tsx22
-rw-r--r--apps/web/app/(canvas)/canvas/thinkPad.tsx274
-rw-r--r--apps/web/app/(canvas)/canvasStyles.css2
-rw-r--r--apps/web/app/(canvas)/layout.tsx13
-rw-r--r--apps/web/app/actions/doers.ts35
-rw-r--r--apps/web/app/actions/fetchers.ts2
-rw-r--r--apps/web/app/api/canvasai/route.ts27
-rw-r--r--apps/web/components/canvas/draggableComponent.tsx28
-rw-r--r--apps/web/components/canvas/resizableLayout.tsx57
-rw-r--r--apps/web/components/canvas/savesnap.tsx5
-rw-r--r--apps/web/lib/loadSnap.ts2
12 files changed, 397 insertions, 73 deletions
diff --git a/apps/web/app/(canvas)/canvas/page.tsx b/apps/web/app/(canvas)/canvas/page.tsx
index 815d0b93..d0824a20 100644
--- a/apps/web/app/(canvas)/canvas/page.tsx
+++ b/apps/web/app/(canvas)/canvas/page.tsx
@@ -6,10 +6,9 @@ import ThinkPads from "./thinkPads";
async function page() {
const canvas = await getCanvas();
return (
- <div className="h-screen w-full bg-[#171B1F] py-32 text-[#FFFFFF] ">
+ <div className="h-screen w-full py-32 text-[#FFFFFF] ">
<div className="flex w-full flex-col items-center gap-8">
<h1 className="text-4xl font-medium">Your thinkpads</h1>
- <p>{JSON.stringify(canvas)}</p>
<SearchandCreate />
{
// @ts-ignore
diff --git a/apps/web/app/(canvas)/canvas/search&create.tsx b/apps/web/app/(canvas)/canvas/search&create.tsx
index 3998dde7..a52a4cbe 100644
--- a/apps/web/app/(canvas)/canvas/search&create.tsx
+++ b/apps/web/app/(canvas)/canvas/search&create.tsx
@@ -1,9 +1,10 @@
-"use client"
+"use client";
import { useFormStatus } from "react-dom";
import Image from "next/image";
import { SearchIcon } from "@repo/ui/icons";
import { createCanvas } from "@/app/actions/doers";
+import { toast } from "sonner";
export default function SearchandCreate() {
return (
@@ -18,18 +19,27 @@ export default function SearchandCreate() {
</button>
</div>
- <form action={createCanvas}>
- <Button />
+ <form
+ action={async () => {
+ const res = await createCanvas();
+ if (!res.success){
+ toast.warning(res.message, {
+ style: {backgroundColor: "rgb(22 31 42 / 0.3)"}
+ });
+ }
+ }}
+ >
+ <Button />
</form>
</div>
);
}
function Button() {
- const {pending} = useFormStatus()
+ const { pending } = useFormStatus();
return (
<button className="rounded-xl bg-[#1F2428] px-5 py-3 text-xl text-[#B8C4C6]">
- {pending? "Creating.." : "Create New"}
+ {pending ? "Creating.." : "Create New"}
</button>
);
-} \ No newline at end of file
+}
diff --git a/apps/web/app/(canvas)/canvas/thinkPad.tsx b/apps/web/app/(canvas)/canvas/thinkPad.tsx
index dad2de48..00a83fda 100644
--- a/apps/web/app/(canvas)/canvas/thinkPad.tsx
+++ b/apps/web/app/(canvas)/canvas/thinkPad.tsx
@@ -1,5 +1,13 @@
-import {motion} from "framer-motion"
+import { getCanvasData } from "@/app/actions/fetchers";
+import { AnimatePresence, motion } from "framer-motion";
import Link from "next/link";
+import {
+ EllipsisHorizontalCircleIcon,
+ TrashIcon,
+ PencilSquareIcon,
+} from "@heroicons/react/24/outline";
+import { toast } from "sonner";
+import { Label } from "@repo/ui/shadcn/label";
const childVariants = {
hidden: { opacity: 0, y: 10, filter: "blur(2px)" },
@@ -10,27 +18,259 @@ export default function ThinkPad({
title,
description,
image,
- id
+ id,
}: {
title: string;
description: string;
image: string;
id: string;
}) {
+ const [deleted, setDeleted] = useState(false);
+ const [info, setInfo] = useState({ title, description });
return (
- <motion.div
- variants={childVariants}
- className="flex h-48 gap-4 rounded-2xl bg-[#1F2428] p-2"
- >
- <Link className="h-full min-w-[40%] rounded-xl bg-[#363f46]" href={`/canvas/${id}`}>
- <div></div>
- </Link>
- <div className="flex flex-col gap-2">
- <div>{title}</div>
- <div className="overflow-hidden text-ellipsis text-[#B8C4C6]">
- {description}
- </div>
- </div>
- </motion.div>
+ <AnimatePresence mode="sync">
+ {!deleted && (
+ <motion.div
+ layout
+ exit={{ opacity: 0, scaleY: 0 }}
+ variants={childVariants}
+ className="flex h-48 origin-top relative gap-4 rounded-2xl bg-[#1F2428] p-2"
+ >
+ <Link
+ className="h-full select-none min-w-[40%] bg-[#363f46] rounded-xl overflow-hidden"
+ href={`/canvas/${id}`}
+ >
+ <Suspense
+ fallback={
+ <div className=" h-full w-full flex justify-center items-center">
+ Loading...
+ </div>
+ }
+ >
+ <ImageComponent id={id} />
+ </Suspense>
+ </Link>
+ <div className="flex flex-col gap-2">
+ <motion.h2
+ initial={{ opacity: 0, filter: "blur(3px)" }}
+ animate={{ opacity: 1, filter: "blur(0px)" }}
+ key={info.title}
+ >
+ {info.title}
+ </motion.h2>
+ <motion.h3
+ key={info.description}
+ initial={{ opacity: 0, filter: "blur(3px)" }}
+ animate={{ opacity: 1, filter: "blur(0px)" }}
+ className="overflow-hidden text-ellipsis text-[#B8C4C6]"
+ >
+ {info.description}
+ </motion.h3>
+ </div>
+ <Menu
+ info={info}
+ id={id}
+ setDeleted={() => setDeleted(true)}
+ setInfo={(e) => setInfo(e)}
+ />
+ </motion.div>
+ )}
+ </AnimatePresence>
);
-} \ No newline at end of file
+}
+
+import {
+ Popover,
+ PopoverContent,
+ PopoverTrigger,
+} from "@repo/ui/shadcn/popover";
+
+function Menu({
+ info,
+ id,
+ setDeleted,
+ setInfo,
+}: {
+ info: { title: string; description: string };
+ id: string;
+ setDeleted: () => void;
+ setInfo: ({
+ title,
+ description,
+ }: {
+ title: string;
+ description: string;
+ }) => void;
+}) {
+ return (
+ <Popover>
+ <PopoverTrigger className="absolute z-20 top-0 right-0" asChild>
+ <Button variant="secondary">
+ <EllipsisHorizontalCircleIcon className="size-5 stroke-2 stroke-[#B8C4C6]" />
+ </Button>
+ </PopoverTrigger>
+ <PopoverContent
+ align="start"
+ className="w-32 px-2 py-2 bg-[#161f2a]/30 text-[#B8C4C6] border-border flex flex-col gap-3"
+ >
+ <EditToolbar info={info} id={id} setInfo={setInfo} />
+ <Button
+ onClick={async () => {
+ const res = await deleteCanvas(id);
+ if (res.success) {
+ toast.success("Thinkpad removed.", {
+ style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
+ });
+ setDeleted();
+ } else {
+ toast.warning("Something went wrong.", {
+ style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
+ });
+ }
+ }}
+ className="flex gap-2 border-border"
+ variant="outline"
+ >
+ <TrashIcon className="size-8 stroke-1" /> Delete
+ </Button>
+ </PopoverContent>
+ </Popover>
+ );
+}
+
+function EditToolbar({
+ id,
+ setInfo,
+ info
+}: {
+ id: string;
+ setInfo: ({
+ title,
+ description,
+ }: {
+ title: string;
+ description: string;
+ }) => void;
+ info: {
+ title: string;
+ description: string;
+ }
+}) {
+ const [open, setOpen] = useState(false);
+ return (
+ <Dialog open={open} onOpenChange={setOpen}>
+ <DialogTrigger asChild>
+ <Button className="flex gap-2 border-border" variant="outline">
+ <PencilSquareIcon className="size-8 stroke-1" /> Edit
+ </Button>
+ </DialogTrigger>
+ <DialogContent className="sm:max-w-[425px] bg-[#161f2a]/30 border-0">
+ <form
+ action={async (FormData) => {
+ const data = {
+ title: FormData.get("title") as string,
+ description: FormData.get("description") as string,
+ };
+ const res = await AddCanvasInfo({ id, ...data });
+ if (res.success) {
+ setOpen(false);
+ setInfo(data);
+ } else {
+ setOpen(false);
+ toast.error("Something went wrong.", {
+ style: { backgroundColor: "rgb(22 31 42 / 0.3)" },
+ });
+ }
+ }}
+ >
+ <DialogHeader>
+ <DialogTitle>Edit Canvas</DialogTitle>
+ <DialogDescription>
+ Add Description to your canvas. Pro tip: Let AI do the job, as you
+ add your content into canvas, we will autogenerate your
+ description.
+ </DialogDescription>
+ </DialogHeader>
+ <div className="grid gap-4 py-4">
+ <div className="grid grid-cols-4 items-center gap-4">
+ <Label htmlFor="title" className="text-right">
+ Title
+ </Label>
+ <Input
+ defaultValue={info.title}
+ name="title"
+ id="title"
+ placeholder="life planning..."
+ className="col-span-3 border-0"
+ />
+ </div>
+ <div className="grid grid-cols-4 items-center gap-4">
+ <Label htmlFor="description" className="text-right">
+ Description
+ </Label>
+ <Textarea
+ defaultValue={info.description}
+ rows={6}
+ id="description"
+ name="description"
+ placeholder="contains information about..."
+ className="col-span-3 border-0 resize-none"
+ />
+ </div>
+ </div>
+ <DialogFooter>
+ <Button type="submit">Save changes</Button>
+ </DialogFooter>
+ </form>
+ </DialogContent>
+ </Dialog>
+ );
+}
+
+import { Suspense, memo, use, useState } from "react";
+import { Box, TldrawImage } from "tldraw";
+import { Button } from "@repo/ui/shadcn/button";
+import { AddCanvasInfo, deleteCanvas } from "@/app/actions/doers";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@repo/ui/shadcn/dialog";
+import { Input } from "@repo/ui/shadcn/input";
+import { Textarea } from "@repo/ui/shadcn/textarea";
+import { textCardUtil } from "@/components/canvas/textCard";
+import { twitterCardUtil } from "@/components/canvas/twitterCard";
+
+const ImageComponent = memo(({ id }: { id: string }) => {
+ const snapshot = use(getCanvasData(id));
+ if (snapshot.bounds) {
+ const pageBounds = new Box(
+ snapshot.bounds.x,
+ snapshot.bounds.y,
+ snapshot.bounds.w,
+ snapshot.bounds.h
+ );
+
+ return (
+ <TldrawImage
+ shapeUtils={[twitterCardUtil, textCardUtil]}
+ snapshot={snapshot.snapshot}
+ background={false}
+ darkMode={true}
+ bounds={pageBounds}
+ padding={0}
+ scale={1}
+ format="png"
+ />
+ );
+ }
+ return (
+ <div className=" h-full w-full flex justify-center items-center">
+ Drew things to seee here
+ </div>
+ );
+});
diff --git a/apps/web/app/(canvas)/canvasStyles.css b/apps/web/app/(canvas)/canvasStyles.css
index 04da2054..a9029680 100644
--- a/apps/web/app/(canvas)/canvasStyles.css
+++ b/apps/web/app/(canvas)/canvasStyles.css
@@ -1,5 +1,5 @@
.tl-background {
- background: #1F2428 !important;
+ background: #181E23 !important;
}
.tlui-style-panel.tlui-style-panel__wrapper, .tlui-navigation-panel::before ,.tlui-menu-zone, .tlui-toolbar__tools, .tlui-popover__content, .tlui-menu, .tlui-button__help, .tlui-help-menu, .tlui-dialog__content {
diff --git a/apps/web/app/(canvas)/layout.tsx b/apps/web/app/(canvas)/layout.tsx
index 5c925573..33e0adfb 100644
--- a/apps/web/app/(canvas)/layout.tsx
+++ b/apps/web/app/(canvas)/layout.tsx
@@ -1,19 +1,30 @@
import { auth } from "@/server/auth";
import "./canvasStyles.css";
import { redirect } from "next/navigation";
+import BackgroundPlus from "../(landing)/GridPatterns/PlusGrid";
+import { Toaster } from "@repo/ui/shadcn/sonner";
export default async function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
-
const info = await auth();
if (!info) {
return redirect("/signin");
}
return (
+ <>
+ <div className="relative flex justify-center z-40 pointer-events-none">
+ <div
+ className="absolute -z-10 left-0 top-[10%] h-32 w-[90%] overflow-x-hidden bg-[rgb(54,157,253)] bg-opacity-100 md:bg-opacity-70 blur-[337.4px]"
+ style={{ transform: "rotate(-30deg)" }}
+ />
+ </div>
+ <BackgroundPlus className="absolute top-0 left-0 w-full h-full -z-50 opacity-70" />
<div>{children}</div>
+ <Toaster />
+ </>
);
}
diff --git a/apps/web/app/actions/doers.ts b/apps/web/app/actions/doers.ts
index 00feaa17..da0e49b5 100644
--- a/apps/web/app/actions/doers.ts
+++ b/apps/web/app/actions/doers.ts
@@ -391,6 +391,12 @@ export const createCanvas = async () => {
return { error: "Not authenticated", success: false };
}
+ const canvases = await db.select().from(canvas).where(eq(canvas.userId, data.user.id))
+
+ if (canvases.length >= 5){
+ return {success: false, message: "A user currently can only have 5 canvases"}
+ }
+
const resp = await db
.insert(canvas)
.values({ userId: data.user.id }).returning({id: canvas.id});
@@ -428,4 +434,33 @@ export const SaveCanvas = async ({id, data}: {id: string, data: string}) => {
} catch (error) {
return {success: false, error, message:"An error occured while saving your canvas"}
}
+}
+
+export const deleteCanvas = async (id: string) => {
+ try {
+ await process.env.CANVAS_SNAPS.delete(id)
+ await db.delete(canvas).where(eq(canvas.id,id))
+ return {
+ success: true,
+ message: "in-sync"
+ }
+ } catch (error) {
+ return {success: false, error, message:"An error occured while saving your canvas"}
+ }
+}
+
+export async function AddCanvasInfo({id, title, description}: {id: string, title: string, description: string}){
+ try {
+ await db.update(canvas).set({description, title}).where(eq(canvas.id, id))
+ return {
+ success: true,
+ message: "info updated successfully"
+ }
+ } catch (error) {
+ return {
+ success: false,
+ message: "something went wrong :/"
+ }
+
+ }
} \ No newline at end of file
diff --git a/apps/web/app/actions/fetchers.ts b/apps/web/app/actions/fetchers.ts
index d93cd4d0..4cb5d1dc 100644
--- a/apps/web/app/actions/fetchers.ts
+++ b/apps/web/app/actions/fetchers.ts
@@ -251,7 +251,7 @@ export const getCanvasData = async (canvasId: string) => {
if (canvas){
return JSON.parse(canvas);
} else {
- return {}
+ return {snapshot: {}}
}
}
diff --git a/apps/web/app/api/canvasai/route.ts b/apps/web/app/api/canvasai/route.ts
new file mode 100644
index 00000000..07538bdf
--- /dev/null
+++ b/apps/web/app/api/canvasai/route.ts
@@ -0,0 +1,27 @@
+import type { NextRequest } from "next/server";
+import { ensureAuth } from "../ensureAuth";
+
+export const runtime = "edge";
+
+export async function POST(request: NextRequest) {
+ const session = await ensureAuth(request);
+ if (!session) {
+ return new Response("Unauthorized", { status: 401 });
+ }
+ const res : {query: string} = await request.json()
+
+ try {
+ const resp = await fetch(`${process.env.BACKEND_BASE_URL}/api/search?query=${res.query}&user=${session.user.id}`);
+ if (resp.status !== 200 || !resp.ok) {
+ const errorData = await resp.text();
+ console.log(errorData);
+ return new Response(
+ JSON.stringify({ message: "Error in CF function", error: errorData }),
+ { status: resp.status },
+ );
+ }
+ return new Response(JSON.stringify({response:await resp.json(), status: 200 }));
+ } catch (error) {
+ return new Response(`Error, ${error}`)
+ }
+} \ No newline at end of file
diff --git a/apps/web/components/canvas/draggableComponent.tsx b/apps/web/components/canvas/draggableComponent.tsx
index 8c39c732..cc31246e 100644
--- a/apps/web/components/canvas/draggableComponent.tsx
+++ b/apps/web/components/canvas/draggableComponent.tsx
@@ -1,25 +1,19 @@
import Image from "next/image";
import { useRef, useState } from "react";
-
-interface DraggableComponentsProps {
- content: string;
- extraInfo?: string;
- iconAlt: string;
-}
+import {motion} from "framer-motion"
export default function DraggableComponentsContainer({
content,
}: {
- content: DraggableComponentsProps[];
+ content: {context:string}[] | undefined;
}) {
+ if (content === undefined) return null;
return (
<div className="flex flex-col gap-10">
{content.map((i) => {
return (
<DraggableComponents
- content={i.content}
- iconAlt={i.iconAlt}
- extraInfo={i.extraInfo}
+ content={i.context}
/>
);
})}
@@ -29,9 +23,7 @@ export default function DraggableComponentsContainer({
function DraggableComponents({
content,
- extraInfo,
- iconAlt,
-}: DraggableComponentsProps) {
+}: {content: string}) {
const [isDragging, setIsDragging] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
@@ -49,19 +41,21 @@ function DraggableComponents({
};
return (
- <div
+ <motion.div
+ initial={{opacity: 0, y: 5}}
+ animate={{opacity: 1, y: 0}}
ref={containerRef}
onDragEnd={handleDragEnd}
onDragStart={handleDragStart}
draggable
- className={`flex gap-4 px-1 rounded-md text-[#989EA4] border-2 transition ${isDragging ? "border-blue-600" : "border-[#1F2428]"}`}
+ className={`flex gap-4 px-3 overflow-hidden rounded-md text-[#989EA4] border-2 transition ${isDragging ? "border-blue-600" : "border-[#1F2428]"}`}
>
<div className="flex flex-col gap-2">
<div>
<h1 className="line-clamp-3">{content}</h1>
</div>
- <p className="line-clamp-1 text-[#369DFD]">{extraInfo}</p>
+ {/* <p className="line-clamp-1 text-[#369DFD]">{extraInfo}</p> */}
</div>
- </div>
+ </motion.div>
);
}
diff --git a/apps/web/components/canvas/resizableLayout.tsx b/apps/web/components/canvas/resizableLayout.tsx
index 5ba6780b..5f88de55 100644
--- a/apps/web/components/canvas/resizableLayout.tsx
+++ b/apps/web/components/canvas/resizableLayout.tsx
@@ -13,12 +13,18 @@ interface RectContextType {
setFullScreen: React.Dispatch<React.SetStateAction<boolean>>;
visible: boolean;
setVisible: React.Dispatch<React.SetStateAction<boolean>>;
- id: string
+ id: string;
}
const RectContext = createContext<RectContextType | undefined>(undefined);
-export const RectProvider = ({ id, children }: {id: string, children: React.ReactNode}) => {
+export const RectProvider = ({
+ id,
+ children,
+}: {
+ id: string;
+ children: React.ReactNode;
+}) => {
const [fullScreen, setFullScreen] = useState(false);
const [visible, setVisible] = useState(true);
@@ -36,12 +42,11 @@ export const RectProvider = ({ id, children }: {id: string, children: React.Reac
export const useRect = () => {
const context = useContext(RectContext);
if (context === undefined) {
- throw new Error('useRect must be used within a RectProvider');
+ throw new Error("useRect must be used within a RectProvider");
}
return context;
};
-
export function ResizaleLayout() {
const { setVisible, fullScreen, setFullScreen } = useRect();
@@ -82,7 +87,7 @@ export function ResizaleLayout() {
}
function DragIconContainer() {
- const { fullScreen} = useRect();
+ const { fullScreen } = useRect();
return (
<div
className={`rounded-lg bg-[#2F363B] ${!fullScreen && "px-1"} transition-all py-2`}
@@ -93,7 +98,7 @@ function DragIconContainer() {
}
function CanvasContainer() {
- const { fullScreen} = useRect();
+ const { fullScreen } = useRect();
return (
<div
className={`absolute overflow-hidden transition-all inset-0 ${fullScreen ? "h-screen " : "h-[calc(100vh-3rem)] rounded-2xl"} w-full`}
@@ -104,7 +109,7 @@ function CanvasContainer() {
}
function SidePanelContainer() {
- const { fullScreen, visible} = useRect();
+ const { fullScreen, visible } = useRect();
return (
<div
className={`flex transition-all rounded-2xl ${fullScreen ? "h-screen" : "h-[calc(100vh-3rem)]"} w-full flex-col overflow-hidden bg-[#1F2428]`}
@@ -123,35 +128,35 @@ function SidePanelContainer() {
}
function SidePanel() {
- const [value, setValue] = useState("");
- // const [dragAsText, setDragAsText] = useState(false);
+ const [content, setContent] = useState<{context: string}[]>()
return (
<>
<div className="px-3 py-5">
- <input
- placeholder="search..."
- onChange={(e) => {
- setValue(e.target.value);
+ <form
+ action={async (FormData) => {
+ const search = FormData.get("search");
+ console.log(search)
+ const res = await fetch("/api/canvasai", {
+ method: "POST",
+ body: JSON.stringify({ query: search }),
+ });
+ const t = await res.json()
+ console.log(t.response.response);
+ setContent(t.response.response)
}}
- value={value}
- // rows={1}
- className="w-full resize-none rounded-xl bg-[#151515] px-3 py-4 text-xl text-[#989EA4] outline-none focus:outline-none sm:max-h-52"
- />
- </div>
- <div className="flex items-center justify-end px-3 py-4">
- {/* <Switch
- className="bg-[#151515] data-[state=unchecked]:bg-red-400 data-[state=checked]:bg-blue-400"
- onCheckedChange={(e) => setDragAsText(e)}
- id="drag-text-mode"
- /> */}
- <Label htmlFor="drag-text-mode">Drag as Text</Label>
+ >
+ <input
+ placeholder="search..."
+ name="search"
+ className="w-full resize-none rounded-xl bg-[#151515] px-3 py-4 text-xl text-[#989EA4] outline-none focus:outline-none sm:max-h-52"
+ />
+ </form>
</div>
<DraggableComponentsContainer content={content} />
</>
);
}
-
const content = [
{
content:
diff --git a/apps/web/components/canvas/savesnap.tsx b/apps/web/components/canvas/savesnap.tsx
index 45fc7e9d..52654bd1 100644
--- a/apps/web/components/canvas/savesnap.tsx
+++ b/apps/web/components/canvas/savesnap.tsx
@@ -10,7 +10,10 @@ export function SaveStatus({id}: {id:string}) {
const debouncedSave = useCallback(
debounce(async () => {
const snapshot = getSnapshot(editor.store)
- SaveCanvas({id, data: JSON.stringify(snapshot)})
+ const bounds = editor.getViewportPageBounds()
+ console.log(bounds)
+
+ SaveCanvas({id, data: JSON.stringify({snapshot, bounds})})
setSave("saved!");
}, 3000),
diff --git a/apps/web/lib/loadSnap.ts b/apps/web/lib/loadSnap.ts
index fb4647f6..0d5bb593 100644
--- a/apps/web/lib/loadSnap.ts
+++ b/apps/web/lib/loadSnap.ts
@@ -9,6 +9,6 @@ export async function loadRemoteSnapshot(id:string) {
const newStore = createTLStore({
shapeUtils: [...defaultShapeUtils, twitterCardUtil, textCardUtil],
});
- loadSnapshot(newStore, snapshot);
+ loadSnapshot(newStore, snapshot.snapshot);
return newStore;
}