Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 228 additions & 0 deletions src/app/dashboard/pool-market/[id]/components/StakePool.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { truncate } from '@/lib/utils';
import React, { useState } from 'react';
import { FaSpinner } from 'react-icons/fa';
import useStakePool from '@/app/hooks/useStakePool';
import useWalletBalance from '@/app/hooks/useWalletBalance';
import ConnectWalletModal from '@/components/ConnectWalletModal';

interface StakePoolProps {
poolId: string;
isConnected?: boolean;
address?: string;
onStakeSuccess?: () => void;
}

export default function StakePool({
poolId,
isConnected,
address,
onStakeSuccess
}: StakePoolProps) {
const [stakeAmount, setStakeAmount] = useState('');
const [showConnectModal, setShowConnectModal] = useState(false);
const { stakeStatus, stakeOnPool } = useStakePool(poolId);

// Get wallet balance using the dedicated hook
const { balance: walletBalance, isLoading: balanceLoading } = useWalletBalance({
address,
enabled: isConnected,
});

const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const value = e.target.value;

// Allow empty string
if (value === '') {
setStakeAmount('');
return;
}

// Only allow valid number patterns (digits and single decimal point)
const numberRegex = /^\d*\.?\d*$/;

if (numberRegex.test(value)) {
// Prevent multiple decimal points
const decimalCount = (value.match(/\./g) || []).length;
if (decimalCount <= 1) {
// Convert to number to check if it's positive
const numValue = parseFloat(value);
if (isNaN(numValue) || numValue >= 0) {
setStakeAmount(value);
}
}
}
};

const handleStake = async () => {
await stakeOnPool(stakeAmount);
if (onStakeSuccess) {
onStakeSuccess();
}
// Clear the input after successful stake
setStakeAmount('');
};

const isLoading = stakeStatus === 'pending';
const stakeAmountNum = parseFloat(stakeAmount) || 0;
const isValidAmount = stakeAmount && stakeAmountNum > 0;
const isMinimumStake = stakeAmountNum >= 10;
const hasSufficientBalance = stakeAmountNum <= walletBalance;
const canStake = isValidAmount && isMinimumStake && hasSufficientBalance && isConnected;

return (
<div className="p-2">
<p className="text-sm text-gray-400 mb-4">
Stake tokens on this pool without voting on a specific outcome.
Your stake will be distributed proportionally based on the final results.
</p>

<div className="mb-6">
<div className="flex justify-between items-center mb-3">
<label className="text-sm font-medium text-gray-300">Balance</label>
<div className="text-sm text-gray-400">
<span className="text-teal-400 font-semibold">
{balanceLoading ? 'Loading...' : `${walletBalance.toFixed(2)} STRK`}
</span>
</div>
</div>

<div className="relative">
<div className="absolute inset-0 bg-gradient-to-r from-teal-500/10 to-blue-500/10 rounded-lg blur-sm"></div>
<div className="relative bg-gray-900/80 border border-gray-700 rounded-lg p-4 backdrop-blur-sm hover:border-teal-500/50 transition-colors duration-200">
<div className="flex items-center space-x-3">
<div className="flex-1">
<Input
type="text"
placeholder="0.00"
value={stakeAmount}
onChange={handleInputChange}
className="bg-transparent border-0 text-2xl font-bold text-white placeholder-gray-500 focus:outline-0 focus:border-0 focus:ring-0 focus:shadow-none outline-0 ring-0 shadow-none p-0 h-auto [&:focus]:outline-none [&:focus]:border-none [&:focus]:ring-0 [&:focus]:shadow-none"
disabled={isLoading}
style={{ outline: 'none', border: 'none', boxShadow: 'none' }}
/>
<div className="text-xs text-gray-400 mt-1">
Enter amount to stake
</div>
</div>
<div className="flex flex-col items-end">
<div className="bg-teal-500/20 text-teal-400 px-3 py-1 rounded-full text-sm font-medium">
STRK
</div>
<div className="text-xs text-gray-500 mt-1">
Token
</div>
</div>
</div>
</div>
</div>

<div className="flex gap-2 mt-3">
{['10', '50', '100', '500'].map((amount) => (
<button
key={amount}
onClick={() => setStakeAmount(amount)}
disabled={isLoading}
className="flex-1 px-3 py-2 text-xs bg-gray-800 hover:bg-gray-700 text-gray-300 hover:text-white rounded-md transition-colors duration-200 disabled:opacity-50 disabled:cursor-not-allowed"
>
{amount}
</button>
))}
</div>
</div>

{isConnected && stakeAmount && (
<div className="mb-3 space-y-1">
{!isMinimumStake && (
<div className="text-xs text-red-400 flex items-center gap-1">
<span>⚠️</span>
Minimum stake is 10 STRK
</div>
)}
{!hasSufficientBalance && (
<div className="text-xs text-red-400 flex items-center gap-1">
<span>⚠️</span>
Insufficient balance. You have {walletBalance.toFixed(2)} STRK
</div>
)}
{isValidAmount && isMinimumStake && hasSufficientBalance && (
<div className="text-xs text-green-400 flex items-center gap-1">
<span>✓</span>
Ready to stake
</div>
)}
</div>
)}

{/* Wallet Balance Display */}
{isConnected && (
<div className="mb-4 p-3 bg-gray-800/50 rounded-lg">
<div className="flex justify-between items-center text-sm">
<span className="text-gray-400">Wallet Balance:</span>
<span className="text-white font-medium">
{balanceLoading ? 'Loading...' : `${walletBalance.toFixed(2)} STRK`}
</span>
</div>
</div>
)}

{/* Stake Button */}
{isLoading ? (
<Button
disabled
className="w-full bg-teal-500 py-4 hover:bg-teal-600 text-black rounded-lg"
>
<div className="rounded-full animate-spin mr-2">
<FaSpinner />
</div>
Staking...
</Button>
) : !isConnected ? (
<Button
onClick={() => setShowConnectModal(true)}
className="w-full bg-teal-500 py-4 hover:bg-teal-600 text-black rounded-lg"
>
Connect Wallet to Stake
</Button>
) : !isValidAmount ? (
<Button
disabled
className="w-full bg-gray-500 py-4 text-white rounded-lg"
>
Enter Valid Amount
</Button>
) : !isMinimumStake ? (
<Button
disabled
className="w-full bg-gray-500 py-4 text-white rounded-lg"
>
Minimum Stake: 10 STRK
</Button>
) : !hasSufficientBalance ? (
<Button
disabled
className="w-full bg-gray-500 py-4 text-white rounded-lg"
>
Insufficient Balance
</Button>
) : (
<Button
onClick={handleStake}
className="w-full bg-teal-500 py-4 hover:bg-teal-600 text-black rounded-lg"
>
Stake {stakeAmount} STRK from {truncate(address ?? "", { maxLength: 16, truncateMiddle: { front: 6, back: 5 } })}
</Button>
)}

<div className="text-xs text-center text-gray-400 mt-2">
Stakes are locked until pool settlement
</div>

<ConnectWalletModal
isOpen={showConnectModal}
onClose={() => setShowConnectModal(false)}
/>
</div>
);
}
96 changes: 65 additions & 31 deletions src/app/dashboard/pool-market/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { PREDIFI_ABI } from "@/app/abi/predifi_abi";
import { GET_POOL } from "@/constants/functionNames";
import { PoolCardDetails, PoolDescription } from "./components/poolDetails";
import PoolPrediction from "./components/poolPrediction";
import StakePool from "./components/StakePool";
import SocialsShare from "./components/socialsShare";
import Comments from "@/components/ui/comments";
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
Expand Down Expand Up @@ -36,11 +37,14 @@ export default function Market() {
readData: pool,
} = useContractFetch(PREDIFI_ABI, GET_POOL, [poolId]);

const { data: hasUserAlreadyParticipated, isLoading: isUserParticipatedLoading } = useGetUserParticiaptionInPool({
const {
data: hasUserAlreadyParticipated,
isLoading: isUserParticipatedLoading,
} = useGetUserParticiaptionInPool({
enabled: isConnected,
userAddress: address,
poolId: poolId
})
poolId: poolId,
});

const [poolDetails, setPoolDetails] = useState<PoolDetails | null>(null);

Expand Down Expand Up @@ -114,8 +118,8 @@ export default function Market() {
</div>
</Link>

<div className="grid grid-cols-1 lg:grid-cols-5 gap-4 mt-8 rounded-md">
<div className="col-span-3">
<div className="grid grid-cols-1 lg:grid-cols-8 gap-4 mt-8 rounded-md">
<div className="col-span-5">
{readIsLoading ? (
<Skeleton className="h-20 rounded-md mb-4" />
) : readError ? (
Expand Down Expand Up @@ -152,11 +156,37 @@ export default function Market() {
<span>{poolDetails?.option2}</span>
<span>{poolDetails?.totalStakeOption2}</span>
</div>
<div className="text-right text-sm">
Total stake distribution:{" "}
<span className="text-lg font-semibold">
{poolDetails?.totalBetAmountStrk}
</span>
<div className="flex justify-between items-center">
<div className="text-sm">
Total stake distribution:{" "}
<span className="text-lg font-semibold text-teal-400">
{poolDetails?.totalBetAmountStrk} STRK
</span>
</div>
{poolDetails && (
<Dialog>
<DialogTrigger className="bg-gradient-to-r from-teal-500 to-teal-600 hover:from-teal-600 hover:to-teal-700 text-black px-6 py-2 rounded-full font-medium text-sm transition-all duration-200 shadow-lg hover:shadow-teal-500/25 flex items-center gap-2">
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
Stake
</DialogTrigger>
<DialogContent className="text-white bg-black border-gray-800 shadow-lg rounded-md max-w-md">
<DialogHeader>
<DialogTitle>Stake on Pool</DialogTitle>
</DialogHeader>
<StakePool
poolId={poolId}
isConnected={isConnected}
address={address}
onStakeSuccess={() => {
// Optionally refresh pool data after successful stake
window.location.reload();
}}
/>
</DialogContent>
</Dialog>
)}
</div>
</>
)}
Expand Down Expand Up @@ -216,27 +246,31 @@ export default function Market() {
</div>
</div>

{poolDetails && (
<PoolPrediction
name={poolDetails?.poolName ?? ""}
isParticipationLoading={isUserParticipatedLoading}
hasParticipatedAlready={hasUserAlreadyParticipated}
isConnected={isConnected}
address={address}
creator={poolDetails.address}
poolId={poolId}
predictions={[
{
options: poolDetails?.option1 || "Option 1",
odds: poolDetails?.totalStakeOption1.toString() ?? "",
},
{
options: poolDetails?.option2 || "Option 2",
odds: poolDetails?.totalStakeOption2.toString() ?? "",
},
]}
/>
)}
<div className="space-y-4 col-span-3 ">
{poolDetails && (
<PoolPrediction
name={poolDetails?.poolName ?? ""}
isParticipationLoading={isUserParticipatedLoading}
hasParticipatedAlready={hasUserAlreadyParticipated}
isConnected={isConnected}
address={address}
creator={poolDetails.address}
poolId={poolId}
predictions={[
{
options: poolDetails?.option1 || "Option 1",
odds: poolDetails?.totalStakeOption1.toString() ?? "",
},
{
options: poolDetails?.option2 || "Option 2",
odds: poolDetails?.totalStakeOption2.toString() ?? "",
},
]}
/>
)}


</div>
</div>
</div>
);
Expand Down
Loading