polished a bit

This commit is contained in:
Sewmina 2025-04-09 17:46:57 +05:30
parent 147ef9e669
commit 6a4de52d06
10 changed files with 212 additions and 67 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4 B

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -154,3 +154,15 @@ body {
opacity: 1;
transform: translateX(0); /* Slide the button to the left */
}
/* Remove spinner arrows in number inputs for WebKit (Chrome, Safari) */
input[type='number']::-webkit-outer-spin-button,
input[type='number']::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
/* Remove spinner arrows for Firefox */
input[type='number'] {
-moz-appearance: textfield;
}

View File

@ -1,5 +1,6 @@
"use client";
import { URL_TELEGRAM, URL_TWITTER } from "@/shared/constants";
import Image from "next/image";
import Link from "next/link";
@ -22,7 +23,7 @@ export default function Footer() {
{/* Social Buttons */}
<div className="flex gap-4">
<a
href="https://t.me/yourchannel"
href={URL_TELEGRAM}
target="_blank"
rel="noopener noreferrer"
className="transition-transform duration-300 hover:scale-110"
@ -35,7 +36,7 @@ export default function Footer() {
/>
</a>
<a
href="https://twitter.com/yourhandle"
href={URL_TWITTER}
target="_blank"
rel="noopener noreferrer"
className="transition-transform duration-300 hover:scale-110"

View File

@ -14,9 +14,13 @@ export function GameSelection({ games, selectedGame, onSelect }: GameSelectionPr
<div
key={game.id}
className={`p-4 cursor-pointer rounded-xl border-2 transition-transform duration-300
${selectedGame?.id === game.id ? "scale-110 border-gray-400 bg-gray-800" : "border-gray-600 bg-[rgb(10,10,10)]"}
hover:scale-105 hover:border-gray-400`}
onClick={() => onSelect(game)}
${selectedGame?.id === game.id
? "scale-110 border-gray-400 bg-gray-800"
: game.isAvailable
? "border-gray-600 bg-[rgb(10,10,10)] hover:scale-105 hover:border-gray-400"
: "border-gray-800 bg-[rgb(5,5,5)] opacity-50 cursor-not-allowed"
}`}
onClick={() => game.isAvailable && onSelect(game)}
>
<Image
src={game.thumbnail}
@ -26,6 +30,9 @@ export function GameSelection({ games, selectedGame, onSelect }: GameSelectionPr
className="mb-2 rounded-md"
/>
<h3 className="text-center text-sm">{game.name}</h3>
{!game.isAvailable && (
<p className="text-center text-xs text-gray-500 mt-1">Coming Soon</p>
)}
</div>
))}
</div>

View File

@ -5,6 +5,7 @@ import Image from "next/image";
import { FaTelegram, FaXTwitter } from "react-icons/fa6";
import { useState } from "react";
import PrivyButton from "./PrivyButton";
import { URL_TELEGRAM, URL_TWITTER, URL_WHITEPAPER } from "@/shared/constants";
export default function Header() {
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
@ -25,7 +26,7 @@ export default function Header() {
{/* Navigation Links (hidden on mobile, visible on md+) */}
<nav className="hidden md:flex gap-6 ml-6">
<Link
href="/whitepaper"
href={URL_WHITEPAPER}
className="text-white transition-colors duration-[500ms] hover:text-[rgb(248,144,22)]"
>
Whitepaper
@ -44,7 +45,7 @@ export default function Header() {
{/* Socials & Login (hidden on mobile, visible on md+) */}
<div className="hidden md:flex items-center gap-6">
<a
href="https://t.me/yourchannel"
href={URL_TELEGRAM}
target="_blank"
rel="noopener noreferrer"
className="transition-transform duration-300 hover:scale-120"
@ -57,7 +58,7 @@ export default function Header() {
/>
</a>
<a
href="https://twitter.com/yourhandle"
href={URL_TWITTER}
target="_blank"
rel="noopener noreferrer"
className="transition-transform duration-300 hover:scale-120"

View File

@ -19,6 +19,7 @@ export default function YourGames({ bets }: GameModalProps) {
const [loading, setLoading] = useState(true);
const { user } = usePrivy();
const [selectedBet, setSelectedBet] = useState<Bet | null>(null); // Track selected bet
const [isProcessing, setIsProcessing] = useState(false); // Track processing state
const handleJoinGame = async () => {
if (!selectedBet) return;
@ -29,27 +30,35 @@ export default function YourGames({ bets }: GameModalProps) {
wallet = _wallet;
}
});
toast.loading("Joining Bet");
const tx = await joinBet(wallet, user?.id ?? "", selectedBet.id, selectedBet.address);
const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx);
if (tx.length > 5) {
connection.confirmTransaction(tx, CONFIRMATION_THRESHOLD).finally(()=>{
setIsProcessing(true);
toast.loading("Joining Bet");
try {
const tx = await joinBet(wallet, user?.id ?? "", selectedBet.id, selectedBet.address);
const url = EXPLORER_TX_TEMPLATE.replace("{address}", tx);
if (tx.length > 5) {
connection.confirmTransaction(tx, CONFIRMATION_THRESHOLD).finally(()=>{
toast.dismiss();
toast.success("Joined game successfully!", {
action: {
label: "View TX",
onClick: () => window.open(url, "_blank"),
},
});
})
} else {
toast.dismiss();
toast.success("Joined game successfully!", {
action: {
label: "View TX",
onClick: () => window.open(url, "_blank"),
},
});
})
} else {
toast.error("Failed to join this game");
}
} catch (error) {
console.error("Error joining bet:", error);
toast.error("Failed to join this game");
} finally {
setIsProcessing(false);
setSelectedBet(null);
}
setSelectedBet(null); // Close modal
};
const updateBets = async () => {
@ -150,29 +159,88 @@ export default function YourGames({ bets }: GameModalProps) {
{/* Modal */}
{selectedBet && (
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50">
<div className="bg-gray-900 text-white p-6 rounded-lg shadow-lg max-w-sm w-full">
<h2 className="text-xl font-bold mb-4">Are you sure to join this bet?</h2>
<p><strong>Wager:</strong> {selectedBet.wager} SOL | <strong>Prize:</strong> {(selectedBet.wager * 2).toFixed(2)} SOL</p>
<p><strong>Game:</strong> {games.find(g => g.id === selectedBet.id)?.name}</p>
<div className="flex items-center mt-3">
{selectedBet.ownerProfile && (
<>
<Image
src={selectedBet.ownerProfile.x_profile_url || `https://vps.playpoolstudios.com/duelfi/profile_pics/${selectedBet.ownerProfile.id}.jpg`}
alt={selectedBet.ownerProfile.username}
width={32}
height={32}
className="w-8 h-8 rounded-full mr-2"
/>
<p><strong>Offered by:</strong> {selectedBet.ownerProfile.username}</p>
</>
)}
</div>
<div className="flex justify-end gap-3 mt-4">
<button onClick={() => setSelectedBet(null)} className="bg-gray-700 px-4 py-2 rounded">Cancel</button>
<button onClick={handleJoinGame} className="bg-blue-500 px-4 py-2 rounded">Confirm</button>
</div>
<div className="fixed inset-0 flex items-center justify-center z-50 backdrop-blur-sm bg-black/50">
<div className="bg-[rgb(30,30,30)] text-white p-6 rounded-lg shadow-lg max-w-sm w-full">
{isProcessing ? (
<div className="flex flex-col items-center">
<svg
className="animate-spin h-8 w-8 text-blue-400 mb-4"
viewBox="0 0 24 24"
fill="none"
>
<circle
className="opacity-25"
cx="12"
cy="12"
r="10"
stroke="currentColor"
strokeWidth="4"
></circle>
<path
className="opacity-75"
fill="currentColor"
d="M4 12a8 8 0 018-8v8H4z"
></path>
</svg>
<h3 className="text-lg font-semibold">Processing...</h3>
<p className="text-gray-400 text-sm mt-2">Joining the bet, please wait...</p>
</div>
) : (
<>
<h2 className="text-xl font-bold mb-4">Are you sure to join this bet?</h2>
<div className="flex gap-4 mb-4">
<div className="w-1/2 relative h-48 overflow-hidden rounded-xl">
<Image
src={games.find(g => g.id === selectedBet.id)?.thumbnail || ''}
alt={games.find(g => g.id === selectedBet.id)?.name || ''}
layout="fill"
objectFit="cover"
/>
</div>
<div className="w-1/2 flex flex-col justify-center space-y-4">
{selectedBet.ownerProfile && (
<>
<p className="text-gray-400 mb-2">Offered by:</p>
<div className="flex items-center">
<Image
src={selectedBet.ownerProfile.x_profile_url || `https://vps.playpoolstudios.com/duelfi/profile_pics/${selectedBet.ownerProfile.id}.jpg`}
alt={selectedBet.ownerProfile.username}
width={32}
height={32}
className="w-8 h-8 rounded-full mr-2"
/>
<p className="font-bold">{selectedBet.ownerProfile.username}</p>
</div>
</>
)}
<div className="flex justify-between items-center">
<span className="text-gray-400">Wager:</span>
<span className="font-bold">{selectedBet.wager} SOL</span>
</div>
<div className="flex justify-between items-center">
<span className="text-gray-400">Prize:</span>
<span className="font-bold">{(selectedBet.wager * 2).toFixed(2)} SOL</span>
</div>
</div>
</div>
<div className="flex justify-end gap-3 mt-4">
<button
onClick={() => setSelectedBet(null)}
className="bg-gray-700 px-4 py-2 rounded hover:scale-105 transition-all duration-300"
disabled={isProcessing}
>
Cancel
</button>
<button
onClick={handleJoinGame}
className="bg-[rgb(248,144,22)] text-black font-bold px-4 py-2 rounded hover:scale-105 transition-all duration-300"
disabled={isProcessing}
>
Confirm
</button>
</div>
</>
)}
</div>
</div>
)}

View File

@ -1,3 +1,4 @@
import { useState, useEffect } from "react";
import Image from "next/image";
interface PriceSelectionProps {
@ -6,27 +7,74 @@ interface PriceSelectionProps {
}
export function PriceSelection({ selectedPrice, onSelect }: PriceSelectionProps) {
const prices = [0.2, 0.5, 0.8];
const presets = [0.05, 0.1, 0.2, 0.5, 1.0];
const [inputValue, setInputValue] = useState<string>("");
const MIN_AMOUNT = 0.05;
useEffect(() => {
if (selectedPrice !== null) {
setInputValue(selectedPrice.toString());
}
}, [selectedPrice]);
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;
setInputValue(value);
const parsed = parseFloat(value);
if (!isNaN(parsed) && parsed >= MIN_AMOUNT) {
onSelect(parsed);
}
};
const handlePresetClick = (value: number) => {
onSelect(value);
setInputValue(value.toString());
};
return (
<div className="mb-6 space-y-2">
{prices.map((price) => (
<p
key={price}
className={`flex items-center gap-2 text-gray-400 font-mono cursor-pointer transition-transform duration-500
${selectedPrice === price ? "text-white translate-x-2" : ""}`}
onClick={() => onSelect(price)}
>
<Image
src="/duelfiassets/solana logo.png"
alt="SOL"
width={16}
height={16}
className="inline"
/>
{price} SOL
</p>
))}
<label className="block text-sm text-gray-300 font-mono mb-1">Select Currency</label>
<select
className="bg-[rgb(10,10,10)] border border-gray-700 text-[rgb(248,144,22)] font-mono px-2 py-1 rounded-lg w-full cursor-pointer"
>
<option>SOL</option>
</select>
<label className="block text-sm text-gray-300 font-mono mb-1">Enter Price (SOL)</label>
<div className="flex items-center gap-2">
<input
type="number"
step="0.01"
value={inputValue}
onChange={handleInputChange}
className="bg-[rgb(10,10,10)] border border-gray-700 text-[rgb(248,144,22)] font-mono px-2 py-1 rounded-lg flex-1 appearance-none no-spinner"
placeholder="e.g. 0.5"
/>
</div>
<div className="flex items-center gap-2">
<div className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 text-[rgb(248,144,22)]">
<svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
<div className="flex gap-2 mt-2">
{presets.map((price) => (
<button
key={price}
onClick={() => handlePresetClick(price)}
className={`px-3 py-1 rounded font-mono text-sm border transition
${selectedPrice === price
? "bg-[rgb(248,144,22)] text-black font-bold border-white scale-105"
: "bg-[rgb(10,10,10)] text-white border-gray-600 hover:bg-[rgb(30,30,30)] hover:scale-105"
}`}
>
{price}
</button>
))}
</div>
</div>
);
}

View File

@ -6,19 +6,22 @@ export const games = [
name: "Block Drop",
entryFee: "0.1 SOL",
thumbnail: "/duelfiassets/Block Drop Illustration.jpeg",
isAvailable: true
},
{
id: "snakes",
name: "Venom Trails",
entryFee: "0.02 ETH",
thumbnail: "/duelfiassets/Venom Trail Game Cover Illustration.png",
isAvailable: false
},
{
id: "bubbles",
name: "Wall Smash",
entryFee: "0.05 ETH",
thumbnail: "/duelfiassets/Wall Smash Game Cover Illustration.png",
},
isAvailable: false
}
];
export function GetGameByID(id:string):Game | undefined{

View File

@ -3,4 +3,8 @@ import { Commitment, PublicKey } from "@solana/web3.js";
export const FEE_COLLECTOR_PUBKEY = new PublicKey("cocD4r4yNpHxPq7CzUebxEMyLki3X4d2Y3HcTX5ptUc");
export const WAGER_PRIZE_MULT = 0.95;
export const CONFIRMATION_THRESHOLD:Commitment = 'finalized';
export const CONFIRMATION_THRESHOLD:Commitment = 'finalized';
export const URL_TELEGRAM = "https://t.me/duelfidotio";
export const URL_TWITTER = "https://x.com/duelfidotio";
export const URL_WHITEPAPER = "https://duelfi-io.gitbook.io/whitepaper";

View File

@ -3,4 +3,5 @@ export interface Game {
name: string;
entryFee: string;
thumbnail: string;
isAvailable: boolean;
}