Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Operator Billing Subscription #211

Open
wants to merge 11 commits into
base: dev
Choose a base branch
from
148 changes: 148 additions & 0 deletions app/billing/Home.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import NavMenu from "@/components/NavMenu";
import Button from "@/components/ui/Button";
import { selectOperatorSlice, setIsBillingModalOpen, setIsPayModalOpen } from "@/store/operatorSlice";
import { useAppDispatch, useAppSelector } from "@/store/store";

const planDetails = [
{
title: "Current plan",
value: "Starter"
},
{
title: "API calls",
value: "10K"
},
{
title: "Transactions",
value: "1000"
},
{
title: "Webhook calls",
value: "10K"
}
]

const paymentDetails = [
{
title: "Next invoice issue date",
value: "-"
},
{
title: "Prepaid months remaining",
value: "-"
}
]

const invoices = [
{
date: "Aug 24, 2024",
months: 1,
total: "$100",
status: "Paid"
},
{
date: "Sep 24, 2024",
months: 3,
total: "$300",
status: "Paid"
}
];

const Home = () => {
const dispatch = useAppDispatch();
const operatorSlice = useAppSelector(selectOperatorSlice);

return (
<div className="w-full bg-light-gray flex flex-col items-center">
<div className="w-8/9 flex flex-col mt-[30.84px] mb-[104.95px] md:mt-12 md:w-9/10 max-w-7xl">
<NavMenu menuItems={operatorSlice.menuItems} isOpen={true} selected="billing & plan" className="md:flex md:justify-center" />

<section className="mt-20 flex flex-col gap-10 max-w-[915px]">
<div className="flex justify-between items-center gap-4 md:flex-col md:items-start">
<h1 className="text-5xl md:text-[32px] text-fuse-black font-semibold leading-none md:leading-tight md:text-center">
Billing & Plan
</h1>
<Button
text="Enter Billing Info"
className="transition ease-in-out bg-success text-lg leading-none text-black font-semibold rounded-full hover:bg-black hover:text-white"
padding="py-3 px-9"
onClick={() => dispatch(setIsBillingModalOpen(true))}
/>
</div>
<div className="flex justify-between items-center gap-4 max-w-[824px] md:flex-col md:items-start">
{planDetails.map((detail, index) => (
<div key={index} className="flex flex-col gap-2">
<p className="text-text-dark-gray">
{detail.title}
</p>
<p className="text-2xl text-fuse-black font-semibold leading-none">
{detail.value}
</p>
</div>
))}
</div>
</section>

<section className="mt-40 flex flex-col gap-10 max-w-[688px] md:mt-20">
<h2 className="text-2xl text-fuse-black font-bold underline underline-offset-4">
Payment
</h2>
<div className="flex justify-between items-center gap-4 md:flex-col md:items-start">
<Button
text="Upgrade"
className="transition ease-in-out text-lg leading-none text-white font-semibold bg-black hover:text-black hover:bg-white rounded-full"
padding="py-3 px-11"
onClick={() => dispatch(setIsPayModalOpen(true))}
/>
{paymentDetails.map((detail, index) => (
<div key={index} className="flex flex-col gap-2">
<p className="text-text-dark-gray">
{detail.title}
</p>
<p className="text-2xl text-fuse-black font-semibold leading-none">
{detail.value}
</p>
</div>
))}
</div>
</section>

<section className="mt-40 flex flex-col gap-10 max-w-[930px] md:mt-20">
<h2 className="text-2xl text-fuse-black font-bold underline underline-offset-4">
Invoices
</h2>
<table className="w-full">
<thead>
<tr>
<th className="text-left">Date</th>
<th className="text-left">Months</th>
<th className="text-left">
<p className="block md:hidden">Invoice Total</p>
<p className="hidden md:block">Total</p>
</th>
<th className="text-left">Status</th>
</tr>
</thead>
<tbody className="text-text-dark-gray">
{invoices.map((invoice, index) => (
<tr key={index}>
<td className="py-4">{invoice.date}</td>
<td className="py-4">{invoice.months}</td>
<td className="py-4">{invoice.total}</td>
<td className="pt-10 pb-4">
<p>{invoice.status}</p>
<p className="text-fuse-black font-medium underline underline-offset-4">
View Invoice
</p>
</td>
</tr>
))}
</tbody>
</table>
</section>
</div>
</div>
);
};

export default Home;
14 changes: 14 additions & 0 deletions app/billing/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { Metadata } from 'next'

export const metadata: Metadata = {
title: 'Billing & Plan - Fuse Console',
description: 'Upgrade your billing & plan and view your invoices',
}

export default function BillingLayout({
children,
}: {
children: React.ReactNode
}) {
return <section>{children}</section>
}
46 changes: 46 additions & 0 deletions app/billing/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"use client";

import { useEffect } from "react";
import { useRouter } from "next/navigation";
import Home from "./Home";

import { useAppDispatch, useAppSelector } from "@/store/store";
import { setSelectedNavbar } from "@/store/navbarSlice";
import Footer from "@/components/Footer";
import Topbar from "@/components/Topbar";
import ChainModal from "@/components/ChainModal";
import PayModal from "@/components/billing/PayModal";
import BillingModal from "@/components/billing/BillingModal";
import { selectOperatorSlice } from "@/store/operatorSlice";
import { path } from "@/lib/helpers";

const Billing = () => {
const dispatch = useAppDispatch();
const operatorSlice = useAppSelector(selectOperatorSlice);
const router = useRouter();

useEffect(() => {
dispatch(setSelectedNavbar("billing"));
}, [dispatch])

useEffect(() => {
if(!operatorSlice.isAuthenticated && operatorSlice.isHydrated) {
router.push(path.DASHBOARD);
}
}, [dispatch, operatorSlice.isAuthenticated, operatorSlice.isHydrated, router])

return (
<div className="w-full font-mona justify-end min-h-screen">
<div className="flex-col flex items-center bg-light-gray h-screen">
<ChainModal description="To work with the Operator account you must be connected to the Fuse Network" />
<PayModal />
<BillingModal />
<Topbar />
<Home />
<Footer />
</div>
</div>
);
};

export default Billing;
6 changes: 4 additions & 2 deletions app/build/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import theGraph from "@/public/the-graph.png"
import taskOn from "@/public/taskon.png"
import { useRouter } from "next/navigation";
import NavMenu from "@/components/NavMenu";
import { buildSubMenuItems } from "@/lib/helpers";
import * as amplitude from "@amplitude/analytics-browser";
import { useAppSelector } from "@/store/store";
import { selectOperatorSlice } from "@/store/operatorSlice";

const apps = [
{
Expand Down Expand Up @@ -54,6 +55,7 @@ const apps = [

const Home = () => {
const router = useRouter();
const operatorSlice = useAppSelector(selectOperatorSlice);

function createAccount(eventInput: string) {
amplitude.track(eventInput);
Expand All @@ -64,7 +66,7 @@ const Home = () => {
<div className="w-full bg-light-gray">
<div className="w-full flex flex-col items-center">
<div className="w-8/9 flex flex-col mt-[30.84px] md:mt-12 md:w-9/10 max-w-7xl">
<NavMenu menuItems={buildSubMenuItems} isOpen={true} selected="welcome" className="md:flex md:justify-center" liClassName="w-28" />
<NavMenu menuItems={operatorSlice.menuItems} isOpen={true} selected="welcome" className="md:flex md:justify-center" />
</div>
</div>
<div className="w-full flex flex-col items-center">
Expand Down
55 changes: 30 additions & 25 deletions app/dashboard/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { useEffect, useMemo, useState } from "react";
import Button from "@/components/ui/Button";
import { buildSubMenuItems, evmDecimals, signDataMessage } from "@/lib/helpers";
import { evmDecimals, signDataMessage } from "@/lib/helpers";
import { useAppDispatch, useAppSelector } from "@/store/store";
import { BalanceStateType, fetchUsdPrice, selectBalanceSlice } from "@/store/balanceSlice";
import { useAccount, useBalance, useBlockNumber, useSignMessage } from "wagmi";
import { fuse } from "wagmi/chains";
import { checkIsActivated, fetchSponsorIdBalance, fetchSponsoredTransactions, generateSecretApiKey, selectOperatorSlice, setIsContactDetailsModalOpen, setIsRollSecretKeyModalOpen, setIsTopupAccountModalOpen, setIsWithdrawModalOpen, validateOperator, withRefreshToken } from "@/store/operatorSlice";
import { OperatorStateType, checkIsActivated, fetchSponsorIdBalance, fetchSponsoredTransactions, fetchTokenBalances, generateSecretApiKey, selectOperatorSlice, setIsContactDetailsModalOpen, setIsRollSecretKeyModalOpen, setIsTopupAccountModalOpen, setIsWithdrawModalOpen, validateOperator, withRefreshToken } from "@/store/operatorSlice";
import TopupAccountModal from "@/components/dashboard/TopupAccountModal";
import Image from "next/image";
import copy from "@/assets/copy-black.svg";
Expand All @@ -31,7 +31,7 @@ import hide from "@/assets/hide.svg";
import { formatUnits } from "viem";
import { SignMessageVariables } from "wagmi/query";
import contactSupport from "@/assets/contact-support.svg";

import { useRouter } from "next/navigation";
type CreateOperatorWalletProps = {
isValidated: boolean;
signMessage: (variables: SignMessageVariables) => void;
Expand All @@ -48,7 +48,7 @@ type OperatorAccountBalanceProps = {
chain: any;
balanceSlice: BalanceStateType;
balance: any;
isActivated: boolean;
operatorSlice: OperatorStateType;
dispatch: ThunkDispatch<any, undefined, AnyAction> & Dispatch<AnyAction>;
}

Expand Down Expand Up @@ -125,12 +125,12 @@ const ConnectEoaWallet = () => {
)
}

const OperatorAccountBalance = ({ chain, balanceSlice, balance, isActivated, dispatch }: OperatorAccountBalanceProps) => {
const OperatorAccountBalance = ({ chain, balanceSlice, balance, operatorSlice, dispatch }: OperatorAccountBalanceProps) => {
useEffect(() => {
const fiveSecondInMillisecond = 5000;

const intervalId = setInterval(() => {
if (isActivated) {
if (operatorSlice.isActivated) {
dispatch(withRefreshToken(() => dispatch(fetchSponsoredTransactions())));
} else {
dispatch(withRefreshToken(() => dispatch(checkIsActivated())));
Expand All @@ -140,7 +140,13 @@ const OperatorAccountBalance = ({ chain, balanceSlice, balance, isActivated, dis
return () => {
clearInterval(intervalId);
}
}, [dispatch, isActivated])
}, [dispatch, operatorSlice.isActivated])

useEffect(() => {
if (operatorSlice.operator.user.smartWalletAddress) {
dispatch(fetchTokenBalances({ address: operatorSlice.operator.user.smartWalletAddress }));
}
}, [dispatch, operatorSlice.operator.user.smartWalletAddress])

return (
<div className="flex flex-col justify-between items-start">
Expand All @@ -160,24 +166,15 @@ const OperatorAccountBalance = ({ chain, balanceSlice, balance, isActivated, dis
</div>
</div>
<div className="flex items-end md:flex-wrap gap-x-[30px] md:gap-x-4">
<h1 className="font-bold text-5xl leading-none whitespace-nowrap">
{(chain && chain.id === fuse.id) ?
new Intl.NumberFormat().format(
parseFloat(formatUnits(balance?.value ?? BigInt(0), balance?.decimals ?? evmDecimals) ?? "0")
) :
0
} FUSE
</h1>
{balanceSlice.isUsdPriceLoading ?
<span className="px-10 py-2 ml-2 rounded-md animate-pulse bg-white/80"></span> :
<p className="text-[20px]/7 font-medium">
{operatorSlice.isFetchingTokenBalances || balanceSlice.isUsdPriceLoading ?
<span className="w-20 h-10 rounded-md animate-pulse bg-white/80"></span> :
<h1 className="font-bold text-5xl leading-none whitespace-nowrap">
${(chain && chain.id === fuse.id) ?
new Intl.NumberFormat().format(
parseFloat((parseFloat(formatUnits(balance?.value ?? BigInt(0), balance?.decimals ?? evmDecimals) ?? "0.00") * balanceSlice.price).toString())
(parseFloat(balance?.formatted ?? "0") * balanceSlice.price) + operatorSlice.totalTokenBalance
) :
"0.00"
}
</p>
"0.00"}
</h1>
}
</div>
</div>
Expand Down Expand Up @@ -209,6 +206,7 @@ const Home = () => {
const operatorSlice = useAppSelector(selectOperatorSlice);
const [showSecretKey, setShowSecretKey] = useState(false);
const controller = useMemo(() => new AbortController(), []);
const router = useRouter();
const { isConnected, address, chain } = useAccount();
const signer = useEthersSigner();
const { data: blockNumber } = useBlockNumber({ watch: true });
Expand Down Expand Up @@ -292,7 +290,7 @@ const Home = () => {
{operatorSlice.isAccountCreationModalOpen && <AccountCreationModal />}
{operatorSlice.isCongratulationModalOpen && <CongratulationModal />}
<div className="w-8/9 flex flex-col mt-[30.84px] mb-[104.95px] md:mt-12 md:w-9/10 max-w-7xl">
<NavMenu menuItems={buildSubMenuItems} isOpen={true} selected="dashboard" className="md:flex md:justify-center" liClassName="w-28" />
<NavMenu menuItems={operatorSlice.menuItems} isOpen={true} selected="dashboard" className="md:flex md:justify-center" />
<div className={`flex justify-between md:flex-col gap-2 mt-[66.29px] md:mt-14 ${operatorSlice.isActivated ? "mb-[70px]" : "mb-[42px]"} md:mb-[50px]`}>
<h1 className="text-5xl md:text-[32px] text-fuse-black font-semibold leading-none md:leading-tight md:text-center">
Operator Dashboard
Expand Down Expand Up @@ -342,15 +340,22 @@ const Home = () => {
</div>
}
<div className="flex flex-col gap-y-[30px] md:gap-y-[21px] mb-[143.32px] md:mb-[66px]">
<div className="flex flex-row md:flex-col gap-x-4 gap-y-12 bg-lightest-gray justify-between rounded-[20px] p-12 md:p-8 min-h-[297px]">
<div className="flex relative flex-row md:flex-col gap-x-4 gap-y-12 bg-lightest-gray justify-between rounded-[20px] p-12 md:p-8 min-h-[297px]">
{operatorSlice.isAuthenticated && (
<Button
text="Upgrade now"
className="absolute top-[34px] right-6 md:right-4 sm:top-[75px] sm:w-[152px] w-[162px] h-[43px] py-[14px] px-5 bg-success text-black rounded-full hover:bg-black hover:text-white transition ease-in-out text-base font-bold leading-[15.47px] text-center"
onClick={() => router.push("/billing")}
/>
)}
{(!isConnected || !signer) ?
<ConnectEoaWallet /> :
operatorSlice.isAuthenticated ?
<OperatorAccountBalance
chain={chain}
balanceSlice={balanceSlice}
balance={balance}
isActivated={operatorSlice.isActivated}
operatorSlice={operatorSlice}
dispatch={dispatch}
/> :
operatorSlice.isOperatorExist ?
Expand Down
3 changes: 3 additions & 0 deletions assets/caret-down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/chevron-gray-down.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions assets/search-big.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading