Skip to content

Commit

Permalink
Setup global tx modal component
Browse files Browse the repository at this point in the history
  • Loading branch information
jmrossy committed Dec 27, 2023
1 parent 0d16d9e commit 388611e
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 35 deletions.
6 changes: 4 additions & 2 deletions src/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import { ErrorBoundary } from 'src/components/errors/ErrorBoundary';
import { Footer } from 'src/components/nav/Footer';
import { Header } from 'src/components/nav/Header';
import { WagmiContext } from 'src/config/wagmi';
import { TransactionModal } from 'src/features/transactions/TransactionModal';
import { useIsSsr } from 'src/utils/ssr';
import 'src/vendor/inpage-metamask';
import 'src/vendor/polyfill';

function SafeHydrate({ children }: PropsWithChildren<any>) {
// Disable app SSR for now as it's not needed and
// complicates wallet integrations
// Avoid SSR for now as it's not needed and it
// complicates wallet integrations and media query hooks
const isSsr = useIsSsr();
if (isSsr) {
return <div></div>;
Expand All @@ -32,6 +33,7 @@ export function App({ children }: PropsWithChildren<any>) {
<QueryClientProvider client={queryClient}>
<WagmiContext>
<BodyLayout>{children}</BodyLayout>
<TransactionModal />
<ToastContainer transition={Zoom} position={toast.POSITION.BOTTOM_RIGHT} />
</WagmiContext>
</QueryClientProvider>
Expand Down
6 changes: 5 additions & 1 deletion src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { Section } from 'src/components/layout/Section';
import { Modal, useModal } from 'src/components/menus/Modal';
import { Amount } from 'src/components/numbers/Amount';
import { StakeForm } from 'src/features/staking/StakeForm';
import { useTransactionModal } from 'src/features/transactions/TransactionModal';
import { TxModalType } from 'src/features/transactions/types';
import { ValidatorGroupTable } from 'src/features/validators/ValidatorGroupTable';
import { ValidatorGroup, ValidatorStatus } from 'src/features/validators/types';
import { useValidatorGroups } from 'src/features/validators/useValidatorGroups';
Expand Down Expand Up @@ -44,13 +46,15 @@ function HeroSection({ totalVotes, groups }: { totalVotes?: bigint; groups?: Val
return min;
}, [groups]);

const showStakeModal = useTransactionModal(TxModalType.Stake);

return (
<Section className="bg-purple-500 text-white" containerClassName="all:px-0">
<div className="my-10 flex items-center justify-between gap-20 lg:gap-x-40 xl:gap-x-80">
<div className="flex w-80 flex-col space-y-6">
<h1 className="font-serif text-4xl">Discover Validators</h1>
<p>Stake your CELO with validators to start earning rewards immediately.</p>
<SolidButton>{`Stake and earn 4%`}</SolidButton>
<SolidButton onClick={showStakeModal}>{`Stake and earn 4%`}</SolidButton>
</div>
<div className="hidden grid-cols-2 grid-rows-2 gap-10 border border-white/20 p-6 md:grid">
<HeroStat label="Staking APY" text="6%" />
Expand Down
8 changes: 4 additions & 4 deletions src/components/buttons/OutlineButton.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import clsx from 'clsx';
import { ButtonHTMLAttributes, PropsWithChildren } from 'react';

export function OutlineButton({
Expand All @@ -6,11 +7,10 @@ export function OutlineButton({
...props
}: PropsWithChildren<ButtonHTMLAttributes<HTMLButtonElement>>) {
return (
<button
className={`btn btn-outline h-fit min-h-fit rounded-full border-taupe-300 px-4 py-2.5 font-semibold text-black hover:border-taupe-400 hover:bg-black/5 hover:text-black ${className}`}
{...props}
>
<button className={clsx(OutlineButtonClassName, className)} {...props}>
{children}
</button>
);
}

export const OutlineButtonClassName = `btn btn-outline outline-none h-fit min-h-fit rounded-full border-taupe-300 px-4 py-2.5 font-semibold text-black hover:border-taupe-400 hover:bg-black/5 hover:text-black`;
9 changes: 2 additions & 7 deletions src/components/menus/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Menu, Popover, Transition } from '@headlessui/react';
import { Fragment, ReactElement, ReactNode } from 'react';
import { OutlineButton } from 'src/components/buttons/OutlineButton';

interface MenuProps {
button: ReactNode;
Expand All @@ -13,9 +12,7 @@ interface MenuProps {
export function DropdownMenu({ button, buttonClasses, menuItems, menuClasses }: MenuProps) {
return (
<Menu as="div" className="relative">
<Menu.Button as={OutlineButton} className={buttonClasses}>
{button}
</Menu.Button>
<Menu.Button className={buttonClasses}>{button}</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
Expand Down Expand Up @@ -50,9 +47,7 @@ export function DropdownModal({ button, buttonClasses, modal, modalClasses }: Mo
<Popover className="relative">
{({ open }) => (
<>
<Popover.Button as={OutlineButton} className={buttonClasses}>
{button({ open })}
</Popover.Button>
<Popover.Button className={buttonClasses}>{button({ open })}</Popover.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-200"
Expand Down
31 changes: 13 additions & 18 deletions src/features/store.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { TxModalType } from 'src/features/transactions/types';
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

Expand All @@ -7,35 +8,29 @@ const PERSIST_STATE_VERSION = 0;
// Keeping everything here for now as state is simple
// Will refactor into slices as necessary
export interface AppState {
transactions: any[];
addTransactions: (t: any) => void;
resetTransactions: () => void;
failUnconfirmedTransactions: () => void;
activeModal: {
type: TxModalType | null;
props?: object;
};
setTransactionModal: (args: { type: TxModalType; props?: object }) => void;
}

// TODO is a store needed?
export const useStore = create<AppState>()(
persist(
(set) => ({
transactions: [],
addTransactions: (t: any) => {
set((state) => ({ transactions: [...state.transactions, t] }));
activeModal: {
type: null,
props: {},
},
resetTransactions: () => {
set(() => ({ transactions: [] }));
},
failUnconfirmedTransactions: () => {
//TODO
set((state) => state);
setTransactionModal: (args: { type: TxModalType; props?: object }) => {
set(() => ({ activeModal: args }));
},
}),
{
name: 'app-state',
partialize: (state) => ({ transactions: state.transactions }),
name: 'celo-station-state',
partialize: (_state) => ({}),
version: PERSIST_STATE_VERSION,
onRehydrateStorage: () => (state) => {
state?.failUnconfirmedTransactions();
},
},
),
);
46 changes: 46 additions & 0 deletions src/features/transactions/TransactionModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { useCallback, useEffect } from 'react';
import { Modal, useModal } from 'src/components/menus/Modal';
import { StakeForm } from 'src/features/staking/StakeForm';
import { useStore } from 'src/features/store';
import { TxModalType } from 'src/features/transactions/types';

const TypeToComponent: Record<TxModalType, React.FC<any>> = {
[TxModalType.Lock]: PlaceholderContent,
[TxModalType.Unlock]: PlaceholderContent,
[TxModalType.Withdraw]: PlaceholderContent,
[TxModalType.Stake]: StakeForm,
[TxModalType.Unstake]: PlaceholderContent,
[TxModalType.Vote]: PlaceholderContent,
[TxModalType.Unvote]: PlaceholderContent,
[TxModalType.Delegate]: PlaceholderContent,
[TxModalType.Undelegate]: PlaceholderContent,
};

export function useTransactionModal(type: TxModalType, props?: any) {
const setTxModal = useStore((state) => state.setTransactionModal);
return useCallback(() => setTxModal({ type, props }), [setTxModal, type, props]);
}

export function TransactionModal() {
const { isModalOpen, closeModal, openModal } = useModal();

const activeModal = useStore((state) => state.activeModal);
const { type, props } = activeModal;

const Component = type ? TypeToComponent[type] : PlaceholderContent;

useEffect(() => {
if (!openModal || !activeModal?.type) return;
openModal();
}, [activeModal, openModal]);

return (
<Modal isOpen={isModalOpen} close={closeModal}>
<Component {...props} />
</Modal>
);
}

function PlaceholderContent() {
return <div className="flex items-center justify-center px-4 py-6">...</div>;
}
11 changes: 11 additions & 0 deletions src/features/transactions/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export enum TxModalType {
Lock = 'lock',
Unlock = 'unlock',
Withdraw = 'withdraw',
Stake = 'stake',
Unstake = 'unstake',
Vote = 'vote',
Unvote = 'unvote',
Delegate = 'delegate',
Undelegate = 'undelegate',
}
6 changes: 3 additions & 3 deletions src/features/wallet/WalletDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useConnectModal } from '@rainbow-me/rainbowkit';
import Link from 'next/link';
import { OutlineButton } from 'src/components/buttons/OutlineButton';
import { OutlineButton, OutlineButtonClassName } from 'src/components/buttons/OutlineButton';
import { SolidButton } from 'src/components/buttons/SolidButton';
import { Identicon } from 'src/components/icons/Identicon';
import { DropdownModal } from 'src/components/menus/Dropdown';
Expand All @@ -27,7 +27,7 @@ export function WalletDropdown() {
<div className="text-sm">{shortenAddress(address, true)}</div>
</div>
)}
buttonClasses="pl-1.5 pr-3 all:py-1"
buttonClasses={`${OutlineButtonClassName} pl-1.5 pr-3 all:py-1`}
modal={({ close }) => (
<DropdownContent address={address} disconnect={disconnect} close={close} />
)}
Expand Down Expand Up @@ -58,7 +58,7 @@ function DropdownContent({

const totalBalance = (walletBalance?.value || 0n) + (lockedBalance?.value || 0n);

const onClickCopy = useCopyHandler(address, close);
const onClickCopy = useCopyHandler(address);

return (
<div className="flex min-w-[18rem] flex-col items-center space-y-3">
Expand Down

0 comments on commit 388611e

Please sign in to comment.