Skip to content
Open
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
9 changes: 9 additions & 0 deletions src/ProtectedLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { isDev } from "@/utils/utils";
import { Navigate, Route, Routes } from "react-router-dom";
import DevPage from "./components/DevPage";
import PageMetaWrapper from "./components/PageMetaWrapper";
import Beanstalk from "./pages/Beanstalk";
import Collection from "./pages/Collection";
import Error404 from "./pages/Error404";
import Explorer from "./pages/Explorer";
Expand Down Expand Up @@ -69,6 +70,14 @@ export default function ProtectedLayout() {
</PageMetaWrapper>
}
/>
<Route
path="/beanstalk"
element={
<PageMetaWrapper metaKey="beanstalk">
<Beanstalk />
</PageMetaWrapper>
}
/>
<Route
path="/market/pods"
element={
Expand Down
64 changes: 64 additions & 0 deletions src/components/BeanstalkStatField.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import TextSkeleton from "@/components/TextSkeleton";
import { Button } from "@/components/ui/Button";
import { ReactNode } from "react";

interface BeanstalkStatFieldAction {
label: string;
onClick?: () => void;
disabled?: boolean;
}

interface BeanstalkStatFieldProps {
title: string;
value: ReactNode;
isLoading?: boolean;
disabled?: boolean;
actions?: BeanstalkStatFieldAction[];
children?: ReactNode;
}

/**
* Reusable stat field component with title, value, and optional action buttons
* Used in Beanstalk obligations and global stats cards
*/
const BeanstalkStatField: React.FC<BeanstalkStatFieldProps> = ({
title,
value,
isLoading = false,
disabled = false,
actions,
children,
}) => {
return (
<div className={`flex flex-col gap-1 ${disabled ? "opacity-60" : ""}`}>
<div className="flex items-center justify-between">
<div className="pinto-sm text-pinto-light">{title}</div>
{actions && actions.length > 0 && (
<div className="flex items-center gap-3">
{actions.map((action) => (
<Button
key={action.label}
variant="hoverTextPrimary"
size="sm"
noPadding
onClick={action.onClick}
disabled={disabled || action.disabled}
>
{action.label}
</Button>
))}
</div>
)}
</div>
{children ? (
children
) : (
<TextSkeleton loading={isLoading} height="body" className="w-24">
<div className="pinto-body-light">{disabled ? <span className="text-pinto-light">N/A</span> : value}</div>
</TextSkeleton>
)}
</div>
);
};

export default BeanstalkStatField;
2 changes: 1 addition & 1 deletion src/components/Tractor/form/SowOrderV0Fields.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -496,7 +496,7 @@ SowOrderV0Fields.MorningAuction = function MorningAuction() {

// TODO: ADD REFERRAL CODE VALIDATOR!

const BONUS_MULTIPLIER = 0.1;
const BONUS_MULTIPLIER = 0.05;

SowOrderV0Fields.PodDisplay = function PodDisplay({
onOpenReferralPopover,
Expand Down
1 change: 1 addition & 0 deletions src/components/nav/nav/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,7 @@ export const navLinks = {
field: "/field",
swap: "/swap",
referral: "/referral",
beanstalk: "/beanstalk",
sPinto: "/sPinto",
collection: "/collection",
podmarket: "/market/pods",
Expand Down
3 changes: 3 additions & 0 deletions src/components/nav/nav/Navi.desktop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,9 @@ const AppNavi = () => {
Referral
</Link>
</NavigationMenuItem>
<NavigationMenuItem>
<Link href={navLinks.beanstalk}>Beanstalk</Link>
</NavigationMenuItem>
<NavigationMenuItem>
<Link href={navLinks.sPinto} onClick={trackClick(ANALYTICS_EVENTS.NAVIGATION.MAIN_SPINTO_CLICK)}>
sPinto
Expand Down
3 changes: 3 additions & 0 deletions src/components/nav/nav/Navi.mobile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ function MobileNavContent({ learnOpen, setLearnOpen, unmount, close }: IMobileNa
>
Referral
</MobileNavLink>
<MobileNavLink href={navLinks.beanstalk} onClick={unmountAndClose}>
Beanstalk
</MobileNavLink>
<MobileNavLink
href={navLinks.explorer}
onClick={unmountAndClose}
Expand Down
3 changes: 2 additions & 1 deletion src/components/ui/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ const buttonVariants = cva(
link: "!text-sm !font-normal text-pinto-gray-4 underline-offset-4 hover:text-pinto-green-4 hover:underline",
"silo-action":
"hover:bg-pinto-green-1 h-[2.5rem] rounded-[1rem] font-[400] text-[1.25rem] text-pinto-gray-5 flex flex-row justify-start gap-[0.625rem] disabled:bg-transparent disabled:text-pinto-gray-5 disabled:opacity-30",
hoverTextPrimary: "text-sm font-light text-pinto-green-4 hover:text-pinto-green-5 hover:underline",
hoverTextPrimary:
"text-sm font-light text-pinto-green-4 hover:text-pinto-green-5 hover:underline disabled:bg-transparent disabled:text-pinto-gray-4",
morning: "bg-pinto-morning-orange text-pinto-morning",
gradient:
"bg-gradient-primary hover:bg-gradient-primary-hover text-white disabled:text-white disabled:opacity-60",
Expand Down
16 changes: 16 additions & 0 deletions src/constants/internalTokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,19 @@ export const PODS: InternalToken = {
displayDecimals: 2,
logoURI: podLogo,
};

export const URBDV: InternalToken = {
name: "Unripe BDV",
symbol: "urBDV",
decimals: 6,
displayDecimals: 2,
logoURI: "", // TODO: Add logo if needed
};

export const SPROUTS: InternalToken = {
name: "Sprouts",
symbol: "SPROUTS",
decimals: 6,
displayDecimals: 2,
logoURI: "", // TODO: Add logo if needed
};
6 changes: 6 additions & 0 deletions src/constants/meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const slugs = [
"sPinto",
"tractor",
"referral",
"beanstalk",
] as const;

const nestedSlugs = ["PINTOUSDC", "PINTOcbBTC", "PINTOWSOL", "PINTOWETH", "PINTOcbETH", "PINTO"] as const;
Expand Down Expand Up @@ -139,6 +140,11 @@ const PINTO_META: Record<MetaSlug, MetaProps> = {
description: "Share Pinto and earn rewards through referrals.",
url: "https://pinto.money/referral",
},
beanstalk: {
title: "Beanstalk Obligations | Pinto",
description: "View your legacy Beanstalk holder obligations including Silo Payback, Pods, and Fertilizer.",
url: "https://pinto.money/beanstalk",
},
};

export default PINTO_META;
54 changes: 54 additions & 0 deletions src/pages/Beanstalk.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import ReadMoreAccordion from "@/components/ReadMoreAccordion";
import { Card } from "@/components/ui/Card";
import PageContainer from "@/components/ui/PageContainer";
import { Separator } from "@/components/ui/Separator";
import { Link } from "react-router-dom";
import BeanstalkGlobalCard from "./beanstalk/components/BeanstalkGlobalCard";
import BeanstalkObligationsCard from "./beanstalk/components/BeanstalkObligationsCard";

const Beanstalk = () => {
return (
<PageContainer variant="lg">
<div className="flex flex-col w-full mt-4 sm:mt-0">
<div className="flex flex-col self-center w-full gap-4 mb-20 sm:mb-0 sm:gap-8">
{/* Hero Section */}
<div className="flex flex-col gap-y-3">
<div className="pinto-h2 sm:pinto-h1">Beanstalk Obligations</div>
<div className="pinto-sm sm:pinto-body-light text-pinto-light sm:text-pinto-light">
Beanstalk Debt issued by Pinto.
<ReadMoreAccordion inline>
Beanstalk participants at the time of Pinto launch were issued assets based on their holdings. When
Pinto exceeds 1 Billion in supply, 3% of mints go towards these between Beanstalk Silo Tokens, Pods, and
Fertilizer.{" "}
<Link
to="https://docs.pinto.money/appendix/old-beanstalk-holders"
target="_blank"
rel="noopener noreferrer"
className="text-pinto-green-4 hover:underline"
>
Learn more
</Link>
</ReadMoreAccordion>
</div>
</div>
<Separator />

{/* Main Cards - Two Column Layout */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4 sm:gap-6 sm:items-stretch">
{/* Left Panel - Obligations Card */}
<Card className="p-4 sm:p-6 h-full">
<BeanstalkObligationsCard />
</Card>

{/* Right Panel - Global Stats Card */}
<Card className="p-4 sm:p-6 h-full">
<BeanstalkGlobalCard />
</Card>
</div>
</div>
</div>
</PageContainer>
);
};

export default Beanstalk;
39 changes: 39 additions & 0 deletions src/pages/beanstalk/components/BeanstalkFertilizerSection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { TokenValue } from "@/classes/TokenValue";
import BeanstalkStatField from "@/components/BeanstalkStatField";
import { formatter } from "@/utils/format";

interface BeanstalkFertilizerSectionProps {
balance: TokenValue;
isLoading: boolean;
disabled?: boolean;
onRinse?: () => void;
onSend?: () => void;
}

/**
* Section component displaying fertilizer balance
*/
const BeanstalkFertilizerSection: React.FC<BeanstalkFertilizerSectionProps> = ({
balance,
isLoading,
disabled = false,
onRinse,
onSend,
}) => {
const hasBalance = !balance.isZero;

return (
<BeanstalkStatField
title="My Beanstalk Fertilizer"
value={formatter.number(balance, { minDecimals: 2, maxDecimals: 2 })}
isLoading={isLoading}
disabled={disabled}
actions={[
{ label: "Rinse", onClick: onRinse, disabled: !hasBalance },
{ label: "Send", onClick: onSend },
]}
/>
);
};

export default BeanstalkFertilizerSection;
66 changes: 66 additions & 0 deletions src/pages/beanstalk/components/BeanstalkGlobalCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import BeanstalkStatField from "@/components/BeanstalkStatField";
import { Button } from "@/components/ui/Button";
import { useBeanstalkGlobalStats } from "@/state/useBeanstalkGlobalStats";
import { formatter } from "@/utils/format";

/**
* Component displaying global Beanstalk repayment statistics
* Shows total urBDV distributed, total pods in repayment field,
* total unfertilized sprouts, and total Pinto paid out
* Shows N/A values when data cannot be loaded
*/
const BeanstalkGlobalCard: React.FC = () => {
const {
totalUrBdvDistributed,
totalPodsInRepaymentField,
totalUnfertilizedSprouts,
totalPintoPaidOut,
isLoading,
isError,
refetch,
} = useBeanstalkGlobalStats();

const formatValue = (value: typeof totalUrBdvDistributed) => {
return formatter.number(value, { minDecimals: 2, maxDecimals: 2 });
};

return (
<div className={`flex flex-col ${isError ? "opacity-60" : ""}`}>
{isError && (
<div className="flex justify-end mb-4">
<Button variant="outline" size="sm" rounded="full" onClick={() => refetch()}>
Retry
</Button>
</div>
)}
<div className="flex flex-col gap-8">
<BeanstalkStatField
title="Total Beanstalk Repayment Silo Tokens"
value={formatValue(totalUrBdvDistributed)}
isLoading={isLoading}
disabled={isError}
/>
<BeanstalkStatField
title="Total Beanstalk Repayment Pods"
value={formatValue(totalPodsInRepaymentField)}
isLoading={isLoading}
disabled={isError}
/>
<BeanstalkStatField
title="Total Beanstalk Repayment Sprouts"
value={formatValue(totalUnfertilizedSprouts)}
isLoading={isLoading}
disabled={isError}
/>
<BeanstalkStatField
title="Pintos issued to Beanstalk Holders"
value={formatValue(totalPintoPaidOut)}
isLoading={isLoading}
disabled={isError}
/>
</div>
</div>
);
};

export default BeanstalkGlobalCard;
47 changes: 47 additions & 0 deletions src/pages/beanstalk/components/BeanstalkObligationsCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Button } from "@/components/ui/Button";
import { useFarmerBeanstalkRepayment } from "@/state/useFarmerBeanstalkRepayment";
import { useAccount } from "wagmi";
import BeanstalkFertilizerSection from "./BeanstalkFertilizerSection";
import BeanstalkPodsSection from "./BeanstalkPodsSection";
import BeanstalkSiloSection from "./BeanstalkSiloSection";

/**
* Container component for displaying user's Beanstalk obligations
* Shows Silo Payback (urBDV), Pods from repayment field, and Fertilizer data
* Shows N/A values when wallet is not connected or data cannot be loaded
*/
const BeanstalkObligationsCard: React.FC = () => {
const account = useAccount();
const { silo, pods, fertilizer, isLoading, isError, refetch } = useFarmerBeanstalkRepayment();

const isConnected = !!account.address;
const showDisabled = !isConnected || isError;

return (
<div className="flex flex-col h-full">
{isConnected && isError && (
<div className="flex justify-end mb-4">
<Button variant="outline" size="sm" rounded="full" onClick={() => refetch()}>
Retry
</Button>
</div>
)}
<div className="flex flex-col gap-8">
<BeanstalkSiloSection balance={silo.balance} isLoading={isConnected && isLoading} disabled={showDisabled} />
<BeanstalkPodsSection
plots={pods.plots}
totalPods={pods.totalPods}
isLoading={isConnected && isLoading}
disabled={showDisabled}
/>
<BeanstalkFertilizerSection
balance={fertilizer.balance}
isLoading={isConnected && isLoading}
disabled={showDisabled}
/>
</div>
</div>
);
};

export default BeanstalkObligationsCard;
Loading