feat: add dice game
This commit is contained in:
parent
3a9af7396b
commit
2b44465bec
14 changed files with 203 additions and 38 deletions
|
@ -1,5 +1,5 @@
|
|||
import { observer } from 'mobx-react-lite'
|
||||
import { type FC } from 'react'
|
||||
import {type FC} from 'react'
|
||||
import { Outlet } from 'react-router-dom'
|
||||
|
||||
import { useScrollTop } from '../../../hooks/useScrollTop.ts'
|
||||
|
|
|
@ -2,8 +2,12 @@ import CoinIcon from "../../icons/CoinIcon";
|
|||
import ProfileIcon from "../../icons/ProfileIcon";
|
||||
import {ConnectStateButton} from "../../../web3/connect";
|
||||
import chipImg from '../../../../assets/icons/chip.png'
|
||||
import {useStores} from "../../../hooks/useStores.tsx";
|
||||
import {observer} from "mobx-react-lite";
|
||||
import {CheckBalanceButton} from "../../../web3/balance/CheckBalanceButton.tsx";
|
||||
|
||||
export const AppNav = () => {
|
||||
export const AppNav = observer(() => {
|
||||
const { userStore } = useStores()
|
||||
return (
|
||||
<div style={{ height: 'max-content' }} className="bg-[#30333C] text-white py-2 z-50 sticky top-0">
|
||||
<div className="container mx-auto">
|
||||
|
@ -20,7 +24,7 @@ export const AppNav = () => {
|
|||
<div className="flex flex-row gap-[3px] items-center px-[15px] py-[11px]">
|
||||
<CoinIcon />
|
||||
{/* placeholder for balance */}
|
||||
<span>0.000000</span>
|
||||
{userStore.balance ? <span>{userStore.balance}</span> : <CheckBalanceButton/>}
|
||||
</div>
|
||||
<ConnectStateButton/>
|
||||
</div>
|
||||
|
@ -33,4 +37,4 @@ export const AppNav = () => {
|
|||
</div>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
})
|
|
@ -1,7 +1,31 @@
|
|||
export const DicePlayButton = () => {
|
||||
import {usePlayRoulette} from "../../web3/functions/Roulette/usePlayRoulette.ts";
|
||||
import {FC, useEffect} from "react";
|
||||
|
||||
interface DicePlayButtonProps {
|
||||
value: number
|
||||
onSuccess: (result: unknown) => void
|
||||
onLoading: () => void
|
||||
}
|
||||
|
||||
export const DicePlayButton: FC<DicePlayButtonProps> = ({ value, onSuccess, onLoading }) => {
|
||||
const { playRoulette, result, isLoading } = usePlayRoulette()
|
||||
|
||||
useEffect(() => {
|
||||
if (result) onSuccess(result)
|
||||
}, [result])
|
||||
|
||||
useEffect(() => {
|
||||
if (isLoading) onLoading()
|
||||
}, [isLoading])
|
||||
|
||||
return (
|
||||
<button className="bg-rose-700 rounded-[100px] shadow text-white text-2xl font-bold py-5 px-10 hover:bg-rose-600">
|
||||
Бросить
|
||||
<button onClick={() => { playRoulette(value) }} className="bg-rose-700 rounded-[100px] shadow text-white text-2xl font-bold py-5 px-10 hover:bg-rose-600">
|
||||
{
|
||||
isLoading && 'Loading'
|
||||
}
|
||||
{
|
||||
(!isLoading && !result) && 'Играть'
|
||||
}
|
||||
</button>
|
||||
)
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import {IGetResult} from "../web3/functions/utils/useGetResult.ts";
|
||||
|
||||
const useIntervalAsync = <R = unknown>(fn: (tx: string) => Promise<R>, ms: number) => {
|
||||
const useIntervalAsync = <R = unknown>(fn: ({tx, notificationKey}: IGetResult) => Promise<R>, ms: number) => {
|
||||
const runningCount = useRef(0)
|
||||
const timeout = useRef<number>()
|
||||
const mountedRef = useRef(false)
|
||||
|
@ -17,9 +18,9 @@ const useIntervalAsync = <R = unknown>(fn: (tx: string) => Promise<R>, ms: numbe
|
|||
[ms],
|
||||
)
|
||||
|
||||
const run = useCallback(async (tx: string) => {
|
||||
const run = useCallback(async (tx: string, notificationKey?: string) => {
|
||||
runningCount.current += 1
|
||||
const result = await fn(tx)
|
||||
const result = await fn({tx, notificationKey})
|
||||
runningCount.current -= 1
|
||||
|
||||
next(run)
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import { useCallback, useState } from 'react'
|
||||
import { useAccount } from 'wagmi'
|
||||
import {stringifyError} from "../utils/error/stringifyError.ts";
|
||||
|
||||
export function useStatusState<ResultType, Arguments = void>() {
|
||||
const { isConnected } = useAccount()
|
||||
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<string>()
|
||||
const [result, setResult] = useState<ResultType>()
|
||||
|
@ -26,7 +23,7 @@ export function useStatusState<ResultType, Arguments = void>() {
|
|||
throw err
|
||||
}
|
||||
}
|
||||
}, [isConnected])
|
||||
}, [])
|
||||
|
||||
return {
|
||||
statuses: {
|
||||
|
|
18
frontend/casino/src/app/hooks/useStores.tsx
Normal file
18
frontend/casino/src/app/hooks/useStores.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import { createContext, type PropsWithChildren, useContext } from 'react'
|
||||
|
||||
import { type RootStore, rootStore } from '../stores/RootStore'
|
||||
|
||||
export const StoreContext = createContext<RootStore>(rootStore)
|
||||
|
||||
export function StoreProvider({
|
||||
children,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||
}: PropsWithChildren): JSX.Element {
|
||||
return (
|
||||
<StoreContext.Provider value={rootStore}>{children}</StoreContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function useStores(): RootStore {
|
||||
return useContext(StoreContext)
|
||||
}
|
|
@ -1,8 +1,29 @@
|
|||
import ContainerLayout from "../../utils/ContainerLayout"
|
||||
import { DicePlayButton } from "../../components/web3/DicePlayButton"
|
||||
import diceImg from '../../../assets/img/dice-img.png'
|
||||
import {useState} from "react";
|
||||
import {useStores} from "../../hooks/useStores.tsx";
|
||||
import {observer} from "mobx-react-lite";
|
||||
|
||||
export const DiceGamePage = observer(() => {
|
||||
const [diceValue, setDiceValue] = useState<string | undefined>()
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||
const { userStore } = useStores()
|
||||
|
||||
const onSuccess = (result: unknown) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
setDiceValue(result.value)
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
userStore.setBalance(result.balance ?? '0')
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
const onLoading = () => {
|
||||
setIsLoading(true)
|
||||
}
|
||||
|
||||
export const DiceGamePage = () => {
|
||||
return (
|
||||
<ContainerLayout>
|
||||
<div className="flex flex-col items-center text-white">
|
||||
|
@ -17,16 +38,16 @@ export const DiceGamePage = () => {
|
|||
<img src={diceImg} alt="Dice" />
|
||||
</div>
|
||||
{/* Кнопка для бека */}
|
||||
<DicePlayButton />
|
||||
<DicePlayButton onLoading={onLoading} onSuccess={onSuccess} value={5}/>
|
||||
<div className="flex flex-row justify-around w-full font-semibold">
|
||||
<div className="bg-gray-500 rounded-[30px] py-5 px-10 text-2xl">
|
||||
Вы загадали: 5
|
||||
</div>
|
||||
<div className="bg-gray-800 rounded-[30px] py-5 px-10 text-2xl">
|
||||
Выпало число: 5
|
||||
</div>
|
||||
{(diceValue && !isLoading) && <div className="bg-gray-800 rounded-[30px] py-5 px-10 text-2xl">
|
||||
Выпало число: {diceValue}
|
||||
</div>}
|
||||
</div>
|
||||
</div>
|
||||
</ContainerLayout>
|
||||
)
|
||||
}
|
||||
})
|
11
frontend/casino/src/app/stores/RootStore.ts
Normal file
11
frontend/casino/src/app/stores/RootStore.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { UserStore } from './User/UserStore'
|
||||
|
||||
export class RootStore {
|
||||
userStore: UserStore
|
||||
|
||||
constructor() {
|
||||
this.userStore = new UserStore()
|
||||
}
|
||||
}
|
||||
|
||||
export const rootStore = new RootStore()
|
13
frontend/casino/src/app/stores/User/UserStore.ts
Normal file
13
frontend/casino/src/app/stores/User/UserStore.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { makeAutoObservable } from 'mobx'
|
||||
|
||||
export class UserStore {
|
||||
balance: string | undefined = undefined
|
||||
constructor() {
|
||||
makeAutoObservable(this)
|
||||
}
|
||||
|
||||
setBalance(balance: string) {
|
||||
this.balance = balance
|
||||
console.log(balance)
|
||||
}
|
||||
}
|
18
frontend/casino/src/app/web3/balance/CheckBalanceButton.tsx
Normal file
18
frontend/casino/src/app/web3/balance/CheckBalanceButton.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import {useCheckBalance} from "./useCheckBalance.ts";
|
||||
import {observer} from "mobx-react-lite";
|
||||
import {useEffect} from "react";
|
||||
import {useStores} from "../../hooks/useStores.tsx";
|
||||
|
||||
export const CheckBalanceButton = observer(() => {
|
||||
const { checkBalance, result } = useCheckBalance()
|
||||
const { userStore } = useStores()
|
||||
useEffect(() => {
|
||||
if (result) userStore.setBalance(result.balance)
|
||||
}, [result])
|
||||
|
||||
return (
|
||||
<button onClick={checkBalance}>
|
||||
Check balance
|
||||
</button>
|
||||
);
|
||||
});
|
36
frontend/casino/src/app/web3/balance/useCheckBalance.ts
Normal file
36
frontend/casino/src/app/web3/balance/useCheckBalance.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import {useWalletConnect} from "@cityofzion/wallet-connect-sdk-react";
|
||||
import {useCallback} from "react";
|
||||
import {useGetResult} from "../functions/utils/useGetResult.ts";
|
||||
import {config} from "../functions/config/config.ts";
|
||||
|
||||
export const useCheckBalance = () => {
|
||||
const wcSdk = useWalletConnect()
|
||||
const { getResult, ...statuses } = useGetResult()
|
||||
|
||||
const checkBalance = useCallback(async () => {
|
||||
console.log(config.zaCoin.contractAddress)
|
||||
const address = wcSdk.getAccountAddress()
|
||||
|
||||
if (!address) return
|
||||
|
||||
const resp = await wcSdk.invokeFunction({
|
||||
invocations: [{
|
||||
scriptHash: config.zaCoin.contractAddress,
|
||||
operation: 'balanceOf',
|
||||
args: [
|
||||
{ type: 'Hash160', value: address },
|
||||
]
|
||||
}],
|
||||
signers: [{
|
||||
scopes: 'Global',
|
||||
}]
|
||||
})
|
||||
console.log(resp)
|
||||
await getResult(resp, 'balance')
|
||||
}, [wcSdk, getResult])
|
||||
|
||||
return {
|
||||
checkBalance,
|
||||
...statuses
|
||||
}
|
||||
}
|
|
@ -1,15 +1,13 @@
|
|||
import {useWalletConnect} from "@cityofzion/wallet-connect-sdk-react";
|
||||
import {useCallback} from "react";
|
||||
import {useGetResult} from "../utils/useGetResult.ts";
|
||||
import {useStatusState} from "../../../hooks/useStatusState.ts";
|
||||
import {config} from "../config/config.ts";
|
||||
|
||||
export const usePlayRoulette = () => {
|
||||
const wcSdk = useWalletConnect()
|
||||
const { getResult } = useGetResult()
|
||||
const { statuses, wrapPromise } = useStatusState()
|
||||
const { getResult, ...statuses } = useGetResult()
|
||||
|
||||
const playRoulette = useCallback(wrapPromise(async () => {
|
||||
const playRoulette = useCallback(async (value: number) => {
|
||||
// const resp = await wcSdk.invokeFunction({
|
||||
// invocations: [{
|
||||
// scriptHash: '270c825a5ac041e18be45074bbb942255164a214',
|
||||
|
@ -22,13 +20,14 @@ export const usePlayRoulette = () => {
|
|||
// scopes: 'Global',
|
||||
// }]
|
||||
// })
|
||||
console.log(config.roulette.contractAddress)
|
||||
const resp = await wcSdk.invokeFunction({
|
||||
invocations: [{
|
||||
scriptHash: config.roulette.contractAddress,
|
||||
operation: 'playRoulette',
|
||||
args: [
|
||||
{ type: 'Integer', value: '40' },
|
||||
{ type: 'Integer', value: '3' },
|
||||
{ type: 'Integer', value: value.toString() },
|
||||
]
|
||||
}],
|
||||
signers: [{
|
||||
|
@ -36,9 +35,8 @@ export const usePlayRoulette = () => {
|
|||
}]
|
||||
})
|
||||
console.log(resp)
|
||||
const result = await getResult(resp)
|
||||
console.log(result)
|
||||
}), [wcSdk, getResult, wrapPromise])
|
||||
await getResult(resp, 'rouletteNumber')
|
||||
}, [wcSdk, getResult])
|
||||
|
||||
return {
|
||||
playRoulette,
|
||||
|
|
|
@ -3,9 +3,12 @@ export const config = {
|
|||
contractAddress: '13f7312810758d0e2271e2c620ba93f3568e0e8c'
|
||||
},
|
||||
roulette: {
|
||||
contractAddress: '13f7312810758d0e2271e2c620ba93f3568e0e8c'
|
||||
contractAddress: '9a8e8297364f134f29bafe1322323af73e5ab434'
|
||||
},
|
||||
slotMachine: {
|
||||
contractAddress: '13f7312810758d0e2271e2c620ba93f3568e0e8c'
|
||||
},
|
||||
zaCoin: {
|
||||
contractAddress: 'fdfb5c2974779e9cb9347e083a80054feae55a2d'
|
||||
}
|
||||
}
|
|
@ -1,36 +1,57 @@
|
|||
import {useCallback} from "react";
|
||||
import axios from "axios";
|
||||
import useIntervalAsync from "../../../hooks/useIntervalAsync.ts";
|
||||
import {useStatusState} from "../../../hooks/useStatusState.ts";
|
||||
|
||||
export interface IGetResult {
|
||||
tx: string;
|
||||
notificationKey?: string
|
||||
}
|
||||
|
||||
const baseUrlToGet = 'https://dora.coz.io/api/v2/neo3/testnet/log/'
|
||||
export const useGetResult = () => {
|
||||
const { statuses, wrapPromise } = useStatusState<{ value: string, balance: string} | undefined, IGetResult>()
|
||||
const getResultReq = useCallback(async (tx: string | undefined) => {
|
||||
const result = await axios.get(baseUrlToGet + tx)
|
||||
console.log(result)
|
||||
return result
|
||||
}, [])
|
||||
|
||||
const { run: runIsApprovedRefetch } = useIntervalAsync(async (tx) => {
|
||||
const { run: runIsApprovedRefetch } = useIntervalAsync(wrapPromise(async ({tx, notificationKey}) => {
|
||||
if (!tx) return
|
||||
try {
|
||||
const result = await getResultReq(tx)
|
||||
if (!result) setTimeout(() => {
|
||||
runIsApprovedRefetch(tx)
|
||||
}, 3000)
|
||||
return result
|
||||
} catch (e) { setTimeout(() => {
|
||||
runIsApprovedRefetch(tx)
|
||||
}, 3000) }
|
||||
}, 3000)
|
||||
|
||||
const getResult = useCallback(async (tx: string | undefined) => {
|
||||
let balance;
|
||||
|
||||
if (notificationKey === 'balance') {
|
||||
balance = result.data?.stack[0].value
|
||||
} else {
|
||||
balance = result.data?.notifications?.find((item: { event_name: string | undefined; }) => item?.event_name === 'playerBalance')?.state?.value[0].value
|
||||
}
|
||||
const value = result.data?.notifications?.find((item: { event_name: string | undefined; }) => item?.event_name === notificationKey)?.state?.value[0].value
|
||||
|
||||
return {
|
||||
value,
|
||||
balance
|
||||
}
|
||||
} catch (e) { setTimeout(() => {
|
||||
runIsApprovedRefetch(tx, notificationKey)
|
||||
}, 3000) }
|
||||
}), 3000)
|
||||
|
||||
const getResult = useCallback(async (tx: string | undefined, notificationKey?: string) => {
|
||||
if (!tx) return
|
||||
console.log(tx)
|
||||
const result = await runIsApprovedRefetch(tx)
|
||||
const result = await runIsApprovedRefetch(tx, notificationKey)
|
||||
console.log(result)
|
||||
}, [runIsApprovedRefetch])
|
||||
|
||||
return {
|
||||
getResult
|
||||
getResult,
|
||||
...statuses
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue