diff --git a/.github/workflows/deploy-contracts.yml b/.github/workflows/deploy-contracts.yml index c59f4dc5..123ab492 100644 --- a/.github/workflows/deploy-contracts.yml +++ b/.github/workflows/deploy-contracts.yml @@ -67,7 +67,6 @@ jobs: needs: test_inputs name: Build and deploy runs-on: ubuntu-latest - if: github.ref != 'refs/heads/master' steps: - name: Checkout uses: actions/checkout@v3 @@ -130,7 +129,7 @@ jobs: NODE_ENV: production - name: Commit .env.production - if: ${{ github.event.inputs.commit_changes }} + if: ${{ github.event.inputs.commit_changes && github.ref != 'refs/heads/master' }} uses: EndBug/add-and-commit@v9 with: message: 'chore: update contract ids' diff --git a/.gitignore b/.gitignore index 3031cda5..6e742899 100644 --- a/.gitignore +++ b/.gitignore @@ -41,7 +41,7 @@ node_modules # testing coverage -cypress +/cypress # next.js .next/ diff --git a/docs/LEGAL_DISCLAIMER.md b/docs/LEGAL_DISCLAIMER.md new file mode 100644 index 00000000..e69de29b diff --git a/packages/app/.gitignore b/packages/app/.gitignore index 1704aa16..ed987507 100644 --- a/packages/app/.gitignore +++ b/packages/app/.gitignore @@ -1,3 +1 @@ .env.test -cypress/videos/ -cypress/screenshots/ diff --git a/packages/app/cypress/e2e/App.cy.ts b/packages/app/cypress/e2e/App.cy.ts index 6f433d14..aa22347d 100644 --- a/packages/app/cypress/e2e/App.cy.ts +++ b/packages/app/cypress/e2e/App.cy.ts @@ -1,5 +1,5 @@ -describe('App flow', () => { - it('should execute whole app flow', () => { +describe('End-to-end Test: 😁 Happy Path', () => { + it('should execute whole app basic flow', () => { cy.visit('/'); cy.contains('button', /Launch app/i).click(); @@ -7,7 +7,8 @@ describe('App flow', () => { // create a wallet and fund it cy.contains('button', /Create wallet/i).click(); cy.contains('button', 'Give me ETH').click(); - cy.contains('button', 'Go to Swap').click(); + cy.getByAriaLabel('Accept the use agreement').check(); + cy.contains('button', 'Get Swapping!').click(); cy.contains('Enter amount'); // mint tokens @@ -43,25 +44,25 @@ describe('App flow', () => { if (hasPoolCreated) { // validate add liquidity cy.contains('Enter Ether amount'); - cy.get('[aria-label="Coin From Input"]').type('0.2'); + cy.getByAriaLabel('Coin From Input').type('0.2'); // make sure preview output box shows up - cy.get('[aria-label="preview-add-liquidity-output"]'); + cy.getByAriaLabel('Preview Add Liquidity Output'); // make sure pool price box shows up - cy.get('[aria-label="pool-price"]'); + cy.getByAriaLabel('Pool Price Box'); cy.contains('button', 'Add liquidity').click(); } else { // validate create pool cy.contains('Enter Ether amount'); - cy.get('[aria-label="Coin From Input"]').type('0.2'); - cy.get('[aria-label="Coin To Input"]').type('190'); + cy.getByAriaLabel('Coin From Input').type('0.2'); + cy.getByAriaLabel('Coin To Input').type('190'); // make sure preview output box shows up - cy.get('[aria-label="preview-add-liquidity-output"]'); + cy.getByAriaLabel('Preview Add Liquidity Output'); // make sure pool price box shows up - cy.get('[aria-label="pool-price"]'); + cy.getByAriaLabel('Pool Price Box'); cy.contains('button', 'Create liquidity').click(); } @@ -71,22 +72,23 @@ describe('App flow', () => { // validate swap cy.contains('button', 'Swap').click(); cy.contains('Enter amount'); - cy.get('[aria-label="Coin From Input"]').type('0.1'); + cy.getByAriaLabel('Coin From Input').type('0.1'); // make sure preview output box shows up - cy.get('[aria-label="preview-swap-output"]'); + cy.getByAriaLabel('Preview Swap Output'); - // make sure "swap" button comes from inside swap page only - cy.contains('[aria-label="Swap button"]', 'Swap').click(); + // execute swap operation + cy.getByAriaLabel('Swap button').click(); cy.contains('Swap made successfully!'); // validate remove liquidity cy.contains('button', 'Pool').click(); cy.contains('button', 'Remove liquidity').click(); - cy.get('.coinSelector--maxButton').click(); + cy.getByAriaLabel('Set Maximun Balance').click(); + // // make sure preview output box shows up - cy.get('[aria-label="preview-remove-liquidity-output"]'); + cy.getByAriaLabel('Preview Remove Liquidity Output'); // make sure current positions box shows up - cy.get('[aria-label="pool-current-position"]'); + cy.getByAriaLabel('Pool Current Position'); cy.contains('button', 'Remove liquidity').click(); cy.contains('Liquidity removed successfully!'); }); diff --git a/packages/app/cypress/support/commands.ts b/packages/app/cypress/support/commands.ts index 95857aea..8d19e3d0 100644 --- a/packages/app/cypress/support/commands.ts +++ b/packages/app/cypress/support/commands.ts @@ -35,3 +35,7 @@ // } // } // } + +Cypress.Commands.add('getByAriaLabel', (selector, options) => + cy.get(`[aria-label="${selector}"]`, options) +); diff --git a/packages/app/cypress/support/index.d.ts b/packages/app/cypress/support/index.d.ts new file mode 100644 index 00000000..2438f86b --- /dev/null +++ b/packages/app/cypress/support/index.d.ts @@ -0,0 +1,16 @@ +/// + +declare namespace Cypress { + interface Chainable { + /** + * Get element by aria-label + * @example + * cy.getByAriaLabel('Aria Label'); + * cy.getByAriaLabel('Aria Label', { log: false, timeout: 6000 }); + */ + getByAriaLabel( + ariaLabel: string, + options?: Partial + ): Chainable>; + } +} diff --git a/packages/app/src/styles/components/coin-balance.css b/packages/app/src/styles/components/coin-balance.css new file mode 100644 index 00000000..4cbc6a0e --- /dev/null +++ b/packages/app/src/styles/components/coin-balance.css @@ -0,0 +1,5 @@ +@layer components { + .coinBalance--maxButton { + @apply text-xs py-0 px-1 h-auto bg-primary-800/60 text-primary-500 hover:bg-primary-800; + } +} diff --git a/packages/app/src/styles/components/coin-input.css b/packages/app/src/styles/components/coin-input.css index 49ac8cc8..ae38ee76 100644 --- a/packages/app/src/styles/components/coin-input.css +++ b/packages/app/src/styles/components/coin-input.css @@ -1,6 +1,6 @@ @layer components { .coinInput { - @apply flex bg-gray-700 rounded-2xl p-2 border border-gray-700; + @apply bg-gray-700 rounded-2xl p-2 border border-gray-700; } .coinInput--input { @apply w-[100px] flex-1 ml-2 h-10 bg-transparent placeholder:text-gray-300; diff --git a/packages/app/src/styles/components/coin-selector.css b/packages/app/src/styles/components/coin-selector.css index 3dbeaf01..5905e8e2 100644 --- a/packages/app/src/styles/components/coin-selector.css +++ b/packages/app/src/styles/components/coin-selector.css @@ -11,7 +11,4 @@ .coinSelector--root { @apply flex flex-col items-end; } - .coinSelector--maxButton { - @apply text-xs py-0 px-1 h-auto bg-primary-800/60 text-primary-500 hover:bg-primary-800; - } } diff --git a/packages/app/src/styles/components/welcome-page.css b/packages/app/src/styles/components/welcome-page.css index ea4505ae..d8a65461 100644 --- a/packages/app/src/styles/components/welcome-page.css +++ b/packages/app/src/styles/components/welcome-page.css @@ -9,6 +9,10 @@ } .welcomePage--content { @apply relative m-8 grid place-items-center max-h-[100vh]; + + @media (max-width: 640px) { + @apply m-0 my-4; + } } /* WelcomeSidebar diff --git a/packages/app/src/systems/Core/components/AssetItem.tsx b/packages/app/src/systems/Core/components/AssetItem.tsx index e1b4f756..4111b372 100644 --- a/packages/app/src/systems/Core/components/AssetItem.tsx +++ b/packages/app/src/systems/Core/components/AssetItem.tsx @@ -15,8 +15,6 @@ export function AssetItem({ coin }: AssetItemProps) { coin, amount: coin.amount, isReadOnly: true, - showBalance: false, - showMaxButton: false, }); return ( diff --git a/packages/app/src/systems/Core/components/CoinBalance.tsx b/packages/app/src/systems/Core/components/CoinBalance.tsx new file mode 100644 index 00000000..fcbff7c2 --- /dev/null +++ b/packages/app/src/systems/Core/components/CoinBalance.tsx @@ -0,0 +1,58 @@ +import { useMemo } from "react"; + +import { useBalances } from "../hooks"; +import { parseToFormattedNumber } from "../utils"; + +import { Button, Tooltip } from "~/systems/UI"; +import type { Coin, Maybe } from "~/types"; + +export type CoinBalanceProps = { + coin?: Maybe; + showMaxButton?: boolean; + onSetMaxBalance?: () => void; + gasFee?: Maybe; +}; + +export const CoinBalance = ({ + coin, + showMaxButton = true, + onSetMaxBalance, + gasFee, +}: CoinBalanceProps) => { + const { data: balances } = useBalances({ enabled: true }); + + const balance = useMemo(() => { + const coinBalance = balances?.find( + (item) => item.assetId === coin?.assetId + ); + return parseToFormattedNumber(coinBalance?.amount || BigInt(0)); + }, [balances, coin?.assetId]); + + return ( +
+
+ Balance: {balance} +
+ {showMaxButton && ( + + + + )} +
+ ); +}; diff --git a/packages/app/src/systems/Core/components/CoinInput.tsx b/packages/app/src/systems/Core/components/CoinInput.tsx index f1fbefcc..33659f49 100644 --- a/packages/app/src/systems/Core/components/CoinInput.tsx +++ b/packages/app/src/systems/Core/components/CoinInput.tsx @@ -9,7 +9,17 @@ import { Spinner } from "~/systems/UI"; function getRightValue(value: string, displayType: string) { if (displayType === "text") return parseToFormattedNumber(value); - return value === "0.0" ? "0" : value; + + switch (value) { + case "0.0": + return "0"; + + case ".": + return "0."; + + default: + return value; + } } export const CoinInput = forwardRef( @@ -23,6 +33,7 @@ export const CoinInput = forwardRef( autoFocus, isLoading, rightElement, + bottomElement, ...props }, ref @@ -38,32 +49,35 @@ export const CoinInput = forwardRef( return (
- {isLoading ? ( -
- -
- ) : ( - ) => { - onChange?.(e.target.value); - setValue(e.target.value); - }} - decimalScale={DECIMAL_UNITS} - placeholder={props.placeholder || "0"} - className="coinInput--input" - thousandSeparator={false} - onInput={onInput} - /> - )} - {rightElement} +
+ {isLoading ? ( +
+ +
+ ) : ( + ) => { + onChange?.(e.target.value); + setValue(e.target.value); + }} + decimalScale={DECIMAL_UNITS} + placeholder={props.placeholder || "0"} + thousandSeparator={false} + onInput={onInput} + /> + )} + {rightElement} +
+ {bottomElement}
); } diff --git a/packages/app/src/systems/Core/components/CoinSelector.tsx b/packages/app/src/systems/Core/components/CoinSelector.tsx index 27151a2f..0fadf211 100644 --- a/packages/app/src/systems/Core/components/CoinSelector.tsx +++ b/packages/app/src/systems/Core/components/CoinSelector.tsx @@ -1,10 +1,9 @@ import cx from "classnames"; -import { useState, useEffect, forwardRef, useMemo } from "react"; +import { useState, useEffect, forwardRef } from "react"; import { FiChevronDown } from "react-icons/fi"; -import { useBalances } from "../hooks"; import type { CoinSelectorProps } from "../hooks/useCoinInput"; -import { TOKENS, parseToFormattedNumber, isCoinEth } from "../utils"; +import { TOKENS } from "../utils"; import { CoinsListDialog } from "./CoinsListDialog"; import { TokenIcon } from "./TokenIcon"; @@ -13,29 +12,9 @@ import { Button, Dialog, Tooltip, useDialog } from "~/systems/UI"; import type { Coin, Maybe } from "~/types"; export const CoinSelector = forwardRef( - ( - { - coin, - isReadOnly, - showBalance = true, - showMaxButton = true, - onChange, - onSetMaxBalance, - tooltip: tooltipContent, - }, - ref - ) => { + ({ coin, isReadOnly, onChange, tooltip: tooltipContent }, ref) => { const [selected, setSelected] = useState>(null); const dialog = useDialog(); - const { data: balances } = useBalances({ enabled: showBalance }); - const coinBalance = balances?.find( - (item) => item.assetId === coin?.assetId - ); - - const balance = useMemo( - () => parseToFormattedNumber(coinBalance?.amount || BigInt(0)), - [coinBalance?.assetId, coinBalance?.amount] - ); function handleSelect(assetId: string) { const next = TOKENS.find((item) => item.assetId === assetId)!; @@ -89,34 +68,6 @@ export const CoinSelector = forwardRef( - {(showBalance || showMaxButton) && ( -
- {showBalance && ( -
- Balance: {balance} -
- )} - {showMaxButton && ( - - - - )} -
- )} ); } diff --git a/packages/app/src/systems/Core/components/PrivateRoute.tsx b/packages/app/src/systems/Core/components/PrivateRoute.tsx index 4d87972c..ca20d2f2 100644 --- a/packages/app/src/systems/Core/components/PrivateRoute.tsx +++ b/packages/app/src/systems/Core/components/PrivateRoute.tsx @@ -3,13 +3,14 @@ import { Navigate } from "react-router-dom"; import { useWallet } from "../hooks"; -import { getCurrent } from "~/systems/Welcome"; +import { getCurrent, getAgreement } from "~/systems/Welcome"; import { Pages } from "~/types"; export function PrivateRoute({ children }: { children: ReactNode }) { const current = getCurrent(); + const acceptAgreement = getAgreement(); const wallet = useWallet(); - if (current.id > 2 || (wallet && !current.id)) { + if ((current.id > 2 && acceptAgreement) || (wallet && !current.id)) { return <>{children}; } return ; diff --git a/packages/app/src/systems/Core/hooks/useCoinInput.ts b/packages/app/src/systems/Core/hooks/useCoinInput.ts index c122b853..ba369254 100644 --- a/packages/app/src/systems/Core/hooks/useCoinInput.ts +++ b/packages/app/src/systems/Core/hooks/useCoinInput.ts @@ -2,6 +2,7 @@ import type { ReactNode } from 'react'; import { useMemo, useEffect, useState } from 'react'; import type { NumberFormatValues } from 'react-number-format'; +import type { CoinBalanceProps } from '../components/CoinBalance'; import { formatUnits, isCoinEth, MAX_U64_VALUE, parseInputValueBigInt, ZERO } from '../utils'; import { useBalances } from './useBalances'; @@ -22,8 +23,6 @@ export type UseCoinParams = { /** * Coins for */ - showBalance?: boolean; - showMaxButton?: boolean; onChangeCoin?: (coin: Coin) => void; disableWhenEth?: boolean; }; @@ -34,6 +33,7 @@ export type UseCoinInput = { setGasFee: React.Dispatch>>; getInputProps: () => CoinInputProps; getCoinSelectorProps: () => CoinSelectorProps; + getCoinBalanceProps: () => CoinBalanceProps; formatted: string; hasEnoughBalance: boolean; }; @@ -45,6 +45,7 @@ export type CoinInputProps = Omit & autoFocus?: boolean; isLoading?: boolean; rightElement?: ReactNode; + bottomElement?: ReactNode; isAllowed?: (values: NumberFormatValues) => boolean; onChange?: (val: string) => void; }; @@ -72,8 +73,6 @@ export function useCoinInput({ gasFee: initialGasFee, isReadOnly, onInput, - showBalance, - showMaxButton, onChangeCoin, disableWhenEth, ...params @@ -84,10 +83,6 @@ export function useCoinInput({ const coinBalance = balances?.find((item) => item.assetId === coin?.assetId); const isEth = useMemo(() => isCoinEth(coin), [coin?.assetId]); - useEffect(() => { - if (initialGasFee) setGasFee(initialGasFee); - }, [initialGasFee]); - // TODO: consider real gas fee, replacing GAS_FEE variable. // For now we need to keep 1 unit in the wallet(it's not spent) in order to complete "create pool" transaction. function getSafeMaxBalance() { @@ -97,7 +92,7 @@ export function useCoinInput({ return value; } - const handleInputPropsChange = (val: string) => { + function handleInputPropsChange(val: string) { if (isReadOnly) return; const next = val !== '' ? parseInputValueBigInt(val) : null; if (typeof onChange === 'function') { @@ -105,11 +100,11 @@ export function useCoinInput({ } else { setAmount(next); } - }; + } - const isAllowed = ({ value }: NumberFormatValues) => { + function isAllowed({ value }: NumberFormatValues) { return parseInputValueBigInt(value) <= MAX_U64_VALUE; - }; + } function getInputProps() { return { @@ -127,8 +122,6 @@ export function useCoinInput({ return { coin, isReadOnly, - showBalance, - showMaxButton, onChange: onChangeCoin, onSetMaxBalance: () => { onInput?.(); @@ -142,6 +135,21 @@ export function useCoinInput({ } as CoinSelectorProps; } + function getCoinBalanceProps() { + return { + coin, + gasFee, + onSetMaxBalance: () => { + onInput?.(); + handleInputPropsChange(formatValue(getSafeMaxBalance())); + }, + } as CoinBalanceProps; + } + + useEffect(() => { + if (initialGasFee) setGasFee(initialGasFee); + }, [initialGasFee]); + useEffect(() => { // Enable value initialAmount to be null if (initialAmount !== undefined) setAmount(initialAmount); @@ -153,6 +161,7 @@ export function useCoinInput({ setGasFee, getInputProps, getCoinSelectorProps, + getCoinBalanceProps, formatted: formatValue(amount), hasEnoughBalance: getSafeMaxBalance() >= (amount || ZERO), }; diff --git a/packages/app/src/systems/Pool/components/AddLiquidityPoolPrice.tsx b/packages/app/src/systems/Pool/components/AddLiquidityPoolPrice.tsx index 0a132b81..d2847adf 100644 --- a/packages/app/src/systems/Pool/components/AddLiquidityPoolPrice.tsx +++ b/packages/app/src/systems/Pool/components/AddLiquidityPoolPrice.tsx @@ -23,7 +23,7 @@ export const AddLiquidityPoolPrice = ({ Math.floor(oneAssetAmount * 1 * reservesRatio) ); return ( -
+

Pool price

diff --git a/packages/app/src/systems/Pool/components/AddLiquidityPreview.tsx b/packages/app/src/systems/Pool/components/AddLiquidityPreview.tsx index 686d9781..8cfd2085 100644 --- a/packages/app/src/systems/Pool/components/AddLiquidityPreview.tsx +++ b/packages/app/src/systems/Pool/components/AddLiquidityPreview.tsx @@ -43,7 +43,7 @@ export const AddLiquidityPreview = ({ { const coinTo = coinMetaData?.pairOf?.[1]; return ( - + { const successFeedback = await screen.findByText(/New pool created/i); expect(successFeedback).toBeInTheDocument(); }); + + it("should show '0.' if typed only '.' in the input", async () => { + jest.unmock("../hooks/useUserPositions.ts"); + + renderWithRouter(, { + route: "/pool/add-liquidity", + }); + + const coinFromInput = screen.getByLabelText(/Coin From Input/); + fireEvent.change(coinFromInput, { + target: { + value: ".", + }, + }); + + await waitFor(() => { + expect(coinFromInput).toHaveValue("0."); + }); + }); }); diff --git a/packages/app/src/systems/Pool/pages/AddLiquidity.tsx b/packages/app/src/systems/Pool/pages/AddLiquidity.tsx index 84921965..bda41def 100644 --- a/packages/app/src/systems/Pool/pages/AddLiquidity.tsx +++ b/packages/app/src/systems/Pool/pages/AddLiquidity.tsx @@ -23,6 +23,7 @@ import { TOKENS, useBalances, } from "~/systems/Core"; +import { CoinBalance } from "~/systems/Core/components/CoinBalance"; import { Button, Card, Spinner } from "~/systems/UI"; import type { Coin, Maybe } from "~/types"; @@ -201,6 +202,9 @@ export function AddLiquidity() { rightElement={ } + bottomElement={ + + } /> } + bottomElement={} />
} + bottomElement={} />
diff --git a/packages/app/src/systems/Swap/components/SwapComponent.tsx b/packages/app/src/systems/Swap/components/SwapComponent.tsx index b562a872..eccc567c 100644 --- a/packages/app/src/systems/Swap/components/SwapComponent.tsx +++ b/packages/app/src/systems/Swap/components/SwapComponent.tsx @@ -12,6 +12,7 @@ import type { SwapState } from "../types"; import { SwapDirection } from "../types"; import { CoinInput, useCoinInput, CoinSelector } from "~/systems/Core"; +import { CoinBalance } from "~/systems/Core/components/CoinBalance"; import { InvertButton } from "~/systems/UI"; import type { Coin, Maybe } from "~/types"; @@ -144,6 +145,7 @@ export function SwapComponent({ {...(activeInput === SwapDirection.toFrom && { isLoading })} autoFocus={activeInput === SwapDirection.fromTo} rightElement={} + bottomElement={} />
@@ -156,6 +158,7 @@ export function SwapComponent({ {...(activeInput === SwapDirection.fromTo && { isLoading })} autoFocus={activeInput === SwapDirection.toFrom} rightElement={} + bottomElement={} />
diff --git a/packages/app/src/systems/Swap/components/SwapPreview.tsx b/packages/app/src/systems/Swap/components/SwapPreview.tsx index 268576f0..e3462912 100644 --- a/packages/app/src/systems/Swap/components/SwapPreview.tsx +++ b/packages/app/src/systems/Swap/components/SwapPreview.tsx @@ -24,7 +24,7 @@ export function SwapPreview() { : "Max. sent after slippage"; return ( -
+
diff --git a/packages/app/src/systems/Welcome/components/WelcomeDone.tsx b/packages/app/src/systems/Welcome/components/WelcomeDone.tsx index 2a52ed4e..4b1caba6 100644 --- a/packages/app/src/systems/Welcome/components/WelcomeDone.tsx +++ b/packages/app/src/systems/Welcome/components/WelcomeDone.tsx @@ -1,31 +1,57 @@ -import { useNavigate } from "react-router-dom"; - import { useWelcomeSteps } from "../hooks"; import { WelcomeStep } from "./WelcomeStep"; import { relativeUrl } from "~/systems/Core"; -import { Button } from "~/systems/UI"; +import { Button, Link } from "~/systems/UI"; + +const DISCLAIMER_URL = + "https://github.com/FuelLabs/swayswap/blob/master/docs/LEGAL_DISCLAIMER.md"; export function WelcomeDone() { - const navigate = useNavigate(); - const { send } = useWelcomeSteps(); + const { send, state } = useWelcomeSteps(); function handleDone() { - navigate("/swap"); send("FINISH"); } return ( -

Done, congrats!

+

Wallet Created!

- Now you are done to swap your coins using the fatest modular execution - layer + Now you're ready to swap and pool test assets using Fuel: + the fastest modular execution layer.

-
); diff --git a/packages/app/src/systems/Welcome/components/WelcomePage.test.tsx b/packages/app/src/systems/Welcome/components/WelcomePage.test.tsx index 42e511fc..c43721e3 100644 --- a/packages/app/src/systems/Welcome/components/WelcomePage.test.tsx +++ b/packages/app/src/systems/Welcome/components/WelcomePage.test.tsx @@ -39,8 +39,16 @@ describe("WelcomePage", () => { /** * Third step: done */ + + const acceptAgreement = await screen.findByRole("checkbox", { + name: /Accept the use agreement/i, + }); + expect(acceptAgreement).not.toBeChecked(); + await user.click(acceptAgreement); + expect(acceptAgreement).toBeChecked(); + const goToSwapBtn = await screen.findByRole("button", { - name: /Go to Swap/i, + name: /Get Swapping!/i, }); expect(goToSwapBtn).toBeInTheDocument(); await user.click(goToSwapBtn); diff --git a/packages/app/src/systems/Welcome/hooks/useWelcomeSteps.tsx b/packages/app/src/systems/Welcome/hooks/useWelcomeSteps.tsx index d8b04319..2de99190 100644 --- a/packages/app/src/systems/Welcome/hooks/useWelcomeSteps.tsx +++ b/packages/app/src/systems/Welcome/hooks/useWelcomeSteps.tsx @@ -11,6 +11,8 @@ import type { Maybe } from "~/types"; import { Pages } from "~/types"; export const LOCALSTORAGE_WELCOME_KEY = "fuel--welcomeStep"; +export const LOCALSTORAGE_AGREEMENT_KEY = "fuel--agreement"; + export const STEPS = [ { id: 0, path: Pages["welcome.createWallet"] }, { id: 1, path: Pages["welcome.addFunds"] }, @@ -18,6 +20,14 @@ export const STEPS = [ { id: 3, path: null }, ]; +export function getAgreement() { + return localStorage.getItem(LOCALSTORAGE_AGREEMENT_KEY) === "true"; +} + +export function setAgreement(accept: boolean) { + localStorage.setItem(LOCALSTORAGE_AGREEMENT_KEY, String(accept)); +} + export function getCurrent() { try { const curr = localStorage.getItem(LOCALSTORAGE_WELCOME_KEY); @@ -50,6 +60,7 @@ export type Step = { type MachineContext = { current: Step; + acceptAgreement: boolean; wallet?: Maybe; }; @@ -64,6 +75,7 @@ const welcomeStepsMachine = createMachine({ }, context: { current: getCurrent(), + acceptAgreement: getAgreement(), }, states: { init: { @@ -85,9 +97,12 @@ const welcomeStepsMachine = createMachine({ }, { target: "done", - cond: (ctx) => ctx.current.id === 2, + cond: (ctx) => + ctx.current.id === 2 || + (ctx.current.id >= 2 && !ctx.acceptAgreement), }, { + cond: (ctx) => ctx.current.id === 3 && ctx.acceptAgreement, target: "finished", }, ], @@ -111,6 +126,9 @@ const welcomeStepsMachine = createMachine({ done: { entry: [assignCurrent(2), "navigateTo"], on: { + ACCEPT_AGREEMENT: { + actions: ["acceptAgreement"], + }, FINISH: { target: "finished", }, @@ -158,6 +176,7 @@ export function StepsProvider({ children }: WelcomeStepsProviderProps) { .withContext({ wallet, current: getCurrent(), + acceptAgreement: getAgreement(), }) .withConfig({ actions: { @@ -165,6 +184,13 @@ export function StepsProvider({ children }: WelcomeStepsProviderProps) { if (context.current.id > 2) return; navigate(`/welcome/${context.current.path}`); }, + acceptAgreement: assign((context, event) => { + setAgreement(event.value); + return { + ...context, + acceptAgreement: event.value, + }; + }), }, }) );