diff --git a/src/components/organisms/Draft/Card/index.tsx b/src/components/organisms/Draft/Card/index.tsx index ae6215a..a7c37eb 100644 --- a/src/components/organisms/Draft/Card/index.tsx +++ b/src/components/organisms/Draft/Card/index.tsx @@ -4,7 +4,14 @@ import Tilt, { GlareProps } from "react-parallax-tilt"; import { useState } from "react"; import { CardDTO } from "@/utils/api/cardData/CardDataType"; -export const DraftCard = (cardDTO: CardDTO) => { +export const DraftCard = ({ + isSelected, + onSelect, + ...cardDTO +}: CardDTO & { + isSelected?: boolean; + onSelect?(): void; +}) => { const [isOver, setIsOver] = useState(false); function over() { setIsOver(true); @@ -28,22 +35,28 @@ export const DraftCard = (cardDTO: CardDTO) => { zIndex: isOver ? 10000 : 1, transform: isOver && cardDTO.guardian.type === "Site" ? " rotate(90deg)" : "", - filter: isOver ? `saturate(1.5)` : "saturate(1)", + filter: isOver || isSelected ? `saturate(1.5)` : "saturate(1)", }} > diff --git a/src/components/organisms/Draft/Ribbon/SelectedCardsModal.tsx b/src/components/organisms/Draft/Ribbon/SelectedCardsModal.tsx new file mode 100644 index 0000000..cff3989 --- /dev/null +++ b/src/components/organisms/Draft/Ribbon/SelectedCardsModal.tsx @@ -0,0 +1,106 @@ +import { Box, Flex, Grid } from "styled-system/jsx"; +import { Modal } from "@/components/atoms/Modal"; +import { getCardImage } from "../../GameBoard/constants"; +import { reduceCardCount, sortAlphabetical } from "./helpers"; +import { CardDTO } from "@/utils/api/cardData/CardDataType"; +import { Button } from "@/components/ui/button"; +import { useCopyToClipboard } from "@/utils/hooks"; +import toast from "react-hot-toast"; + +export const SelectedCardsModal = ({ + cards = [], + isOpen, + onToggle, +}: { + cards?: CardDTO[]; + isOpen?: boolean; + onToggle?(): void; +}) => { + const [, copy] = useCopyToClipboard(); + + return ( + <> + + + {cards.sort(sortAlphabetical).map((card, index) => ( + + card image + + ))} + + + {cards + .sort(sortAlphabetical) + .reduce(reduceCardCount, []) + .map((card, index) => { + return ( + +

{card.count}x

+

{card.name}

+
+ ); + })} + +

+ Copy & Paste into Curiosa 'bulk add' in Create + Deck +

+
+ + } + /> + + ); +}; diff --git a/src/components/organisms/Draft/Ribbon/helpers.ts b/src/components/organisms/Draft/Ribbon/helpers.ts new file mode 100644 index 0000000..9e0585f --- /dev/null +++ b/src/components/organisms/Draft/Ribbon/helpers.ts @@ -0,0 +1,24 @@ +import { CardDTO } from "@/utils/api/cardData/CardDataType"; + +export function sortAlphabetical(a: CardDTO, b: CardDTO) { + if (a.name < b.name) { + return -1; + } + if (a.name > b.name) { + return 1; + } + return 0; +} + +export function reduceCardCount( + acc: { name: string; count: number }[], + card: Card, +) { + const existingCard = acc.find((item) => item.name === card.name); + if (existingCard) { + existingCard.count += 1; + } else { + acc.push({ name: card.name, count: 1 }); + } + return acc; +} diff --git a/src/components/organisms/Draft/Ribbon/index.tsx b/src/components/organisms/Draft/Ribbon/index.tsx index d554836..903e599 100644 --- a/src/components/organisms/Draft/Ribbon/index.tsx +++ b/src/components/organisms/Draft/Ribbon/index.tsx @@ -1,9 +1,129 @@ -import { Box } from "styled-system/jsx"; +import { Button } from "@/components/ui/button"; +import { Flex, Grid } from "styled-system/jsx"; +import { DraftProps } from "../types"; +import { generateBoosterPack } from "../helpers"; +import { useCardFullData } from "@/utils/api/cardData/useCardData"; +import { useState } from "react"; +import { SelectedCardsModal } from "./SelectedCardsModal"; + +export const DraftRibbon = ( + props: DraftProps & { + takeAndPass?(): void; + }, +) => { + const [isOpen, setIsOpen] = useState(false); + const disclosure = { + isOpen, + toggle: () => setIsOpen((prev) => !prev), + onOpen: () => setIsOpen(true), + onClose: () => setIsOpen(true), + }; + + const { data: cardData = [] } = useCardFullData(); + + function crackBooster() { + const newBooster = generateBoosterPack({ + cardData, + expansionSlug: "bet", + }); + props.setPlayerData({ + ...props.player, + activePack: newBooster, + packsOpened: (props.player.packsOpened ?? 0) + 1, + }); + } + + // function takeAndPass() { + // const { activePack, finishedPacks, selectedIndex, selectedCards } = + // props.player; + + // if (selectedIndex === undefined) return; + + // const updatedPack = [...activePack]; + // const updatedSelected = [...selectedCards]; + + // const [card] = updatedPack.splice(selectedIndex, 1); + // updatedSelected.push(card); + + // props.setPlayerData({ + // ...props.player, + // activePack: [], + // finishedPacks: [...finishedPacks, updatedPack], + // selectedIndex: undefined, + // }); + // } + + function activatePendingPack() { + const { pendingPacks } = props.player; + const updatedPending = [...pendingPacks]; + const [pack] = updatedPending.splice(0, 1); + + props.setPlayerData({ + ...props.player, + pendingPacks: updatedPending, + activePack: pack, + }); + } + + const [nextPack] = props.player.pendingPacks ?? []; -export const DraftRibbon = () => { return ( - -

ribbon

-
+ <> + + + + + + + +
+

{props.player.packsOpened ?? "0"} packs opened

+
+ + {props.player.activePack.length === 0 ? ( + + ) : ( + + )} +
+ + + +
+ ); }; diff --git a/src/components/organisms/Draft/Tray/index.tsx b/src/components/organisms/Draft/Tray/index.tsx index c36a9fd..576f387 100644 --- a/src/components/organisms/Draft/Tray/index.tsx +++ b/src/components/organisms/Draft/Tray/index.tsx @@ -1,18 +1,108 @@ +import { Box, Flex, HStack } from "styled-system/jsx"; import { DraftPlayerData } from "../types"; +import { LuArrowBigRightDash as IconRight } from "react-icons/lu"; +import { RiArrowGoBackLine as IconReturnArrow } from "react-icons/ri"; +import { sortPlayersByJoin } from "../helpers"; +import { useRouter } from "next/router"; +import { Properties } from "styled-system/types/csstype"; + export const DraftTray = (props: { players: Record; }) => { - const players = Object.entries(props.players); + const { query, push } = useRouter(); + const players = Object.entries(props.players).sort(sortPlayersByJoin); + + function changePlayer(name: string) { + push({ + query: { + ...query, + name, + }, + }); + } + return ( -
- {players?.map(([key, value]) => { + + {players?.map(([key, value], index) => { return ( -

- {key}:{value.joinedSessionTimestamp} -

+ + + changePlayer(key)} + style={{ + borderColor: + query.name === key ? "gold" : "rgba(0,200,250,0.5)", + }} + > +

{key}

+

{getStatus(value)}

+
+ {index !== players.length - 1 ? ( + + ) : ( + + )} +
); })} -
+ ); }; + +const Dots = (props: DraftPlayerData) => { + const s = "0.85rem"; // dot size + + const style: Properties = { + width: s, + height: s, + position: "absolute", + borderRadius: "100%", + background: "red", + }; + + return ( + <> + {props.pendingPacks.map( + (pack, index) => + (pack ?? []).length > 0 && ( + + ), + )} + {props.finishedPacks.map((pack, index) => ( + + ))} + + ); +}; + +function getStatus(props: DraftPlayerData) { + const activeIsEmpty = props.activePack.length === 0; + // const pendingIsEmpty = props.pendingPacks.length === 0; + const isSelecting = props.selectedIndex !== undefined; + + if (activeIsEmpty) return "waiting"; + if (!activeIsEmpty && !isSelecting) return "thinking"; + if (!activeIsEmpty && isSelecting) return "selecting"; +} diff --git a/src/components/organisms/Draft/helpers.ts b/src/components/organisms/Draft/helpers.ts index 192d6ce..701db12 100644 --- a/src/components/organisms/Draft/helpers.ts +++ b/src/components/organisms/Draft/helpers.ts @@ -1,4 +1,5 @@ import { CardDTO } from "@/utils/api/cardData/CardDataType"; +import { DraftPlayerData } from "./types"; function shuffleAndSelect(arr: CardDTO[], count = 15) { const shuffled = arr.slice(); // Shallow copy to avoid mutating the original array @@ -66,3 +67,43 @@ export function generateBoosterPack(props: { return newBooster; } + +/** + * SORT PLAYERS BASED ON JOIN + * */ +type PlayersEntry = [string, DraftPlayerData]; +export function sortPlayersByJoin(a: PlayersEntry, b: PlayersEntry) { + const [, valueA] = a; + const [, valueB] = b; + return valueA.joinedSessionTimestamp - valueB.joinedSessionTimestamp; +} + +/** + * FIND ADJACENT SEATS IN DRAFT + * */ +export const findAdjacentPlayers = ( + currentPlayer: DraftPlayerData, + _players: Record, +) => { + const players = Object.entries(_players).sort(sortPlayersByJoin); + + const currentIndex = players.findIndex( + ([, value]) => + value.joinedSessionTimestamp === currentPlayer.joinedSessionTimestamp, + ); + + const previousPlayer = + currentIndex === 0 + ? players[players.length - 1] // If first player, return the last player + : players[currentIndex - 1]; // Else, return the previous player + + const nextPlayer = + currentIndex === players.length - 1 + ? players[0] // If last player, return the first player + : players[currentIndex + 1]; // Else, return the next player + + return { + previousPlayer, + nextPlayer, + }; +}; diff --git a/src/components/organisms/Draft/index.tsx b/src/components/organisms/Draft/index.tsx index 55a1a7b..2c88e64 100644 --- a/src/components/organisms/Draft/index.tsx +++ b/src/components/organisms/Draft/index.tsx @@ -1,7 +1,6 @@ import { DraftCard } from "@/components/organisms/Draft/Card"; import { Grid } from "styled-system/jsx"; import { DraftPlayerData } from "./types"; -import { useMemo, useState } from "react"; import { DraftRibbon } from "./Ribbon"; import { DraftTray } from "./Tray"; @@ -14,11 +13,15 @@ export const DraftBoard = (props: { players: Record; player: DraftPlayerData; setPlayerData(data: DraftPlayerData): void; + takeAndPass?(): void; }) => { - const [activeView] = useState(0); - const cardView = useMemo(() => { - return props.player.finishedPacks?.[activeView]; - }, [activeView, props.player.finishedPacks.length]); + // select a card from active pack, ready for taking + function setSelectedIndex(index?: number) { + props.setPlayerData({ + ...props.player, + selectedIndex: index, + }); + } return ( - + - {(!cardView || cardView?.length === 0) && ( -

No packs... yet! Click Crack a Pack!

- )} - {cardView?.map((card, index) => ( - + {props.player.activePack?.map((card, index) => ( + + props.player.selectedIndex === index + ? setSelectedIndex(undefined) + : setSelectedIndex(index) + } + /> ))}
diff --git a/src/components/organisms/Draft/types.ts b/src/components/organisms/Draft/types.ts index 00a82a4..1b38c15 100644 --- a/src/components/organisms/Draft/types.ts +++ b/src/components/organisms/Draft/types.ts @@ -8,10 +8,12 @@ export type BoosterPack = { export type DraftPlayerData = { joinedSessionTimestamp: number; // time of joining the session. Used for ordering + selectedIndex?: number; // index of card ready for selection from activePack selectedCards: CardDTO[]; // cards you've picked activePack: CardDTO[]; // pack actively picking from pendingPacks: CardDTO[][]; // packs ready for pick (passed by other player) finishedPacks: CardDTO[][]; // packs you've picked and ready to pass (ready to pass to other player) + packsOpened: number; deck?: CardDTO[]; // after draft, the deck you construct from selectedCards }; @@ -21,6 +23,7 @@ export const initPlayer: DraftPlayerData = { activePack: [], pendingPacks: [], finishedPacks: [], + packsOpened: 0, }; export const initPlayers: Record = [1, 2, 3, 4].reduce( diff --git a/src/pages/crack.tsx b/src/pages/crack.tsx index 038dd61..02b34c5 100644 --- a/src/pages/crack.tsx +++ b/src/pages/crack.tsx @@ -10,6 +10,7 @@ export default function CrackPacksPage() { activePack: [], pendingPacks: [[]], finishedPacks: [], + packsOpened: 0, }, }); diff --git a/src/pages/draft/index.tsx b/src/pages/draft/index.tsx index c2e1f02..c1e5f3a 100644 --- a/src/pages/draft/index.tsx +++ b/src/pages/draft/index.tsx @@ -1,28 +1,64 @@ import { DraftBoard } from "@/components/organisms/Draft"; +import { findAdjacentPlayers } from "@/components/organisms/Draft/helpers"; import { DraftPlayerData, initPlayers, } from "@/components/organisms/Draft/types"; -import { useState } from "react"; +import { useRouter } from "next/router"; +import { useMemo, useState } from "react"; export default function DraftSoloPage() { + const { query } = useRouter(); + const name = (query?.name as string) ?? ("p1" as string); + const [players, setPlayers] = useState>(initPlayers); function setPlayer(data: DraftPlayerData) { return setPlayers((prev) => ({ ...prev, - p1: { + [name]: { ...data, }, })); } + const { nextPlayer } = useMemo(() => { + return findAdjacentPlayers(players[name], players); + }, [Object.values(players).map((player) => player.joinedSessionTimestamp)]); + + function takeAndPass() { + const me = players?.[name]; + if (me.selectedIndex === undefined) return; + + const updatedPack = [...me.activePack]; + const [selectedCard] = updatedPack.splice(me.selectedIndex, 1); + const newSelectedCards = [...me.selectedCards, selectedCard]; + + const [nextPlayerName, nextPlayerData] = nextPlayer; + // const [previousPlayerName, previousPlayerData] = previousPlayer; + + setPlayers({ + ...players, + [name]: { + ...me, + activePack: [], + selectedIndex: undefined, + selectedCards: newSelectedCards, + }, + [nextPlayerName]: { + ...nextPlayerData, + pendingPacks: [...nextPlayerData.pendingPacks, updatedPack], + }, + }); + } + return ( ); }