Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use shadcn/ui as the primary component library for the frontend. #104

Merged
merged 36 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from 33 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
c3e68e5
fix: readme
ultraviolet10 Jan 15, 2024
61df2f5
update packages
ultraviolet10 Jan 16, 2024
a409a0a
add new font
ultraviolet10 Jan 16, 2024
5d71404
add new spinner svg for loading state modals
ultraviolet10 Jan 16, 2024
632a45d
shadcn component scripts + util func for classname merges
ultraviolet10 Jan 16, 2024
1260dde
recreate `coffee` theme from daisy UI within global styles
ultraviolet10 Jan 16, 2024
4f17825
redo navbar
ultraviolet10 Jan 16, 2024
8e30831
relocate `loadingModal`, update imports
ultraviolet10 Jan 16, 2024
f85417c
redo common modal elements
ultraviolet10 Jan 16, 2024
a1b5aa4
preload font into `_app` before render
ultraviolet10 Jan 16, 2024
470ce60
update pages with new components
ultraviolet10 Jan 16, 2024
9acbcf9
update `MintDeckModal`
ultraviolet10 Jan 16, 2024
5b66baf
update modals with `Dialog` component
ultraviolet10 Jan 16, 2024
5b657ce
update navbar style
ultraviolet10 Jan 16, 2024
2827a4b
refactor `JoinGameModal`
ultraviolet10 Jan 16, 2024
40bafa2
add default open state for `JoinGameModal`
ultraviolet10 Jan 16, 2024
9a0d96d
refactor `createGameModal`
ultraviolet10 Jan 16, 2024
a73e0d4
Merge branch 'master' into ac/goodbye-daisy
ultraviolet10 Jan 16, 2024
571853d
add missing type
ultraviolet10 Jan 16, 2024
9746d73
remove log statement
ultraviolet10 Jan 18, 2024
0a17342
remove extra parent and parentheses
ultraviolet10 Jan 18, 2024
6294819
add references for new components
ultraviolet10 Jan 18, 2024
e77c5c0
added docs ref
ultraviolet10 Jan 18, 2024
4e54154
use absoute imports for ui components
ultraviolet10 Jan 18, 2024
7bb1ab8
add docstring for util function
ultraviolet10 Jan 18, 2024
554e92b
improve code readability
ultraviolet10 Jan 18, 2024
978f2a1
use absolute imports for components
ultraviolet10 Jan 21, 2024
1d39ac1
define open/close behaviour for `joinGameModal`
ultraviolet10 Jan 21, 2024
8ca7ea3
Merge branch 'master' into goodbye-daisy
ultraviolet10 Jan 21, 2024
97d7a39
define open/close behaviour for `CreateGameModal`
ultraviolet10 Jan 21, 2024
6dd18e0
update event handlers for dialog components
ultraviolet10 Jan 25, 2024
4292863
fix: typo
ultraviolet10 Jan 25, 2024
eb5989b
abstract keyclick handlers, add prop to `dialog` component
ultraviolet10 Jan 27, 2024
1f364bb
remove font preload, use state variable for modal default opens
ultraviolet10 Jan 31, 2024
03c6f1e
add missing style to dialog
ultraviolet10 Jan 31, 2024
40a94c5
add back dialog close button styles
ultraviolet10 Jan 31, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ data".
If you access the app via http://localhost:3000/, you can interact with it with your wallet.
You'll need to "Claim Airdrop" in order to get a deck to play with.

However, for testing, it's expected you'll the Anvil ("test ... junk" mnemonic) accounts. These come
However, for testing, it's expected you'll use the Anvil ("test ... junk" mnemonic) accounts. These come
preloaded with local testnet ETH, and as part of our deploy script, we pre-airdrop a deck to
accounts 0 and 1.

Expand Down
9 changes: 8 additions & 1 deletion packages/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,25 @@
"@dnd-kit/sortable": "^7.0.2",
"@dnd-kit/utilities": "^3.2.1",
"@emotion/react": "^11.11.1",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-slot": "^1.0.2",
"circomlibjs": "^0.1.7",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"connectkit": "^1.5.3",
"jotai": "^2.4.3",
"jotai-devtools": "^0.7.0",
"lodash": "^4.17.21",
"lucide-react": "^0.309.0",
"next": "^13.5.6",
"next-transpile-modules": "^10.0.1",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.11.0",
"snarkjs": "^0.7.1",
"tailwind-merge": "^2.2.0",
"tailwindcss-animate": "^1.0.7",
"viem": "^1.16.6",
"wagmi": "^1.4.5"
},
Expand All @@ -39,7 +47,6 @@
"@wagmi/cli": "^1.5.2",
"@welldone-software/why-did-you-render": "^7.0.1",
"autoprefixer": "^10.4.16",
"daisyui": "^3.8.2",
"eslint": "^8.52.0",
"eslint-config-next": "^13.5.6",
"postcss": "^8.4.31",
Expand Down
Binary file added packages/webapp/public/font/BluuNext-Bold.otf
Binary file not shown.
22 changes: 22 additions & 0 deletions packages/webapp/public/img/spinner.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion packages/webapp/src/actions/drawCard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ import { FAKE_PROOF, proveInWorker, SHOULD_GENERATE_PROOFS } from "src/utils/zkp
import { bigintToHexString } from "src/utils/js-utils"
import { mimcHash } from "src/utils/hashing"
import { DRAW_CARD_PROOF_TIMEOUT } from "src/constants"
import { CancellationHandler } from "src/components/lib/loadingModal"
import { CancellationHandler } from "src/components/modals/loadingModal"
import { checkFresh, freshWrap } from "src/store/checkFresh"

// =================================================================================================
Expand Down
2 changes: 1 addition & 1 deletion packages/webapp/src/actions/joinGame.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import {
import { NUM_CARDS_FOR_PROOF } from "src/game/constants"
import { packCards } from "src/game/fableProofs"
import { DRAW_HAND_PROOF_TIMEOUT } from "src/constants"
import { CancellationHandler } from "src/components/lib/loadingModal"
import { CancellationHandler } from "src/components/modals/loadingModal"
import { checkFresh, freshWrap } from "src/store/checkFresh"
import { getPlayerHand } from "src/store/derive"

Expand Down
2 changes: 1 addition & 1 deletion packages/webapp/src/actions/playCard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { FAKE_PROOF, proveInWorker, SHOULD_GENERATE_PROOFS } from "src/utils/zkp
import { bigintToHexString } from "src/utils/js-utils"
import { mimcHash } from "src/utils/hashing"
import { PLAY_CARD_PROOF_TIMEOUT } from "src/constants"
import { CancellationHandler } from "src/components/lib/loadingModal"
import { CancellationHandler } from "src/components/modals/loadingModal"
import { checkFresh, freshWrap } from "src/store/checkFresh"

// =================================================================================================
Expand Down
2 changes: 1 addition & 1 deletion packages/webapp/src/components/hand.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
import { CardPlacement } from "src/store/types"
import CardContainer from "./cards/cardContainer"
import { convertBigIntArrayToStringArray } from "src/utils/js-utils"
import { CancellationHandler } from "src/components/lib/loadingModal"
import { CancellationHandler } from "src/components/modals/loadingModal"

const Hand = ({
cards,
Expand Down
43 changes: 25 additions & 18 deletions packages/webapp/src/components/lib/modalElements.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,35 @@
// =================================================================================================


import { PropsWithChildren } from "react"

export const ModalTitle = ({ children }: PropsWithChildren) => {
return <h3 className="text-xl font-bold normal-case">{children}</h3>
}

// -------------------------------------------------------------------------------------------------
import Image from "next/image"
import { Button } from "src/components/ui/button"

export const Spinner = () => {
return <div className="flex justify-center my-8">
<span className="loading loading-spinner loading-lg text-primary"></span>
</div>
return (
<div className="flex justify-center my-8">
<Image
height={80}
width={80}
src={"/img/spinner.svg"}
alt="loading"
/>
</div>
)
}

// -------------------------------------------------------------------------------------------------

export const ModalMenuButton = ({ display, label }: { display: () => void, label: string}) => {
return <button
onClick={display}
className="hover:border-3 btn-lg btn btn-neutral border-2 border-green-900 text-2xl normal-case hover:scale-105 hover:border-green-800">
{label}
</button>
export const ModalMenuButton = ({
display,
label,
}: {
display: () => void
label: string
}) => {
return (
<Button variant="outline" onClick={display} className="rounded-lg p-6 font-fable text-2xl border-green-900 border-2 h-16 hover:scale-105 hover:border-green-800 hover:border-3">
{label}
</Button>
)
}

// =================================================================================================
// =================================================================================================
157 changes: 89 additions & 68 deletions packages/webapp/src/components/modals/createGameModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { useRouter } from "next/router"
import { useCallback, useEffect, useState } from "react"
import { decodeEventLog } from "viem"

import { LoadingModalContent } from "src/components/lib/loadingModal"
import { Modal, ModalController, useModalController } from "src/components/lib/modal"
import { ModalMenuButton, ModalTitle, Spinner } from "src/components/lib/modalElements"
import { LoadingModalContent } from "src/components/modals/loadingModal"
import { Spinner } from "src/components/lib/modalElements"
import { InGameMenuModalContent } from "src/components/modals/inGameMenuModalContent"
import { gameABI } from "src/generated"
import { useGameWrite } from "src/hooks/useFableWrite"
Expand All @@ -14,39 +13,51 @@ import { GameStatus } from "src/store/types"
import { navigate } from "src/utils/navigate"
import { useCancellationHandler } from "src/hooks/useCancellationHandler"
import { concede } from "src/actions/concede"
import {
Dialog,
DialogContent,
DialogDescription,
DialogTitle,
DialogTrigger,
} from "src/components/ui/dialog"
import { Button } from "src/components/ui/button"

interface CreateGameModalContentProps {
loading: string|null;
setLoading: React.Dispatch<React.SetStateAction<string|null>>;
gameStatus: GameStatus
}

// =================================================================================================

export const CreateGameModal = () => {
const [gameID] = store.useGameID()

const [ loading, setLoading ] = useState<string|null>(null);
const isGameCreator = store.useIsGameCreator()
const ctrl = useModalController({ loaded: isGameCreator })
const gameStatus = store.useGameStatus()

useEffect(() => {
const canCloseExternally = loading == null && gameStatus < GameStatus.CREATED
return (
// If we're on the home page and we're the game creator, this modal should be displayed.
if (isGameCreator && !ctrl.displayed)
ctrl.display()
}, [isGameCreator, ctrl, ctrl.displayed, gameID])

return <>
<ModalMenuButton display={ctrl.display} label="Create Game →" />
<Modal ctrl={ctrl}>
<CreateGameModalContent ctrl={ctrl} />
</Modal>
</>
<Dialog defaultOpen={isGameCreator}>
<DialogTrigger asChild>
<Button variant="outline" className="rounded-lg p-6 font-fable text-2xl border-green-900 border-2 h-16 hover:scale-105 hover:border-green-800 hover:border-3">
Create Game →
</Button>
</DialogTrigger>
<DialogContent canCloseExternally={canCloseExternally}>
<CreateGameModalContent loading={loading} setLoading={setLoading} gameStatus={gameStatus}/>
</DialogContent>
</Dialog>
)
}

// =================================================================================================

const CreateGameModalContent = ({ ctrl }: { ctrl: ModalController }) => {

const CreateGameModalContent: React.FC<CreateGameModalContentProps> = ({ loading, setLoading, gameStatus }) => {
const playerAddress = store.usePlayerAddress()
const [ gameID, setGameID ] = store.useGameID()
const gameStatus = store.useGameStatus()
const allPlayersJoined = store.useAllPlayersJoined()
const [ hasVisitedBoard ] = store.useHasVisitedBoard()
const [ loading, setLoading ] = useState<string|null>(null)
const [ drawCompleted, setDrawCompleted ] = useState(false)
const router = useRouter()

Expand All @@ -55,16 +66,9 @@ const CreateGameModalContent = ({ ctrl }: { ctrl: ModalController }) => {
const joined = gameStatus >= GameStatus.HAND_DRAWN || drawCompleted
const started = gameStatus >= GameStatus.STARTED && gameStatus < GameStatus.ENDED

// If the game is created, the modal can't be closed in the normal way, same if loading.
useEffect(() => {
// React forces us to use an effect, can't update a component state in another component.
ctrl.closeableAndSurroundCloseable = !created && loading === null
}, [created, loading, ctrl])

// Load game board game once the game start, unless we've visited it for this game already.
useEffect(() => {
if (!hasVisitedBoard && started)
void navigate(router, "/play")
if (!hasVisitedBoard && started) void navigate(router, "/play")
}, [hasVisitedBoard, router, started])

// -----------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -121,7 +125,6 @@ const CreateGameModalContent = ({ ctrl }: { ctrl: ModalController }) => {
setLoading,
onSuccess() {
setGameID(null)
ctrl.close()
}
})

Expand All @@ -132,10 +135,9 @@ const CreateGameModalContent = ({ ctrl }: { ctrl: ModalController }) => {
setLoading,
onSuccess: () => {
setGameID(null)
ctrl.close()
}
}),
[gameID, playerAddress, setGameID, ctrl])
[gameID, playerAddress, setGameID, setLoading])

// -----------------------------------------------------------------------------------------------

Expand All @@ -146,43 +148,62 @@ const CreateGameModalContent = ({ ctrl }: { ctrl: ModalController }) => {
cancellationHandler={cancellationHandler}
/>

if (!created) return <>
<ModalTitle>Create Game</ModalTitle>
<p className="py-4">
Once a game is created, you can invite your friends to join with the game ID.
</p>
<div className="flex justify-center">
<button className="btn center" disabled={!create} onClick={create}>
Create Game
</button>
</div>
</>

if (created && !started) return <>
<ModalTitle>{joined ? "Waiting for other player..." : "Game Created"}</ModalTitle>
<p className="py-4 font-mono">
Share the following code to invite players to battle:
</p>
<p className="mb-5 rounded-xl border border-white/50 bg-black py-4 text-center font-mono">
{`${gameID}`}
</p>
{!joined && <div className="flex justify-center gap-4">
<button className="btn" onClick={join}>
Join Game
</button>
<button className="btn" disabled={!cancel} onClick={cancel}>
Cancel Game
</button>
</div>}
{joined && <div className="flex flex-col justify-center gap-4 items-center">
<Spinner />
{!allPlayersJoined && <button className="btn" disabled={!cancel} onClick={cancel}>
Cancel Game
</button>}
</div>}
</>
if (!created)
return (
<>
<DialogTitle className="font-fable text-xl">Create Game</DialogTitle>
<DialogDescription>
<p className="py-4 font-mono">
Once a game is created, you can invite your friends to join with the
game ID.
</p>
<div className="flex justify-center">
<Button className="font-fable" variant={"secondary"} disabled={!create} onClick={create}>
Create Game
</Button>
</div>
</DialogDescription>
</>
)

if (created && !started)
return (
<>
<DialogTitle className="font-fable text-xl">
{joined ? "Waiting for other player..." : "Game Created"}
</DialogTitle>
<DialogDescription>
<p className="py-4 font-mono">
Share the following code to invite players to battle:
</p>
<p className="mb-5 rounded-xl border border-white/50 bg-black py-4 text-center font-mono">
{`${gameID}`}
</p>
{!joined && (
<div className="flex justify-center gap-4">
<Button className="font-fable" variant={"secondary"} onClick={join}>
Join Game
</Button>
<Button className="font-fable" variant={"secondary"} disabled={!cancel} onClick={cancel}>
Cancel Game
</Button>
</div>
)}
{joined && (
<div className="flex flex-col justify-center gap-4 items-center">
<Spinner />
{!allPlayersJoined && (
<Button className="font-fable" variant={"secondary"} disabled={!cancel} onClick={cancel}>
Cancel Game
</Button>
)}
</div>
)}
</DialogDescription>
</>
)

if (started) return <InGameMenuModalContent concede={doConcede} />
}

// =================================================================================================
// =================================================================================================
Loading