diff --git a/src/app/games/blackjack/BlackjackClient.tsx b/src/app/games/blackjack/BlackjackClient.tsx deleted file mode 100644 index 8682fd99..00000000 --- a/src/app/games/blackjack/BlackjackClient.tsx +++ /dev/null @@ -1,354 +0,0 @@ -"use client"; - -import { useRef, useState, useTransition } from "react"; -import { startGame, hit, stand, revealDealer } from "./actions"; -import { motion } from "framer-motion"; - -type Phase = "idle" | "playing" | "revealing" | "finished"; - -export default function BlackjackClient() { - const [state, setState] = useState(null); - const [gameId, setGameId] = useState(null); - const [phase, setPhase] = useState("idle"); - const [visibleDealerCount, setVisibleDealerCount] = useState(1); - - const dealerRevealIndex = useRef(0); - const MAX_DEALER_CARDS = 5; - const CARD_WIDTH = 64; // w-16 - const GAP = 8; // space-x-2 - - const [, startTransition] = useTransition(); - - // animation snapshots - const lastPlayerLen = useRef(0); - - /* ---------------- actions ---------------- */ - - const start = async () => { - const res: any = await startGame(10); - lastPlayerLen.current = 0; - setVisibleDealerCount(1); - setGameId(res.gameId); - setPhase("playing"); - setState(res); - }; - - const getDealerCards = () => { - // if we don't yet have any dealer info, nothing to render - if (!state?.dealerUpcard && !state?.dealer) return []; - - // ALWAYS start with upcard + hole slot; prefer the authoritative dealer state when available - const upcard = state?.dealer?.[0] ?? state.dealerUpcard[0]; - const base = [upcard, "__HOLE__"]; - - // If dealer is revealed, append extra cards ONLY - if (state?.dealer && state.dealer.length > 2) { - return [...base, ...state.dealer.slice(2)]; - } - - return base; - }; - - const onHit = async () => { - lastPlayerLen.current = state.player.length; - - const res = await hit(gameId!); - setState((s: any) => ({ ...s, ...res })); - - if (res.status === "bust") { - const dealerRes = await revealDealer(gameId!); - - setState((s: any) => ({ - ...s, - dealer: dealerRes.dealer, - dealerTotal: dealerRes.dealerTotal, - status: "bust", - })); - - setVisibleDealerCount(dealerRes.dealer.length); - setPhase("finished"); - } - }; - - const onStand = async () => { - const res = await stand(gameId!); - - setPhase("revealing"); - // start with only upcard visible; we'll reveal the hole (i=1) then extra cards (i>=2) - dealerRevealIndex.current = 0; - setVisibleDealerCount(1); - - setState((s: any) => ({ - ...s, - dealer: res.dealer, - dealerTotal: res.dealerTotal, - status: "finished", - result: res.result, - })); - - // sequential reveal: - // - i = 1 -> flip the hole card - // - i >= 2 -> reveal each drawn card in order - for (let i = 1; i < res.dealer.length; i++) { - // wait before revealing next slot - await new Promise((r) => setTimeout(r, 1600)); - // set which dealer index should be animating (1 for hole, 2..n for drawn) - dealerRevealIndex.current = i; - // make the slot visible (i+1 slots visible: 0..i) - setVisibleDealerCount(i + 1); - // small pause so the flip animation has time to start/finish before next reveal step - await new Promise((r) => setTimeout(r, 600)); - } - - setPhase("finished"); - }; - - /* ---------------- cards ---------------- */ - - const CardStatic = ({ c }: any) => ( -
- {c.label} - {c.suit} -
- ); - - const CardBack = () => ( -
- RUST -
- ); - - const CardFlip = ({ c }: any) => ( - - - {/* BACK */} -
- RUST -
- - {/* FRONT */} -
- {c.label} - {c.suit} -
-
-
- ); - - function isSoftHand(hand: any[], total: number) { - const hasAce = hand.some((c) => c.label === "A"); - if (!hasAce) return false; - - // if counting all aces as 1 would reduce the total, it's soft - const minTotal = hand.reduce( - (sum, c) => sum + (c.label === "A" ? 1 : c.value), - 0, - ); - - return minTotal !== total; - } - - /* Stable key helpers to prevent React remount flashes */ - const dealerCardKey = (slot: any, index: number) => { - // slot can be a card object or "__HOLE__" - if (slot === "__HOLE__") { - // When hole is hidden, use a stable hidden key so it doesn't briefly mount/unmount - if (phase === "playing" || state?.status === "bust") { - return "dealer-hole-hidden"; - } - // When revealed, key should reflect the actual dealer card identity - const revealedCard = state?.dealer?.[1]; - if (revealedCard?.code) return `dealer-${revealedCard.code}`; - if (revealedCard) - return `dealer-${revealedCard.label}${revealedCard.suit}`; - return `dealer-hole-${index}`; - } - - // For upcard and other visible cards prefer a unique code if available - if (slot?.code) return `dealer-${slot.code}`; - if (slot?.label && slot?.suit) - return `dealer-${slot.label}${slot.suit}-${index}`; - - // fallback stable index - return `dealer-${index}`; - }; - - /* ---------------- render ---------------- */ - - return ( -
-
- {/* DEALER */} -
- {getDealerCards().map((c, i) => { - // Always render upcard (index 0) regardless of visibleDealerCount - if (i === 0 && c && c !== "__HOLE__") { - const key = dealerCardKey(c, i); - return ; - } - - // HOLE CARD (index 1) - if (i === 1) { - const key = dealerCardKey(c, i); - - // Hidden hole card during play or when player busts - // Also respect visibleDealerCount: hole is visible only when visibleDealerCount >= 2 - if ( - phase === "playing" || - state?.status === "bust" || - visibleDealerCount < 2 - ) { - return ; - } - - // During reveal phase: flip the hole only when dealerRevealIndex === 1 - if (phase === "revealing" && state?.dealer) { - if (dealerRevealIndex.current === 1) { - return ; - } else { - return ; - } - } - - // Final static revealed card - return ; - } - - // DRAWN CARDS (index >= 2) - if (i >= 2 && state?.dealer) { - // only render this slot once it's been made visible by visibleDealerCount - if (i >= visibleDealerCount) return null; - - const key = dealerCardKey(state.dealer[i], i); - - // If we're currently revealing this index, play the flip animation. - // Otherwise show the static face - if (phase === "revealing" && i === dealerRevealIndex.current) { - return ; - } - - return ; - } - - return null; - })} -
- - {/* TERMINAL */} -
- {phase === "idle" &&

> INSERT SCRAP

} - - {phase === "playing" && state && ( - <> -

- > PLAYER TOTAL: {state.playerTotal} - {isSoftHand(state.player, state.playerTotal) && " (SOFT)"} -

- -

> DEALER WAITING...

- - )} - - {phase === "revealing" && ( - - > DEALER DRAWING... - - )} - - {phase === "finished" && state && ( - -

> PLAYER: {state.playerTotal}

- - {state.status === "bust" && ( -

> PLAYER BUSTED

- )} - - {/* Always show dealer score once dealer is revealed */} - {state.dealerTotal !== undefined && ( -

> DEALER: {state.dealerTotal}

- )} - - {state.result &&

> RESULT: {state.result.toUpperCase()}

} -
- )} -
- {/* PLAYER */} -
- {state?.player?.map((c: any, i: number) => - phase === "playing" && i >= lastPlayerLen.current ? ( - - ) : ( - - ), - )} -
- {/* CONTROLS */} -
- {phase === "idle" && ( - - )} - - {phase === "playing" && state?.status === "playing" && ( - <> - - - - )} -
-
-
- ); -} diff --git a/src/app/games/blackjack/actions.ts b/src/app/games/blackjack/actions.ts deleted file mode 100644 index 0488cb61..00000000 --- a/src/app/games/blackjack/actions.ts +++ /dev/null @@ -1,84 +0,0 @@ -"use server"; - -import crypto from "crypto"; -import { - createDeck, - shuffle, - calculateHand, - type Card, -} from "~/lib/blackjack/engine"; - -// In-memory storage (replace with DB/Redis in production) -const games = new Map(); - -export async function startGame(bet: number) { - const deck = shuffle(createDeck()); - - const player: Card[] = [deck.pop()!, deck.pop()!]; - const dealer: Card[] = [deck.pop()!, deck.pop()!]; - - const gameId = crypto.randomUUID(); - - games.set(gameId, { deck, player, dealer, bet, status: "playing" }); - - return { - gameId, - player, - dealerUpcard: [dealer[0]], - playerTotal: calculateHand(player), - status: "playing", - }; -} - -export async function hit(gameId: string) { - const game = games.get(gameId); - if (!game) throw new Error("Game not found"); - - const card = game.deck.pop(); - game.player.push(card); - - const playerTotal = calculateHand(game.player); - if (playerTotal > 21) game.status = "bust"; - - return { - player: game.player, - playerTotal, - status: game.status, - }; -} - -export async function stand(gameId: string) { - const game = games.get(gameId); - if (!game) throw new Error("Game not found"); - - while (calculateHand(game.dealer) < 17) { - game.dealer.push(game.deck.pop()); - } - - const playerTotal = calculateHand(game.player); - const dealerTotal = calculateHand(game.dealer); - - let result: "win" | "lose" | "push" = "lose"; - if (dealerTotal > 21 || playerTotal > dealerTotal) result = "win"; - if (playerTotal === dealerTotal) result = "push"; - - // TODO: update user balance here - - games.delete(gameId); - - return { - dealer: game.dealer, - dealerTotal, - result, - }; -} - -export async function revealDealer(gameId: string) { - const game = games.get(gameId); - if (!game) throw new Error("Game not found"); - - return { - dealer: game.dealer, - dealerTotal: calculateHand(game.dealer), - }; -} diff --git a/src/app/games/blackjack/page.tsx b/src/app/games/blackjack/page.tsx deleted file mode 100644 index 8b1d79f9..00000000 --- a/src/app/games/blackjack/page.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import BlackjackClient from "./BlackjackClient"; - -export default function BlackjackPage() { - return ( -
-

Blackjack

- -
- ); -} diff --git a/src/components/HomeClient.tsx b/src/components/HomeClient.tsx index 2e1e5701..20d1d411 100644 --- a/src/components/HomeClient.tsx +++ b/src/components/HomeClient.tsx @@ -43,9 +43,7 @@ type Props = { export default function HomeClient({ session }: Props) { const [query, setQuery] = useState(""); const [userData, setUserData] = useState(null); - const [sellableData, setSellableData] = useState( - null, - ); + const [sellableData, setSellableData] = useState([]); const [loading, setLoading] = useState(true); // Fetch /api/user once and store globally here @@ -68,7 +66,7 @@ export default function HomeClient({ session }: Props) { setLoading(true); const res = await fetch("/api/sellable"); if (!res.ok) throw new Error("Failed to fetch sellable"); - const data = (await res.json()) as SellableResponse; + const data = (await res.json()) as SellableResponse[]; setSellableData(data); } catch (err) { console.error(err); diff --git a/src/components/account.tsx b/src/components/account.tsx index de8e9e97..8c6a37f7 100644 --- a/src/components/account.tsx +++ b/src/components/account.tsx @@ -37,7 +37,7 @@ export default function AccountButton({ loading }: Props) { return false; } - const data = await res.json(); + const data = (await res.json()) as UserData; setUserData({ adresses: data.adresses ?? [], @@ -83,7 +83,7 @@ export default function AccountButton({ loading }: Props) { }; const res = await saveAll(next); - if (!res || !res.ok) { + if (!res?.ok) { console.error("Failed to add address"); return; } @@ -99,7 +99,7 @@ export default function AccountButton({ loading }: Props) { }; const res = await saveAll(next); - if (!res || !res.ok) { + if (!res?.ok) { console.error("Failed to remove address"); return; } @@ -119,7 +119,7 @@ export default function AccountButton({ loading }: Props) { }; const res = await saveAll(next); - if (!res || !res.ok) { + if (!res?.ok) { console.error("Failed to add shop"); return; } @@ -136,7 +136,7 @@ export default function AccountButton({ loading }: Props) { }; const res = await saveAll(next); - if (!res || !res.ok) { + if (!res?.ok) { console.error("Failed to remove shop"); return; } diff --git a/src/components/cart.tsx b/src/components/cart.tsx index 2025d221..7e70f928 100644 --- a/src/components/cart.tsx +++ b/src/components/cart.tsx @@ -1,7 +1,5 @@ "use client"; -import type { CartItem } from "generated/prisma"; -import { IMAGES_MANIFEST } from "next/dist/shared/lib/constants"; import { useEffect, useState, useCallback } from "react"; type Cart = { @@ -202,12 +200,12 @@ export default function CartButton({ [dragging], ); - const handleMouseRelease = useCallback(async () => { + const handleMouseRelease = useCallback(() => { if (dragging) { const { itemId, quantity } = dragging; setDragging(null); setSliderActive(null); - await removeItem(itemId, quantity); + void removeItem(itemId, quantity); } }, [dragging]); @@ -370,7 +368,12 @@ export default function CartButton({ cartItems.length === 0 ) return; - void buyItems(cartItems, selectedAddress); + void buyItems(cartItems, selectedAddress).then(async () => { + setCartItems([]); + setTotalQuantity(0); + setIsOpen(false); + await reloadUser(); + }); }} disabled={buyDisabled} >