From 7b4b5d7a4df706c153cd333d89448f0afac6093a Mon Sep 17 00:00:00 2001 From: jordan Date: Thu, 8 Aug 2024 16:20:18 -0700 Subject: [PATCH 1/7] pull down user grant data --- src/.graphclient/index.ts | 49 +++++++++++++++++++++++++++++++++ src/queries/getProjectCards.ts | 1 - src/queries/getProjectGrants.ts | 40 +++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 1 deletion(-) diff --git a/src/.graphclient/index.ts b/src/.graphclient/index.ts index be794e5d..44f1b813 100644 --- a/src/.graphclient/index.ts +++ b/src/.graphclient/index.ts @@ -9843,6 +9843,12 @@ const merger = new(BareMerger as any)({ return printWithCache(GetProjectGrantsDocument); }, location: 'GetProjectGrantsDocument.graphql' + },{ + document: GetAllProjectGrantsDocument, + get rawSDL() { + return printWithCache(GetAllProjectGrantsDocument); + }, + location: 'GetAllProjectGrantsDocument.graphql' },{ document: GetProjectsDocument, get rawSDL() { @@ -10238,6 +10244,26 @@ export type getProjectGrantsQuery = { grants: Array<( )> } )> }; +export type getAllProjectGrantsQueryVariables = Exact<{ + userAddress: Scalars['String']; + gameId: Scalars['String']; +}>; + + +export type getAllProjectGrantsQuery = { grants: Array<( + Pick + & { ship?: Maybe<( + Pick + & { profileMetadata?: Maybe> } + )>, project?: Maybe<( + Pick + & { metadata?: Maybe> } + )>, currentMilestones?: Maybe<( + Pick + & { milestones: Array> } + )> } + )> }; + export type ProjectDetailsFragment = Pick; export type RawMetadataFragment = Pick; @@ -10874,6 +10900,25 @@ export const getProjectGrantsDocument = gql` } ${GrantBasicFragmentDoc} ${ShipDisplayFragmentDoc}` as unknown as DocumentNode; +export const getAllProjectGrantsDocument = gql` + query getAllProjectGrants($userAddress: String!, $gameId: String!) { + grants: Grant( + where: {gameManager_id: {_eq: $gameId}, project: {owner: {_eq: $userAddress}}} + ) { + ...GrantBasic + amountDistributed + amountAllocated + ship { + ...ShipDisplay + } + project { + ...ProjectDisplay + } + } +} + ${GrantBasicFragmentDoc} +${ShipDisplayFragmentDoc} +${ProjectDisplayFragmentDoc}` as unknown as DocumentNode; export const GetProjectsDocument = gql` query GetProjects($chainId: Int!) { Project( @@ -11108,6 +11153,7 @@ export const ShipsPageQueryDocument = gql` + export type Requester = (doc: DocumentNode, vars?: V, options?: C) => Promise | AsyncIterable @@ -11137,6 +11183,9 @@ export function getSdk(requester: Requester) { getProjectGrants(variables: getProjectGrantsQueryVariables, options?: C): Promise { return requester(getProjectGrantsDocument, variables, options) as Promise; }, + getAllProjectGrants(variables: getAllProjectGrantsQueryVariables, options?: C): Promise { + return requester(getAllProjectGrantsDocument, variables, options) as Promise; + }, GetProjects(variables: GetProjectsQueryVariables, options?: C): Promise { return requester(GetProjectsDocument, variables, options) as Promise; }, diff --git a/src/queries/getProjectCards.ts b/src/queries/getProjectCards.ts index eb66cd3a..417ee8f8 100644 --- a/src/queries/getProjectCards.ts +++ b/src/queries/getProjectCards.ts @@ -6,7 +6,6 @@ import { } from '../.graphclient'; import { getGatewayUrl, getIpfsJson } from '../utils/ipfs/get'; import { ProjectProfileMetadata } from '../utils/ipfs/metadataValidation'; -import { SUBGRAPH_URL } from '../constants/gameSetup'; import { PROJECT_FILTER_LIST } from '../constants/filterLists'; type ProjectMetadataType = z.infer; diff --git a/src/queries/getProjectGrants.ts b/src/queries/getProjectGrants.ts index d3f2d1a3..a0ffaafb 100644 --- a/src/queries/getProjectGrants.ts +++ b/src/queries/getProjectGrants.ts @@ -3,6 +3,7 @@ import { ShipDisplayFragment, getBuiltGraphSDK, } from '../.graphclient'; +import { resolveProjectMetadata } from '../resolvers/projectResolvers'; import { ShipMetadata, resolveShipMetadata } from '../resolvers/shipResolvers'; type ProjectGrantBasic = GrantBasicFragment & { @@ -47,3 +48,42 @@ export const getProjectGrants = async (projectId: string, gameId: string) => { return resolvedGrants; }; + +export const getAllUserGrants = async (userAddress: string, gameId: string) => { + const { getAllProjectGrants } = getBuiltGraphSDK(); + + const data = await getAllProjectGrants({ + userAddress, + gameId, + }); + + if (!data?.grants) { + console.error('Error locating grants collection for user: ', userAddress); + throw new Error('No grants found for user'); + } + + const resolvedGrants = await Promise.all( + data.grants.map(async (grant) => { + const shipMetadata = await resolveShipMetadata( + grant.ship?.profileMetadata?.pointer + ); + const projectMetadata = await resolveProjectMetadata( + grant.project?.metadata?.pointer + ); + + return { + ...grant, + ship: { + ...grant.ship, + profileMetadata: shipMetadata, + }, + project: { + ...grant.project, + metadata: projectMetadata, + }, + }; + }) + ); + + return resolvedGrants; +}; From 54f9c81eb79819f9a9f9859ee2233dfd4e235e8d Mon Sep 17 00:00:00 2001 From: jordan Date: Thu, 8 Aug 2024 16:20:52 -0700 Subject: [PATCH 2/7] display grants --- src/components/grant/GrantCard.tsx | 1 - .../newQueries/getProjectGrants.graphql | 19 ++++ src/layout/DesktopNav/DesktopNav.tsx | 12 ++- src/layout/DesktopNav/GrantsNavSection.tsx | 90 +++++++++++++++++++ 4 files changed, 119 insertions(+), 3 deletions(-) create mode 100644 src/layout/DesktopNav/GrantsNavSection.tsx diff --git a/src/components/grant/GrantCard.tsx b/src/components/grant/GrantCard.tsx index c5036ff4..dd97db53 100644 --- a/src/components/grant/GrantCard.tsx +++ b/src/components/grant/GrantCard.tsx @@ -1,6 +1,5 @@ import { Avatar, - Box, Group, Paper, Text, diff --git a/src/graphql/newQueries/getProjectGrants.graphql b/src/graphql/newQueries/getProjectGrants.graphql index 87edcfd7..91740f96 100644 --- a/src/graphql/newQueries/getProjectGrants.graphql +++ b/src/graphql/newQueries/getProjectGrants.graphql @@ -19,3 +19,22 @@ query getProjectGrants($projectId: String!, $gameId: String!) { } } } + +query getAllProjectGrants($userAddress: String!, $gameId: String!) { + grants: Grant( + where: { + gameManager_id: { _eq: $gameId } + project: { owner: { _eq: $userAddress } } + } + ) { + ...GrantBasic + amountDistributed + amountAllocated + ship { + ...ShipDisplay + } + project { + ...ProjectDisplay + } + } +} diff --git a/src/layout/DesktopNav/DesktopNav.tsx b/src/layout/DesktopNav/DesktopNav.tsx index ddbc488d..b350202d 100644 --- a/src/layout/DesktopNav/DesktopNav.tsx +++ b/src/layout/DesktopNav/DesktopNav.tsx @@ -1,4 +1,11 @@ -import { Group, Code, Title, useMantineTheme, Tooltip } from '@mantine/core'; +import { + Group, + Code, + Title, + useMantineTheme, + Tooltip, + Divider, +} from '@mantine/core'; import { IconRocket, IconAward, @@ -16,6 +23,7 @@ import { useUserData } from '../../hooks/useUserState'; import { useAccount } from 'wagmi'; import { navItems } from '../../constants/navItems'; import { useTablet } from '../../hooks/useBreakpoint'; +import { GrantsNavSection } from './GrantsNavSection'; export function DesktopNav() { const location = useLocation(); @@ -156,11 +164,11 @@ export function DesktopNav() { )} {links} +
{dashboardLink}
- {!isTablet && ( v{process.env.PACKAGE_VERSION} diff --git a/src/layout/DesktopNav/GrantsNavSection.tsx b/src/layout/DesktopNav/GrantsNavSection.tsx new file mode 100644 index 00000000..7b864f02 --- /dev/null +++ b/src/layout/DesktopNav/GrantsNavSection.tsx @@ -0,0 +1,90 @@ +import { + Avatar, + AvatarGroup, + Box, + Divider, + Flex, + Group, + Text, + useMantineTheme, +} from '@mantine/core'; +import { useQuery } from '@tanstack/react-query'; +import { useAccount, useChainId } from 'wagmi'; +import { useUserData } from '../../hooks/useUserState'; +import { getAllUserGrants } from '../../queries/getProjectGrants'; +import { GAME_MANAGER } from '../../constants/gameSetup'; +import { Address } from 'viem'; +import { Link, useLocation } from 'react-router-dom'; +import classes from './DesktoNavStyles.module.css'; + +export const GrantsNavSection = () => { + const { address } = useAccount(); + const theme = useMantineTheme(); + + const { data: grants } = useQuery({ + queryKey: ['user-project-grants', address, GAME_MANAGER.ADDRESS], + queryFn: () => getAllUserGrants(address as Address, GAME_MANAGER.ADDRESS), + enabled: !!address, + }); + + return ( + + + + Grants + + {grants?.map((grant) => ( + ${grant.ship?.name}`} + /> + ))} + + ); +}; + +const NavGrantLink = ({ + projectImgUrl, + shipImgUrl, + collabText, + grantId, +}: { + projectImgUrl: string; + shipImgUrl: string; + collabText: string; + grantId: string; +}) => { + const location = useLocation(); + const isActive = location.pathname.includes(grantId); + + console.log('isActive', isActive); + return ( + // + + + + + + + + {collabText} + + + + ); +}; From b044b123331ad6bed8fe349610051035eafafa3f Mon Sep 17 00:00:00 2001 From: jordan Date: Thu, 8 Aug 2024 16:21:08 -0700 Subject: [PATCH 3/7] style grants nav section --- .../DesktopNav/DesktoNavStyles.module.css | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/layout/DesktopNav/DesktoNavStyles.module.css b/src/layout/DesktopNav/DesktoNavStyles.module.css index f044f541..bdde75b6 100644 --- a/src/layout/DesktopNav/DesktoNavStyles.module.css +++ b/src/layout/DesktopNav/DesktoNavStyles.module.css @@ -142,3 +142,29 @@ .full-width { width: 100%; } + +.grantLink { + display: block; + text-decoration: none; + max-width: 230px; + padding: var(--mantine-spacing-xs); + &:hover { + background-color: var(--mantine-color-dark-6); + } + border-radius: var(--mantine-radius-sm); + + &[data-active] { + &, + &:hover { + background-color: var(--mantine-color-blue-light); + color: var(--mantine-color-blue-light-color); + } + } +} + +.grantLinkText { + flexgrow: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} From ae4151a1a8541842a28425ca6290e660bcdc9bbe Mon Sep 17 00:00:00 2001 From: jordan Date: Thu, 8 Aug 2024 16:23:46 -0700 Subject: [PATCH 4/7] finish grants nav --- src/layout/DesktopNav/GrantsNavSection.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/layout/DesktopNav/GrantsNavSection.tsx b/src/layout/DesktopNav/GrantsNavSection.tsx index 7b864f02..4e38f083 100644 --- a/src/layout/DesktopNav/GrantsNavSection.tsx +++ b/src/layout/DesktopNav/GrantsNavSection.tsx @@ -21,12 +21,18 @@ export const GrantsNavSection = () => { const { address } = useAccount(); const theme = useMantineTheme(); - const { data: grants } = useQuery({ + const { + data: grants, + isLoading, + error, + } = useQuery({ queryKey: ['user-project-grants', address, GAME_MANAGER.ADDRESS], queryFn: () => getAllUserGrants(address as Address, GAME_MANAGER.ADDRESS), enabled: !!address, }); + if (isLoading || error || !grants || grants.length === 0) return null; + return ( @@ -60,9 +66,7 @@ const NavGrantLink = ({ const location = useLocation(); const isActive = location.pathname.includes(grantId); - console.log('isActive', isActive); return ( - // Date: Thu, 8 Aug 2024 16:29:57 -0700 Subject: [PATCH 5/7] display grants in ship operator dash --- src/pages/ShipOpDashboard.tsx | 40 ++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/src/pages/ShipOpDashboard.tsx b/src/pages/ShipOpDashboard.tsx index 9f4d686d..7e958eec 100644 --- a/src/pages/ShipOpDashboard.tsx +++ b/src/pages/ShipOpDashboard.tsx @@ -23,11 +23,12 @@ import { import ShipAbi from '../abi/GrantShip.json'; import { Tag } from '../constants/tags'; import { Address } from 'viem'; -import { ZER0_ADDRESS } from '../constants/gameSetup'; -import { ReportStatus } from '../types/common'; -// import { getRecentPortfolioReport } from '../queries/getRecordsByTag'; -import { ADDR } from '../constants/addresses'; +import { GAME_MANAGER, ZER0_ADDRESS } from '../constants/gameSetup'; + import { SettingsPanel } from '../components/dashboard/ship/SettingsPanel'; +import { getShipGrants } from '../queries/getShipGrants'; +import { GrantCard } from '../components/grant/GrantCard'; +import { GrantStatus } from '../types/common'; export const ShipOpDashboard = () => { const { id } = useParams(); @@ -43,6 +44,16 @@ export const ShipOpDashboard = () => { enabled: !!id, }); + const { + data: grants, + // isLoading: grantsLoading, + // error: grantsError, + } = useQuery({ + queryKey: [`ship-grants-${id}`], + queryFn: () => getShipGrants(id as string, GAME_MANAGER.ADDRESS), + enabled: !!id, + }); + return ( @@ -68,12 +79,21 @@ export const ShipOpDashboard = () => { Post - <> - {/* */} + + {grants?.map((grant) => ( + = GrantStatus.Allocated} + linkUrl={`/grant/${grant.id}/timeline`} + status={grant.status} + /> + ))} + Date: Thu, 8 Aug 2024 21:54:19 -0700 Subject: [PATCH 6/7] Separate needs action in ship Op dashboard --- src/components/grant/GrantCard.tsx | 87 ++++++++++++++++++------------ src/pages/ShipOpDashboard.tsx | 87 +++++++++++++++++++++++++----- 2 files changed, 127 insertions(+), 47 deletions(-) diff --git a/src/components/grant/GrantCard.tsx b/src/components/grant/GrantCard.tsx index dd97db53..9a1618aa 100644 --- a/src/components/grant/GrantCard.tsx +++ b/src/components/grant/GrantCard.tsx @@ -1,6 +1,7 @@ import { Avatar, Group, + Indicator, Paper, Text, Tooltip, @@ -21,7 +22,7 @@ import { IconShieldX, } from '@tabler/icons-react'; import { Link } from 'react-router-dom'; -import { ReactNode } from 'react'; +import { ReactNode, useMemo } from 'react'; export const GrantCard = ({ avatarUrls, @@ -32,6 +33,7 @@ export const GrantCard = ({ hasPending, hasRejected, allCompleted, + notify, }: { linkUrl: string; isActive: boolean; @@ -41,43 +43,60 @@ export const GrantCard = ({ hasPending: boolean; hasRejected: boolean; allCompleted: boolean; + notify?: boolean; }) => { const theme = useMantineTheme(); - return ( - - - - - {avatarUrls.map((url) => ( - - ))} - - - {label} - - - - + const cardGuts = useMemo(() => { + return ( + + + + + {avatarUrls.map((url) => ( + + ))} + + + {label} + + + + + + - - - - ); + + ); + }, [ + avatarUrls, + label, + isActive, + linkUrl, + status, + hasPending, + hasRejected, + allCompleted, + ]); + + if (notify) { + return {cardGuts}; + } + return cardGuts; }; const GrantStatusIndicator = ({ diff --git a/src/pages/ShipOpDashboard.tsx b/src/pages/ShipOpDashboard.tsx index 7e958eec..5a10f596 100644 --- a/src/pages/ShipOpDashboard.tsx +++ b/src/pages/ShipOpDashboard.tsx @@ -29,10 +29,13 @@ import { SettingsPanel } from '../components/dashboard/ship/SettingsPanel'; import { getShipGrants } from '../queries/getShipGrants'; import { GrantCard } from '../components/grant/GrantCard'; import { GrantStatus } from '../types/common'; +import { useMemo } from 'react'; export const ShipOpDashboard = () => { const { id } = useParams(); + const theme = useMantineTheme(); + const { data: shipData, error: shipError, @@ -54,6 +57,36 @@ export const ShipOpDashboard = () => { enabled: !!id, }); + const { needsAttention, idleGrants } = useMemo(() => { + if (!grants) + return { + needsAttention: null, + idleGrants: null, + }; + + let needsAttention = []; + let idleGrants = []; + + for (let grant of grants) { + if ( + grant.hasPendingMilestones || + grant.status === GrantStatus.ProjectInitiated || + grant.status === GrantStatus.ApplicationSubmitted || + grant.status === GrantStatus.MilestonesSubmitted || + grant.status === GrantStatus.FacilitatorRejected + // grant.status === GrantStatus.AllMilestonesComplete + ) { + needsAttention.push(grant); + } else { + idleGrants.push(grant); + } + } + return { + needsAttention, + idleGrants, + }; + }, [grants]); + return ( @@ -80,19 +113,47 @@ export const ShipOpDashboard = () => { - {grants?.map((grant) => ( - = GrantStatus.Allocated} - linkUrl={`/grant/${grant.id}/timeline`} - status={grant.status} - /> - ))} + + + Needs Attention + + + {needsAttention?.map((grant) => ( + = GrantStatus.Allocated} + linkUrl={`/grant/${grant.id}/timeline`} + status={grant.status} + notify + /> + ))} + + + + + Idle + + + {idleGrants?.map((grant) => ( + = GrantStatus.Allocated} + linkUrl={`/grant/${grant.id}/timeline`} + status={grant.status} + /> + ))} + + From a66dfafe31a6963efc88c496f4289c5120aaf4c7 Mon Sep 17 00:00:00 2001 From: jordan Date: Thu, 8 Aug 2024 22:11:23 -0700 Subject: [PATCH 7/7] display and sort grants in facilitator dash --- src/pages/FacilitatorDashboard.tsx | 100 ++++++++++++++++++++++++++--- 1 file changed, 91 insertions(+), 9 deletions(-) diff --git a/src/pages/FacilitatorDashboard.tsx b/src/pages/FacilitatorDashboard.tsx index 5a9f469a..63e8dd5f 100644 --- a/src/pages/FacilitatorDashboard.tsx +++ b/src/pages/FacilitatorDashboard.tsx @@ -1,4 +1,4 @@ -import { Tabs } from '@mantine/core'; +import { Box, Stack, Tabs, Text, useMantineTheme } from '@mantine/core'; import { MainSection, PageTitle } from '../layout/Sections'; import { useQuery } from '@tanstack/react-query'; @@ -8,13 +8,15 @@ import { FacilitatorGameDash } from '../components/dashboard/facilitator/Facilit import { useGameManager } from '../hooks/useGameMangers'; import GameManagerAbi from '../abi/GameManager.json'; import { useMemo } from 'react'; -import { NETWORK_ID, SHIP_AMOUNT } from '../constants/gameSetup'; +import { GAME_MANAGER, NETWORK_ID, SHIP_AMOUNT } from '../constants/gameSetup'; import { useReadContract } from 'wagmi'; import { ADDR } from '../constants/addresses'; -import { GameStatus } from '../types/common'; +import { GameStatus, GrantStatus } from '../types/common'; // import { ProjectApproval } from '../components/dashboard/facilitator/ProjectApproval'; import { FacPostUpdatePanel } from '../components/dashboard/facilitator/FacPostUpdatePanel'; import { useVoting } from '../hooks/useVoting'; +import { getFacilitatorGrants } from '../queries/getFacilitatorGrants'; +import { GrantCard } from '../components/grant/GrantCard'; export const FacilitatorDashboard = () => { const { data: shipData, isLoading: shipsLoading } = useQuery({ @@ -22,6 +24,16 @@ export const FacilitatorDashboard = () => { queryFn: getFacDashShipData, }); + const { + data: grants, + // isLoading: grantsLoading, + // error: grantsError, + } = useQuery({ + queryKey: [`facilitator-grants`, GAME_MANAGER.ADDRESS], + queryFn: () => getFacilitatorGrants(GAME_MANAGER.ADDRESS), + enabled: !!GAME_MANAGER.ADDRESS, + }); + const { gm, isLoadingGm } = useGameManager(); const { data: poolBalance, isLoading: poolLoading } = useReadContract({ @@ -31,8 +43,8 @@ export const FacilitatorDashboard = () => { address: ADDR.GAME_MANAGER, }); - const { contestStatus, votingExists, isVotingActive, isLoadingVoting } = - useVoting(); + const { isLoadingVoting } = useVoting(); + const theme = useMantineTheme(); const gameOperationStage = useMemo(() => { if (!gm || !shipData || typeof poolBalance !== 'bigint') { @@ -112,6 +124,29 @@ export const FacilitatorDashboard = () => { // } }, [shipData, gm, poolBalance]); + const { needsAttention, idleGrants } = useMemo(() => { + if (!grants) + return { + needsAttention: null, + idleGrants: null, + }; + + let needsAttention = []; + let idleGrants = []; + + for (let grant of grants) { + if (grant.status === GrantStatus.MilestonesApproved) { + needsAttention.push(grant); + } else { + idleGrants.push(grant); + } + } + return { + needsAttention, + idleGrants, + }; + }, [grants]); + return ( @@ -119,7 +154,7 @@ export const FacilitatorDashboard = () => { Game Ships - Approvals + Grants Post @@ -139,9 +174,56 @@ export const FacilitatorDashboard = () => { shipData={shipData} /> - - <> - {/* */} + + + + + Needs Attention + + + {needsAttention?.map((grant) => ( + = GrantStatus.Allocated} + linkUrl={`/grant/${grant.id}/timeline`} + status={grant.status} + notify + /> + ))} + + + + + Idle + + + {idleGrants?.map((grant) => ( + = GrantStatus.Allocated} + linkUrl={`/grant/${grant.id}/timeline`} + status={grant.status} + /> + ))} + + +