little bugs

This commit is contained in:
Sewmina 2025-04-18 08:39:18 +05:30
parent 49ed569cb4
commit a230c97078
6 changed files with 189 additions and 96 deletions

View File

@ -0,0 +1,67 @@
import { useEffect, useState } from "react";
interface FirstVisitModalProps {
isOpen: boolean;
onClose: () => void;
}
export function FirstVisitModal({ isOpen, onClose }: FirstVisitModalProps) {
const [shouldRender, setShouldRender] = useState(isOpen);
const [animationClass, setAnimationClass] = useState("");
useEffect(() => {
if (isOpen) {
setShouldRender(true);
setAnimationClass("modal-enter");
} else {
setAnimationClass("modal-exit");
// Wait for animation to finish before unmounting
const timer = setTimeout(() => setShouldRender(false), 200); // match animation duration
return () => clearTimeout(timer);
}
}, [isOpen]);
if (!shouldRender) return null;
return (
<div
className="fixed inset-0 bg-black/70 flex justify-center items-start pt-10 z-50"
onClick={onClose}
>
<div
className={`bg-[rgb(30,30,30)] text-white w-full max-w-lg p-6 rounded-2xl shadow-lg transform transition-transform duration-300 ${animationClass}`}
onClick={(e) => e.stopPropagation()}
>
<h2 className="text-xl font-bold mb-4">Welcome to DuelFi.io!</h2>
<div className="space-y-4">
<p className="text-gray-300">
Challenge your friends (or strangers) in a head-to-head skill games and earn from your victories!
<br />
<br />
Create or join duels, set an entry fee, and the winner takes all.
</p>
{[
{ step: "New here? Check out our", desc: "Whitepaper to understand how everything works." },
{ step: "Got questions or just wanna vibe with the squad?", desc: "Join our Telegram." },
].map(({ step, desc }, index) => (
<div key={index}>
<h3 className="text-[rgb(248,144,22)] font-bold text-lg">
{step}
</h3>
<p className="text-s text-gray-400">{desc}</p>
</div>
))}
</div>
<button
className="mt-6 w-full bg-[rgb(248,144,22)] text-black font-semibold py-2 rounded-xl transition hover:bg-white hover:scale-105"
onClick={onClose}
>
Okay
</button>
</div>
</div>
);
}

View File

@ -51,7 +51,7 @@ export default function GameHistoryModal({
); );
const gameData = res.data || []; const gameData = res.data || [];
setGamesHistory(gameData); setGamesHistory(gameData);
console.log(`history data ${gameData}`);
const opponentIds: string[] = gameData.map((game: GameHistory) => const opponentIds: string[] = gameData.map((game: GameHistory) =>
game.master_id === userId ? game.client_id : game.master_id game.master_id === userId ? game.client_id : game.master_id
); );

View File

@ -141,20 +141,24 @@ export default function Header() {
{/* Mobile Navigation Links */} {/* Mobile Navigation Links */}
<div className="flex flex-col gap-6"> <div className="flex flex-col gap-6">
<Link <a
href="/whitepaper" href={URL_WHITEPAPER}
target="_blank"
rel="noopener noreferrer"
className="text-white transition-colors duration-[500ms] hover:text-[rgb(248,144,22)]" className="text-white transition-colors duration-[500ms] hover:text-[rgb(248,144,22)]"
onClick={() => setIsDrawerOpen(false)} // Close drawer on link click onClick={() => setIsDrawerOpen(false)}
> >
Whitepaper Whitepaper
</Link> </a>
<Link <button
href="/support" onClick={() => {
className="text-white transition-colors duration-[500ms] hover:text-[rgb(248,144,22)]" setShowSupportModal(true);
onClick={() => setIsDrawerOpen(false)} // Close drawer on link click setIsDrawerOpen(false);
}}
className="text-white transition-colors duration-[500ms] hover:text-[rgb(248,144,22)] text-left"
> >
Support Support
</Link> </button>
{/* Socials */} {/* Socials */}
<a <a

View File

@ -5,6 +5,7 @@ import Image from "next/image";
import OpenGames from "./OpenGames"; import OpenGames from "./OpenGames";
import GameModal from "./GameModal"; import GameModal from "./GameModal";
import { HowItWorksModal } from "./HowItWorksModal"; import { HowItWorksModal } from "./HowItWorksModal";
import { FirstVisitModal } from "./FirstVisitModal";
import YourGames from "./YourGames"; import YourGames from "./YourGames";
import { Bet } from "@/types/Bet"; import { Bet } from "@/types/Bet";
import { fetchOpenBets, createBet, getVaultByAddress, fetchOpenBetsDev } from "@/shared/solana_helpers"; import { fetchOpenBets, createBet, getVaultByAddress, fetchOpenBetsDev } from "@/shared/solana_helpers";
@ -16,6 +17,7 @@ import { clusterApiUrl } from "@solana/web3.js";
export default function HeroSection() { export default function HeroSection() {
const [isModalOpen, setIsModalOpen] = useState(false); const [isModalOpen, setIsModalOpen] = useState(false);
const [isGameModalOpen, setIsGameModalOpen] = useState(false); const [isGameModalOpen, setIsGameModalOpen] = useState(false);
const [isFirstVisitModalOpen, setIsFirstVisitModalOpen] = useState(false);
const [bets, setBets] = useState<Bet[]>([]); const [bets, setBets] = useState<Bet[]>([]);
const [solWallet, setSolWallet] = useState<ConnectedSolanaWallet>(); const [solWallet, setSolWallet] = useState<ConnectedSolanaWallet>();
const [myActiveBet, setMyActiveBet] = useState<Bet>(); const [myActiveBet, setMyActiveBet] = useState<Bet>();
@ -28,6 +30,17 @@ export default function HeroSection() {
const { user } = usePrivy(); const { user } = usePrivy();
const iframeRef = useRef<HTMLIFrameElement>(null); const iframeRef = useRef<HTMLIFrameElement>(null);
// Check if this is the user's first visit
useEffect(() => {
if (typeof window !== 'undefined') {
const hasVisited = localStorage.getItem('duelfi_has_visited');
if (!hasVisited) {
setIsFirstVisitModalOpen(true);
localStorage.setItem('duelfi_has_visited', 'true');
}
}
}, []);
const game_close_signal = (status: number) => { const game_close_signal = (status: number) => {
setRematch(status == 1); setRematch(status == 1);
setMyActiveBet(undefined); setMyActiveBet(undefined);
@ -315,6 +328,7 @@ export default function HeroSection() {
<GameModal isOpen={isGameModalOpen} onClose={() => setIsGameModalOpen(false)} /> <GameModal isOpen={isGameModalOpen} onClose={() => setIsGameModalOpen(false)} />
<HowItWorksModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} /> <HowItWorksModal isOpen={isModalOpen} onClose={() => setIsModalOpen(false)} />
<FirstVisitModal isOpen={isFirstVisitModalOpen} onClose={() => setIsFirstVisitModalOpen(false)} />
</> </>
); );
} }

View File

@ -6,9 +6,9 @@ interface PriceSelectionProps {
} }
export function PriceSelection({ selectedPrice, onSelect }: PriceSelectionProps) { export function PriceSelection({ selectedPrice, onSelect }: PriceSelectionProps) {
const presets = [0.05, 0.1, 0.2, 0.5, 1.0]; const presets = [0.01, 0.05, 0.1, 0.2, 0.5, 1.0];
const [inputValue, setInputValue] = useState<string>(""); const [inputValue, setInputValue] = useState<string>("");
const MIN_AMOUNT = 0.05; const MIN_AMOUNT = 0.01;
useEffect(() => { useEffect(() => {
if (selectedPrice !== null) { if (selectedPrice !== null) {

View File

@ -14,6 +14,7 @@ import { WAGER_PRIZE_MULT } from "@/shared/constants";
import { EXPLORER_ADDRESS_TEMPLATE } from "@/data/shared"; import { EXPLORER_ADDRESS_TEMPLATE } from "@/data/shared";
interface GameHistory { interface GameHistory {
ended_time: string;
address: string; address: string;
master_score: string; master_score: string;
client_score: string; client_score: string;
@ -249,8 +250,10 @@ export default function PrivyButton() {
const fetchGames = async () => { const fetchGames = async () => {
setLoading(true); setLoading(true);
try { try {
const url= `${API_URL}get_game_history.php?uid=${user.id}`;
console.log(`history data ${url}`);
const res = await axios.get( const res = await axios.get(
`${API_URL}get_game_history.php?uid=${user.id}` url
); );
const gameData = res.data || []; const gameData = res.data || [];
setGamesHistory(gameData); setGamesHistory(gameData);
@ -522,94 +525,99 @@ export default function PrivyButton() {
<p className="text-gray-500">No games played yet.</p> <p className="text-gray-500">No games played yet.</p>
) : ( ) : (
<div className="space-y-4 max-h-96 overflow-y-auto"> <div className="space-y-4 max-h-96 overflow-y-auto">
{gamesHistory.map((game, idx) => { {gamesHistory
const isUserMaster = game.master_id === user.id; .sort((a, b) => new Date(b.ended_time).getTime() - new Date(a.ended_time).getTime())
const userScore = isUserMaster .map((game, idx) => {
? game.master_score const isUserMaster = game.master_id === user.id;
: game.client_score; const userScore = isUserMaster
const opponentScore = isUserMaster ? game.master_score
? game.client_score : game.client_score;
: game.master_score; const opponentScore = isUserMaster
const opponentId = isUserMaster ? game.client_score
? game.client_id : game.master_score;
: game.master_id; const opponentId = isUserMaster
const didUserWin = ? game.client_id
(isUserMaster && game.winner === "master") || : game.master_id;
(!isUserMaster && game.winner === "client"); const didUserWin =
(isUserMaster && game.winner === "master") ||
(!isUserMaster && game.winner === "client");
const opponent = opponentInfo[opponentId]; const opponent = opponentInfo[opponentId];
const profileUrl = const profileUrl =
opponent?.x_profile_url || opponent?.x_profile_url ||
(opponent?.username (opponent?.username
? `${API_URL}profile_picture/${opponent.username}.jpg` ? `${API_URL}profile_picture/${opponent.username}.jpg`
: "/duelfiassets/default-avatar.png"); : "/duelfiassets/default-avatar.png");
const gameImageUrl = gameImages[game.game] || "/duelfiassets/default-game-thumbnail.png"; const gameImageUrl = gameImages[game.game] || "/duelfiassets/default-game-thumbnail.png";
const wagerAmount = parseFloat(game.wager); const wagerAmount = parseFloat(game.wager);
const outcomeText = didUserWin const outcomeText = didUserWin
? `+${(wagerAmount / 1e8) * 2 * WAGER_PRIZE_MULT} SOL` ? `+${(wagerAmount / 1e8) * 2 * WAGER_PRIZE_MULT} SOL`
: `-${(wagerAmount / 1e8)} SOL`; : `-${(wagerAmount / 1e8)} SOL`;
return ( return (
<div <div
key={idx} key={idx}
className="relative border border-[rgb(30,30,30)] rounded-xl p-3 flex gap-3 items-center group" className="relative border border-[rgb(30,30,30)] rounded-xl p-3 flex gap-3 items-center group"
> >
<div className="flex-1"> <div className="flex-1">
<p className="text-sm font-semibold text-white"> <p className="text-sm font-semibold text-white">
{opponent?.username || "Unknown Opponent"} {opponent?.username || "Unknown Opponent"}
</p> </p>
<p className="text-xs text-gray-400"> <p className="text-xs text-gray-400">
Score: {userScore} - {opponentScore} Score: {userScore} - {opponentScore}
</p> </p>
<p <p className="text-xs text-gray-400">
className={`text-sm font-semibold ${ {new Date(game.ended_time).toLocaleString()}
didUserWin ? "text-green-500" : "text-gray-500" </p>
}`} <p
> className={`text-sm font-semibold ${
{didUserWin ? "You won" : "You lost"} didUserWin ? "text-green-500" : "text-gray-500"
</p> }`}
<p className={`text-xs ${didUserWin ? "text-green-500" : "text-red-500"}`}> >
{outcomeText} {didUserWin ? "You won" : "You lost"}
</p> </p>
<p className={`text-xs ${didUserWin ? "text-green-500" : "text-red-500"}`}>
{outcomeText}
</p>
</div>
<Image
src={failedImages.has(profileUrl) ? defaultPFP : profileUrl}
alt="Profile"
width={40}
height={40}
className="w-10 h-10 rounded-full border border-gray-700 object-cover"
onError={(e) => {
// @ts-expect-error - Type mismatch expected
e.target.src = defaultPFP;
setFailedImages(prev => new Set(prev).add(profileUrl));
}}
/>
<Image
src={failedImages.has(gameImageUrl) ? "/duelfiassets/default-game-thumbnail.png" : gameImageUrl}
alt="Game Thumbnail"
width={64}
height={64}
className="w-16 h-16 rounded-md object-cover ml-4"
onError={(e) => {
// @ts-expect-error - Type mismatch expected
e.target.src = "/duelfiassets/default-game-thumbnail.png";
setFailedImages(prev => new Set(prev).add(gameImageUrl));
}}
/>
<div className="absolute top-0 right-0 h-full w-28 bg-blue-500 text-white flex items-center justify-center opacity-0 group-hover:opacity-100 group-hover:translate-x-0 transition-all">
<button
onClick={() => handleViewTxClick(game.address)}
className="px-4 py-2 text-sm font-semibold"
>
View TX
</button>
</div>
</div> </div>
<Image );
src={failedImages.has(profileUrl) ? defaultPFP : profileUrl} })}
alt="Profile"
width={40}
height={40}
className="w-10 h-10 rounded-full border border-gray-700 object-cover"
onError={(e) => {
// @ts-expect-error - Type mismatch expected
e.target.src = defaultPFP;
setFailedImages(prev => new Set(prev).add(profileUrl));
}}
/>
<Image
src={failedImages.has(gameImageUrl) ? "/duelfiassets/default-game-thumbnail.png" : gameImageUrl}
alt="Game Thumbnail"
width={64}
height={64}
className="w-16 h-16 rounded-md object-cover ml-4"
onError={(e) => {
// @ts-expect-error - Type mismatch expected
e.target.src = "/duelfiassets/default-game-thumbnail.png";
setFailedImages(prev => new Set(prev).add(gameImageUrl));
}}
/>
<div className="absolute top-0 right-0 h-full w-28 bg-blue-500 text-white flex items-center justify-center opacity-0 group-hover:opacity-100 group-hover:translate-x-0 transition-all">
<button
onClick={() => handleViewTxClick(game.address)}
className="px-4 py-2 text-sm font-semibold"
>
View TX
</button>
</div>
</div>
);
})}
</div> </div>
)} )}
</div> </div>