diff --git a/src/app/bakery/components/Banners/BalanceBanner.tsx b/src/app/bakery/components/Banners/BalanceBanner.tsx
new file mode 100644
index 00000000..8ad00761
--- /dev/null
+++ b/src/app/bakery/components/Banners/BalanceBanner.tsx
@@ -0,0 +1,195 @@
+"use client";
+
+import clsx from "clsx";
+import { ReactNode } from "react";
+import { Logo } from "@breadcoop/ui";
+import { useConnectedUser } from "@/app/core/hooks/useConnectedUser";
+import { useTokenBalances } from "@/app/core/context/TokenBalanceContext/TokenBalanceContext";
+import { XDAIIcon } from "@/app/core/components/Icons/TokenIcons";
+import type { TSwapMode } from "../Swap/Panel";
+import { ArrowIcon } from "./Shared";
+
+const GAS_FLOOR = 0.001;
+
+type BalanceBannerProps = {
+ onSelectTab: (mode: TSwapMode) => void;
+};
+
+export function BalanceBanner({ onSelectTab }: BalanceBannerProps) {
+ const { user } = useConnectedUser();
+ const { xDAI, BREAD } = useTokenBalances();
+
+ if (user.status !== "CONNECTED") return null;
+ if (!xDAI || xDAI.status !== "SUCCESS") return null;
+ if (!BREAD || BREAD.status !== "SUCCESS") return null;
+
+ const xdaiBalance = parseFloat(xDAI.value);
+ const breadBalance = parseFloat(BREAD.value);
+
+ if (xdaiBalance < GAS_FLOOR) {
+ return ;
+ }
+
+ if (breadBalance === 0) {
+ return ;
+ }
+
+ return null;
+}
+
+function NoGasBanner({ onSelectTab }: BalanceBannerProps) {
+ const title = "You need xDAI to bake";
+ const description =
+ "xDAI pays for gas on Gnosis Chain. Bridge in from another chain or buy with fiat to get started.";
+
+ return (
+
+ {/* large */}
+
+
+
+
+
+
+
onSelectTab("BRIDGE")}
+ >
+ Bridge
+
+
onSelectTab("BUY")}
+ >
+ Buy
+
+
+
+ {/* small */}
+
+
+
+
onSelectTab("BRIDGE")}
+ withArrow
+ >
+ Bridge
+
+
onSelectTab("BUY")}
+ withArrow
+ >
+ Buy
+
+
+
+
+ );
+}
+
+function NoBreadBanner({ onSelectTab }: BalanceBannerProps) {
+ const title = "Ready to bake?";
+ const description =
+ "You have xDAI but no BREAD yet. Bake BREAD to join the Solidarity Fund and support a worker-owned future.";
+
+ return (
+
+ {/* large */}
+
+
+
+
+
+
+
onSelectTab("BAKE")}
+ >
+ Bake BREAD
+
+
+
+ {/* small */}
+
+
+
onSelectTab("BAKE")}
+ >
+ Bake BREAD
+
+
+
+ );
+}
+
+function InlineText({
+ title,
+ description,
+}: {
+ title: string;
+ description: string;
+}) {
+ return (
+
+
+ {title}
+
+ {description}
+
+ );
+}
+
+function StackedText({
+ title,
+ description,
+}: {
+ title: string;
+ description: string;
+}) {
+ return (
+
+ {title}
+ {description}
+
+ );
+}
+
+function FullWidthBand({ children }: { children: ReactNode }) {
+ return (
+
+ );
+}
+
+function PillButton({
+ children,
+ onClick,
+ variant,
+ withArrow = false,
+}: {
+ children: string;
+ onClick: () => void;
+ variant: "primary" | "secondary";
+ withArrow?: boolean;
+}) {
+ return (
+
+ {children}
+ {withArrow && }
+
+ );
+}
diff --git a/src/app/bakery/components/Swap/NewSwap.tsx b/src/app/bakery/components/Swap/NewSwap.tsx
index 8d686f83..da4c86cf 100644
--- a/src/app/bakery/components/Swap/NewSwap.tsx
+++ b/src/app/bakery/components/Swap/NewSwap.tsx
@@ -1,5 +1,12 @@
import { useConnectedUser } from "@/app/core/hooks/useConnectedUser";
-import React, { ChangeEvent, useCallback, useState, useEffect } from "react";
+import React, {
+ ChangeEvent,
+ Dispatch,
+ SetStateAction,
+ useCallback,
+ useState,
+ useEffect,
+} from "react";
import { Address } from "viem";
import { useSearchParams } from "next/navigation";
// import { FromPanel } from "./FromPanel";
@@ -38,16 +45,15 @@ const notes: Record = {
"BUY": "Clicking the button will open the Peer website where you can complete your purchase of xDAI to bake into BREAD.",
};
-const initialSwapState: TSwapState = {
- mode: "BAKE",
- value: "",
+type NewSwapProps = {
+ swapState: TSwapState;
+ setSwapState: Dispatch>;
};
-const NewSwap = () => {
+const NewSwap = ({ swapState, setSwapState }: NewSwapProps) => {
const { user, isSafe } = useConnectedUser();
const [connectedAccountAddress, setConnectedAccountAddress] =
useState(null);
- const [swapState, setSwapState] = useState(initialSwapState);
const searchParams = useSearchParams();
const { toastDispatch } = useToast();
const { isMobile } = useStrictMobile()
@@ -69,13 +75,15 @@ const NewSwap = () => {
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams.get("peer")]);
- if (
- user.status === "CONNECTED" &&
- user.address !== connectedAccountAddress
- ) {
- setConnectedAccountAddress(user.address);
- setSwapState((state) => ({ ...state, value: "" }));
- }
+ useEffect(() => {
+ if (
+ user.status === "CONNECTED" &&
+ user.address !== connectedAccountAddress
+ ) {
+ setConnectedAccountAddress(user.address);
+ setSwapState((state) => ({ ...state, value: "" }));
+ }
+ }, [user, connectedAccountAddress, setSwapState]);
const clearInputValue = useCallback(() => {
setSwapState((state) => ({ ...state, value: "" }));
diff --git a/src/app/components/HomeBanner.tsx b/src/app/components/HomeBanner.tsx
new file mode 100644
index 00000000..a046563d
--- /dev/null
+++ b/src/app/components/HomeBanner.tsx
@@ -0,0 +1,25 @@
+"use client";
+
+import dynamic from "next/dynamic";
+import type { TSwapMode } from "../bakery/components/Swap/Panel";
+
+const BalanceBanner = dynamic(
+ () =>
+ import("../bakery/components/Banners/BalanceBanner").then(
+ (m) => m.BalanceBanner
+ ),
+ { ssr: false }
+);
+
+export const SWAP_WIDGET_ANCHOR_ID = "swap-widget";
+
+export default function HomeBanner() {
+ const handleSelectTab = (mode: TSwapMode) => {
+ if (typeof window === "undefined") return;
+ window.location.hash = mode.toLowerCase();
+ const el = document.getElementById(SWAP_WIDGET_ANCHOR_ID);
+ if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
+ };
+
+ return ;
+}
diff --git a/src/app/components/SwapWrapper.tsx b/src/app/components/SwapWrapper.tsx
index b95ec2d4..d59a78b4 100644
--- a/src/app/components/SwapWrapper.tsx
+++ b/src/app/components/SwapWrapper.tsx
@@ -1,21 +1,45 @@
"use client";
import dynamic from "next/dynamic";
-// import NewSwap from "../bakery/components/Swap/NewSwap";
-
-// const Swap = dynamic(() => import("../bakery/components/Swap/Swap"), {
-// ssr: false,
-// });
+import { useEffect, useState } from "react";
+import type { TSwapMode, TSwapState } from "../bakery/components/Swap/Panel";
+import { SWAP_WIDGET_ANCHOR_ID } from "./HomeBanner";
const NewSwap = dynamic(() => import("../bakery/components/Swap/NewSwap"), {
ssr: false,
});
+const initialSwapState: TSwapState = {
+ mode: "BAKE",
+ value: "",
+};
+
+const VALID_MODES: TSwapMode[] = ["BAKE", "BURN", "BRIDGE", "BUY"];
+
+function readModeFromHash(): TSwapMode | null {
+ if (typeof window === "undefined") return null;
+ const hash = window.location.hash.replace(/^#/, "").toUpperCase();
+ return (VALID_MODES as string[]).includes(hash) ? (hash as TSwapMode) : null;
+}
+
export default function SwapWrapper() {
+ const [swapState, setSwapState] = useState(initialSwapState);
+
+ useEffect(() => {
+ const mode = readModeFromHash();
+ if (mode) setSwapState({ mode, value: "" });
+
+ const onHashChange = () => {
+ const next = readModeFromHash();
+ if (next) setSwapState({ mode: next, value: "" });
+ };
+ window.addEventListener("hashchange", onHashChange);
+ return () => window.removeEventListener("hashchange", onHashChange);
+ }, []);
+
return (
- <>
- {/* */}
-
- >
+
+
+
);
}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 49fe74e9..3667f22b 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -4,6 +4,7 @@ import { type Metadata } from "next";
import { AppTitle } from "./bakery/components/AppTitle";
import FAQWrapper from "./components/FAQWrapper";
+import HomeBanner from "./components/HomeBanner";
import SwapWrapper from "./components/SwapWrapper";
import { Body, Heading1, Heading2, LiftedButton } from "@breadcoop/ui";
import { Apy } from "./bakery/components/Apy";
@@ -19,6 +20,8 @@ import { generateMetadata } from "@/lib/site-metadata";
export default function Home() {
return (
+ <>
+
@@ -78,5 +81,6 @@ export default function Home() {
+ >
);
}