From d4ea05b42fd47c04cada8d424e7b39e2d05aff9f Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 8 May 2024 11:58:54 +0200 Subject: [PATCH 01/34] Removal of unnecessary code from deposit flow --- .../TransactionModal/ActionFormModal.tsx | 68 ++++++++----------- .../ActiveStakingStep/OverviewModal/index.tsx | 36 ---------- .../ActiveStakingStep/OverviewModal/steps.tsx | 25 ------- .../ActiveStakingStep/SignMessageModal.tsx | 58 ---------------- .../ActiveStakingStep/index.tsx | 6 -- .../ActiveUnstakingStep/SignMessageModal.tsx | 8 +-- .../TransactionModal/ModalContentWrapper.tsx | 12 +--- .../TransactionModal/ResumeModal.tsx | 50 -------------- .../TransactionModal/SuccessModal.tsx | 1 - .../src/components/TransactionModal/index.tsx | 45 +++--------- .../shared/alerts/ReceiveSTBTCAlert.tsx | 18 ----- dapp/src/components/shared/alerts/index.ts | 1 - dapp/src/contexts/ModalFlowContext.tsx | 2 - dapp/src/hooks/useInitApp.ts | 4 +- .../pages/OverviewPage/PositionDetails.tsx | 10 ++- dapp/src/types/action-flow.ts | 6 +- 16 files changed, 51 insertions(+), 299 deletions(-) delete mode 100644 dapp/src/components/TransactionModal/ActiveStakingStep/OverviewModal/index.tsx delete mode 100644 dapp/src/components/TransactionModal/ActiveStakingStep/OverviewModal/steps.tsx delete mode 100644 dapp/src/components/TransactionModal/ActiveStakingStep/SignMessageModal.tsx delete mode 100644 dapp/src/components/TransactionModal/ResumeModal.tsx delete mode 100644 dapp/src/components/shared/alerts/ReceiveSTBTCAlert.tsx diff --git a/dapp/src/components/TransactionModal/ActionFormModal.tsx b/dapp/src/components/TransactionModal/ActionFormModal.tsx index 0f7c824c9..e536d426e 100644 --- a/dapp/src/components/TransactionModal/ActionFormModal.tsx +++ b/dapp/src/components/TransactionModal/ActionFormModal.tsx @@ -1,15 +1,6 @@ import React, { useCallback, useState } from "react" +import { Box, ModalBody, ModalCloseButton, ModalHeader } from "@chakra-ui/react" import { - ModalBody, - Tabs, - TabList, - Tab, - TabPanels, - TabPanel, - ModalCloseButton, -} from "@chakra-ui/react" -import { - useModalFlowContext, useStakeFlowContext, useTransactionContext, useWalletContext, @@ -20,16 +11,36 @@ import { logPromiseFailure } from "#/utils" import StakeFormModal from "./ActiveStakingStep/StakeFormModal" import UnstakeFormModal from "./ActiveUnstakingStep/UnstakeFormModal" -const TABS = Object.values(ACTION_FLOW_TYPES) +const FORM_DATA: Record< + ActionFlowType, + { + header: string + FormComponent: ( + props: React.ComponentProps< + typeof StakeFormModal | typeof UnstakeFormModal + >, + ) => React.ReactNode + } +> = { + stake: { + header: "Deposit", + FormComponent: StakeFormModal, + }, + unstake: { + header: "Withdraw", + FormComponent: UnstakeFormModal, + }, +} -function ActionFormModal({ defaultType }: { defaultType: ActionFlowType }) { +function ActionFormModal({ type }: { type: ActionFlowType }) { const { btcAccount, ethAccount } = useWalletContext() - const { type, setType } = useModalFlowContext() const { setTokenAmount } = useTransactionContext() const { initStake } = useStakeFlowContext() const [isLoading, setIsLoading] = useState(false) + const { header, FormComponent } = FORM_DATA[type] + const handleInitStake = useCallback(async () => { const btcAddress = btcAccount?.address const ethAddress = ethAccount?.address @@ -67,34 +78,11 @@ function ActionFormModal({ defaultType }: { defaultType: ActionFlowType }) { return ( <> {!isLoading && } + {header} - - - {TABS.map((actionFlowType) => ( - setType(actionFlowType)} - isDisabled={actionFlowType !== type && isLoading} - > - {actionFlowType} - - ))} - - - - - - - - - - + + + ) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/OverviewModal/index.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/OverviewModal/index.tsx deleted file mode 100644 index c5806b663..000000000 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/OverviewModal/index.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from "react" -import { - Button, - ModalBody, - ModalFooter, - ModalHeader, - StepNumber, -} from "@chakra-ui/react" -import StepperBase from "#/components/shared/StepperBase" -import { useModalFlowContext } from "#/hooks" -import { STEPS } from "./steps" - -export default function OverviewModal() { - const { goNext } = useModalFlowContext() - - return ( - <> - Staking steps overview - - } - steps={STEPS} - /> - - - - - - ) -} diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/OverviewModal/steps.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/OverviewModal/steps.tsx deleted file mode 100644 index 899fc7960..000000000 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/OverviewModal/steps.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from "react" -import { StepBase } from "#/components/shared/StepperBase" -import { Description, Title } from "../StakingStepsModalContent" - -export const STEPS: StepBase[] = [ - { - id: "sign-message", - title: Sign message, - description: ( - - You will sign a gas-free Ethereum message to indicate the address where - you'd like to get your stBTC liquid staking token. - - ), - }, - { - id: "deposit-btc", - title: Deposit BTC, - description: ( - - You will make a Bitcoin transaction to deposit and stake your BTC. - - ), - }, -] diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/SignMessageModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/SignMessageModal.tsx deleted file mode 100644 index 37bba6883..000000000 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/SignMessageModal.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React, { useCallback, useEffect, useState } from "react" -import { - useExecuteFunction, - useModalFlowContext, - useStakeFlowContext, - useToast, -} from "#/hooks" -import { logPromiseFailure } from "#/utils" -import { PROCESS_STATUSES, TOASTS, TOAST_IDS } from "#/types" -import { ReceiveSTBTCAlert } from "#/components/shared/alerts" -import StakingStepsModalContent from "./StakingStepsModalContent" - -const TOAST_ID = TOAST_IDS.SIGNING_ERROR -const TOAST = TOASTS[TOAST_ID] - -export default function SignMessageModal() { - const { goNext, setStatus } = useModalFlowContext() - const { signMessage } = useStakeFlowContext() - const [buttonText, setButtonText] = useState("Sign now") - const { closeToast, openToast } = useToast() - - useEffect(() => { - setStatus(PROCESS_STATUSES.PENDING) - }, [setStatus]) - - const onSignMessageSuccess = useCallback(() => { - closeToast(TOAST_ID) - goNext() - }, [closeToast, goNext]) - - const onSignMessageError = useCallback(() => { - openToast({ - id: TOAST_ID, - render: TOAST, - }) - setButtonText("Try again") - }, [openToast]) - - const handleSignMessage = useExecuteFunction( - signMessage, - onSignMessageSuccess, - onSignMessageError, - ) - - const handleSignMessageWrapper = useCallback(() => { - logPromiseFailure(handleSignMessage()) - }, [handleSignMessage]) - - return ( - - - - ) -} diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/index.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/index.tsx index 6776365f4..d03576d31 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/index.tsx @@ -1,17 +1,11 @@ import React from "react" import { ACTION_FLOW_STEPS_TYPES, ACTION_FLOW_TYPES } from "#/types" -import SignMessageModal from "./SignMessageModal" import DepositBTCModal from "./DepositBTCModal" -import OverviewModal from "./OverviewModal" const STEPS = ACTION_FLOW_STEPS_TYPES[ACTION_FLOW_TYPES.STAKE] export function ActiveStakingStep({ activeStep }: { activeStep: number }) { switch (activeStep) { - case STEPS.OVERVIEW: - return - case STEPS.SIGN_MESSAGE: - return case STEPS.DEPOSIT_BTC: return default: { diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx index d462cab94..e46359396 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx @@ -1,18 +1,13 @@ -import React, { useCallback, useEffect } from "react" +import React, { useCallback } from "react" import { useExecuteFunction, useModalFlowContext } from "#/hooks" import { PROCESS_STATUSES } from "#/types" import { Button, ModalBody, ModalFooter, ModalHeader } from "@chakra-ui/react" import { TextMd } from "#/components/shared/Typography" import { logPromiseFailure } from "#/utils" -import { ReceiveSTBTCAlert } from "#/components/shared/alerts" export default function SignMessageModal() { const { setStatus } = useModalFlowContext() - useEffect(() => { - setStatus(PROCESS_STATUSES.PENDING) - }, [setStatus]) - const onSignMessageSuccess = useCallback(() => { setStatus(PROCESS_STATUSES.SUCCEEDED) }, [setStatus]) @@ -46,7 +41,6 @@ export default function SignMessageModal() { You will sign a gas-free Ethereum message to indicate the address where you'd like to get your stBTC liquid staking token. - - - - - ) -} diff --git a/dapp/src/components/TransactionModal/SuccessModal.tsx b/dapp/src/components/TransactionModal/SuccessModal.tsx index e6dac590e..5ea882804 100644 --- a/dapp/src/components/TransactionModal/SuccessModal.tsx +++ b/dapp/src/components/TransactionModal/SuccessModal.tsx @@ -46,7 +46,6 @@ export default function SuccessModal({ type, tokenAmount }: SuccessModalProps) { /> - + {/* TODO: Simplify the logic of opening modals */} + diff --git a/dapp/src/types/action-flow.ts b/dapp/src/types/action-flow.ts index ac31a0ff0..a587b58b9 100644 --- a/dapp/src/types/action-flow.ts +++ b/dapp/src/types/action-flow.ts @@ -7,9 +7,7 @@ export type ActionFlowType = (typeof ACTION_FLOW_TYPES)[keyof typeof ACTION_FLOW_TYPES] const STAKING_STEPS = { - OVERVIEW: 1, - SIGN_MESSAGE: 2, - DEPOSIT_BTC: 3, + DEPOSIT_BTC: 1, } as const const UNSTAKING_STEPS = { SIGN_MESSAGE: 1 } as const @@ -21,8 +19,6 @@ export const ACTION_FLOW_STEPS_TYPES = { export const PROCESS_STATUSES = { IDLE: "IDLE", - PAUSED: "PAUSED", - PENDING: "PENDING", LOADING: "LOADING", FAILED: "FAILED", SUCCEEDED: "SUCCEEDED", From 148117f9d3c4c742dedb002de5d32df16c5134dd Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 8 May 2024 12:04:36 +0200 Subject: [PATCH 02/34] Update `DepositBTCModal` component --- .../ActiveStakingStep/DepositBTCModal.tsx | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index 4b68afa54..17c1dfe2d 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from "react" +import React, { useCallback } from "react" import { useDepositBTCTransaction, useDepositTelemetry, @@ -9,13 +9,16 @@ import { useTransactionContext, useWalletContext, } from "#/hooks" -import { TextMd } from "#/components/shared/Typography" import { logPromiseFailure } from "#/utils" import { PROCESS_STATUSES } from "#/types" -import { CardAlert } from "#/components/shared/alerts" import { TOASTS, TOAST_IDS } from "#/types/toast" -import StakingStepsModalContent from "./StakingStepsModalContent" +import { ModalBody, ModalHeader, Highlight, useTimeout } from "@chakra-ui/react" +import Spinner from "#/components/shared/Spinner" +import { TextMd } from "#/components/shared/Typography" +import { CardAlert } from "#/components/shared/alerts" +import { ONE_SEC_IN_MILLISECONDS } from "#/constants" +const DELAY = ONE_SEC_IN_MILLISECONDS * 3 const TOAST_ID = TOAST_IDS.DEPOSIT_TRANSACTION_ERROR const TOAST = TOASTS[TOAST_ID] @@ -27,9 +30,6 @@ export default function DepositBTCModal() { const depositTelemetry = useDepositTelemetry() const { closeToast, openToast } = useToast() - const [isLoading, setIsLoading] = useState(false) - const [buttonText, setButtonText] = useState("Deposit BTC") - const onStakeBTCSuccess = useCallback( () => setStatus(PROCESS_STATUSES.SUCCEEDED), [setStatus], @@ -57,7 +57,6 @@ export default function DepositBTCModal() { id: TOAST_ID, render: TOAST, }) - setButtonText("Try again") }, [openToast]) const onDepositBTCError = useCallback(() => showError(), [showError]) @@ -71,13 +70,11 @@ export default function DepositBTCModal() { if (!tokenAmount?.amount || !btcAddress || !depositReceipt || !ethAccount) return - setIsLoading(true) const response = await depositTelemetry( depositReceipt, btcAddress, ethAccount.address, ) - setIsLoading(false) if (response.verificationStatus === "valid") { logPromiseFailure(sendBitcoinTransaction(tokenAmount?.amount, btcAddress)) @@ -98,18 +95,23 @@ export default function DepositBTCModal() { logPromiseFailure(handledDepositBTC()) }, [handledDepositBTC]) + useTimeout(handledDepositBTCWrapper, DELAY) + return ( - - - - Make a Bitcoin transaction to deposit and stake your BTC. - - - + <> + Waiting transaction... + + + Please complete the transaction in your wallet. + + + + You will receive your Rewards once the deposit transaction is + completed. + + + + + ) } From a51979f05bb2bc6dd95c7d4534d3093dc7982fd8 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 8 May 2024 12:09:45 +0200 Subject: [PATCH 03/34] Update `Sidebar` component --- dapp/src/assets/icons/ShieldPlus.tsx | 17 ---- dapp/src/assets/icons/index.ts | 1 - dapp/src/assets/images/right-sidebar-bg.png | Bin 124416 -> 0 bytes dapp/src/components/Sidebar.tsx | 70 ++++++++++++++++ dapp/src/components/Sidebar/index.tsx | 84 -------------------- 5 files changed, 70 insertions(+), 102 deletions(-) delete mode 100644 dapp/src/assets/icons/ShieldPlus.tsx delete mode 100644 dapp/src/assets/images/right-sidebar-bg.png create mode 100644 dapp/src/components/Sidebar.tsx delete mode 100644 dapp/src/components/Sidebar/index.tsx diff --git a/dapp/src/assets/icons/ShieldPlus.tsx b/dapp/src/assets/icons/ShieldPlus.tsx deleted file mode 100644 index b3fbea2bc..000000000 --- a/dapp/src/assets/icons/ShieldPlus.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from "react" -import { createIcon } from "@chakra-ui/react" - -export const ShieldPlusIcon = createIcon({ - displayName: "ShieldPlusIcon", - viewBox: "0 0 20 20", - path: ( - - ), -}) diff --git a/dapp/src/assets/icons/index.ts b/dapp/src/assets/icons/index.ts index 62d802cf8..e2832df45 100644 --- a/dapp/src/assets/icons/index.ts +++ b/dapp/src/assets/icons/index.ts @@ -5,7 +5,6 @@ export * from "./ArrowRight" export * from "./AcreLogo" export * from "./stBTC" export * from "./BTC" -export * from "./ShieldPlus" export * from "./Pending" export * from "./Syncing" export * from "./Complete" diff --git a/dapp/src/assets/images/right-sidebar-bg.png b/dapp/src/assets/images/right-sidebar-bg.png deleted file mode 100644 index 540134e584502ef1baf0217d3b4d02648cd5be32..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 124416 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�ec2i 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

R zgxo(q6+!t4Kuf=7+`}9m$oEjzq*|P6cl0N_=?~oPyX3LYL04|5WNFqZ@j8MwOOhTC zt;=DQ&klje42N|{u}ME!F1zdXJ5WgmQ}TXTD9b$%ku~+4Y3h0y+GDhIYQ5Djmzw>< z(fM=^id!)lw0l5NGYF>7p=9978WT#TjqNxd-V&}A50^(yAD+Z}6Ad)vpLER!*%*8d zs&Xv^Vv_geY#L?izO2PNa(h09_w15t-XwKAj}DalJm5jj^Wa8~;`7fUcno>_E`Ypl z9VFIL7*a6!l0X08k?d~Zkm328aoJE-nStKh65%*P3_*6*B)cd>Ke-1qz5{d&9&Cnl zY(VIPT!G`XO6f#Y+Ovc5#_KmFvx@V8YvTpke||V5hoC?mIvSEEk(w-`wCr?U5&`9oZ#7CD% z{J=`Bt;oAdBQBJkkCw9XWT`4e1f=P4gZz`ZklNFI~q^GSIExU{)f zmE)&lGByaNnf(PzAlvrKGCA#%fBbikqBN<=-@LtyAYqo%r|A+FmUlne209s%8K4Dn zI4es@@D|*SEV73OYJQ(HRSyq;|SlSHe;x=|A+8kc> z>vbW@)3}yFhh32#w_k365WTwtITMFp_g`}m4#_t2>We-UO<$V@xTG6$_Rqrbkrp$k+ohz899xN z=+kGq?kvz!G07%k#a27G}BxOYZ zB7zO)ZX(Do&yRa_ck|7ag!oWy(GP_3=*#DZWCXA|jqLtYl&O5U2(P*Nq|a;77q+X! zZ3jqzrA}9!?3(I7%g$a0o9a;3y&N5oBT#o(qI76RWoD__H=3el15UWSy}TpKS#Swe zloM*@Nhd$ihnD?EZTvkc6qahnMt`XuK#?jK++2Ak9FXT?2$J**M!>F<=i>4>{BI%} zkW)vdRkEhFsuN#lfHAQr3FuILZWuJl8}Ni7Xyj3fKUl~}GTwrsX3%ml00uvZXM$_3 z1`udY;JO9a-v;8EHXsYx zuH1c?lH4YcE|S#@xW-BFj8T*s9VfiJ4h-iYKBz7u`k~XEGAhGqIixLH8R};?_5Z7o3d{RNO>9)46Opk?RYZD0# z%2@J%Msx@e^klZQWFO~|Q|w_pUtI$NOT#9&?`MT-w-Xohm5YA)?XN|pl)&{)wj`bI zAgI+)i3BB=&dJz_4=Qv~;_{n3?S7ltRTx7 z>@q%LmF@U}8kO&4Ah`kF4~)?F)ga636 zh~B6-uS4>_{NlJ6Am4HM)sKI^0(RS}t>hO$mTjVZ$radpVh2fz%m-Ps!GGy)1Bcikj7jGVTstmzf>gv_I-al0b&`1Hp-Eixj`93kZ9Mr`ogK+Dn2%c zQN03*$%7gMq>SQgqd-nmkn~>|w#vD=kPHousnx^=0_N}ExGh^{gX}CM1r>*s+&wup zIV8q>K?5g=)D$u^w?nY5rSbXN>ZbVJ0pTVSsz(v0N#+JzKuKn~zmW$Km&a!*$v^zo zOY+<^hgD)uWUKNq4&6Zl*BXQ$W$P|yPdg?>PZn-s!OdY1F)}EyK;ukQqo)i0XiEMP z)U@NH14`iXL1u$KkqLt}o8m|qezdkFw>ERC(lK~Bp&oIkHVI=L45S4l5gT6z<&rLY z;L&UUP@mflEjD`MHpJ_8sFLV(b|6Ru)Srh4xRlL`lXew^^GFOd&vJl7 zCU@a;1%bMX2&`NpXx3|s0!JcQgG*{7$aLfgRL^0QqcK_~00lPA zk4pla<pk?;z9W{gQRv8oXr5&`kWh)g&B)%;Pd3be@^1?uP&G5>W67a&N#pz zy@%*&jS?+@pNq#)P6Mra@b}~-IUC23nMaQnv;+?$7&n26h(vs#G1A>FS?g?~w6CfQ zLJ`3qpdE;(Coi0zmc9L?)H_x!ec0{G?tG4+*zF#0hh?2h>-0cqCwHtMuGI0+nN z)V$yM#<D%sQ|`}k30N<371 z`)$b5707Zf@#5yPhHv#~IOGHT^=OX@d6XlEM&-lXkHldFIgE#e4;zsUA8Iyrkqc3y zUcA;;Dy=>k^^RH9B5ntwH?TkC(*Z)8HnDM2Wuw2iY$K!nC2Yv!FsBvSuK^|CqqSWL zgCrb9J!`JC#DjXA`?J|OO6vx%U>*z_v6H(WCeP}1Vp8dQbA zUv)r;mQdEa;Ar4)F3*mb)>B^S2c0sqwHQj=_j*NF_v#Kk1*rQViL}&70 z+W?orQQd2S!)k-vZ6f%!IJ86GXB>bo%ElR>lM7L=*uj_3*xhIaw}V5~Lm+b#g&-iY zSA@n|R2y?B&+{Pf-SlUs{e^{_Q{7SmN92V1#}{ow_480&L?CcM)nn=FL~xvi#LV~3 z5f1~9O!u%~)(uU$HXldEwMrU?!HvUmG|b>(08iMKK{m*dpNt|=~7 zuZ981i}$+@km1L5iWjYN?O{$bH6{^}fAIAo`IYB_vNV4H1*@h#XiHE%ojxlRW2=_j zbl_~_c`AVyyY{3iyWnQ&CFKG#Gfh7;aAk7V0~CONxSCUw;o*=|Muxnyx1EzjhP+Ex zd6GuH3wgUE`}P$=@_DrTF^J^J7dp0KLP`5Ua z^A3>%AZpS5V2S~ITPErT7C&uUYfyGX6PltqqHk@VDrH+Q$NP3JB9cQOg{>h_< z|k138bHHcThNA{(7LI!MTJ zLH%9WKjG}JrU{1q>d?kj8}tY1042Th%rQB2biBX2n+ZewK9@Zgt2{~W-d12#91i5>E%S6&zl%cLJl zBaonuG@^v$xWVqmhAN0`I%5DY;PqHm@@M99TytEYs}tgIm-q8u6lFEWQP9sso5FFi*aD6AiYl1JE$9sHy3-UF-Hv&X=8XVIU%Cnfe zE1_ox<&!{3){yFgcy25WhkSSyoYGoTT^oHqo5b*JjXIo)Wa#-5a~b61Ak@#1zAY)Q zQ4NUi-rWT`bM}-D)v~cPnNEXr9gyADf@CGFO_k);K)-^IrMAZw`Vyw2DbF9X)ER^7nu3v-1Ca|84n=Z+=1S)eA2IfC4P)2h5rhYR*BqT?bP3rk9{he1ShxB0==uKML1rGY zu&&a0YGVaN`mkj8^LUi~&OP8uT-RF8P%z5A=Ow%m|>7G*Pt z(Yc_=sEC@Wdx&2HYasF zLscNYy#&f{Br*C9XG)?T+;o-P-$`jt7*U`XWN;pdKM$n46VJ#pxK{|micDRuhWzTQ zqw?HQNcHiYe7sWBVc!BofDDve9|Cv-YLa)@hMxgsWe>|6;CLA$=yW+aXpk?zI4oXE zLs#mW_Qh}o_#XsOU7kHRCK=TIsI(-N%gg2GXFyJ-a8s@FbgLwFNO?AtHQ)Qzv^;av zEuY-ol_ZGWNC=7wvdPLSNFh+ZD?$+e+i6E4qvJ8XyFIG%IQU$Q#Hrxnjzv0XXr#x`hm4tRx=}~Kp&UqMrBaA1oRTd&j}*a1;Mq1ft@@UWV^FU&8<;Mj=Tat`9&dVR>cKw1xLDfxfz-jew|0t^trRBQslIVb)I0(tsC ziF9MTCS&-#0|AG2F*gy|(&H|9=gyjJ0ZF9eH6b3pJmi)WQ4p^9*?FL)nW>PNCY`c~ z(&g&YylkPgDWE)@#I-mNvNVYMWQBwsePKwRqC~#&WJmUw!BcHD5M<1{688|D4u_y5 zB8&gLosez_NDM!(RGKlm|ZuGL^-kIdtV>bEy-##f#0E`08{BDx_-R-)E zy8)Cq5DH7rfOC!V*=+R72lrEQf4zq9MUt|0<(X3+`Ru8Hgnb5p1*iPYhnr#=J>j|X z?3oC%fe8;$SGM=75*f70iNhel5CoQ23p&G5FL6_2PyXkBc3fr34?+0SZ7Ujc%AMO# zgq9kzy@h`pHA~prlA(!FK+_U5b#MwG4UZmm%B71zUCXe$4JqsgHA#e~C+&CX#xeq@N+yXZXf{5e(BwojN7t>-LjOqR$OVcLF z^)z^&3d+SGc&lMZ$dFr|DCH2GCQ!Q<(s>Zh4z*sqS;|QTpW6o%zyUfcM4Yasj9&s& z4-Z8kg{S)HohcT(2+=a}5OlE|TYBCL?=X6$E5H z!(OCOA9yz#xGf_NOjd>l2E=sQA76EGK-$pjJu1gWjd*6uxZizr%3a(J7gWBESv`*GD3x5ews!z5-p1Ka75cl;XD1!<8^1Ct z&zuMVco^j;S9j#?ds*dpO<(=?)RmpxlFEXaTvZNFfd@clEfkD$>wXdhoSD?MeEIW3 z@~hvO1X4-LyYH{d#B5BCpx#{uq>6))@AOdbdjOee%nS}Be$bGuy$m3}157rvrpV&c zsw^yk69FUtjc-J8Xlkep9rEyzNuEAV%0=)wXD=Wt9J3=T*8u@LAc%o%2n(wjK)?)O zbrd{@QRPlba)&3!Xw*W_HaWBK!B%4Ms;4w2_A85Wa2W8dVkm!P_JWMpo`Lki`lcujQFBNGF8;@J}H2&&>c#YJMq5rb(A(V zz}CIgR#oH3+0cNC8wWV3N8>E^FFEjMwjz*+pTwaa3A?n*l)Im+xQ=XEECDgu?M@|Q zb&DrHZ;8{@oNJ=VJX_dxQ-eXhV{D)P9m@fI9b^BsXm~zE;?vX9DSTQ;Y;W(#q1idf z#gj-v+Y%j!YU$=e_RTnZa2m0^fa$1h!y&rbd^xUIm$|i#6(918*R%+ExZ`sd!m+? zO35QjKTjfw_5EW=xQ^oc`76w|zPP1cb6buLLh&8(%PIZYwrEsv-%B{$E89hFwfO9FhvfF%9o)D6df>sBPumlA z!Ktj{Y~5ajq~5RqG_dze)+713=tOdHwx8dFy6E4zj2qI^6MI6H_bZx{}P`W`U4m zkIO* z`Ji7OE#X7tv+_AWw?;Lsoxitl?8(MzMNXWI$jhGvT!ey^hc5s6o4b(W>hkIrCS@el z)4e{Ap8yHq1J%Kt?;m8Oj-Nd=g!&M*dKWh|8jh)}9haK-M;B#=n}dP~I8~sWXuz)Tn-i*C5CbhD8ti^4+=FeQQUYX?wYL5mz+FfuleE|s1rpxXPdic3Ym59ShTg*=z=Xk9h+}T8j zJ7cRa=R(P%(_shKwyh%ELvC3SG1D{5ws9FfdHN{agl3F4s9`m_U;?OrV{|KY*B z{N&!IG;m)l@rv?1Z94wbS`+B00lc)9-VLI4lhw4;=WTjc&VD^TlgSIy!pwxeE6WMqX$%+ zkItCnyI+mS#Gp-{t`+1j-j2&wlKw|f5%EkNnKjGTUjpX{Y3#~C$fgcn>{MNxuStB5 z%iBd>cs3;8{K~l8yPuF750l!pI5Fqcd6*q&>suRDnLXqKw_*p&TvO}6Ckx03*|wZK z5tFs8Jvno7Tz>i1ni8?Eeq{ihiUq*~Qs6r&x&5%BE&RX!?Frf0O+e<1EkD76MdPKheqo2tD9#k*w7wNT& z?5kyzt*<)m?k53l2T>w&`n09ZZscPsKrx(sYFnCWZ%9`;w$jt%g>|gm4MGt&j86XC ztXazuz8YnHHzO{eNe011Iklk;2MPf$je{S+!RN-mVjYsPKa6_S2i3(SL$;P&8Zya= zfE6XGU+j^9Y&ebbZywx}xA*pSk5&zw5O*jy*b0uUx?RuX;5TI4=u^L7u5c-$e2T`R zlFMY(;h%1Q?r0cwby+N^hHUtJUOWg!1nB@uQ8zaiAcMPbGz0j1SEnvc5bGY?Xdiwy zXLP0k*+*jmymy}tsxo_VppOTiY7%{88B94~=fE`%1;R3jdvz!l1nFv)VGn~SeN!xW zO#_Hi1RHRAHi!ZwP`yxeqBvk|a_cFt7Dj+}Njeelx6n^csHuAvf#@$wyYi!@Jz0hf z+XeU3Z5xzW<{_+Q%i>^<5Q1E}*Z}tj@)w9VhWix5*W2a1c<`ReKzm&T6bp^5aJ`2E z?DeUs#b^=bf(59ia@Z=j^9A|ny@b>ba6oW9FCZbD3c17v;<{;S$>7UV@{9a|929!8 z|M7+d%SL&AJSH(@m>9~pHQ zBRTnT{~#{mp0wr2fJ=^zIF&c!ls#3)WT7GVpKQvPU-_&mq1>4H!3Q78#b?hcGA|jD zve(?fo$PAAOAVwfH^wfkm%;h?WCq;d7AlkyluRF_^bfA>qTEEn0w>f4M`^|9x&W%= z_rH5u&dyp;w(d!x=F*Ls93gPyd${~@FAH@P8oCMN9i zjjxUYEtCNLaqvhq;Se4==9Imyp4@}J9vm>rE3eI{7tHpRAMg4{e*Gr04jLcOH8i70I$lRQ~@y>M|Y7erRK^Lxh>ER31;6niL z_yZuU*JN@qB$Yb19~gjI47e~L3)58z0eQJWh=vfXK&gXGFN!}F)>ijA_zN2X4;O2h zTju`WX^@+@fh{F| zWFiiUh9XhzrXGqyUgmqnfg-;!jPfrCZh`?Ih+m^1+J}(chk@iqq40%$AhI1M^{gQ} zZR22*_bGxC;NhoKOgj#8G@Lk~IXyd!X_UqZdUom>47^4L^#pdJL7glaN+Z@5tO`N0?5aB z$}KlHv-0?UPTC+(Z74xrh{ZI>&?GD{022NAY58L$fHZaGZfl`DinGa@;Q`Mxb&b z5OPHgy{q?ux*p7L%gZmF2PYapWl)qi-+fW{CMUOJd-Bb0aud%3Srv- z$4J&YXOV-J>_cpl8CCd~5kMZ0|aOQRACfe5~xl%KqtkoBFOJ|Avh zM=oCo%XdD9q=$R^aH}QDafg^9M_gC3fLz5)SLcJi_}Y*hJz@vU$^tT50hvG~m5fk< z0&?d*>Nk`F=g+$3^yxvs*oMq6lq8j|$e{@wP&}|@O1VK0U4W0Y?ezH~sDV+c7|O_$ zoW$+P(J9W+c!4m=cwjnx`%Ms#({q$It!k!Kh0^u-NdXt4FU_%wcn}$gOXvvq4ZVhT z5OaaN%YnKU2fzo2d}v}o{`9ZkMzD&B*J0CPKPMi%5~Oj;*yE$a2x2RU$Prx~+M=O0 zJz`KRA0(mt;D&IR%!~}p)g&GL1%$xv*m_S5geZM+;=4&3Onl!G5R41RY7!9I>U1bU z%8i6pBiG7O-i4Hk>v(FI^HW9{m>ZT~R#Wm{pR7PO+?P_OsW;hHWG78m8{VAA%mt9T zY|OGE!xR98Q{3W!a%BW6NDqOK`_kzR%bt_{T{;X#b;pty$Rd}^XnzV#xJ%%kIHgYO z1|p?85FH8qa;>I;u!8cDyUgp2PJh6$ip++?U&TYm?o*B#T#-F zse1st;&a3DW0cW5V7-fNP;?kv*EcT@$~P_!LBvSozI7y9@Ao2c^n`5`R2dnF)42XDhnEvwx%RXy8|j{I zPd#(YB7gU5VL6F%>+xn$e)(ZqZY@+%UR!iW_=!1_yz&CBA<$B~Y>_vwp_JTc%E4|` zOf|FPN&(S40?y#eU!6t(sLPX;0v`SbYHX2nM?)f3&d1X+IojC~OY zH99mbxpWfQ(5AnJNN};z93X45TG6$ zq$(cphsj-OyV`QX8`D8ay5LiuXHN+guYRYrMeQ8vh~{>>bXOMFwS>451X+rv@BE#+ znQ5lvg`BK%DImSu;nI@HrQ-vl)~fWMZIY|vl0R@CoH*we2pX?*w19?SHku^i5K}T| zrgI_?44_;X9f{zzU9vAedBZ*s&)Hc^{=< zrw*>iVbGC=Dgqt15j1n)&u|zD;O-1KumuDv#Cw!S{Rw?Ms*khPWstYpU2MwAwS>M-010T)=avDk`vszmK*n|%IZM>m zQt9-l2iJ&$tOm7EeY~(EPnN;Syz+v0>Gy&_{_|hHC&!P>$pq9ZBkJL{u>ykmK&+q- zT80{4vn#tOkybVuP;Lnbdh+qDT_{bs_uzhfI@ed1S5PJV-q(+dvz6EG|9x<<2dTF1 zYTDn=a`%f2413h{tc1_WDgK2$k!$mL^^*d1B;R;BCSQBm2?4+$Hy6$F#`QEPmZsjL zfVU}Me1LBc@K!e&zfLzg)cFlZQ{Tvat!sX?B9R zbjZ-)fLy;3mr(?RW5+}4+i~{Hq&&KpmTV4z1qX=zE>9lqD_icSYXiRL0E~TjU{vls zS(V9Azx>uWXXKOnS=mILSFKvrALcU`0}}C?@qp%Jbezq8HpzfA^#_rq!DK&ti252F ziqnmn(QlA+yo);Cs{Xf|`}>g6qDnS38r;SJ>Dx`RZ3;3xA~az69LMyp-B&~q0Ccyq z9S20J#Z6A&Kn{a|+(+3HbGc!5=*Mbz6P8GG0FP}`QjA&|85~nY(5hNq{25=%4 zr&XQyQU|zU34=`I(0=ZS9|5K;+fWLk_-{J`Z>v_*-h(hQ(tgY%|HqB%GLM^_+b>8R z#H!taz=YC-KaZ{b4b}^C48-n6rN!hGq`|Jw@(Ri_FJ(0 z;K~(teb%86(KdsIR$(YpCWA?RZj3z0;H=u8nw6K3{TURwg@B$T4C-o%jjxvwP^;iL zB0z?wzK*9mtGz%|9=r}aa|coG5a~w2wfIr4(twE@K-pRlz~6IllLMbb7R;PK`q?K^ zp1dGA1d>|5Cf|rQ#9T-!!l0QJyIm{A0+I~W7PA9EttK4=*LJ3^9mVWGB{A8u8gvte z8JXB;_UI1va-pJ9I_)$&T zM-q6SS3kTV=T0A%*nn4!#nWw|gYvv&ds)yN72S{4Hqj^?!8Ol-Wdj|6GXf;#^4n4V z069BE{`s#ShoXh*;PIZs!JV#e)KMXH)Ko1QFU#p;W2i#P5(3Y;zP2Mdpp&~xRk^=f z2S3@9=g&Fh*S{Q<+3Ai3kRSd$1v&)WO&#bGCFn_%pPzrmEYss4oR`b;!QG@D_OJ!|AhwSZXS*xQDuispgvwY*nCQy^c`>uOVy7Y0=VNFY^kZ&C1 zZVo8NC5H|T%X=T*1Mg8+BP}-cQ62Ii>(&ZcnHrx$8MUcKMfI++@4ANrNM(wHne0lX zag+@tx5d$4jm!MiC{ubDDeWP{=liRJKRfBybtvpg9s&``^(m}(xq76Hn*V3@ocyOd zPb3RAz6>(c2*R_O10Ta{N9mVa6mSTr*0eyZ*LqOHfc`olBRTunWU59Gup;YL(gj&; z7Nvo#Ipq(EvEI@JW88zq{tJ!-Fko=lj@`GksN@V4l^AZYG(y3l300>}PalIm5In&2 zlc5^L^(OR&CfXq<5)KBnS?>Vl8F>^30)o}lhs$)}s{>WEa4$O?7e>%5Ws;JGqQu_^ zIcy2kSV;9``yeh}{Jh^e>6PDcuZSnU29mv{_n3V?Tw7B`z|5nJ`}qb4Vk8m>?+B2z zmjo#anUn#})S_xgM}AL#Cpb#MHuxK>%P%b?=nM`@cC{=~9Bev65_Od`1sOoF;OGdi z5xF!s0w$gC%#9XGk(#*AB9H=Q|8VC(w!lMnmoqYm1AG=L*080EAYc;j383y+6KY}u z0TY}d$k(;ISxMqxI#D7GlI+H_7v!#VaB~3{&C|Lvh_e17RK{TlFTBolTPy<|{Nlqq z;L=?3(#2y?+`RI1epT+?ek`X>9>z0l*B#%*P8KL|O$t)RpR2l8Yb#!qB?vN4QMy#v zQI2b7#p`>aUWI_5eiK~H*IvZ)y_Jyty^5@Zrr`bR@7Ah8_V)sLdT?f{I-S3sC`r8O zl8+vi02Gj%Jx2M>Z^q=A!xkmtfBaDi@3n+-*og8KpL4__UwzRpmk!(1*7JiqP5ERV z*D=ZG3Q8YJ%3pc;kQ|;i>%B4g54o-+4(giqqA`$QJq}itp@;ygSK2y@L1K}YGn7Y6VFaKpi%OV5j_+tPlG5FY+vgxYGe zj1N>6ALPMR1ffR@8ChL#gP>|?nZuqHx~Q{5*9fiywUK=5bCdELuN}rgh9ZE#GrtZ= zwANJe(u|X0v@#6zKza^Huf{G;lN?1*`tXzck_Oo`7#mdMCUQdD7Fh<7c5L>z?C)%9 zna~88OL?3f&Ge-y;GwTK=!5Fh#xD=bi)PJ$>(B(+!Rz9Cqd;=geuum;?1JR%mbq!r z@w~7WvyS#>>>c?p`5pO7s0ir|1dswqN7@tsrPNB`M%sO2CvGR;ay71rA+n-*I?EAm z6QH!)!_A9~jp+784l0tYP2;mxQ9d5^hE-`}Qys4z1F&w4;1&X7KX|amCN>n6>6u9x z9*LqX#DfNuH8FzwiOkNuS3%VCL8wT6lz&`+r>l8eb#OcteJ>0IbPJ>e%e_l9o+^V! zU?U(sYbjyVa&mlR1hu|P%cu^NLX+JjZ@qa}0*9s~d;5L)-!2(+kNDP;9Rw{7<{J9G zLb%PG^U48fy}N}#)8J%uf87dap=w-M&qhg*-M-0~)Np{x?J{I_8^~V+eqKWIIPfi$ z2i##*1?&E}22~E9V+tsgb8K{oqhiN4e)=W{K;pVQR^8UHk7wY`cwY7wP?l`vWfBDR z5h6A4YlCiZMYG@?gCJuO`L|OA6$;kCBW^uLkTi3nC6SC<=d7G2qZ*1c*u+4LKR+9Q zLg&z}o*tC0@k~vA^w-yAc63xuo}5J@waDVrEhO?Kxpe6`xJo@_!g2eJ>ayfbd1+c( zI)K{2^H6P=`)24yyK-e#GY4Rsa%m}p@%wuZlIq{W)g_5UMRrgVa-BjnVgX6zmNFaI@P^7D{(|5n!dzC5hxX1> zs94!1Sa#I!Y+=SjO9`zCY181Us64%$Xv^(8toLeaY-Ho?3~dFF;Cg! zyCA2IM&;)HRgjMrT?0a7wUsHUiV$>|wClBNAYoaTSa4hi(&%Aw=GYNgSlyNF^%a>u zJge>6v_xb(_QC!R{%#sFaT1va@?^WeFkR(tW{L`j3EPTH;+r6Dk+f@ck2mnfMP&$O z*$WZW+=IA2IHYXs3WkHy#CyKkEXcp#+LX5*u1P!fsU;@W8oW;uvNn?w%7{Z(hqBqN zn9hlnRuo2)HVQU@5^M+nRRqL=;2^Tat`1%zCrYnl0{K{yE!5z{ey7aZyvm)hJhb8( zHnj}u>TFXFe8lR?j%=)MNeXh`?sirm$k zkbNoHbJrF}E4lKdtc`nAm7E$B0)c>X5^nBi;xe>mQ%Ow9)gBV7)w15#yVoAcM~_ye z0>byODFNtimOCG>$)VW@f|gC2`$)XAo0;X^LuB3}IhR~oV&g;lH8+A6$$fg;RKTG* z!Dp5Cy0VAzrnFmEPRrr5ifhCyv6DVYm7u;LQLI8%wcvVlV3Z>v+?&J_Q!OxXXnnYE zoR@w0cptETU#HAR!THP~cuztch(QJQAN9*(s41Jso1hqh#*MPG+>xF8;No!jC-Jjq zpb~kJ6j?6mSO$K^WXLX`K^gCL^!KPcP-bmxm*p>i@sa%PFI|+GiI{Glc)GAA@%Vw9 zJv}8Ma9de?ZYO2((yElq8L4)G8o99oCFSyFU6yxix+0o;utvg8nHez4zx=IBa%zP8 z&C;^L9;`UZB&cU;NZ-eCojD1g1O*X|Ikd5~09hI#SKhsyk&Prx-Wu}S3tsuVD3xgN zc6GTTKYS-6+wqQee-DkAA*_c)cKZ5tMiKnu?%TR#22tMz%e5Gyp_YlMQeX0o1|t2aZEM@q+a}G8+P_ph`sNHK%~xysRpL2VuzihBG0 z!E{~YlB`S?5Vot6tZpYBP-H=l6UWUhqAMlJ+5R4-Vek?I2#9BZVqU>RI5|vt5-h(DvLPO}KS5CV zqbFM+-5SzO8~UbCP;)AKDI5lm_VRGQI8_x6cXA&OB~Gd}EZ5kp!>ydU@B#!ag9z8! z_6jHwi2%6O(C1R6FpOYxX<$(IU31!+$c8AOjs&p0xTFfv*u*GMfm^LmxgnFI7;HZ5 za$hq3>|Q`gzuZ7kiLNDhPK==C_NMg}JdH-j3}00r=mw49*=Tti2W<;# zrW0jt8`-!=!!DFe9APL|QNA9SC5z`S4^%BmIN~~-4oK(_5XFE;=iGY8^cEr^AoKZF z8Rbhr_p|qyfE@jO-VK(1cz^W4aUmggL2lCe^9Yjf04J#tWQ_QksfbAyxCdYuec1wRts09T3#}EOo-<$^`cn+dU z1Oc}x@4j~(!U2*66eE|{t=lpzc!rnr^OCnDqz%ENf$QJJ^YLh-D2wYA9c1L_hNV}N zzxC|2eD8}#AV#(3OMjiTqFN1OJq8xet zxu{wbY(fubTe8vGMXl@7RTys6-PHtJsIrv!N4RW;l6|1_-IU;$SQAra0{7a zN{9El$_?0Avdcg?B8m96?pA7a4O)KCY>4Q9c7}Dxx3rO*0%Rr}J_#cH)gvAm_xIFL zi8c_Apa%Tm+LrusaSK_uEasL~*Va-kv4PvL17YP7X?5mf{|l`Y`cbvNyV~XA#4Y#| z{CrMC8}J!u@Wo97_*lAKlPHHXV@9ra0qJ)vW|7mO0bPB|Nom?<>h=M(e{|`6*xufg zLN1H@W>DD7-Vz39a%(nR|9Z8k=1bWO;PyUBkaS*_msUa0=QIJRn;Tc#a`_zY6CL|# z;n3vP19RW%lL(=K(vKQ!36R(2wi<2OAu&x|o&(mPxkXZiuo!dBP zMIf0=5{?9P%KGE0_hbyE?9gBc#IQrRWLP~Qrfb`hm5kIO!`JX$O9CDRfnp~Pm9%Bn z&T$`t&p-a|896!bmaEs+pe9j4MLj^6f81%*6`BF$Of~#5E1!uJ1lbcVfKrsL!>`FFtWjM;D9x`2d?)ezVBq$@%2!3w? zHYX(zvdZMN1wo@Nd(h}P)jd0ln-Mds^+Fu5tWj&oF_2>|WT70$v9-mbl)#x>x&+b- zsAzs6iHp{ebEg85PIA$eRo1p^^7FUyk}Y#(qE(Ho&6b|7lHeKPjfvs;{2zYfjJ$AW zM6Takly`1qrHjKDK~`~EJ#y_ngUVIJ27NN? zZOggnn4%wDpaS^BVZ{tcW2eimWidw-q6m<~xW_uJjsNDpAI>hBw4=Gd9WoH{1Ko`v z;Mvr0DmFZzD2;te?N5;siD6zV{+j@ZQZ_PDOcfp!-YCcRedz5LE zqUqVZ4q0_&CnqnSoztc^%G>NsqCp?W5*DFc?tw7RZRf>>pi~EOzBiwiMgn3&zR@qU z!4U+Zb_|_@j9t$w(OECjU$=`uf}rbQ^0J}kLjVBjz7vnjIs&O1WzGP%QHoXfarJ0A zU#0y?69~qMB#A(T%(PeN$X%9LKx%h$em0SDfS(kLgN+;||PgTrc_;sVdvX^~s% z$Xb3`s&*tw1{nmn11M?>n?+gPYUtE-yJ?n_;Mo4BUwcN3&63=_vxlnD4IZ@b0x&k@ z6ZUM7xF>2OC)q&xh+HXa5HMB_MDDGjbaxx%@@cF5gRc(D{3k>v74HnCW_Q z?xv(rxbSO4NQ)aLv0a4{+QCQwHQ>^nZ}{d+swV8v37`@fgA@fsl3 zws#Y_xTp*BG|TGm(6-`a*Sl3EsV&G{Zu^jMVQZ~Xkdc9iw$i`-;dP1nLr~9#kkRYP zM-bhRu&!m3YP8isxl*V1idrAoz!!n@0g?Ol^AV_ECCJ~X-E9W>w;+3exe}Lh%c3UF zJ*Wb%P*90wsti<9XnMsCGn)yYpI&)Tm$X$L=z)!XYybo|s&*9}lm~4(?{l3@rLDV+ zdXRg)PVfm(3+Ot}#o$!0PT}D`JIIx&eeWN7VvzYS;`>($3E2)oJtVU5U{t-M4MwgjHfrfX zf1c3vsHC=c>4F0JzKt@V3`mHQog*DO+~^aVmaHKEbtI*$UOfz?9L9{JRC@DiQW6MU z4bZFe*LHCY9FWCras(nkl(w5ri+b>KKiLpyf=kn3neel5)25u(PO2vV@h{$#7f;Q} zOD`Qq3Ejizt*D9B)D%ebuuaQ8HP;96yOl$^WGsl_dGDfpY8W|dWzn*F3uvPbQRPc# zBl6#U{fsQ!TL2kb$Mr-|s@o-B$N-fA6#+>{poFFhC7C-sAjyL~-iuSpNMbivO0t&f z0zEb5pZ&%m`4Z&mYOyJQ@QZ}JezOSP4LTnLj4wYIL`4z6d9Ta+w@Y&4KFZWGP(I%O z2m;5~pAR6>8<0pkGQU#=ASi$$>8SrSeWyo<4033ciYnI{cf?$;(3#98@BcEb2mka1 zpG<++{1`#O%~=#OG-MkPU+P;;seQ70vf47YIlQZG~#ntP@0%=jk&#%$fyb) z#_#LzgyKkr$z)Juz;<&t%8n=bth^0^H-i$9b5fnkf%el_@%IJ%Y#$I}v9d1~NV0YW z;W}Q+4}y2p2M#EglJ9=$vdp*weOETF*Tv^yupwc~v5zySPxQNX$t%_BY6sy3O6rEZ zT51gr4r-ZaLb-Qjv?q^1c&?65iX8~f&?w4Z|KuIn9COH5F8yuYM@~>yLofzqAXT8> z`i@zNe?rTS8A{239YN+0NZSVo&{_iA0nOn#<7S7FcnAvOB+%*gheauKyaEYw zG}t%O8iKfCrimA>r3XRgP{bv(A&U(9x%=CU>eVWbR`=xU^(XT8zI<6`=O#c-mn0o0 z(Y_;dGvh#mC~IrrFTowv4P^xRCsH!z`~F3EZnL^i9GwWs>sL2qVMkEC@Y*8)18qHX%nL~yVgSUNc+Man zJSsrhf~si(A_hk}5a=N(xpn-A;hFVt2h@c*m9GEGr%RNkAhE#%^%Gt!BUsyIoxDx`v63-Nn+7y)Q0PF?)<16pZ3b%;KSd4(o_QV)KQmAj(Fq% zQ1|1T`#2D0wH;tz0HtMW2OQ$0blq+j<;%|x$*Vw3H=i!aB4G6^FFqr0emF0K112S# zzx>%DX#PgI`YIV5h7*#%By19m#``^W(8J4?7I zThQ_Wp`GK9BEjnCi!z8h`s{_H^4_&OxIi2jv;)3&H9NC&c6NMBijSYjQ6QcjkYXXc z_v3@Kk0`4DMiOZ6uXb^8D^@9?G|VNF;zY>^x)C*M2AIVv?r>Pj$$jNkf>3X=_Cx;w~iPWLi250JvV>AObSVGfo7%MnY2DC`oXNC2$vq zyeEwkK12Zut$miT)0rlA56^{E)AL9ms9oGO6y~Kh*K0xz!dlzU0*W7Ma1&KXMW*#m)hlj%b z;KTP#%TxOGy#3)r`RdE(W%BSaBS{@Skf;jiPf64Bxn__N1e8}UPT@MXQNko4gKx_mg4fF09?l4o8aOwngEMi&*+8m< Z|3B(bFvffUMuGqU002ovPDHLkV1j6o9i;#O diff --git a/dapp/src/components/Sidebar.tsx b/dapp/src/components/Sidebar.tsx new file mode 100644 index 000000000..2b154650d --- /dev/null +++ b/dapp/src/components/Sidebar.tsx @@ -0,0 +1,70 @@ +import React from "react" +import { + Box, + Card, + CardBody, + Flex, + useMultiStyleConfig, +} from "@chakra-ui/react" +import { useSidebar, useDocsDrawer } from "#/hooks" +import { TextSm } from "./shared/Typography" +import ButtonLink from "./shared/ButtonLink" + +const BUTTONS = [ + { label: "Docs", variant: "solid" }, + { label: "FAQ", colorScheme: "gold" }, + { label: "Token Contract", colorScheme: "gold" }, + { label: "Bridge Contract", colorScheme: "gold" }, +] + +const BENEFITS = [ + { label: "1x Rewards Boost", icon: undefined }, + { label: "1x Mystery Box", icon: undefined }, + { label: "1x Season Key", icon: undefined }, +] + +export default function Sidebar() { + const { isOpen } = useSidebar() + const { onOpen: openDocsDrawer } = useDocsDrawer() + const styles = useMultiStyleConfig("Sidebar") + + return ( + + + Rewards you’ll unlock + + + {BENEFITS.map(({ label }) => ( + + + {label} + {/* TODO: Add a correct icon */} + + + ))} + + + {BUTTONS.map(({ label, variant, colorScheme }) => ( + + {label} + + ))} + + + ) +} diff --git a/dapp/src/components/Sidebar/index.tsx b/dapp/src/components/Sidebar/index.tsx deleted file mode 100644 index 0b485b142..000000000 --- a/dapp/src/components/Sidebar/index.tsx +++ /dev/null @@ -1,84 +0,0 @@ -import React from "react" -import { - Box, - Icon, - useMultiStyleConfig, - Image, - Card, - CardBody, - CardHeader, - CardFooter, - HStack, - Link, -} from "@chakra-ui/react" -import RightSidebar from "#/assets/images/right-sidebar-bg.png" -import { useSidebar, useDocsDrawer } from "#/hooks" -import { ShieldPlusIcon } from "#/assets/icons" -import { TextMd, TextSm } from "../shared/Typography" -import ButtonLink from "../shared/ButtonLink" - -const readMoreEarnings = "https://#" - -const BUTTONS = [ - { label: "FAQ" }, - { label: "Token Contract" }, - { label: "Bridge Contract" }, -] - -export default function Sidebar() { - const { isOpen } = useSidebar() - const { onOpen: openDocsDrawer } = useDocsDrawer() - const styles = useMultiStyleConfig("Sidebar") - - return ( - - - - Docs - - - - - - - - - Maximize your earnings by using tBTC to deposit and redeem BTC in - DeFi! - - - - - - Read more - - - - - - - How we calculate fees - - - - - - Fees is software empowered by the Threshold DAO. - - - - - {BUTTONS.map(({ label }) => ( - - {label} - - ))} - - - ) -} From 4d9e3db04f89db28fbd283e1a40678e0699a3b1a Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 8 May 2024 12:35:40 +0200 Subject: [PATCH 04/34] Update global styles for modal component --- .../ActiveStakingStep/StakeFormModal/index.tsx | 2 +- .../ActiveUnstakingStep/UnstakeFormModal/index.tsx | 2 +- .../TransactionModal/MissingAccountModal.tsx | 4 ++-- dapp/src/theme/Modal.ts | 12 +++++++++--- 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx index 7e627e73d..4e7d0d443 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx @@ -27,7 +27,7 @@ function StakeFormModal({ minTokenAmount={minDepositAmount} maxTokenAmount={tokenBalance} /> - Stake + Stake ) } diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx index 981a43b62..7cdbaad15 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx @@ -47,7 +47,7 @@ function UnstakeFormModal({ - Unstake + Unstake ) } diff --git a/dapp/src/components/TransactionModal/MissingAccountModal.tsx b/dapp/src/components/TransactionModal/MissingAccountModal.tsx index d93f7287e..1215371a3 100644 --- a/dapp/src/components/TransactionModal/MissingAccountModal.tsx +++ b/dapp/src/components/TransactionModal/MissingAccountModal.tsx @@ -36,7 +36,7 @@ export default function MissingAccountModal({ <> {name} account not installed - + @@ -53,7 +53,7 @@ export default function MissingAccountModal({ - + diff --git a/dapp/src/theme/Modal.ts b/dapp/src/theme/Modal.ts index 8436cc293..33a6ac658 100644 --- a/dapp/src/theme/Modal.ts +++ b/dapp/src/theme/Modal.ts @@ -2,7 +2,6 @@ import { modalAnatomy as parts } from "@chakra-ui/anatomy" import { createMultiStyleConfigHelpers, defineStyle } from "@chakra-ui/react" const baseStyleDialog = defineStyle({ - p: 4, borderWidth: "var(--chakra-space-modal_borderWidth)", boxShadow: "none", borderColor: "white", @@ -31,11 +30,13 @@ const baseStyleOverlay = defineStyle({ }) const baseStyleHeader = defineStyle({ - textAlign: "center", + textAlign: "left", fontSize: "lg", lineHeight: "lg", fontWeight: "bold", - py: 6, + pt: 10, + px: 10, + pb: 8, }) const baseStyleBody = defineStyle({ @@ -45,11 +46,16 @@ const baseStyleBody = defineStyle({ flexDirection: "column", alignItems: "center", gap: 6, + pt: 0, + px: 8, + pb: 10, }) const baseStyleFooter = defineStyle({ flexDirection: "column", gap: 6, + px: 8, + pb: 10, }) const multiStyleConfig = createMultiStyleConfigHelpers(parts.keys) From 69e0169d6bcd87d9bd9924f1bdff0a03fd058dfe Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 8 May 2024 13:25:44 +0200 Subject: [PATCH 05/34] Update a stake details in form modal --- .../ActiveStakingStep/StakeFormModal/StakeDetails.tsx | 1 - dapp/src/components/shared/FeesDetails/FeesItem.tsx | 10 ++-------- dapp/src/components/shared/FeesDetails/index.tsx | 2 +- 3 files changed, 3 insertions(+), 10 deletions(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/StakeDetails.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/StakeDetails.tsx index 6fc73f611..416d691b0 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/StakeDetails.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/StakeDetails.tsx @@ -50,7 +50,6 @@ function StakeDetails({ /> & - Pick + Pick function FeesDetailsAmountItem({ label, - sublabel, tooltip, from, to, }: FeesDetailsItemAmountItemProps) { return ( - + Date: Wed, 8 May 2024 13:26:35 +0200 Subject: [PATCH 06/34] Show currency conversion in form --- .../StakeFormModal/index.tsx | 1 + .../TokenAmountForm/TokenAmountFormBase.tsx | 3 ++ .../shared/TokenBalanceInput/index.tsx | 28 +++++++++++++------ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx index 4e7d0d443..24c590b9d 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx @@ -18,6 +18,7 @@ function StakeFormModal({ {children} diff --git a/dapp/src/components/shared/TokenBalanceInput/index.tsx b/dapp/src/components/shared/TokenBalanceInput/index.tsx index 2f3a89ff8..3c198d369 100644 --- a/dapp/src/components/shared/TokenBalanceInput/index.tsx +++ b/dapp/src/components/shared/TokenBalanceInput/index.tsx @@ -19,6 +19,7 @@ import { } from "#/utils" import { CurrencyType } from "#/types" import { IconInfoCircle } from "@tabler/icons-react" +import { useCurrencyConversion } from "#/hooks" import NumberFormatInput, { NumberFormatInputValues, } from "../NumberFormatInput" @@ -58,18 +59,25 @@ function HelperErrorText({ } type FiatCurrencyBalanceProps = { - fiatAmount?: string - fiatCurrency?: CurrencyType + amount: bigint + currency: CurrencyType + fiatCurrency: CurrencyType } function FiatCurrencyBalance({ - fiatAmount, + amount, + currency, fiatCurrency, }: FiatCurrencyBalanceProps) { const styles = useMultiStyleConfig("Form") const { fontWeight } = styles.helperText - if (fiatAmount && fiatCurrency) { + const fiatAmount = useCurrencyConversion({ + from: { amount, currency }, + to: { currency: fiatCurrency }, + }) + + if (fiatAmount !== undefined) { return ( void } & InputProps & - HelperErrorTextProps & - FiatCurrencyBalanceProps + HelperErrorTextProps export default function TokenBalanceInput({ amount, @@ -105,7 +113,6 @@ export default function TokenBalanceInput({ errorMsgText, helperText, hasError = false, - fiatAmount, fiatCurrency, ...inputProps }: TokenBalanceInputProps) { @@ -118,6 +125,8 @@ export default function TokenBalanceInput({ valueRef.current = value ? userAmountToBigInt(value, decimals) : undefined } + const showConversionBalance = amount !== undefined && !!fiatCurrency + return ( @@ -163,10 +172,11 @@ export default function TokenBalanceInput({ errorMsgText={errorMsgText} hasError={hasError} /> - {!hasError && !helperText && ( + {!hasError && !helperText && showConversionBalance && ( From 9dad5b3b5c062afffa1940a88ac68f7a384fe8b7 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 8 May 2024 13:51:30 +0200 Subject: [PATCH 07/34] Update `SuccessModal` component --- .../TransactionModal/SuccessModal.tsx | 75 ++++++++++++++----- 1 file changed, 56 insertions(+), 19 deletions(-) diff --git a/dapp/src/components/TransactionModal/SuccessModal.tsx b/dapp/src/components/TransactionModal/SuccessModal.tsx index 5ea882804..2c2a2c5de 100644 --- a/dapp/src/components/TransactionModal/SuccessModal.tsx +++ b/dapp/src/components/TransactionModal/SuccessModal.tsx @@ -2,6 +2,7 @@ import React from "react" import { Box, Button, + HStack, ModalBody, ModalFooter, ModalHeader, @@ -11,11 +12,53 @@ import { LoadingSpinnerSuccessIcon } from "#/assets/icons" import { useModalFlowContext } from "#/hooks" import { CurrencyBalanceWithConversion } from "#/components/shared/CurrencyBalanceWithConversion" import { ACTION_FLOW_TYPES, ActionFlowType, TokenAmount } from "#/types" -import { ReceiveSTBTCAlert } from "#/components/shared/alerts" +import { TextMd } from "../shared/Typography" +import Spinner from "../shared/Spinner" +import BlockExplorerLink from "../shared/BlockExplorerLink" -const HEADER = { - [ACTION_FLOW_TYPES.STAKE]: "Staking successful!", - [ACTION_FLOW_TYPES.UNSTAKE]: "Unstaking successful!", +const CONTENT: Record< + ActionFlowType, + { + header: string + renderBody: (tokenAmount: TokenAmount) => React.ReactNode + footer: string + } +> = { + [ACTION_FLOW_TYPES.STAKE]: { + header: "Deposit received", + renderBody: (tokenAmount) => ( + <> + + + + {/* TODO: Use correct tx hash and update styles */} + + + ), + footer: "The staking will continue in the background", + }, + [ACTION_FLOW_TYPES.UNSTAKE]: { + header: "Withdrawal initiated", + renderBody: () => ( + + You’ll receive your funds once the unstaking process is completed. + Follow the progress in your dashboard. + + ), + footer: "The unstaking will continue in the background", + }, } type SuccessModalProps = { @@ -26,31 +69,25 @@ type SuccessModalProps = { export default function SuccessModal({ type, tokenAmount }: SuccessModalProps) { const { onClose } = useModalFlowContext() + const { header, footer, renderBody } = CONTENT[type] + return ( <> - {HEADER[type]} + {header} - - - + {renderBody(tokenAmount)} - + + + + {footer} + ) From 10729f8b3155254ff786d15df79d32be7db4a497 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 8 May 2024 14:09:01 +0200 Subject: [PATCH 08/34] Update styles for error modals --- .../ActiveStakingStep/StakingErrorModal/RetryModal.tsx | 8 +++++--- .../StakingErrorModal/ServerErrorModal.tsx | 7 ++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/RetryModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/RetryModal.tsx index b1a33e03e..c1c144eae 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/RetryModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/RetryModal.tsx @@ -41,8 +41,10 @@ export default function RetryModal({ retry }: { retry: () => void }) { return ( <> - Oops! There was an error. - + + Oops! There was an error. + + @@ -66,7 +68,7 @@ export default function RetryModal({ retry }: { retry: () => void }) { - + diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/ServerErrorModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/ServerErrorModal.tsx index ff4cc6b5e..179c0d62c 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/ServerErrorModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/ServerErrorModal.tsx @@ -32,10 +32,10 @@ export default function ServerErrorModal({ return ( <> - + We're currently facing system issues. - + @@ -56,7 +56,6 @@ export default function ServerErrorModal({ From 879ffcc77e125e49d7d69550872b79de4a986f30 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 9 May 2024 09:11:03 +0200 Subject: [PATCH 09/34] Update value of delay for triggering Bitcoin transaction --- .../TransactionModal/ActiveStakingStep/DepositBTCModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index 17c1dfe2d..1878b03c3 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -18,7 +18,7 @@ import { TextMd } from "#/components/shared/Typography" import { CardAlert } from "#/components/shared/alerts" import { ONE_SEC_IN_MILLISECONDS } from "#/constants" -const DELAY = ONE_SEC_IN_MILLISECONDS * 3 +const DELAY = ONE_SEC_IN_MILLISECONDS * 2 const TOAST_ID = TOAST_IDS.DEPOSIT_TRANSACTION_ERROR const TOAST = TOASTS[TOAST_ID] From 0c49175839b96ef7e306676237861ede3b87fc34 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 9 May 2024 10:47:54 +0200 Subject: [PATCH 10/34] Move data from context to redux for action flow --- .../TransactionModal/ActiveFlowStep.tsx | 11 ++++- .../ActiveStakingStep/DepositBTCModal.tsx | 9 ++-- .../StakingErrorModal/index.tsx | 10 ++--- .../ActiveUnstakingStep/SignMessageModal.tsx | 11 +++-- .../TransactionModal/ModalContentWrapper.tsx | 6 ++- .../src/components/TransactionModal/index.tsx | 30 ++++---------- dapp/src/contexts/ModalFlowContext.tsx | 6 --- dapp/src/hooks/store/index.ts | 3 ++ .../hooks/store/useActionFlowActiveStep.ts | 6 +++ dapp/src/hooks/store/useActionFlowStatus.ts | 6 +++ dapp/src/hooks/store/useActionFlowType.ts | 6 +++ .../store/action-flow/actionFlowSelectors.ts | 11 +++++ dapp/src/store/action-flow/actionFlowSlice.ts | 41 +++++++++++++++++++ dapp/src/store/action-flow/index.ts | 2 + dapp/src/store/reducer.ts | 2 + 15 files changed, 110 insertions(+), 50 deletions(-) create mode 100644 dapp/src/hooks/store/useActionFlowActiveStep.ts create mode 100644 dapp/src/hooks/store/useActionFlowStatus.ts create mode 100644 dapp/src/hooks/store/useActionFlowType.ts create mode 100644 dapp/src/store/action-flow/actionFlowSelectors.ts create mode 100644 dapp/src/store/action-flow/actionFlowSlice.ts create mode 100644 dapp/src/store/action-flow/index.ts diff --git a/dapp/src/components/TransactionModal/ActiveFlowStep.tsx b/dapp/src/components/TransactionModal/ActiveFlowStep.tsx index 0e27f2f74..5465363af 100644 --- a/dapp/src/components/TransactionModal/ActiveFlowStep.tsx +++ b/dapp/src/components/TransactionModal/ActiveFlowStep.tsx @@ -1,5 +1,9 @@ import React, { ReactElement, useEffect } from "react" -import { useModalFlowContext } from "#/hooks" +import { + useModalFlowContext, + useActionFlowActiveStep, + useActionFlowType, +} from "#/hooks" import { ACTION_FLOW_STEPS_TYPES, ActionFlowType, @@ -18,7 +22,10 @@ const FLOW: Record ReactElement> = { } export function ActiveFlowStep() { - const { activeStep, type, onClose } = useModalFlowContext() + const { onClose } = useModalFlowContext() + const activeStep = useActionFlowActiveStep() + const type = useActionFlowType() + const numberOfSteps = Object.keys(ACTION_FLOW_STEPS_TYPES[type]).length useEffect(() => { diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index 1878b03c3..0940b2f63 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -3,7 +3,6 @@ import { useDepositBTCTransaction, useDepositTelemetry, useExecuteFunction, - useModalFlowContext, useStakeFlowContext, useToast, useTransactionContext, @@ -17,6 +16,7 @@ import Spinner from "#/components/shared/Spinner" import { TextMd } from "#/components/shared/Typography" import { CardAlert } from "#/components/shared/alerts" import { ONE_SEC_IN_MILLISECONDS } from "#/constants" +import { setStatus } from "#/store/action-flow" const DELAY = ONE_SEC_IN_MILLISECONDS * 2 const TOAST_ID = TOAST_IDS.DEPOSIT_TRANSACTION_ERROR @@ -25,19 +25,18 @@ const TOAST = TOASTS[TOAST_ID] export default function DepositBTCModal() { const { ethAccount } = useWalletContext() const { tokenAmount } = useTransactionContext() - const { setStatus } = useModalFlowContext() const { btcAddress, depositReceipt, stake } = useStakeFlowContext() const depositTelemetry = useDepositTelemetry() const { closeToast, openToast } = useToast() const onStakeBTCSuccess = useCallback( () => setStatus(PROCESS_STATUSES.SUCCEEDED), - [setStatus], + [], ) const onStakeBTCError = useCallback(() => { setStatus(PROCESS_STATUSES.FAILED) - }, [setStatus]) + }, []) const handleStake = useExecuteFunction( stake, @@ -50,7 +49,7 @@ export default function DepositBTCModal() { setStatus(PROCESS_STATUSES.LOADING) logPromiseFailure(handleStake()) - }, [closeToast, setStatus, handleStake]) + }, [closeToast, handleStake]) const showError = useCallback(() => { openToast({ diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx index 6ae4e1276..1473e50ef 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx @@ -1,17 +1,13 @@ import React, { useCallback, useState } from "react" -import { - useExecuteFunction, - useModalFlowContext, - useStakeFlowContext, -} from "#/hooks" +import { useExecuteFunction, useStakeFlowContext } from "#/hooks" import { PROCESS_STATUSES } from "#/types" import { logPromiseFailure } from "#/utils" +import { setStatus } from "#/store/action-flow" import ServerErrorModal from "./ServerErrorModal" import RetryModal from "./RetryModal" import LoadingModal from "../../LoadingModal" export default function StakingErrorModal() { - const { setStatus } = useModalFlowContext() const { stake } = useStakeFlowContext() const [isLoading, setIsLoading] = useState(false) @@ -19,7 +15,7 @@ export default function StakingErrorModal() { const onStakeBTCSuccess = useCallback( () => setStatus(PROCESS_STATUSES.SUCCEEDED), - [setStatus], + [], ) const onStakeBTCError = useCallback(() => setIsServerError(true), []) diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx index e46359396..e05bc5bec 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx @@ -1,21 +1,20 @@ import React, { useCallback } from "react" -import { useExecuteFunction, useModalFlowContext } from "#/hooks" +import { useExecuteFunction } from "#/hooks" import { PROCESS_STATUSES } from "#/types" import { Button, ModalBody, ModalFooter, ModalHeader } from "@chakra-ui/react" import { TextMd } from "#/components/shared/Typography" import { logPromiseFailure } from "#/utils" +import { setStatus } from "#/store/action-flow" export default function SignMessageModal() { - const { setStatus } = useModalFlowContext() - const onSignMessageSuccess = useCallback(() => { setStatus(PROCESS_STATUSES.SUCCEEDED) - }, [setStatus]) + }, []) // TODO: After a failed attempt, we should display the message const onSignMessageError = useCallback(() => { setStatus(PROCESS_STATUSES.FAILED) - }, [setStatus]) + }, []) const handleSignMessage = useExecuteFunction( // TODO: Use a correct function from the SDK @@ -31,7 +30,7 @@ export default function SignMessageModal() { setTimeout(() => { logPromiseFailure(handleSignMessage()) }, 5000) - }, [setStatus, handleSignMessage]) + }, [handleSignMessage]) return ( <> diff --git a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx index 776b36ec3..914acf999 100644 --- a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx +++ b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx @@ -1,6 +1,7 @@ import React from "react" import { - useModalFlowContext, + useActionFlowStatus, + useActionFlowType, useRequestBitcoinAccount, useRequestEthereumAccount, useTransactionContext, @@ -23,7 +24,8 @@ export default function ModalContentWrapper({ const { btcAccount, ethAccount } = useWalletContext() const { requestAccount: requestBitcoinAccount } = useRequestBitcoinAccount() const { requestAccount: requestEthereumAccount } = useRequestEthereumAccount() - const { type, status } = useModalFlowContext() + const status = useActionFlowStatus() + const type = useActionFlowType() const { tokenAmount } = useTransactionContext() if (!btcAccount || !isSupportedBTCAddressType(btcAccount.address)) diff --git a/dapp/src/components/TransactionModal/index.tsx b/dapp/src/components/TransactionModal/index.tsx index 545417ef3..803712f78 100644 --- a/dapp/src/components/TransactionModal/index.tsx +++ b/dapp/src/components/TransactionModal/index.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useEffect, useMemo, useState } from "react" +import React, { useEffect, useMemo } from "react" import { ModalFlowContext, ModalFlowContextValue, @@ -6,14 +6,13 @@ import { TransactionContextProvider, } from "#/contexts" import { useSidebar } from "#/hooks" -import { ActionFlowType, PROCESS_STATUSES, ProcessStatus } from "#/types" +import { ActionFlowType } from "#/types" import { ModalCloseButton } from "@chakra-ui/react" +import { resetState, setType } from "#/store/action-flow" import ModalBase from "../shared/ModalBase" import ModalContentWrapper from "./ModalContentWrapper" import { ActiveFlowStep } from "./ActiveFlowStep" -const DEFAULT_ACTIVE_STEP = 1 - type TransactionModalProps = { type: ActionFlowType isOpen: boolean @@ -27,17 +26,9 @@ export default function TransactionModal({ }: TransactionModalProps) { const { onOpen: openSideBar, onClose: closeSidebar } = useSidebar() - const [activeStep, setActiveStep] = useState(DEFAULT_ACTIVE_STEP) - const [status, setStatus] = useState(PROCESS_STATUSES.IDLE) - - const handleGoNext = useCallback(() => { - setActiveStep((prevStep) => prevStep + 1) - }, []) - - const resetState = useCallback(() => { - setActiveStep(DEFAULT_ACTIVE_STEP) - setStatus(PROCESS_STATUSES.IDLE) - }, [setStatus]) + useEffect(() => { + setType(type) + }, [type]) useEffect(() => { let timeout: NodeJS.Timeout @@ -49,18 +40,13 @@ export default function TransactionModal({ timeout = setTimeout(resetState, 100) } return () => clearTimeout(timeout) - }, [isOpen, resetState, openSideBar, closeSidebar]) + }, [isOpen, openSideBar, closeSidebar]) const contextValue: ModalFlowContextValue = useMemo( () => ({ - type, - activeStep, - status, - setStatus, onClose, - goNext: handleGoNext, }), - [type, activeStep, status, onClose, handleGoNext], + [onClose], ) return ( diff --git a/dapp/src/contexts/ModalFlowContext.tsx b/dapp/src/contexts/ModalFlowContext.tsx index 87f3754c9..df0d9363f 100644 --- a/dapp/src/contexts/ModalFlowContext.tsx +++ b/dapp/src/contexts/ModalFlowContext.tsx @@ -1,13 +1,7 @@ -import { ActionFlowType, ProcessStatus } from "#/types" import { createContext } from "react" export type ModalFlowContextValue = { - type: ActionFlowType - activeStep: number - status: ProcessStatus onClose: () => void - goNext: () => void - setStatus: React.Dispatch> } export const ModalFlowContext = createContext< diff --git a/dapp/src/hooks/store/index.ts b/dapp/src/hooks/store/index.ts index bdad282f8..9e114aada 100644 --- a/dapp/src/hooks/store/index.ts +++ b/dapp/src/hooks/store/index.ts @@ -3,3 +3,6 @@ export * from "./useAppSelector" export * from "./useEstimatedBTCBalance" export * from "./useSharesBalance" export * from "./useMinDepositAmount" +export * from "./useActionFlowType" +export * from "./useActionFlowStatus" +export * from "./useActionFlowActiveStep" diff --git a/dapp/src/hooks/store/useActionFlowActiveStep.ts b/dapp/src/hooks/store/useActionFlowActiveStep.ts new file mode 100644 index 000000000..f59e88bf4 --- /dev/null +++ b/dapp/src/hooks/store/useActionFlowActiveStep.ts @@ -0,0 +1,6 @@ +import { selectActionFlowActiveStep } from "#/store/action-flow" +import { useAppSelector } from "./useAppSelector" + +export function useActionFlowActiveStep() { + return useAppSelector(selectActionFlowActiveStep) +} diff --git a/dapp/src/hooks/store/useActionFlowStatus.ts b/dapp/src/hooks/store/useActionFlowStatus.ts new file mode 100644 index 000000000..5fb18915d --- /dev/null +++ b/dapp/src/hooks/store/useActionFlowStatus.ts @@ -0,0 +1,6 @@ +import { selectActionFlowStatus } from "#/store/action-flow" +import { useAppSelector } from "./useAppSelector" + +export function useActionFlowStatus() { + return useAppSelector(selectActionFlowStatus) +} diff --git a/dapp/src/hooks/store/useActionFlowType.ts b/dapp/src/hooks/store/useActionFlowType.ts new file mode 100644 index 000000000..c2b0d3c65 --- /dev/null +++ b/dapp/src/hooks/store/useActionFlowType.ts @@ -0,0 +1,6 @@ +import { selectActionFlowType } from "#/store/action-flow" +import { useAppSelector } from "./useAppSelector" + +export function useActionFlowType() { + return useAppSelector(selectActionFlowType) +} diff --git a/dapp/src/store/action-flow/actionFlowSelectors.ts b/dapp/src/store/action-flow/actionFlowSelectors.ts new file mode 100644 index 000000000..6de0a44bb --- /dev/null +++ b/dapp/src/store/action-flow/actionFlowSelectors.ts @@ -0,0 +1,11 @@ +import { ActionFlowType, ProcessStatus } from "#/types" +import { RootState } from ".." + +export const selectActionFlowType = (state: RootState): ActionFlowType => + state.actionFlow.type + +export const selectActionFlowActiveStep = (state: RootState): number => + state.actionFlow.activeStep + +export const selectActionFlowStatus = (state: RootState): ProcessStatus => + state.actionFlow.status diff --git a/dapp/src/store/action-flow/actionFlowSlice.ts b/dapp/src/store/action-flow/actionFlowSlice.ts new file mode 100644 index 000000000..6a3b8a721 --- /dev/null +++ b/dapp/src/store/action-flow/actionFlowSlice.ts @@ -0,0 +1,41 @@ +import { ActionFlowType, PROCESS_STATUSES, ProcessStatus } from "#/types" +import { PayloadAction, createSlice } from "@reduxjs/toolkit" + +type ActionFlowState = { + type: ActionFlowType + activeStep: number + status: ProcessStatus +} + +const initialState: ActionFlowState = { + type: "stake", + activeStep: 1, + status: PROCESS_STATUSES.IDLE, +} + +export const actionFlowSlice = createSlice({ + name: "action-flow", + initialState, + reducers: { + setType(state, action: PayloadAction) { + state.type = action.payload + }, + setActiveStep(state, action: PayloadAction) { + state.activeStep = action.payload + }, + setStatus(state, action: PayloadAction) { + state.status = action.payload + }, + goNextStep(state) { + state.activeStep += 1 + }, + resetState(state) { + state.type = initialState.type + state.activeStep = initialState.activeStep + state.status = initialState.status + }, + }, +}) + +export const { setType, setStatus, goNextStep, resetState } = + actionFlowSlice.actions diff --git a/dapp/src/store/action-flow/index.ts b/dapp/src/store/action-flow/index.ts new file mode 100644 index 000000000..261659e2f --- /dev/null +++ b/dapp/src/store/action-flow/index.ts @@ -0,0 +1,2 @@ +export * from "./actionFlowSlice" +export * from "./actionFlowSelectors" diff --git a/dapp/src/store/reducer.ts b/dapp/src/store/reducer.ts index a49f551e8..6ed0155e2 100644 --- a/dapp/src/store/reducer.ts +++ b/dapp/src/store/reducer.ts @@ -1,6 +1,8 @@ import { combineReducers } from "@reduxjs/toolkit" import { btcSlice } from "./btc/btcSlice" +import { actionFlowSlice } from "./action-flow/actionFlowSlice" export const reducer = combineReducers({ btc: btcSlice.reducer, + actionFlow: actionFlowSlice.reducer, }) From a38eb0fb3318ac99b3935317ea11c77cf49a76cc Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 9 May 2024 12:53:14 +0200 Subject: [PATCH 11/34] Simplifying the logic for opening modals Modal is a global component and should be rendered on the top of the components tree. It shouldn't be rendered from the `PositionDetails` level. --- dapp/src/DApp.tsx | 2 + dapp/src/components/ModalRoot/index.tsx | 19 ++++++ .../components/ModalRoot/withBaseModal.tsx | 28 ++++++++ .../TransactionModal/ActiveFlowStep.tsx | 12 ++-- .../StakingErrorModal/ServerErrorModal.tsx | 2 +- .../TransactionModal/SuccessModal.tsx | 6 +- .../src/components/TransactionModal/index.tsx | 68 ++++++------------- .../src/components/shared/ModalBase/index.tsx | 13 ---- dapp/src/contexts/ModalFlowContext.tsx | 9 --- dapp/src/contexts/index.tsx | 1 - dapp/src/hooks/index.ts | 2 +- dapp/src/hooks/useModal.ts | 27 ++++++++ dapp/src/hooks/useModalFlowContext.ts | 14 ---- .../pages/OverviewPage/PositionDetails.tsx | 31 ++------- dapp/src/store/modal/index.ts | 2 + dapp/src/store/modal/modalSelectors.ts | 8 +++ dapp/src/store/modal/modalSlice.ts | 32 +++++++++ dapp/src/store/reducer.ts | 2 + dapp/src/types/index.ts | 1 + dapp/src/types/modal.ts | 12 ++++ 20 files changed, 169 insertions(+), 122 deletions(-) create mode 100644 dapp/src/components/ModalRoot/index.tsx create mode 100644 dapp/src/components/ModalRoot/withBaseModal.tsx delete mode 100644 dapp/src/components/shared/ModalBase/index.tsx delete mode 100644 dapp/src/contexts/ModalFlowContext.tsx create mode 100644 dapp/src/hooks/useModal.ts delete mode 100644 dapp/src/hooks/useModalFlowContext.ts create mode 100644 dapp/src/store/modal/index.ts create mode 100644 dapp/src/store/modal/modalSelectors.ts create mode 100644 dapp/src/store/modal/modalSlice.ts create mode 100644 dapp/src/types/modal.ts diff --git a/dapp/src/DApp.tsx b/dapp/src/DApp.tsx index 71db1bc16..97e513488 100644 --- a/dapp/src/DApp.tsx +++ b/dapp/src/DApp.tsx @@ -17,6 +17,7 @@ import Sidebar from "./components/Sidebar" import DocsDrawer from "./components/DocsDrawer" import GlobalStyles from "./components/GlobalStyles" import { router } from "./router" +import ModalRoot from "./components/ModalRoot" function DApp() { useInitApp() @@ -29,6 +30,7 @@ function DApp() { + ) } diff --git a/dapp/src/components/ModalRoot/index.tsx b/dapp/src/components/ModalRoot/index.tsx new file mode 100644 index 000000000..22e6187f5 --- /dev/null +++ b/dapp/src/components/ModalRoot/index.tsx @@ -0,0 +1,19 @@ +import React, { ElementType } from "react" +import { useModal } from "#/hooks" +import { ModalType } from "#/types" +import TransactionModal from "../TransactionModal" + +const MODALS: Record = { + STAKE: TransactionModal, + UNSTAKE: TransactionModal, +} as const + +export default function ModalRoot() { + const { modalType, modalProps, closeModal } = useModal() + + if (!modalType) { + return null + } + const SpecificModal = MODALS[modalType] + return +} diff --git a/dapp/src/components/ModalRoot/withBaseModal.tsx b/dapp/src/components/ModalRoot/withBaseModal.tsx new file mode 100644 index 000000000..f0c24a99f --- /dev/null +++ b/dapp/src/components/ModalRoot/withBaseModal.tsx @@ -0,0 +1,28 @@ +import React, { ComponentType } from "react" +import { Modal, ModalContent, ModalOverlay } from "@chakra-ui/react" +import { BaseModalProps } from "#/types" + +export const MODAL_BASE_SIZE = "lg" + +function withBaseModal( + WrappedModalContent: ComponentType, +) { + return function ModalBase(props: T) { + const { closeModal } = props + return ( + + + + + + + ) + } +} + +export default withBaseModal diff --git a/dapp/src/components/TransactionModal/ActiveFlowStep.tsx b/dapp/src/components/TransactionModal/ActiveFlowStep.tsx index 5465363af..bf8e5eafa 100644 --- a/dapp/src/components/TransactionModal/ActiveFlowStep.tsx +++ b/dapp/src/components/TransactionModal/ActiveFlowStep.tsx @@ -1,9 +1,5 @@ import React, { ReactElement, useEffect } from "react" -import { - useModalFlowContext, - useActionFlowActiveStep, - useActionFlowType, -} from "#/hooks" +import { useActionFlowActiveStep, useActionFlowType, useModal } from "#/hooks" import { ACTION_FLOW_STEPS_TYPES, ActionFlowType, @@ -22,7 +18,7 @@ const FLOW: Record ReactElement> = { } export function ActiveFlowStep() { - const { onClose } = useModalFlowContext() + const { closeModal } = useModal() const activeStep = useActionFlowActiveStep() const type = useActionFlowType() @@ -30,9 +26,9 @@ export function ActiveFlowStep() { useEffect(() => { if (activeStep > numberOfSteps) { - onClose() + closeModal() } - }, [activeStep, numberOfSteps, onClose]) + }, [activeStep, closeModal, numberOfSteps]) return FLOW[type](activeStep) } diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/ServerErrorModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/ServerErrorModal.tsx index 179c0d62c..e83409338 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/ServerErrorModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/ServerErrorModal.tsx @@ -15,7 +15,7 @@ import { CableWithPlugIcon, Info } from "#/assets/icons" import { TextMd } from "#/components/shared/Typography" import { EXTERNAL_HREF } from "#/constants" import IconWrapper from "#/components/shared/IconWrapper" -import { MODAL_BASE_SIZE } from "#/components/shared/ModalBase" +import { MODAL_BASE_SIZE } from "#/components/ModalRoot/withBaseModal" import { IconBrandDiscordFilled, IconReload, diff --git a/dapp/src/components/TransactionModal/SuccessModal.tsx b/dapp/src/components/TransactionModal/SuccessModal.tsx index 2c2a2c5de..3e873612a 100644 --- a/dapp/src/components/TransactionModal/SuccessModal.tsx +++ b/dapp/src/components/TransactionModal/SuccessModal.tsx @@ -9,7 +9,7 @@ import { VStack, } from "@chakra-ui/react" import { LoadingSpinnerSuccessIcon } from "#/assets/icons" -import { useModalFlowContext } from "#/hooks" +import { useModal } from "#/hooks" import { CurrencyBalanceWithConversion } from "#/components/shared/CurrencyBalanceWithConversion" import { ACTION_FLOW_TYPES, ActionFlowType, TokenAmount } from "#/types" import { TextMd } from "../shared/Typography" @@ -67,7 +67,7 @@ type SuccessModalProps = { } export default function SuccessModal({ type, tokenAmount }: SuccessModalProps) { - const { onClose } = useModalFlowContext() + const { closeModal } = useModal() const { header, footer, renderBody } = CONTENT[type] @@ -81,7 +81,7 @@ export default function SuccessModal({ type, tokenAmount }: SuccessModalProps) { - diff --git a/dapp/src/components/TransactionModal/index.tsx b/dapp/src/components/TransactionModal/index.tsx index 803712f78..13f6ed82d 100644 --- a/dapp/src/components/TransactionModal/index.tsx +++ b/dapp/src/components/TransactionModal/index.tsx @@ -1,29 +1,18 @@ -import React, { useEffect, useMemo } from "react" -import { - ModalFlowContext, - ModalFlowContextValue, - StakeFlowProvider, - TransactionContextProvider, -} from "#/contexts" +import React, { useEffect } from "react" +import { StakeFlowProvider, TransactionContextProvider } from "#/contexts" import { useSidebar } from "#/hooks" -import { ActionFlowType } from "#/types" +import { ActionFlowType, BaseModalProps } from "#/types" import { ModalCloseButton } from "@chakra-ui/react" -import { resetState, setType } from "#/store/action-flow" -import ModalBase from "../shared/ModalBase" +import { setType } from "#/store/action-flow" import ModalContentWrapper from "./ModalContentWrapper" import { ActiveFlowStep } from "./ActiveFlowStep" +import withBaseModal from "../ModalRoot/withBaseModal" type TransactionModalProps = { type: ActionFlowType - isOpen: boolean - onClose: () => void -} +} & BaseModalProps -export default function TransactionModal({ - type, - isOpen, - onClose, -}: TransactionModalProps) { +function TransactionModalBase({ type }: TransactionModalProps) { const { onOpen: openSideBar, onClose: closeSidebar } = useSidebar() useEffect(() => { @@ -31,36 +20,21 @@ export default function TransactionModal({ }, [type]) useEffect(() => { - let timeout: NodeJS.Timeout - - if (isOpen) { - openSideBar() - } else { - closeSidebar() - timeout = setTimeout(resetState, 100) - } - return () => clearTimeout(timeout) - }, [isOpen, openSideBar, closeSidebar]) - - const contextValue: ModalFlowContextValue = useMemo( - () => ({ - onClose, - }), - [onClose], - ) + openSideBar() + return () => closeSidebar() + }, [closeSidebar, openSideBar]) return ( - - - - - - - - - - - - + + + + + + + + ) } + +const TransactionModal = withBaseModal(TransactionModalBase) +export default TransactionModal diff --git a/dapp/src/components/shared/ModalBase/index.tsx b/dapp/src/components/shared/ModalBase/index.tsx deleted file mode 100644 index 416b22850..000000000 --- a/dapp/src/components/shared/ModalBase/index.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react" -import { Modal, ModalContent, ModalOverlay, ModalProps } from "@chakra-ui/react" - -export const MODAL_BASE_SIZE = "lg" - -export default function ModalBase({ children, ...restProps }: ModalProps) { - return ( - - - {children} - - ) -} diff --git a/dapp/src/contexts/ModalFlowContext.tsx b/dapp/src/contexts/ModalFlowContext.tsx deleted file mode 100644 index df0d9363f..000000000 --- a/dapp/src/contexts/ModalFlowContext.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { createContext } from "react" - -export type ModalFlowContextValue = { - onClose: () => void -} - -export const ModalFlowContext = createContext< - ModalFlowContextValue | undefined ->(undefined) diff --git a/dapp/src/contexts/index.tsx b/dapp/src/contexts/index.tsx index 47a8cd708..a3365497c 100644 --- a/dapp/src/contexts/index.tsx +++ b/dapp/src/contexts/index.tsx @@ -3,6 +3,5 @@ export * from "./WalletApiReactTransportProvider" export * from "./LedgerWalletAPIProvider" export * from "./DocsDrawerContext" export * from "./SidebarContext" -export * from "./ModalFlowContext" export * from "./TransactionContext" export * from "./StakeFlowContext" diff --git a/dapp/src/hooks/index.ts b/dapp/src/hooks/index.ts index 1c483932d..b5f482748 100644 --- a/dapp/src/hooks/index.ts +++ b/dapp/src/hooks/index.ts @@ -7,7 +7,6 @@ export * from "./useRequestEthereumAccount" export * from "./useWalletContext" export * from "./useSidebar" export * from "./useDocsDrawer" -export * from "./useModalFlowContext" export * from "./useTransactionContext" export * from "./useTransactionDetails" export * from "./useDepositBTCTransaction" @@ -24,3 +23,4 @@ export * from "./useCountdown" export * from "./useActivities" export * from "./useSize" export * from "./useTransactionFee" +export * from "./useModal" diff --git a/dapp/src/hooks/useModal.ts b/dapp/src/hooks/useModal.ts new file mode 100644 index 000000000..8d213e785 --- /dev/null +++ b/dapp/src/hooks/useModal.ts @@ -0,0 +1,27 @@ +import { + closeModal, + openModal, + selectModalProps, + selectModalType, +} from "#/store/modal" +import { ModalProps, ModalType } from "#/types" +import { useAppDispatch } from "./store/useAppDispatch" +import { useAppSelector } from "./store/useAppSelector" + +export function useModal() { + const modalType = useAppSelector(selectModalType) + const modalProps = useAppSelector(selectModalProps) + const dispatch = useAppDispatch() + + const handleOpenModal = (type: ModalType, props?: ModalProps) => + dispatch(openModal({ modalType: type, props })) + + const handleCloseModal = () => dispatch(closeModal()) + + return { + modalType, + modalProps, + openModal: handleOpenModal, + closeModal: handleCloseModal, + } +} diff --git a/dapp/src/hooks/useModalFlowContext.ts b/dapp/src/hooks/useModalFlowContext.ts deleted file mode 100644 index fda6eb681..000000000 --- a/dapp/src/hooks/useModalFlowContext.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useContext } from "react" -import { ModalFlowContext } from "#/contexts" - -export function useModalFlowContext() { - const context = useContext(ModalFlowContext) - - if (!context) { - throw new Error( - "ModalFlowContext used outside of ModalFlowContext component", - ) - } - - return context -} diff --git a/dapp/src/pages/OverviewPage/PositionDetails.tsx b/dapp/src/pages/OverviewPage/PositionDetails.tsx index 179f46c6d..8a432b9f1 100644 --- a/dapp/src/pages/OverviewPage/PositionDetails.tsx +++ b/dapp/src/pages/OverviewPage/PositionDetails.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from "react" +import React from "react" import { Button, CardBody, @@ -9,23 +9,15 @@ import { } from "@chakra-ui/react" import { CurrencyBalanceWithConversion } from "#/components/shared/CurrencyBalanceWithConversion" import { TextMd } from "#/components/shared/Typography" -import { ACTION_FLOW_TYPES, ActionFlowType } from "#/types" -import TransactionModal from "#/components/TransactionModal" +import { MODAL_TYPES } from "#/types" import { useEstimatedBTCBalance } from "#/hooks/store" import { LiquidStakingTokenPopover } from "#/components/LiquidStakingTokenPopover" -import { useSize } from "#/hooks" +import { useModal, useSize } from "#/hooks" export default function PositionDetails(props: CardProps) { const estimatedBtcBalance = useEstimatedBTCBalance() const { ref, size } = useSize() - - const [actionFlowType, setActionFlowType] = useState< - ActionFlowType | undefined - >(undefined) - - const handleCloseTransactionModal = useCallback(() => { - setActionFlowType(undefined) - }, []) + const { openModal } = useModal() return ( @@ -50,29 +42,18 @@ export default function PositionDetails(props: CardProps) { - {/* TODO: Simplify the logic of opening modals */} - - ) } diff --git a/dapp/src/store/modal/index.ts b/dapp/src/store/modal/index.ts new file mode 100644 index 000000000..06ad11596 --- /dev/null +++ b/dapp/src/store/modal/index.ts @@ -0,0 +1,2 @@ +export * from "./modalSlice" +export * from "./modalSelectors" diff --git a/dapp/src/store/modal/modalSelectors.ts b/dapp/src/store/modal/modalSelectors.ts new file mode 100644 index 000000000..39f642e8e --- /dev/null +++ b/dapp/src/store/modal/modalSelectors.ts @@ -0,0 +1,8 @@ +import { ModalProps, ModalType } from "#/types" +import { RootState } from ".." + +export const selectModalType = (state: RootState): ModalType | null => + state.modal.modalType + +export const selectModalProps = (state: RootState): ModalProps | undefined => + state.modal.props diff --git a/dapp/src/store/modal/modalSlice.ts b/dapp/src/store/modal/modalSlice.ts new file mode 100644 index 000000000..9877bfbd9 --- /dev/null +++ b/dapp/src/store/modal/modalSlice.ts @@ -0,0 +1,32 @@ +import { ModalType, ModalProps } from "#/types" +import { createSlice, PayloadAction } from "@reduxjs/toolkit" + +type ModalState = { + modalType: ModalType | null + props?: ModalProps +} + +const initialState: ModalState = { + modalType: null, + props: {}, +} + +export const modalSlice = createSlice({ + name: "modal", + initialState, + reducers: { + openModal: ( + state: ModalState, + action: PayloadAction<{ modalType: ModalType; props?: ModalProps }>, + ) => { + state.modalType = action.payload.modalType + state.props = action.payload.props + }, + closeModal: (state: ModalState) => { + state.modalType = null + state.props = {} + }, + }, +}) + +export const { openModal, closeModal } = modalSlice.actions diff --git a/dapp/src/store/reducer.ts b/dapp/src/store/reducer.ts index 6ed0155e2..1e15ef056 100644 --- a/dapp/src/store/reducer.ts +++ b/dapp/src/store/reducer.ts @@ -1,8 +1,10 @@ import { combineReducers } from "@reduxjs/toolkit" import { btcSlice } from "./btc/btcSlice" import { actionFlowSlice } from "./action-flow/actionFlowSlice" +import { modalSlice } from "./modal/modalSlice" export const reducer = combineReducers({ btc: btcSlice.reducer, actionFlow: actionFlowSlice.reducer, + modal: modalSlice.reducer, }) diff --git a/dapp/src/types/index.ts b/dapp/src/types/index.ts index b3b047b18..19adfd5ab 100644 --- a/dapp/src/types/index.ts +++ b/dapp/src/types/index.ts @@ -14,3 +14,4 @@ export * from "./time" export * from "./size" export * from "./toast" export * from "./fee" +export * from "./modal" diff --git a/dapp/src/types/modal.ts b/dapp/src/types/modal.ts new file mode 100644 index 000000000..75febc189 --- /dev/null +++ b/dapp/src/types/modal.ts @@ -0,0 +1,12 @@ +export type ModalProps = Record + +export type BaseModalProps = { + closeModal: () => void +} + +export const MODAL_TYPES = { + STAKE: "STAKE", + UNSTAKE: "UNSTAKE", +} as const + +export type ModalType = (typeof MODAL_TYPES)[keyof typeof MODAL_TYPES] From bd1e73a306132c5415ed92fff6ade558f2517806 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Thu, 9 May 2024 13:01:41 +0200 Subject: [PATCH 12/34] Implement `PageLayout` component --- .../OverviewPage/PageLayout/PageLayout.tsx | 51 +++++++++++++++++++ .../PageLayout/PageLayoutSidebar.tsx | 8 +++ .../pages/OverviewPage/PageLayout/index.ts | 2 + dapp/src/theme/index.ts | 3 ++ 4 files changed, 64 insertions(+) create mode 100644 dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx create mode 100644 dapp/src/pages/OverviewPage/PageLayout/PageLayoutSidebar.tsx create mode 100644 dapp/src/pages/OverviewPage/PageLayout/index.ts diff --git a/dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx b/dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx new file mode 100644 index 000000000..b1d996294 --- /dev/null +++ b/dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx @@ -0,0 +1,51 @@ +import React from "react" +import { Grid, GridProps, Box } from "@chakra-ui/react" +import PageLayoutSidebar from "./PageLayoutSidebar" + +type PageLayoutProps = Omit & { + children: React.ReactNode + leftSidebar: React.ReactNode + rightSidebar: React.ReactNode +} + +function PageLayout(props: PageLayoutProps) { + const { children, leftSidebar, rightSidebar, ...restProps } = props + const isSidebarPropsInvalid = [leftSidebar, rightSidebar].some( + (value) => !React.isValidElement(value) || value.type !== PageLayoutSidebar, + ) + + if (isSidebarPropsInvalid) { + throw new Error("Sidebars must be wrapped with `PageLayout.Sidebar`.") + } + + return ( + + + {children} + + {leftSidebar} + {rightSidebar} + + ) +} + +export default PageLayout diff --git a/dapp/src/pages/OverviewPage/PageLayout/PageLayoutSidebar.tsx b/dapp/src/pages/OverviewPage/PageLayout/PageLayoutSidebar.tsx new file mode 100644 index 000000000..6abb979e9 --- /dev/null +++ b/dapp/src/pages/OverviewPage/PageLayout/PageLayoutSidebar.tsx @@ -0,0 +1,8 @@ +import React from "react" +import { StackProps, VStack } from "@chakra-ui/react" + +function PageLayoutSidebar(props: StackProps) { + return +} + +export default PageLayoutSidebar diff --git a/dapp/src/pages/OverviewPage/PageLayout/index.ts b/dapp/src/pages/OverviewPage/PageLayout/index.ts new file mode 100644 index 000000000..4121f6e16 --- /dev/null +++ b/dapp/src/pages/OverviewPage/PageLayout/index.ts @@ -0,0 +1,2 @@ +export { default as PageLayout } from "./PageLayout" +export { default as PageLayoutSidebar } from "./PageLayoutSidebar" diff --git a/dapp/src/theme/index.ts b/dapp/src/theme/index.ts index add79614a..5cdff7e5a 100644 --- a/dapp/src/theme/index.ts +++ b/dapp/src/theme/index.ts @@ -45,6 +45,9 @@ const defaultTheme = { zIndices, semanticTokens, styles, + breakpoints: { + "2.5xl": "100.5rem", // 1608px + }, components: { Alert: alertTheme, Button: buttonTheme, From 126acdd02d0153b959b7b94650879371bf67bc0c Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 9 May 2024 14:15:53 +0200 Subject: [PATCH 13/34] Add the missing icons in the sidebar for benefit cards --- .../src/assets/images/rewards-boost-arrow.svg | 68 +++++++++++++++++++ dapp/src/components/Sidebar.tsx | 25 +++++-- 2 files changed, 86 insertions(+), 7 deletions(-) create mode 100644 dapp/src/assets/images/rewards-boost-arrow.svg diff --git a/dapp/src/assets/images/rewards-boost-arrow.svg b/dapp/src/assets/images/rewards-boost-arrow.svg new file mode 100644 index 000000000..b8cb985cf --- /dev/null +++ b/dapp/src/assets/images/rewards-boost-arrow.svg @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dapp/src/components/Sidebar.tsx b/dapp/src/components/Sidebar.tsx index 2b154650d..3dfc36915 100644 --- a/dapp/src/components/Sidebar.tsx +++ b/dapp/src/components/Sidebar.tsx @@ -5,10 +5,14 @@ import { CardBody, Flex, useMultiStyleConfig, + Image, } from "@chakra-ui/react" import { useSidebar, useDocsDrawer } from "#/hooks" -import { TextSm } from "./shared/Typography" +import rewardsBoostArrow from "#/assets/images/rewards-boost-arrow.svg" +import mysteryBoxIcon from "#/assets/images/mystery-box.svg" +import seasonKeyIcon from "#/assets/images/season-key.svg" import ButtonLink from "./shared/ButtonLink" +import { TextSm } from "./shared/Typography" const BUTTONS = [ { label: "Docs", variant: "solid" }, @@ -18,9 +22,9 @@ const BUTTONS = [ ] const BENEFITS = [ - { label: "1x Rewards Boost", icon: undefined }, - { label: "1x Mystery Box", icon: undefined }, - { label: "1x Season Key", icon: undefined }, + { label: "1x Rewards Boost", iconSrc: rewardsBoostArrow }, + { label: "1x Mystery Box", iconSrc: mysteryBoxIcon }, + { label: "1x Season Key", iconSrc: seasonKeyIcon }, ] export default function Sidebar() { @@ -39,16 +43,23 @@ export default function Sidebar() { Rewards you’ll unlock - {BENEFITS.map(({ label }) => ( + {BENEFITS.map(({ label, iconSrc }) => ( - + {label} - {/* TODO: Add a correct icon */} + ))} From 5a3b4c7a5bbcd532ff785d6463c38a38fc113ad4 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 9 May 2024 14:25:11 +0200 Subject: [PATCH 14/34] Add ability to open `TransactionModal` from landing page --- .../pages/LandingPage/components/HeroSection.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/dapp/src/pages/LandingPage/components/HeroSection.tsx b/dapp/src/pages/LandingPage/components/HeroSection.tsx index 14d8a7e4a..280f2a710 100644 --- a/dapp/src/pages/LandingPage/components/HeroSection.tsx +++ b/dapp/src/pages/LandingPage/components/HeroSection.tsx @@ -1,7 +1,11 @@ import React from "react" import { Button, Heading, VStack, Text } from "@chakra-ui/react" +import { useModal } from "#/hooks" +import { MODAL_TYPES } from "#/types" export default function HeroSection() { + const { openModal } = useModal() + return ( The open source, decentralized way to grow your bitcoin - From 1e64bc0abaf7a794e1332ec3738d4569555a2d08 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 9 May 2024 14:27:05 +0200 Subject: [PATCH 15/34] Fix issue with closing modal --- dapp/src/components/ModalRoot/withBaseModal.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/dapp/src/components/ModalRoot/withBaseModal.tsx b/dapp/src/components/ModalRoot/withBaseModal.tsx index f0c24a99f..de804ca0d 100644 --- a/dapp/src/components/ModalRoot/withBaseModal.tsx +++ b/dapp/src/components/ModalRoot/withBaseModal.tsx @@ -14,6 +14,7 @@ function withBaseModal( isOpen onClose={closeModal} scrollBehavior="inside" + closeOnOverlayClick={false} size={MODAL_BASE_SIZE} > From b39b1200645c36b5fc26a126854b1056ba19a749 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 9 May 2024 15:06:00 +0200 Subject: [PATCH 16/34] Fix issue with switching type for action flow --- dapp/src/components/TransactionModal/index.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/dapp/src/components/TransactionModal/index.tsx b/dapp/src/components/TransactionModal/index.tsx index 13f6ed82d..d6fd04303 100644 --- a/dapp/src/components/TransactionModal/index.tsx +++ b/dapp/src/components/TransactionModal/index.tsx @@ -1,9 +1,9 @@ import React, { useEffect } from "react" import { StakeFlowProvider, TransactionContextProvider } from "#/contexts" -import { useSidebar } from "#/hooks" +import { useAppDispatch, useSidebar } from "#/hooks" import { ActionFlowType, BaseModalProps } from "#/types" import { ModalCloseButton } from "@chakra-ui/react" -import { setType } from "#/store/action-flow" +import { resetState, setType } from "#/store/action-flow" import ModalContentWrapper from "./ModalContentWrapper" import { ActiveFlowStep } from "./ActiveFlowStep" import withBaseModal from "../ModalRoot/withBaseModal" @@ -14,10 +14,11 @@ type TransactionModalProps = { function TransactionModalBase({ type }: TransactionModalProps) { const { onOpen: openSideBar, onClose: closeSidebar } = useSidebar() + const dispatch = useAppDispatch() useEffect(() => { - setType(type) - }, [type]) + dispatch(setType(type)) + }, [dispatch, type]) useEffect(() => { openSideBar() From 5f9f85dd534ce05a3377f03f63de0660d343af65 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 10 May 2024 09:21:44 +0200 Subject: [PATCH 17/34] Reset state for `TransactionModal` --- dapp/src/components/TransactionModal/index.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/dapp/src/components/TransactionModal/index.tsx b/dapp/src/components/TransactionModal/index.tsx index d6fd04303..5dc91b9f5 100644 --- a/dapp/src/components/TransactionModal/index.tsx +++ b/dapp/src/components/TransactionModal/index.tsx @@ -20,6 +20,13 @@ function TransactionModalBase({ type }: TransactionModalProps) { dispatch(setType(type)) }, [dispatch, type]) + // eslint-disable-next-line arrow-body-style + useEffect(() => { + return () => { + dispatch(resetState()) + } + }, [dispatch]) + useEffect(() => { openSideBar() return () => closeSidebar() From 15c3f9e96dcdcaba2781c219602521ca3dde045a Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 10 May 2024 09:23:45 +0200 Subject: [PATCH 18/34] Update value of delay for triggering Bitcoin transaction --- .../TransactionModal/ActiveStakingStep/DepositBTCModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index 0940b2f63..3ef936def 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -18,7 +18,7 @@ import { CardAlert } from "#/components/shared/alerts" import { ONE_SEC_IN_MILLISECONDS } from "#/constants" import { setStatus } from "#/store/action-flow" -const DELAY = ONE_SEC_IN_MILLISECONDS * 2 +const DELAY = ONE_SEC_IN_MILLISECONDS const TOAST_ID = TOAST_IDS.DEPOSIT_TRANSACTION_ERROR const TOAST = TOASTS[TOAST_ID] From b9dd2d8a078b8afa237a949510694738fa2aebf6 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 10 May 2024 09:36:05 +0200 Subject: [PATCH 19/34] Move `TransactionContext` to redux store --- .../TransactionModal/ActionFormModal.tsx | 13 +++---- .../ActiveStakingStep/DepositBTCModal.tsx | 4 +-- .../TransactionModal/ModalContentWrapper.tsx | 4 +-- .../src/components/TransactionModal/index.tsx | 16 ++++----- dapp/src/contexts/TransactionContext.tsx | 36 ------------------- dapp/src/contexts/index.tsx | 1 - dapp/src/hooks/index.ts | 1 - dapp/src/hooks/store/index.ts | 1 + .../hooks/store/useActionFlowTokenAmount.ts | 6 ++++ dapp/src/hooks/useTransactionContext.ts | 14 -------- .../store/action-flow/actionFlowSelectors.ts | 6 +++- dapp/src/store/action-flow/actionFlowSlice.ts | 13 +++++-- 12 files changed, 39 insertions(+), 76 deletions(-) delete mode 100644 dapp/src/contexts/TransactionContext.tsx create mode 100644 dapp/src/hooks/store/useActionFlowTokenAmount.ts delete mode 100644 dapp/src/hooks/useTransactionContext.ts diff --git a/dapp/src/components/TransactionModal/ActionFormModal.tsx b/dapp/src/components/TransactionModal/ActionFormModal.tsx index e536d426e..e38c1684b 100644 --- a/dapp/src/components/TransactionModal/ActionFormModal.tsx +++ b/dapp/src/components/TransactionModal/ActionFormModal.tsx @@ -1,15 +1,12 @@ import React, { useCallback, useState } from "react" import { Box, ModalBody, ModalCloseButton, ModalHeader } from "@chakra-ui/react" -import { - useStakeFlowContext, - useTransactionContext, - useWalletContext, -} from "#/hooks" +import { useAppDispatch, useStakeFlowContext, useWalletContext } from "#/hooks" import { ACTION_FLOW_TYPES, ActionFlowType } from "#/types" import { TokenAmountFormValues } from "#/components/shared/TokenAmountForm/TokenAmountFormBase" import { logPromiseFailure } from "#/utils" import StakeFormModal from "./ActiveStakingStep/StakeFormModal" import UnstakeFormModal from "./ActiveUnstakingStep/UnstakeFormModal" +import { setTokenAmount } from "#/store/action-flow" const FORM_DATA: Record< ActionFlowType, @@ -34,8 +31,8 @@ const FORM_DATA: Record< function ActionFormModal({ type }: { type: ActionFlowType }) { const { btcAccount, ethAccount } = useWalletContext() - const { setTokenAmount } = useTransactionContext() const { initStake } = useStakeFlowContext() + const dispatch = useAppDispatch() const [isLoading, setIsLoading] = useState(false) @@ -59,14 +56,14 @@ function ActionFormModal({ type }: { type: ActionFlowType }) { // TODO: Init unstake flow if (type === ACTION_FLOW_TYPES.STAKE) await handleInitStake() - setTokenAmount({ amount: values.amount, currency: "bitcoin" }) + dispatch(setTokenAmount({ amount: values.amount, currency: "bitcoin" })) } catch (error) { console.error(error) } finally { setIsLoading(false) } }, - [handleInitStake, setTokenAmount, type], + [dispatch, handleInitStake, type], ) const handleSubmitFormWrapper = useCallback( diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index 3ef936def..b6843ce9e 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -1,11 +1,11 @@ import React, { useCallback } from "react" import { + useActionFlowTokenAmount, useDepositBTCTransaction, useDepositTelemetry, useExecuteFunction, useStakeFlowContext, useToast, - useTransactionContext, useWalletContext, } from "#/hooks" import { logPromiseFailure } from "#/utils" @@ -24,7 +24,7 @@ const TOAST = TOASTS[TOAST_ID] export default function DepositBTCModal() { const { ethAccount } = useWalletContext() - const { tokenAmount } = useTransactionContext() + const tokenAmount = useActionFlowTokenAmount() const { btcAddress, depositReceipt, stake } = useStakeFlowContext() const depositTelemetry = useDepositTelemetry() const { closeToast, openToast } = useToast() diff --git a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx index 914acf999..cfe99aea7 100644 --- a/dapp/src/components/TransactionModal/ModalContentWrapper.tsx +++ b/dapp/src/components/TransactionModal/ModalContentWrapper.tsx @@ -1,10 +1,10 @@ import React from "react" import { useActionFlowStatus, + useActionFlowTokenAmount, useActionFlowType, useRequestBitcoinAccount, useRequestEthereumAccount, - useTransactionContext, useWalletContext, } from "#/hooks" import { BitcoinIcon, EthereumIcon } from "#/assets/icons" @@ -26,7 +26,7 @@ export default function ModalContentWrapper({ const { requestAccount: requestEthereumAccount } = useRequestEthereumAccount() const status = useActionFlowStatus() const type = useActionFlowType() - const { tokenAmount } = useTransactionContext() + const tokenAmount = useActionFlowTokenAmount() if (!btcAccount || !isSupportedBTCAddressType(btcAccount.address)) return ( diff --git a/dapp/src/components/TransactionModal/index.tsx b/dapp/src/components/TransactionModal/index.tsx index 5dc91b9f5..7fc1c88ad 100644 --- a/dapp/src/components/TransactionModal/index.tsx +++ b/dapp/src/components/TransactionModal/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from "react" -import { StakeFlowProvider, TransactionContextProvider } from "#/contexts" +import { StakeFlowProvider } from "#/contexts" import { useAppDispatch, useSidebar } from "#/hooks" import { ActionFlowType, BaseModalProps } from "#/types" import { ModalCloseButton } from "@chakra-ui/react" @@ -33,14 +33,12 @@ function TransactionModalBase({ type }: TransactionModalProps) { }, [closeSidebar, openSideBar]) return ( - - - - - - - - + + + + + + ) } diff --git a/dapp/src/contexts/TransactionContext.tsx b/dapp/src/contexts/TransactionContext.tsx deleted file mode 100644 index 8105fc841..000000000 --- a/dapp/src/contexts/TransactionContext.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React, { createContext, useMemo, useState } from "react" -import { TokenAmount } from "#/types" - -type TransactionContextValue = { - tokenAmount?: TokenAmount - setTokenAmount: React.Dispatch> -} - -export const TransactionContext = createContext< - TransactionContextValue | undefined ->(undefined) - -export function TransactionContextProvider({ - children, -}: { - children: React.ReactNode -}): React.ReactElement { - const [tokenAmount, setTokenAmount] = useState( - undefined, - ) - - const contextValue: TransactionContextValue = - useMemo( - () => ({ - tokenAmount, - setTokenAmount, - }), - [tokenAmount], - ) - - return ( - - {children} - - ) -} diff --git a/dapp/src/contexts/index.tsx b/dapp/src/contexts/index.tsx index a3365497c..b4218417a 100644 --- a/dapp/src/contexts/index.tsx +++ b/dapp/src/contexts/index.tsx @@ -3,5 +3,4 @@ export * from "./WalletApiReactTransportProvider" export * from "./LedgerWalletAPIProvider" export * from "./DocsDrawerContext" export * from "./SidebarContext" -export * from "./TransactionContext" export * from "./StakeFlowContext" diff --git a/dapp/src/hooks/index.ts b/dapp/src/hooks/index.ts index 150dda613..b3405e7be 100644 --- a/dapp/src/hooks/index.ts +++ b/dapp/src/hooks/index.ts @@ -8,7 +8,6 @@ export * from "./useRequestEthereumAccount" export * from "./useWalletContext" export * from "./useSidebar" export * from "./useDocsDrawer" -export * from "./useTransactionContext" export * from "./useTransactionDetails" export * from "./useDepositBTCTransaction" export * from "./useTransactionHistoryTable" diff --git a/dapp/src/hooks/store/index.ts b/dapp/src/hooks/store/index.ts index 7c30653b6..396d4e68f 100644 --- a/dapp/src/hooks/store/index.ts +++ b/dapp/src/hooks/store/index.ts @@ -6,5 +6,6 @@ export * from "./useMinDepositAmount" export * from "./useActionFlowType" export * from "./useActionFlowStatus" export * from "./useActionFlowActiveStep" +export * from "./useActionFlowTokenAmount" // TODO: Rename when the old hook is deleted. export { useActivities as useActivitiesNEW } from "./useActivities" diff --git a/dapp/src/hooks/store/useActionFlowTokenAmount.ts b/dapp/src/hooks/store/useActionFlowTokenAmount.ts new file mode 100644 index 000000000..f580df058 --- /dev/null +++ b/dapp/src/hooks/store/useActionFlowTokenAmount.ts @@ -0,0 +1,6 @@ +import { selectActionFlowTokenAmount } from "#/store/action-flow" +import { useAppSelector } from "./useAppSelector" + +export function useActionFlowTokenAmount() { + return useAppSelector(selectActionFlowTokenAmount) +} diff --git a/dapp/src/hooks/useTransactionContext.ts b/dapp/src/hooks/useTransactionContext.ts deleted file mode 100644 index 41a8a8359..000000000 --- a/dapp/src/hooks/useTransactionContext.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useContext } from "react" -import { TransactionContext } from "#/contexts" - -export function useTransactionContext() { - const context = useContext(TransactionContext) - - if (!context) { - throw new Error( - "TransactionContext used outside of TransactionContext component", - ) - } - - return context -} diff --git a/dapp/src/store/action-flow/actionFlowSelectors.ts b/dapp/src/store/action-flow/actionFlowSelectors.ts index 6de0a44bb..a814d6290 100644 --- a/dapp/src/store/action-flow/actionFlowSelectors.ts +++ b/dapp/src/store/action-flow/actionFlowSelectors.ts @@ -1,4 +1,4 @@ -import { ActionFlowType, ProcessStatus } from "#/types" +import { ActionFlowType, ProcessStatus, TokenAmount } from "#/types" import { RootState } from ".." export const selectActionFlowType = (state: RootState): ActionFlowType => @@ -9,3 +9,7 @@ export const selectActionFlowActiveStep = (state: RootState): number => export const selectActionFlowStatus = (state: RootState): ProcessStatus => state.actionFlow.status + +export const selectActionFlowTokenAmount = ( + state: RootState, +): TokenAmount | undefined => state.actionFlow.tokenAmount diff --git a/dapp/src/store/action-flow/actionFlowSlice.ts b/dapp/src/store/action-flow/actionFlowSlice.ts index 6a3b8a721..1866699c8 100644 --- a/dapp/src/store/action-flow/actionFlowSlice.ts +++ b/dapp/src/store/action-flow/actionFlowSlice.ts @@ -1,10 +1,16 @@ -import { ActionFlowType, PROCESS_STATUSES, ProcessStatus } from "#/types" +import { + ActionFlowType, + PROCESS_STATUSES, + ProcessStatus, + TokenAmount, +} from "#/types" import { PayloadAction, createSlice } from "@reduxjs/toolkit" type ActionFlowState = { type: ActionFlowType activeStep: number status: ProcessStatus + tokenAmount?: TokenAmount } const initialState: ActionFlowState = { @@ -26,6 +32,9 @@ export const actionFlowSlice = createSlice({ setStatus(state, action: PayloadAction) { state.status = action.payload }, + setTokenAmount(state, action: PayloadAction) { + state.tokenAmount = action.payload + }, goNextStep(state) { state.activeStep += 1 }, @@ -37,5 +46,5 @@ export const actionFlowSlice = createSlice({ }, }) -export const { setType, setStatus, goNextStep, resetState } = +export const { setType, setStatus, setTokenAmount, goNextStep, resetState } = actionFlowSlice.actions From af3135a8d20979b1bc44b5ccac9839a50bc571cc Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 10 May 2024 09:44:38 +0200 Subject: [PATCH 20/34] Fix eslint issue with imports --- dapp/src/components/Layout.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dapp/src/components/Layout.tsx b/dapp/src/components/Layout.tsx index 528160b9c..43a0bc2d3 100644 --- a/dapp/src/components/Layout.tsx +++ b/dapp/src/components/Layout.tsx @@ -1,6 +1,5 @@ -import React from "react" +import React, { useState } from "react" import { AnimatePresence, motion, Variants } from "framer-motion" -import { useState } from "react" import { useLocation, useOutlet } from "react-router-dom" import DocsDrawer from "./DocsDrawer" import Header from "./Header" From 754f2b36e8725e9fcc48fb6bddf7766a835e5e93 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 10 May 2024 09:46:05 +0200 Subject: [PATCH 21/34] Fix issue with resetting state for deposit flow --- dapp/src/store/action-flow/actionFlowSlice.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dapp/src/store/action-flow/actionFlowSlice.ts b/dapp/src/store/action-flow/actionFlowSlice.ts index 1866699c8..a1fdf4c2f 100644 --- a/dapp/src/store/action-flow/actionFlowSlice.ts +++ b/dapp/src/store/action-flow/actionFlowSlice.ts @@ -17,6 +17,7 @@ const initialState: ActionFlowState = { type: "stake", activeStep: 1, status: PROCESS_STATUSES.IDLE, + tokenAmount: undefined, } export const actionFlowSlice = createSlice({ @@ -42,6 +43,7 @@ export const actionFlowSlice = createSlice({ state.type = initialState.type state.activeStep = initialState.activeStep state.status = initialState.status + state.tokenAmount = initialState.tokenAmount }, }, }) From 4b2d4ef7565dcabd6100a7024be53fce6cd1ef7a Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 10 May 2024 09:50:46 +0200 Subject: [PATCH 22/34] Fix eslint issue --- dapp/src/components/TransactionModal/ActionFormModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dapp/src/components/TransactionModal/ActionFormModal.tsx b/dapp/src/components/TransactionModal/ActionFormModal.tsx index e38c1684b..dff2cc2a8 100644 --- a/dapp/src/components/TransactionModal/ActionFormModal.tsx +++ b/dapp/src/components/TransactionModal/ActionFormModal.tsx @@ -4,9 +4,9 @@ import { useAppDispatch, useStakeFlowContext, useWalletContext } from "#/hooks" import { ACTION_FLOW_TYPES, ActionFlowType } from "#/types" import { TokenAmountFormValues } from "#/components/shared/TokenAmountForm/TokenAmountFormBase" import { logPromiseFailure } from "#/utils" +import { setTokenAmount } from "#/store/action-flow" import StakeFormModal from "./ActiveStakingStep/StakeFormModal" import UnstakeFormModal from "./ActiveUnstakingStep/UnstakeFormModal" -import { setTokenAmount } from "#/store/action-flow" const FORM_DATA: Record< ActionFlowType, From d86f928fbb2eb4d385390d09ee6140e7c3cf6368 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 10 May 2024 10:07:04 +0200 Subject: [PATCH 23/34] Fix issue for redux actions --- .../ActiveStakingStep/DepositBTCModal.tsx | 14 ++++++++------ .../StakingErrorModal/index.tsx | 11 ++++++++--- .../ActiveUnstakingStep/SignMessageModal.tsx | 16 +++++++++------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx index b6843ce9e..f3fb591e5 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/DepositBTCModal.tsx @@ -1,6 +1,7 @@ import React, { useCallback } from "react" import { useActionFlowTokenAmount, + useAppDispatch, useDepositBTCTransaction, useDepositTelemetry, useExecuteFunction, @@ -28,15 +29,16 @@ export default function DepositBTCModal() { const { btcAddress, depositReceipt, stake } = useStakeFlowContext() const depositTelemetry = useDepositTelemetry() const { closeToast, openToast } = useToast() + const dispatch = useAppDispatch() const onStakeBTCSuccess = useCallback( - () => setStatus(PROCESS_STATUSES.SUCCEEDED), - [], + () => dispatch(setStatus(PROCESS_STATUSES.SUCCEEDED)), + [dispatch], ) const onStakeBTCError = useCallback(() => { - setStatus(PROCESS_STATUSES.FAILED) - }, []) + dispatch(setStatus(PROCESS_STATUSES.FAILED)) + }, [dispatch]) const handleStake = useExecuteFunction( stake, @@ -46,10 +48,10 @@ export default function DepositBTCModal() { const onDepositBTCSuccess = useCallback(() => { closeToast(TOAST_ID) - setStatus(PROCESS_STATUSES.LOADING) + dispatch(setStatus(PROCESS_STATUSES.LOADING)) logPromiseFailure(handleStake()) - }, [closeToast, handleStake]) + }, [closeToast, dispatch, handleStake]) const showError = useCallback(() => { openToast({ diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx index 1473e50ef..969a4e145 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakingErrorModal/index.tsx @@ -1,5 +1,9 @@ import React, { useCallback, useState } from "react" -import { useExecuteFunction, useStakeFlowContext } from "#/hooks" +import { + useAppDispatch, + useExecuteFunction, + useStakeFlowContext, +} from "#/hooks" import { PROCESS_STATUSES } from "#/types" import { logPromiseFailure } from "#/utils" import { setStatus } from "#/store/action-flow" @@ -9,13 +13,14 @@ import LoadingModal from "../../LoadingModal" export default function StakingErrorModal() { const { stake } = useStakeFlowContext() + const dispatch = useAppDispatch() const [isLoading, setIsLoading] = useState(false) const [isServerError, setIsServerError] = useState(false) const onStakeBTCSuccess = useCallback( - () => setStatus(PROCESS_STATUSES.SUCCEEDED), - [], + () => dispatch(setStatus(PROCESS_STATUSES.SUCCEEDED)), + [dispatch], ) const onStakeBTCError = useCallback(() => setIsServerError(true), []) diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx index e05bc5bec..3a8950325 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/SignMessageModal.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from "react" -import { useExecuteFunction } from "#/hooks" +import { useAppDispatch, useExecuteFunction } from "#/hooks" import { PROCESS_STATUSES } from "#/types" import { Button, ModalBody, ModalFooter, ModalHeader } from "@chakra-ui/react" import { TextMd } from "#/components/shared/Typography" @@ -7,14 +7,16 @@ import { logPromiseFailure } from "#/utils" import { setStatus } from "#/store/action-flow" export default function SignMessageModal() { + const dispatch = useAppDispatch() + const onSignMessageSuccess = useCallback(() => { - setStatus(PROCESS_STATUSES.SUCCEEDED) - }, []) + dispatch(setStatus(PROCESS_STATUSES.SUCCEEDED)) + }, [dispatch]) // TODO: After a failed attempt, we should display the message const onSignMessageError = useCallback(() => { - setStatus(PROCESS_STATUSES.FAILED) - }, []) + dispatch(setStatus(PROCESS_STATUSES.FAILED)) + }, [dispatch]) const handleSignMessage = useExecuteFunction( // TODO: Use a correct function from the SDK @@ -24,13 +26,13 @@ export default function SignMessageModal() { ) const handleSignMessageWrapper = useCallback(() => { - setStatus(PROCESS_STATUSES.LOADING) + dispatch(setStatus(PROCESS_STATUSES.LOADING)) // TODO: Remove when SDK is ready setTimeout(() => { logPromiseFailure(handleSignMessage()) }, 5000) - }, [handleSignMessage]) + }, [dispatch, handleSignMessage]) return ( <> From e2cec5fd7385f814a08b878a3989e639b6e32d51 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Fri, 10 May 2024 10:29:33 +0200 Subject: [PATCH 24/34] Improve styles for `SuccessModal` --- dapp/src/components/TransactionModal/SuccessModal.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dapp/src/components/TransactionModal/SuccessModal.tsx b/dapp/src/components/TransactionModal/SuccessModal.tsx index 2c2a2c5de..00811bbc5 100644 --- a/dapp/src/components/TransactionModal/SuccessModal.tsx +++ b/dapp/src/components/TransactionModal/SuccessModal.tsx @@ -1,6 +1,5 @@ import React from "react" import { - Box, Button, HStack, ModalBody, @@ -28,7 +27,7 @@ const CONTENT: Record< header: "Deposit received", renderBody: (tokenAmount) => ( <> - + - + {/* TODO: Use correct tx hash and update styles */} From 1e2383ec1edf4bc7af6f25834f31f08f3d54fb28 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 10 May 2024 13:44:46 +0200 Subject: [PATCH 25/34] Apply `PageLayout` to dashboard page --- dapp/src/pages/DashboardPage/index.tsx | 52 ++++++------------- .../OverviewPage/PageLayout/PageLayout.tsx | 15 +++--- 2 files changed, 25 insertions(+), 42 deletions(-) diff --git a/dapp/src/pages/DashboardPage/index.tsx b/dapp/src/pages/DashboardPage/index.tsx index ae1e36102..84d1b7f5f 100644 --- a/dapp/src/pages/DashboardPage/index.tsx +++ b/dapp/src/pages/DashboardPage/index.tsx @@ -1,42 +1,22 @@ import React from "react" -import { Flex, Grid, HStack, Switch } from "@chakra-ui/react" -import { TextSm } from "#/components/shared/Typography" -import { USD } from "#/constants" -import { chakraUnitToPx } from "#/theme/utils" -import PositionDetails from "./PositionDetails" -import Statistics from "./Statistics" -import TransactionHistory from "./TransactionHistory" -import { DocsCard } from "./DocsCard" -import { ActivityCarousel } from "./ActivityCarousel" +import { useWallet } from "#/hooks" +import { PageLayout, PageLayoutSidebar } from "./PageLayout" +import DashboardCard from "./DashboardCard" +import GrantedSeasonPassCard from "./GrantedSeasonPassCard" export default function DashboardPage() { - return ( - - - {/* TODO: Handle click actions */} - - Show values in {USD.symbol} - + const { bitcoin } = useWallet() + const bitcoinWalletBalance = bitcoin.account?.balance.toString() ?? "0" - - - - - - - - - - + return ( + + + + } + > + + ) } diff --git a/dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx b/dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx index b1d996294..249d0116f 100644 --- a/dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx +++ b/dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx @@ -4,18 +4,21 @@ import PageLayoutSidebar from "./PageLayoutSidebar" type PageLayoutProps = Omit & { children: React.ReactNode - leftSidebar: React.ReactNode - rightSidebar: React.ReactNode + leftSidebar?: React.ReactNode + rightSidebar?: React.ReactNode } function PageLayout(props: PageLayoutProps) { const { children, leftSidebar, rightSidebar, ...restProps } = props - const isSidebarPropsInvalid = [leftSidebar, rightSidebar].some( - (value) => !React.isValidElement(value) || value.type !== PageLayoutSidebar, - ) + const isSidebarPropsInvalid = [leftSidebar, rightSidebar] + .filter(Boolean) + .some( + (value) => + !React.isValidElement(value) || value.type !== PageLayoutSidebar, + ) if (isSidebarPropsInvalid) { - throw new Error("Sidebars must be wrapped with `PageLayout.Sidebar`.") + throw new Error("Sidebar content must be wrapped with `PageLayoutSidebar`.") } return ( From 720d91c02abbc87405bcc111d63762efa2feb8f5 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Fri, 10 May 2024 13:46:53 +0200 Subject: [PATCH 26/34] Resolve merge errors --- .../{OverviewPage => DashboardPage}/PageLayout/PageLayout.tsx | 0 .../PageLayout/PageLayoutSidebar.tsx | 0 .../pages/{OverviewPage => DashboardPage}/PageLayout/index.ts | 0 dapp/src/theme/index.ts | 1 + 4 files changed, 1 insertion(+) rename dapp/src/pages/{OverviewPage => DashboardPage}/PageLayout/PageLayout.tsx (100%) rename dapp/src/pages/{OverviewPage => DashboardPage}/PageLayout/PageLayoutSidebar.tsx (100%) rename dapp/src/pages/{OverviewPage => DashboardPage}/PageLayout/index.ts (100%) diff --git a/dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx b/dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx similarity index 100% rename from dapp/src/pages/OverviewPage/PageLayout/PageLayout.tsx rename to dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx diff --git a/dapp/src/pages/OverviewPage/PageLayout/PageLayoutSidebar.tsx b/dapp/src/pages/DashboardPage/PageLayout/PageLayoutSidebar.tsx similarity index 100% rename from dapp/src/pages/OverviewPage/PageLayout/PageLayoutSidebar.tsx rename to dapp/src/pages/DashboardPage/PageLayout/PageLayoutSidebar.tsx diff --git a/dapp/src/pages/OverviewPage/PageLayout/index.ts b/dapp/src/pages/DashboardPage/PageLayout/index.ts similarity index 100% rename from dapp/src/pages/OverviewPage/PageLayout/index.ts rename to dapp/src/pages/DashboardPage/PageLayout/index.ts diff --git a/dapp/src/theme/index.ts b/dapp/src/theme/index.ts index 55164bbdf..4338c5039 100644 --- a/dapp/src/theme/index.ts +++ b/dapp/src/theme/index.ts @@ -47,6 +47,7 @@ const defaultTheme = { styles, breakpoints: { "2.5xl": "100.5rem", // 1608px + }, space: { 13: "3.25rem", 15: "3.75rem", From 0a420d1f030e4ebca6e03665018e4e5b059ec126 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 14 May 2024 08:24:08 +0200 Subject: [PATCH 27/34] Simplify form data for ActionFormModal component --- .../TransactionModal/ActionFormModal.tsx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/dapp/src/components/TransactionModal/ActionFormModal.tsx b/dapp/src/components/TransactionModal/ActionFormModal.tsx index e536d426e..70efc7a1b 100644 --- a/dapp/src/components/TransactionModal/ActionFormModal.tsx +++ b/dapp/src/components/TransactionModal/ActionFormModal.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useState } from "react" +import React, { ReactNode, useCallback, useState } from "react" import { Box, ModalBody, ModalCloseButton, ModalHeader } from "@chakra-ui/react" import { useStakeFlowContext, @@ -14,21 +14,21 @@ import UnstakeFormModal from "./ActiveUnstakingStep/UnstakeFormModal" const FORM_DATA: Record< ActionFlowType, { - header: string - FormComponent: ( + heading: string + renderComponent: ( props: React.ComponentProps< typeof StakeFormModal | typeof UnstakeFormModal >, - ) => React.ReactNode + ) => ReactNode } > = { stake: { - header: "Deposit", - FormComponent: StakeFormModal, + heading: "Deposit", + renderComponent: StakeFormModal, }, unstake: { - header: "Withdraw", - FormComponent: UnstakeFormModal, + heading: "Withdraw", + renderComponent: UnstakeFormModal, }, } @@ -39,7 +39,7 @@ function ActionFormModal({ type }: { type: ActionFlowType }) { const [isLoading, setIsLoading] = useState(false) - const { header, FormComponent } = FORM_DATA[type] + const { heading, renderComponent } = FORM_DATA[type] const handleInitStake = useCallback(async () => { const btcAddress = btcAccount?.address @@ -78,10 +78,10 @@ function ActionFormModal({ type }: { type: ActionFlowType }) { return ( <> {!isLoading && } - {header} + {heading} - + {renderComponent({ onSubmitForm: handleSubmitFormWrapper })} From a14030fd9d0cab789dd0785b97f009fe025ee936 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 14 May 2024 08:33:33 +0200 Subject: [PATCH 28/34] Create a shared type for forms --- .../components/TransactionModal/ActionFormModal.tsx | 12 +++++------- .../ActiveStakingStep/StakeFormModal/index.tsx | 5 ++--- .../ActiveUnstakingStep/UnstakeFormModal/index.tsx | 5 ++--- dapp/src/components/shared/TokenAmountForm/index.tsx | 5 +++-- dapp/src/types/form.ts | 3 +++ dapp/src/types/index.ts | 1 + 6 files changed, 16 insertions(+), 15 deletions(-) create mode 100644 dapp/src/types/form.ts diff --git a/dapp/src/components/TransactionModal/ActionFormModal.tsx b/dapp/src/components/TransactionModal/ActionFormModal.tsx index 70efc7a1b..cc0ac9b67 100644 --- a/dapp/src/components/TransactionModal/ActionFormModal.tsx +++ b/dapp/src/components/TransactionModal/ActionFormModal.tsx @@ -5,7 +5,7 @@ import { useTransactionContext, useWalletContext, } from "#/hooks" -import { ACTION_FLOW_TYPES, ActionFlowType } from "#/types" +import { ACTION_FLOW_TYPES, ActionFlowType, BaseFormProps } from "#/types" import { TokenAmountFormValues } from "#/components/shared/TokenAmountForm/TokenAmountFormBase" import { logPromiseFailure } from "#/utils" import StakeFormModal from "./ActiveStakingStep/StakeFormModal" @@ -15,11 +15,7 @@ const FORM_DATA: Record< ActionFlowType, { heading: string - renderComponent: ( - props: React.ComponentProps< - typeof StakeFormModal | typeof UnstakeFormModal - >, - ) => ReactNode + renderComponent: (props: BaseFormProps) => ReactNode } > = { stake: { @@ -81,7 +77,9 @@ function ActionFormModal({ type }: { type: ActionFlowType }) { {heading} - {renderComponent({ onSubmitForm: handleSubmitFormWrapper })} + {renderComponent({ + onSubmitForm: handleSubmitFormWrapper, + })} diff --git a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx index 24c590b9d..220051905 100644 --- a/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveStakingStep/StakeFormModal/index.tsx @@ -3,13 +3,12 @@ import TokenAmountForm from "#/components/shared/TokenAmountForm" import { TokenAmountFormValues } from "#/components/shared/TokenAmountForm/TokenAmountFormBase" import { useMinDepositAmount, useWalletContext } from "#/hooks" import { FormSubmitButton } from "#/components/shared/Form" +import { BaseFormProps } from "#/types" import StakeDetails from "./StakeDetails" function StakeFormModal({ onSubmitForm, -}: { - onSubmitForm: (values: TokenAmountFormValues) => void -}) { +}: BaseFormProps) { const minDepositAmount = useMinDepositAmount() const { btcAccount } = useWalletContext() const tokenBalance = BigInt(btcAccount?.balance.toString() ?? "0") diff --git a/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx b/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx index 7cdbaad15..17dc1f07b 100644 --- a/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx +++ b/dapp/src/components/TransactionModal/ActiveUnstakingStep/UnstakeFormModal/index.tsx @@ -6,6 +6,7 @@ import { TextMd, TextSm } from "#/components/shared/Typography" import Spinner from "#/components/shared/Spinner" import { FormSubmitButton } from "#/components/shared/Form" import { useMinDepositAmount } from "#/hooks" +import { BaseFormProps } from "#/types" import UnstakeDetails from "./UnstakeDetails" // TODO: Use a position amount @@ -13,9 +14,7 @@ const MOCK_POSITION_AMOUNT = BigInt("2398567898") function UnstakeFormModal({ onSubmitForm, -}: { - onSubmitForm: (values: TokenAmountFormValues) => void -}) { +}: BaseFormProps) { const minDepositAmount = useMinDepositAmount() return ( diff --git a/dapp/src/components/shared/TokenAmountForm/index.tsx b/dapp/src/components/shared/TokenAmountForm/index.tsx index 80850c4e0..1aa49ad9d 100644 --- a/dapp/src/components/shared/TokenAmountForm/index.tsx +++ b/dapp/src/components/shared/TokenAmountForm/index.tsx @@ -1,14 +1,15 @@ import { FormikErrors, withFormik } from "formik" import { getErrorsObj, validateTokenAmount } from "#/utils" +import { BaseFormProps } from "#/types" import TokenAmountFormBase, { TokenAmountFormBaseProps, TokenAmountFormValues, } from "./TokenAmountFormBase" type TokenAmountFormProps = { - onSubmitForm: (values: TokenAmountFormValues) => void minTokenAmount: bigint -} & TokenAmountFormBaseProps +} & TokenAmountFormBaseProps & + BaseFormProps const TokenAmountForm = withFormik( { diff --git a/dapp/src/types/form.ts b/dapp/src/types/form.ts new file mode 100644 index 000000000..6a4526228 --- /dev/null +++ b/dapp/src/types/form.ts @@ -0,0 +1,3 @@ +export type BaseFormProps = { + onSubmitForm: (values: T) => void +} diff --git a/dapp/src/types/index.ts b/dapp/src/types/index.ts index dc645ffc8..0623cda8a 100644 --- a/dapp/src/types/index.ts +++ b/dapp/src/types/index.ts @@ -17,3 +17,4 @@ export * from "./core" export * from "./fee" export * from "./navigation" export * from "./subgraphAPI" +export * from "./form" From a533fbb50460bec2227556507d17cafda5710861 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Tue, 14 May 2024 14:47:33 +0200 Subject: [PATCH 29/34] Refactor `PageLayout` component --- .../DashboardPage/PageLayout/PageLayout.tsx | 35 +++---------------- .../PageLayout/PageLayoutColumn.tsx | 29 +++++++++++++++ .../PageLayout/PageLayoutSidebar.tsx | 8 ----- .../pages/DashboardPage/PageLayout/index.ts | 2 +- dapp/src/pages/DashboardPage/index.tsx | 18 +++++----- 5 files changed, 43 insertions(+), 49 deletions(-) create mode 100644 dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx delete mode 100644 dapp/src/pages/DashboardPage/PageLayout/PageLayoutSidebar.tsx diff --git a/dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx b/dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx index 249d0116f..0303ae3b9 100644 --- a/dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx +++ b/dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx @@ -1,25 +1,8 @@ import React from "react" -import { Grid, GridProps, Box } from "@chakra-ui/react" -import PageLayoutSidebar from "./PageLayoutSidebar" +import { Grid, GridProps } from "@chakra-ui/react" -type PageLayoutProps = Omit & { - children: React.ReactNode - leftSidebar?: React.ReactNode - rightSidebar?: React.ReactNode -} - -function PageLayout(props: PageLayoutProps) { - const { children, leftSidebar, rightSidebar, ...restProps } = props - const isSidebarPropsInvalid = [leftSidebar, rightSidebar] - .filter(Boolean) - .some( - (value) => - !React.isValidElement(value) || value.type !== PageLayoutSidebar, - ) - - if (isSidebarPropsInvalid) { - throw new Error("Sidebar content must be wrapped with `PageLayoutSidebar`.") - } +function PageLayout(props: GridProps) { + const { children, ...restProps } = props return ( - - {children} - - {leftSidebar} - {rightSidebar} + {children} ) } diff --git a/dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx b/dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx new file mode 100644 index 000000000..5e5b12872 --- /dev/null +++ b/dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx @@ -0,0 +1,29 @@ +import React from "react" +import { StackProps, VStack } from "@chakra-ui/react" + +type PageLayoutColumnProps = StackProps & { + isMain?: boolean +} + +function PageLayoutColumn(props: PageLayoutColumnProps) { + const { isMain = false, ...restProps } = props + + return ( + + ) +} + +export default PageLayoutColumn diff --git a/dapp/src/pages/DashboardPage/PageLayout/PageLayoutSidebar.tsx b/dapp/src/pages/DashboardPage/PageLayout/PageLayoutSidebar.tsx deleted file mode 100644 index 6abb979e9..000000000 --- a/dapp/src/pages/DashboardPage/PageLayout/PageLayoutSidebar.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from "react" -import { StackProps, VStack } from "@chakra-ui/react" - -function PageLayoutSidebar(props: StackProps) { - return -} - -export default PageLayoutSidebar diff --git a/dapp/src/pages/DashboardPage/PageLayout/index.ts b/dapp/src/pages/DashboardPage/PageLayout/index.ts index 4121f6e16..8197d5e88 100644 --- a/dapp/src/pages/DashboardPage/PageLayout/index.ts +++ b/dapp/src/pages/DashboardPage/PageLayout/index.ts @@ -1,2 +1,2 @@ export { default as PageLayout } from "./PageLayout" -export { default as PageLayoutSidebar } from "./PageLayoutSidebar" +export { default as PageLayoutColumn } from "./PageLayoutColumn" diff --git a/dapp/src/pages/DashboardPage/index.tsx b/dapp/src/pages/DashboardPage/index.tsx index 84d1b7f5f..40d5e80ba 100644 --- a/dapp/src/pages/DashboardPage/index.tsx +++ b/dapp/src/pages/DashboardPage/index.tsx @@ -1,6 +1,6 @@ import React from "react" import { useWallet } from "#/hooks" -import { PageLayout, PageLayoutSidebar } from "./PageLayout" +import { PageLayout, PageLayoutColumn } from "./PageLayout" import DashboardCard from "./DashboardCard" import GrantedSeasonPassCard from "./GrantedSeasonPassCard" @@ -9,14 +9,14 @@ export default function DashboardPage() { const bitcoinWalletBalance = bitcoin.account?.balance.toString() ?? "0" return ( - - - - } - > - + + + + + + + + ) } From 590011fffd2b4634c76e8cce9b59afe23906329f Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Tue, 14 May 2024 14:52:01 +0200 Subject: [PATCH 30/34] Update layout Resolve bug with empty `ul` element of `ActivityList` --- .../shared/ActivitiesList/ActivitiesList.tsx | 4 ++-- .../pages/DashboardPage/GrantedSeasonPassCard.tsx | 14 +++----------- dapp/src/pages/DashboardPage/index.tsx | 4 +++- 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/dapp/src/components/shared/ActivitiesList/ActivitiesList.tsx b/dapp/src/components/shared/ActivitiesList/ActivitiesList.tsx index d4df818ed..755d2857f 100644 --- a/dapp/src/components/shared/ActivitiesList/ActivitiesList.tsx +++ b/dapp/src/components/shared/ActivitiesList/ActivitiesList.tsx @@ -23,7 +23,7 @@ function ActivitiesList(props: ListProps) { setDismissedActivities((prev) => [...prev, txHash]) } - return ( + return activities.length > 0 ? ( {activities.map((item) => ( @@ -47,7 +47,7 @@ function ActivitiesList(props: ListProps) { ))} - ) + ) : null } export default ActivitiesList diff --git a/dapp/src/pages/DashboardPage/GrantedSeasonPassCard.tsx b/dapp/src/pages/DashboardPage/GrantedSeasonPassCard.tsx index 8ad864e54..36fb10538 100644 --- a/dapp/src/pages/DashboardPage/GrantedSeasonPassCard.tsx +++ b/dapp/src/pages/DashboardPage/GrantedSeasonPassCard.tsx @@ -10,19 +10,11 @@ import { import { IconDiscountCheckFilled, IconLock } from "@tabler/icons-react" import { TextMd } from "#/components/shared/Typography" -type GrantedSeasonPassCardProps = CardProps & { - heading: string -} - -export default function GrantedSeasonPassCard( - props: GrantedSeasonPassCardProps, -) { - const { heading, ...restProps } = props - +export default function GrantedSeasonPassCard(props: CardProps) { return ( - + - {heading} + Season 2. Pre-launch staking - + + ) From eed479f4ff7ca5ead98b7676050119816df475b2 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Tue, 14 May 2024 15:17:55 +0200 Subject: [PATCH 31/34] Add Dashboard's gamification placeholder Hid season stats --- .../images/gamification-placeholder.svg | 1 + dapp/src/pages/DashboardPage/index.tsx | 26 ++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 dapp/src/assets/images/gamification-placeholder.svg diff --git a/dapp/src/assets/images/gamification-placeholder.svg b/dapp/src/assets/images/gamification-placeholder.svg new file mode 100644 index 000000000..22847cb95 --- /dev/null +++ b/dapp/src/assets/images/gamification-placeholder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/dapp/src/pages/DashboardPage/index.tsx b/dapp/src/pages/DashboardPage/index.tsx index c583afd21..311cb3730 100644 --- a/dapp/src/pages/DashboardPage/index.tsx +++ b/dapp/src/pages/DashboardPage/index.tsx @@ -1,10 +1,16 @@ import React from "react" import { useWallet } from "#/hooks" +import { Icon, Image, VStack } from "@chakra-ui/react" +import gamificationPlaceholderImage from "#/assets/images/gamification-placeholder.svg" +import { AcreLogo } from "#/assets/icons" +import { TextMd } from "#/components/shared/Typography" import { PageLayout, PageLayoutColumn } from "./PageLayout" import DashboardCard from "./DashboardCard" import GrantedSeasonPassCard from "./GrantedSeasonPassCard" import { CurrentSeasonCard } from "./CurrentSeasonCard" +// TODO: Remove placeholder image and replace with actual gamification content + export default function DashboardPage() { const { bitcoin } = useWallet() const bitcoinWalletBalance = bitcoin.account?.balance.toString() ?? "0" @@ -16,9 +22,27 @@ export default function DashboardPage() { - + + + + + + Coming soon... + + Gamification placeholder + ) } From 39ffbc7499c4b13c702520fa5f51812b736dce66 Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Tue, 14 May 2024 15:20:16 +0200 Subject: [PATCH 32/34] Use hook instead of getting value manually --- dapp/src/pages/DashboardPage/index.tsx | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/dapp/src/pages/DashboardPage/index.tsx b/dapp/src/pages/DashboardPage/index.tsx index 311cb3730..0f40289ca 100644 --- a/dapp/src/pages/DashboardPage/index.tsx +++ b/dapp/src/pages/DashboardPage/index.tsx @@ -1,5 +1,5 @@ import React from "react" -import { useWallet } from "#/hooks" +import { useEstimatedBTCBalance } from "#/hooks" import { Icon, Image, VStack } from "@chakra-ui/react" import gamificationPlaceholderImage from "#/assets/images/gamification-placeholder.svg" import { AcreLogo } from "#/assets/icons" @@ -12,8 +12,7 @@ import { CurrentSeasonCard } from "./CurrentSeasonCard" // TODO: Remove placeholder image and replace with actual gamification content export default function DashboardPage() { - const { bitcoin } = useWallet() - const bitcoinWalletBalance = bitcoin.account?.balance.toString() ?? "0" + const bitcoinWalletBalance = useEstimatedBTCBalance() return ( From 2a72fc3915c68446bf1f993edd4e9d78a075890d Mon Sep 17 00:00:00 2001 From: Kamil Pyszkowski Date: Tue, 14 May 2024 15:53:44 +0200 Subject: [PATCH 33/34] Adjust page layout breakpoints --- dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx | 5 ++--- dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx | 4 ++-- dapp/src/pages/DashboardPage/index.tsx | 1 + dapp/src/theme/index.ts | 3 --- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx b/dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx index 0303ae3b9..041416507 100644 --- a/dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx +++ b/dapp/src/pages/DashboardPage/PageLayout/PageLayout.tsx @@ -13,9 +13,8 @@ function PageLayout(props: GridProps) { gridTemplateColumns={{ base: "1fr", md: "repeat(2, 1fr)", - xl: "0.76fr auto", - "2.5xl": - "minmax(358px, 0.25fr) minmax(748px, 1fr) minmax(358px, 0.25fr)", + lg: "1fr 0.5fr", + xl: "minmax(358px, auto) 1fr minmax(358px, auto)", }} {...restProps} > diff --git a/dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx b/dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx index 5e5b12872..78d26a4d4 100644 --- a/dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx +++ b/dapp/src/pages/DashboardPage/PageLayout/PageLayoutColumn.tsx @@ -14,9 +14,9 @@ function PageLayoutColumn(props: PageLayoutColumnProps) { gridArea={ isMain ? { - xl: "1 / 1 / 3 / 2", - "2.5xl": "1 / 2 / -1 / 3", base: "1 / 1 / -1 / -1", + lg: "1 / 1 / 3 / 2", + xl: "1 / 2 / -1 / 3", } : undefined } diff --git a/dapp/src/pages/DashboardPage/index.tsx b/dapp/src/pages/DashboardPage/index.tsx index 0f40289ca..f9dff67ac 100644 --- a/dapp/src/pages/DashboardPage/index.tsx +++ b/dapp/src/pages/DashboardPage/index.tsx @@ -31,6 +31,7 @@ export default function DashboardPage() { Coming soon... Date: Wed, 15 May 2024 07:22:41 +0200 Subject: [PATCH 34/34] Open `TransactionModal` from new dashboard --- dapp/src/pages/DashboardPage/DashboardCard.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/dapp/src/pages/DashboardPage/DashboardCard.tsx b/dapp/src/pages/DashboardPage/DashboardCard.tsx index 5b97bd116..459132cc8 100644 --- a/dapp/src/pages/DashboardPage/DashboardCard.tsx +++ b/dapp/src/pages/DashboardPage/DashboardCard.tsx @@ -15,8 +15,9 @@ import { TextMd } from "#/components/shared/Typography" import IconTag from "#/components/shared/IconTag" import { BoostArrowIcon } from "#/assets/icons" import { CurrencyBalanceWithConversion } from "#/components/shared/CurrencyBalanceWithConversion" -import { AmountType } from "#/types" +import { AmountType, MODAL_TYPES } from "#/types" import { ActivitiesList } from "#/components/shared/ActivitiesList" +import { useModal } from "#/hooks" const buttonStyles: ButtonProps = { size: "lg", @@ -35,6 +36,9 @@ type DashboardCardProps = CardProps & { export default function DashboardCard(props: DashboardCardProps) { const { bitcoinAmount, positionPercentage, ...restProps } = props + + const { openModal } = useModal() + return ( @@ -83,7 +87,12 @@ export default function DashboardCard(props: DashboardCardProps) { - +