mirror of
https://github.com/FranP-code/Reflecto.git
synced 2025-10-13 00:43:31 +00:00
feat: implement user-specific space snapshot retrieval in Appwrite integration
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { MoreHorizontal, Plus, Search } from "lucide-react";
|
import { MoreHorizontal, Plus, Search } from "lucide-react";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
@@ -8,69 +9,72 @@ import {
|
|||||||
CardHeader,
|
CardHeader,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { listUserSpaceSnapshots } from "@/lib/appwrite-db";
|
||||||
|
import { authClient } from "@/lib/auth-client";
|
||||||
|
|
||||||
type Space = {
|
type SpaceCard = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
lastEdited: string;
|
lastEdited: string;
|
||||||
preview: string;
|
snapshotText: string;
|
||||||
itemCount: number;
|
itemCount: number;
|
||||||
color: string;
|
color: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mock data for demonstration
|
|
||||||
const mockSpaces: Space[] = [
|
|
||||||
{
|
|
||||||
id: "1",
|
|
||||||
name: "Product Strategy",
|
|
||||||
lastEdited: "2 hours ago",
|
|
||||||
preview: "/whiteboard-with-product-strategy-diagrams-and-stic.jpg",
|
|
||||||
itemCount: 12,
|
|
||||||
color: "bg-blue-500",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "2",
|
|
||||||
name: "Research Notes",
|
|
||||||
lastEdited: "1 day ago",
|
|
||||||
preview: "/whiteboard-with-research-mind-map-and-connections.jpg",
|
|
||||||
itemCount: 8,
|
|
||||||
color: "bg-green-500",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "3",
|
|
||||||
name: "Meeting Ideas",
|
|
||||||
lastEdited: "3 days ago",
|
|
||||||
preview: "/whiteboard-with-meeting-notes-and-action-items.jpg",
|
|
||||||
itemCount: 15,
|
|
||||||
color: "bg-purple-500",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "4",
|
|
||||||
name: "Design System",
|
|
||||||
lastEdited: "1 week ago",
|
|
||||||
preview: "/whiteboard-with-ui-components-and-design-patterns.jpg",
|
|
||||||
itemCount: 24,
|
|
||||||
color: "bg-orange-500",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "5",
|
|
||||||
name: "Learning Path",
|
|
||||||
lastEdited: "2 weeks ago",
|
|
||||||
preview: "/whiteboard-with-learning-roadmap-and-progress-trac.jpg",
|
|
||||||
itemCount: 6,
|
|
||||||
color: "bg-pink-500",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "6",
|
|
||||||
name: "Project Planning",
|
|
||||||
lastEdited: "3 weeks ago",
|
|
||||||
preview: "/whiteboard-with-project-timeline-and-milestones.jpg",
|
|
||||||
itemCount: 18,
|
|
||||||
color: "bg-cyan-500",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export function SpacesGrid() {
|
export function SpacesGrid() {
|
||||||
|
const { data: session } = authClient.useSession();
|
||||||
|
|
||||||
|
const spacesQuery = useQuery({
|
||||||
|
queryKey: ["spaces", session?.$id],
|
||||||
|
queryFn: async () => {
|
||||||
|
if (!session?.$id) {
|
||||||
|
return [] as SpaceCard[];
|
||||||
|
}
|
||||||
|
const rows = await listUserSpaceSnapshots(session.$id);
|
||||||
|
return rows.map(({ row, snapshot }, idx) => {
|
||||||
|
const snapshotText = snapshot
|
||||||
|
? JSON.stringify(snapshot)
|
||||||
|
: (row.snapshot ?? "");
|
||||||
|
const itemCount = (() => {
|
||||||
|
try {
|
||||||
|
const doc = snapshot?.document as unknown as
|
||||||
|
| {
|
||||||
|
store?: { records?: Record<string, unknown> };
|
||||||
|
records?: Record<string, unknown>;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
|
const recs = doc?.store?.records ?? doc?.records;
|
||||||
|
return recs ? Object.keys(recs).length : 0;
|
||||||
|
} catch {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
const COLORS = [
|
||||||
|
"bg-blue-500",
|
||||||
|
"bg-green-500",
|
||||||
|
"bg-purple-500",
|
||||||
|
"bg-orange-500",
|
||||||
|
"bg-pink-500",
|
||||||
|
"bg-cyan-500",
|
||||||
|
] as const;
|
||||||
|
return {
|
||||||
|
id: row.spaceId,
|
||||||
|
name: row.spaceId,
|
||||||
|
lastEdited: row.$updatedAt
|
||||||
|
? new Date(row.$updatedAt).toLocaleString()
|
||||||
|
: "",
|
||||||
|
snapshotText,
|
||||||
|
itemCount,
|
||||||
|
// keep some color variety
|
||||||
|
color: COLORS[idx % COLORS.length],
|
||||||
|
} satisfies SpaceCard;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
staleTime: 10_000,
|
||||||
|
});
|
||||||
|
|
||||||
|
const spaces = spacesQuery.data ?? [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6 p-6">
|
<div className="space-y-6 p-6">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@@ -94,13 +98,13 @@ export function SpacesGrid() {
|
|||||||
<Input className="pl-10" placeholder="Search spaces..." />
|
<Input className="pl-10" placeholder="Search spaces..." />
|
||||||
</div>
|
</div>
|
||||||
<Badge className="px-3 py-1" variant="secondary">
|
<Badge className="px-3 py-1" variant="secondary">
|
||||||
{mockSpaces.length} spaces
|
{spacesQuery.isLoading ? "Loading…" : `${spaces.length} spaces`}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Spaces Grid */}
|
{/* Spaces Grid */}
|
||||||
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3">
|
||||||
{mockSpaces.map((space) => (
|
{spaces.map((space) => (
|
||||||
<Card
|
<Card
|
||||||
className="group cursor-pointer border-border/50 transition-all duration-200 hover:border-border hover:shadow-lg"
|
className="group cursor-pointer border-border/50 transition-all duration-200 hover:border-border hover:shadow-lg"
|
||||||
key={space.id}
|
key={space.id}
|
||||||
@@ -124,14 +128,12 @@ export function SpacesGrid() {
|
|||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent className="pb-3">
|
<CardContent className="pb-3">
|
||||||
{/* Whiteboard Preview */}
|
{/* Stringified snapshot preview (temporary) */}
|
||||||
<div className="relative overflow-hidden rounded-lg border border-border/30 bg-muted/30">
|
<div className="relative overflow-hidden rounded-lg border border-border/30 bg-muted/30">
|
||||||
<img
|
<pre className="h-32 w-full overflow-auto p-2 text-xs leading-tight">
|
||||||
alt={`${space.name} preview`}
|
{space.snapshotText}
|
||||||
className="h-32 w-full object-cover transition-transform duration-200 group-hover:scale-105"
|
</pre>
|
||||||
src={space.preview || "/placeholder.svg"}
|
<div className="pointer-events-none absolute inset-0 bg-gradient-to-t from-background/10 to-transparent" />
|
||||||
/>
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-background/20 to-transparent" />
|
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
|
|
||||||
@@ -144,7 +146,7 @@ export function SpacesGrid() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Empty State (when no spaces exist) */}
|
{/* Empty State (when no spaces exist) */}
|
||||||
{mockSpaces.length === 0 && (
|
{!spacesQuery.isLoading && spaces.length === 0 && (
|
||||||
<div className="flex flex-col items-center justify-center py-16 text-center">
|
<div className="flex flex-col items-center justify-center py-16 text-center">
|
||||||
<div className="mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-muted">
|
<div className="mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-muted">
|
||||||
<Plus className="h-8 w-8 text-muted-foreground" />
|
<Plus className="h-8 w-8 text-muted-foreground" />
|
||||||
|
|||||||
@@ -122,3 +122,30 @@ export async function upsertSpaceSnapshot(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List all space snapshots for a user.
|
||||||
|
*/
|
||||||
|
export async function listUserSpaceSnapshots(
|
||||||
|
userId?: string
|
||||||
|
): Promise<Array<{ row: SpaceSnapshotRow; snapshot: RemoteSnapshot | null }>> {
|
||||||
|
ensureEnv();
|
||||||
|
const uid = userId ?? (await getCurrentUserId());
|
||||||
|
if (!uid) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
const res = (await databases.listDocuments(DATABASE_ID, COLLECTION_ID, [
|
||||||
|
Query.equal("userId", uid),
|
||||||
|
])) as Models.DocumentList<Models.Document>;
|
||||||
|
|
||||||
|
return (res.documents ?? []).map((doc) => {
|
||||||
|
const row = doc as unknown as SpaceSnapshotRow;
|
||||||
|
let parsed: RemoteSnapshot | null = null;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(row.snapshot) as RemoteSnapshot;
|
||||||
|
} catch {
|
||||||
|
parsed = null;
|
||||||
|
}
|
||||||
|
return { row, snapshot: parsed };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user