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 || [];
setGamesHistory(gameData);
console.log(`history data ${gameData}`);
const opponentIds: string[] = gameData.map((game: GameHistory) =>
game.master_id === userId ? game.client_id : game.master_id
);

View File

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

View File

@ -5,6 +5,7 @@ import Image from "next/image";
import OpenGames from "./OpenGames";
import GameModal from "./GameModal";
import { HowItWorksModal } from "./HowItWorksModal";
import { FirstVisitModal } from "./FirstVisitModal";
import YourGames from "./YourGames";
import { Bet } from "@/types/Bet";
import { fetchOpenBets, createBet, getVaultByAddress, fetchOpenBetsDev } from "@/shared/solana_helpers";
@ -16,6 +17,7 @@ import { clusterApiUrl } from "@solana/web3.js";
export default function HeroSection() {
const [isModalOpen, setIsModalOpen] = useState(false);
const [isGameModalOpen, setIsGameModalOpen] = useState(false);
const [isFirstVisitModalOpen, setIsFirstVisitModalOpen] = useState(false);
const [bets, setBets] = useState<Bet[]>([]);
const [solWallet, setSolWallet] = useState<ConnectedSolanaWallet>();
const [myActiveBet, setMyActiveBet] = useState<Bet>();
@ -28,6 +30,17 @@ export default function HeroSection() {
const { user } = usePrivy();
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) => {
setRematch(status == 1);
setMyActiveBet(undefined);
@ -315,6 +328,7 @@ export default function HeroSection() {
<GameModal isOpen={isGameModalOpen} onClose={() => setIsGameModalOpen(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) {
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 MIN_AMOUNT = 0.05;
const MIN_AMOUNT = 0.01;
useEffect(() => {
if (selectedPrice !== null) {

View File

@ -14,6 +14,7 @@ import { WAGER_PRIZE_MULT } from "@/shared/constants";
import { EXPLORER_ADDRESS_TEMPLATE } from "@/data/shared";
interface GameHistory {
ended_time: string;
address: string;
master_score: string;
client_score: string;
@ -249,8 +250,10 @@ export default function PrivyButton() {
const fetchGames = async () => {
setLoading(true);
try {
const url= `${API_URL}get_game_history.php?uid=${user.id}`;
console.log(`history data ${url}`);
const res = await axios.get(
`${API_URL}get_game_history.php?uid=${user.id}`
url
);
const gameData = res.data || [];
setGamesHistory(gameData);
@ -522,94 +525,99 @@ export default function PrivyButton() {
<p className="text-gray-500">No games played yet.</p>
) : (
<div className="space-y-4 max-h-96 overflow-y-auto">
{gamesHistory.map((game, idx) => {
const isUserMaster = game.master_id === user.id;
const userScore = isUserMaster
? game.master_score
: game.client_score;
const opponentScore = isUserMaster
? game.client_score
: game.master_score;
const opponentId = isUserMaster
? game.client_id
: game.master_id;
const didUserWin =
(isUserMaster && game.winner === "master") ||
(!isUserMaster && game.winner === "client");
{gamesHistory
.sort((a, b) => new Date(b.ended_time).getTime() - new Date(a.ended_time).getTime())
.map((game, idx) => {
const isUserMaster = game.master_id === user.id;
const userScore = isUserMaster
? game.master_score
: game.client_score;
const opponentScore = isUserMaster
? game.client_score
: game.master_score;
const opponentId = isUserMaster
? game.client_id
: game.master_id;
const didUserWin =
(isUserMaster && game.winner === "master") ||
(!isUserMaster && game.winner === "client");
const opponent = opponentInfo[opponentId];
const profileUrl =
opponent?.x_profile_url ||
(opponent?.username
? `${API_URL}profile_picture/${opponent.username}.jpg`
: "/duelfiassets/default-avatar.png");
const opponent = opponentInfo[opponentId];
const profileUrl =
opponent?.x_profile_url ||
(opponent?.username
? `${API_URL}profile_picture/${opponent.username}.jpg`
: "/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 outcomeText = didUserWin
? `+${(wagerAmount / 1e8) * 2 * WAGER_PRIZE_MULT} SOL`
: `-${(wagerAmount / 1e8)} SOL`;
const wagerAmount = parseFloat(game.wager);
const outcomeText = didUserWin
? `+${(wagerAmount / 1e8) * 2 * WAGER_PRIZE_MULT} SOL`
: `-${(wagerAmount / 1e8)} SOL`;
return (
<div
key={idx}
className="relative border border-[rgb(30,30,30)] rounded-xl p-3 flex gap-3 items-center group"
>
<div className="flex-1">
<p className="text-sm font-semibold text-white">
{opponent?.username || "Unknown Opponent"}
</p>
<p className="text-xs text-gray-400">
Score: {userScore} - {opponentScore}
</p>
<p
className={`text-sm font-semibold ${
didUserWin ? "text-green-500" : "text-gray-500"
}`}
>
{didUserWin ? "You won" : "You lost"}
</p>
<p className={`text-xs ${didUserWin ? "text-green-500" : "text-red-500"}`}>
{outcomeText}
</p>
return (
<div
key={idx}
className="relative border border-[rgb(30,30,30)] rounded-xl p-3 flex gap-3 items-center group"
>
<div className="flex-1">
<p className="text-sm font-semibold text-white">
{opponent?.username || "Unknown Opponent"}
</p>
<p className="text-xs text-gray-400">
Score: {userScore} - {opponentScore}
</p>
<p className="text-xs text-gray-400">
{new Date(game.ended_time).toLocaleString()}
</p>
<p
className={`text-sm font-semibold ${
didUserWin ? "text-green-500" : "text-gray-500"
}`}
>
{didUserWin ? "You won" : "You lost"}
</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>
<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>