-
Notifications
You must be signed in to change notification settings - Fork 55
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat:
use-inkathon
flipper frontend example (#59)
* replaces ink flipper example with standalone basic use-inkathon example * adds flipper frontend demo gif
- Loading branch information
Showing
19 changed files
with
2,518 additions
and
254 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
module.exports = { | ||
root: true, | ||
env: { browser: true, es2020: true }, | ||
extends: [ | ||
'eslint:recommended', | ||
'plugin:@typescript-eslint/recommended', | ||
'plugin:react-hooks/recommended', | ||
], | ||
ignorePatterns: ['dist', '.eslintrc.cjs'], | ||
parser: '@typescript-eslint/parser', | ||
plugins: ['react-refresh'], | ||
rules: { | ||
'react-refresh/only-export-components': [ | ||
'warn', | ||
{ allowConstantExport: true }, | ||
], | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,9 @@ | ||
# Logs | ||
logs | ||
*.log | ||
npm-debug.log* | ||
yarn-debug.log* | ||
yarn-error.log* | ||
pnpm-debug.log* | ||
lerna-debug.log* | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,21 @@ | ||
# Have Questions? | ||
# ink! Frontend Example | ||
|
||
For any questions about building front end applications with [useink](https://use.ink/frontend/overview/), join the [Element chat](https://matrix.to/#/%23useink:parity.io). | ||
This is a vanilla [vite + typescript](https://vitejs.dev/) project to showcase the use of [`useinkathon`](https://github.com/scio-labs/use-inkathon). | ||
|
||
## Getting Started | ||
|
||
You can use the package manager of your choice to install the dependencies and start the project in development mode. We like `pnpm` right now. But this example should work with `npm` & `yarn` as well. | ||
|
||
```sh | ||
pnpm install | ||
pnpm dev | ||
``` | ||
|
||
## Change the Code | ||
|
||
The actual interaction with the contract is all contained in the `./src/App.tsx` file. Every other file in the folder is only relevant for styling and bundling. | ||
|
||
|
||
## Demo | ||
|
||
<img src="demo.gif" width="600px" /> |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,30 +1,35 @@ | ||
{ | ||
"name": "flipper", | ||
"name": "flipper-frontend-example", | ||
"private": true, | ||
"version": "0.1.0", | ||
"version": "0.0.0", | ||
"type": "module", | ||
"scripts": { | ||
"dev": "vite", | ||
"build": "tsc && vite build", | ||
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0", | ||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", | ||
"preview": "vite preview" | ||
}, | ||
"dependencies": { | ||
"ui": "workspace:ui@*" | ||
"@polkadot/api-contract": "^10.11.2", | ||
"@polkadot/util-crypto": "^12.6.2", | ||
"@scio-labs/use-inkathon": "^0.6.3", | ||
"@tanstack/react-query": "^5.17.19", | ||
"react": "^18.2.0", | ||
"react-dom": "^18.2.0" | ||
}, | ||
"devDependencies": { | ||
"@types/react": "^18.0.37", | ||
"@types/react-dom": "^18.0.11", | ||
"@typescript-eslint/eslint-plugin": "^5.59.0", | ||
"@typescript-eslint/parser": "^5.59.0", | ||
"@vitejs/plugin-react": "^4.0.0", | ||
"autoprefixer": "^10.4.14", | ||
"eslint": "^8.38.0", | ||
"@types/react": "^18.2.48", | ||
"@types/react-dom": "^18.2.18", | ||
"@typescript-eslint/eslint-plugin": "^6.19.1", | ||
"@typescript-eslint/parser": "^6.19.1", | ||
"@vitejs/plugin-react-swc": "^3.5.0", | ||
"autoprefixer": "^10.4.17", | ||
"eslint": "^8.56.0", | ||
"eslint-plugin-react-hooks": "^4.6.0", | ||
"eslint-plugin-react-refresh": "^0.3.4", | ||
"postcss": "^8.4.24", | ||
"tailwindcss": "^3.3.2", | ||
"typescript": "^5.0.2", | ||
"vite": "^4.5.2" | ||
"eslint-plugin-react-refresh": "^0.4.5", | ||
"postcss": "^8.4.33", | ||
"tailwindcss": "^3.4.1", | ||
"typescript": "^5.3.3", | ||
"vite": "^5.0.12" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,4 +3,4 @@ export default { | |
tailwindcss: {}, | ||
autoprefixer: {}, | ||
}, | ||
}; | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,49 +1,222 @@ | ||
import { Button, Card, ConnectButton, InkLayout, formatContractName } from 'ui'; | ||
import { useCallSubscription, useContract, useTx, useWallet } from 'useink'; | ||
import { useTxNotifications } from 'useink/notifications'; | ||
import { pickDecoded, shouldDisable } from 'useink/utils'; | ||
import metadata from './assets/flipper.json'; | ||
import { CONTRACT_ROCOCO_ADDRESS } from './constants'; | ||
import { | ||
SubstrateDeployment, | ||
UseInkathonProvider, | ||
contractQuery, | ||
contractTx, | ||
decodeOutput, | ||
rococo, | ||
useBalance, | ||
useInkathon, | ||
useRegisteredContract, | ||
} from "@scio-labs/use-inkathon"; | ||
import { | ||
QueryClient, | ||
QueryClientProvider, | ||
useMutation, | ||
useQuery, | ||
} from "@tanstack/react-query"; | ||
|
||
function App() { | ||
const { account } = useWallet(); | ||
const contract = useContract(CONTRACT_ROCOCO_ADDRESS, metadata); | ||
const getSub = useCallSubscription<boolean>(contract, 'get', [], { | ||
defaultCaller: true, | ||
}); | ||
import CONTRACT_METADATA from "./flipper.json"; | ||
const CONTRACT_NAME = "flipper"; | ||
const queryClient = new QueryClient(); | ||
|
||
const flip = useTx(contract, 'flip'); | ||
useTxNotifications(flip); | ||
const getDeployments = async (): Promise<SubstrateDeployment[]> => { | ||
return [ | ||
{ | ||
contractId: CONTRACT_NAME, | ||
networkId: rococo.network, | ||
abi: CONTRACT_METADATA, | ||
address: "5Fsk6oqWHJzMAQmkBTVzxxqZPPngLbHG48Tro3i53LC3quao", | ||
}, | ||
]; | ||
}; | ||
|
||
export default function WrappedApp() { | ||
return ( | ||
<InkLayout | ||
className='md:py-12 md:p-6 p-4 h-screen flex items-center justify-center' | ||
animationSrc='https://raw.githubusercontent.com/paritytech/ink-workshop/d819d10a35b2ac3d2bff4f77a96701a527b3ad3a/frontend/public/dark-sea-creatures.json' | ||
> | ||
<Card className='mx-auto p-6 flex flex-col w-full max-w-md backdrop-blur-sm bg-opacity-70'> | ||
<h1 className='text-2xl font-bold'> | ||
{formatContractName(metadata.contract.name)} | ||
</h1> | ||
|
||
<p className='mt-6'> | ||
Flipped:{' '} | ||
<b className='uppercase'>{pickDecoded(getSub.result)?.toString()}</b> | ||
</p> | ||
|
||
{account ? ( | ||
<Button | ||
disabled={shouldDisable(flip)} | ||
onClick={() => flip.signAndSend()} | ||
className='mt-6' | ||
> | ||
{shouldDisable(flip) ? 'Flipping...' : 'Flip'} | ||
</Button> | ||
) : ( | ||
<ConnectButton className='mt-6' /> | ||
<QueryClientProvider client={queryClient}> | ||
<UseInkathonProvider | ||
appName="Flipper Frontend Example" | ||
deployments={getDeployments()} | ||
defaultChain={rococo} | ||
> | ||
<App /> | ||
</UseInkathonProvider> | ||
</QueryClientProvider> | ||
); | ||
} | ||
|
||
function App() { | ||
const { isConnected } = useInkathon(); | ||
return ( | ||
<div className="w-screen h-screen flex justify-center p-4"> | ||
<div className="max-w-2xl flex flex-col gap-4 "> | ||
<ConnectionState /> | ||
{isConnected && ( | ||
<> | ||
<FlipperInteraction /> | ||
</> | ||
)} | ||
</Card> | ||
</InkLayout> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
export default App; | ||
const ConnectionState = () => { | ||
const { | ||
connect, | ||
disconnect, | ||
isConnected, | ||
activeChain, | ||
activeAccount, | ||
setActiveAccount, | ||
accounts, | ||
} = useInkathon(); | ||
const { contract } = useRegisteredContract(CONTRACT_NAME); | ||
const balance = useBalance(activeAccount?.address, true); | ||
|
||
if (!isConnected) { | ||
return ( | ||
<div> | ||
<button type="button" onClick={() => (connect ? connect() : undefined)}> | ||
Connect | ||
</button> | ||
</div> | ||
); | ||
} | ||
|
||
return ( | ||
<div className="card"> | ||
<div className="grid grid-rows gap-2 overflow-hidden"> | ||
{activeChain && ( | ||
<div> | ||
<div className="text-sm text-slate-500">Chain</div> | ||
<div className="text-lg font-semibold">{activeChain.name}</div> | ||
</div> | ||
)} | ||
|
||
{activeAccount && accounts && ( | ||
<div> | ||
<div className="text-sm text-slate-500">Active Account</div> | ||
|
||
<select | ||
value={activeAccount.address} | ||
onChange={(v) => { | ||
const selectedAccount = accounts.find( | ||
(account) => account.address === v.target.value | ||
); | ||
|
||
if (selectedAccount && setActiveAccount) | ||
setActiveAccount(selectedAccount); | ||
}} | ||
> | ||
{accounts.map((account) => ( | ||
<option value={account.address} key={account.address}> | ||
{account.name ? account.name : account.address} | ||
</option> | ||
))} | ||
</select> | ||
<div className="text-sm text-ellipsis overflow-hidden"> | ||
{activeAccount.address} | ||
</div> | ||
</div> | ||
)} | ||
|
||
{balance && ( | ||
<div> | ||
<div className="text-sm text-slate-500">Account Balance</div> | ||
<div className="text-lg font-semibold"> | ||
{balance.balanceFormatted} | ||
</div> | ||
<div className="text-sm text-ellipsis overflow-hidden"> | ||
<a href="https://use.ink/5.x/faucet"> | ||
Get Tokens from Testnet Faucet | ||
</a> | ||
</div> | ||
</div> | ||
)} | ||
|
||
{contract && ( | ||
<div> | ||
<div className="text-sm text-slate-500">Contract</div> | ||
<div className="text-lg font-semibold text-ellipsis overflow-hidden"> | ||
{contract?.address.toHex()} | ||
</div> | ||
</div> | ||
)} | ||
</div> | ||
<button type="button" onClick={disconnect}> | ||
Disconnect | ||
</button> | ||
</div> | ||
); | ||
}; | ||
|
||
const FlipperInteraction = () => { | ||
const { api, activeAccount } = useInkathon(); | ||
const { contract } = useRegisteredContract(CONTRACT_NAME); | ||
|
||
const { data: flipState, refetch: refetchFlipState } = useQuery({ | ||
queryKey: ["flipper", "get"], | ||
queryFn: async () => { | ||
if (!api || !contract) throw Error("api or contract not available"); | ||
const outcome = await contractQuery(api, "", contract, "get", {}, []); | ||
return decodeOutput(outcome, contract, "get"); | ||
}, | ||
enabled: !!api && !!contract, | ||
}); | ||
|
||
const { | ||
mutateAsync: flip, | ||
isPending, | ||
error, | ||
data: flipResult, | ||
} = useMutation({ | ||
mutationKey: ["flipper", "flip"], | ||
mutationFn: async () => { | ||
if (!contract) throw new Error("Contract not available"); | ||
if (!api) throw new Error("API not available"); | ||
if (!activeAccount) throw new Error("Account not available"); | ||
|
||
return contractTx(api, activeAccount.address, contract, "flip", {}, []); | ||
}, | ||
onSuccess: () => { | ||
refetchFlipState(); | ||
}, | ||
}); | ||
|
||
return ( | ||
<div className="card"> | ||
<div> | ||
<h3 className="font-semibold text-lg">Flip </h3> | ||
<p className="slate-500">Change contracts storage value</p> | ||
</div> | ||
|
||
<div className="flex flex-row justify-between items-center"> | ||
{flipState?.decodedOutput && ( | ||
<div className="flex flow-row gap-2"> | ||
<code>Flipper.get()</code> | ||
<div className="font-bold">{flipState?.decodedOutput}</div> | ||
</div> | ||
)} | ||
|
||
<button type="submit" disabled={isPending} onClick={() => flip()}> | ||
{isPending ? "flipping..." : "Flipper.flip()"} | ||
</button> | ||
</div> | ||
|
||
{error && ( | ||
<> | ||
<hr /> | ||
<div className="error">{JSON.stringify(error)}</div> | ||
</> | ||
)} | ||
|
||
{flipResult && !!flipResult.successEvent && ( | ||
<> | ||
<hr /> | ||
<div className="success">Value Flipped!</div> | ||
</> | ||
)} | ||
</div> | ||
); | ||
}; |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
File renamed without changes.
Oops, something went wrong.