Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
195 changes: 195 additions & 0 deletions src/app/bakery/components/Banners/BalanceBanner.tsx
Original file line number Diff line number Diff line change
@@ -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 <NoGasBanner onSelectTab={onSelectTab} />;
}

if (breadBalance === 0) {
return <NoBreadBanner onSelectTab={onSelectTab} />;
}

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 (
<FullWidthBand>
{/* large */}
<div className="hidden gap-3 w-full sm:flex items-center">
<div className="shrink-0">
<XDAIIcon />
</div>
<InlineText title={title} description={description} />
<div className="flex gap-2 items-center shrink-0">
<PillButton
variant="secondary"
onClick={() => onSelectTab("BRIDGE")}
>
Bridge
</PillButton>
<PillButton
variant="primary"
onClick={() => onSelectTab("BUY")}
>
Buy
</PillButton>
</div>
</div>
{/* small */}
<div className="sm:hidden flex flex-col gap-2 w-full">
<StackedText title={title} description={description} />
<div className="flex gap-2">
<PillButton
variant="secondary"
onClick={() => onSelectTab("BRIDGE")}
withArrow
>
Bridge
</PillButton>
<PillButton
variant="primary"
onClick={() => onSelectTab("BUY")}
withArrow
>
Buy
</PillButton>
</div>
</div>
</FullWidthBand>
);
}

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 (
<FullWidthBand>
{/* large */}
<div className="hidden gap-3 w-full sm:flex items-center">
<div className="shrink-0">
<Logo variant="square" size={24} />
</div>
<InlineText title={title} description={description} />
<div className="shrink-0">
<PillButton
variant="primary"
onClick={() => onSelectTab("BAKE")}
>
Bake BREAD
</PillButton>
</div>
</div>
{/* small */}
<div className="sm:hidden flex flex-col gap-2 w-full">
<StackedText title={title} description={description} />
<PillButton
variant="primary"
onClick={() => onSelectTab("BAKE")}
>
Bake BREAD
</PillButton>
</div>
</FullWidthBand>
);
}

function InlineText({
title,
description,
}: {
title: string;
description: string;
}) {
return (
<div className="flex items-baseline gap-2 grow min-w-0">
<span className="text-sm font-bold text-white whitespace-nowrap">
{title}
</span>
<span className="text-sm text-white/90 truncate">{description}</span>
</div>
);
}

function StackedText({
title,
description,
}: {
title: string;
description: string;
}) {
return (
<div className="flex flex-col">
<span className="text-sm font-bold text-white">{title}</span>
<span className="text-xs text-white/90">{description}</span>
</div>
);
}

function FullWidthBand({ children }: { children: ReactNode }) {
return (
<div className="w-full bg-primary-orange mb-6 md:mb-8">
<div className="max-w-6xl mx-auto px-4 md:px-8 py-2">{children}</div>
</div>
);
}

function PillButton({
children,
onClick,
variant,
withArrow = false,
}: {
children: string;
onClick: () => void;
variant: "primary" | "secondary";
withArrow?: boolean;
}) {
return (
<button
type="button"
onClick={onClick}
className={clsx(
"flex items-center justify-center gap-2 rounded-full text-sm px-5 py-2 font-bold tracking-wider border-2 shadow-[0_2px_0_rgba(0,0,0,0.18)] hover:-translate-y-[1px] hover:shadow-[0_3px_0_rgba(0,0,0,0.2)] active:translate-y-[1px] active:shadow-none transition-all",
variant === "primary" &&
"bg-white text-primary-orange border-white",
variant === "secondary" &&
"bg-breadviolet-shaded text-white border-breadviolet-shaded dark:bg-breadpink-shaded dark:border-breadpink-shaded dark:text-breadgray-grey100"
)}
>
<span>{children}</span>
{withArrow && <ArrowIcon />}
</button>
);
}
34 changes: 21 additions & 13 deletions src/app/bakery/components/Swap/NewSwap.tsx
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -38,16 +45,15 @@ const notes: Record<TSwapState["mode"], string> = {
"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<SetStateAction<TSwapState>>;
};

const NewSwap = () => {
const NewSwap = ({ swapState, setSwapState }: NewSwapProps) => {
const { user, isSafe } = useConnectedUser();
const [connectedAccountAddress, setConnectedAccountAddress] =
useState<null | Address>(null);
const [swapState, setSwapState] = useState<TSwapState>(initialSwapState);
const searchParams = useSearchParams();
const { toastDispatch } = useToast();
const { isMobile } = useStrictMobile()
Expand All @@ -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: "" }));
Expand Down
25 changes: 25 additions & 0 deletions src/app/components/HomeBanner.tsx
Original file line number Diff line number Diff line change
@@ -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 <BalanceBanner onSelectTab={handleSelectTab} />;
}
42 changes: 33 additions & 9 deletions src/app/components/SwapWrapper.tsx
Original file line number Diff line number Diff line change
@@ -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<TSwapState>(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 (
<>
{/* <Swap /> */}
<NewSwap />
</>
<div id={SWAP_WIDGET_ANCHOR_ID}>
<NewSwap swapState={swapState} setSwapState={setSwapState} />
</div>
);
}
4 changes: 4 additions & 0 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -19,6 +20,8 @@ import { generateMetadata } from "@/lib/site-metadata";

export default function Home() {
return (
<>
<HomeBanner />
<div className={clsx(WRAPPER_CLASSES, "")}>
<div className="md:grid md:grid-cols-2 md:gap-x-6 lg:gap-x-[9.5rem]">
<Heading1 className="text-[2.5rem] text-primary-orange mb-4 md:max-w-[32rem] md:flex md:flex-col md:leading-[0.9] md:text-[3.5rem] lg:text-[4rem]">
Expand Down Expand Up @@ -78,5 +81,6 @@ export default function Home() {
</div>
<FAQWrapper />
</div>
</>
);
}
Loading