Skip to content

Commit

Permalink
add connection instruction and fix handling moves (#827)
Browse files Browse the repository at this point in the history
* add connection instruction

* enable only email wallet

* fix: hydration error

* improve visual indicator when move is made

* chore: display wallet button

* chore: run prettier
  • Loading branch information
KannuSingh authored Feb 19, 2025
1 parent 00936c4 commit 761fc00
Show file tree
Hide file tree
Showing 8 changed files with 411 additions and 200 deletions.
2 changes: 1 addition & 1 deletion advanced/dapps/smart-sessions-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,4 @@
"typescript": "^5",
"typescript-eslint": "^8.2.0"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,12 @@ createAppKit({
defaultNetwork: baseSepolia,
projectId: ConstantsUtil.ProjectId,
features: {
email: true,
socials: [],
emailShowWallets: false,
analytics: true,
},
allWallets: "HIDE",
themeMode: "light",
termsConditionsUrl: "https://reown.com/terms-of-service",
privacyPolicyUrl: "https://reown.com/privacy-policy",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function DCAForm() {
const [isLoading, setLoading] = React.useState(false);
const isSupported = useMemo(
() => isSmartSessionSupported(),
[status, address]
[status, address],
);

const isWalletConnecting =
Expand All @@ -67,7 +67,7 @@ function DCAForm() {

const intervalInMilliseconds = calculateInterval(
strategyWithTimestamp.investmentInterval,
strategyWithTimestamp.intervalUnit
strategyWithTimestamp.intervalUnit,
);

const expirationTime =
Expand All @@ -79,7 +79,7 @@ function DCAForm() {
strategy={strategyWithTimestamp}
key={Date.now()}
/>,
{ duration: expirationTime - strategyWithTimestamp.createdTimestamp }
{ duration: expirationTime - strategyWithTimestamp.createdTimestamp },
);
} catch (e) {
toast("Error", {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,64 @@ function PositionSquare({
index,
handleMove,
loading,
isAvailable,
}: {
gameState: GameState;
index: number;
handleMove: (gameId: string, position: number) => void;
loading: boolean;
isSystemThinking?: boolean;
isAvailable?: boolean;
}) {
const isWinningSquare = gameState.winningLine?.includes(index);
const isDisabled =
loading ||
!!gameState.board[index] ||
!!gameState.winner ||
!gameState.isXNext; // Disable when it's not player's turn

const baseClasses = `
text-lg
font-medium
flex items-center justify-center
rounded-lg
transition-all duration-200
relative
${isWinningSquare ? "bg-yellow-50" : "bg-gray-50"}
${!isDisabled && !gameState.board[index] ? "hover:bg-gray-100 cursor-pointer" : "cursor-not-allowed"}
${isAvailable ? "bg-blue-50" : ""}
border border-gray-200
${isDisabled && !gameState.board[index] ? "text-gray-300" : "text-gray-400"}
`;

return (
<button
className={`w-20 h-20 sm:w-24 sm:h-24 md:w-32 md:h-32 text-xl sm:text-2xl md:text-3xl font-bold border-2 flex items-center justify-center
${isWinningSquare ? "bg-yellow-100 border-yellow-400" : "border-gray-300"}
${!gameState.board[index] && !loading ? "hover:bg-gray-100" : ""}
transition-colors duration-200`}
className={baseClasses}
onClick={() => handleMove(gameState.gameId!, index)}
disabled={loading || !!gameState.board[index] || !!gameState.winner}
disabled={isDisabled}
aria-label={`Square ${index + 1}`}
>
{!gameState.board[index] && index + 1}
{gameState.board[index] === "X" && (
<Cross1Icon width={40} height={40} color="green" />
)}
{gameState.board[index] === "O" && (
<CircleIcon width={40} height={40} color="red" />
{!gameState.board[index] ? (
<span className="text-inherit">{index + 1}</span>
) : gameState.board[index] === "X" ? (
<Cross1Icon width={32} height={32} className="text-green-500" />
) : (
<CircleIcon width={32} height={32} className="text-red-500" />
)}
</button>
);
}

export default PositionSquare;
export default React.memo(PositionSquare, (prevProps, nextProps) => {
return (
prevProps.gameState.board[prevProps.index] ===
nextProps.gameState.board[nextProps.index] &&
prevProps.loading === nextProps.loading &&
prevProps.isSystemThinking === nextProps.isSystemThinking &&
prevProps.isAvailable === nextProps.isAvailable &&
prevProps.gameState.isXNext === nextProps.gameState.isXNext && // Add isXNext to comparison
(prevProps.gameState.winningLine?.includes(prevProps.index) ?? false) ===
(nextProps.gameState.winningLine?.includes(nextProps.index) ?? false)
);
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"use client";
import React from "react";
import React, { useState, useEffect } from "react";
import { Button } from "@/components/ui/button";
import {
Card,
Expand All @@ -16,11 +16,13 @@ import { useTicTacToeContext } from "@/context/TicTacToeContextProvider";
import { useTicTacToeActions } from "@/hooks/useTicTacToeActions";
import { useAppKitAccount } from "@reown/appkit/react";

export default function TicTacToe() {
const [isLoading, setIsLoading] = React.useState(false);
// This component contains all your hooks and JSX.
function TicTacToeInner() {
const [isLoading, setIsLoading] = useState(false);
const { smartSession, clearSmartSession } = useTicTacToeContext();
const { startGame } = useTicTacToeActions();
const { status, address } = useAppKitAccount();

const isWalletConnected = status === "connected" || address !== undefined;
const isWalletConnecting = status
? ["connecting", "reconnecting"].includes(status)
Expand All @@ -37,104 +39,144 @@ export default function TicTacToe() {
try {
await startGame();
} catch (e) {
console.warn("Error:", e);
const errorMessage = (e as Error)?.message || "Error starting game";
toast.error("Error", {
description: errorMessage,
});
toast.error("Error", { description: errorMessage });
} finally {
setIsLoading(false);
}
}

if (isWalletConnecting) {
return (
<main className="flex min-h-screen flex-col items-center justify-center p-8 bg-gradient-to-b from-blue-100 to-purple-100">
<div className="w-full max-w-sm text-center mb-12">
<h1 className="text-3xl font-bold mb-4 text-gray-800 dark:text-gray-200">
TicTacToe Game
</h1>
<div className="flex w-full mb-4 items-center justify-center">
<Loader2 className="animate-spin h-8 w-8 mr-2" />
<p className="text-lg text-gray-600 dark:text-gray-400 font-bold">
Loading...
</p>
</div>
// Subcomponent: Loading screen shown during wallet connection
const LoadingScreen = () => (
<main className="flex min-h-screen flex-col items-center justify-center p-8 bg-gradient-to-b from-blue-100 to-purple-100">
<div className="w-full max-w-sm text-center mb-12">
<h1 className="text-3xl font-bold mb-4 text-gray-800 dark:text-gray-200">
TicTacToe Game
</h1>
<div className="flex items-center justify-center space-x-2">
<Loader2 className="animate-spin h-8 w-8 text-gray-600 dark:text-gray-400" />
<p className="text-lg font-bold text-gray-600 dark:text-gray-400">
Loading...
</p>
</div>
</main>
);
}
</div>
</main>
);

return (
const NotConnectedScreen = () => (
<main className="flex min-h-screen flex-col items-center justify-center p-8 bg-gradient-to-b from-blue-100 to-purple-100">
<div className="w-full max-w-md text-center mb-12">
<h1 className="text-3xl font-bold mb-4 text-gray-800 dark:text-gray-200">
TicTacToe Game
</h1>
{isWalletConnected && (
<div className="flex w-full mb-4 items-center justify-center">
<w3m-button />
</div>
)}
{!isWalletConnected && !grantedPermissions && !isWalletConnecting && (
<p className="text-lg text-gray-600 dark:text-gray-400 font-bold mb-4">
Connect Wallet to get started.
</p>
)}
<Card className="w-full bg-gradient-to-br from-purple-100 to-indigo-100 shadow-lg mb-4 items-center justify-center">
<CardContent className="p-4 ">
<p className="text-sm text-gray-700 mb-2">
When connecting, please use your Email Wallet with:
</p>
<p className="font-mono text-sm bg-gray-100 dark:bg-gray-800 p-2 rounded">
youremail
<span className="text-blue-500 dark:text-blue-400">
+smart-sessions
</span>
@domain.com
</p>
<p className="text-xs text-gray-500 mt-1">
Example: john
<span className="text-blue-500 dark:text-blue-400">
+smart-sessions
</span>
@doe.com
</p>
</CardContent>
<CardFooter className="p-4 justify-center">
<div className="flex ">
<ConnectWalletButton />
</div>
</CardFooter>
</Card>
</div>
</main>
);

{grantedPermissions && gameStarted && (
<div className="flex flex-col items-center mb-4 gap-4">
const GameIntroCard = () => (
<div className="w-full max-w-md mx-auto mb-12">
<div className="flex w-full mb-4 items-center justify-center">
<w3m-button />
</div>
<Card className="w-full bg-gradient-to-br from-purple-100 to-indigo-100 shadow-lg">
<CardHeader className="p-4">
<CardTitle className="text-center text-xl sm:text-2xl md:text-3xl font-bold text-indigo-800">
Let&apos;s Play TicTacToe
</CardTitle>
</CardHeader>
<CardContent className="p-4">
<p className="text-base text-gray-700 leading-relaxed">
Players take turns placing their marks in empty squares. The first
to align three marks in a row—vertically, horizontally, or
diagonally—wins. If all squares are filled and no one has three in a
row, the game ends in a tie.
</p>
</CardContent>
<CardFooter className="p-4 justify-center">
<div className="flex items-center justify-center">
<Button
variant="destructive"
className="items-center mb-4"
onClick={resetGame}
onClick={onStartGame}
disabled={isLoading}
className="w-full max-w-xs bg-indigo-700 hover:bg-indigo-600 text-white"
>
End Game
{isLoading ? (
<div className="flex items-center justify-center space-x-2">
<Loader2 className="animate-spin h-4 w-4" />
<span>Starting...</span>
</div>
) : (
"Start New Game"
)}
</Button>
<TicTacToeBoard />
</div>
)}
</CardFooter>
</Card>
</div>
);

{(!grantedPermissions || !gameStarted) && (
<Card className="w-full mb-4 max-w-2xl bg-gradient-to-br from-purple-100 to-indigo-100 shadow-lg">
<CardHeader>
<CardTitle className="text-center text-xl sm:text-2xl md:text-3xl font-bold text-indigo-800">
Lets Play TicTacToe
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-start text-base sm:text-md md:text-lg text-gray-700 leading-relaxed">
Players take turns placing their marks in empty squares. The
first to align three marks in a row—vertically, horizontally, or
diagonally—wins. If all squares are filled and no one has three
in a row, the game ends in a tie.
</p>
</CardContent>
<CardFooter>
<div className="flex w-full mb-4 items-center justify-center">
{isWalletConnected && !gameStarted ? (
<Button
onClick={onStartGame}
disabled={isLoading}
className="w-full max-w-xs bg-indigo-700 hover:bg-indigo-600 text-white"
>
{isLoading ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Starting...
</>
) : (
"Start New Game"
)}
</Button>
) : (
<ConnectWalletButton />
)}
</div>
</CardFooter>
</Card>
)}
const GameBoard = () => (
<div className="w-full max-w-md mx-auto mb-12">
<div className="flex flex-col items-center gap-4 mb-4">
<Button
variant="destructive"
className="w-full max-w-xs"
onClick={resetGame}
>
End Game
</Button>
<TicTacToeBoard />
</div>
</div>
);

if (isWalletConnecting) {
return <LoadingScreen />;
}

if (!isWalletConnected) {
return <NotConnectedScreen />;
}

return (
<main className="flex min-h-screen flex-col items-center justify-center p-8 bg-gradient-to-b from-blue-100 to-purple-100">
{grantedPermissions && gameStarted ? <GameBoard /> : <GameIntroCard />}
</main>
);
}

// The outer component only handles the mounting check.
export default function TicTacToe() {
const [hasMounted, setHasMounted] = useState(false);
useEffect(() => {
setHasMounted(true);
}, []);

if (!hasMounted) return null;
return <TicTacToeInner />;
}
Loading

0 comments on commit 761fc00

Please sign in to comment.