├── web ├── public │ ├── .gitkeep │ ├── logo.png │ ├── hero.webp │ ├── favicon.ico │ ├── game_resources │ │ ├── cards │ │ │ ├── 2c.png │ │ │ ├── 2d.png │ │ │ ├── 2h.png │ │ │ ├── 2s.png │ │ │ ├── 3c.png │ │ │ ├── 3d.png │ │ │ ├── 3h.png │ │ │ ├── 3s.png │ │ │ ├── 4c.png │ │ │ ├── 4d.png │ │ │ ├── 4h.png │ │ │ ├── 4s.png │ │ │ ├── 5c.png │ │ │ ├── 5d.png │ │ │ ├── 5h.png │ │ │ ├── 5s.png │ │ │ ├── 6c.png │ │ │ ├── 6d.png │ │ │ ├── 6h.png │ │ │ ├── 6s.png │ │ │ ├── 7c.png │ │ │ ├── 7d.png │ │ │ ├── 7h.png │ │ │ ├── 7s.png │ │ │ ├── 8c.png │ │ │ ├── 8d.png │ │ │ ├── 8h.png │ │ │ ├── 8s.png │ │ │ ├── 9c.png │ │ │ ├── 9d.png │ │ │ ├── 9h.png │ │ │ ├── 9s.png │ │ │ ├── Ac.png │ │ │ ├── Ad.png │ │ │ ├── Ah.png │ │ │ ├── As.png │ │ │ ├── Jc.png │ │ │ ├── Jd.png │ │ │ ├── Jh.png │ │ │ ├── Js.png │ │ │ ├── Kc.png │ │ │ ├── Kd.png │ │ │ ├── Kh.png │ │ │ ├── Ks.png │ │ │ ├── Qc.png │ │ │ ├── Qd.png │ │ │ ├── Qh.png │ │ │ ├── Qs.png │ │ │ ├── Tc.png │ │ │ ├── Td.png │ │ │ ├── Th.png │ │ │ ├── Ts.png │ │ │ └── card_back.png │ │ └── table_1.png │ └── hero.svg ├── app │ ├── page.module.css │ ├── api │ │ └── hello │ │ │ └── route.ts │ ├── page.tsx │ ├── account │ │ ├── page.tsx │ │ └── [address] │ │ │ └── page.tsx │ ├── pocketbase │ │ └── pocketbase.tsx │ ├── game │ │ └── page.tsx │ ├── leaderboard │ │ └── page.tsx │ ├── global.css │ ├── react-query-provider.tsx │ └── layout.tsx ├── components │ ├── blockchain │ │ ├── src │ │ │ ├── index.ts │ │ │ └── leaderboard-exports.ts │ │ └── target │ │ │ ├── idl │ │ │ └── poksol_leaderboard.json │ │ │ └── types │ │ │ └── poksol_leaderboard.ts │ ├── game-components │ │ ├── PokerGameQuizContext.js │ │ ├── TableCard.js │ │ ├── gameData.js │ │ ├── Table.js │ │ ├── Player.js │ │ ├── Timer │ │ │ └── Timer.js │ │ ├── UsernameModal.js │ │ ├── UsernameModal.module.css │ │ ├── Option.js │ │ ├── Result.js │ │ ├── PokerGameQuiz.module.css │ │ └── PokerGameQuiz.js │ ├── account │ │ ├── account-list-feature.tsx │ │ ├── account-detail-feature.tsx │ │ ├── account-data-access.tsx │ │ └── account-ui.tsx │ ├── leaderboard │ │ ├── LeaderboardMember.js │ │ ├── Leaderboard.module.css │ │ └── Leaderboard.js │ ├── cluster │ │ ├── cluster-feature.tsx │ │ ├── cluster-data-access.tsx │ │ └── cluster-ui.tsx │ ├── solana │ │ └── solana-provider.tsx │ ├── solana-poker-quiz │ │ ├── solana-poker-quiz-feature.tsx │ │ ├── solana-poker-quiz-data-access.tsx │ │ └── solana-poker-quiz-ui.tsx │ ├── dashboard │ │ └── dashboard-feature.tsx │ └── ui │ │ └── ui-layout.tsx ├── index.d.ts ├── next-env.d.ts ├── tailwind.config.js ├── postcss.config.js ├── next.config.js ├── tsconfig.json ├── .eslintrc.json └── project.json ├── .eslintignore ├── .prettierrc ├── image.png ├── img1.png ├── img2.png ├── img3.png ├── vercel.json ├── anchor ├── .gitignore ├── .prettierignore ├── programs │ └── poksol-leaderboard │ │ ├── Xargo.toml │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── src │ ├── index.ts │ └── leaderboard-exports.ts ├── Cargo.toml ├── tsconfig.json ├── Anchor.toml ├── migrations │ └── deploy.ts ├── package.json └── tests │ └── poksol-leaderboard.ts ├── jest.preset.js ├── jest.config.ts ├── .vscode └── extensions.json ├── .editorconfig ├── .prettierignore ├── tsconfig.base.json ├── .eslintrc.json ├── .gitignore ├── LICENSE ├── nx.json ├── package.json └── README.md /web/public/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /web/app/page.module.css: -------------------------------------------------------------------------------- 1 | .page { 2 | } 3 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/image.png -------------------------------------------------------------------------------- /img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/img1.png -------------------------------------------------------------------------------- /img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/img2.png -------------------------------------------------------------------------------- /img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/img3.png -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "buildCommand": "npm run build", 3 | "outputDirectory": "dist/web/.next" 4 | } 5 | -------------------------------------------------------------------------------- /web/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/logo.png -------------------------------------------------------------------------------- /anchor/.gitignore: -------------------------------------------------------------------------------- 1 | .anchor 2 | .DS_Store 3 | target 4 | **/*.rs.bk 5 | node_modules 6 | test-ledger 7 | .yarn 8 | -------------------------------------------------------------------------------- /anchor/.prettierignore: -------------------------------------------------------------------------------- 1 | .anchor 2 | .DS_Store 3 | target 4 | node_modules 5 | dist 6 | build 7 | test-ledger 8 | -------------------------------------------------------------------------------- /web/public/hero.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/hero.webp -------------------------------------------------------------------------------- /anchor/programs/poksol-leaderboard/Xargo.toml: -------------------------------------------------------------------------------- 1 | [target.bpfel-unknown-unknown.dependencies.std] 2 | features = [] 3 | -------------------------------------------------------------------------------- /jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nx/jest/preset').default; 2 | 3 | module.exports = { ...nxPreset }; 4 | -------------------------------------------------------------------------------- /web/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/favicon.ico -------------------------------------------------------------------------------- /web/app/api/hello/route.ts: -------------------------------------------------------------------------------- 1 | export async function GET(request: Request) { 2 | return new Response('Hello, from API!'); 3 | } 4 | -------------------------------------------------------------------------------- /web/public/game_resources/cards/2c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/2c.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/2d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/2d.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/2h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/2h.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/2s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/2s.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/3c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/3c.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/3d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/3d.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/3h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/3h.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/3s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/3s.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/4c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/4c.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/4d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/4d.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/4h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/4h.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/4s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/4s.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/5c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/5c.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/5d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/5d.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/5h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/5h.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/5s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/5s.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/6c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/6c.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/6d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/6d.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/6h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/6h.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/6s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/6s.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/7c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/7c.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/7d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/7d.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/7h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/7h.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/7s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/7s.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/8c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/8c.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/8d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/8d.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/8h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/8h.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/8s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/8s.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/9c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/9c.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/9d.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/9d.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/9h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/9h.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/9s.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/9s.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Ac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Ac.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Ad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Ad.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Ah.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Ah.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/As.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/As.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Jc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Jc.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Jd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Jd.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Jh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Jh.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Js.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Kc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Kc.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Kd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Kd.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Kh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Kh.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Ks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Ks.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Qc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Qc.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Qd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Qd.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Qh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Qh.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Qs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Qs.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Tc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Tc.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Td.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Td.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Th.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Th.png -------------------------------------------------------------------------------- /web/public/game_resources/cards/Ts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/Ts.png -------------------------------------------------------------------------------- /web/public/game_resources/table_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/table_1.png -------------------------------------------------------------------------------- /anchor/src/index.ts: -------------------------------------------------------------------------------- 1 | // This file was generated by preset-anchor. Programs are exported from this file. 2 | 3 | export * from './leaderboard-exports'; 4 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import { getJestProjectsAsync } from '@nx/jest'; 2 | 3 | export default async () => ({ 4 | projects: await getJestProjectsAsync(), 5 | }); 6 | -------------------------------------------------------------------------------- /web/public/game_resources/cards/card_back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/0xRustPro/solana-poker-casino-game/HEAD/web/public/game_resources/cards/card_back.png -------------------------------------------------------------------------------- /web/components/blockchain/src/index.ts: -------------------------------------------------------------------------------- 1 | // This file was generated by preset-anchor. Programs are exported from this file. 2 | 3 | export * from './leaderboard-exports'; 4 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "esbenp.prettier-vscode", 5 | "firsttris.vscode-jest-runner" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /web/app/page.tsx: -------------------------------------------------------------------------------- 1 | import DashboardFeature from '@/components/dashboard/dashboard-feature'; 2 | 3 | export default function Page() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /web/app/account/page.tsx: -------------------------------------------------------------------------------- 1 | import AccountListFeature from '@/components/account/account-list-feature'; 2 | 3 | export default function Page() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /web/app/pocketbase/pocketbase.tsx: -------------------------------------------------------------------------------- 1 | import PocketBase from 'pocketbase'; 2 | 3 | const url = 'https://poksol.pockethost.io/' 4 | const client = new PocketBase(url) 5 | 6 | export default client -------------------------------------------------------------------------------- /web/components/game-components/PokerGameQuizContext.js: -------------------------------------------------------------------------------- 1 | import { createContext } from 'react'; 2 | 3 | const PokerGameQuizContext = createContext(); 4 | 5 | export default PokerGameQuizContext; -------------------------------------------------------------------------------- /web/app/game/page.tsx: -------------------------------------------------------------------------------- 1 | import PokerGameQuiz from "@/components/game-components/PokerGameQuiz"; 2 | 3 | export default function Page() { 4 | return ( 5 | 6 | ) 7 | } -------------------------------------------------------------------------------- /web/app/leaderboard/page.tsx: -------------------------------------------------------------------------------- 1 | import Leaderboard from "@/components/leaderboard/Leaderboard"; 2 | 3 | export default function Page() { 4 | return ( 5 | 6 | ) 7 | } -------------------------------------------------------------------------------- /web/app/account/[address]/page.tsx: -------------------------------------------------------------------------------- 1 | import AccountDetailFeature from '@/components/account/account-detail-feature'; 2 | 3 | export default function Page() { 4 | return ; 5 | } 6 | -------------------------------------------------------------------------------- /web/index.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | declare module '*.svg' { 3 | const content: any; 4 | export const ReactComponent: any; 5 | export default content; 6 | } 7 | -------------------------------------------------------------------------------- /web/next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /anchor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "programs/*" 4 | ] 5 | resolver = "2" 6 | 7 | [profile.release] 8 | overflow-checks = true 9 | lto = "fat" 10 | codegen-units = 1 11 | [profile.release.build-override] 12 | opt-level = 3 13 | incremental = false 14 | codegen-units = 1 15 | -------------------------------------------------------------------------------- /anchor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["mocha", "chai"], 4 | "typeRoots": ["./node_modules/@types"], 5 | "lib": ["es2015"], 6 | "module": "commonjs", 7 | "target": "es6", 8 | "esModuleInterop": true, 9 | "resolveJsonModule": true 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | /dist 3 | /coverage 4 | /.nx/cache 5 | /.nx/workspace-data 6 | .anchor 7 | anchor/target/deploy 8 | anchor/target/debug 9 | anchor/target/release 10 | anchor/target/sbf-solana-solana 11 | anchor/target/.rustc_info.json 12 | !anchor/target/idl/*.json 13 | !anchor/target/types/*.ts 14 | node_modules 15 | dist 16 | tmp 17 | build 18 | test-ledger -------------------------------------------------------------------------------- /anchor/Anchor.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | 3 | [features] 4 | resolution = true 5 | skip-lint = false 6 | 7 | [programs.localnet] 8 | poksol_leaderboard = "2rT5faxcrTnWXE125tWzzje9PFHt3k4C3VUjhNfQuzvY" 9 | 10 | [registry] 11 | url = "https://api.apr.dev" 12 | 13 | [provider] 14 | cluster = "Localnet" 15 | wallet = "~/.config/solana/id.json" 16 | 17 | [scripts] 18 | test = "yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts" 19 | -------------------------------------------------------------------------------- /anchor/migrations/deploy.ts: -------------------------------------------------------------------------------- 1 | // Migrations are an early feature. Currently, they're nothing more than this 2 | // single deploy script that's invoked from the CLI, injecting a provider 3 | // configured from the workspace's Anchor.toml. 4 | 5 | const anchor = require("@coral-xyz/anchor"); 6 | 7 | module.exports = async function (provider) { 8 | // Configure client to use the provider. 9 | anchor.setProvider(provider); 10 | 11 | // Add your deploy script here. 12 | }; 13 | -------------------------------------------------------------------------------- /web/app/global.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | body { 6 | height: 100%; 7 | overflow-y: auto; 8 | } 9 | 10 | .wallet-adapter-button-trigger { 11 | background: rgb(100, 26, 230) !important; 12 | border-radius: 8px !important; 13 | padding-left: 16px !important; 14 | padding-right: 16px !important; 15 | } 16 | .wallet-adapter-dropdown-list, 17 | .wallet-adapter-button { 18 | font-family: inherit !important; 19 | } 20 | -------------------------------------------------------------------------------- /web/components/game-components/TableCard.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import Style from "./PokerGameQuiz.module.css"; 4 | 5 | const TableCard = ({ imgSrc }) => { 6 | return ( 7 | poker table image e.target.src = './game_resources/cards/card_back.png'} 11 | /> 12 | ); 13 | } 14 | 15 | export default TableCard; -------------------------------------------------------------------------------- /web/components/game-components/gameData.js: -------------------------------------------------------------------------------- 1 | const deck = [ 2 | // spades 3 | "As", "2s", "3s", "4s", "5s", "6s", "7s", "8s", "9s", "Ts", "Js", "Qs", "Ks", 4 | // hearts 5 | "Ah", "2h", "3h", "4h", "5h", "6h", "7h", "8h", "9h", "Th", "Jh", "Qh", "Kh", 6 | // clubs 7 | "Ac", "2c", "3c", "4c", "5c", "6c", "7c", "8c", "9c", "Tc", "Jc", "Qc", "Kc", 8 | // diamonds 9 | "Ad", "2d", "3d", "4d", "5d", "6d", "7d", "8d", "9d", "Td", "Jd", "Qd", "Kd" 10 | ]; 11 | 12 | export { deck }; -------------------------------------------------------------------------------- /web/tailwind.config.js: -------------------------------------------------------------------------------- 1 | const { createGlobPatternsForDependencies } = require('@nx/react/tailwind'); 2 | const { join } = require('path'); 3 | 4 | /** @type {import('tailwindcss').Config} */ 5 | module.exports = { 6 | content: [ 7 | join( 8 | __dirname, 9 | '{src,pages,components,app}/**/*!(*.stories|*.spec).{ts,tsx,html}' 10 | ), 11 | ...createGlobPatternsForDependencies(__dirname), 12 | ], 13 | theme: { 14 | extend: {}, 15 | }, 16 | plugins: [require('daisyui')], 17 | }; 18 | -------------------------------------------------------------------------------- /anchor/programs/poksol-leaderboard/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "poksol-leaderboard" 3 | version = "0.1.0" 4 | description = "Created with Anchor" 5 | edition = "2021" 6 | 7 | [lib] 8 | crate-type = ["cdylib", "lib"] 9 | name = "poksol_leaderboard" 10 | 11 | [features] 12 | default = [] 13 | cpi = ["no-entrypoint"] 14 | no-entrypoint = [] 15 | no-idl = [] 16 | no-log-ix-name = [] 17 | idl-build = ["anchor-lang/idl-build"] 18 | 19 | [dependencies] 20 | anchor-lang = { version = "0.30.1", features = ["init-if-needed"] } 21 | 22 | -------------------------------------------------------------------------------- /web/postcss.config.js: -------------------------------------------------------------------------------- 1 | const { join } = require('path'); 2 | 3 | // Note: If you use library-specific PostCSS/Tailwind configuration then you should remove the `postcssConfig` build 4 | // option from your application's configuration (i.e. project.json). 5 | // 6 | // See: https://nx.dev/guides/using-tailwind-css-in-react#step-4:-applying-configuration-to-libraries 7 | 8 | module.exports = { 9 | plugins: { 10 | tailwindcss: { 11 | config: join(__dirname, 'tailwind.config.js'), 12 | }, 13 | autoprefixer: {}, 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /anchor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "license": "ISC", 3 | "scripts": { 4 | "lint:fix": "prettier */*.js \"*/**/*{.js,.ts}\" -w", 5 | "lint": "prettier */*.js \"*/**/*{.js,.ts}\" --check" 6 | }, 7 | "dependencies": { 8 | "@coral-xyz/anchor": "^0.30.1" 9 | }, 10 | "devDependencies": { 11 | "chai": "^4.3.4", 12 | "mocha": "^9.0.3", 13 | "ts-mocha": "^10.0.0", 14 | "@types/bn.js": "^5.1.0", 15 | "@types/chai": "^4.3.0", 16 | "@types/mocha": "^9.0.0", 17 | "typescript": "^4.3.5", 18 | "prettier": "^2.6.2" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /web/app/react-query-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import React, { ReactNode, useState } from 'react'; 4 | import { ReactQueryStreamedHydration } from '@tanstack/react-query-next-experimental'; 5 | import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; 6 | 7 | export function ReactQueryProvider({ children }: { children: ReactNode }) { 8 | const [client] = useState(new QueryClient()); 9 | 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /web/components/account/account-list-feature.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useWallet } from '@solana/wallet-adapter-react'; 4 | import { WalletButton } from '../solana/solana-provider'; 5 | 6 | import { redirect } from 'next/navigation'; 7 | 8 | export default function AccountListFeature() { 9 | const { publicKey } = useWallet(); 10 | 11 | if (publicKey) { 12 | return redirect(`/account/${publicKey.toString()}`); 13 | } 14 | 15 | return ( 16 |
17 |
18 | 19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "lib": ["es2020", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "@/*": ["./web/*"], 19 | "@solana-poker-quiz/anchor": ["anchor/src/index.ts"] 20 | } 21 | }, 22 | "exclude": ["node_modules", "tmp"] 23 | } 24 | -------------------------------------------------------------------------------- /web/components/leaderboard/LeaderboardMember.js: -------------------------------------------------------------------------------- 1 | // style 2 | import Style from './Leaderboard.module.css'; 3 | 4 | const LeaderboardMember = ({username, score, position, zeroOrOne}) => { 5 | return ( 6 |
7 |
8 |
{position}.
9 |
{username}
10 |
11 |
{score}
12 |
13 | ); 14 | } 15 | 16 | export default LeaderboardMember; -------------------------------------------------------------------------------- /web/next.config.js: -------------------------------------------------------------------------------- 1 | //@ts-check 2 | 3 | // eslint-disable-next-line @typescript-eslint/no-var-requires 4 | const { composePlugins, withNx } = require('@nx/next'); 5 | 6 | /** 7 | * @type {import('@nx/next/plugins/with-nx').WithNxOptions} 8 | **/ 9 | const nextConfig = { 10 | webpack: (config) => { 11 | config.externals = [ 12 | ...(config.externals || []), 13 | 'bigint', 14 | 'node-gyp-build', 15 | ]; 16 | return config; 17 | }, 18 | nx: { 19 | // Set this to true if you would like to use SVGR 20 | // See: https://github.com/gregberge/svgr 21 | svgr: false, 22 | }, 23 | }; 24 | 25 | const plugins = [ 26 | // Add more Next.js plugins to this list if needed. 27 | withNx, 28 | ]; 29 | 30 | module.exports = composePlugins(...plugins)(nextConfig); 31 | -------------------------------------------------------------------------------- /web/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "jsx": "preserve", 5 | "allowJs": true, 6 | "esModuleInterop": true, 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true, 9 | "forceConsistentCasingInFileNames": true, 10 | "noEmit": true, 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "incremental": true, 14 | "plugins": [ 15 | { 16 | "name": "next" 17 | } 18 | ] 19 | }, 20 | "include": [ 21 | "**/*.ts", 22 | "**/*.tsx", 23 | "**/*.js", 24 | "**/*.jsx", 25 | "../web/.next/types/**/*.ts", 26 | "../dist/web/.next/types/**/*.ts", 27 | "next-env.d.ts", 28 | ".next/types/**/*.ts" 29 | ], 30 | "exclude": ["node_modules", "jest.config.ts", "**/*.spec.ts", "**/*.test.ts"] 31 | } 32 | -------------------------------------------------------------------------------- /web/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "plugin:@nx/react-typescript", 4 | "next", 5 | "next/core-web-vitals", 6 | "../.eslintrc.json" 7 | ], 8 | "ignorePatterns": ["!**/*", ".next/**/*"], 9 | "overrides": [ 10 | { 11 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 12 | "rules": { 13 | "@next/next/no-html-link-for-pages": ["error", "web/pages"] 14 | } 15 | }, 16 | { 17 | "files": ["*.ts", "*.tsx"], 18 | "rules": {} 19 | }, 20 | { 21 | "files": ["*.js", "*.jsx"], 22 | "rules": {} 23 | }, 24 | { 25 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 26 | "rules": { 27 | "@nx/enforce-module-boundaries": [ 28 | "error", 29 | { 30 | "allow": ["@/"] 31 | } 32 | ] 33 | } 34 | } 35 | ] 36 | } 37 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/*"], 4 | "plugins": ["@nx"], 5 | "overrides": [ 6 | { 7 | "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], 8 | "rules": { 9 | "@nx/enforce-module-boundaries": [ 10 | "error", 11 | { 12 | "enforceBuildableLibDependency": true, 13 | "allow": [], 14 | "depConstraints": [ 15 | { 16 | "sourceTag": "*", 17 | "onlyDependOnLibsWithTags": ["*"] 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | }, 24 | { 25 | "files": ["*.ts", "*.tsx"], 26 | "extends": ["plugin:@nx/typescript"], 27 | "rules": {} 28 | }, 29 | { 30 | "files": ["*.js", "*.jsx"], 31 | "extends": ["plugin:@nx/javascript"], 32 | "rules": {} 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /web/components/cluster/cluster-feature.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useState } from 'react'; 4 | import { AppHero } from '../ui/ui-layout'; 5 | import { ClusterUiModal } from './cluster-ui'; 6 | import { ClusterUiTable } from './cluster-ui'; 7 | 8 | export default function ClusterFeature() { 9 | const [showModal, setShowModal] = useState(false); 10 | 11 | return ( 12 |
13 | 17 | setShowModal(false)} 20 | /> 21 | 27 | 28 | 29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | dist 5 | tmp 6 | /out-tsc 7 | # dependencies 8 | node_modules 9 | # IDEs and editors 10 | /.idea 11 | .project 12 | .classpath 13 | .c9/ 14 | *.launch 15 | .settings/ 16 | *.sublime-workspace 17 | # IDE - VSCode 18 | .vscode/* 19 | !.vscode/settings.json 20 | !.vscode/tasks.json 21 | !.vscode/launch.json 22 | !.vscode/extensions.json 23 | # misc 24 | /.sass-cache 25 | /connect.lock 26 | /coverage 27 | /libpeerconnection.log 28 | npm-debug.log 29 | yarn-error.log 30 | testem.log 31 | /typings 32 | # System Files 33 | .DS_Store 34 | Thumbs.db 35 | .nx/cache 36 | .nx/workspace-data 37 | # Next.js 38 | .next 39 | out 40 | .anchor 41 | anchor/target/deploy 42 | anchor/target/debug 43 | anchor/target/release 44 | anchor/target/sbf-solana-solana 45 | anchor/target/.rustc_info.json 46 | !anchor/target/idl/*.json 47 | !anchor/target/types/*.ts 48 | test-ledger 49 | .yarn -------------------------------------------------------------------------------- /web/components/game-components/Table.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | // style 4 | import Style from "./PokerGameQuiz.module.css"; 5 | 6 | // my components 7 | import TableCard from "./TableCard"; 8 | 9 | const Table = ({ card1, card2, card3, card4, card5 }) => { 10 | return ( 11 |
12 | poker table image 13 |
14 | 15 | 16 | 17 | 18 | 19 |
20 |
21 | ); 22 | } 23 | 24 | export default Table; -------------------------------------------------------------------------------- /web/components/game-components/Player.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | 3 | // stlye 4 | import Style from "./PokerGameQuiz.module.css"; 5 | 6 | // my components 7 | import Option from "./Option"; 8 | 9 | // context 10 | import PokerGameQuizContext from './PokerGameQuizContext'; 11 | 12 | const Player = ({ card1, card2, card3, card4}) => { 13 | 14 | const { playerOneChance, playerTwoChance, setPlayerOneChance, setPlayerTwoChance } = useContext(PokerGameQuizContext); 15 | 16 | const handleOptionOneClick = () => { 17 | 18 | } 19 | 20 | const handleOptionTwoClick = () => { 21 | 22 | } 23 | 24 | return ( 25 |
26 |
29 | ); 30 | } 31 | 32 | export default Player; -------------------------------------------------------------------------------- /web/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './global.css'; 2 | import { UiLayout } from '@/components/ui/ui-layout'; 3 | import { ClusterProvider } from '@/components/cluster/cluster-data-access'; 4 | import { SolanaProvider } from '@/components/solana/solana-provider'; 5 | import { ReactQueryProvider } from './react-query-provider'; 6 | 7 | export const metadata = { 8 | title: 'solana-poker-quiz', 9 | description: 'Generated by create-solana-dapp', 10 | }; 11 | 12 | const links: { label: string; path: string }[] = [ 13 | {label:'Home', path:'/'}, 14 | { label: 'Game', path: '/game' }, 15 | { label: 'Leaderboard', path: '/leaderboard' }, 16 | ]; 17 | 18 | export default function RootLayout({ 19 | children, 20 | }: { 21 | children: React.ReactNode; 22 | }) { 23 | return ( 24 | 25 | 26 | 27 | 28 | 29 | {children} 30 | 31 | 32 | 33 | 34 | 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 matejdrazic-balov 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /anchor/programs/poksol-leaderboard/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anchor_lang::prelude::*; 2 | 3 | declare_id!("2rT5faxcrTnWXE125tWzzje9PFHt3k4C3VUjhNfQuzvY"); 4 | 5 | #[program] 6 | pub mod poksol_leaderboard { 7 | use super::*; 8 | 9 | pub fn initialize(_ctx: Context) -> Result<()> { 10 | msg!("Leaderboard initialized"); 11 | Ok(()) 12 | } 13 | 14 | pub fn update_score(ctx: Context, score: u64) -> Result<()> { 15 | let user_score = &mut ctx.accounts.user_score; 16 | user_score.user = ctx.accounts.user.key(); 17 | user_score.score = score; 18 | msg!("Score updated for user: {:?}", user_score.user); 19 | Ok(()) 20 | } 21 | } 22 | 23 | #[derive(Accounts)] 24 | pub struct Initialize {} 25 | 26 | #[derive(Accounts)] 27 | #[instruction(score: u64)] 28 | pub struct UpdateScore<'info> { 29 | #[account( 30 | init_if_needed, 31 | payer = user, 32 | space = 8 + 32 + 8, 33 | seeds = [b"user-score", user.key().as_ref()], 34 | bump 35 | )] 36 | pub user_score: Account<'info, UserScore>, 37 | #[account(mut)] 38 | pub user: Signer<'info>, 39 | pub system_program: Program<'info, System>, 40 | } 41 | 42 | #[account] 43 | pub struct UserScore { 44 | pub user: Pubkey, 45 | pub score: u64, 46 | } -------------------------------------------------------------------------------- /web/components/blockchain/src/leaderboard-exports.ts: -------------------------------------------------------------------------------- 1 | // Here we export some useful types and functions for interacting with the Anchor program. 2 | import { AnchorProvider, Program } from '@coral-xyz/anchor'; 3 | import { Cluster, PublicKey } from '@solana/web3.js'; 4 | import LeaderboardIDL from '../target/idl/poksol_leaderboard.json'; 5 | import type { PoksolLeaderboard } from '../target/types/poksol_leaderboard'; 6 | 7 | // Re-export the generated IDL and type 8 | export { PoksolLeaderboard, LeaderboardIDL }; 9 | 10 | // The programId is imported from the program IDL. 11 | export const WAGER_PROGRAM_ID = new PublicKey(LeaderboardIDL.address); 12 | 13 | // This is a helper function to get the Counter Anchor program. 14 | export function getLeaderboardProgram(provider: AnchorProvider) { 15 | return new Program(LeaderboardIDL as PoksolLeaderboard, provider); 16 | } 17 | 18 | // This is a helper function to get the program ID for the Counter program depending on the cluster. 19 | export function getLeaderboardProgramId(cluster: Cluster) { 20 | switch (cluster) { 21 | case 'devnet': 22 | case 'testnet': 23 | // This is the program ID for the Counter program on devnet and testnet. 24 | return new PublicKey('2rT5faxcrTnWXE125tWzzje9PFHt3k4C3VUjhNfQuzvY'); 25 | case 'mainnet-beta': 26 | default: 27 | return WAGER_PROGRAM_ID; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /web/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web", 3 | "$schema": "../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "web", 5 | "projectType": "application", 6 | "tags": [], 7 | "targets": { 8 | "build": { 9 | "executor": "@nx/next:build", 10 | "outputs": ["{options.outputPath}"], 11 | "defaultConfiguration": "production", 12 | "options": { 13 | "outputPath": "dist/web" 14 | }, 15 | "configurations": { 16 | "development": { 17 | "outputPath": "web" 18 | }, 19 | "production": {} 20 | } 21 | }, 22 | "serve": { 23 | "executor": "@nx/next:server", 24 | "defaultConfiguration": "development", 25 | "options": { 26 | "buildTarget": "web:build", 27 | "dev": true, 28 | "port": 3000 29 | }, 30 | "configurations": { 31 | "development": { 32 | "buildTarget": "web:build:development", 33 | "dev": true 34 | }, 35 | "production": { 36 | "buildTarget": "web:build:production", 37 | "dev": false 38 | } 39 | } 40 | }, 41 | "export": { 42 | "executor": "@nx/next:export", 43 | "options": { 44 | "buildTarget": "web:build:production" 45 | } 46 | }, 47 | "lint": { 48 | "executor": "@nx/eslint:lint" 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /anchor/src/leaderboard-exports.ts: -------------------------------------------------------------------------------- 1 | // // Here we export some useful types and functions for interacting with the Anchor program. 2 | // import { AnchorProvider, Program } from '@coral-xyz/anchor'; 3 | // import { Cluster, PublicKey } from '@solana/web3.js'; 4 | // import LeaderboardIDL from '../target/idl/poksol_leaderboard.json'; 5 | // import type { PoksolLeaderboard } from '../target/types/poksol_leaderboard'; 6 | 7 | // // Re-export the generated IDL and type 8 | // export { PoksolLeaderboard, LeaderboardIDL }; 9 | 10 | // // The programId is imported from the program IDL. 11 | // export const WAGER_PROGRAM_ID = new PublicKey(LeaderboardIDL.address); 12 | 13 | // // This is a helper function to get the Counter Anchor program. 14 | // export function getLeaderboardProgram(provider: AnchorProvider) { 15 | // return new Program(LeaderboardIDL as PoksolLeaderboard, provider); 16 | // } 17 | 18 | // // This is a helper function to get the program ID for the Counter program depending on the cluster. 19 | // export function getLeaderboardProgramId(cluster: Cluster) { 20 | // switch (cluster) { 21 | // case 'devnet': 22 | // case 'testnet': 23 | // // This is the program ID for the Counter program on devnet and testnet. 24 | // return new PublicKey('2rT5faxcrTnWXE125tWzzje9PFHt3k4C3VUjhNfQuzvY'); 25 | // case 'mainnet-beta': 26 | // default: 27 | // return WAGER_PROGRAM_ID; 28 | // } 29 | // } 30 | -------------------------------------------------------------------------------- /web/components/account/account-detail-feature.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { PublicKey } from '@solana/web3.js'; 4 | import { useMemo } from 'react'; 5 | 6 | import { useParams } from 'next/navigation'; 7 | 8 | import { ExplorerLink } from '../cluster/cluster-ui'; 9 | import { AppHero, ellipsify } from '../ui/ui-layout'; 10 | import { 11 | AccountBalance, 12 | AccountButtons, 13 | AccountTokens, 14 | AccountTransactions, 15 | } from './account-ui'; 16 | 17 | export default function AccountDetailFeature() { 18 | const params = useParams(); 19 | const address = useMemo(() => { 20 | if (!params.address) { 21 | return; 22 | } 23 | try { 24 | return new PublicKey(params.address); 25 | } catch (e) { 26 | console.log(`Invalid public key`, e); 27 | } 28 | }, [params]); 29 | if (!address) { 30 | return
Error loading account
; 31 | } 32 | 33 | return ( 34 |
35 | } 37 | subtitle={ 38 |
39 | 43 |
44 | } 45 | > 46 |
47 | 48 |
49 |
50 |
51 | 52 | 53 |
54 |
55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /web/components/game-components/Timer/Timer.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useContext } from 'react'; 2 | 3 | // context 4 | import PokerGameQuizContext from '../PokerGameQuizContext'; 5 | 6 | const Timer = ({ initialSeconds = 60, onTimeout }) => { 7 | 8 | const [seconds, setSeconds] = useState(initialSeconds); 9 | const { quizIsRunning } = useContext(PokerGameQuizContext); 10 | 11 | const { setIsResultVisible, timeLeft, setTimeLeft } = useContext(PokerGameQuizContext); 12 | 13 | useEffect(() => { 14 | 15 | let interval; 16 | 17 | if (quizIsRunning && seconds > 0) { 18 | 19 | interval = setInterval(() => { 20 | setSeconds(prevSeconds => prevSeconds - 1); 21 | 22 | // time left 23 | setTimeLeft(seconds); 24 | }, 1000); 25 | 26 | } 27 | 28 | // clear the interval when the timer reaches 0 or the quiz stops running 29 | if (seconds === 0) { 30 | // clear interval 31 | clearInterval(interval); 32 | 33 | // show result 34 | setIsResultVisible(true); 35 | 36 | // call the onTimeout callback when the timer reaches 0 37 | if (onTimeout) { 38 | onTimeout(); 39 | } 40 | 41 | } 42 | 43 | return () => clearInterval(interval); 44 | }, [quizIsRunning, seconds, onTimeout]); 45 | 46 | // reset the timer if the quiz starts again 47 | useEffect(() => { 48 | if (!quizIsRunning) { 49 | setSeconds(initialSeconds); 50 | } 51 | }, [quizIsRunning, initialSeconds]); 52 | 53 | return
{seconds}s
; 54 | }; 55 | 56 | export default Timer; 57 | -------------------------------------------------------------------------------- /web/components/game-components/UsernameModal.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react'; 2 | import Style from './UsernameModal.module.css'; 3 | 4 | import pocketbase from '../../app/pocketbase/pocketbase'; 5 | import { useWallet } from '@solana/wallet-adapter-react'; 6 | 7 | // Create a context for the username 8 | const UsernameContext = React.createContext(); 9 | 10 | export const UsernameProvider = ({ children }) => { 11 | const [username, setUsername] = useState(''); 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | }; 18 | 19 | const UsernameModal = ({handleUsernameSubmit}) => { 20 | const [inputValue, setInputValue] = useState(''); 21 | 22 | const handleSubmit = async (e) => { 23 | 24 | e.preventDefault(); 25 | await handleUsernameSubmit(inputValue); 26 | 27 | }; 28 | 29 | return ( 30 |
31 |
32 |

Enter Your Username

33 |
34 | setInputValue(e.target.value)} 38 | placeholder="Username" 39 | required 40 | className={Style.Input} 41 | /> 42 | 45 |
46 |
47 |
48 | ); 49 | }; 50 | 51 | export default UsernameModal; -------------------------------------------------------------------------------- /web/components/solana/solana-provider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import dynamic from 'next/dynamic'; 4 | import { AnchorProvider } from '@coral-xyz/anchor'; 5 | import { WalletError } from '@solana/wallet-adapter-base'; 6 | import { 7 | AnchorWallet, 8 | useConnection, 9 | useWallet, 10 | ConnectionProvider, 11 | WalletProvider, 12 | } from '@solana/wallet-adapter-react'; 13 | import { WalletModalProvider } from '@solana/wallet-adapter-react-ui'; 14 | import { ReactNode, useCallback, useMemo } from 'react'; 15 | import { useCluster } from '../cluster/cluster-data-access'; 16 | 17 | require('@solana/wallet-adapter-react-ui/styles.css'); 18 | 19 | export const WalletButton = dynamic( 20 | async () => 21 | (await import('@solana/wallet-adapter-react-ui')).WalletMultiButton, 22 | { ssr: false } 23 | ); 24 | 25 | export function SolanaProvider({ children }: { children: ReactNode }) { 26 | const { cluster } = useCluster(); 27 | const endpoint = useMemo(() => cluster.endpoint, [cluster]); 28 | const onError = useCallback((error: WalletError) => { 29 | console.error(error); 30 | }, []); 31 | 32 | return ( 33 | 34 | 35 | {children} 36 | 37 | 38 | ); 39 | } 40 | 41 | export function useAnchorProvider() { 42 | const { connection } = useConnection(); 43 | const wallet = useWallet(); 44 | 45 | return new AnchorProvider(connection, wallet as AnchorWallet, { 46 | commitment: 'confirmed', 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "namedInputs": { 4 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 5 | "production": [ 6 | "default", 7 | "!{projectRoot}/.eslintrc.json", 8 | "!{projectRoot}/eslint.config.js", 9 | "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", 10 | "!{projectRoot}/tsconfig.spec.json", 11 | "!{projectRoot}/jest.config.[jt]s", 12 | "!{projectRoot}/src/test-setup.[jt]s", 13 | "!{projectRoot}/test-setup.[jt]s" 14 | ], 15 | "sharedGlobals": [] 16 | }, 17 | "targetDefaults": { 18 | "@nx/next:build": { 19 | "cache": true, 20 | "dependsOn": ["^build"], 21 | "inputs": ["production", "^production"] 22 | }, 23 | "@nx/eslint:lint": { 24 | "cache": true, 25 | "inputs": [ 26 | "default", 27 | "{workspaceRoot}/.eslintrc.json", 28 | "{workspaceRoot}/.eslintignore", 29 | "{workspaceRoot}/eslint.config.js" 30 | ] 31 | }, 32 | "@nx/jest:jest": { 33 | "cache": true, 34 | "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"], 35 | "options": { 36 | "passWithNoTests": true 37 | }, 38 | "configurations": { 39 | "ci": { 40 | "ci": true, 41 | "codeCoverage": true 42 | } 43 | } 44 | } 45 | }, 46 | "generators": { 47 | "@nx/next": { 48 | "application": { 49 | "style": "css", 50 | "linter": "eslint" 51 | } 52 | } 53 | }, 54 | "plugins": [ 55 | { 56 | "plugin": "@nx/rollup/plugin", 57 | "options": { 58 | "buildTargetName": "build" 59 | } 60 | } 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /web/components/solana-poker-quiz/solana-poker-quiz-feature.tsx: -------------------------------------------------------------------------------- 1 | // 'use client'; 2 | 3 | // import { useWallet } from '@solana/wallet-adapter-react'; 4 | // import { WalletButton } from '../solana/solana-provider'; 5 | // import { AppHero, ellipsify } from '../ui/ui-layout'; 6 | // import { ExplorerLink } from '../cluster/cluster-ui'; 7 | // import { useSolanaPokerQuizProgram } from './solana-poker-quiz-data-access'; 8 | // import { 9 | // SolanaPokerQuizCreate, 10 | // SolanaPokerQuizList, 11 | // } from './solana-poker-quiz-ui'; 12 | 13 | // export default function SolanaPokerQuizFeature() { 14 | // const { publicKey } = useWallet(); 15 | // const { programId } = useSolanaPokerQuizProgram(); 16 | 17 | // return publicKey ? ( 18 | //
19 | // import SolanaPokerQuizFeature 25 | //

26 | // 30 | //

31 | // 32 | //
33 | // 34 | //
35 | // ) : ( 36 | //
37 | //
38 | //
39 | // 40 | //
41 | //
42 | //
43 | // ); 44 | // } 45 | -------------------------------------------------------------------------------- /web/components/leaderboard/Leaderboard.module.css: -------------------------------------------------------------------------------- 1 | .LeaderboardPage { 2 | display: flex; 3 | flex-direction: column; 4 | width: 100vw; 5 | align-items: center; 6 | padding-top: 2em; 7 | min-height: 100vh; 8 | } 9 | 10 | .LeaderboardContainer { 11 | display: flex; 12 | flex-direction: column; 13 | align-items: center; 14 | padding: 2em; 15 | width: 80%; 16 | max-width: 1200px; 17 | margin-top: 2em; 18 | border-radius: 1.5em; 19 | box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); 20 | backdrop-filter: blur(10px); 21 | border: 3px solid rgba(255, 255, 255, 0.3); 22 | } 23 | 24 | .LeaderboardMemberContainerZero, .LeaderboardMemberContainerOne { 25 | display: flex; 26 | flex-direction: row; 27 | width: 100%; 28 | justify-content: space-between; 29 | padding: 1.5em; 30 | margin: 1em 0; 31 | border-radius: 1em; 32 | box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1); 33 | transition: transform 0.3s ease, background-color 0.3s ease; 34 | } 35 | 36 | .LeaderboardMemberContainerZero:hover, .LeaderboardMemberContainerOne:hover { 37 | transform: translateY(-5px); 38 | background-color: #3c3b6e; 39 | } 40 | 41 | .LeaderboardMemberContainerZero { 42 | background-color: #4e5d94; 43 | } 44 | 45 | .LeaderboardMemberContainerOne { 46 | background-color: #364785; 47 | } 48 | 49 | .LeaderboardMemberUser { 50 | display: flex; 51 | flex-direction: row; 52 | align-items: center; 53 | } 54 | 55 | .LeaderboardMember { 56 | margin: 0 1.5em; 57 | font-size: 1.5em; 58 | color: #fff; 59 | font-weight: 600; 60 | } 61 | 62 | .LeaderboardTitle { 63 | color: #fff; 64 | font-family: 'Poppins', sans-serif; 65 | font-size: 2.5em; 66 | font-weight: bold; 67 | text-align: center; 68 | margin-top: 20px; 69 | } -------------------------------------------------------------------------------- /web/components/leaderboard/Leaderboard.js: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import React, { useEffect, useState } from 'react'; 3 | import Image from 'next/image'; 4 | import client from '../../app/pocketbase/pocketbase'; 5 | 6 | // style 7 | import Style from './Leaderboard.module.css'; 8 | 9 | // my components 10 | import LeaderboardMember from './LeaderboardMember'; 11 | 12 | const Leaderboard = () => { 13 | const [users, setUsers] = useState([]); 14 | const [isLoading, setIsLoading] = useState(false); 15 | const [error, setError] = useState(null); 16 | 17 | const fetchUsers = async () => { 18 | setIsLoading(true); 19 | try { 20 | const records = await client.collection('users').getFullList({ 21 | sort: '-score', 22 | }); 23 | setUsers(records); 24 | console.log(records); 25 | } catch (err) { 26 | // setError('Failed to fetch users. Please try again.'); 27 | console.error(err); 28 | } finally { 29 | setIsLoading(false); 30 | } 31 | }; 32 | 33 | useEffect(() => { 34 | fetchUsers(); 35 | 36 | return () => { 37 | client.cancelAllRequests(); 38 | } 39 | }, []); 40 | 41 | return ( 42 |
43 |

44 | {isLoading ? 'Loading...' : 'PokSol Top Achievers'} 45 |

46 | {error &&

{error}

} 47 | {users.length > 0 && ( 48 |
49 | {users.map((user, index) => ( 50 | 57 | ))} 58 |
59 | )} 60 |
61 | ); 62 | 63 | }; 64 | 65 | export default Leaderboard; 66 | -------------------------------------------------------------------------------- /web/components/game-components/UsernameModal.module.css: -------------------------------------------------------------------------------- 1 | .ModalContainer { 2 | display: flex; 3 | justify-content: center; 4 | align-items: center; 5 | height: 100vh; 6 | background-color: rgba(0, 0, 0, 0.512); 7 | position: fixed; 8 | top: 0; 9 | left: 0; 10 | right: 0; 11 | bottom: 0; 12 | } 13 | 14 | .Modal { 15 | background-color: #6F8FAF; /* Light blue background */ 16 | padding: 2.5rem; 17 | border-radius: 20px; 18 | box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2); 19 | text-align: center; 20 | max-width: 400px; 21 | width: 100%; 22 | border: 2px solid #1e3a8a; /* Dark blue border */ 23 | } 24 | 25 | .ModalTitle { 26 | color: #000000; /* Black color */ 27 | margin-bottom: 0.5rem; 28 | font-size: 2rem; 29 | font-weight: 700; 30 | } 31 | 32 | .ModalSubtitle { 33 | color: #4a5568; /* Gray color for subtitle */ 34 | margin-bottom: 1.5rem; 35 | font-size: 1rem; 36 | } 37 | 38 | .Form { 39 | display: flex; 40 | flex-direction: column; 41 | } 42 | 43 | .Input { 44 | width: 100%; 45 | padding: 0.75rem; 46 | margin-bottom: 1.5rem; 47 | border: 2px solid #cbd5e0; /* Light gray border */ 48 | border-radius: 10px; 49 | font-size: 1rem; 50 | transition: border-color 0.3s ease; 51 | } 52 | 53 | .Input:focus { 54 | outline: none; 55 | border-color: #4299e1; /* Blue highlight on focus */ 56 | } 57 | 58 | .SubmitButton { 59 | background-color: #000000; /* Black background */ 60 | color: white; /* White text for better contrast */ 61 | border: none; 62 | padding: 0.75rem 1rem; 63 | font-size: 1rem; 64 | border-radius: 10px; 65 | cursor: pointer; 66 | transition: background-color 0.3s ease, transform 0.1s ease; 67 | font-weight: 600; 68 | } 69 | 70 | .SubmitButton:hover { 71 | background-color: #333333; /* Slightly lighter black on hover */ 72 | transform: translateY(-2px); 73 | } 74 | 75 | .SubmitButton:active { 76 | transform: translateY(0); 77 | } -------------------------------------------------------------------------------- /web/components/blockchain/target/idl/poksol_leaderboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "address": "2rT5faxcrTnWXE125tWzzje9PFHt3k4C3VUjhNfQuzvY", 3 | "metadata": { 4 | "name": "poksol_leaderboard", 5 | "version": "0.1.0", 6 | "spec": "0.1.0", 7 | "description": "Created with Anchor" 8 | }, 9 | "instructions": [ 10 | { 11 | "name": "initialize", 12 | "discriminator": [ 13 | 175, 14 | 175, 15 | 109, 16 | 31, 17 | 13, 18 | 152, 19 | 155, 20 | 237 21 | ], 22 | "accounts": [], 23 | "args": [] 24 | }, 25 | { 26 | "name": "update_score", 27 | "discriminator": [ 28 | 188, 29 | 226, 30 | 238, 31 | 41, 32 | 14, 33 | 241, 34 | 105, 35 | 215 36 | ], 37 | "accounts": [ 38 | { 39 | "name": "user_score", 40 | "writable": true, 41 | "pda": { 42 | "seeds": [ 43 | { 44 | "kind": "const", 45 | "value": [ 46 | 117, 47 | 115, 48 | 101, 49 | 114, 50 | 45, 51 | 115, 52 | 99, 53 | 111, 54 | 114, 55 | 101 56 | ] 57 | }, 58 | { 59 | "kind": "account", 60 | "path": "user" 61 | } 62 | ] 63 | } 64 | }, 65 | { 66 | "name": "user", 67 | "writable": true, 68 | "signer": true 69 | }, 70 | { 71 | "name": "system_program", 72 | "address": "11111111111111111111111111111111" 73 | } 74 | ], 75 | "args": [ 76 | { 77 | "name": "score", 78 | "type": "u64" 79 | } 80 | ] 81 | } 82 | ], 83 | "accounts": [ 84 | { 85 | "name": "UserScore", 86 | "discriminator": [ 87 | 212, 88 | 150, 89 | 123, 90 | 224, 91 | 34, 92 | 227, 93 | 84, 94 | 39 95 | ] 96 | } 97 | ], 98 | "types": [ 99 | { 100 | "name": "UserScore", 101 | "type": { 102 | "kind": "struct", 103 | "fields": [ 104 | { 105 | "name": "user", 106 | "type": "pubkey" 107 | }, 108 | { 109 | "name": "score", 110 | "type": "u64" 111 | } 112 | ] 113 | } 114 | } 115 | ] 116 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@solana-poker-quiz/source", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "anchor": "nx run anchor:anchor", 7 | "anchor-build": "nx run anchor:anchor build", 8 | "anchor-localnet": "nx run anchor:anchor localnet", 9 | "anchor-test": "nx run anchor:anchor test", 10 | "feature": "nx generate @solana-developers/preset-react:feature", 11 | "build": "nx build web", 12 | "dev": "nx serve web" 13 | }, 14 | "private": true, 15 | "dependencies": { 16 | "@coral-xyz/anchor": "^0.30.1", 17 | "@heroicons/react": "^2.1.5", 18 | "@material-tailwind/react": "^2.1.10", 19 | "@solana-developers/preset-next": "3.1.0", 20 | "@solana/spl-token": "0.4.6", 21 | "@solana/wallet-adapter-base": "^0.9.23", 22 | "@solana/wallet-adapter-react": "^0.15.35", 23 | "@solana/wallet-adapter-react-ui": "^0.9.35", 24 | "@solana/web3.js": "1.91.9", 25 | "@tabler/icons-react": "3.5.0", 26 | "@tailwindcss/typography": "0.5.13", 27 | "@tanstack/react-query": "5.40.0", 28 | "@tanstack/react-query-next-experimental": "5.40.0", 29 | "bs58": "5.0.0", 30 | "buffer": "6.0.3", 31 | "daisyui": "4.11.1", 32 | "encoding": "0.1.13", 33 | "jotai": "2.8.3", 34 | "next": "14.2.3", 35 | "poker-odds-calc": "^0.0.14", 36 | "pocketbase": "^0.21.5", 37 | "react": "18.3.1", 38 | "react-dom": "18.3.1", 39 | "react-hot-toast": "2.4.1", 40 | "tslib": "^2.3.0" 41 | }, 42 | "devDependencies": { 43 | "@nx/eslint": "19.3.2", 44 | "@nx/eslint-plugin": "19.3.2", 45 | "@nx/jest": "19.3.2", 46 | "@nx/js": "19.3.2", 47 | "@nx/next": "19.3.2", 48 | "@nx/rollup": "19.3.2", 49 | "@nx/workspace": "19.3.2", 50 | "@swc-node/register": "~1.9.1", 51 | "@swc/cli": "~0.3.12", 52 | "@swc/core": "~1.5.7", 53 | "@swc/helpers": "~0.5.11", 54 | "@swc/jest": "~0.2.36", 55 | "@types/jest": "^29.4.0", 56 | "@types/node": "18.16.9", 57 | "@types/react": "18.3.1", 58 | "@types/react-dom": "18.3.0", 59 | "@typescript-eslint/eslint-plugin": "^7.3.0", 60 | "@typescript-eslint/parser": "^7.3.0", 61 | "autoprefixer": "10.4.13", 62 | "eslint": "~8.57.0", 63 | "eslint-config-next": "14.2.3", 64 | "eslint-config-prettier": "^9.0.0", 65 | "eslint-plugin-import": "2.27.5", 66 | "eslint-plugin-jsx-a11y": "6.7.1", 67 | "eslint-plugin-react": "7.32.2", 68 | "eslint-plugin-react-hooks": "4.6.0", 69 | "jest": "^29.4.1", 70 | "jest-environment-jsdom": "^29.4.1", 71 | "nx": "19.3.2", 72 | "postcss": "8.4.38", 73 | "prettier": "^2.6.2", 74 | "rollup": "^4.14.0", 75 | "swc-loader": "0.1.15", 76 | "tailwindcss": "3.4.3", 77 | "ts-jest": "^29.1.0", 78 | "ts-node": "10.9.1", 79 | "typescript": "~5.4.2" 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /web/components/blockchain/target/types/poksol_leaderboard.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Program IDL in camelCase format in order to be used in JS/TS. 3 | * 4 | * Note that this is only a type helper and is not the actual IDL. The original 5 | * IDL can be found at `target/idl/poksol_leaderboard.json`. 6 | */ 7 | export type PoksolLeaderboard = { 8 | "address": "2rT5faxcrTnWXE125tWzzje9PFHt3k4C3VUjhNfQuzvY", 9 | "metadata": { 10 | "name": "poksolLeaderboard", 11 | "version": "0.1.0", 12 | "spec": "0.1.0", 13 | "description": "Created with Anchor" 14 | }, 15 | "instructions": [ 16 | { 17 | "name": "initialize", 18 | "discriminator": [ 19 | 175, 20 | 175, 21 | 109, 22 | 31, 23 | 13, 24 | 152, 25 | 155, 26 | 237 27 | ], 28 | "accounts": [], 29 | "args": [] 30 | }, 31 | { 32 | "name": "updateScore", 33 | "discriminator": [ 34 | 188, 35 | 226, 36 | 238, 37 | 41, 38 | 14, 39 | 241, 40 | 105, 41 | 215 42 | ], 43 | "accounts": [ 44 | { 45 | "name": "userScore", 46 | "writable": true, 47 | "pda": { 48 | "seeds": [ 49 | { 50 | "kind": "const", 51 | "value": [ 52 | 117, 53 | 115, 54 | 101, 55 | 114, 56 | 45, 57 | 115, 58 | 99, 59 | 111, 60 | 114, 61 | 101 62 | ] 63 | }, 64 | { 65 | "kind": "account", 66 | "path": "user" 67 | } 68 | ] 69 | } 70 | }, 71 | { 72 | "name": "user", 73 | "writable": true, 74 | "signer": true 75 | }, 76 | { 77 | "name": "systemProgram", 78 | "address": "11111111111111111111111111111111" 79 | } 80 | ], 81 | "args": [ 82 | { 83 | "name": "score", 84 | "type": "u64" 85 | } 86 | ] 87 | } 88 | ], 89 | "accounts": [ 90 | { 91 | "name": "userScore", 92 | "discriminator": [ 93 | 212, 94 | 150, 95 | 123, 96 | 224, 97 | 34, 98 | 227, 99 | 84, 100 | 39 101 | ] 102 | } 103 | ], 104 | "types": [ 105 | { 106 | "name": "userScore", 107 | "type": { 108 | "kind": "struct", 109 | "fields": [ 110 | { 111 | "name": "user", 112 | "type": "pubkey" 113 | }, 114 | { 115 | "name": "score", 116 | "type": "u64" 117 | } 118 | ] 119 | } 120 | } 121 | ] 122 | }; 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # solana-poker-quiz 2 | 3 | This project is generated with the [create-solana-dapp](https://github.com/solana-developers/create-solana-dapp) generator. 4 | 5 | ## Getting Started 6 | 7 | This project is a blockchain-based Texas Holdem Poker Quiz that challenges users to choose between two poker hands and determine which one has a higher chance of winning. The game quiz covers different stages of a poker round: preflop, flop, turn, and river, allowing users to analyze the odds and make decisions based on each stage. User score and address are saved on the Solana blockchain. 8 | 9 | PokSol is a first of its kind on-chain experience for all recreational and professional enjoyers of the game of Hold-Em Poker. 10 | 11 | Following the path of classical poker communities like PokerStars and GGPoker we want to become the GO TO place for everything poker - with an on-chain twist on Solana. By incentivizing users with tokens and other rewards we want to create both an educational and professional platform for all types of players. 12 | 13 | PokSol features a play-to-earn model. Each week, players compete by answering quiz questions and selecting the poker hand with the highest chance of winning. Your performance is reflected on the leaderboard, and the top player each week will receive free tokens as a reward! 14 | 15 | ![Alt text](./img1.png) 16 | 17 | ![Alt text](./img2.png) 18 | 19 | After result is saved to blockchain: 20 | 21 | ![Alt text](./img3.png) 22 | 23 | ### Prerequisites 24 | 25 | - Node v18.18.0 or higher 26 | 27 | - Rust v1.77.2 or higher 28 | - Anchor CLI 0.30.1 or higher 29 | - Solana CLI 1.18.17 or higher 30 | 31 | ### Installation 32 | 33 | #### Clone the repo 34 | 35 | ```shell 36 | git clone https://github.com/0xRustPro/solana-poker-casino-game 37 | cd solana-poker-casino-game 38 | ``` 39 | 40 | #### Install Dependencies 41 | 42 | ```shell 43 | npm install 44 | ``` 45 | 46 | #### Start the web app 47 | 48 | ``` 49 | npm run dev 50 | ``` 51 | 52 | ## Apps 53 | 54 | ### anchor 55 | 56 | This is a Solana program written in Rust using the Anchor framework. 57 | 58 | #### Commands 59 | 60 | You can use any normal anchor commands. Either move to the `anchor` directory and run the `anchor` command or prefix the command with `npm run`, eg: `npm run anchor`. 61 | 62 | #### Sync the program id: 63 | 64 | Running this command will create a new keypair in the `anchor/target/deploy` directory and save the address to the Anchor config file and update the `declare_id!` macro in the `./src/lib.rs` file of the program. 65 | 66 | You will manually need to update the constant in `anchor/lib/counter-exports.ts` to match the new program id. 67 | 68 | ```shell 69 | npm run anchor keys sync 70 | ``` 71 | 72 | #### Build the program: 73 | 74 | ```shell 75 | npm run anchor-build 76 | ``` 77 | 78 | #### Start the test validator with the program deployed: 79 | 80 | ```shell 81 | npm run anchor-localnet 82 | ``` 83 | 84 | #### Run the tests 85 | 86 | ```shell 87 | npm run anchor-test 88 | ``` 89 | 90 | #### Deploy to Devnet 91 | 92 | ```shell 93 | npm run anchor deploy --provider.cluster devnet 94 | ``` 95 | 96 | ### web 97 | 98 | This is a React app that uses the Anchor generated client to interact with the Solana program. 99 | 100 | #### Commands 101 | 102 | Start the web app 103 | 104 | ```shell 105 | npm run dev 106 | ``` 107 | 108 | Build the web app 109 | 110 | ```shell 111 | npm run build 112 | ``` 113 | -------------------------------------------------------------------------------- /web/components/game-components/Option.js: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | 3 | // style 4 | import Style from "./PokerGameQuiz.module.css"; 5 | 6 | // context 7 | import PokerGameQuizContext from './PokerGameQuizContext'; 8 | 9 | const Option = ({ imgSrc1, imgSrc2, isPlayerOne, isPlayerTwo }) => { 10 | 11 | const { playerOneChance, playerTwoChance, nextRound, score, setScore, result, points, addNewHistoryElement, roundCounter } = useContext(PokerGameQuizContext); 12 | 13 | const handleOptionClick = () => { 14 | // remove all ~ and % from the string and parse it to float 15 | const parsedChanceOne = parseFloat(playerOneChance.replace(/[~%]/g, '')); 16 | const parsedChanceTwo = parseFloat(playerTwoChance.replace(/[~%]/g, '')); 17 | 18 | // result - history - saving player chances 19 | result.playerOneChance = playerOneChance; 20 | result.playerTwoChance = playerTwoChance; 21 | 22 | if(isPlayerOne){ 23 | if(parsedChanceOne > parsedChanceTwo) { 24 | console.log('YOU WON!'); 25 | setScore(score + points) 26 | 27 | // save result 28 | result.winner = 0; 29 | result.correct = true; 30 | result.score = points; 31 | result.round = roundCounter; 32 | 33 | // add result to history 34 | addNewHistoryElement(result); 35 | 36 | } else { 37 | console.log('YOU LOST!'); 38 | setScore(score) 39 | 40 | // save result 41 | result.winner = 1; 42 | result.correct = 0; 43 | result.score = 0; 44 | 45 | // add result to history 46 | addNewHistoryElement(result); 47 | } 48 | 49 | nextRound(); 50 | 51 | } else if(isPlayerTwo){ 52 | console.log('player 2 chance to win'); 53 | if(parsedChanceTwo > parsedChanceOne){ 54 | console.log('YOU WON!'); 55 | setScore(score + points) 56 | 57 | // save result 58 | result.winner = 1; 59 | result.correct = 1; 60 | result.score = points; 61 | 62 | // add result to history 63 | addNewHistoryElement(result); 64 | 65 | } else { 66 | console.log('YOU LOST!'); 67 | setScore(score) 68 | 69 | // save result 70 | result.winner = 0; 71 | result.correct = 0; 72 | result.score = 0; 73 | 74 | // add result to history 75 | addNewHistoryElement(result); 76 | } 77 | 78 | nextRound(); 79 | } 80 | } 81 | 82 | return ( 83 |
handleOptionClick()} className={Style.OptionContainer}> 84 | player_card1 e.target.src = './game_resources/cards/card_back.png'} 88 | /> 89 | player_card2 e.target.src = './game_resources/cards/card_back.png'} 93 | /> 94 |
95 | ); 96 | } 97 | 98 | export default Option; -------------------------------------------------------------------------------- /web/components/dashboard/dashboard-feature.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { AppHero } from '../ui/ui-layout'; 3 | import { useRouter } from 'next/navigation'; 4 | import Image from 'next/image'; 5 | import heroImage from '@/public/hero.svg' 6 | import { useEffect, useState } from 'react'; 7 | 8 | export default function DashboardFeature() { 9 | const router = useRouter(); 10 | const [isVisible, setIsVisible] = useState(false); 11 | 12 | useEffect(() => { 13 | const timer = setTimeout(() => { 14 | setIsVisible(true); 15 | }, 300); 16 | return () => clearTimeout(timer); 17 | }, []); 18 | 19 | return ( 20 |
21 | 24 | PokSol 25 | 26 | } 27 | subtitle={ 28 |

29 | "Quiz your poker skills and track your results on the blockchain" 30 |

31 | } 32 | /> 33 | 34 |
35 | Poker quiz illustration 40 |
41 | 42 | 43 |
44 |
45 |

Our Vision

46 |

47 | We're dedicated to enhancing poker skills through engaging quizzes while leveraging the transparency of blockchain technology. 48 |

49 |
50 | 51 |
52 |

How It Works

53 |
54 | {[ 55 | { title: "1. Take the Quiz", description: "Connect your wallet to take the quiz and earn tokens and points." }, 56 | { title: "2. Save Your Results", description: "Results are securely stored on the Solana blockchain." }, 57 | { title: "3. Improve Your Skills", description: "Track your progress and enhance your poker abilities." } 58 | ].map((step, index) => ( 59 |
60 |

{step.title}

61 |

{step.description}

62 |
63 | ))} 64 |
65 |
66 | 67 |
68 |

Take the Quiz

69 | 75 |
76 |
77 |
78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /web/components/cluster/cluster-data-access.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { clusterApiUrl, Connection } from '@solana/web3.js'; 4 | import { atom, useAtomValue, useSetAtom } from 'jotai'; 5 | import { atomWithStorage } from 'jotai/utils'; 6 | import { createContext, ReactNode, useContext } from 'react'; 7 | import toast from 'react-hot-toast'; 8 | 9 | export interface Cluster { 10 | name: string; 11 | endpoint: string; 12 | network?: ClusterNetwork; 13 | active?: boolean; 14 | } 15 | 16 | export enum ClusterNetwork { 17 | Mainnet = 'mainnet-beta', 18 | Testnet = 'testnet', 19 | Devnet = 'devnet', 20 | Custom = 'custom', 21 | } 22 | 23 | // By default, we don't configure the mainnet-beta cluster 24 | // The endpoint provided by clusterApiUrl('mainnet-beta') does not allow access from the browser due to CORS restrictions 25 | // To use the mainnet-beta cluster, provide a custom endpoint 26 | export const defaultClusters: Cluster[] = [ 27 | { 28 | name: 'devnet', 29 | endpoint: clusterApiUrl('devnet'), 30 | network: ClusterNetwork.Devnet, 31 | }, 32 | { name: 'local', endpoint: 'http://localhost:8899' }, 33 | { 34 | name: 'testnet', 35 | endpoint: clusterApiUrl('testnet'), 36 | network: ClusterNetwork.Testnet, 37 | }, 38 | ]; 39 | 40 | const clusterAtom = atomWithStorage( 41 | 'solana-cluster', 42 | defaultClusters[0] 43 | ); 44 | const clustersAtom = atomWithStorage( 45 | 'solana-clusters', 46 | defaultClusters 47 | ); 48 | 49 | const activeClustersAtom = atom((get) => { 50 | const clusters = get(clustersAtom); 51 | const cluster = get(clusterAtom); 52 | return clusters.map((item) => ({ 53 | ...item, 54 | active: item.name === cluster.name, 55 | })); 56 | }); 57 | 58 | const activeClusterAtom = atom((get) => { 59 | const clusters = get(activeClustersAtom); 60 | 61 | return clusters.find((item) => item.active) || clusters[0]; 62 | }); 63 | 64 | export interface ClusterProviderContext { 65 | cluster: Cluster; 66 | clusters: Cluster[]; 67 | addCluster: (cluster: Cluster) => void; 68 | deleteCluster: (cluster: Cluster) => void; 69 | setCluster: (cluster: Cluster) => void; 70 | getExplorerUrl(path: string): string; 71 | } 72 | 73 | const Context = createContext( 74 | {} as ClusterProviderContext 75 | ); 76 | 77 | export function ClusterProvider({ children }: { children: ReactNode }) { 78 | const cluster = useAtomValue(activeClusterAtom); 79 | const clusters = useAtomValue(activeClustersAtom); 80 | const setCluster = useSetAtom(clusterAtom); 81 | const setClusters = useSetAtom(clustersAtom); 82 | 83 | const value: ClusterProviderContext = { 84 | cluster, 85 | clusters: clusters.sort((a, b) => (a.name > b.name ? 1 : -1)), 86 | addCluster: (cluster: Cluster) => { 87 | try { 88 | new Connection(cluster.endpoint); 89 | setClusters([...clusters, cluster]); 90 | } catch (err) { 91 | toast.error(`${err}`); 92 | } 93 | }, 94 | deleteCluster: (cluster: Cluster) => { 95 | setClusters(clusters.filter((item) => item.name !== cluster.name)); 96 | }, 97 | setCluster: (cluster: Cluster) => setCluster(cluster), 98 | getExplorerUrl: (path: string) => 99 | `https://explorer.solana.com/${path}${getClusterUrlParam(cluster)}`, 100 | }; 101 | return {children}; 102 | } 103 | 104 | export function useCluster() { 105 | return useContext(Context); 106 | } 107 | 108 | function getClusterUrlParam(cluster: Cluster): string { 109 | let suffix = ''; 110 | switch (cluster.network) { 111 | case ClusterNetwork.Devnet: 112 | suffix = 'devnet'; 113 | break; 114 | case ClusterNetwork.Mainnet: 115 | suffix = ''; 116 | break; 117 | case ClusterNetwork.Testnet: 118 | suffix = 'testnet'; 119 | break; 120 | default: 121 | suffix = `custom&customUrl=${encodeURIComponent(cluster.endpoint)}`; 122 | break; 123 | } 124 | 125 | return suffix.length ? `?cluster=${suffix}` : ''; 126 | } 127 | -------------------------------------------------------------------------------- /anchor/tests/poksol-leaderboard.ts: -------------------------------------------------------------------------------- 1 | import * as anchor from "@coral-xyz/anchor"; 2 | import { Program } from "@coral-xyz/anchor"; 3 | import { PoksolLeaderboard } from "../target/types/poksol_leaderboard"; 4 | import { expect } from "chai"; 5 | 6 | describe("poksol-leaderboard", () => { 7 | // Configure the client to use the local cluster. 8 | anchor.setProvider(anchor.AnchorProvider.env()); 9 | 10 | const program = anchor.workspace.PoksolLeaderboard as Program; 11 | const provider = anchor.getProvider() as anchor.AnchorProvider; 12 | 13 | it("Initializes the program", async () => { 14 | // const tx = await program.methods.initialize().rpc(); 15 | // console.log("Initialization transaction signature", tx); 16 | }); 17 | 18 | it("Updates scores for 3 users", async () => { 19 | const users = await Promise.all( 20 | Array(3).fill(0).map(async (_, i) => { 21 | const user = anchor.web3.Keypair.generate(); 22 | const signature = await provider.connection.requestAirdrop(user.publicKey, 100 * anchor.web3.LAMPORTS_PER_SOL); 23 | await provider.connection.confirmTransaction(signature, "confirmed"); 24 | return user; 25 | }) 26 | ); 27 | 28 | for (let i = 0; i < users.length; i++) { 29 | const user = users[i]; 30 | const score = (i + 1) * 100; // Different score for each user 31 | 32 | const [userScorePda] = anchor.web3.PublicKey.findProgramAddressSync( 33 | [Buffer.from("user-score"), user.publicKey.toBuffer()], 34 | program.programId 35 | ); 36 | 37 | try { 38 | const tx = await program.methods 39 | .updateScore(new anchor.BN(score)) 40 | .accounts({ 41 | userScore: userScorePda, 42 | user: user.publicKey, 43 | systemProgram: anchor.web3.SystemProgram.programId, 44 | }) 45 | .signers([user]) 46 | .rpc(); 47 | 48 | console.log(`Score update transaction for user ${i + 1}:`, tx); 49 | 50 | // Fetch the account and verify the score 51 | const userScoreAccount = await program.account.userScore.fetch(userScorePda); 52 | expect(userScoreAccount.user.toString()).to.equal(user.publicKey.toString()); 53 | expect(userScoreAccount.score.toNumber()).to.equal(score); 54 | } catch (error) { 55 | console.error(`Error updating score for user ${i + 1}:`, error); 56 | throw error; 57 | } 58 | } 59 | }); 60 | 61 | it("Updates scores for 3 users again", async () => { 62 | const users = await Promise.all( 63 | Array(3).fill(0).map(async (_, i) => { 64 | const user = anchor.web3.Keypair.generate(); 65 | const signature = await provider.connection.requestAirdrop(user.publicKey, 100 * anchor.web3.LAMPORTS_PER_SOL); 66 | await provider.connection.confirmTransaction(signature, "confirmed"); 67 | return user; 68 | }) 69 | ); 70 | 71 | for (let i = 0; i < users.length; i++) { 72 | const user = users[i]; 73 | const score = (i + 1) * 49; // Different score for each user 74 | 75 | const [userScorePda] = anchor.web3.PublicKey.findProgramAddressSync( 76 | [Buffer.from("user-score"), user.publicKey.toBuffer()], 77 | program.programId 78 | ); 79 | 80 | try { 81 | const tx = await program.methods 82 | .updateScore(new anchor.BN(score)) 83 | .accounts({ 84 | userScore: userScorePda, 85 | user: user.publicKey, 86 | systemProgram: anchor.web3.SystemProgram.programId, 87 | }) 88 | .signers([user]) 89 | .rpc(); 90 | 91 | console.log(`Score update transaction for user ${i + 1}:`, tx); 92 | 93 | // Fetch the account and verify the score 94 | const userScoreAccount = await program.account.userScore.fetch(userScorePda); 95 | expect(userScoreAccount.user.toString()).to.equal(user.publicKey.toString()); 96 | expect(userScoreAccount.score.toNumber()).to.equal(score); 97 | } catch (error) { 98 | console.error(`Error updating score for user ${i + 1}:`, error); 99 | throw error; 100 | } 101 | } 102 | }); 103 | }); -------------------------------------------------------------------------------- /web/components/solana-poker-quiz/solana-poker-quiz-data-access.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { 4 | getLeaderboardProgram, 5 | getLeaderboardProgramId, 6 | } from '../blockchain/src/leaderboard-exports'; 7 | import { BN, Program } from '@coral-xyz/anchor'; 8 | import { useConnection } from '@solana/wallet-adapter-react'; 9 | import { Cluster, Keypair, PublicKey, SystemProgram } from '@solana/web3.js'; 10 | import { useMutation, useQuery } from '@tanstack/react-query'; 11 | import { useMemo } from 'react'; 12 | import toast from 'react-hot-toast'; 13 | import { useCluster } from '../cluster/cluster-data-access'; 14 | import { useAnchorProvider } from '../solana/solana-provider'; 15 | import { useTransactionToast } from '../ui/ui-layout'; 16 | 17 | interface UpdateScoreArgs { 18 | user: PublicKey; 19 | score: BN; 20 | } 21 | 22 | export function useSolanaPokerQuizProgram() { 23 | const { connection } = useConnection(); 24 | const { cluster } = useCluster(); 25 | const transactionToast = useTransactionToast(); 26 | const provider = useAnchorProvider(); 27 | const programId = useMemo( 28 | () => getLeaderboardProgramId(cluster.network as Cluster), 29 | [cluster] 30 | ); 31 | const program = getLeaderboardProgram(provider); 32 | 33 | const accounts = useQuery({ 34 | queryKey: ['solana-poker-quiz', 'all', { cluster }], 35 | queryFn: () => program.account.userScore.all(), 36 | }); 37 | 38 | const getProgramAccount = useQuery({ 39 | queryKey: ['get-program-account', { cluster }], 40 | queryFn: () => connection.getParsedAccountInfo(programId), 41 | }); 42 | 43 | const updateScore = useMutation({ 44 | mutationKey: ['solana-poker-quiz', 'updateScore', { cluster }], 45 | mutationFn: async ({ user, score }) => { 46 | const [userScorePda] = PublicKey.findProgramAddressSync( 47 | [Buffer.from('user-score'), user.toBuffer()], 48 | program.programId 49 | ); 50 | 51 | // TODO: Have to deploy on devnet and connect with frontend 52 | return program.methods 53 | .updateScore(score) 54 | .accounts({ 55 | //@ts-ignore 56 | userScore: userScorePda, 57 | user: user, 58 | systemProgram: SystemProgram.programId, 59 | }) 60 | .rpc(); 61 | }, 62 | onSuccess: (signature) => { 63 | transactionToast('Score saved on Solana! - ' + signature); 64 | return accounts.refetch(); 65 | }, 66 | onError: () => toast.error('Failed to initialize account'), 67 | }); 68 | 69 | return { 70 | program, 71 | programId, 72 | accounts, 73 | getProgramAccount, 74 | updateScore, 75 | }; 76 | } 77 | 78 | /* 79 | export function useSolanaPokerQuizProgramAccount({ 80 | account, 81 | }: { 82 | account: PublicKey; 83 | }) { 84 | const { cluster } = useCluster(); 85 | const transactionToast = useTransactionToast(); 86 | const { program, accounts } = useSolanaPokerQuizProgram(); 87 | 88 | const accountQuery = useQuery({ 89 | queryKey: ['solana-poker-quiz', 'fetch', { cluster, account }], 90 | queryFn: () => program.account.solanaPokerQuiz.fetch(account), 91 | }); 92 | 93 | const closeMutation = useMutation({ 94 | mutationKey: ['solana-poker-quiz', 'close', { cluster, account }], 95 | mutationFn: () => 96 | program.methods.close().accounts({ solanaPokerQuiz: account }).rpc(), 97 | onSuccess: (tx) => { 98 | transactionToast(tx); 99 | return accounts.refetch(); 100 | }, 101 | }); 102 | 103 | const decrementMutation = useMutation({ 104 | mutationKey: ['solana-poker-quiz', 'decrement', { cluster, account }], 105 | mutationFn: () => 106 | program.methods.decrement().accounts({ solanaPokerQuiz: account }).rpc(), 107 | onSuccess: (tx) => { 108 | transactionToast(tx); 109 | return accountQuery.refetch(); 110 | }, 111 | }); 112 | 113 | const incrementMutation = useMutation({ 114 | mutationKey: ['solana-poker-quiz', 'increment', { cluster, account }], 115 | mutationFn: () => 116 | program.methods.increment().accounts({ solanaPokerQuiz: account }).rpc(), 117 | onSuccess: (tx) => { 118 | transactionToast(tx); 119 | return accountQuery.refetch(); 120 | }, 121 | }); 122 | 123 | const setMutation = useMutation({ 124 | mutationKey: ['solana-poker-quiz', 'set', { cluster, account }], 125 | mutationFn: (value: number) => 126 | program.methods.set(value).accounts({ solanaPokerQuiz: account }).rpc(), 127 | onSuccess: (tx) => { 128 | transactionToast(tx); 129 | return accountQuery.refetch(); 130 | }, 131 | }); 132 | 133 | return { 134 | accountQuery, 135 | closeMutation, 136 | decrementMutation, 137 | incrementMutation, 138 | setMutation, 139 | }; 140 | } 141 | 142 | */ 143 | -------------------------------------------------------------------------------- /web/components/ui/ui-layout.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { WalletButton } from '../solana/solana-provider'; 4 | import * as React from 'react'; 5 | import { ReactNode, Suspense, useEffect, useRef } from 'react'; 6 | 7 | import Link from 'next/link'; 8 | import { usePathname } from 'next/navigation'; 9 | 10 | import { AccountChecker } from '../account/account-ui'; 11 | import { 12 | ClusterChecker, 13 | ClusterUiSelect, 14 | ExplorerLink, 15 | } from '../cluster/cluster-ui'; 16 | import toast, { Toaster } from 'react-hot-toast'; 17 | 18 | export function UiLayout({ 19 | children, 20 | links, 21 | }: { 22 | children: ReactNode; 23 | links: { label: string; path: string }[]; 24 | }) { 25 | const pathname = usePathname(); 26 | 27 | return ( 28 |
29 |
30 |
31 |
    32 | {links.map(({ label, path }) => ( 33 |
  • 34 | 38 | {label} 39 | 40 |
  • 41 | ))} 42 |
43 |
44 |
45 | 46 | 47 |
48 |
49 | 50 | 51 | 52 |
53 | 56 | 57 |
58 | } 59 | > 60 | {children} 61 | 62 | 63 |
64 | 65 | ); 66 | } 67 | 68 | export function AppModal({ 69 | children, 70 | title, 71 | hide, 72 | show, 73 | submit, 74 | submitDisabled, 75 | submitLabel, 76 | }: { 77 | children: ReactNode; 78 | title: string; 79 | hide: () => void; 80 | show: boolean; 81 | submit?: () => void; 82 | submitDisabled?: boolean; 83 | submitLabel?: string; 84 | }) { 85 | const dialogRef = useRef(null); 86 | 87 | useEffect(() => { 88 | if (!dialogRef.current) return; 89 | if (show) { 90 | dialogRef.current.showModal(); 91 | } else { 92 | dialogRef.current.close(); 93 | } 94 | }, [show, dialogRef]); 95 | 96 | return ( 97 | 98 |
99 |

{title}

100 | {children} 101 |
102 |
103 | {submit ? ( 104 | 111 | ) : null} 112 | 115 |
116 |
117 |
118 |
119 | ); 120 | } 121 | 122 | export function AppHero({ 123 | children, 124 | title, 125 | subtitle, 126 | }: { 127 | children?: ReactNode; 128 | title: ReactNode; 129 | subtitle: ReactNode; 130 | }) { 131 | return ( 132 |
133 |
134 |
135 | {typeof title === 'string' ? ( 136 |

{title}

137 | ) : ( 138 | title 139 | )} 140 | {typeof subtitle === 'string' ? ( 141 |

{subtitle}

142 | ) : ( 143 | subtitle 144 | )} 145 | {children} 146 |
147 |
148 |
149 | ); 150 | } 151 | 152 | export function ellipsify(str = '', len = 4) { 153 | if (str.length > 30) { 154 | return ( 155 | str.substring(0, len) + '..' + str.substring(str.length - len, str.length) 156 | ); 157 | } 158 | return str; 159 | } 160 | 161 | export function useTransactionToast() { 162 | return (signature: string) => { 163 | toast.success( 164 |
165 |
Transaction sent
166 | 171 |
172 | ); 173 | }; 174 | } 175 | -------------------------------------------------------------------------------- /web/components/solana-poker-quiz/solana-poker-quiz-ui.tsx: -------------------------------------------------------------------------------- 1 | // 'use client'; 2 | 3 | // import { Keypair, PublicKey } from '@solana/web3.js'; 4 | // import { useMemo } from 'react'; 5 | // import { ellipsify } from '../ui/ui-layout'; 6 | // import { ExplorerLink } from '../cluster/cluster-ui'; 7 | // import { 8 | // useSolanaPokerQuizProgram, 9 | // useSolanaPokerQuizProgramAccount, 10 | // } from './solana-poker-quiz-data-access'; 11 | 12 | // export function SolanaPokerQuizCreate() { 13 | // const { initialize } = useSolanaPokerQuizProgram(); 14 | 15 | // return ( 16 | // 23 | // ); 24 | // } 25 | 26 | // export function SolanaPokerQuizList() { 27 | // const { accounts, getProgramAccount } = useSolanaPokerQuizProgram(); 28 | 29 | // if (getProgramAccount.isLoading) { 30 | // return ; 31 | // } 32 | // if (!getProgramAccount.data?.value) { 33 | // return ( 34 | //
35 | // 36 | // Program account not found. Make sure you have deployed the program and 37 | // are on the correct cluster. 38 | // 39 | //
40 | // ); 41 | // } 42 | // return ( 43 | //
44 | // {accounts.isLoading ? ( 45 | // 46 | // ) : accounts.data?.length ? ( 47 | //
48 | // {accounts.data?.map((account) => ( 49 | // 53 | // ))} 54 | //
55 | // ) : ( 56 | //
57 | //

No accounts

58 | // No accounts found. Create one above to get started. 59 | //
60 | // )} 61 | //
62 | // ); 63 | // } 64 | 65 | // function SolanaPokerQuizCard({ account }: { account: PublicKey }) { 66 | // const { 67 | // accountQuery, 68 | // incrementMutation, 69 | // setMutation, 70 | // decrementMutation, 71 | // closeMutation, 72 | // } = useSolanaPokerQuizProgramAccount({ account }); 73 | 74 | // const count = useMemo( 75 | // () => accountQuery.data?.count ?? 0, 76 | // [accountQuery.data?.count] 77 | // ); 78 | 79 | // return accountQuery.isLoading ? ( 80 | // 81 | // ) : ( 82 | //
83 | //
84 | //
85 | //

accountQuery.refetch()} 88 | // > 89 | // {count} 90 | //

91 | //
92 | // 99 | // 119 | // 126 | //
127 | //
128 | //

129 | // 133 | //

134 | // 150 | //
151 | //
152 | //
153 | //
154 | // ); 155 | // } 156 | -------------------------------------------------------------------------------- /web/components/account/account-data-access.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useConnection, useWallet } from '@solana/wallet-adapter-react'; 4 | import { TOKEN_2022_PROGRAM_ID, TOKEN_PROGRAM_ID } from '@solana/spl-token'; 5 | import { 6 | Connection, 7 | LAMPORTS_PER_SOL, 8 | PublicKey, 9 | SystemProgram, 10 | TransactionMessage, 11 | TransactionSignature, 12 | VersionedTransaction, 13 | } from '@solana/web3.js'; 14 | import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; 15 | import toast from 'react-hot-toast'; 16 | import { useTransactionToast } from '../ui/ui-layout'; 17 | 18 | export function useGetBalance({ address }: { address: PublicKey }) { 19 | const { connection } = useConnection(); 20 | 21 | return useQuery({ 22 | queryKey: ['get-balance', { endpoint: connection.rpcEndpoint, address }], 23 | queryFn: () => connection.getBalance(address), 24 | }); 25 | } 26 | 27 | export function useGetSignatures({ address }: { address: PublicKey }) { 28 | const { connection } = useConnection(); 29 | 30 | return useQuery({ 31 | queryKey: ['get-signatures', { endpoint: connection.rpcEndpoint, address }], 32 | queryFn: () => connection.getConfirmedSignaturesForAddress2(address), 33 | }); 34 | } 35 | 36 | export function useGetTokenAccounts({ address }: { address: PublicKey }) { 37 | const { connection } = useConnection(); 38 | 39 | return useQuery({ 40 | queryKey: [ 41 | 'get-token-accounts', 42 | { endpoint: connection.rpcEndpoint, address }, 43 | ], 44 | queryFn: async () => { 45 | const [tokenAccounts, token2022Accounts] = await Promise.all([ 46 | connection.getParsedTokenAccountsByOwner(address, { 47 | programId: TOKEN_PROGRAM_ID, 48 | }), 49 | connection.getParsedTokenAccountsByOwner(address, { 50 | programId: TOKEN_2022_PROGRAM_ID, 51 | }), 52 | ]); 53 | return [...tokenAccounts.value, ...token2022Accounts.value]; 54 | }, 55 | }); 56 | } 57 | 58 | export function useTransferSol({ address }: { address: PublicKey }) { 59 | const { connection } = useConnection(); 60 | const transactionToast = useTransactionToast(); 61 | const wallet = useWallet(); 62 | const client = useQueryClient(); 63 | 64 | return useMutation({ 65 | mutationKey: [ 66 | 'transfer-sol', 67 | { endpoint: connection.rpcEndpoint, address }, 68 | ], 69 | mutationFn: async (input: { destination: PublicKey; amount: number }) => { 70 | let signature: TransactionSignature = ''; 71 | try { 72 | const { transaction, latestBlockhash } = await createTransaction({ 73 | publicKey: address, 74 | destination: input.destination, 75 | amount: input.amount, 76 | connection, 77 | }); 78 | 79 | // Send transaction and await for signature 80 | signature = await wallet.sendTransaction(transaction, connection); 81 | 82 | // Send transaction and await for signature 83 | await connection.confirmTransaction( 84 | { signature, ...latestBlockhash }, 85 | 'confirmed' 86 | ); 87 | 88 | console.log(signature); 89 | return signature; 90 | } catch (error: unknown) { 91 | console.log('error', `Transaction failed! ${error}`, signature); 92 | 93 | return; 94 | } 95 | }, 96 | onSuccess: (signature) => { 97 | if (signature) { 98 | transactionToast(signature); 99 | } 100 | return Promise.all([ 101 | client.invalidateQueries({ 102 | queryKey: [ 103 | 'get-balance', 104 | { endpoint: connection.rpcEndpoint, address }, 105 | ], 106 | }), 107 | client.invalidateQueries({ 108 | queryKey: [ 109 | 'get-signatures', 110 | { endpoint: connection.rpcEndpoint, address }, 111 | ], 112 | }), 113 | ]); 114 | }, 115 | onError: (error) => { 116 | toast.error(`Transaction failed! ${error}`); 117 | }, 118 | }); 119 | } 120 | 121 | export function useRequestAirdrop({ address }: { address: PublicKey }) { 122 | const { connection } = useConnection(); 123 | const transactionToast = useTransactionToast(); 124 | const client = useQueryClient(); 125 | 126 | return useMutation({ 127 | mutationKey: ['airdrop', { endpoint: connection.rpcEndpoint, address }], 128 | mutationFn: async (amount: number = 1) => { 129 | const [latestBlockhash, signature] = await Promise.all([ 130 | connection.getLatestBlockhash(), 131 | connection.requestAirdrop(address, amount * LAMPORTS_PER_SOL), 132 | ]); 133 | 134 | await connection.confirmTransaction( 135 | { signature, ...latestBlockhash }, 136 | 'confirmed' 137 | ); 138 | return signature; 139 | }, 140 | onSuccess: (signature) => { 141 | transactionToast(signature); 142 | return Promise.all([ 143 | client.invalidateQueries({ 144 | queryKey: [ 145 | 'get-balance', 146 | { endpoint: connection.rpcEndpoint, address }, 147 | ], 148 | }), 149 | client.invalidateQueries({ 150 | queryKey: [ 151 | 'get-signatures', 152 | { endpoint: connection.rpcEndpoint, address }, 153 | ], 154 | }), 155 | ]); 156 | }, 157 | }); 158 | } 159 | 160 | async function createTransaction({ 161 | publicKey, 162 | destination, 163 | amount, 164 | connection, 165 | }: { 166 | publicKey: PublicKey; 167 | destination: PublicKey; 168 | amount: number; 169 | connection: Connection; 170 | }): Promise<{ 171 | transaction: VersionedTransaction; 172 | latestBlockhash: { blockhash: string; lastValidBlockHeight: number }; 173 | }> { 174 | // Get the latest blockhash to use in our transaction 175 | const latestBlockhash = await connection.getLatestBlockhash(); 176 | 177 | // Create instructions to send, in this case a simple transfer 178 | const instructions = [ 179 | SystemProgram.transfer({ 180 | fromPubkey: publicKey, 181 | toPubkey: destination, 182 | lamports: amount * LAMPORTS_PER_SOL, 183 | }), 184 | ]; 185 | 186 | // Create a new TransactionMessage with version and compile it to legacy 187 | const messageLegacy = new TransactionMessage({ 188 | payerKey: publicKey, 189 | recentBlockhash: latestBlockhash.blockhash, 190 | instructions, 191 | }).compileToLegacyMessage(); 192 | 193 | // Create a new VersionedTransaction which supports legacy and v0 194 | const transaction = new VersionedTransaction(messageLegacy); 195 | 196 | return { 197 | transaction, 198 | latestBlockhash, 199 | }; 200 | } 201 | -------------------------------------------------------------------------------- /web/components/game-components/Result.js: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useContext } from 'react'; 2 | 3 | // style 4 | import Style from "./PokerGameQuiz.module.css"; 5 | 6 | // context 7 | import PokerGameQuizContext from './PokerGameQuizContext'; 8 | 9 | import { useSolanaPokerQuizProgram } from '../solana-poker-quiz/solana-poker-quiz-data-access'; 10 | 11 | // change to next route - leaderboard 12 | import { useRouter } from 'next/navigation'; 13 | import { useWallet } from '@solana/wallet-adapter-react'; 14 | 15 | import pb from '../../app/pocketbase/pocketbase' 16 | import { BN } from '@coral-xyz/anchor'; 17 | 18 | const Result = () => { 19 | 20 | const { score, history, setIsResultVisible, setScore, setHistory, setRoundCounter, setQuizIsRunning } = useContext(PokerGameQuizContext); 21 | 22 | const [isMounted, setIsMounted] = useState(false); 23 | 24 | const { publicKey } = useWallet(); 25 | 26 | const { updateScore } = useSolanaPokerQuizProgram(); 27 | 28 | // router (first check if the component is mounted) 29 | const router = useRouter(); 30 | 31 | const handleTryAgainClick = () => { 32 | // reset game settings 33 | setRoundCounter(0); 34 | setHistory([]); 35 | setScore(0); 36 | setQuizIsRunning(true); 37 | setIsResultVisible(false); 38 | } 39 | 40 | const handleLeaderboardClick = () => { 41 | // change to leaderboard route 42 | if(router) { 43 | router.push('/leaderboard'); 44 | } 45 | } 46 | 47 | const saveScoreToSolana = async () => { 48 | 49 | updateScore.mutateAsync({ user: publicKey, score: new BN(9) }); 50 | 51 | let existingUser 52 | 53 | try { 54 | existingUser = await pb.collection('users').getFirstListItem( 55 | `wallet_address="${publicKey.toBase58()}"` 56 | ); 57 | console.log(existingUser) 58 | if (existingUser) { 59 | await pb.collection('users').update(existingUser.id, { 60 | score: score 61 | }); 62 | } 63 | } catch (error) { 64 | console.log("Error: ", error); 65 | } 66 | 67 | // save score to solana 68 | console.log("Save score to solana"); 69 | } 70 | 71 | // when components is ounted 72 | useEffect(() => { 73 | 74 | }, []); 75 | 76 | return ( 77 |
78 | < div className={Style.ResultModal}> 79 |
Your score: {score}
80 |
81 | { 82 | history.map((element, index) => ( 83 |
84 |
{index + 1}
85 |
86 | 87 | 88 | 89 | 90 | 91 |
92 |
93 | {/* DID PLAYER WIN? */} 94 | {element.winner === 0 ?
: null} 95 | {/* PLAYER ONE CHANCE */} 96 |
{(element.playerOneChance).replace("~", "")}
97 | {/* TO-DO - WHAT PLAYER SELECTED */} 98 |
99 |
100 | 101 |
102 |
103 | 104 |
105 |
106 |
107 | {/* DID PLAYER WIN? */} 108 | {element.winner === 1 ?
: null} 109 | {/* PLAYER TWO CHANCE */} 110 |
{(element.playerTwoChance).replace("~", "")}
111 | {/* TO-DO - WHAT PLAYER SELECTED */} 112 |
113 |
114 | 115 |
116 |
117 | 118 |
119 |
120 |
{element.score}
121 |
122 | 123 | )) 124 | } 125 | 126 |
127 |
128 | 129 | 130 | 131 | 132 |
133 |
134 | 135 | ); 136 | } 137 | 138 | export default Result; -------------------------------------------------------------------------------- /web/components/cluster/cluster-ui.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useConnection } from '@solana/wallet-adapter-react'; 4 | import { IconTrash } from '@tabler/icons-react'; 5 | import { useQuery } from '@tanstack/react-query'; 6 | import { ReactNode, useState } from 'react'; 7 | import { AppModal } from '../ui/ui-layout'; 8 | import { ClusterNetwork, useCluster } from './cluster-data-access'; 9 | import { Connection } from '@solana/web3.js'; 10 | 11 | export function ExplorerLink({ 12 | path, 13 | label, 14 | className, 15 | }: { 16 | path: string; 17 | label: string; 18 | className?: string; 19 | }) { 20 | const { getExplorerUrl } = useCluster(); 21 | return ( 22 | 28 | {label} 29 | 30 | ); 31 | } 32 | 33 | export function ClusterChecker({ children }: { children: ReactNode }) { 34 | const { cluster } = useCluster(); 35 | const { connection } = useConnection(); 36 | 37 | const query = useQuery({ 38 | queryKey: ['version', { cluster, endpoint: connection.rpcEndpoint }], 39 | queryFn: () => connection.getVersion(), 40 | retry: 1, 41 | }); 42 | if (query.isLoading) { 43 | return null; 44 | } 45 | if (query.isError || !query.data) { 46 | return ( 47 |
48 | 49 | Error connecting to cluster {cluster.name} 50 | 51 | 57 |
58 | ); 59 | } 60 | return children; 61 | } 62 | 63 | export function ClusterUiSelect() { 64 | const { clusters, setCluster, cluster } = useCluster(); 65 | return ( 66 |
67 | 70 |
    74 | {clusters.map((item) => ( 75 |
  • 76 | 84 |
  • 85 | ))} 86 |
87 |
88 | ); 89 | } 90 | 91 | export function ClusterUiModal({ 92 | hideModal, 93 | show, 94 | }: { 95 | hideModal: () => void; 96 | show: boolean; 97 | }) { 98 | const { addCluster } = useCluster(); 99 | const [name, setName] = useState(''); 100 | const [network, setNetwork] = useState(); 101 | const [endpoint, setEndpoint] = useState(''); 102 | 103 | return ( 104 | { 109 | try { 110 | new Connection(endpoint); 111 | if (name) { 112 | addCluster({ name, network, endpoint }); 113 | hideModal(); 114 | } else { 115 | console.log('Invalid cluster name'); 116 | } 117 | } catch { 118 | console.log('Invalid cluster endpoint'); 119 | } 120 | }} 121 | submitLabel="Save" 122 | > 123 | setName(e.target.value)} 129 | /> 130 | setEndpoint(e.target.value)} 136 | /> 137 | 147 | 148 | ); 149 | } 150 | 151 | export function ClusterUiTable() { 152 | const { clusters, setCluster, deleteCluster } = useCluster(); 153 | return ( 154 |
155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | {clusters.map((item) => ( 164 | 165 | 188 | 200 | 201 | ))} 202 | 203 |
Name/ Network / EndpointActions
166 |
167 | 168 | {item?.active ? ( 169 | item.name 170 | ) : ( 171 | 178 | )} 179 | 180 |
181 | 182 | Network: {item.network ?? 'custom'} 183 | 184 |
185 | {item.endpoint} 186 |
187 |
189 | 199 |
204 |
205 | ); 206 | } 207 | -------------------------------------------------------------------------------- /web/components/game-components/PokerGameQuiz.module.css: -------------------------------------------------------------------------------- 1 | .GameContainer { 2 | display: flex; 3 | flex-direction: column; 4 | height: 100vh; 5 | width: 100vw; 6 | position: relative; 7 | overflow-y: hidden; 8 | 9 | /* HIDE SCROLLBAR */ 10 | -ms-overflow-style: none; /* IE and Edge */ 11 | scrollbar-width: none; /* Firefox */ 12 | } 13 | 14 | /* Hide scrollbar for Chrome, Safari and Opera */ 15 | .GameContainer::-webkit-scrollbar { 16 | display: none; 17 | } 18 | 19 | /* Hide scrollbar for IE, Edge and Firefox */ 20 | .GameContainer { 21 | } 22 | 23 | .GameHeader { 24 | display: flex; 25 | justify-content: flex-end; 26 | margin-left: 5em; 27 | margin-right: 5em; 28 | align-items: center; 29 | color: white; 30 | font-size: 2em; 31 | height: 5%; 32 | } 33 | 34 | .TimeCounter { 35 | font-size: 3.5em; 36 | align-self: center; 37 | } 38 | 39 | .Question { 40 | align-self: center; 41 | font-size: 2.5em; 42 | color: #ffffff; 43 | } 44 | 45 | .TableContainer { 46 | position: relative; 47 | align-self: center; 48 | height: 40%; 49 | } 50 | 51 | .TableImg { 52 | width: 100%; 53 | height: 100%; 54 | object-fit: contain; 55 | } 56 | 57 | /* ALL 5 CARDS */ 58 | .TableCardsContainer { 59 | position: absolute; 60 | top: 50%; 61 | left: 50%; 62 | transform: translate(-50%, -50%); 63 | width: 100%; 64 | height: 35%; 65 | display: flex; 66 | justify-content: center; 67 | align-items: center; 68 | } 69 | 70 | /* SINGLE CARD */ 71 | .TableCard { 72 | height: 100%; 73 | margin: 1em; 74 | } 75 | 76 | .PlayerContainer { 77 | display: flex; 78 | flex-direction: row; 79 | align-items: center; 80 | justify-content: center; 81 | margin-top: 1rem; 82 | height: 20%; 83 | } 84 | 85 | .OptionContainer { 86 | display: flex; 87 | height: 100%; 88 | flex-direction: row; 89 | align-items: center; 90 | margin-top: 1rem; 91 | background-color: #02228268; 92 | margin: 1em; 93 | padding: 20px; 94 | border-radius: 20px; 95 | height: 100%; 96 | } 97 | 98 | 99 | .OptionContainer:hover { 100 | display: flex; 101 | flex-direction: row; 102 | background-color: #0339db68; 103 | padding: 20px; 104 | border-radius: 20px; 105 | margin: 1em; 106 | cursor: pointer; 107 | } 108 | 109 | .PlayerChance { 110 | font-size: 2em; 111 | color: white; 112 | } 113 | 114 | .OptionCardLeft { 115 | margin-right: 0.5rem; 116 | width: auto; 117 | height: 100%; 118 | object-fit: contain; 119 | } 120 | 121 | .OptionCardRight { 122 | margin-left: 0.5rem; 123 | width: auto; 124 | height: 100%; 125 | object-fit: contain; 126 | } 127 | 128 | .WelcomeTitle { 129 | font-size: 3em; 130 | color: white; 131 | text-align: center; 132 | margin-bottom: 0.3em; 133 | } 134 | 135 | .StartQuizContainer{ 136 | display: flex; 137 | flex-direction: column; 138 | justify-content: center; 139 | align-items: center; 140 | position: absolute; 141 | height: 100%; 142 | width: 100%; 143 | background-color: #000000cc; 144 | } 145 | 146 | .StartQuizButton { 147 | background-color: #410aae; 148 | padding: 0.3em 0.5em; 149 | border-radius: 0.5em; 150 | font-size: 3em; 151 | color: white; 152 | } 153 | 154 | .ResultContainer { 155 | position: absolute; 156 | width: 100%; 157 | height: 100%; 158 | top: 0; 159 | left: 0; 160 | right: 0; 161 | bottom: 0; 162 | background-color: #00000044; 163 | display: flex; 164 | align-items: center; 165 | justify-content: center; 166 | 167 | } 168 | 169 | /* RESULT MODAL */ 170 | 171 | .ResultModal { 172 | display: flex; 173 | flex-direction: column; 174 | width: 40%; 175 | height: 70%; 176 | background-color: #3b07a2ef; 177 | border-radius: 30px; 178 | padding: 1em; 179 | /* ANIMATION */ 180 | transform: scale(0); 181 | animation: spawn 0.3s ease-out forwards; 182 | } 183 | 184 | @keyframes spawn { 185 | 0% { 186 | opacity: 0; 187 | transform: scale(0); 188 | } 189 | 100% { 190 | opacity: 1; 191 | transform: scale(1); 192 | } 193 | } 194 | 195 | .ScoreTitle { 196 | font-size: 2.8em; 197 | color: white; 198 | text-align: center; 199 | } 200 | 201 | .HandsResults { 202 | display: flex; 203 | flex-direction: column; 204 | width: 100%; 205 | height: 100%; 206 | max-height: 100%; 207 | overflow-y: auto; 208 | justify-content: flex-start; 209 | padding: 20px; 210 | box-shadow: inset 0 10px 10px -5px rgba(0, 0, 0, 0.15), /* Top shadow */ 211 | inset 0 -10px 10px -5px rgba(0, 0, 0, 0.15); /* Bottom shadow */ 212 | } 213 | 214 | /* Hide scrollbar for Chrome, Safari and Opera */ 215 | .HandsResults::-webkit-scrollbar { 216 | display: none; 217 | } 218 | 219 | /* Hide scrollbar for IE, Edge and Firefox */ 220 | .HandsResults { 221 | -ms-overflow-style: none; /* IE and Edge */ 222 | scrollbar-width: none; /* Firefox */ 223 | } 224 | 225 | .HandContainer { 226 | display: flex; 227 | flex-direction: row; 228 | width: 100%; 229 | height: auto; 230 | align-items: center; 231 | flex: 1; 232 | margin-bottom: 2em; 233 | } 234 | 235 | .HandCard { 236 | object-fit: contain; 237 | flex-grow: 1; 238 | max-width: 100%; 239 | height: auto; 240 | width: 1em; 241 | margin: 2px; 242 | } 243 | 244 | .HandCardPlayer { 245 | 246 | } 247 | 248 | .PlayerCardResult { 249 | display: flex; 250 | flex-direction: row; 251 | align-items: center; 252 | position: relative; 253 | } 254 | 255 | .ResultRound { 256 | flex: 1; 257 | font-size: 2.2em; 258 | } 259 | 260 | .ResultScore { 261 | flex: 1; 262 | font-size: 2.2em; 263 | margin-left: 1em; 264 | } 265 | 266 | .TableCards { 267 | flex: 5; 268 | display: flex; 269 | flex-direction: row; 270 | width: 100%; 271 | margin-right: 1em; 272 | } 273 | 274 | .PlayerOneCards { 275 | flex: 2; 276 | display: flex; 277 | flex-direction: row; 278 | width: 100%; 279 | margin-right: 1em; 280 | position: relative; 281 | } 282 | 283 | .PlayerTwoCards { 284 | flex: 2; 285 | display: flex; 286 | flex-direction: row; 287 | width: 100%; 288 | position: relative; 289 | } 290 | 291 | .Correct { 292 | background-color: #126f0872; 293 | position: absolute; 294 | height: 100%; 295 | width: 100%; 296 | border-radius: 5px; 297 | border: 2px solid #126f08; 298 | z-index: 100; 299 | 300 | } 301 | 302 | .ResultPlayerChance { 303 | position: absolute; 304 | height: 100%; 305 | width: 100%; 306 | bottom: -65%; 307 | left: 0; 308 | display: grid; 309 | place-items: center; 310 | text-align: center; 311 | color: #ffffff; 312 | z-index: 100; 313 | } 314 | 315 | .ResultButtonsContainer { 316 | display: flex; 317 | flex-direction: row; 318 | justify-content: center; 319 | align-items: center; 320 | margin-top: 1em; 321 | } 322 | 323 | 324 | .LeaderboardButton, 325 | .TryAgainButton, 326 | .SaveToSolanaButton { 327 | width: 150px; 328 | height: 50px; 329 | background-color: #250760; 330 | border-radius: 10px; 331 | font-size: 1.2em; 332 | color: white; 333 | margin: 0 0.5em; 334 | display: flex; 335 | justify-content: center; 336 | align-items: center; 337 | text-align: center; 338 | transition: background-color 0.3s ease; 339 | } 340 | 341 | .LeaderboardButton:hover, 342 | .TryAgainButton:hover, 343 | .SaveToSolanaButton:hover { 344 | background-color: #430eab; 345 | cursor: pointer; 346 | } -------------------------------------------------------------------------------- /web/public/hero.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | Created by potrace 1.10, written by Peter Selinger 2001-2011 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /web/components/game-components/PokerGameQuiz.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { useState, useEffect } from 'react'; 4 | 5 | // context 6 | import PokerGameQuizContext from './PokerGameQuizContext'; 7 | 8 | // my components 9 | import Table from "./Table"; 10 | import Player from "./Player"; 11 | 12 | //style 13 | import Style from "./PokerGameQuiz.module.css"; 14 | 15 | // poker odds 16 | import { TexasHoldem } from 'poker-odds-calc'; 17 | 18 | // game 19 | import { deck } from './gameData.js' // importing deck tha 20 | 21 | import Timer from './Timer/Timer'; 22 | import { set } from '@coral-xyz/anchor/dist/cjs/utils/features'; 23 | import Result from './Result'; 24 | 25 | import UsernameModal from './UsernameModal'; 26 | 27 | import pocketbase from '../../app/pocketbase/pocketbase' 28 | import { useWallet } from '@solana/wallet-adapter-react'; 29 | import toast from 'react-hot-toast'; 30 | 31 | const PokerGameQuiz = () => { 32 | // shuffle deck 33 | 34 | // deck used for calculating odds - table cards 35 | let oddsDeckTable = ['', '', '', '', '']; 36 | 37 | // deck used for calculating odds - players cards 38 | let oddsDeckPlayers = ['', '', '', '']; 39 | 40 | // deck used for displaying cards on the table 41 | const [displayedDeck, setDisplayedDeck] = useState(['', '', '', '', '', '', '', '', '']); 42 | 43 | // number of cards on the table 44 | let numberOfCards = 0; 45 | 46 | const [startQuizIsVisible, setStartQuizIsVisible] = useState(true); 47 | const [isResultVisible, setIsResultVisible] = useState(false); 48 | 49 | const [isUsernameModalVisible, setIsUsernameModalVisible] = useState(false); 50 | const wallet = useWallet(); 51 | 52 | // username 53 | const [username, setUsername] = useState(''); 54 | 55 | //game data 56 | const [roundCounter, setRoundCounter] = useState(1); 57 | const [score, setScore] = useState(0); 58 | const [timeLeft, setTimeLeft] = useState(0); 59 | 60 | // history 61 | const [history, setHistory] = useState([]); 62 | // default point 63 | const points = 99; 64 | 65 | const result ={ 66 | round: 0, 67 | playerOneChance: 0.5, 68 | playerTwoChance: 0.5, 69 | correct: 1, 70 | winner: 0, 71 | score: 1, 72 | tableCards: ['Kh', 'Kc', '8h', '8s', '8c'], 73 | playerOneCards: ['Ks', '8s'], 74 | playerTwoCards: ['Kh', '8c'] 75 | } 76 | 77 | 78 | // saving player's odds 79 | const [playerOneChance, setPlayerOneChance] = useState(0); 80 | const [playerTwoChance, setPlayerTwoChance] = useState(0); 81 | 82 | // game state 83 | const [quizIsRunning, setQuizIsRunning] = useState(true); 84 | 85 | const handleUsernameSubmit = async (username) => { 86 | 87 | // Push username database 88 | await pocketbase.collection('users').create({ 89 | username: username, 90 | score: 0, 91 | wallet_address: wallet.publicKey.toBase58(), 92 | }); 93 | 94 | setUsername(username); 95 | 96 | setIsUsernameModalVisible(false); 97 | } 98 | 99 | const startQuiz = () => { 100 | 101 | if (!wallet.publicKey) { 102 | toast.error('Please connect your wallet to start the quiz'); 103 | return 104 | } 105 | 106 | // quiz is running - state 107 | setQuizIsRunning(true); 108 | 109 | setStartQuizIsVisible(false); 110 | // initialize game 111 | setRoundCounter(0); 112 | 113 | // reset score to 0 114 | setScore(0); 115 | 116 | // generate first hand - round 117 | generateHand(); 118 | 119 | // saving round number 120 | result.round = roundCounter; 121 | } 122 | 123 | const generateDeck = () => { 124 | // shuffle deck 125 | const tempDeck = deck.sort(() => Math.random() - 0.5); 126 | 127 | // generate number of table cards 128 | const numbers = [0, 3, 4, 5]; 129 | const randomIndex = Math.floor(Math.random() * numbers.length); 130 | numberOfCards = numbers[randomIndex]; 131 | 132 | // local state for displayed deck 133 | let updatedDisplayedDeck = ['', '', '', '', '', '', '', '', '']; 134 | 135 | // displayed deck - values are used to display cards on the table 136 | // odds deck - values are used to calculate odds 137 | if(numberOfCards === 3){ 138 | updatedDisplayedDeck = [ 139 | tempDeck[0], tempDeck[1], tempDeck[2], 'card_back', 'card_back', 140 | tempDeck[5], tempDeck[6], tempDeck[7], tempDeck[8] 141 | ]; 142 | 143 | // setting up result object that is later added to history - used to display all results 144 | result.tableCards = [tempDeck[0], tempDeck[1], tempDeck[2], 'card_back', 'card_back']; 145 | result.playerOneCards = [tempDeck[5], tempDeck[6]]; 146 | result.playerTwoCards = [tempDeck[7], tempDeck[8]]; 147 | 148 | 149 | oddsDeckTable.length = 3; 150 | oddsDeckTable[0] = tempDeck[0]; 151 | oddsDeckTable[1] = tempDeck[1]; 152 | oddsDeckTable[2] = tempDeck[2]; 153 | 154 | oddsDeckPlayers[0] = tempDeck[5]; 155 | oddsDeckPlayers[1] = tempDeck[6]; 156 | oddsDeckPlayers[2] = tempDeck[7]; 157 | oddsDeckPlayers[3] = tempDeck[8]; 158 | 159 | } else if(numberOfCards === 4){ 160 | updatedDisplayedDeck = [ 161 | tempDeck[0], tempDeck[1], tempDeck[2], tempDeck[3], 'card_back', 162 | tempDeck[5], tempDeck[6], tempDeck[7], tempDeck[8] 163 | ]; 164 | 165 | // setting up result object that is later added to history - used to display all results 166 | result.tableCards = [tempDeck[0], tempDeck[1], tempDeck[2], tempDeck[3], 'card_back']; 167 | result.playerOneCards = [tempDeck[5], tempDeck[6]]; 168 | result.playerTwoCards = [tempDeck[7], tempDeck[8]]; 169 | 170 | oddsDeckTable.length = 4; 171 | oddsDeckTable[0] = tempDeck[0]; 172 | oddsDeckTable[1] = tempDeck[1]; 173 | oddsDeckTable[2] = tempDeck[2]; 174 | oddsDeckTable[3] = tempDeck[3]; 175 | 176 | oddsDeckPlayers[0] = tempDeck[5]; 177 | oddsDeckPlayers[1] = tempDeck[6]; 178 | oddsDeckPlayers[2] = tempDeck[7]; 179 | oddsDeckPlayers[3] = tempDeck[8]; 180 | 181 | } else if(numberOfCards === 5){ 182 | updatedDisplayedDeck = [ 183 | tempDeck[0], tempDeck[1], tempDeck[2], tempDeck[3], tempDeck[4], 184 | tempDeck[5], tempDeck[6], tempDeck[7], tempDeck[8] 185 | ]; 186 | 187 | // setting up result object that is later added to history - used to display all results 188 | result.tableCards = [tempDeck[0], tempDeck[1], tempDeck[2], tempDeck[3], tempDeck[4]]; 189 | result.playerOneCards = [tempDeck[5], tempDeck[6]]; 190 | result.playerTwoCards = [tempDeck[7], tempDeck[8]]; 191 | 192 | oddsDeckTable.length = 5; 193 | oddsDeckTable[0] = tempDeck[0]; 194 | oddsDeckTable[1] = tempDeck[1]; 195 | oddsDeckTable[2] = tempDeck[2]; 196 | oddsDeckTable[3] = tempDeck[3]; 197 | oddsDeckTable[4] = tempDeck[4]; 198 | 199 | oddsDeckPlayers[0] = tempDeck[5]; 200 | oddsDeckPlayers[1] = tempDeck[6]; 201 | oddsDeckPlayers[2] = tempDeck[7]; 202 | oddsDeckPlayers[3] = tempDeck[8]; 203 | } else if(numberOfCards === 0){ 204 | updatedDisplayedDeck = [ 205 | 'card_back', 'card_back', 'card_back', 'card_back', 'card_back', 206 | tempDeck[5], tempDeck[6], tempDeck[7], tempDeck[8] 207 | ]; 208 | 209 | // setting up result object that is later added to history - used to display all results 210 | result.tableCards = ['card_back', 'card_back', 'card_back', 'card_back', 'card_back']; 211 | result.playerOneCards = [tempDeck[5], tempDeck[6]]; 212 | result.playerTwoCards = [tempDeck[7], tempDeck[8]]; 213 | 214 | oddsDeckTable.length = 0; 215 | 216 | oddsDeckPlayers[0] = tempDeck[5]; 217 | oddsDeckPlayers[1] = tempDeck[6]; 218 | oddsDeckPlayers[2] = tempDeck[7]; 219 | oddsDeckPlayers[3] = tempDeck[8]; 220 | } 221 | 222 | // Update the displayedDeck state using setDisplayedDeck 223 | setDisplayedDeck(updatedDisplayedDeck); 224 | }; 225 | 226 | const generateHand = () => { 227 | // generate new deck 228 | generateDeck(); 229 | 230 | // calculate odds 231 | const Table = new TexasHoldem(); 232 | Table 233 | .addPlayer([oddsDeckPlayers[0], oddsDeckPlayers[1]]) 234 | .addPlayer([oddsDeckPlayers[2], oddsDeckPlayers[3]]) 235 | 236 | if(numberOfCards != 0){ 237 | Table.setBoard(oddsDeckTable); 238 | } 239 | const Result = Table.calculate(); 240 | 241 | 242 | Result.getPlayers().forEach(player => { 243 | console.log(`${player.getName()} - ${player.getHand()} - Wins: ${player.getWinsPercentageString()} - Ties: ${player.getTiesPercentageString()}`); 244 | // set player 1 chance 245 | if(player.getName() === 'Player #1'){ 246 | setPlayerOneChance(player.getWinsPercentageString()); 247 | } 248 | // set player 2 chance 249 | else if(player.getName() === 'Player #2') { 250 | setPlayerTwoChance(player.getWinsPercentageString()); 251 | } 252 | // handle error 253 | else { 254 | console.log('error - wrong name'); 255 | } 256 | 257 | }); 258 | } 259 | 260 | const nextRound = () => { 261 | const tempRoundCounter = roundCounter; 262 | if(roundCounter != 10){ 263 | // increase round counter 264 | setRoundCounter(roundCounter + 1); 265 | 266 | let pomRoundCounter = roundCounter + 1; 267 | if(pomRoundCounter === 10){ 268 | setRoundCounter(9); 269 | } 270 | 271 | // generate new deck 272 | generateHand(); 273 | 274 | } 275 | // end quiz and show result 276 | if(tempRoundCounter === 9) { 277 | // set score - time points 278 | setScore(score + timeLeft*10); 279 | 280 | // show result 281 | 282 | setIsResultVisible(true); 283 | 284 | // set quiz state to false and stop timer 285 | setQuizIsRunning(false); 286 | } 287 | } 288 | 289 | const addNewHistoryElement = (newElement) => { 290 | // adding new element 291 | setHistory((prevHistory) => [...prevHistory, newElement]); 292 | } 293 | 294 | 295 | useEffect(() => { 296 | 297 | const checkAddressInPocketBase = async () => { 298 | // Check in pocket base is there username associated with the wallet address currently in use 299 | try { 300 | 301 | const records = await pocketbase.collection('users').getFirstListItem( 302 | `wallet_address='${wallet.publicKey.toBase58()}'` 303 | ); 304 | 305 | setUsername(records.username); 306 | 307 | setIsUsernameModalVisible(false); 308 | } catch (err) { 309 | // setError('Failed to fetch users. Please try again.'); 310 | setIsUsernameModalVisible(true); 311 | } 312 | } 313 | 314 | if (wallet.publicKey){ 315 | checkAddressInPocketBase(); 316 | } 317 | 318 | }, [wallet.publicKey, startQuizIsVisible]); // Moved the closing bracket for the callback function here 319 | 320 | 321 | return ( 322 | 341 |
342 |
343 |
{`${roundCounter+1}/10`}
344 |
345 |
346 | {!startQuizIsVisible && } 347 |
348 |
Who has higher chance to win?
349 | 350 | 351 | 352 | {startQuizIsVisible && 353 |
354 |
Welcome {username}!
355 | 356 |
357 | } 358 | 359 | {isResultVisible && } 360 | {isUsernameModalVisible && } 361 | 362 | 363 | 364 | ); 365 | } 366 | 367 | export default PokerGameQuiz; -------------------------------------------------------------------------------- /web/components/account/account-ui.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useWallet } from '@solana/wallet-adapter-react'; 4 | import { LAMPORTS_PER_SOL, PublicKey } from '@solana/web3.js'; 5 | import { IconRefresh } from '@tabler/icons-react'; 6 | import { useQueryClient } from '@tanstack/react-query'; 7 | import { useMemo, useState } from 'react'; 8 | import { AppModal, ellipsify } from '../ui/ui-layout'; 9 | import { useCluster } from '../cluster/cluster-data-access'; 10 | import { ExplorerLink } from '../cluster/cluster-ui'; 11 | import { 12 | useGetBalance, 13 | useGetSignatures, 14 | useGetTokenAccounts, 15 | useRequestAirdrop, 16 | useTransferSol, 17 | } from './account-data-access'; 18 | 19 | export function AccountBalance({ address }: { address: PublicKey }) { 20 | const query = useGetBalance({ address }); 21 | 22 | return ( 23 |
24 |

query.refetch()} 27 | > 28 | {query.data ? : '...'} SOL 29 |

30 |
31 | ); 32 | } 33 | export function AccountChecker() { 34 | const { publicKey } = useWallet(); 35 | if (!publicKey) { 36 | return null; 37 | } 38 | return ; 39 | } 40 | export function AccountBalanceCheck({ address }: { address: PublicKey }) { 41 | const { cluster } = useCluster(); 42 | const mutation = useRequestAirdrop({ address }); 43 | const query = useGetBalance({ address }); 44 | 45 | if (query.isLoading) { 46 | return null; 47 | } 48 | if (query.isError || !query.data) { 49 | return ( 50 |
51 | 52 | You are connected to {cluster.name} but your account 53 | is not found on this cluster. 54 | 55 | 63 |
64 | ); 65 | } 66 | return null; 67 | } 68 | 69 | export function AccountButtons({ address }: { address: PublicKey }) { 70 | const wallet = useWallet(); 71 | const { cluster } = useCluster(); 72 | const [showAirdropModal, setShowAirdropModal] = useState(false); 73 | const [showReceiveModal, setShowReceiveModal] = useState(false); 74 | const [showSendModal, setShowSendModal] = useState(false); 75 | 76 | return ( 77 |
78 | setShowAirdropModal(false)} 80 | address={address} 81 | show={showAirdropModal} 82 | /> 83 | setShowReceiveModal(false)} 87 | /> 88 | setShowSendModal(false)} 92 | /> 93 |
94 | 101 | 108 | 114 |
115 |
116 | ); 117 | } 118 | 119 | export function AccountTokens({ address }: { address: PublicKey }) { 120 | const [showAll, setShowAll] = useState(false); 121 | const query = useGetTokenAccounts({ address }); 122 | const client = useQueryClient(); 123 | const items = useMemo(() => { 124 | if (showAll) return query.data; 125 | return query.data?.slice(0, 5); 126 | }, [query.data, showAll]); 127 | 128 | return ( 129 |
130 |
131 |
132 |

Token Accounts

133 |
134 | {query.isLoading ? ( 135 | 136 | ) : ( 137 | 148 | )} 149 |
150 |
151 |
152 | {query.isError && ( 153 |
154 |           Error: {query.error?.message.toString()}
155 |         
156 | )} 157 | {query.isSuccess && ( 158 |
159 | {query.data.length === 0 ? ( 160 |
No token accounts found.
161 | ) : ( 162 |
163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | {items?.map(({ account, pubkey }) => ( 172 | 173 | 183 | 193 | 198 | 199 | ))} 200 | 201 | {(query.data?.length ?? 0) > 5 && ( 202 | 203 | 211 | 212 | )} 213 | 214 |
Public KeyMintBalance
174 |
175 | 176 | 180 | 181 |
182 |
184 |
185 | 186 | 190 | 191 |
192 |
194 | 195 | {account.data.parsed.info.tokenAmount.uiAmount} 196 | 197 |
204 | 210 |
215 | )} 216 |
217 | )} 218 | 219 | ); 220 | } 221 | 222 | export function AccountTransactions({ address }: { address: PublicKey }) { 223 | const query = useGetSignatures({ address }); 224 | const [showAll, setShowAll] = useState(false); 225 | 226 | const items = useMemo(() => { 227 | if (showAll) return query.data; 228 | return query.data?.slice(0, 5); 229 | }, [query.data, showAll]); 230 | 231 | return ( 232 |
233 |
234 |

Transaction History

235 |
236 | {query.isLoading ? ( 237 | 238 | ) : ( 239 | 245 | )} 246 |
247 |
248 | {query.isError && ( 249 |
250 |           Error: {query.error?.message.toString()}
251 |         
252 | )} 253 | {query.isSuccess && ( 254 |
255 | {query.data.length === 0 ? ( 256 |
No transactions found.
257 | ) : ( 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | {items?.map((item) => ( 269 | 270 | 276 | 282 | 285 | 297 | 298 | ))} 299 | {(query.data?.length ?? 0) > 5 && ( 300 | 301 | 309 | 310 | )} 311 | 312 |
SignatureSlotBlock TimeStatus
271 | 275 | 277 | 281 | 283 | {new Date((item.blockTime ?? 0) * 1000).toISOString()} 284 | 286 | {item.err ? ( 287 |
291 | Failed 292 |
293 | ) : ( 294 |
Success
295 | )} 296 |
302 | 308 |
313 | )} 314 |
315 | )} 316 |
317 | ); 318 | } 319 | 320 | function BalanceSol({ balance }: { balance: number }) { 321 | return ( 322 | {Math.round((balance / LAMPORTS_PER_SOL) * 100000) / 100000} 323 | ); 324 | } 325 | 326 | function ModalReceive({ 327 | hide, 328 | show, 329 | address, 330 | }: { 331 | hide: () => void; 332 | show: boolean; 333 | address: PublicKey; 334 | }) { 335 | return ( 336 | 337 |

Receive assets by sending them to your public key:

338 | {address.toString()} 339 |
340 | ); 341 | } 342 | 343 | function ModalAirdrop({ 344 | hide, 345 | show, 346 | address, 347 | }: { 348 | hide: () => void; 349 | show: boolean; 350 | address: PublicKey; 351 | }) { 352 | const mutation = useRequestAirdrop({ address }); 353 | const [amount, setAmount] = useState('2'); 354 | 355 | return ( 356 | mutation.mutateAsync(parseFloat(amount)).then(() => hide())} 363 | > 364 | setAmount(e.target.value)} 373 | /> 374 | 375 | ); 376 | } 377 | 378 | function ModalSend({ 379 | hide, 380 | show, 381 | address, 382 | }: { 383 | hide: () => void; 384 | show: boolean; 385 | address: PublicKey; 386 | }) { 387 | const wallet = useWallet(); 388 | const mutation = useTransferSol({ address }); 389 | const [destination, setDestination] = useState(''); 390 | const [amount, setAmount] = useState('1'); 391 | 392 | if (!address || !wallet.sendTransaction) { 393 | return
Wallet not connected
; 394 | } 395 | 396 | return ( 397 | { 404 | mutation 405 | .mutateAsync({ 406 | destination: new PublicKey(destination), 407 | amount: parseFloat(amount), 408 | }) 409 | .then(() => hide()); 410 | }} 411 | > 412 | setDestination(e.target.value)} 419 | /> 420 | setAmount(e.target.value)} 429 | /> 430 | 431 | ); 432 | } 433 | --------------------------------------------------------------------------------