diff --git a/client/src/App.tsx b/client/src/App.tsx index e0a1fb0a..33bcdb70 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -15,6 +15,7 @@ import Home from "./pages/Home"; import Leaderboard from "./pages/Leaderboard"; import Lobby from "./pages/Lobby"; import { POLICIES } from "./lib/constants"; +import Profile from "./pages/Profile"; const options: ControllerOptions = { rpc: "https://api.cartridge.gg/x/mancala-alpha-v8/katana", @@ -75,6 +76,7 @@ export default function App() { } /> } /> } /> + } /> } /> diff --git a/client/src/assets/avatar.png b/client/src/assets/avatar.png new file mode 100644 index 00000000..3a100446 Binary files /dev/null and b/client/src/assets/avatar.png differ diff --git a/client/src/assets/image-add.svg b/client/src/assets/image-add.svg new file mode 100644 index 00000000..d2fcb372 --- /dev/null +++ b/client/src/assets/image-add.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/client/src/assets/long-box.png b/client/src/assets/long-box.png new file mode 100644 index 00000000..7dd33bd3 Binary files /dev/null and b/client/src/assets/long-box.png differ diff --git a/client/src/assets/telegram.png b/client/src/assets/telegram.png new file mode 100644 index 00000000..b37e0e23 Binary files /dev/null and b/client/src/assets/telegram.png differ diff --git a/client/src/assets/twitter.png b/client/src/assets/twitter.png new file mode 100644 index 00000000..4bc52aac Binary files /dev/null and b/client/src/assets/twitter.png differ diff --git a/client/src/assets/wide-box.png b/client/src/assets/wide-box.png new file mode 100644 index 00000000..de8d4821 Binary files /dev/null and b/client/src/assets/wide-box.png differ diff --git a/client/src/components/header.tsx b/client/src/components/header.tsx index 5ab9ca29..4c375b5a 100644 --- a/client/src/components/header.tsx +++ b/client/src/components/header.tsx @@ -19,7 +19,7 @@ import { Link } from "react-router-dom"; import { constants } from "starknet"; import { Button } from "@material-tailwind/react"; import { UserIcon, ChevronDownIcon } from "@heroicons/react/24/solid"; -import { useDojo } from "@/dojo/useDojo"; +// import { useDojo } from "@/dojo/useDojo"; import clsx from "clsx"; import controllerSvg from "../assets/controller.svg"; import connectB from "../assets/connect.svg"; @@ -75,7 +75,13 @@ export default function Header() { setPlaying(!isPlaying); }; - const { account } = useDojo(); + // const { account } = useDojo(); + + const account = { + account: { + address: "0x05e01dB693CBF7461a016343042786DaC5A6000104813cF134a1E8B1D0a6810b" + } + } const { data, startPolling } = useFetchModelsForHeaderQuery(); startPolling(1000); @@ -185,36 +191,41 @@ export default function Header() { {isDropdownClose && (
- - - - Profile - - - - - - Lobby - - - - - - Leaderboard - - - + + + + + + + + +
)} @@ -242,28 +253,27 @@ export default function Header() { {isDropdownOpen && (
- + + +
)} diff --git a/client/src/components/lobby/duels.tsx b/client/src/components/lobby/duels.tsx index 97130fab..ca41ed6d 100644 --- a/client/src/components/lobby/duels.tsx +++ b/client/src/components/lobby/duels.tsx @@ -60,7 +60,7 @@ export default function Duels({ games, transactions, loading }: { games: any, tr return } else { - if (data.length === 0) { + if (data?.length === 0) { return } else { @@ -105,8 +105,8 @@ export default function Duels({ games, transactions, loading }: { games: any, tr - {data.map((item: any, index: number) => { - const isLast = index === data.length - 1; + {data?.map((item: any, index: number) => { + const isLast = index === data?.length - 1; const date = new Date(item.date) return ( diff --git a/client/src/components/profile/empty-game-history.tsx b/client/src/components/profile/empty-game-history.tsx new file mode 100644 index 00000000..c3bdbf21 --- /dev/null +++ b/client/src/components/profile/empty-game-history.tsx @@ -0,0 +1,13 @@ +import mancala_duels_logo from '../../assets/mancala_duels.png'; + +export default function EmptyGameHistory({ id }: { id: string }) { + return ( +
+
+ duels +
{id == "all" ? "You have no duels" : id == "won" ? "You have won no duels" : "You have lost no duels"}
+ +
+
+ ) +} \ No newline at end of file diff --git a/client/src/components/profile/game-history-skeleton.tsx b/client/src/components/profile/game-history-skeleton.tsx new file mode 100644 index 00000000..f012f47c --- /dev/null +++ b/client/src/components/profile/game-history-skeleton.tsx @@ -0,0 +1,36 @@ +import clsx from "clsx"; + +export function GameHistorySkeleton() { + const arr = Array(6).fill(null); + return ( + <> + {arr.map((_, index) => { + const isLast = index === arr.length - 1; + return ( + + +
+
+
+
+ + +
+
+
+
+ + +
+ + +
+ + + ) + + })} + + + ); +} \ No newline at end of file diff --git a/client/src/components/profile/game-history.tsx b/client/src/components/profile/game-history.tsx new file mode 100644 index 00000000..5b65b739 --- /dev/null +++ b/client/src/components/profile/game-history.tsx @@ -0,0 +1,174 @@ +import { duels_header } from "@/lib/constants.ts"; +import { Card, Typography } from "@material-tailwind/react"; +// import { useProvider } from "@starknet-react/core"; +// import { useEffect, useMemo, useState } from "react"; +// import { StarknetIdNavigator } from "starknetid.js"; +// import { constants, StarkProfile } from "starknet"; +import clsx from "clsx"; +import { truncateString } from "@/lib/utils.ts"; +import { GameHistorySkeleton } from "./game-history-skeleton.tsx"; +import { UserIcon } from "@heroicons/react/24/solid"; +import EmptyGameHistory from "./empty-game-history.tsx"; + +export default function GameHistory({ games, loading, id }: { games: any, loading: boolean, id: string }) { + // const { provider } = useProvider(); + // const starknetIdNavigator = useMemo(() => { + // return new StarknetIdNavigator( + // provider, + // constants.StarknetChainId.SN_SEPOLIA + // ); + // }, [provider]) + // const [challengers, setChallengers] = useState([]); + // const [challenged, setChallenged] = useState(); + // const [winners, setWinners] = useState(); + // const challengerAddresses = games?.map((game: any) => game.node.player_one); + // const challengedAddresses = games?.map((game: any) => game.node.player_two); + // const winnerAddresses = games?.map((game: any) => game.node.winner); + // useEffect(() => { + // if (!starknetIdNavigator || !challengerAddresses) return; + // (async () => { + // const challengerData = await starknetIdNavigator?.getStarkProfiles(challengerAddresses) + // const challengedData = await starknetIdNavigator?.getStarkProfiles(challengedAddresses) + // const winnersData = await starknetIdNavigator?.getStarkProfiles(winnerAddresses) + // if (!challengerData) return; + // if (challengerData) setChallengers(challengerData); + // if (!challengedData) return; + // if (challengedData) setChallenged(challengedData); + // if (!winnersData) return; + // if (winnersData) setWinners(winnersData) + // })(); + // }, [challengerAddresses, challengedAddresses, winnerAddresses, starknetIdNavigator]); + // const data = challengers?.map((challenger, index) => { + // return { + // challenger: challenger, + // challenged: challenged ? challenged[index] : null, + // winner: winners ? winners[index] : null, + // date: transactions[index].node.executedAt, + // } + // }) + + const data = games?.map((data: any, index: number) => { + console.log(data) + return { + challenger: data.node.player_one, + challenged: data.node.player_two, + winner: data.node.winner, + date: data.node.entity.executedAt, + } + }) + + + if (loading) { + return + } + else { + if (data?.length === 0) { + return + } + else { + return ( +
+ +
+
+
+ {duels_header.map((head) => ( +
+ + {head.name} + +
+ ))} +
+
+
+ + + + {duels_header.map((head) => ( + + ))} + + + + {data?.map((item: any, index: number) => { + const isLast = index === data?.length - 1; + const date = new Date(item.date) + return ( + + + + + + + ); + })} + +
+ + {head.name} + +
+ {/*
*/} +
+ {/* {`${item.challenger.name} */} +
+
+ +
+
+

+ {item.challenger.name ? item.challenger.name : truncateString(games[index].node.player_one)} +

+
+
+ { + games[index].node.player_two !== "0x0" ?
+ {/* {`${item.challenged.name} */} +
+
+ +
+
+

+ {item.challenged.name ? item.challenged.name : truncateString((games[index].node.player_two))} +

+
:

Matchmaking

+ } +
+

+ {item.winner.name ? item.winner.name : truncateString((games[index].node.winner))} +

+
+

+ {date.toLocaleDateString()} +

+
+
+
+
+
+ ) + } + } +} \ No newline at end of file diff --git a/client/src/components/profile/user-section.tsx b/client/src/components/profile/user-section.tsx new file mode 100644 index 00000000..f702a102 --- /dev/null +++ b/client/src/components/profile/user-section.tsx @@ -0,0 +1,117 @@ +import avatar from "@/assets/avatar.png"; +import twitter from "@/assets/twitter.png"; +import telegram from "@/assets/telegram.png"; +import image from "@/assets/image-add.svg"; +import { Link } from "react-router-dom"; +import { FormEventHandler, useRef, useState } from "react"; +import { Dialog } from "@material-tailwind/react"; +import { truncateString } from "@/lib/utils"; +import { useAccount } from "@starknet-react/core"; + +export default function UserSection({ level, wins, losses, total }: { level: number, wins: number, losses: number, total: number }) { + const [open, setOpen] = useState(false); + const [selectedImage, setSelectedImage] = useState(null); + const ref: any = useRef(); + const handleOpen = () => setOpen(!open); + const handleImageChange = (event: React.ChangeEvent) => { + if (event.target.files && event.target.files[0]) { + setSelectedImage(URL.createObjectURL(event.target.files[0])); + } + }; + const account = useAccount(); + return ( +
+
+
+ +
+

{truncateString(account?.address) || "Guest user"}

+ +
+
+
+
+
+
+
+
+
+
+
+

Level {level}

+

{level}/100

+
+
+
+
+
+

Total Played

+

{total}

+
+
+

Total Won

+

{wins}

+
+
+

Total Lost

+

{losses}

+
+
+
+

Share

+
+ + + + + + +
+
+
+ +
+
+
+
+
+
Profile Photo
+

This image will be displayed on your profile

+ +
+ +
+
+
+
Display Name
+

This name will be displayed on your profile

+
+ +
+
+ +
+
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/client/src/pages/Leaderboard.tsx b/client/src/pages/Leaderboard.tsx index 2f837b4c..9dfd767b 100644 --- a/client/src/pages/Leaderboard.tsx +++ b/client/src/pages/Leaderboard.tsx @@ -49,8 +49,6 @@ export default function Leaderboard() { })(); }, [addresses]); - console.log(players); - return (
{/* Start of header */} @@ -61,7 +59,7 @@ export default function Leaderboard() {
diff --git a/client/src/pages/Lobby.tsx b/client/src/pages/Lobby.tsx index fb3a2ffd..553ec6c1 100644 --- a/client/src/pages/Lobby.tsx +++ b/client/src/pages/Lobby.tsx @@ -96,10 +96,6 @@ export default function Lobby() { connect({ connector: connectors[0] }); }; - const handleConnect = () => { - connectWallet(); - }; - const filteredGames = data?.mancalaAlphaMancalaGameModels?.edges?.filter( (game) => game?.node?.player_one === account.address || diff --git a/client/src/pages/Profile.tsx b/client/src/pages/Profile.tsx index e69de29b..b66d3f79 100644 --- a/client/src/pages/Profile.tsx +++ b/client/src/pages/Profile.tsx @@ -0,0 +1,103 @@ +import Header from "@/components/header"; +import GameHistory from "@/components/profile/game-history"; +import UserSection from "@/components/profile/user-section"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { MancalaGameEdge, useFetchModelsForHeaderQuery, useMancalaModelsFetchQuery } from "@/generated/graphql"; +import { getPlayer } from "@/lib/utils"; +import { useAccount } from "@starknet-react/core"; + +export default function Profile() { + const { data, startPolling, loading } = useMancalaModelsFetchQuery(); + startPolling(1000); + const account = useAccount(); + const filteredGames = data?.mancalaAlphaMancalaGameModels?.edges?.filter( + (game) => + game?.node?.player_one === account.address || + game?.node?.player_two === account.address + ); + + // const filteredTransactions = data?.mancalaAlphaMancalaGameModels?.edges?.reduce( + // (acc: any[], game: any) => { + // if ( + // (game?.node?.player_one === account.address || + // game?.node?.player_two === account.address) && + // game?.node?.entity?.executedAt + // ) { + // acc.push({ + // ...game.node, + // executedAt: game?.node?.entity?.executedAt, + // }); + // } + // return acc; + // }, + // [] + // ) || []; + + const filteredWonGames = filteredGames?.filter(game => game?.node?.winner === account.address) || []; + const filteredLostGames = filteredGames?.filter(game => game?.node?.winner !== "0x0" && game?.node?.winner !== account.address) || []; + + const { data: playerData, startPolling: startPollingPlayer } = useFetchModelsForHeaderQuery(); + startPollingPlayer(1000); + + const player = getPlayer( + playerData?.mancalaAlphaMancalaGameModels?.edges as MancalaGameEdge[], + account.account?.address || "", + ); + + + return ( +
+
+
+
+ + +
+ + + All games + + + Won + + + Lost + + +
+
+ + + + + + + + + +
+
+
+
+
+ ) +} \ No newline at end of file