diff --git a/web/src/app/[cat]/InteractionClient.tsx b/web/src/app/[cat]/InteractionClient.tsx
index 59515c27..5d1553c8 100644
--- a/web/src/app/[cat]/InteractionClient.tsx
+++ b/web/src/app/[cat]/InteractionClient.tsx
@@ -1,7 +1,7 @@
"use client";
import React, { useEffect, useState } from "react";
-import { Info, Coins, Settings, ArrowUpRight, ArrowDownRight, Unlock } from "lucide-react";
+import { Info, Coins, Settings, ArrowUpRight, ArrowDownRight, Unlock, AlertCircle, Loader2 } from "lucide-react";
import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
import { getPublicClient } from "@wagmi/core";
import { config } from "@/utils/config";
@@ -13,6 +13,10 @@ import { Label } from "@/components/ui/label";
import { useAccount, useWriteContract, useWaitForTransactionReceipt } from "wagmi";
import { parseEther } from "viem";
import { showTransactionToast } from "@/components/ui/transaction-toast";
+import { motion } from "framer-motion";
+import Layout from "@/components/Layout";
+import { LoadingState } from "@/components/ui/loading-state";
+import { ButtonLoadingState } from "@/components/ui/button-loading-state";
// Define supported chain IDs
type SupportedChainId = 1 | 137 | 534351 | 5115 | 61 | 2001;
@@ -52,6 +56,9 @@ export default function InteractionClient() {
timestamp: "",
});
+ // Add new state for transaction signing
+ const [isSigning, setIsSigning] = useState(false);
+
// Get vault address and chainId from URL parameters
useEffect(() => {
const vault = searchParams.get("vault");
@@ -229,78 +236,205 @@ export default function InteractionClient() {
}
}, [disableTransferRestrictionData, chainId]);
+ // Update the mint function
+ const handleMint = async () => {
+ try {
+ setIsSigning(true);
+ await mint({
+ abi: CONTRIBUTION_ACCOUNTING_TOKEN_ABI,
+ address: tokenAddress,
+ functionName: "mint",
+ args: [address, parseEther(mintAmount)]
+ });
+ } catch (error) {
+ console.error("Error minting tokens:", error);
+ showTransactionToast({
+ hash: "0x0" as `0x${string}`,
+ chainId: chainId!,
+ success: false,
+ message: "Failed to mint tokens",
+ });
+ } finally {
+ setIsSigning(false);
+ }
+ };
+
+ // Update the reduceMaxSupply function
+ const handleReduceMaxSupply = async () => {
+ try {
+ setIsSigning(true);
+ await reduceMaxSupply({
+ abi: CONTRIBUTION_ACCOUNTING_TOKEN_ABI,
+ address: tokenAddress,
+ functionName: "reduceMaxSupply",
+ args: [parseEther(newMaxSupply)]
+ });
+ } catch (error) {
+ console.error("Error reducing max supply:", error);
+ showTransactionToast({
+ hash: "0x0" as `0x${string}`,
+ chainId: chainId!,
+ success: false,
+ message: "Failed to update max supply",
+ });
+ } finally {
+ setIsSigning(false);
+ }
+ };
+
+ // Update the reduceThresholdSupply function
+ const handleReduceThresholdSupply = async () => {
+ try {
+ setIsSigning(true);
+ await reduceThresholdSupply({
+ abi: CONTRIBUTION_ACCOUNTING_TOKEN_ABI,
+ address: tokenAddress,
+ functionName: "reduceThresholdSupply",
+ args: [parseEther(newThresholdSupply)]
+ });
+ } catch (error) {
+ console.error("Error reducing threshold supply:", error);
+ showTransactionToast({
+ hash: "0x0" as `0x${string}`,
+ chainId: chainId!,
+ success: false,
+ message: "Failed to update threshold supply",
+ });
+ } finally {
+ setIsSigning(false);
+ }
+ };
+
+ // Update the reduceMaxExpansionRate function
+ const handleReduceMaxExpansionRate = async () => {
+ try {
+ setIsSigning(true);
+ await reduceMaxExpansionRate({
+ abi: CONTRIBUTION_ACCOUNTING_TOKEN_ABI,
+ address: tokenAddress,
+ functionName: "reduceMaxExpansionRate",
+ args: [Number(newMaxExpansionRate) * 100]
+ });
+ } catch (error) {
+ console.error("Error reducing max expansion rate:", error);
+ showTransactionToast({
+ hash: "0x0" as `0x${string}`,
+ chainId: chainId!,
+ success: false,
+ message: "Failed to update max expansion rate",
+ });
+ } finally {
+ setIsSigning(false);
+ }
+ };
+
+ // Update the disableTransferRestriction function
+ const handleDisableTransferRestriction = async () => {
+ try {
+ setIsSigning(true);
+ await disableTransferRestriction({
+ abi: CONTRIBUTION_ACCOUNTING_TOKEN_ABI,
+ address: tokenAddress,
+ functionName: "disableTransferRestriction",
+ });
+ } catch (error) {
+ console.error("Error disabling transfer restriction:", error);
+ showTransactionToast({
+ hash: "0x0" as `0x${string}`,
+ chainId: chainId!,
+ success: false,
+ message: "Failed to disable transfer restriction",
+ });
+ } finally {
+ setIsSigning(false);
+ }
+ };
+
if (isLoading) {
return (
-
-
- Loading token details...
-
-
+
);
}
if (error) {
return (
-
+
);
}
return (
-
-
+
+
{/* Header Section */}
-
+
{tokenDetails.tokenSymbol} Token Management
-
+
+
+ {tokenDetails.tokenName}
+
{/* Token Overview Card */}
-
+
-
-
+
+
Token Overview
-
+
-
-
Max Supply
+
+ Max Supply
-
+
{tokenDetails.maxSupply}
-
+
-
-
Threshold Supply
+
+
Threshold Supply
-
+
{tokenDetails.thresholdSupply}
-
+
-
-
Max Expansion Rate
+
+
Max Expansion Rate
-
+
{tokenDetails.maxExpansionRate}%
-
+
-
-
Contract Address
+
+ Contract Address
-
+
{tokenDetails.transactionHash}
@@ -308,47 +442,46 @@ export default function InteractionClient() {
{/* Mint Tokens Card */}
-
+
-
-
+
+
Mint Tokens
{/* Admin Functions Card */}
-
+
-
-
+
+
Admin Functions
@@ -356,95 +489,86 @@ export default function InteractionClient() {
- New Max Expansion Rate (%)
+ New Max Expansion Rate (%)
setNewMaxExpansionRate(e.target.value)}
- className="h-12 text-lg"
+ className="h-12 text-lg bg-white/60 dark:bg-[#1a1400]/70 border-2 border-gray-200 dark:border-yellow-400/20 text-gray-600 dark:text-yellow-200"
/>
reduceMaxExpansionRate({
- abi: CONTRIBUTION_ACCOUNTING_TOKEN_ABI,
- address: tokenAddress,
- functionName: "reduceMaxExpansionRate",
- args: [Number(newMaxExpansionRate) * 100]
- })}
- disabled={!newMaxExpansionRate || isReducingMaxExpansionRate}
- className="w-full h-12 text-lg"
+ onClick={handleReduceMaxExpansionRate}
+ disabled={!newMaxExpansionRate || isReducingMaxExpansionRate || isSigning}
+ className="w-full h-12 text-lg bg-[#5cacc5] dark:bg-[#BA9901] hover:bg-[#4a9db5] dark:hover:bg-[#a88a01] text-white rounded-xl"
>
- {isReducingMaxExpansionRate ? "Updating..." : "Update Max Expansion Rate"}
+ {isReducingMaxExpansionRate || isSigning ? (
+
+ ) : (
+ "Update Max Expansion Rate"
+ )}
{transferRestricted ? (
-
Transfer Restriction
+
Transfer Restriction
- disableTransferRestriction({
- abi: CONTRIBUTION_ACCOUNTING_TOKEN_ABI,
- address: tokenAddress,
- functionName: "disableTransferRestriction",
- })
- }
- disabled={isDisablingTransferRestriction}
- className="w-full h-12 text-lg"
+ onClick={handleDisableTransferRestriction}
+ disabled={isDisablingTransferRestriction || isSigning}
+ className="w-full h-12 text-lg bg-[#5cacc5] dark:bg-[#BA9901] hover:bg-[#4a9db5] dark:hover:bg-[#a88a01] text-white rounded-xl"
>
- {isDisablingTransferRestriction ? (
- "Disabling..."
+ {isDisablingTransferRestriction || isSigning ? (
+
) : (
@@ -455,8 +579,8 @@ export default function InteractionClient() {
) : (
-
Transfer Restriction
-
Transfer restriction is already disabled
+
Transfer Restriction
+
Transfer restriction is already disabled
)}
diff --git a/web/src/app/create/page.tsx b/web/src/app/create/page.tsx
index b959f512..018172a9 100644
--- a/web/src/app/create/page.tsx
+++ b/web/src/app/create/page.tsx
@@ -20,9 +20,12 @@ import {
CardTitle,
CardDescription,
} from "@/components/ui/card";
-import { Info, Loader2 } from "lucide-react";
+import { Info, Loader2, ArrowLeft } from "lucide-react";
import { motion } from "framer-motion";
import { showTransactionToast } from "@/components/ui/transaction-toast";
+import Link from "next/link";
+import { LoadingState } from "@/components/ui/loading-state";
+import { ButtonLoadingState } from "@/components/ui/button-loading-state";
interface DeployContractProps {
tokenName: string;
@@ -32,6 +35,13 @@ interface DeployContractProps {
maxExpansionRate: string;
}
+interface FieldValidation {
+ [key: string]: {
+ isValid: boolean;
+ errorMessage: string;
+ };
+}
+
const fields = [
{
id: "tokenName",
@@ -39,6 +49,10 @@ const fields = [
type: "text",
placeholder: "My Token",
description: "The name of your token",
+ validate: (value: string) => ({
+ isValid: value.length >= 3 && value.length <= 32,
+ errorMessage: "Token name must be between 3 and 32 characters"
+ })
},
{
id: "tokenSymbol",
@@ -46,6 +60,10 @@ const fields = [
type: "text",
placeholder: "TKN",
description: "A short identifier for your token (2-4 characters)",
+ validate: (value: string) => ({
+ isValid: /^[A-Z]{2,4}$/.test(value),
+ errorMessage: "Symbol must be 2-4 uppercase letters"
+ })
},
{
id: "maxSupply",
@@ -53,6 +71,10 @@ const fields = [
type: "number",
placeholder: "1000000",
description: "The maximum number of tokens that can exist",
+ validate: (value: string) => ({
+ isValid: /^\d+$/.test(value) && parseInt(value) > 0,
+ errorMessage: "Maximum supply must be a positive number"
+ })
},
{
id: "thresholdSupply",
@@ -60,6 +82,12 @@ const fields = [
type: "number",
placeholder: "500000",
description: "The supply threshold that triggers expansion",
+ validate: (value: string, formData: DeployContractProps) => ({
+ isValid: /^\d+$/.test(value) &&
+ parseInt(value) > 0 &&
+ parseInt(value) < parseInt(formData.maxSupply),
+ errorMessage: "Threshold must be a positive number less than maximum supply"
+ })
},
{
id: "maxExpansionRate",
@@ -67,6 +95,12 @@ const fields = [
type: "number",
placeholder: "5",
description: "Maximum percentage the supply can expand (1-100)",
+ validate: (value: string) => ({
+ isValid: /^\d+$/.test(value) &&
+ parseInt(value) >= 1 &&
+ parseInt(value) <= 100,
+ errorMessage: "Expansion rate must be between 1 and 100"
+ })
},
];
@@ -79,6 +113,9 @@ export default function CreateCAT() {
maxExpansionRate: "",
});
const [isDeploying, setIsDeploying] = useState(false);
+ const [validation, setValidation] = useState
({});
+ const [showInfo, setShowInfo] = useState<{ [key: string]: boolean }>({});
+ const [isSigning, setIsSigning] = useState(false);
const { address, chainId } = useAccount();
const router = useRouter();
@@ -102,6 +139,7 @@ export default function CreateCAT() {
const deployContract = async () => {
try {
setIsDeploying(true);
+ setIsSigning(true);
const chainId = config.state.chainId;
if (!ClowderVaultFactories[chainId]) {
toast.error("Contract factory instance not available");
@@ -120,7 +158,7 @@ export default function CreateCAT() {
const formattedThresholdSupply = BigInt(thresholdSupply) * BigInt(1e18);
const formattedMaxExpansionRate = BigInt(maxExpansionRate) * BigInt(100);
- deployCAT({
+ await deployCAT({
address: ClowderVaultFactories[chainId],
abi: CAT_FACTORY_ABI,
functionName: "createCAT",
@@ -141,6 +179,7 @@ export default function CreateCAT() {
message: "Failed to deploy CAT contract",
});
setIsDeploying(false);
+ setIsSigning(false);
}
};
@@ -168,127 +207,165 @@ export default function CreateCAT() {
}, [deployData, formData, router]);
const handleChange = (e: React.ChangeEvent) => {
- setFormData({
- ...formData,
- [e.target.name]: e.target.value,
- });
+ const { name, value } = e.target;
+ setFormData(prev => ({
+ ...prev,
+ [name]: value
+ }));
+
+ // Validate the field
+ const field = fields.find(f => f.id === name);
+ if (field?.validate) {
+ const validationResult = field.validate(value, formData);
+ setValidation(prev => ({
+ ...prev,
+ [name]: validationResult
+ }));
+ }
+ };
+
+ const toggleInfo = (fieldId: string) => {
+ setShowInfo(prev => ({
+ ...prev,
+ [fieldId]: !prev[fieldId]
+ }));
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
- await deployContract();
+
+ // Validate all fields before submission
+ const newValidation: FieldValidation = {};
+ let isValid = true;
+
+ fields.forEach(field => {
+ if (field.validate) {
+ const result = field.validate(formData[field.id as keyof DeployContractProps], formData);
+ newValidation[field.id] = result;
+ if (!result.isValid) isValid = false;
+ }
+ });
+
+ setValidation(newValidation);
+
+ if (isValid) {
+ await deployContract();
+ }
};
return (
-
-
-
-
-
+
+
+ {isDeploying || isDeployingTx ? (
+
+ ) : (
+
+
+
+
+ Back to My CATs
+
+
+
Create CAT
-
-
- Deploy a new Contribution Accounting Token
-
-
-
+
{!address ? (
-
-
- Connect your wallet to create a new CAT
+
+
+ Connect your wallet to create a CAT
-
-
+
+
-
- ) : chainId && !ClowderVaultFactories[chainId] ? (
-
-
⚠ Please switch to a supported network.
-
- If you would like support for another network, contact us on{" "}
-
- Discord
-
- .
-
) : (
)}
-
-
-
+
+ )}
+
);
diff --git a/web/src/app/globals.css b/web/src/app/globals.css
index dd02d5a5..45cdb7d2 100644
--- a/web/src/app/globals.css
+++ b/web/src/app/globals.css
@@ -49,6 +49,7 @@ body {
body {
@apply bg-background text-foreground;
+ font-family: 'Inter', sans-serif;
}
h1,
@@ -57,7 +58,8 @@ body {
h4,
h5,
h6 {
- font-family: "Arial", sans-serif;
+ font-family: 'Inter', sans-serif;
+ @apply font-bold tracking-tight;
}
}
@@ -75,4 +77,44 @@ body {
html {
scroll-behavior: smooth;
+}
+
+/* Fix spacing issues */
+main {
+ @apply min-h-[calc(100vh-4rem)] pt-16 pb-8;
+}
+
+/* Modern scrollbar */
+::-webkit-scrollbar {
+ width: 8px;
+ height: 8px;
+}
+
+::-webkit-scrollbar-track {
+ @apply bg-gray-100 dark:bg-gray-800;
+}
+
+::-webkit-scrollbar-thumb {
+ @apply bg-gray-300 dark:bg-gray-600 rounded-full;
+}
+
+::-webkit-scrollbar-thumb:hover {
+ @apply bg-gray-400 dark:bg-gray-500;
+}
+
+/* Animations */
+@keyframes float {
+ 0% {
+ transform: translateY(0px);
+ }
+ 50% {
+ transform: translateY(-10px);
+ }
+ 100% {
+ transform: translateY(0px);
+ }
+}
+
+.animate-float {
+ animation: float 3s ease-in-out infinite;
}
\ No newline at end of file
diff --git a/web/src/app/layout.tsx b/web/src/app/layout.tsx
index af2c2e9b..79a17be4 100644
--- a/web/src/app/layout.tsx
+++ b/web/src/app/layout.tsx
@@ -68,7 +68,7 @@ export default function RootLayout({
diff --git a/web/src/app/my-cats/page.tsx b/web/src/app/my-cats/page.tsx
index c01cb199..3e8ce7fb 100644
--- a/web/src/app/my-cats/page.tsx
+++ b/web/src/app/my-cats/page.tsx
@@ -11,12 +11,23 @@ import { CAT_FACTORY_ABI } from "@/contractsABI/CatFactoryABI";
import detectEthereumProvider from "@metamask/detect-provider";
import { CONTRIBUTION_ACCOUNTING_TOKEN_ABI } from "@/contractsABI/ContributionAccountingTokenABI";
import { motion } from "framer-motion";
-import { Loader2, AlertCircle } from "lucide-react";
+import { Loader2, AlertCircle, Plus, Search, Filter } from "lucide-react";
import { showTransactionToast } from "@/components/ui/transaction-toast";
+import { LoadingState } from "@/components/ui/loading-state";
// Define supported chain IDs
type SupportedChainId = 1 | 137 | 534351 | 5115 | 61 | 2001;
+// Chain ID to name mapping
+const CHAIN_NAMES: Record = {
+ 1: "Ethereum",
+ 137: "Polygon",
+ 534351: "Scroll Sepolia",
+ 5115: "Citrea",
+ 61: "Ethereum Classic",
+ 2001: "Milkomeda"
+};
+
interface CatDetails {
chainId: SupportedChainId;
address: `0x${string}`;
@@ -36,6 +47,8 @@ export default function MyCATsPage() {
const [ownedCATs, setOwnedCATs] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
+ const [searchQuery, setSearchQuery] = useState("");
+ const [selectedChainId, setSelectedChainId] = useState("all");
const { address } = useAccount();
const { writeContract: fetchCATs, data: fetchData } = useWriteContract();
@@ -158,89 +171,214 @@ export default function MyCATsPage() {
}
}, [address]);
+ // Filter and search function
+ const filteredCATs = ownedCATs?.filter((cat) => {
+ const matchesSearch = searchQuery === "" ||
+ cat.tokenName.toLowerCase().includes(searchQuery.toLowerCase()) ||
+ cat.tokenSymbol.toLowerCase().includes(searchQuery.toLowerCase());
+
+ const matchesChain = selectedChainId === "all" || cat.chainId === Number(selectedChainId);
+
+ return matchesSearch && matchesChain;
+ });
+
return (
-
-
-
+
+
- My CATs
-
- {isLoading ? (
-
-
-
- Loading your CATs...
-
-
- ) : error ? (
-
-
- {error}
-
- ) : ownedCATs?.length ? (
-
- {ownedCATs.map((cat) => (
-
+
+ My CATs
+
+
+
-
-
- {cat.tokenName || cat.address}
-
-
- Symbol: {cat.tokenSymbol}
-
-
-
- Chain: {cat.chainId}
-
-
- View Details →
-
-
-
-
- ))}
-
- ) : (
-
+ Create New CAT
+
+
+
+
+ {/* Search and Filter Section */}
+
-
- You don't own any CATs yet.
-
-
- Create a CAT
-
+
+
+
+
+
setSearchQuery(e.target.value)}
+ className="w-full pl-10 pr-4 py-3 rounded-xl bg-white/80 dark:bg-[#1a1400]/70 border border-[#bfdbfe] dark:border-yellow-400/20 text-gray-800 dark:text-yellow-100 placeholder-gray-500 dark:placeholder-yellow-200/50 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-yellow-400 focus:border-transparent transition-all duration-300"
+ />
+
+
+
+
+
+
setSelectedChainId(e.target.value as SupportedChainId | "all")}
+ className="pl-10 pr-4 py-3 rounded-xl bg-white/80 dark:bg-[#1a1400]/70 border border-[#bfdbfe] dark:border-yellow-400/20 text-gray-800 dark:text-yellow-100 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-yellow-400 focus:border-transparent transition-all duration-300 appearance-none cursor-pointer"
+ >
+ All Chains
+ {Object.entries(CHAIN_NAMES).map(([chainId, name]) => (
+
+ {name} ({chainId})
+
+ ))}
+
+
+
- )}
+
+
+ {isLoading ? (
+
+ ) : error ? (
+
+ ) : filteredCATs?.length ? (
+
+ {filteredCATs.map((cat, index) => (
+
+
+
+
+
+
+
+ {cat.tokenSymbol.slice(0, 2)}
+
+
+
+ {cat.tokenName || cat.address}
+
+
+ {cat.tokenSymbol}
+
+
+
+
+ →
+
+
+
+
+
+ Chain ID
+ {cat.chainId}
+
+
+
+
Contract Address
+
+ {cat.address}
+
+
+
+
+
+
+ Manage CAT
+
+
+
+
+
+ ))}
+
+ ) : (
+
+
+
+
+
+
+ No CATs Found
+
+
+ {searchQuery || selectedChainId !== "all"
+ ? "No CATs match your search criteria"
+ : "Start by creating your first Contribution Accounting Token"}
+
+
+ {!searchQuery && selectedChainId === "all" && (
+
+
+
+ Create Your First CAT
+
+
+ )}
+
+ )}
+
+
diff --git a/web/src/app/page.tsx b/web/src/app/page.tsx
index 5a92b2c6..477f84fa 100644
--- a/web/src/app/page.tsx
+++ b/web/src/app/page.tsx
@@ -17,9 +17,10 @@ import { useTheme } from "next-themes"
import { faGithub, faDiscord, faTelegram, faXTwitter} from "@fortawesome/free-brands-svg-icons"
import { useAccount } from "wagmi"
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
-import { motion } from "framer-motion"
+import { motion, AnimatePresence, type MotionProps } from "framer-motion"
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "@/components/ui/dialog"
import { showTransactionToast } from "@/components/ui/transaction-toast"
+import { config } from "@/utils/config"
// import { config } from "@/utils/config"
const services = [
@@ -93,195 +94,253 @@ export default function Home() {
return (
-
+
{/* Hero Section */}
+ {/* Background Effects */}
+
+
-
+
Welcome to Clowder
-
+
+ Create Contribution Accounting Tokens (CATs)
+ to track contributions to your projects.
+
-
- Create Contribution Accounting Tokens (CATs)
- to track contributions to your projects.
-
-
- {contact_links.map(({ href, icon }, index) => (
-
-
-
- ))}
-
-
- {!isWalletConnected ? (
-
-
-
- ) : (
-
- router.push("/my-cats")}
- className="mb-2 mr-2 text-black dark:text-white"
+
+ {contact_links.map(({ href, icon }, index) => (
+
- My CAT's
-
- router.push("/create")}
- className="mb-2 mr-2 text-black dark:text-white"
+
+
+ ))}
+
+
+
+ {!address ? (
+
- Create CAT
-
- setShowPopup(true)} className="bg-gray-600 hover:bg-gray-700 text-white">
- Use CAT
-
-
- )}
+
+
+ ) : (
+ <>
+
+ router.push("/my-cats")}
+ className="h-14 px-8 text-lg bg-white/60 font-bold dark:bg-[#1a1400]/70 text-gray-700 dark:text-yellow-200 hover:bg-white/80 dark:hover:bg-[#1a1400]/90 border border-white/30 dark:border-yellow-400/20 rounded-xl shadow-lg hover:shadow-xl transition-all duration-300"
+ >
+ My CAT's
+
+
+
+ router.push("/create")}
+ className="h-14 px-8 text-lg bg-[#5cacc5] font-bold dark:bg-[#BA9901] text-white hover:bg-[#4a9db5] dark:hover:bg-[#a88a01] rounded-xl shadow-lg hover:shadow-xl transition-all duration-300"
+ >
+ Create CAT
+
+
+
+ setShowPopup(true)}
+ className="h-14 px-8 text-lg font-bold bg-white/60 dark:bg-[#1a1400]/70 text-gray-700 dark:text-yellow-200 hover:bg-white/80 dark:hover:bg-[#1a1400]/90 border border-white/30 dark:border-yellow-400/20 rounded-xl shadow-lg hover:shadow-xl transition-all duration-300"
+ >
+ Use CAT
+
+
+ >
+ )}
+
{/* Services Section */}
-
- Why CATs?
-
-
- {services.map((service, index) => (
-
-
- {service.description}
-
- ))}
+
+
+ Why CATs?
+
+
+ {services.map((service, index) => (
+
+
+
+
+ {service.description}
+
+
+
+ ))}
+
- {/* Contact Us Section */}
+ {/* About Us Section */}
-
-
- {/* Contact Info */}
-
+
-
- Clowder was developed by
- The Stable Order
- within the Stability Nexus.
-
-
- Contact us through:
-
- {contact_links.map(({ href, icon }, index) => (
-
-
-
- ))}
-
-
+ About Us
+
+
+
+
+ Clowder was developed by
+ The Stable Order
+ within the Stability Nexus.
+
+
+
+ Contact us through:
+
+
+ {contact_links.map(({ href, icon }, index) => (
+
+
+
+ ))}
+
+
- {/* Right Content */}
-
-
-
+
+
+
+
+
+
{/* Use CAT Dialog */}
-
+
-
+
Use Existing CAT
-
+
Enter the CAT address and select the network to interact with your token.
-
+
CAT Address
setCatAddress(e.target.value)}
placeholder="0x..."
- className="w-full h-12 text-lg font-mono bg-gray-50 dark:bg-gray-800 border-2 border-gray-200 dark:border-gray-700 dark:text-gray-300 focus:ring-2 focus:ring-blue-500 rounded-xl"
+ className="w-full h-12 text-lg font-mono bg-white/60 dark:bg-[#2a1a00] border-2 border-blue-200 dark:border-yellow-400/20 text-gray-800 dark:text-yellow-100 focus:ring-2 focus:ring-blue-500 dark:focus:ring-yellow-400 rounded-xl"
/>
-
+
Network
-
+
-
+
{supportedChains.map((chain) => (
{chain.name}
@@ -318,26 +377,20 @@ export default function Home() {
setShowPopup(false)}
- className="h-12 px-6 text-lg border-2 border-gray-200 dark:bg-gray-200 dark:border-gray-700 bg-transparent hover:bg-gray-100 dark:hover:bg-gray-400 rounded-xl"
+ className="h-12 px-6 text-lg border-2 border-blue-200 dark:border-yellow-400/20 bg-transparent hover:bg-blue-50 dark:hover:bg-yellow-400/10 text-gray-700 dark:text-yellow-200 rounded-xl"
>
Cancel
Continue
- {showPopup && (
- setShowPopup(false)}
- />
- )}
)
}
diff --git a/web/src/components/Layout.tsx b/web/src/components/Layout.tsx
index f72d7eca..575ea767 100644
--- a/web/src/components/Layout.tsx
+++ b/web/src/components/Layout.tsx
@@ -25,7 +25,7 @@ export default function RootLayout({
@@ -39,3 +39,4 @@ export default function RootLayout({
);
}
+
diff --git a/web/src/components/Navbar.tsx b/web/src/components/Navbar.tsx
index f3bf9834..049c21cc 100644
--- a/web/src/components/Navbar.tsx
+++ b/web/src/components/Navbar.tsx
@@ -42,7 +42,7 @@ const Navbar = () => {
priority
/>
{
)}
-
- CREATE CAT
-
-
- ABOUT US
-
{/* Connect Wallet and Light/Dark Toggle for Desktop */}
diff --git a/web/src/components/ui/button-loading-state.tsx b/web/src/components/ui/button-loading-state.tsx
new file mode 100644
index 00000000..bb4e3a8e
--- /dev/null
+++ b/web/src/components/ui/button-loading-state.tsx
@@ -0,0 +1,21 @@
+import { motion } from "framer-motion";
+import { Coins } from "lucide-react";
+
+interface ButtonLoadingStateProps {
+ text: string;
+}
+
+export function ButtonLoadingState({ text }: ButtonLoadingStateProps) {
+ return (
+
+
+
+
+ {text}
+
+ );
+}
\ No newline at end of file
diff --git a/web/src/components/ui/card.tsx b/web/src/components/ui/card.tsx
index 75934036..9bdc7e26 100644
--- a/web/src/components/ui/card.tsx
+++ b/web/src/components/ui/card.tsx
@@ -36,7 +36,7 @@ const CardTitle = React.forwardRef<
+
+
+
+ {type === "loading" ? (
+
+ ) : (
+
+ )}
+
+
+ {type === "loading" ? title : "Error"}
+
+
+ {type === "loading" ? message : errorMessage}
+
+
+
+ {showSkeleton && type === "loading" && (
+ <>
+
+ {[1, 2, 3].map((index) => (
+
+
+
+
+ ))}
+
+
+
+
+
+
+ >
+ )}
+
+
+ );
+}
\ No newline at end of file
diff --git a/web/src/hooks/ThemeProvider.tsx b/web/src/hooks/ThemeProvider.tsx
index 0947bf51..db73f482 100644
--- a/web/src/hooks/ThemeProvider.tsx
+++ b/web/src/hooks/ThemeProvider.tsx
@@ -17,7 +17,7 @@ export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
}
return (
-
+
{children}
);
diff --git a/web/tailwind.config.ts b/web/tailwind.config.ts
index 74681025..c59cc04d 100644
--- a/web/tailwind.config.ts
+++ b/web/tailwind.config.ts
@@ -56,7 +56,36 @@ const config: Config = {
md: 'calc(var(--radius) - 2px)',
sm: 'calc(var(--radius) - 4px)'
},
- scrollBehavior: ['smooth']
+ scrollBehavior: ['smooth'],
+ keyframes: {
+ "accordion-down": {
+ from: { height: "0" },
+ to: { height: "var(--radix-accordion-content-height)" },
+ },
+ "accordion-up": {
+ from: { height: "var(--radix-accordion-content-height)" },
+ to: { height: "0" },
+ },
+ blob: {
+ "0%": {
+ transform: "translate(0px, 0px) scale(1)",
+ },
+ "33%": {
+ transform: "translate(30px, -50px) scale(1.1)",
+ },
+ "66%": {
+ transform: "translate(-20px, 20px) scale(0.9)",
+ },
+ "100%": {
+ transform: "translate(0px, 0px) scale(1)",
+ },
+ },
+ },
+ animation: {
+ "accordion-down": "accordion-down 0.2s ease-out",
+ "accordion-up": "accordion-up 0.2s ease-out",
+ blob: "blob 7s infinite",
+ }
}
},
plugins: [require("tailwindcss-animate")],