aboutsummaryrefslogtreecommitdiff
path: root/apps/web
diff options
context:
space:
mode:
authorDhravya Shah <[email protected]>2025-02-18 15:28:01 -0700
committerDhravya Shah <[email protected]>2025-02-18 15:28:01 -0700
commit52d89fd1a6036c00bdc79bf1e0ec0df87760890f (patch)
tree0bfeb0af4db20ed2f00ff265770678d73868102f /apps/web
parentadded a batch delete feature (diff)
downloadsupermemory-52d89fd1a6036c00bdc79bf1e0ec0df87760890f.tar.xz
supermemory-52d89fd1a6036c00bdc79bf1e0ec0df87760890f.zip
better space selector
Diffstat (limited to 'apps/web')
-rw-r--r--apps/web/app/components/memories/MemoriesPage.tsx134
-rw-r--r--apps/web/app/components/memories/SharedCard.tsx29
-rw-r--r--apps/web/app/routes/signin.tsx8
3 files changed, 114 insertions, 57 deletions
diff --git a/apps/web/app/components/memories/MemoriesPage.tsx b/apps/web/app/components/memories/MemoriesPage.tsx
index 8b0dbdc9..da12427e 100644
--- a/apps/web/app/components/memories/MemoriesPage.tsx
+++ b/apps/web/app/components/memories/MemoriesPage.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useMemo, useState } from "react";
+import { createContext, memo, useCallback, useContext, useMemo, useState } from "react";
import { useParams } from "react-router-dom";
import { Button } from "../ui/button";
@@ -22,6 +22,14 @@ interface MemoriesPageProps {
isSpace?: boolean;
}
+interface SelectionContextType {
+ isSelectionMode: boolean;
+ selectedItems: Set<string>;
+ toggleSelection: (uuid: string) => void;
+}
+
+const SelectionContext = createContext<SelectionContextType | null>(null);
+
function MemoriesPage({ showAddButtons = true, isSpace = false }: MemoriesPageProps) {
const isHydrated = useHydrated();
const { spaceId } = useParams();
@@ -106,21 +114,23 @@ function MemoriesPage({ showAddButtons = true, isSpace = false }: MemoriesPagePr
// Memoize space items transformation
const spaceItems = useMemo(() => {
if (spaceId) return [];
- return spaces.map((space) => ({
- id: space.uuid,
- type: "space",
- content: space.name,
- createdAt: new Date(space.createdAt),
- description: null,
- ogImage: null,
- title: space.name,
- url: `/space/${space.uuid}`,
- uuid: space.uuid,
- updatedAt: null,
- raw: null,
- userId: space.ownerId,
- isSuccessfullyProcessed: true,
- }));
+ return spaces
+ .filter((space) => space.uuid !== "<HOME>")
+ .map((space) => ({
+ id: space.uuid,
+ type: "space",
+ content: space.name,
+ createdAt: new Date(space.createdAt),
+ description: null,
+ ogImage: null,
+ title: space.name,
+ url: `/space/${space.uuid}`,
+ uuid: space.uuid,
+ updatedAt: null,
+ raw: null,
+ userId: space.ownerId,
+ isSuccessfullyProcessed: true,
+ }));
}, [spaces, spaceId]);
// Memoize filtered memories
@@ -180,8 +190,29 @@ function MemoriesPage({ showAddButtons = true, isSpace = false }: MemoriesPagePr
};
}, [addButtonItem, spaceItems, filteredMemories, selectedVariant, spaceId, isSpace]);
- const renderCard = useCallback(
- ({ data, index }: { data: Memory; index: number }) => {
+ const selectionContextValue = useMemo(
+ () => ({
+ isSelectionMode,
+ selectedItems,
+ toggleSelection: handleToggleSelection,
+ }),
+ [isSelectionMode, selectedItems, handleToggleSelection],
+ );
+
+ const MemoizedSharedCard = memo(
+ ({
+ data,
+ index,
+ showAddButtons,
+ isSpace,
+ }: {
+ data: Memory;
+ index: number;
+ showAddButtons: boolean;
+ isSpace: boolean;
+ }) => {
+ const selection = useContext(SelectionContext);
+
if (index === 0 && showAddButtons) {
return <AddMemory isSpace={isSpace} />;
}
@@ -191,13 +222,30 @@ function MemoriesPage({ showAddButtons = true, isSpace = false }: MemoriesPagePr
return (
<SharedCard
data={data}
- isSelectionMode={isSelectionMode}
- isSelected={selectedItems.has(data.uuid)}
- onToggleSelect={() => handleToggleSelection(data.uuid)}
+ isSelectionMode={selection?.isSelectionMode ?? false}
+ isSelected={selection?.selectedItems.has(data.uuid) ?? false}
+ onToggleSelect={() => selection?.toggleSelection(data.uuid)}
/>
);
},
- [showAddButtons, isSelectionMode, selectedItems, handleToggleSelection],
+ (prevProps, nextProps) => {
+ // Custom comparison function for memo
+ return prevProps.data.uuid === nextProps.data.uuid;
+ },
+ );
+
+ MemoizedSharedCard.displayName = "MemoizedSharedCard";
+
+ const renderCard = useCallback(
+ ({ data, index }: { data: Memory; index: number }) => (
+ <MemoizedSharedCard
+ data={data}
+ index={index}
+ showAddButtons={showAddButtons}
+ isSpace={isSpace}
+ />
+ ),
+ [showAddButtons, isSpace],
);
const handleVariantClick = useCallback((variant: Variant) => {
@@ -345,28 +393,30 @@ function MemoriesPage({ showAddButtons = true, isSpace = false }: MemoriesPagePr
if (!isHydrated) return null;
return (
- <div className="min-h-screen p-2 md:p-4">
- <div className="mb-4">
- {MobileVariantButton}
- {MobileVariantMenu}
- {DesktopVariantMenu}
- </div>
+ <SelectionContext.Provider value={selectionContextValue}>
+ <div className="min-h-screen p-2 md:p-4">
+ <div className="mb-4">
+ {MobileVariantButton}
+ {MobileVariantMenu}
+ {DesktopVariantMenu}
+ </div>
- {SelectionControls}
-
- <Masonry
- key={key + "memories"}
- id="memories-masonry"
- items={items}
- // @ts-ignore
- render={renderCard}
- columnGutter={16}
- columnWidth={Math.min(270, window.innerWidth - 32)}
- onRender={maybeLoadMore}
- />
+ {SelectionControls}
+
+ <Masonry
+ key={key}
+ id="memories-masonry"
+ items={items}
+ // @ts-ignore
+ render={renderCard}
+ columnGutter={16}
+ columnWidth={Math.min(270, window.innerWidth - 32)}
+ onRender={maybeLoadMore}
+ />
- {isLoading && <div className="py-4 text-center text-muted-foreground">Loading more...</div>}
- </div>
+ {isLoading && <div className="py-4 text-center text-muted-foreground">Loading more...</div>}
+ </div>
+ </SelectionContext.Provider>
);
}
diff --git a/apps/web/app/components/memories/SharedCard.tsx b/apps/web/app/components/memories/SharedCard.tsx
index cebdc796..69283324 100644
--- a/apps/web/app/components/memories/SharedCard.tsx
+++ b/apps/web/app/components/memories/SharedCard.tsx
@@ -3,7 +3,7 @@ import { memo, useCallback, useEffect, useMemo, useState } from "react";
import { useInView } from "react-intersection-observer";
import { TweetSkeleton } from "react-tweet";
-import { useNavigate } from "@remix-run/react";
+import { useNavigate, useParams } from "@remix-run/react";
import { NotionIcon } from "../icons/IntegrationIcons";
import { CustomTwitterComp } from "../twitter/render-tweet";
@@ -111,6 +111,7 @@ const renderContent = {
page: ({ data }: { data: Memory }) => (
<WebsiteCard
+ id={data.uuid}
url={data.url ?? ""}
title={data.title}
description={data.description}
@@ -219,7 +220,7 @@ const renderContent = {
space: ({ data }: { data: Memory & Partial<ExtraSpaceMetaData> }) => {
return (
<a
- href={`${data.url}`}
+ href={data.url ?? ""}
className="flex flex-col gap-2 p-6 bg-white dark:bg-neutral-800 border border-gray-200 dark:border-gray-800 rounded-3xl"
>
<div className="flex items-center gap-2 text-gray-600 dark:text-gray-300">
@@ -325,7 +326,7 @@ const renderContent = {
// TODO: This can be improved
return (
<a
- href={data.url ?? ""}
+ href={`/content/${data.id}`}
className="block p-4 rounded-3xl border border-gray-200 dark:border-gray-800 hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors"
>
<div className="flex items-center gap-3 text-gray-600 dark:text-gray-300">
@@ -411,11 +412,13 @@ const WebsiteCard = memo(
title,
description,
image,
+ id,
}: {
url: string;
title?: string | null;
description?: string | null;
image?: string | null;
+ id: string;
}) => {
// Memoize domain extraction to avoid recalculation
const domain = useMemo(() => {
@@ -500,9 +503,7 @@ const WebsiteCard = memo(
<h3 className="text-lg font-semibold tracking-tight">{displayTitle}</h3>
<p className="mt-2 line-clamp-2 text-sm opacity-80">{displayDescription}</p>
<a
- href={url}
- target="_blank"
- rel="noopener noreferrer"
+ href={`/content/${id}`}
className="mt-3 inline-flex items-center gap-1 text-sm hover:underline opacity-70 hover:opacity-100 transition-opacity"
style={{
color: isDark ? "white" : "black",
@@ -686,6 +687,7 @@ export default function SharedCard({
onSuccess: () => {
toast.success("Memory deleted successfully");
queryClient.invalidateQueries({ queryKey: ["memories"] });
+ queryClient.invalidateQueries({ queryKey: ["spaces"] });
},
});
@@ -742,11 +744,6 @@ export default function SharedCard({
onToggleSelect();
return;
}
-
- // Normal navigation behavior
- if (data.url) {
- window.location.href = data.url;
- }
};
return (
@@ -809,6 +806,10 @@ export const SpaceSelector = function SpaceSelector({
onSelect: (spaceId: string) => void;
}) {
const [search, setSearch] = useState("");
+ const { spaceId } = useParams();
+
+ console.log(spaceId);
+
const {
data: spacesData,
isLoading,
@@ -821,11 +822,13 @@ export const SpaceSelector = function SpaceSelector({
const filteredSpaces = useMemo(() => {
if (!spacesData?.spaces) return [];
- return spacesData.spaces.filter((space) =>
- space.name.toLowerCase().includes(search.toLowerCase()),
+ return spacesData.spaces.filter(
+ (space) =>
+ space.name.toLowerCase().includes(search.toLowerCase()) && space.uuid !== (spaceId ? spaceId.split("---")[0] : "<HOME>"),
);
}, [spacesData?.spaces, search]);
+
if (isLoading) {
return (
<DropdownMenuSubContent>
diff --git a/apps/web/app/routes/signin.tsx b/apps/web/app/routes/signin.tsx
index 93fa792f..0ed0a848 100644
--- a/apps/web/app/routes/signin.tsx
+++ b/apps/web/app/routes/signin.tsx
@@ -1,8 +1,12 @@
import { LoaderFunctionArgs, redirect } from "@remix-run/cloudflare";
-
import { getSignInUrl } from "@supermemory/authkit-remix-cloudflare";
+import { getSessionFromRequest } from "@supermemory/authkit-remix-cloudflare/src/session";
-export async function loader({ context }: LoaderFunctionArgs) {
+export async function loader({ request, context }: LoaderFunctionArgs) {
+ const session = await getSessionFromRequest(request, context);
+ if (session) {
+ return redirect("/");
+ }
const signinUrl = await getSignInUrl(context);
return redirect(signinUrl);
}