From 63af9b9c468ff115966679e42f536a9d19b8f44c Mon Sep 17 00:00:00 2001
From: siegfriedbz
Date: Sat, 22 Nov 2025 15:35:41 +0100
Subject: [PATCH 1/5] feat: implement global cooldown for base tokens
---
fe/app/_components/count-down.tsx | 52 +++++++++-----
fe/app/_components/mint-button.tsx | 108 +++++++++++++---------------
fe/app/_context/tokens-provider.tsx | 6 +-
fe/app/_hooks/use-cool-down.ts | 91 ++++++++++-------------
fe/app/_hooks/use-tokens.ts | 3 +
5 files changed, 129 insertions(+), 131 deletions(-)
diff --git a/fe/app/_components/count-down.tsx b/fe/app/_components/count-down.tsx
index 6b02223..1dbb128 100644
--- a/fe/app/_components/count-down.tsx
+++ b/fe/app/_components/count-down.tsx
@@ -1,28 +1,42 @@
"use client";
-import { animate, motion, useMotionValue, useTransform } from "motion/react";
-import { type FC, useEffect } from "react";
+import { type FC, startTransition, useEffect, useState } from "react";
+import { cn } from "@/lib/utils";
+import { useTokens } from "../_hooks/use-tokens";
type Props = {
- coolDownDelay: number | null;
+ coolDownEndTime: number
+ className?: string;
};
-export const CountDown: FC = (props) => {
- const { coolDownDelay } = props;
- const count = useMotionValue(coolDownDelay ?? 60);
- const rounded = useTransform(count, Math.round);
+export const Countdown:FC = (props) => {
+ const {coolDownEndTime, className } = props
- useEffect(() => {
- const animation = animate(count, 0, {
- duration: (coolDownDelay ?? 60) + 4,
- });
+ const [timeLeft, setTimeLeft] = useState(0);
- return () => animation.cancel();
- }, [count, coolDownDelay]);
+ const {setIsCoolDown, setCoolDownEndTime} = useTokens()
- return (
-
- {rounded}
-
- );
-};
+ useEffect(() => {
+ if (!coolDownEndTime) return;
+
+ const updateTimeLeft = () => {
+ const now = Date.now();
+ const remainingTime = Math.max(0, Math.floor((coolDownEndTime - now) / 1000));
+ setTimeLeft(remainingTime);
+
+ if(remainingTime === 0) {
+ startTransition(() => {
+ setIsCoolDown(false)
+ setCoolDownEndTime(null)
+ })
+ }
+ };
+
+ updateTimeLeft();
+ const interval = setInterval(updateTimeLeft, 1000);
+
+ return () => clearInterval(interval);
+ }, [coolDownEndTime, setIsCoolDown, setCoolDownEndTime]);
+
+ return {timeLeft};
+};
\ No newline at end of file
diff --git a/fe/app/_components/mint-button.tsx b/fe/app/_components/mint-button.tsx
index 181017e..ef8ae10 100644
--- a/fe/app/_components/mint-button.tsx
+++ b/fe/app/_components/mint-button.tsx
@@ -1,71 +1,63 @@
"use client";
import { LoaderIcon } from "lucide-react";
-import { type FC, useCallback } from "react";
+import { ComponentProps, type FC, useCallback } from "react";
+import { useAccount } from "wagmi";
import { useCoolDown } from "@/app/_hooks/use-cool-down";
import { useTokens } from "@/app/_hooks/use-tokens";
import { Button } from "@/components/ui/button";
import { cn } from "@/lib/utils";
import { useMint } from "../_hooks/use-mint";
-import { CountDown } from "./count-down";
+import { Countdown } from "./count-down";
-type Props = React.ComponentProps<"button"> & {
- tokenId: number;
- isBaseToken: boolean;
-};
+type Props = ComponentProps & { tokenId: number; isBaseToken: boolean }
export const MintButton: FC = (props) => {
- const { tokenId, isBaseToken, ...rest } = props;
-
- const { mintCall, error, isPending, isConfirming, isConfirmed } = useMint({
- tokenId,
- });
-
- const { isCoolDown, setIsCoolDown, forgeabilityByTokenId } = useTokens();
-
- const coolDownDelay = useCoolDown({
- isBaseToken,
- isMintError: error,
- isMintConfirmed: isConfirmed,
- });
-
+ const {
+ tokenId,
+ isBaseToken,
+ ...rest
+} = props
+
+ const { address } = useAccount();
+ const { mintCall, error, isPending, isConfirming, isConfirmed } = useMint({ tokenId });
+ const { isCoolDown, setIsCoolDown, forgeabilityByTokenId, coolDownEndTime } = useTokens();
+
+ useCoolDown({ isBaseToken, isMintError: error, isMintConfirmed: isConfirmed });
+
const isForgeable = !!forgeabilityByTokenId[tokenId];
-
- const onMint = useCallback(() => {
- if (isBaseToken) {
- setIsCoolDown(true);
- }
- mintCall();
- }, [isBaseToken, setIsCoolDown, mintCall]);
-
- const isDisabled =
- isPending ||
- isConfirming ||
- (isBaseToken && isCoolDown) ||
- (!isBaseToken && !isForgeable);
-
- return (
-
- );
+ const isDisabled = !address || isPending || isConfirming || (isBaseToken && isCoolDown) || (!isBaseToken && !isForgeable);
+
+ const onMint = useCallback(() => {
+ if (isBaseToken) setIsCoolDown(true);
+ mintCall();
+ }, [isBaseToken, setIsCoolDown, mintCall]);
+
+ if (isBaseToken && isCoolDown && coolDownEndTime) {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
};
diff --git a/fe/app/_context/tokens-provider.tsx b/fe/app/_context/tokens-provider.tsx
index 73b64f3..dc9f7c1 100644
--- a/fe/app/_context/tokens-provider.tsx
+++ b/fe/app/_context/tokens-provider.tsx
@@ -9,6 +9,7 @@ export const TokensProvider: FC = (props) => {
// state for tracking base-tokens cooldown
const [isCoolDown, setIsCoolDown] = useState(false);
+ const [coolDownEndTime, setCoolDownEndTime] = useState(null);
// state for tracking which forged-token card is hovered
const [hoveredForgeTokenId, setHoveredForgeTokenId] = useState(
@@ -21,8 +22,10 @@ export const TokensProvider: FC = (props) => {
const value = useMemo(
() => ({
// base tokens
- isCoolDown,
+ isCoolDown,
setIsCoolDown,
+ coolDownEndTime,
+ setCoolDownEndTime,
// forged tokens
forgeabilityByTokenId,
@@ -32,6 +35,7 @@ export const TokensProvider: FC = (props) => {
}),
[
isCoolDown,
+ coolDownEndTime,
forgeabilityByTokenId,
reCheckForgeability,
hoveredForgeTokenId,
diff --git a/fe/app/_hooks/use-cool-down.ts b/fe/app/_hooks/use-cool-down.ts
index 1ea1c56..5524d07 100644
--- a/fe/app/_hooks/use-cool-down.ts
+++ b/fe/app/_hooks/use-cool-down.ts
@@ -1,59 +1,44 @@
"use client";
+
import { useEffect, useMemo } from "react";
import { useReadContract } from "wagmi";
import { forgeContractConfig } from "../_contracts/forge-contract-config";
import { useTokens } from "./use-tokens";
-type ParamsT = {
- isBaseToken: boolean;
- isMintError: Error | null;
- isMintConfirmed: boolean;
-};
-
-export const useCoolDown = (params: ParamsT) => {
- const { isBaseToken, isMintError, isMintConfirmed } = params;
-
- const { setIsCoolDown } = useTokens();
-
- // Fetch cooldown delay from contract
- const { data: coolDownDelay } = useReadContract({
- ...forgeContractConfig,
- functionName: "I_COOL_DOWN_DELAY",
- });
-
- // Convert cooldown delay to milliseconds
- const coolDownDelayInMs: number | null = useMemo(() => {
- return coolDownDelay ? Number(coolDownDelay) * 1000 : null;
- }, [coolDownDelay]);
-
- // Handle cooldown logic
- useEffect(() => {
- if (!isBaseToken || !coolDownDelayInMs) return;
-
- // Reset cooldown on error
- if (isMintError) {
- setIsCoolDown(false);
- return;
- }
-
- // Start cooldown timer only after transaction is confirmed
- if (isMintConfirmed) {
- setIsCoolDown(true);
- const timer = setTimeout(() => {
- setIsCoolDown(false);
- }, coolDownDelayInMs);
-
- return () => {
- clearTimeout(timer);
- };
- }
- }, [
- isBaseToken,
- isMintError,
- isMintConfirmed,
- coolDownDelayInMs,
- setIsCoolDown,
- ]);
-
- return coolDownDelayInMs ? coolDownDelayInMs / 1000 : null;
-};
+type Params = {
+ isBaseToken: boolean
+ isMintError: Error | null
+ isMintConfirmed: boolean
+}
+
+export const useCoolDown = (params: Params) => {
+ const { isBaseToken, isMintError, isMintConfirmed } = params
+
+ const { setIsCoolDown, setCoolDownEndTime } = useTokens();
+
+ const { data: coolDownDelay } = useReadContract({
+ ...forgeContractConfig,
+ functionName: "I_COOL_DOWN_DELAY",
+ });
+
+ const coolDownDelayInMs = useMemo(
+ () => (coolDownDelay ? Number(coolDownDelay) * 1000 : null),
+ [coolDownDelay]
+ );
+
+ useEffect(() => {
+ if (!isBaseToken || !coolDownDelayInMs) return;
+
+ if (isMintError) {
+ setIsCoolDown(false);
+ setCoolDownEndTime(null);
+ return;
+ }
+
+ if (isMintConfirmed) {
+ setIsCoolDown(true);
+ setCoolDownEndTime(Date.now() + coolDownDelayInMs);
+ }
+ }, [isBaseToken, isMintError, isMintConfirmed, coolDownDelayInMs, setIsCoolDown, setCoolDownEndTime]);
+
+};
\ No newline at end of file
diff --git a/fe/app/_hooks/use-tokens.ts b/fe/app/_hooks/use-tokens.ts
index 5a9ab7c..57363e1 100644
--- a/fe/app/_hooks/use-tokens.ts
+++ b/fe/app/_hooks/use-tokens.ts
@@ -7,6 +7,9 @@ export type TokensContextT = {
// base tokens
isCoolDown: boolean;
setIsCoolDown: React.Dispatch>;
+ coolDownEndTime: number | null;
+ setCoolDownEndTime: React.Dispatch>;
+
// forge tokens
forgeabilityByTokenId: Record;
From 54912b28964b5e81ef1bbc6217108e10480da1e1 Mon Sep 17 00:00:00 2001
From: siegfriedbz
Date: Sat, 22 Nov 2025 15:35:55 +0100
Subject: [PATCH 2/5] feat: improve UI/UX across components
---
fe/app/_components/burn-dialog.tsx | 5 +-
fe/app/_components/footer.tsx | 27 +-
fe/app/_components/header.tsx | 3 +-
fe/app/_components/hero.tsx | 2 +-
fe/app/_components/token-card.tsx | 5 +-
fe/app/_components/trade-dialog.tsx | 31 ++-
fe/app/_components/trade-form.tsx | 13 +-
fe/app/globals.css | 2 +-
fe/components/ui/dialog.tsx | 200 +++++++--------
fe/components/ui/dropdown-menu.tsx | 380 ++++++++++++++--------------
fe/components/ui/form.tsx | 264 +++++++++----------
fe/components/ui/label.tsx | 34 +--
fe/components/ui/select.tsx | 284 ++++++++++-----------
fe/components/ui/separator.tsx | 42 +--
fe/components/ui/tooltip.tsx | 84 +++---
15 files changed, 704 insertions(+), 672 deletions(-)
diff --git a/fe/app/_components/burn-dialog.tsx b/fe/app/_components/burn-dialog.tsx
index 670eab3..43924d0 100644
--- a/fe/app/_components/burn-dialog.tsx
+++ b/fe/app/_components/burn-dialog.tsx
@@ -3,6 +3,7 @@
import { LoaderIcon } from "lucide-react";
import Image from "next/image";
import type { ComponentProps, FC, PropsWithChildren } from "react";
+import { useAccount } from "wagmi";
import { Button } from "@/components/ui/button";
import {
Dialog,
@@ -22,6 +23,8 @@ type Props = ComponentProps & {
export const BurnDialog: FC> = (props) => {
const { tokenId: tokenIdToBurn, tokenBalance, ...rest } = props;
+ const { address } = useAccount();
+
const { burnCall, isPending, isConfirming } = useBurn({
tokenIdToBurn,
});
@@ -31,7 +34,7 @@ export const BurnDialog: FC> = (props) => {
diff --git a/fe/app/page.tsx b/fe/app/page.tsx
index 0d82398..41bceb9 100644
--- a/fe/app/page.tsx
+++ b/fe/app/page.tsx
@@ -11,7 +11,7 @@ export default function Home() {
-
+
From cd4f4d6786f353f2a78ece90ebdffde5909df471 Mon Sep 17 00:00:00 2001
From: siegfriedbz
Date: Sat, 22 Nov 2025 15:36:19 +0100
Subject: [PATCH 4/5] feat: update wagmi and root providers
---
fe/app/_context/custom-wagmi-provider.tsx | 37 ++++++-----------------
fe/app/_context/root-providers.tsx | 2 --
2 files changed, 10 insertions(+), 29 deletions(-)
diff --git a/fe/app/_context/custom-wagmi-provider.tsx b/fe/app/_context/custom-wagmi-provider.tsx
index 7217f1e..d2c6319 100644
--- a/fe/app/_context/custom-wagmi-provider.tsx
+++ b/fe/app/_context/custom-wagmi-provider.tsx
@@ -1,14 +1,9 @@
"use client";
import "@rainbow-me/rainbowkit/styles.css";
-import {
- darkTheme,
- lightTheme,
- RainbowKitProvider,
-} from "@rainbow-me/rainbowkit";
+import { darkTheme, RainbowKitProvider } from "@rainbow-me/rainbowkit";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
-import { useTheme } from "next-themes";
-import { type FC, type PropsWithChildren, useMemo, useState } from "react";
+import { type FC, type PropsWithChildren, useState } from "react";
import {
type Config as WagmiConfig,
WagmiProvider,
@@ -16,13 +11,13 @@ import {
} from "wagmi";
import { wagmiHttpConfig } from "../_config/wagmi";
-const rainbowLightTheme = {
- accentColor: "#9b2c2c",
- accentColorForeground: "#fff",
-};
-const rainbowDarkTheme = {
- accentColor: "#df7368",
- accentColorForeground: "#000",
+const rainbowTheme = {
+ ...darkTheme(),
+ colors: {
+ ...darkTheme().colors,
+ modalBackground: "#151210",
+ accentColor: "#df7368",
+ },
};
type Props = {
@@ -32,25 +27,13 @@ type Props = {
export const CustomWagmiProvider: FC> = (props) => {
const { initialWagmiState, children } = props;
- const { theme, systemTheme } = useTheme();
- const currentTheme = useMemo(
- () => (theme === "system" ? systemTheme : theme),
- [theme, systemTheme],
- );
-
const [wagmiConfig] = useState(() => wagmiHttpConfig);
const [queryClient] = useState(() => new QueryClient());
- const rainbowTheme = useMemo(() => {
- return currentTheme === "dark"
- ? darkTheme(rainbowDarkTheme)
- : lightTheme(rainbowLightTheme);
- }, [currentTheme]);
-
return (
-
+
{children}
diff --git a/fe/app/_context/root-providers.tsx b/fe/app/_context/root-providers.tsx
index 86d1928..e24dbe0 100644
--- a/fe/app/_context/root-providers.tsx
+++ b/fe/app/_context/root-providers.tsx
@@ -1,7 +1,6 @@
"use client";
import type { FC, PropsWithChildren } from "react";
-import { Toaster } from "sonner";
import type { State as WagmiState } from "wagmi";
import { CustomWagmiProvider } from "./custom-wagmi-provider";
import { ThemeProvider } from "./theme-provider";
@@ -23,7 +22,6 @@ export const RootProviders: FC> = (props) => {
{children}
-
);
};
From e77a21956e683f2cb9499edf4a26acac0aaac24a Mon Sep 17 00:00:00 2001
From: siegfriedbz
Date: Sat, 22 Nov 2025 15:36:32 +0100
Subject: [PATCH 5/5] feat: update hooks and balance logic
---
fe/app/_hooks/use-balanceOf.tsx | 3 +--
fe/app/_hooks/use-burn.tsx | 14 --------------
fe/app/_hooks/use-mint.tsx | 14 --------------
fe/app/_hooks/use-trade.tsx | 14 --------------
4 files changed, 1 insertion(+), 44 deletions(-)
diff --git a/fe/app/_hooks/use-balanceOf.tsx b/fe/app/_hooks/use-balanceOf.tsx
index b705a61..a2cb15d 100644
--- a/fe/app/_hooks/use-balanceOf.tsx
+++ b/fe/app/_hooks/use-balanceOf.tsx
@@ -6,7 +6,6 @@ import {
useQuery,
} from "@tanstack/react-query";
import { readContract } from "@wagmi/core";
-import { toast } from "sonner";
import type { ReadContractErrorType } from "viem";
import { useAccount } from "wagmi";
import { wagmiHttpConfig } from "../_config/wagmi";
@@ -40,7 +39,7 @@ export const useBalanceOf = (params: ParamsT) => {
});
if (error) {
- toast.error((error as ReadContractErrorType).shortMessage || error.message);
+ console.log((error as ReadContractErrorType).shortMessage || error.message);
}
return { tokenBalance, refetch, error, isPending };
diff --git a/fe/app/_hooks/use-burn.tsx b/fe/app/_hooks/use-burn.tsx
index b4da206..ce0ee9b 100644
--- a/fe/app/_hooks/use-burn.tsx
+++ b/fe/app/_hooks/use-burn.tsx
@@ -1,7 +1,6 @@
"use client";
import { useCallback } from "react";
-import { toast } from "sonner";
import type { BaseError } from "wagmi";
import { forgeContractConfig } from "@/app/_contracts/forge-contract-config";
import { useWriteAndWait } from "./use-write-and-wait";
@@ -29,19 +28,6 @@ export const useBurn = (params: ParamsT) => {
if (error) {
console.log((error as BaseError).shortMessage || error.message);
- toast.error(`Burning Token #${tokenIdToBurn} failed.`);
- }
-
- if (hash) {
- console.log(`Burn Token #${tokenIdToBurn} Transaction Hash: ${hash}`);
- }
-
- if (isConfirming) {
- toast.info(`Waiting for Burn Token #${tokenIdToBurn} confirmation...`);
- }
-
- if (isConfirmed) {
- toast.success(`Burn Token #${tokenIdToBurn} confirmed.`);
}
return { burnCall, hash, error, isPending, isConfirming, isConfirmed };
diff --git a/fe/app/_hooks/use-mint.tsx b/fe/app/_hooks/use-mint.tsx
index f6cf66d..c3bd09e 100644
--- a/fe/app/_hooks/use-mint.tsx
+++ b/fe/app/_hooks/use-mint.tsx
@@ -1,7 +1,6 @@
"use client";
import { useCallback } from "react";
-import { toast } from "sonner";
import type { BaseError } from "wagmi";
import { forgeContractConfig } from "../_contracts/forge-contract-config";
import { useWriteAndWait } from "./use-write-and-wait";
@@ -28,19 +27,6 @@ export const useMint = (params: ParamsT) => {
if (error) {
console.log((error as BaseError).shortMessage || error.message);
- toast.error(`Minting Token #${tokenId} failed.`);
- }
-
- if (hash) {
- console.log(`Mint Token #${tokenId} Transaction Hash: ${hash}`);
- }
-
- if (isConfirming) {
- toast.info(`Waiting for Mint Token #${tokenId} confirmation...`);
- }
-
- if (isConfirmed) {
- toast.success(`Mint Token #${tokenId} confirmed.`);
}
return { mintCall, hash, error, isPending, isConfirming, isConfirmed };
diff --git a/fe/app/_hooks/use-trade.tsx b/fe/app/_hooks/use-trade.tsx
index 99ea337..9c4997e 100644
--- a/fe/app/_hooks/use-trade.tsx
+++ b/fe/app/_hooks/use-trade.tsx
@@ -1,7 +1,6 @@
"use client";
import { useCallback } from "react";
-import { toast } from "sonner";
import type { BaseError } from "wagmi";
import { forgeContractConfig } from "../_contracts/forge-contract-config";
import { useWriteAndWait } from "./use-write-and-wait";
@@ -29,19 +28,6 @@ export const useTrade = (params: ParamsT) => {
if (error) {
console.log((error as BaseError).shortMessage || error.message);
- toast.error(`Trading Token #${tokenIdToBurn} failed.`);
- }
-
- if (hash) {
- console.log(`Trading Token #${tokenIdToBurn} Transaction Hash: ${hash}`);
- }
-
- if (isConfirming) {
- toast.info(`Waiting for Trading Token #${tokenIdToBurn} confirmation...`);
- }
-
- if (isConfirmed) {
- toast.success(`Trading Token #${tokenIdToBurn} confirmed.`);
}
return { tradeCall, hash, error, isPending, isConfirming, isConfirmed };