little bugs
This commit is contained in:
parent
49ed569cb4
commit
a230c97078
67
src/components/FirstVisitModal.tsx
Normal file
67
src/components/FirstVisitModal.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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)} />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user