diff --git a/app/dwarves/earn/fixed-yield/page.tsx b/app/dwarves/earn/fixed-yield/page.tsx
deleted file mode 100644
index 4f51405..0000000
--- a/app/dwarves/earn/fixed-yield/page.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-"use client";
-
-import { LoginPopover } from "@/components/login-popover";
-import { Logo } from "@/components/logo";
-import ProfileDropdown from "@/components/profile-dropdown";
-import { Stake } from "@/components/stake";
-import { TopBar } from "@mochi-ui/core";
-import { LoginWidget, useLoginWidget } from "@mochi-web3/login-widget";
-import { Suspense } from "react";
-
-export default function Page() {
- const { isLoggedIn, isLoggingIn } = useLoginWidget();
-
- return (
-
- }
- rightSlot={!isLoggedIn ? : }
- />
- {isLoggedIn ? (
-
-
-
- ) : (
-
- {!isLoggingIn && (
-
-
-
- )}
-
- )}
-
- );
-}
diff --git a/app/dwarves/earn/flexible-yield/page.tsx b/app/dwarves/earn/flexible-yield/page.tsx
deleted file mode 100644
index cc9139c..0000000
--- a/app/dwarves/earn/flexible-yield/page.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-"use client";
-
-import { LoginPopover } from "@/components/login-popover";
-import { Logo } from "@/components/logo";
-import ProfileDropdown from "@/components/profile-dropdown";
-import { Stake } from "@/components/stake";
-import { TopBar } from "@mochi-ui/core";
-import { LoginWidget, useLoginWidget } from "@mochi-web3/login-widget";
-import { Suspense } from "react";
-
-export default function Page() {
- const { isLoggedIn, isLoggingIn } = useLoginWidget();
-
- return (
-
- }
- rightSlot={!isLoggedIn ? : }
- />
- {isLoggedIn ? (
-
-
-
- ) : (
-
- {!isLoggingIn && (
-
-
-
- )}
-
- )}
-
- );
-}
diff --git a/app/dwarves/page.tsx b/app/dwarves/page.tsx
index 48e18cd..4135ff1 100644
--- a/app/dwarves/page.tsx
+++ b/app/dwarves/page.tsx
@@ -4,6 +4,7 @@ import { Footer } from "@/components/footer";
import { LoginPopover } from "@/components/login-popover";
import { Logo } from "@/components/logo";
import ProfileDropdown from "@/components/profile-dropdown";
+import { FixedStakeModal } from "@/components/stake/fixed/fixed-stake-modal";
import { ROUTES } from "@/constants/routes";
import {
Badge,
@@ -26,10 +27,22 @@ import { useLoginWidget } from "@mochi-web3/login-widget";
import Image from "next/image";
import Link from "next/link";
import { Suspense, useState } from "react";
+import { useDisclosure } from "@dwarvesf/react-hooks";
+import { FlexibleStakeModal } from "@/components/stake/flexible/flexible-stake-modal";
const Overview = () => {
const { isLoggedIn } = useLoginWidget();
const [showInfo, setShowInfo] = useState(false);
+ const {
+ isOpen: isOpenFixedStakeModal,
+ onOpenChange: onOpenChangeFixedStakeModal,
+ onOpen: onOpenFixedStakeModal,
+ } = useDisclosure();
+ const {
+ isOpen: isOpenFlexibleStakeModal,
+ onOpenChange: onOpenChangeFlexibleStakeModal,
+ onOpen: onOpenFlexibleStakeModal,
+ } = useDisclosure();
return (
@@ -134,11 +147,13 @@ const Overview = () => {
},
},
action: (
-
-
-
+
),
},
{
@@ -163,11 +178,13 @@ const Overview = () => {
},
},
action: (
-
-
-
+
),
},
{
@@ -371,9 +388,7 @@ const Overview = () => {
>
),
action: (
-
-
-
+
),
},
{
@@ -434,6 +449,14 @@ const Overview = () => {
))}
+
+
);
};
diff --git a/components/stake.tsx b/components/stake.tsx
deleted file mode 100644
index 13526b5..0000000
--- a/components/stake.tsx
+++ /dev/null
@@ -1,282 +0,0 @@
-import {
- Avatar,
- Button,
- Switch,
- ToggleButton,
- ToggleButtonGroup,
- Tooltip,
- Typography,
-} from "@mochi-ui/core";
-import { ArrowLeftLine } from "@mochi-ui/icons";
-import { useState } from "react";
-import * as Slider from "@radix-ui/react-slider";
-import clsx from "clsx";
-import { utils } from "@consolelabs/mochi-formatter";
-import { TokenAmount, formatTokenAmount } from "@/utils/number";
-import Link from "next/link";
-import { ROUTES } from "@/constants/routes";
-
-const flexibleAPR = 28.7;
-const fixedDurationAPR = [
- {
- duration: "14D",
- apr: 2.79,
- },
- {
- duration: "30D",
- apr: 3.59,
- },
- {
- duration: "60D",
- apr: 5.6,
- },
- {
- duration: "120D",
- apr: 7.7,
- },
-];
-
-interface Props {
- type: "fixed" | "flexible";
-}
-
-export const Stake = (props: Props) => {
- const { type } = props;
-
- const [percent, setPercent] = useState(0);
- const [amount, setAmount] = useState({
- value: 0,
- display: "",
- });
- const [duration, setDuration] = useState("");
- const balance = 23667;
-
- const onMaxAmount = () => {
- setPercent(100);
- setAmount(formatTokenAmount(balance));
- };
-
- const onKeyDown = (e: React.KeyboardEvent) => {
- // Accept only a positive integer / float input
- if (
- e.key === "Backspace" ||
- e.key === "Delete" ||
- e.key === "Tab" ||
- e.key === "Escape" ||
- e.key === "Enter" ||
- e.key === "." ||
- e.key === "," ||
- e.key === "ArrowLeft" ||
- e.key === "ArrowRight" ||
- Number.isFinite(Number(e.key)) ||
- // allow for select all
- (e.metaKey && e.key.toLowerCase() === "a")
- ) {
- // Accept only one dot(".")
- if (amount.display.indexOf(".") !== -1 && e.key === ".") {
- e.preventDefault();
- } else {
- // Accept the first dot(".")
- return;
- }
- } else {
- e.preventDefault();
- }
- if (e.key === "-" || !Number.isFinite(Number(e.key))) {
- e.preventDefault();
- }
- };
-
- const onChange = (e: React.ChangeEvent) => {
- const formattedAmount = formatTokenAmount(e.target.value);
- formattedAmount.display = e.target.value;
- setAmount(formattedAmount);
- const percent = Math.max(
- 0,
- Math.min(100, (formattedAmount.value / balance) * 100)
- );
- setPercent(percent);
- };
-
- const onBlur = (e: React.FocusEvent) => {
- const formattedAmount = formatTokenAmount(e.target.value);
- setAmount(formattedAmount);
- const percent = Math.max(
- 0,
- Math.min(100, (formattedAmount.value / balance) * 100)
- );
- setPercent(percent);
- };
-
- return (
-
-
-
-
-
- Back
-
-
-
-
- Stake ICY
-
-
- Stake ICY to receive DFG and revenue share rewards.
-
-
-
- {type === "fixed" ? (
-
- {fixedDurationAPR.map((each) => (
-
- {each.duration}
-
- {each.apr}%
-
-
- ))}
-
- ) : (
- <>
-
-
- {flexibleAPR}%
-
-
- Fixed APR
-
-
-
- Withdraw anytime at market prices
-
- >
- )}
-
-
-
-
- You’re staking
-
-
-
-
- {
- if (checked) {
- onMaxAmount();
- }
- }}
- />
-
-
-
-
-
-
-
{
- const percent = value[0];
- setPercent(percent);
- setAmount(formatTokenAmount((balance * percent) / 100));
- }}
- max={100}
- step={1}
- >
-
-
-
-
-
-
-
- {percent.toFixed(2)}%
-
-
-
-
-
-
- ≈ $0.00 USD
-
-
- Balance:{" "}
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/components/stake/fixed/fixed-stake-content.tsx b/components/stake/fixed/fixed-stake-content.tsx
new file mode 100644
index 0000000..7757664
--- /dev/null
+++ b/components/stake/fixed/fixed-stake-content.tsx
@@ -0,0 +1,91 @@
+import {
+ Button,
+ ToggleButton,
+ ToggleButtonGroup,
+ Typography,
+} from "@mochi-ui/core";
+import { useState } from "react";
+import clsx from "clsx";
+import { TokenAmount } from "@/utils/number";
+import { StakeInput } from "../stake-input";
+
+const fixedDurationAPR = [
+ {
+ duration: "14D",
+ apr: 2.79,
+ },
+ {
+ duration: "30D",
+ apr: 3.59,
+ },
+ {
+ duration: "60D",
+ apr: 5.6,
+ },
+ {
+ duration: "120D",
+ apr: 7.7,
+ },
+];
+
+interface Props {
+ onStake: () => void;
+}
+
+export const FixedStakeContent = (props: Props) => {
+ const { onStake } = props;
+ const [amount, setAmount] = useState({
+ value: 0,
+ display: "",
+ });
+ const [duration, setDuration] = useState("");
+ const balance = 23667;
+
+ return (
+
+
+
+ {fixedDurationAPR.map((each) => (
+
+ {each.duration}
+
+ {each.apr}%
+
+
+ ))}
+
+
+
+
+
+ Total Est. Rewards
+
+
+ 0
+
+
+
+
+ );
+};
diff --git a/components/stake/fixed/fixed-stake-modal.tsx b/components/stake/fixed/fixed-stake-modal.tsx
new file mode 100644
index 0000000..f6703b1
--- /dev/null
+++ b/components/stake/fixed/fixed-stake-modal.tsx
@@ -0,0 +1,68 @@
+import {
+ Modal,
+ ModalClose,
+ ModalContent,
+ ModalOverlay,
+ ModalPortal,
+ ModalTitle,
+ Typography,
+} from "@mochi-ui/core";
+import { CloseLgLine } from "@mochi-ui/icons";
+import { useState } from "react";
+import { FixedStakeContent } from "./fixed-stake-content";
+import { FixedStakeResponse } from "./fixed-stake-response";
+
+interface Props {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}
+
+export const FixedStakeModal = (props: Props) => {
+ const { open, onOpenChange } = props;
+ const [state, setState] = useState<"init" | "success">("init");
+
+ return (
+ {
+ onOpenChange(open);
+ if (!open) {
+ setState("init");
+ }
+ }}
+ >
+
+
+
+ {state === "init" && (
+
+
+ Stake DFG
+
+
+
+
+
+ )}
+ {state === "init" && (
+ {
+ setState("success");
+ }}
+ />
+ )}
+ {state === "success" && (
+ {
+ onOpenChange(false);
+ setTimeout(() => {
+ setState("init");
+ }, 300);
+ }}
+ />
+ )}
+
+
+
+ );
+};
diff --git a/components/stake/fixed/fixed-stake-response.tsx b/components/stake/fixed/fixed-stake-response.tsx
new file mode 100644
index 0000000..6cfef75
--- /dev/null
+++ b/components/stake/fixed/fixed-stake-response.tsx
@@ -0,0 +1,103 @@
+import {
+ Button,
+ ModalClose,
+ Switch,
+ Tooltip,
+ Typography,
+} from "@mochi-ui/core";
+import { CheckCircleHalfColoredLine, CheckLine } from "@mochi-ui/icons";
+import Image from "next/image";
+
+interface Props {
+ onClose: () => void;
+}
+
+export const FixedStakeResponse = (props: Props) => {
+ const { onClose } = props;
+
+ return (
+ <>
+
+
+
+
+
+ Stake Successful
+
+
+
+ You’re staking
+
+
+
+
+ 2,000 ICY
+
+
+
+
+
+
+
+
+
+
+ 3
+
+
Interest end date
+
+
08/03/2025 07:00
+
+
+
+
+
+ 4
+
+
Next staking date
+
+
08/03/2025 07:00
+
+
+
+
+
+
+ Auto-Staking
+
+ Potential for profit maximization
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/components/stake/flexible/flexible-stake-content.tsx b/components/stake/flexible/flexible-stake-content.tsx
new file mode 100644
index 0000000..9aac080
--- /dev/null
+++ b/components/stake/flexible/flexible-stake-content.tsx
@@ -0,0 +1,48 @@
+import { Button, Typography } from "@mochi-ui/core";
+import { useState } from "react";
+import { TokenAmount } from "@/utils/number";
+import { StakeInput } from "../stake-input";
+
+const flexibleAPR = 28.7;
+
+interface Props {
+ onStake: () => void;
+}
+
+export const FlexibleStakeContent = (props: Props) => {
+ const { onStake } = props;
+ const [amount, setAmount] = useState({
+ value: 0,
+ display: "",
+ });
+ const balance = 23667;
+
+ return (
+
+
+
+
+
+ {flexibleAPR}%
+
+
+ Fixed ICY
+
+
+
+ Withdraw anytime at market prices
+
+
+
+
+
+
+ );
+};
diff --git a/components/stake/flexible/flexible-stake-modal.tsx b/components/stake/flexible/flexible-stake-modal.tsx
new file mode 100644
index 0000000..7131755
--- /dev/null
+++ b/components/stake/flexible/flexible-stake-modal.tsx
@@ -0,0 +1,68 @@
+import {
+ Modal,
+ ModalClose,
+ ModalContent,
+ ModalOverlay,
+ ModalPortal,
+ ModalTitle,
+ Typography,
+} from "@mochi-ui/core";
+import { CloseLgLine } from "@mochi-ui/icons";
+import { useState } from "react";
+import { FlexibleStakeContent } from "./flexible-stake-content";
+import { FlexibleStakeResponse } from "./flexible-stake-response";
+
+interface Props {
+ open: boolean;
+ onOpenChange: (open: boolean) => void;
+}
+
+export const FlexibleStakeModal = (props: Props) => {
+ const { open, onOpenChange } = props;
+ const [state, setState] = useState<"init" | "success">("init");
+
+ return (
+ {
+ onOpenChange(open);
+ if (!open) {
+ setState("init");
+ }
+ }}
+ >
+
+
+
+ {state === "init" && (
+
+
+ Stake ICY
+
+
+
+
+
+ )}
+ {state === "init" && (
+ {
+ setState("success");
+ }}
+ />
+ )}
+ {state === "success" && (
+ {
+ onOpenChange(false);
+ setTimeout(() => {
+ setState("init");
+ }, 300);
+ }}
+ />
+ )}
+
+
+
+ );
+};
diff --git a/components/stake/flexible/flexible-stake-response.tsx b/components/stake/flexible/flexible-stake-response.tsx
new file mode 100644
index 0000000..7007417
--- /dev/null
+++ b/components/stake/flexible/flexible-stake-response.tsx
@@ -0,0 +1,93 @@
+import {
+ Button,
+ ModalClose,
+ Switch,
+ Tooltip,
+ Typography,
+} from "@mochi-ui/core";
+import { CheckCircleHalfColoredLine, CheckLine } from "@mochi-ui/icons";
+import Image from "next/image";
+
+interface Props {
+ onClose: () => void;
+}
+
+export const FlexibleStakeResponse = (props: Props) => {
+ const { onClose } = props;
+
+ return (
+ <>
+
+
+
+
+
+ Stake Successful
+
+
+
+ You’re staking
+
+
+
+
+ 2,000 ICY
+
+
+
+
+
+
+
+
+
+
+ 3
+
+
Interest distribution date
+
+
09/03/2025 07:00
+
+
+
+
+
+
+ Auto-Staking
+
+ Potential for profit maximization
+
+
+
+
+
+
+
+ >
+ );
+};
diff --git a/components/stake/stake-input.tsx b/components/stake/stake-input.tsx
new file mode 100644
index 0000000..9516e0a
--- /dev/null
+++ b/components/stake/stake-input.tsx
@@ -0,0 +1,196 @@
+import { Avatar, Button, Switch, Tooltip, Typography } from "@mochi-ui/core";
+import { Dispatch, SetStateAction, useState } from "react";
+import * as Slider from "@radix-ui/react-slider";
+import clsx from "clsx";
+import { utils } from "@consolelabs/mochi-formatter";
+import { TokenAmount, formatTokenAmount } from "@/utils/number";
+
+interface Props {
+ amount: TokenAmount;
+ setAmount: Dispatch>;
+}
+
+export const StakeInput = (props: Props) => {
+ const { amount, setAmount } = props;
+ const [percent, setPercent] = useState(0);
+ const balance = 23667;
+
+ const onMaxAmount = () => {
+ setPercent(100);
+ setAmount(formatTokenAmount(balance));
+ };
+
+ const onKeyDown = (e: React.KeyboardEvent) => {
+ // Accept only a positive integer / float input
+ if (
+ e.key === "Backspace" ||
+ e.key === "Delete" ||
+ e.key === "Tab" ||
+ e.key === "Escape" ||
+ e.key === "Enter" ||
+ e.key === "." ||
+ e.key === "," ||
+ e.key === "ArrowLeft" ||
+ e.key === "ArrowRight" ||
+ Number.isFinite(Number(e.key)) ||
+ // allow for select all
+ (e.metaKey && e.key.toLowerCase() === "a")
+ ) {
+ // Accept only one dot(".")
+ if (amount.display.indexOf(".") !== -1 && e.key === ".") {
+ e.preventDefault();
+ } else {
+ // Accept the first dot(".")
+ return;
+ }
+ } else {
+ e.preventDefault();
+ }
+ if (e.key === "-" || !Number.isFinite(Number(e.key))) {
+ e.preventDefault();
+ }
+ };
+
+ const onChange = (e: React.ChangeEvent) => {
+ const formattedAmount = formatTokenAmount(e.target.value);
+ formattedAmount.display = e.target.value;
+ setAmount(formattedAmount);
+ const percent = Math.max(
+ 0,
+ Math.min(100, (formattedAmount.value / balance) * 100)
+ );
+ setPercent(percent);
+ };
+
+ const onBlur = (e: React.FocusEvent) => {
+ const formattedAmount = formatTokenAmount(e.target.value);
+ setAmount(formattedAmount);
+ const percent = Math.max(
+ 0,
+ Math.min(100, (formattedAmount.value / balance) * 100)
+ );
+ setPercent(percent);
+ };
+
+ return (
+
+
+
+ You’re staking
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ console.log("Slider change");
+ const percent = value[0];
+ setPercent(percent);
+ setAmount(formatTokenAmount((balance * percent) / 100));
+ }}
+ onValueCommit={() => {
+ console.log("Slider commit");
+ }}
+ onClick={() => {
+ console.log("Slider click");
+ }}
+ max={100}
+ step={1}
+ >
+
+
+
+
+
+ {[0, 25, 50, 75, 100].map((milestone) => (
+
+ milestone
+ ? "bg-primary-solid"
+ : "bg-background-level2"
+ )}
+ onClick={() => {
+ console.log("click");
+ setPercent(milestone);
+ setAmount(formatTokenAmount((balance * milestone) / 100));
+ }}
+ />
+
+ ))}
+
+
+
+ {percent.toFixed(2)}%
+
+
+
+
+
+
+ ≈ $0.00 USD
+
+
+ Balance:{" "}
+
+
+
+
+
+ );
+};
diff --git a/constants/routes.ts b/constants/routes.ts
index aa35af9..8d32165 100644
--- a/constants/routes.ts
+++ b/constants/routes.ts
@@ -1,9 +1,5 @@
export const ROUTES = {
HOME: "/",
OVERVIEW: "/dwarves",
- EARN: {
- FLEXIBLE_YIELD: "/dwarves/earn/flexible-yield",
- FIXED_YIELD: "/dwarves/earn/fixed-yield",
- },
NFT: "/dwarves/nft",
};
diff --git a/package.json b/package.json
index e431224..2eb2862 100644
--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
},
"dependencies": {
"@consolelabs/mochi-formatter": "^20.0.5",
+ "@dwarvesf/react-hooks": "^0.8.2",
"@mochi-ui/core": "^0.13.4",
"@mochi-ui/icons": "^0.7.4",
"@mochi-ui/theme": "^0.17.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4017712..a766d8a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,9 @@ dependencies:
'@consolelabs/mochi-formatter':
specifier: ^20.0.5
version: 20.0.5(@consolelabs/mochi-rest@5.3.3)(ioredis@5.3.2)
+ '@dwarvesf/react-hooks':
+ specifier: ^0.8.2
+ version: 0.8.2(react-dom@18.0.0)(react@18.0.0)
'@mochi-ui/core':
specifier: ^0.13.4
version: 0.13.4(@types/react-dom@18.0.0)(@types/react@18.0.0)(react-dom@18.0.0)(react@18.0.0)(tailwindcss@3.3.0)