Улучшение UI с анимациями Framer Motion

Добавим плавные анимации для стейкинга, анстейкинга и обновления баланса.

Улучшение UI с анимациями Framer Motion

Добавим плавные анимации для стейкинга, анстейкинга и обновления баланса.


1. Установка Framer Motion

npm install framer-motion

2. Обновленный код с анимациями (pages/index.js)

import { useWallet } from "@solana/wallet-adapter-react";
import { WalletMultiButton } from "@solana/wallet-adapter-react-ui";
import { useEffect, useState } from "react";
import { Connection, PublicKey } from "@solana/web3.js";
import { Program, AnchorProvider, web3, BN } from "@project-serum/anchor";
import { motion } from "framer-motion";

const programId = new PublicKey("9HhvqA...pXr"); // Ваш Program ID
const network = "https://api.devnet.solana.com";

export default function Home() {
    const { publicKey } = useWallet();
    const [provider, setProvider] = useState(null);
    const [program, setProgram] = useState(null);
    const [amount, setAmount] = useState(100);
    const [stakeInfo, setStakeInfo] = useState(null);
    const [reward, setReward] = useState(0);

    useEffect(() => {
        if (publicKey) {
            const connection = new Connection(network, "processed");
            const walletProvider = new AnchorProvider(connection, window.solana, {});
            const idl = require("../idl.json"); // Подключение IDL контракта
            setProvider(walletProvider);
            setProgram(new Program(idl, programId, walletProvider));

            fetchStakeInfo(publicKey);
        }
    }, [publicKey]);

    // Получаем текущий стейк пользователя
    const fetchStakeInfo = async (walletAddress) => {
        if (!program) return;

        try {
            const userStakeAccount = await program.account.userStaking.fetch(walletAddress);
            setStakeInfo(userStakeAccount);

            // Рассчитываем награды
            const currentTime = Math.floor(Date.now() / 1000);
            const duration = currentTime - userStakeAccount.startTime.toNumber();
            const rewardAmount = (userStakeAccount.amount.toNumber() * 10 * duration) / 1_000_000;
            setReward(rewardAmount);
        } catch (error) {
            console.log("No staking found for user.");
            setStakeInfo(null);
            setReward(0);
        }
    };

    const stakeTokens = async () => {
        if (!publicKey || !program) return;
        await program.rpc.stake(new BN(amount), {
            accounts: {
                stakingAccount: programId,
                userStaking: publicKey,
                userTokenAccount: publicKey,
                tokenProgram: web3.TOKEN_PROGRAM_ID,
            },
        });

        alert(`Staked ${amount} RUM!`);
        fetchStakeInfo(publicKey);
    };

    const unstakeTokens = async () => {
        if (!publicKey || !program) return;
        await program.rpc.unstake({
            accounts: {
                stakingAccount: programId,
                userStaking: publicKey,
                userTokenAccount: publicKey,
                tokenProgram: web3.TOKEN_PROGRAM_ID,
            },
        });

        alert("Unstaked successfully!");
        fetchStakeInfo(publicKey);
    };

    return (
        <div className="min-h-screen bg-gray-900 text-white flex flex-col items-center justify-center p-6">
            <motion.h1 
                className="text-4xl font-bold mb-4" 
                initial={{ opacity: 0, y: -20 }} 
                animate={{ opacity: 1, y: 0 }} 
                transition={{ duration: 0.5 }}
            >
                RentumAI Staking
            </motion.h1>

            <motion.div 
                initial={{ opacity: 0, scale: 0.9 }} 
                animate={{ opacity: 1, scale: 1 }} 
                transition={{ duration: 0.5 }}
            >
                <WalletMultiButton className="mb-4 bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded" />
            </motion.div>

            {publicKey && (
                <motion.div 
                    className="bg-gray-800 p-6 rounded-lg shadow-lg text-center"
                    initial={{ opacity: 0, y: 20 }} 
                    animate={{ opacity: 1, y: 0 }} 
                    transition={{ duration: 0.6 }}
                >
                    <h2 className="text-xl font-semibold mb-4">Staking Details</h2>

                    {stakeInfo ? (
                        <motion.div 
                            key={stakeInfo.amount.toString()}
                            initial={{ opacity: 0, scale: 0.8 }} 
                            animate={{ opacity: 1, scale: 1 }} 
                            transition={{ duration: 0.4 }}
                        >
                            <p className="mb-2">Staked Amount: <strong>{stakeInfo.amount.toNumber()} RUM</strong></p>
                            <p className="mb-2">Reward: <strong>{reward.toFixed(2)} RUM</strong></p>
                        </motion.div>
                    ) : (
                        <motion.p 
                            initial={{ opacity: 0 }} 
                            animate={{ opacity: 1 }} 
                            transition={{ duration: 0.4 }}
                            className="mb-4"
                        >
                            You have no active stake.
                        </motion.p>
                    )}

                    <motion.input
                        type="number"
                        value={amount}
                        onChange={(e) => setAmount(e.target.value)}
                        className="w-full px-3 py-2 mb-4 text-black rounded"
                        placeholder="Enter amount"
                        whileFocus={{ scale: 1.05 }}
                    />

                    <motion.button
                        onClick={stakeTokens}
                        className="w-full bg-green-500 hover:bg-green-700 text-white font-bold py-2 px-4 rounded mb-2"
                        whileHover={{ scale: 1.05 }}
                        whileTap={{ scale: 0.95 }}
                    >
                        Stake
                    </motion.button>

                    <motion.button
                        onClick={unstakeTokens}
                        className="w-full bg-red-500 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
                        whileHover={{ scale: 1.05 }}
                        whileTap={{ scale: 0.95 }}
                    >
                        Unstake
                    </motion.button>
                </motion.div>
            )}
        </div>
    );
}

3. Что изменилось?

Анимации для заголовка при загрузке ✅ Анимации при появлении кошелькаАнимации для баланса и наградПлавное увеличение кнопок при наведении


4. Запуск проекта

npm run dev

Открываем http://localhost:3000, и теперь у нас гладкие анимации с эффектами 🚀


Last updated