polished a bit
This commit is contained in:
parent
147ef9e669
commit
6a4de52d06
Binary file not shown.
|
Before Width: | Height: | Size: 4 B After Width: | Height: | Size: 15 KiB |
|
|
@ -154,3 +154,15 @@ body {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transform: translateX(0); /* Slide the button to the left */
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { URL_TELEGRAM, URL_TWITTER } from "@/shared/constants";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
|
|
@ -22,7 +23,7 @@ export default function Footer() {
|
||||||
{/* Social Buttons */}
|
{/* Social Buttons */}
|
||||||
<div className="flex gap-4">
|
<div className="flex gap-4">
|
||||||
<a
|
<a
|
||||||
href="https://t.me/yourchannel"
|
href={URL_TELEGRAM}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="transition-transform duration-300 hover:scale-110"
|
className="transition-transform duration-300 hover:scale-110"
|
||||||
|
|
@ -35,7 +36,7 @@ export default function Footer() {
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="https://twitter.com/yourhandle"
|
href={URL_TWITTER}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="transition-transform duration-300 hover:scale-110"
|
className="transition-transform duration-300 hover:scale-110"
|
||||||
|
|
|
||||||
|
|
@ -14,9 +14,13 @@ export function GameSelection({ games, selectedGame, onSelect }: GameSelectionPr
|
||||||
<div
|
<div
|
||||||
key={game.id}
|
key={game.id}
|
||||||
className={`p-4 cursor-pointer rounded-xl border-2 transition-transform duration-300
|
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)]"}
|
${selectedGame?.id === game.id
|
||||||
hover:scale-105 hover:border-gray-400`}
|
? "scale-110 border-gray-400 bg-gray-800"
|
||||||
onClick={() => onSelect(game)}
|
: 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
|
<Image
|
||||||
src={game.thumbnail}
|
src={game.thumbnail}
|
||||||
|
|
@ -26,6 +30,9 @@ export function GameSelection({ games, selectedGame, onSelect }: GameSelectionPr
|
||||||
className="mb-2 rounded-md"
|
className="mb-2 rounded-md"
|
||||||
/>
|
/>
|
||||||
<h3 className="text-center text-sm">{game.name}</h3>
|
<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>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import Image from "next/image";
|
||||||
import { FaTelegram, FaXTwitter } from "react-icons/fa6";
|
import { FaTelegram, FaXTwitter } from "react-icons/fa6";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import PrivyButton from "./PrivyButton";
|
import PrivyButton from "./PrivyButton";
|
||||||
|
import { URL_TELEGRAM, URL_TWITTER, URL_WHITEPAPER } from "@/shared/constants";
|
||||||
|
|
||||||
export default function Header() {
|
export default function Header() {
|
||||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||||
|
|
@ -25,7 +26,7 @@ export default function Header() {
|
||||||
{/* Navigation Links (hidden on mobile, visible on md+) */}
|
{/* Navigation Links (hidden on mobile, visible on md+) */}
|
||||||
<nav className="hidden md:flex gap-6 ml-6">
|
<nav className="hidden md:flex gap-6 ml-6">
|
||||||
<Link
|
<Link
|
||||||
href="/whitepaper"
|
href={URL_WHITEPAPER}
|
||||||
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)]"
|
||||||
>
|
>
|
||||||
Whitepaper
|
Whitepaper
|
||||||
|
|
@ -44,7 +45,7 @@ export default function Header() {
|
||||||
{/* Socials & Login (hidden on mobile, visible on md+) */}
|
{/* Socials & Login (hidden on mobile, visible on md+) */}
|
||||||
<div className="hidden md:flex items-center gap-6">
|
<div className="hidden md:flex items-center gap-6">
|
||||||
<a
|
<a
|
||||||
href="https://t.me/yourchannel"
|
href={URL_TELEGRAM}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="transition-transform duration-300 hover:scale-120"
|
className="transition-transform duration-300 hover:scale-120"
|
||||||
|
|
@ -57,7 +58,7 @@ export default function Header() {
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
<a
|
<a
|
||||||
href="https://twitter.com/yourhandle"
|
href={URL_TWITTER}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="transition-transform duration-300 hover:scale-120"
|
className="transition-transform duration-300 hover:scale-120"
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ export default function YourGames({ bets }: GameModalProps) {
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const { user } = usePrivy();
|
const { user } = usePrivy();
|
||||||
const [selectedBet, setSelectedBet] = useState<Bet | null>(null); // Track selected bet
|
const [selectedBet, setSelectedBet] = useState<Bet | null>(null); // Track selected bet
|
||||||
|
const [isProcessing, setIsProcessing] = useState(false); // Track processing state
|
||||||
|
|
||||||
const handleJoinGame = async () => {
|
const handleJoinGame = async () => {
|
||||||
if (!selectedBet) return;
|
if (!selectedBet) return;
|
||||||
|
|
@ -29,27 +30,35 @@ export default function YourGames({ bets }: GameModalProps) {
|
||||||
wallet = _wallet;
|
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) {
|
setIsProcessing(true);
|
||||||
connection.confirmTransaction(tx, CONFIRMATION_THRESHOLD).finally(()=>{
|
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.dismiss();
|
||||||
|
|
||||||
toast.success("Joined game successfully!", {
|
toast.error("Failed to join this game");
|
||||||
action: {
|
}
|
||||||
label: "View TX",
|
} catch (error) {
|
||||||
onClick: () => window.open(url, "_blank"),
|
console.error("Error joining bet:", error);
|
||||||
},
|
|
||||||
});
|
|
||||||
})
|
|
||||||
|
|
||||||
} else {
|
|
||||||
toast.error("Failed to join this game");
|
toast.error("Failed to join this game");
|
||||||
|
} finally {
|
||||||
|
setIsProcessing(false);
|
||||||
|
setSelectedBet(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
setSelectedBet(null); // Close modal
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateBets = async () => {
|
const updateBets = async () => {
|
||||||
|
|
@ -150,29 +159,88 @@ export default function YourGames({ bets }: GameModalProps) {
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
{selectedBet && (
|
{selectedBet && (
|
||||||
<div className="fixed inset-0 flex items-center justify-center bg-black bg-opacity-50 z-50">
|
<div className="fixed inset-0 flex items-center justify-center z-50 backdrop-blur-sm bg-black/50">
|
||||||
<div className="bg-gray-900 text-white p-6 rounded-lg shadow-lg max-w-sm w-full">
|
<div className="bg-[rgb(30,30,30)] 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>
|
{isProcessing ? (
|
||||||
<p><strong>Wager:</strong> {selectedBet.wager} SOL | <strong>Prize:</strong> {(selectedBet.wager * 2).toFixed(2)} SOL</p>
|
<div className="flex flex-col items-center">
|
||||||
<p><strong>Game:</strong> {games.find(g => g.id === selectedBet.id)?.name}</p>
|
<svg
|
||||||
<div className="flex items-center mt-3">
|
className="animate-spin h-8 w-8 text-blue-400 mb-4"
|
||||||
{selectedBet.ownerProfile && (
|
viewBox="0 0 24 24"
|
||||||
<>
|
fill="none"
|
||||||
<Image
|
>
|
||||||
src={selectedBet.ownerProfile.x_profile_url || `https://vps.playpoolstudios.com/duelfi/profile_pics/${selectedBet.ownerProfile.id}.jpg`}
|
<circle
|
||||||
alt={selectedBet.ownerProfile.username}
|
className="opacity-25"
|
||||||
width={32}
|
cx="12"
|
||||||
height={32}
|
cy="12"
|
||||||
className="w-8 h-8 rounded-full mr-2"
|
r="10"
|
||||||
/>
|
stroke="currentColor"
|
||||||
<p><strong>Offered by:</strong> {selectedBet.ownerProfile.username}</p>
|
strokeWidth="4"
|
||||||
</>
|
></circle>
|
||||||
)}
|
<path
|
||||||
</div>
|
className="opacity-75"
|
||||||
<div className="flex justify-end gap-3 mt-4">
|
fill="currentColor"
|
||||||
<button onClick={() => setSelectedBet(null)} className="bg-gray-700 px-4 py-2 rounded">Cancel</button>
|
d="M4 12a8 8 0 018-8v8H4z"
|
||||||
<button onClick={handleJoinGame} className="bg-blue-500 px-4 py-2 rounded">Confirm</button>
|
></path>
|
||||||
</div>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
interface PriceSelectionProps {
|
interface PriceSelectionProps {
|
||||||
|
|
@ -6,27 +7,74 @@ interface PriceSelectionProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PriceSelection({ selectedPrice, onSelect }: 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 (
|
return (
|
||||||
<div className="mb-6 space-y-2">
|
<div className="mb-6 space-y-2">
|
||||||
{prices.map((price) => (
|
<label className="block text-sm text-gray-300 font-mono mb-1">Select Currency</label>
|
||||||
<p
|
|
||||||
key={price}
|
<select
|
||||||
className={`flex items-center gap-2 text-gray-400 font-mono cursor-pointer transition-transform duration-500
|
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"
|
||||||
${selectedPrice === price ? "text-white translate-x-2" : ""}`}
|
>
|
||||||
onClick={() => onSelect(price)}
|
<option>SOL</option>
|
||||||
>
|
</select>
|
||||||
<Image
|
<label className="block text-sm text-gray-300 font-mono mb-1">Enter Price (SOL)</label>
|
||||||
src="/duelfiassets/solana logo.png"
|
<div className="flex items-center gap-2">
|
||||||
alt="SOL"
|
<input
|
||||||
width={16}
|
type="number"
|
||||||
height={16}
|
step="0.01"
|
||||||
className="inline"
|
value={inputValue}
|
||||||
/>
|
onChange={handleInputChange}
|
||||||
{price} SOL
|
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"
|
||||||
</p>
|
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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,19 +6,22 @@ export const games = [
|
||||||
name: "Block Drop",
|
name: "Block Drop",
|
||||||
entryFee: "0.1 SOL",
|
entryFee: "0.1 SOL",
|
||||||
thumbnail: "/duelfiassets/Block Drop Illustration.jpeg",
|
thumbnail: "/duelfiassets/Block Drop Illustration.jpeg",
|
||||||
|
isAvailable: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "snakes",
|
id: "snakes",
|
||||||
name: "Venom Trails",
|
name: "Venom Trails",
|
||||||
entryFee: "0.02 ETH",
|
entryFee: "0.02 ETH",
|
||||||
thumbnail: "/duelfiassets/Venom Trail Game Cover Illustration.png",
|
thumbnail: "/duelfiassets/Venom Trail Game Cover Illustration.png",
|
||||||
|
isAvailable: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "bubbles",
|
id: "bubbles",
|
||||||
name: "Wall Smash",
|
name: "Wall Smash",
|
||||||
entryFee: "0.05 ETH",
|
entryFee: "0.05 ETH",
|
||||||
thumbnail: "/duelfiassets/Wall Smash Game Cover Illustration.png",
|
thumbnail: "/duelfiassets/Wall Smash Game Cover Illustration.png",
|
||||||
},
|
isAvailable: false
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
export function GetGameByID(id:string):Game | undefined{
|
export function GetGameByID(id:string):Game | undefined{
|
||||||
|
|
|
||||||
|
|
@ -3,4 +3,8 @@ import { Commitment, PublicKey } from "@solana/web3.js";
|
||||||
export const FEE_COLLECTOR_PUBKEY = new PublicKey("cocD4r4yNpHxPq7CzUebxEMyLki3X4d2Y3HcTX5ptUc");
|
export const FEE_COLLECTOR_PUBKEY = new PublicKey("cocD4r4yNpHxPq7CzUebxEMyLki3X4d2Y3HcTX5ptUc");
|
||||||
export const WAGER_PRIZE_MULT = 0.95;
|
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";
|
||||||
|
|
@ -3,4 +3,5 @@ export interface Game {
|
||||||
name: string;
|
name: string;
|
||||||
entryFee: string;
|
entryFee: string;
|
||||||
thumbnail: string;
|
thumbnail: string;
|
||||||
|
isAvailable: boolean;
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user