diff --git a/apps/events/package.json b/apps/events/package.json index 20dde2b7..1b675710 100644 --- a/apps/events/package.json +++ b/apps/events/package.json @@ -11,6 +11,7 @@ "@automerge/automerge": "^v2.2.9-alpha.3", "@automerge/automerge-repo": "^2.0.0-alpha.14", "@automerge/automerge-repo-react-hooks": "^2.0.0-alpha.14", + "@graphprotocol/grc-20": "^0.10.0", "@graphprotocol/hypergraph": "workspace:*", "@graphprotocol/hypergraph-react": "workspace:*", "@noble/hashes": "^1.7.0", @@ -18,11 +19,14 @@ "@radix-ui/react-avatar": "^1.1.2", "@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-slot": "^1.1.1", + "@tanstack/react-query": "^5.67.1", "@tanstack/react-router": "^1.97.1", "@xstate/store": "^2.6.2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "effect": "^3.12.4", + "effect": "^3.14.12", + "framer-motion": "^12.5.0", + "graphql-request": "^7.1.2", "isomorphic-ws": "^5.0.0", "lucide-react": "^0.471.1", "react": "^19.0.0", diff --git a/apps/events/src/components/create-properties-and-types.tsx b/apps/events/src/components/create-properties-and-types.tsx new file mode 100644 index 00000000..d6ccdba2 --- /dev/null +++ b/apps/events/src/components/create-properties-and-types.tsx @@ -0,0 +1,89 @@ +import { getSmartAccountWalletClient } from '@/lib/smart-account'; +import { type GeoSmartAccount, Graph, type Op } from '@graphprotocol/grc-20'; +import { publishOps, useHypergraphSpace } from '@graphprotocol/hypergraph-react'; +import { useState } from 'react'; +import { Button } from './ui/button'; +import { Card, CardContent } from './ui/card'; + +const createPropertiesAndTypes = async ({ + smartAccountWalletClient, + space, +}: { smartAccountWalletClient: GeoSmartAccount; space: string }) => { + const ops: Array = []; + const { id: checkedPropertyId, ops: createCheckedPropertyOps } = Graph.createProperty({ + type: 'CHECKBOX', + name: 'Checked', + }); + ops.push(...createCheckedPropertyOps); + + const { id: userId, ops: createUserOps } = Graph.createType({ + name: 'User', + }); + ops.push(...createUserOps); + + const { id: assigneesRelationTypeId, ops: createAssigneesRelationTypeOps } = Graph.createProperty({ + type: 'RELATION', + name: 'Assignees', + relationValueTypes: [userId], + }); + ops.push(...createAssigneesRelationTypeOps); + + const { id: todoTypeId, ops: createTodoTypeOps } = Graph.createType({ + name: 'Todo', + properties: [checkedPropertyId, assigneesRelationTypeId], + }); + ops.push(...createTodoTypeOps); + + const result = await publishOps({ ops, walletClient: smartAccountWalletClient, space }); + return { result, todoTypeId, checkedPropertyId, userId, assigneesRelationTypeId }; +}; + +export const CreatePropertiesAndTypes = () => { + const [mapping, setMapping] = useState(''); + const space = useHypergraphSpace(); + + return ( +
+ {mapping && ( + + +
{mapping}
+
+
+ )} + +
+ ); +}; diff --git a/apps/events/src/components/dev-tool.tsx b/apps/events/src/components/dev-tool.tsx index 539e5071..3780d806 100644 --- a/apps/events/src/components/dev-tool.tsx +++ b/apps/events/src/components/dev-tool.tsx @@ -10,13 +10,13 @@ export function DevTool({ spaceId }: { spaceId: string }) { const spaces = useSelector(store, (state) => state.context.spaces); const updatesInFlight = useSelector(store, (state) => state.context.updatesInFlight); - const { subscribeToSpace, loading } = useHypergraphApp(); + const { subscribeToSpace, isConnecting } = useHypergraphApp(); useEffect(() => { - if (!loading) { + if (!isConnecting) { subscribeToSpace({ spaceId }); } - }, [loading, subscribeToSpace, spaceId]); + }, [isConnecting, subscribeToSpace, spaceId]); const space = spaces.find((space) => space.id === spaceId); diff --git a/apps/events/src/components/playground.tsx b/apps/events/src/components/playground.tsx new file mode 100644 index 00000000..a164797d --- /dev/null +++ b/apps/events/src/components/playground.tsx @@ -0,0 +1,10 @@ +import { useQuery } from '@graphprotocol/hypergraph-react'; +import { NewsStory } from '../schema'; + +export const Playground = () => { + const { data: entityData, isLoading, isError } = useQuery(NewsStory, { mode: 'public' }); + + console.log({ isLoading, isError, entityData }); + + return
{JSON.stringify(entityData, null, 2)}
; +}; diff --git a/apps/events/src/components/spinner.tsx b/apps/events/src/components/spinner.tsx new file mode 100644 index 00000000..d8484311 --- /dev/null +++ b/apps/events/src/components/spinner.tsx @@ -0,0 +1,42 @@ +'use client'; + +import { cn } from '@/lib/utils'; +import { motion } from 'framer-motion'; + +interface SpinnerProps { + size?: 'sm' | 'md' | 'lg' | 'xl'; + color?: 'default' | 'primary' | 'secondary' | 'accent' | 'white'; + className?: string; +} + +export function Spinner({ size = 'md', color = 'primary', className }: SpinnerProps) { + const sizeClasses = { + sm: 'h-4 w-4 border-2', + md: 'h-6 w-6 border-2', + lg: 'h-8 w-8 border-3', + xl: 'h-12 w-12 border-4', + }; + + const colorClasses = { + default: 'border-muted-foreground/30 border-t-muted-foreground', + primary: 'border-primary/30 border-t-primary', + secondary: 'border-secondary/30 border-t-secondary', + accent: 'border-accent/30 border-t-accent', + white: 'border-white/30 border-t-white', + }; + + return ( + + Loading... + + ); +} diff --git a/apps/events/src/components/todo/todos-local.tsx b/apps/events/src/components/todo/todos-local.tsx new file mode 100644 index 00000000..e6d926bf --- /dev/null +++ b/apps/events/src/components/todo/todos-local.tsx @@ -0,0 +1,48 @@ +import { useHardDeleteEntity, useQuery, useUpdateEntity } from '@graphprotocol/hypergraph-react'; +import { Todo2 } from '../../schema'; +import { Button } from '../ui/button'; + +export const TodosLocal = () => { + const updateEntity = useUpdateEntity(Todo2); + const hardDeleteEntity = useHardDeleteEntity(); + const { data: todosLocalData, deleted: deletedTodosLocalData } = useQuery(Todo2, { mode: 'local' }); + + return ( + <> +

Todos (Local)

+ {todosLocalData.map((todo) => ( +
+

{todo.name}

+
{todo.id}
+ {todo.assignees.map((assignee) => ( + + {assignee.name} + + ))} + updateEntity(todo.id, { checked: e.target.checked })} + /> + {/* @ts-expect-error */} +
{todo.__deleted ? 'deleted' : 'not deleted'}
+ {/* @ts-expect-error */} +
{todo.__version}
+ +
+ ))} +

Deleted Todos (Local)

+ {deletedTodosLocalData.map((todo) => ( +
+

{todo.name}

+
{todo.id}
+ +
+ ))} + + ); +}; diff --git a/apps/events/src/components/todo/todos-public-geo.tsx b/apps/events/src/components/todo/todos-public-geo.tsx new file mode 100644 index 00000000..1d77cca7 --- /dev/null +++ b/apps/events/src/components/todo/todos-public-geo.tsx @@ -0,0 +1,81 @@ +import { getSmartAccountWalletClient } from '@/lib/smart-account'; +import { Id } from '@graphprotocol/grc-20'; +import { + _generateDeleteOps, + publishOps, + useCreateEntity, + useHypergraphSpace, + useQuery, +} from '@graphprotocol/hypergraph-react'; +import { useGenerateCreateOps } from '@graphprotocol/hypergraph-react/internal/use-generate-create-ops'; +import { Todo2 } from '../../schema'; +import { Spinner } from '../spinner'; +import { Button } from '../ui/button'; + +export const TodosPublicGeo = () => { + const space = useHypergraphSpace(); + const { + data: dataPublic, + isLoading: isLoadingPublic, + isError: isErrorPublic, + } = useQuery(Todo2, { + mode: 'public', + include: { assignees: {} }, + }); + + const createTodo = useCreateEntity(Todo2); + const generateCreateOps = useGenerateCreateOps(Todo2); + + return ( + <> +
+

Todos (Public Geo)

+ {isLoadingPublic && } +
+ {isErrorPublic &&
Error loading todos
} + {dataPublic.map((todo) => ( +
+

{todo.name}

+
{todo.id}
+ + {todo.assignees.map((assignee) => ( + + {assignee.name} + + ))} + + +
+ ))} + + + ); +}; diff --git a/apps/events/src/components/todo/todos-public-kg.tsx b/apps/events/src/components/todo/todos-public-kg.tsx new file mode 100644 index 00000000..956dd953 --- /dev/null +++ b/apps/events/src/components/todo/todos-public-kg.tsx @@ -0,0 +1,45 @@ +import { getSmartAccountWalletClient } from '@/lib/smart-account'; +import { + _generateDeleteOps, + publishOps, + useHypergraphSpace, + _useQueryPublicKg as useQueryPublicKg, +} from '@graphprotocol/hypergraph-react'; +import { Todo2 } from '../../schema'; +import { Spinner } from '../spinner'; +import { Button } from '../ui/button'; + +export const TodosPublicKg = () => { + const space = useHypergraphSpace(); + const { data: kgPublicData, isLoading: kgPublicIsLoading, isError: kgPublicIsError } = useQueryPublicKg(Todo2); + + return ( + <> +
+

Todos (Public KG)

+ {kgPublicIsLoading && } +
+ {kgPublicIsError &&
Error loading todos
} + {kgPublicData.map((todo) => ( +
+

{todo.name}

+
{todo.id}
+ + +
+ ))} + + ); +}; diff --git a/apps/events/src/components/todos-read-only-filter.tsx b/apps/events/src/components/todos-read-only-filter.tsx new file mode 100644 index 00000000..aa599ccb --- /dev/null +++ b/apps/events/src/components/todos-read-only-filter.tsx @@ -0,0 +1,48 @@ +import { useQuery } from '@graphprotocol/hypergraph-react'; +import { Todo } from '../schema'; + +export const TodosReadOnlyFilter = () => { + const { data: todosCompleted } = useQuery(Todo, { mode: 'local', filter: { completed: { is: true } } }); + const { data: todosNotCompleted } = useQuery(Todo, { mode: 'local', filter: { completed: { is: false } } }); + + return ( + <> +

Todos Filter (read only)

+

Not completed

+ {todosNotCompleted.map((todo) => ( +
+

{todo.name}

+ {todo.assignees.length > 0 && ( + + Assigned to:{' '} + {todo.assignees.map((assignee) => ( + + {assignee.name} + + ))} + + )} + +
+ ))} + +

Completed

+ {todosCompleted.map((todo) => ( +
+

{todo.name}

+ {todo.assignees.length > 0 && ( + + Assigned to:{' '} + {todo.assignees.map((assignee) => ( + + {assignee.name} + + ))} + + )} + +
+ ))} + + ); +}; diff --git a/apps/events/src/components/todos-read-only.tsx b/apps/events/src/components/todos-read-only.tsx index 411887cb..32da9a83 100644 --- a/apps/events/src/components/todos-read-only.tsx +++ b/apps/events/src/components/todos-read-only.tsx @@ -1,8 +1,8 @@ -import { useQueryEntities } from '@graphprotocol/hypergraph-react'; +import { useQuery } from '@graphprotocol/hypergraph-react'; import { Todo } from '../schema'; export const TodosReadOnly = () => { - const todos = useQueryEntities(Todo); + const { data: todos } = useQuery(Todo, { mode: 'local' }); return ( <> @@ -10,6 +10,16 @@ export const TodosReadOnly = () => { {todos.map((todo) => (

{todo.name}

+ {todo.assignees.length > 0 && ( + + Assigned to:{' '} + {todo.assignees.map((assignee) => ( + + {assignee.name} + + ))} + + )}
))} diff --git a/apps/events/src/components/todos.tsx b/apps/events/src/components/todos.tsx index 94c2ae49..5f0cc706 100644 --- a/apps/events/src/components/todos.tsx +++ b/apps/events/src/components/todos.tsx @@ -1,4 +1,10 @@ -import { useCreateEntity, useDeleteEntity, useQueryEntities, useUpdateEntity } from '@graphprotocol/hypergraph-react'; +import { + useCreateEntity, + useDeleteEntity, + useQuery, + useRemoveRelation, + useUpdateEntity, +} from '@graphprotocol/hypergraph-react'; import { useEffect, useState } from 'react'; import Select from 'react-select'; import { Todo, User } from '../schema'; @@ -6,11 +12,12 @@ import { Button } from './ui/button'; import { Input } from './ui/input'; export const Todos = () => { - const todos = useQueryEntities(Todo); - const users = useQueryEntities(User); + const { data: todos } = useQuery(Todo, { mode: 'local', include: { assignees: {} } }); + const { data: users } = useQuery(User, { mode: 'local' }); const createEntity = useCreateEntity(Todo); const updateEntity = useUpdateEntity(Todo); const deleteEntity = useDeleteEntity(); + const removeRelation = useRemoveRelation(); const [newTodoName, setNewTodoName] = useState(''); const [assignees, setAssignees] = useState<{ value: string; label: string }[]>([]); @@ -34,7 +41,11 @@ export const Todos = () => { alert('Todo text is required'); return; } - createEntity({ name: newTodoName, completed: false, assignees: assignees.map(({ value }) => value) }); + createEntity({ + name: newTodoName, + completed: false, + assignees: assignees.map(({ value }) => value), + }); setNewTodoName(''); }} > @@ -52,11 +63,11 @@ export const Todos = () => { {assignee.name} + + ))} + + +
+ setNewUserName(e.target.value)} /> + +
+ +
+

Todos (Merged)

+ {isLoadingTodos && } +
+ {isErrorTodos &&
Error loading todos
} +
+ {dataTodos.map((todo) => ( +
+

{todo.name}

+
{todo.id}
+ updateTodo(todo.id, { checked: e.target.checked })} + /> + {todo.assignees.length > 0 && ( + + Assigned to:{' '} + {todo.assignees.map((assignee) => ( + + {assignee.name} + + + ))} + + )} + {/* @ts-expect-error */} +
{todo.__version}
+ +
+ ))} +
+ +
+ setNewTodoName(e.target.value)} /> +