From 15bcf50cd330e1509209b3bf6da372e44890ba00 Mon Sep 17 00:00:00 2001 From: jonty007 Date: Mon, 20 Jan 2025 21:30:12 +0530 Subject: [PATCH 01/11] Chess game refactor and uI --- .../chess-app/src/components/ChessBoard.tsx | 447 ++++++++++++++++-- packages/chess-app/src/components/Gallery.tsx | 48 +- packages/chess-app/src/components/Navbar.tsx | 21 +- .../chess-app/src/components/StartGame.tsx | 235 ++++++++- packages/chess-app/src/index.css | 1 + packages/chess-app/tailwind.config.js | 16 +- packages/chess-contracts/.env.example | 11 - packages/chess-server/README.md | 4 +- packages/chess-server/package.json | 3 - packages/chess-server/scripts/deploy.ts | 2 +- packages/components/built/Loader.js | 2 +- packages/components/built/Modal.d.ts | 10 +- packages/components/built/Modal.js | 9 +- packages/components/built/Wallet.js | 22 +- packages/components/src/Loader.tsx | 2 +- packages/components/src/Modal.tsx | 24 +- packages/components/src/Wallet.tsx | 80 +++- packages/vite-template/src/index.css | 1 + 18 files changed, 797 insertions(+), 141 deletions(-) diff --git a/packages/chess-app/src/components/ChessBoard.tsx b/packages/chess-app/src/components/ChessBoard.tsx index 48bcb7be0..3805397cc 100644 --- a/packages/chess-app/src/components/ChessBoard.tsx +++ b/packages/chess-app/src/components/ChessBoard.tsx @@ -1,23 +1,46 @@ -import { ComputerContext, Modal, UtilsContext } from "@bitcoin-computer/components" -import { ChessContract, ChessContractHelper, Chess as ChessLib, Square } from "@bitcoin-computer/chess-contracts" -import { useCallback, useContext, useEffect, useState } from "react" -import { useParams, Link } from "react-router-dom" -import { Chessboard } from "react-chessboard" -import { getGameState } from "./utils" -import { VITE_CHESS_GAME_MOD_SPEC } from "../constants/modSpecs" +import { Auth, ComputerContext, Modal, UtilsContext } from '@bitcoin-computer/components' +import { + ChessContract, + ChessContractHelper, + Chess as ChessLib, + Square, +} from '@bitcoin-computer/chess-contracts' +import { useCallback, useContext, useEffect, useRef, useState } from 'react' +import { useParams, Link, useNavigate } from 'react-router-dom' +import { Chessboard } from 'react-chessboard' +import { getGameState } from './utils' +import { getHash } from '../services/secret.service' +import { VITE_CHESS_GAME_MOD_SPEC } from '../constants/modSpecs' +import { BiGitCompare } from 'react-icons/bi' + +const newGameModal = 'new-game-modal' + +export type Class = new (...args: unknown[]) => unknown + +export type UserQuery = Partial<{ + mod: string + publicKey: string + limit: number + offset: number + order: 'ASC' | 'DESC' + ids: string[] + contract: { + class: T + args?: ConstructorParameters + } +}> function currentPlayer(fen: string) { - const parts = fen.split(" ") + const parts = fen.split(' ') const activeColor = parts[1] - if (activeColor === "w") return "White" - if (activeColor === "b") return "Black" - throw new Error("Invalid FEN: Unknown active color") + if (activeColor === 'w') return 'White' + if (activeColor === 'b') return 'Black' + throw new Error('Invalid FEN: Unknown active color') } function getWinnerPubKey(chessLibrary: ChessLib, { publicKeyW, publicKeyB }: ChessContract) { - if (chessLibrary.isCheckmate()) - return chessLibrary.turn() === 'b' ? publicKeyW : publicKeyB + if (chessLibrary.isCheckmate()) return chessLibrary.turn() === 'b' ? publicKeyW : publicKeyB return null } @@ -51,7 +74,7 @@ function ListLayout(props: { listOfMoves: string[] }) { return
{rows}
} -function WinnerModal(data: { winnerPubKey: string, userPubKey: string }) { +function WinnerModal(data: { winnerPubKey: string; userPubKey: string }) { return ( <>
@@ -84,11 +107,331 @@ function WinnerModal(data: { winnerPubKey: string, userPubKey: string }) { ) } +export function NewGameModalContent({ + nameW, + setName, + nameB, + setNameB, + publicKeyB, + setSecondPlayerPublicKey, + amount, + setAmount, + copied, + setCopied, + serializedTx, + setSerializedTx, +}: any) { + const navigate = useNavigate() + const computerW = useContext(ComputerContext) + + const { showLoader, showSnackBar } = UtilsContext.useUtilsComponents() + + const handleCopy = () => { + navigator.clipboard + .writeText(serializedTx) + .then(() => setCopied(true)) + .catch(() => setCopied(false)) + + setTimeout(() => setCopied(false), 2000) + } + + const onSubmit = async (e: React.SyntheticEvent) => { + e.preventDefault() + try { + showLoader(true) + const secretHashW = await getHash() + const secretHashB = await getHash() + + if (!secretHashW || !secretHashB) throw new Error('Could not obtain hash from server') + + const publicKeyW = computerW.getPublicKey() + const chessContractHelper = new ChessContractHelper({ + computer: computerW, + amount: parseFloat(amount) * 1e8, + nameW, + nameB, + publicKeyW, + publicKeyB, + secretHashW, + secretHashB, + mod: VITE_CHESS_GAME_MOD_SPEC, + }) + const tx = await chessContractHelper.makeTx() + setSerializedTx(`http://localhost:1032/start/${tx.serialize()}`) + + showLoader(false) + } catch (err) { + if (err instanceof Error) { + showSnackBar(err.message, false) + } else { + showSnackBar('Error occurred!', false) + } + showLoader(false) + } + } + + return ( + <> + {!!serializedTx ? ( +
+
+

+ {/* Dynamically show truncated content */} + {`${serializedTx.slice(0, 50)}...`} +

+ {/* Show full link on hover */} + + {serializedTx} + +
+ + {/* Button Container */} +
+ +
+
+ ) : ( + <> +
+
+
+ + setAmount(e.target.value)} + placeholder="Enter amount" + className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-md focus:ring-blue-500 focus:border-blue-500 block w-full p-3 dark:bg-gray-700 dark:border-gray-600 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" + /> +
+
+ + setSecondPlayerPublicKey(e.target.value)} + className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-md focus:ring-blue-500 focus:border-blue-500 block w-full p-3 dark:bg-gray-700 dark:border-gray-600 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500" + /> +
+
+ +
+ + )} + + ) +} + +export function NewGameModal() { + const [nameW, setName] = useState('White') + const [nameB, setNameB] = useState('Black') + const [publicKeyB, setSecondPlayerPublicKey] = useState('') + const [amount, setAmount] = useState(`0.1`) + const [copied, setCopied] = useState(false) + const [serializedTx, setSerializedTx] = useState('') + + const handleClear = () => { + setName('White') + setNameB('Black') + setSecondPlayerPublicKey('') + setAmount(`0.1`) + setCopied(false) + setSerializedTx('') + } + return ( + + ) +} + +function GameCard({ chessContract }: { chessContract: ChessContract }) { + const chessLib = new ChessLib(chessContract.fen) + const publicKey = Auth.getComputer().getPublicKey() + return ( + <> +
+
+

+ {getGameState(chessLib)} +

+

+ {chessContract.nameW} +

+
+ +
+

+ {chessContract.nameB} +

+
+
+ + ) +} + +const InfiniteScroll = () => { + const [items, setItems] = useState([]) + const [hasMore, setHasMore] = useState(true) + const [loading, setLoading] = useState(false) + const scrollContainerRef = useRef(null) + const computer = useContext(ComputerContext) + const contractsPerPage = 10 + + const fetchMoreItems = async (q: UserQuery): Promise => { + const query = { ...q } + query.limit = contractsPerPage * 2 // For a chess game we have two contracts Payment and ChessContract + query.order = 'DESC' + const result = (await computer.query(query)) as string[] + const gamesPromise: Promise[] = [] + + const filteredRevs = (result || []).filter((rev) => rev.split(':')[1] === '0') + return filteredRevs + // filteredRevs.forEach((chessRev) => { + // return gamesPromise.push(computer.sync(chessRev) as Promise) + // }) + + // return Promise.all(gamesPromise) + } + + const loadMoreItems = useCallback(async () => { + if (loading || !hasMore) return + + setLoading(true) + const newItems = await fetchMoreItems({ mod: VITE_CHESS_GAME_MOD_SPEC, offset: items.length }) + + setItems((prev) => [...prev, ...newItems]) + if (newItems.length < contractsPerPage) setHasMore(false) // Stop fetching when no more items are available + setLoading(false) + }, [loading, hasMore, items]) + + const handleScroll = () => { + const container = scrollContainerRef.current + if (container) { + const bottomReached = container.scrollTop + container.clientHeight >= container.scrollHeight + if (bottomReached) { + loadMoreItems() + } + } + } + + useEffect(() => { + // Initial fetch without relying on scroll + const initialFetch = async () => { + setLoading(true) + const initialItems = await fetchMoreItems({ mod: VITE_CHESS_GAME_MOD_SPEC, offset: 0 }) + setItems(initialItems) + if (initialItems.length < contractsPerPage) setHasMore(false) + setLoading(false) + } + + initialFetch() + }, []) + + return ( +
+
+
    + {items.map((item, index) => ( +
  • + {item} + {/* */} +
  • + ))} + {!hasMore && ( +
  • + No more items to load. +
  • + )} +
+ {loading && ( +
Loading...
+ )} +
+
+ ) +} + export function ChessBoard() { const params = useParams() const { showSnackBar } = UtilsContext.useUtilsComponents() - const [gameId] = useState(params.id || "") - const [orientation, setOrientation] = useState<"white" | "black">("white") + const [gameId] = useState(params.id || '') + const [orientation, setOrientation] = useState<'white' | 'black'>('white') const [skipSync, setSkipSync] = useState(false) const [winnerData, setWinnerData] = useState({}) const [game, setGame] = useState(null) @@ -107,7 +450,7 @@ export function ChessBoard() { return } setWinnerData({ winnerPubKey: winnerPubKey, userPubKey: computer.getPublicKey() }) - Modal.showModal("winner-modal") + Modal.showModal('winner-modal') }, [game, chessContract, computer]) const syncChessContract = useCallback(async () => { @@ -141,32 +484,47 @@ export function ChessBoard() { useEffect(() => { const intervalId = setInterval(() => { syncChessContract() - }, 3000) + }, 10000) return () => clearInterval(intervalId) }, [syncChessContract]) + const publishMove = async (from: Square, to: Square) => { + if (!chessContract) throw new Error('Chess contract is not defined.') + const chessHelper = ChessContractHelper.fromContract( + computer, + chessContract, + VITE_CHESS_GAME_MOD_SPEC, + ) + await chessHelper.move(chessContract, from, to) + } + const handleError = (error: unknown) => { + if (error instanceof Error) { + showSnackBar(error.message, false) + setSkipSync(false) + syncChessContract() + } + } + // OnDrop action for chess game const onDropSync = (from: Square, to: Square) => { - let dropResult = false; - (async () => { - if (!chessContract) throw new Error('Chess contract is not defined.') - const chessHelper = ChessContractHelper.fromContract(computer, chessContract, VITE_CHESS_GAME_MOD_SPEC) - const { newChessContract } = await chessHelper.move(chessContract, from, to) - + try { + const chessGameInstance = new ChessLib(chessContract?.fen) setSkipSync(true) - setGame(new ChessLib(newChessContract.fen)) - dropResult = true + chessGameInstance.move({ + from, + to, + }) - })() - .catch((error) => { - if (error instanceof Error) { - showSnackBar(error.message, false) - setSkipSync(false) - syncChessContract() - } - }) - return dropResult + setGame(new ChessLib(chessGameInstance.fen())) + publishMove(from, to).catch((error) => { + handleError(error) + }) + return true + } catch (error) { + handleError(error) + return false + } } return ( @@ -175,6 +533,19 @@ export function ChessBoard() { {/* Game Info Column */} {game && (
+ +
+ +
+
@@ -200,7 +571,7 @@ export function ChessBoard() {
- { orientation === 'white' ? chessContract!.nameB : chessContract!.nameW } + {orientation === 'white' ? chessContract!.nameB : chessContract!.nameW}
@@ -218,7 +589,7 @@ export function ChessBoard() {
- { orientation === 'white' ? chessContract!.nameW : chessContract!.nameB } + {orientation === 'white' ? chessContract!.nameW : chessContract!.nameB}
@@ -243,6 +614,8 @@ export function ChessBoard() { contentData={winnerData} id={'winner-modal'} /> + +
) } diff --git a/packages/chess-app/src/components/Gallery.tsx b/packages/chess-app/src/components/Gallery.tsx index 278348dcb..9340090d9 100644 --- a/packages/chess-app/src/components/Gallery.tsx +++ b/packages/chess-app/src/components/Gallery.tsx @@ -1,12 +1,12 @@ -import { Computer } from "@bitcoin-computer/lib" -import { useContext, useEffect, useMemo, useState } from "react" -import { Link, useLocation, useNavigate } from "react-router-dom" -import { initFlowbite } from "flowbite" -import { Auth, ComputerContext, UtilsContext } from "@bitcoin-computer/components" -import { BiGitCompare } from "react-icons/bi" -import { ChessContract } from "../../../chess-contracts/" -import { Chess as ChessLib } from "../../../chess-contracts/" -import { getGameState } from "./utils" +import { Computer } from '@bitcoin-computer/lib' +import { useContext, useEffect, useMemo, useState } from 'react' +import { Link, useLocation, useNavigate } from 'react-router-dom' +import { initFlowbite } from 'flowbite' +import { Auth, ComputerContext, UtilsContext } from '@bitcoin-computer/components' +import { BiGitCompare } from 'react-icons/bi' +import { ChessContract } from '../../../chess-contracts/' +import { Chess as ChessLib } from '../../../chess-contracts/' +import { getGameState } from './utils' export type Class = new (...args: unknown[]) => unknown @@ -31,8 +31,8 @@ function GameCard({ chessContract }: { chessContract: ChessContract }) {
@@ -42,8 +42,8 @@ function GameCard({ chessContract }: { chessContract: ChessContract }) {

@@ -55,8 +55,8 @@ function GameCard({ chessContract }: { chessContract: ChessContract }) {

@@ -78,9 +78,9 @@ function HomePageCard({ content }: { content: () => JSX.Element }) { ) } -function ValueComponent({ rev, computer }: { rev: string; computer: Computer }) { +export function ValueComponent({ rev, computer }: { rev: string; computer: Computer }) { const [value, setValue] = useState({ _id: '' } as ChessContract) - const [errorMsg, setMsgError] = useState("") + const [errorMsg, setMsgError] = useState('') const [loading, setLoading] = useState(true) useEffect(() => { @@ -178,9 +178,9 @@ function FromRevs({ revs, computer }: { revs: string[]; computer: Computer }) { } type PaginationType = { - isPrevAvailable: boolean, - isNextAvailable: boolean, - handlePrev: React.MouseEventHandler, + isPrevAvailable: boolean + isNextAvailable: boolean + handlePrev: React.MouseEventHandler handleNext: React.MouseEventHandler } function Pagination({ isPrevAvailable, handlePrev, isNextAvailable, handleNext }: PaginationType) { @@ -254,9 +254,9 @@ export function WithPagination(q: UserQuery) { const params = useMemo( () => Object.fromEntries(new URLSearchParams(location.search)), - [location.search] + [location.search], ) - + useEffect(() => { initFlowbite() }, []) @@ -270,7 +270,7 @@ export function WithPagination(q: UserQuery) { query.order = 'DESC' const result = await computer.query(query) setIsNextAvailable(result.length > contractsPerPage) - setRevs(result.slice(0, contractsPerPage)) + setRevs((result.slice(0, contractsPerPage) || []).filter((rev) => rev.split(':')[1] === '0')) if (pageNum === 0 && result?.length === 0) { setShowNoAsset(true) } @@ -306,7 +306,7 @@ export function WithPagination(q: UserQuery) { diff --git a/packages/chess-app/src/components/Navbar.tsx b/packages/chess-app/src/components/Navbar.tsx index f05941ad0..3ff28ffa4 100644 --- a/packages/chess-app/src/components/Navbar.tsx +++ b/packages/chess-app/src/components/Navbar.tsx @@ -107,7 +107,7 @@ export function NotLoggedMenu() { localStorage.setItem('CHAIN', chain) localStorage.setItem('NETWORK', network) setDropDownLabel(formatChainAndNetwork(chain, network)) - window.location.href = "/" + window.location.href = '/' } catch (error) { if (error instanceof Error) { showSnackBar(`Error setting chain and network: ${error.message}`, false) @@ -197,14 +197,14 @@ function WalletItem() { const capitalizeFirstLetter = (s: string) => s.charAt(0).toUpperCase() + s.slice(1) -function Item({ dest }: { dest: string }) { +function Item({ dest, label }: { dest: string; label: string }) { return ( - {capitalizeFirstLetter(dest)} + {capitalizeFirstLetter(label)} ) @@ -213,7 +213,7 @@ function Item({ dest }: { dest: string }) { export function LoggedInMenu() { return (

    - +
) @@ -248,12 +248,21 @@ function NavbarDropdownButton() { ) } -export function Logo({ name = 'TBC Chess' }) { +export function Logo() { + const { chain } = Auth.defaultConfiguration() + const AppName = + chain === 'LTC' + ? 'Lite Chess' + : chain === 'DOGE' + ? 'Doge Chess' + : chain === 'PEPE' + ? 'Pepe Chess' + : 'Bit Chess' return ( Bitcoin Computer Logo - {name} + {AppName} ) diff --git a/packages/chess-app/src/components/StartGame.tsx b/packages/chess-app/src/components/StartGame.tsx index e22a4cdc5..a7c773f9b 100644 --- a/packages/chess-app/src/components/StartGame.tsx +++ b/packages/chess-app/src/components/StartGame.tsx @@ -1,20 +1,24 @@ -import { useContext, useState } from "react" -import { ComputerContext, Modal, UtilsContext } from "@bitcoin-computer/components" -import { Computer, Transaction } from "@bitcoin-computer/lib" -import { useParams } from "react-router-dom" -import { ChessContract, ChessContractHelper } from "../../../chess-contracts/" -import { VITE_CHESS_GAME_MOD_SPEC } from "../constants/modSpecs" +import { useContext, useEffect, useState } from 'react' +import { ComputerContext, Modal, UtilsContext } from '@bitcoin-computer/components' +import { Computer, Transaction } from '@bitcoin-computer/lib' +import { useParams } from 'react-router-dom' +import { ChessContract, ChessContractHelper } from '../../../chess-contracts/' +import { VITE_CHESS_GAME_MOD_SPEC } from '../constants/modSpecs' + +const startGameModal = 'start-game-modal' function ErrorContent(msg: string) { return ( <>
- Something went wrong.
{msg} + Something went wrong. +
+ {msg}
- +

- {copied ? "Copied!" : "Copy Link"} + {copied ? 'Copied!' : 'Copy Link'}

- ) } +export function StartGameModalContent({ + serialized, + game, + computer, + copied, + setCopied, + link, + setLink, +}: { + serialized: string + game: ChessContract + computer: Computer + copied: any + setCopied: any + link: any + setLink: any +}) { + const { showLoader, showSnackBar } = UtilsContext.useUtilsComponents() + + const handleCopy = () => { + navigator.clipboard + .writeText(link) + .then(() => setCopied(true)) + .catch(() => setCopied(false)) + + setTimeout(() => setCopied(false), 2000) + } + + const onSubmit = async (e: React.SyntheticEvent) => { + e.preventDefault() + try { + showLoader(true) + const tx = Transaction.deserialize(serialized) + const chessContractHelper = ChessContractHelper.fromContract( + computer, + game, + VITE_CHESS_GAME_MOD_SPEC, + ) + const txId = await chessContractHelper.completeTx(tx) + setLink(`http://localhost:1032/game/${txId}:0`) + showLoader(false) + } catch (err) { + if (err instanceof Error) { + showSnackBar(err.message, false) + } else { + showSnackBar('Error occurred!', false) + } + showLoader(false) + } + } + + return ( + <> + {!!link ? ( +
+
+

+ {/* Dynamically show truncated content */} + {`${link.slice(0, 50)}...`} +

+ {/* Show full link on hover */} + + {link} + +
+ + {/* Button Container */} +
+ +
+
+ ) : ( + <> + {!!game && ( +
+
+
+

+ You have been challenged to a game +

+
+ +
+
+ + Amount + + + {game.amount / 1e8} {computer.getChain()} + +
+
+
+
+ + Opponent + +
+ + {game.publicKeyW} + +
+ {game.publicKeyW} +
+
+
+
+
+ +
+ )} + + )} + + ) +} + +export function StartGameModal() { + const { serialized } = useParams() + const computer = useContext(ComputerContext) + const [game, setGame] = useState(null) + const [copied, setCopied] = useState(false) + const [link, setLink] = useState('') + + const { showLoader, showSnackBar } = UtilsContext.useUtilsComponents() + + useEffect(() => { + const fetch = async () => { + try { + if (serialized) { + const tx = Transaction.deserialize(serialized) + const { effect } = await computer.encode(tx.onChainMetaData as never) + const { res } = effect + const game = res as unknown as ChessContract + setGame(game) + Modal.showModal(startGameModal) + showLoader(false) + } else { + showSnackBar('Not a valid link', false) + } + } catch (error) { + showLoader(false) + if (error instanceof Error) { + showSnackBar(error.message, false) + } else { + showSnackBar('Error occurred', false) + } + } finally { + showLoader(false) + } + } + fetch() + }, [serialized]) + + return ( + + ) +} + export default function StartGame() { const computer = useContext(ComputerContext) - const [errorMsg, setErrorMsg] = useState("") + const [errorMsg, setErrorMsg] = useState('') return ( <> + ) } diff --git a/packages/chess-app/src/index.css b/packages/chess-app/src/index.css index a47e51dd6..eebebf109 100644 --- a/packages/chess-app/src/index.css +++ b/packages/chess-app/src/index.css @@ -2,6 +2,7 @@ @tailwind components; @tailwind utilities; body { + @apply bg-white dark:bg-gray-900; margin: 0; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif; diff --git a/packages/chess-app/tailwind.config.js b/packages/chess-app/tailwind.config.js index c53aed771..f65149acc 100644 --- a/packages/chess-app/tailwind.config.js +++ b/packages/chess-app/tailwind.config.js @@ -1,18 +1,18 @@ /** @type {import('tailwindcss').Config} */ module.exports = { content: [ - "./src/**/*.{js,jsx,ts,tsx}", - "./node_modules/@bitcoin-computer/components/built/**/*.{js,jsx,ts,tsx}", - "../components/built/**/*.{js,jsx,ts,tsx}" + './src/**/*.{js,jsx,ts,tsx}', + './node_modules/@bitcoin-computer/components/built/**/*.{js,jsx,ts,tsx}', + '../components/built/**/*.{js,jsx,ts,tsx}', ], - darkMode: "media", + darkMode: 'media', theme: { extend: { colors: { - "blue-1": "#000F38", - "blue-2": "#002A99", - "blue-3": "#0046FF", - "blue-4": "#A7BFFF", + 'blue-1': '#000F38', + 'blue-2': '#002A99', + 'blue-3': '#0046FF', + 'blue-4': '#A7BFFF', }, }, }, diff --git a/packages/chess-contracts/.env.example b/packages/chess-contracts/.env.example index 8f6db2530..7cff0b340 100644 --- a/packages/chess-contracts/.env.example +++ b/packages/chess-contracts/.env.example @@ -1,12 +1 @@ -# Application configuration -VITE_CHAIN=LTC -VITE_NETWORK=regtest -VITE_URL=http://127.0.0.1:1031 VITE_API_BASE_URL=http://127.0.0.1:4000 - -# Application Port -VITE_PORT=1032 - -# Smart Contract Locations -# Run 'npm run deploy' to generate module specifiers -VITE_CHESS_GAME_MOD_SPEC= diff --git a/packages/chess-server/README.md b/packages/chess-server/README.md index 97cb46c82..1fe62f3f9 100644 --- a/packages/chess-server/README.md +++ b/packages/chess-server/README.md @@ -21,7 +21,7 @@ npm run deploy Start the database. ``` -npm run start-postgress +npm run start:postgress ``` Then start the express server. @@ -43,4 +43,4 @@ npm run up ``` npm test -``` \ No newline at end of file +``` diff --git a/packages/chess-server/package.json b/packages/chess-server/package.json index 2c785309f..f7e3b0167 100644 --- a/packages/chess-server/package.json +++ b/packages/chess-server/package.json @@ -8,10 +8,7 @@ "create:migration": "migrate create", "migrate": "migrate up", "rollback": "migrate down", - "create-migration": "migrate create", "lint": "tslint -p tsconfig.json --fix", - "start-postgres": "docker compose up postgres", - "build-docker": "docker build -t chess-server .", "clean": "rm -rf db/db-data", "test": "POSTGRES_HOST=127.0.0.1 BITCOIN_RPC_HOST=127.0.0.1 BCN_ZMQ_URL=tcp://127.0.0.1:28332 mocha -r ts-node/register --loader=ts-node/esm --timeout=30000000 test/**/*.test.ts test/*.test.ts", "deploy": "node --loader ts-node/esm ./scripts/deploy.ts", diff --git a/packages/chess-server/scripts/deploy.ts b/packages/chess-server/scripts/deploy.ts index 6464e618a..852121271 100644 --- a/packages/chess-server/scripts/deploy.ts +++ b/packages/chess-server/scripts/deploy.ts @@ -54,7 +54,7 @@ Update the following rows in your .env file. VITE_CHESS_GAME_MOD_SPEC\x1b[2m=${mod}\x1b[0m `) } else { - const files = ['../chess-app/.env', '../chess-server/.env', '../chess-contracts/.env'] + const files = ['../chess-app/.env', '../chess-server/.env'] for (const file of files) { // Update module specifiers in the .env file diff --git a/packages/components/built/Loader.js b/packages/components/built/Loader.js index a7d7be82e..5aef60d10 100644 --- a/packages/components/built/Loader.js +++ b/packages/components/built/Loader.js @@ -1,4 +1,4 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; export function Loader() { - return (_jsx("div", { className: "grid place-items-center h-screen w-full top-0 left-0 fixed", children: _jsxs("svg", { "aria-hidden": "true", className: "mr-2 w-12 h-12 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600", viewBox: "0 0 100 101", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("path", { d: "M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z", fill: "currentColor" }), _jsx("path", { d: "M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z", fill: "currentFill" })] }) })); + return (_jsx("div", { className: "grid place-items-center h-screen w-full top-0 left-0 fixed z-50", children: _jsxs("svg", { "aria-hidden": "true", className: "mr-2 w-12 h-12 text-gray-200 animate-spin dark:text-gray-600 fill-blue-600", viewBox: "0 0 100 101", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [_jsx("path", { d: "M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z", fill: "currentColor" }), _jsx("path", { d: "M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z", fill: "currentFill" })] }) })); } diff --git a/packages/components/built/Modal.d.ts b/packages/components/built/Modal.d.ts index dcad1ac4a..af859b8b8 100644 --- a/packages/components/built/Modal.d.ts +++ b/packages/components/built/Modal.d.ts @@ -2,10 +2,16 @@ import { Modal as ModalClass } from 'flowbite'; export declare const Modal: { get: (id: string) => ModalClass; showModal: (id: string) => void; - hideModal: (id: string) => void; + hideModal: (id: string, onClickClose?: () => void) => void; toggleModal: (id: string) => void; ShowButton: ({ id, text }: any) => import("react/jsx-runtime").JSX.Element; HideButton: ({ id, text }: any) => import("react/jsx-runtime").JSX.Element; ToggleButton: ({ id, text }: any) => import("react/jsx-runtime").JSX.Element; - Component: ({ title, content, contentData, id }: any) => import("react/jsx-runtime").JSX.Element; + Component: ({ title, content, contentData, id, onClickClose, }: { + title: string; + content: any; + id: string; + contentData?: any; + onClickClose?: () => void; + }) => import("react/jsx-runtime").JSX.Element; }; diff --git a/packages/components/built/Modal.js b/packages/components/built/Modal.js index 1a97bdce7..1936f3b4f 100644 --- a/packages/components/built/Modal.js +++ b/packages/components/built/Modal.js @@ -9,8 +9,11 @@ var get = function (id) { var showModal = function (id) { get(id).show(); }; -var hideModal = function (id) { +var hideModal = function (id, onClickClose) { get(id).hide(); + if (onClickClose) { + onClickClose(); + } }; var toggleModal = function (id) { get(id).toggle(); @@ -28,8 +31,8 @@ var ToggleButton = function (_a) { return (_jsx("button", { "data-modal-target": id, "data-modal-toggle": id, type: "button", children: text })); }; var Component = function (_a) { - var title = _a.title, content = _a.content, contentData = _a.contentData, id = _a.id; - return (_jsx("div", { id: id, tabIndex: -1, "aria-hidden": "true", className: "hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full", children: _jsx("div", { className: "relative p-4 w-full max-w-sm max-h-full", children: _jsxs("div", { className: "relative bg-white rounded-lg shadow dark:bg-gray-700", children: [_jsxs("div", { className: "flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600", children: [_jsx("h3", { className: "text-xl font-semibold text-gray-900 dark:text-white", children: title }), _jsxs("button", { type: "button", className: "text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white", "data-modal-hide": id, "data-modal-target": id, onClick: function () { return hideModal(id); }, children: [_jsx("svg", { className: "w-3 h-3", "aria-hidden": "true", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 14 14", children: _jsx("path", { stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", d: "m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" }) }), _jsx("span", { className: "sr-only", children: "Close modal" })] })] }), content(contentData)] }) }) })); + var title = _a.title, content = _a.content, contentData = _a.contentData, id = _a.id, onClickClose = _a.onClickClose; + return (_jsx("div", { id: id, tabIndex: -1, "aria-hidden": "true", style: { zIndex: 45 }, className: "hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full", children: _jsx("div", { className: "relative p-4 w-full max-w-sm max-h-full", children: _jsxs("div", { className: "relative bg-white rounded-lg shadow dark:bg-gray-700", children: [_jsxs("div", { className: "flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600", children: [_jsx("h3", { className: "text-xl font-semibold text-gray-900 dark:text-white", children: title }), _jsxs("button", { type: "button", className: "text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white", "data-modal-hide": id, "data-modal-target": id, onClick: function () { return hideModal(id, onClickClose); }, children: [_jsx("svg", { className: "w-3 h-3", "aria-hidden": "true", xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 14 14", children: _jsx("path", { stroke: "currentColor", strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: "2", d: "m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" }) }), _jsx("span", { className: "sr-only", children: "Close modal" })] })] }), content(contentData)] }) }) })); }; export var Modal = { get: get, diff --git a/packages/components/built/Wallet.js b/packages/components/built/Wallet.js index 9c5ed81bd..47e3c1556 100644 --- a/packages/components/built/Wallet.js +++ b/packages/components/built/Wallet.js @@ -36,7 +36,7 @@ var __generator = (this && this.__generator) || function (thisArg, body) { }; import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime"; import { useCallback, useContext, useEffect, useState } from 'react'; -import { HiRefresh } from 'react-icons/hi'; +import { HiRefresh, HiOutlineClipboardCopy } from 'react-icons/hi'; import { Auth } from './Auth'; import { Drawer } from './Drawer'; import { UtilsContext } from './UtilsContext'; @@ -120,11 +120,27 @@ var Balance = function (_a) { }; var Address = function (_a) { var computer = _a.computer; - return (_jsxs("div", { className: "mb-4", children: [_jsx("h6", { className: "text-lg font-bold dark:text-white", children: "Address" }), _jsx("p", { className: "mb-4 font-mono text-xs text-gray-500 dark:text-gray-400", children: computer.getAddress() })] })); + var _b = useState(false), copied = _b[0], setCopied = _b[1]; + var handleCopy = function () { + navigator.clipboard.writeText(computer.getAddress()); + setCopied(true); + setTimeout(function () { return setCopied(false); }, 2000); // Reset icon color after 2 seconds + }; + return (_jsxs("div", { className: "mb-4", children: [_jsxs("div", { className: "flex items-center", children: [_jsx("h6", { className: "text-lg font-bold dark:text-white", children: "Address" }), _jsx("button", { onClick: handleCopy, className: "ml-2 p-1 ".concat(copied + ? 'text-green-500 dark:text-green-400' + : 'text-gray-500 dark:text-gray-400 hover:text-gray-800 dark:hover:text-white'), "aria-label": "Copy address", children: _jsx(HiOutlineClipboardCopy, { className: "w-5 h-5" }) })] }), _jsx("p", { className: "mb-4 font-mono text-xs text-gray-500 dark:text-gray-400", children: computer.getAddress() })] })); }; var PublicKey = function (_a) { var computer = _a.computer; - return (_jsxs("div", { className: "mb-4", children: [_jsx("h6", { className: "text-lg font-bold dark:text-white", children: "Public Key" }), _jsx("p", { className: "mb-4 text-xs font-mono text-gray-500 dark:text-gray-400 break-words", children: computer.getPublicKey() })] })); + var _b = useState(false), copied = _b[0], setCopied = _b[1]; + var handleCopy = function () { + navigator.clipboard.writeText(computer.getPublicKey()); + setCopied(true); + setTimeout(function () { return setCopied(false); }, 2000); // Reset icon color after 2 seconds + }; + return (_jsxs("div", { className: "mb-4", children: [_jsxs("div", { className: "flex items-center", children: [_jsx("h6", { className: "text-lg font-bold dark:text-white", children: "Public Key" }), _jsx("button", { onClick: handleCopy, className: "ml-2 p-1 ".concat(copied + ? 'text-green-500 dark:text-green-400' + : 'text-gray-500 dark:text-gray-400 hover:text-gray-800 dark:hover:text-white'), "aria-label": "Copy public key", children: _jsx(HiOutlineClipboardCopy, { className: "w-5 h-5" }) })] }), _jsx("p", { className: "mb-4 text-xs font-mono text-gray-500 dark:text-gray-400 break-words", children: computer.getPublicKey() })] })); }; var Mnemonic = function (_a) { var computer = _a.computer; diff --git a/packages/components/src/Loader.tsx b/packages/components/src/Loader.tsx index 4063c52f5..7a065558e 100644 --- a/packages/components/src/Loader.tsx +++ b/packages/components/src/Loader.tsx @@ -1,6 +1,6 @@ export function Loader() { return ( -
+