Skip to content

Commit

Permalink
games overview
Browse files Browse the repository at this point in the history
  • Loading branch information
arjanjohan committed Apr 29, 2024
1 parent 69d29b7 commit ce78234
Show file tree
Hide file tree
Showing 19 changed files with 1,372 additions and 5 deletions.
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"matchstick-as": "0.5.0"
},
"dependencies": {
"@apollo/client": "^3.10.1",
"@aztec/accounts": "^0.35.1",
"@aztec/aztec.js": "^0.35.1",
"@aztec/noir-contracts.js": "^0.35.1",
Expand All @@ -55,6 +56,7 @@
"@types/node": "^20.12.7",
"@usedapp/core": "^1.2.13",
"ethers": "^6.12.0",
"graphql": "^16.8.1",
"react-toastify": "^10.0.5",
"typescript": "^5.4.5",
"validator": "^13.11.0",
Expand Down
76 changes: 76 additions & 0 deletions packages/nextjs/app/games/_components/QueryGames.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
"use client";

import React, { useEffect, useState } from "react";
import { ApolloClient, InMemoryCache, gql } from "@apollo/client";

interface Game {
gameId: string;
players: string[];
fighterIds: string[];
state: string;
}

interface QueryData {
games: Game[];
}

interface QueryError {
message: string;
}

export function QueryGames() {
const [games, setGames] = useState<Game[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<QueryError | null>(null);

const APIURL = "https://api.studio.thegraph.com/query/72991/scrollfighter/version/latest";

const tokensQuery = gql`
query {
games(first: 5) {
gameId
players
fighterIds
state
}
}
`;

const client = new ApolloClient({
uri: APIURL,
cache: new InMemoryCache(),
});

useEffect(() => {
client
.query<QueryData>({
query: tokensQuery,
})
.then(response => {
setGames(response.data.games);
setLoading(false);
})
.catch(err => {
console.error("Error fetching data: ", err);
setError(err);
setLoading(false);
});
}, []);

if (loading) return <div>Loading...</div>;
if (error) return <div>Error fetching data: {error.message}</div>;

return (
<div>
<h1>Games</h1>
{games.map(game => (
<div key={game.gameId}>
<h2>Game ID: {game.gameId}</h2>
<p>Players: {game.players.join(", ")}</p>
<p>Fighter IDs: {game.fighterIds.join(", ")}</p>
<p>State: {game.state}</p>
</div>
))}
</div>
);
}
84 changes: 84 additions & 0 deletions packages/nextjs/app/games/_components/contract/ContractInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"use client";

import { Dispatch, SetStateAction } from "react";
import { Tuple } from "./Tuple";
import { TupleArray } from "./TupleArray";
import { AbiParameter } from "abitype";
import {
AddressInput,
Bytes32Input,
BytesInput,
InputBase,
IntegerInput,
IntegerVariant,
} from "~~/components/scaffold-eth";
import { AbiParameterTuple } from "~~/utils/scaffold-eth/contract";

type ContractInputProps = {
setForm: Dispatch<SetStateAction<Record<string, any>>>;
form: Record<string, any> | undefined;
stateObjectKey: string;
paramType: AbiParameter;
};

/**
* Generic Input component to handle input's based on their function param type
*/
export const ContractInput = ({ setForm, form, stateObjectKey, paramType }: ContractInputProps) => {
const inputProps = {
name: stateObjectKey,
value: form?.[stateObjectKey],
placeholder: paramType.name ? `${paramType.type} ${paramType.name}` : paramType.type,
onChange: (value: any) => {
setForm(form => ({ ...form, [stateObjectKey]: value }));
},
};

const renderInput = () => {
switch (paramType.type) {
case "address":
return <AddressInput {...inputProps} />;
case "bytes32":
return <Bytes32Input {...inputProps} />;
case "bytes":
return <BytesInput {...inputProps} />;
case "string":
return <InputBase {...inputProps} />;
case "tuple":
return (
<Tuple
setParentForm={setForm}
parentForm={form}
abiTupleParameter={paramType as AbiParameterTuple}
parentStateObjectKey={stateObjectKey}
/>
);
default:
// Handling 'int' types and 'tuple[]' types
if (paramType.type.includes("int") && !paramType.type.includes("[")) {
return <IntegerInput {...inputProps} variant={paramType.type as IntegerVariant} />;
} else if (paramType.type.startsWith("tuple[")) {
return (
<TupleArray
setParentForm={setForm}
parentForm={form}
abiTupleParameter={paramType as AbiParameterTuple}
parentStateObjectKey={stateObjectKey}
/>
);
} else {
return <InputBase {...inputProps} />;
}
}
};

return (
<div className="flex flex-col gap-1.5 w-full">
<div className="flex items-center ml-2">
{paramType.name && <span className="text-xs font-medium mr-2 leading-none">{paramType.name}</span>}
<span className="block text-xs font-extralight leading-none">{paramType.type}</span>
</div>
{renderInput()}
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Abi, AbiFunction } from "abitype";
import { ReadOnlyFunctionForm } from "~~/app/debug/_components/contract";
import { Contract, ContractName, GenericContract, InheritedFunctions } from "~~/utils/scaffold-eth/contract";

export const ContractReadMethods = ({ deployedContractData }: { deployedContractData: Contract<ContractName> }) => {
if (!deployedContractData) {
return null;
}

const functionsToDisplay = (
((deployedContractData.abi || []) as Abi).filter(part => part.type === "function") as AbiFunction[]
)
.filter(fn => {
const isQueryableWithParams =
(fn.stateMutability === "view" || fn.stateMutability === "pure") && fn.inputs.length > 0;
return isQueryableWithParams;
})
.map(fn => {
return {
fn,
inheritedFrom: ((deployedContractData as GenericContract)?.inheritedFunctions as InheritedFunctions)?.[fn.name],
};
})
.sort((a, b) => (b.inheritedFrom ? b.inheritedFrom.localeCompare(a.inheritedFrom) : 1));

if (!functionsToDisplay.length) {
return <>No read methods</>;
}

return (
<>
{functionsToDisplay.map(({ fn, inheritedFrom }) => (
<ReadOnlyFunctionForm
abi={deployedContractData.abi as Abi}
contractAddress={deployedContractData.address}
abiFunction={fn}
key={fn.name}
inheritedFrom={inheritedFrom}
/>
))}
</>
);
};
104 changes: 104 additions & 0 deletions packages/nextjs/app/games/_components/contract/ContractUI.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
"use client";

// @refresh reset
import { useReducer } from "react";
import { ContractReadMethods } from "./ContractReadMethods";
import { ContractVariables } from "./ContractVariables";
import { ContractWriteMethods } from "./ContractWriteMethods";
import { Address, Balance } from "~~/components/scaffold-eth";
import { useDeployedContractInfo, useNetworkColor } from "~~/hooks/scaffold-eth";
import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork";
import { ContractName } from "~~/utils/scaffold-eth/contract";

type ContractUIProps = {
contractName: ContractName;
className?: string;
};

/**
* UI component to interface with deployed contracts.
**/
export const ContractUI = ({ contractName, className = "" }: ContractUIProps) => {
const [refreshDisplayVariables, triggerRefreshDisplayVariables] = useReducer(value => !value, false);
const { targetNetwork } = useTargetNetwork();
const { data: deployedContractData, isLoading: deployedContractLoading } = useDeployedContractInfo(contractName);
const networkColor = useNetworkColor();

if (deployedContractLoading) {
return (
<div className="mt-14">
<span className="loading loading-spinner loading-lg"></span>
</div>
);
}

if (!deployedContractData) {
return (
<p className="text-3xl mt-14">
{`No contract found by the name of "${contractName}" on chain "${targetNetwork.name}"!`}
</p>
);
}

return (
<div className={`grid grid-cols-1 lg:grid-cols-6 px-6 lg:px-10 lg:gap-12 w-full max-w-7xl my-0 ${className}`}>
<div className="col-span-5 grid grid-cols-1 lg:grid-cols-3 gap-8 lg:gap-10">
<div className="col-span-1 flex flex-col">
<div className="bg-base-100 border-base-300 border shadow-md shadow-secondary rounded-3xl px-6 lg:px-8 mb-6 space-y-1 py-4">
<div className="flex">
<div className="flex flex-col gap-1">
<span className="font-bold">{contractName}</span>
<Address address={deployedContractData.address} />
<div className="flex gap-1 items-center">
<span className="font-bold text-sm">Balance:</span>
<Balance address={deployedContractData.address} className="px-0 h-1.5 min-h-[0.375rem]" />
</div>
</div>
</div>
{targetNetwork && (
<p className="my-0 text-sm">
<span className="font-bold">Network</span>:{" "}
<span style={{ color: networkColor }}>{targetNetwork.name}</span>
</p>
)}
</div>
<div className="bg-base-300 rounded-3xl px-6 lg:px-8 py-4 shadow-lg shadow-base-300">
<ContractVariables
refreshDisplayVariables={refreshDisplayVariables}
deployedContractData={deployedContractData}
/>
</div>
</div>
<div className="col-span-1 lg:col-span-2 flex flex-col gap-6">
<div className="z-10">
<div className="bg-base-100 rounded-3xl shadow-md shadow-secondary border border-base-300 flex flex-col mt-10 relative">
<div className="h-[5rem] w-[5.5rem] bg-base-300 absolute self-start rounded-[22px] -top-[38px] -left-[1px] -z-10 py-[0.65rem] shadow-lg shadow-base-300">
<div className="flex items-center justify-center space-x-2">
<p className="my-0 text-sm">Read</p>
</div>
</div>
<div className="p-5 divide-y divide-base-300">
<ContractReadMethods deployedContractData={deployedContractData} />
</div>
</div>
</div>
<div className="z-10">
<div className="bg-base-100 rounded-3xl shadow-md shadow-secondary border border-base-300 flex flex-col mt-10 relative">
<div className="h-[5rem] w-[5.5rem] bg-base-300 absolute self-start rounded-[22px] -top-[38px] -left-[1px] -z-10 py-[0.65rem] shadow-lg shadow-base-300">
<div className="flex items-center justify-center space-x-2">
<p className="my-0 text-sm">Write</p>
</div>
</div>
<div className="p-5 divide-y divide-base-300">
<ContractWriteMethods
deployedContractData={deployedContractData}
onChange={triggerRefreshDisplayVariables}
/>
</div>
</div>
</div>
</div>
</div>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { DisplayVariable } from "./DisplayVariable";
import { Abi, AbiFunction } from "abitype";
import { Contract, ContractName, GenericContract, InheritedFunctions } from "~~/utils/scaffold-eth/contract";

export const ContractVariables = ({
refreshDisplayVariables,
deployedContractData,
}: {
refreshDisplayVariables: boolean;
deployedContractData: Contract<ContractName>;
}) => {
if (!deployedContractData) {
return null;
}

const functionsToDisplay = (
(deployedContractData.abi as Abi).filter(part => part.type === "function") as AbiFunction[]
)
.filter(fn => {
const isQueryableWithNoParams =
(fn.stateMutability === "view" || fn.stateMutability === "pure") && fn.inputs.length === 0;
return isQueryableWithNoParams;
})
.map(fn => {
return {
fn,
inheritedFrom: ((deployedContractData as GenericContract)?.inheritedFunctions as InheritedFunctions)?.[fn.name],
};
})
.sort((a, b) => (b.inheritedFrom ? b.inheritedFrom.localeCompare(a.inheritedFrom) : 1));

if (!functionsToDisplay.length) {
return <>No contract variables</>;
}

return (
<>
{functionsToDisplay.map(({ fn, inheritedFrom }) => (
<DisplayVariable
abi={deployedContractData.abi as Abi}
abiFunction={fn}
contractAddress={deployedContractData.address}
key={fn.name}
refreshDisplayVariables={refreshDisplayVariables}
inheritedFrom={inheritedFrom}
/>
))}
</>
);
};
Loading

0 comments on commit ce78234

Please sign in to comment.