Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add Welcome component #836

Merged
merged 20 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions dapp/src/DApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,21 @@ import queryClient from "./queryClient"
import { delay, logPromiseFailure } from "./utils"
import { AcreLogo } from "./assets/icons"

function SplashPage() {
return (
<Center h="100vh" w="100vw">
<Icon as={AcreLogo} w={200} h={300} />
</Center>
)
}

function DApp() {
useInitApp()

return (
<>
<GlobalStyles />
<RouterProvider router={router} />
<RouterProvider router={router} fallbackElement={<SplashPage />} />
<ReactQueryDevtools initialIsOpen={false} />
</>
)
Expand All @@ -48,9 +56,7 @@ function DAppProviders() {
if (!config)
return (
<Fade in={!config}>
<Center h="100vh" w="100vw">
<Icon as={AcreLogo} alt="Acre logo" w={200} h={300} />
</Center>
<SplashPage />
</Fade>
)

Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
245 changes: 178 additions & 67 deletions dapp/src/components/WelcomeModal.tsx
Original file line number Diff line number Diff line change
@@ -1,83 +1,194 @@
import React from "react"
import React, { ReactElement } from "react"
import {
Button,
ModalBody,
ModalHeader,
VStack,
Image,
Box,
Flex,
ModalCloseButton,
Stepper,
Step,
StepIndicator,
ModalFooter,
useSteps,
SimpleGrid,
StepIndicatorProps,
UseStepsReturn,
Highlight,
} from "@chakra-ui/react"
import { H5, TextLg, TextMd, TextSm } from "#/components/shared/Typography"
import { useModal } from "#/hooks"
import confettiWebp from "#/assets/webps/confetti.webp"
import { BENEFITS } from "#/constants"
import { H3, TextSm } from "#/components/shared/Typography"
import { BaseModalProps } from "#/types"
import { EmbedApp } from "#/utils/referralProgram"
import { useIsEmbed } from "#/hooks"
import withBaseModal from "./ModalRoot/withBaseModal"
import step1Video from "../assets/videos/welcome-steps/welcome-step-1.mov"
import step2Video from "../assets/videos/welcome-steps/welcome-step-2.mov"
import step3Video from "../assets/videos/welcome-steps/welcome-step-3.mov"

function WelcomeModalBase() {
const { closeModal } = useModal()
const handleCloseModal = () => {
closeModal()
}
const embeddedAppToContent: Record<EmbedApp, () => ReactElement> = {
"ledger-live": () => (
<Highlight query="Ledger Live">
Acre makes earning rewards with your BTC simple and secure. Tailored for
Ledger Live everyone, it&apos;s fun and easy to use. No advanced knowledge
required.
</Highlight>
),
}

const steps = [
{
id: 0,
title: (
<H3 fontWeight="semibold">
Activate your BTC,{" "}
<Box as="span" display="block" color="orange.30">
earn rewards
</Box>
</H3>
),
content: (embeddedApp?: EmbedApp) =>
embeddedApp ? (
embeddedAppToContent[embeddedApp]()
) : (
<>
Acre makes earning rewards with your BTC simple and secure. Dedicated
to everyone, it&apos;s fun and easy to use. No advanced knowledge
required.
</>
),
video: step1Video,
kkosiorowska marked this conversation as resolved.
Show resolved Hide resolved
},
{
id: 1,
title: (
<H3 fontWeight="semibold">
<Box as="span" display="block" color="orange.30">
Battle-tested{" "}
</Box>
in the market
</H3>
),
content: () => (
<Highlight query="tBTC">
Acre is powered by tBTC, the trusted Bitcoin bridge that secured over
half a billion dollars in BTC. No centralized custodians, everything is
fully on-chain.
</Highlight>
),
video: step2Video,
},
{
id: 2,
title: (
<H3 fontWeight="semibold">
One dashboard{" "}
<Box as="span" display="block" color="orange.30">
endless rewards
</Box>
</H3>
),
content: () => (
<Highlight query="Acre Points Program">
As a depositor, you&apos;re automatically in the Acre Points Program.
Enjoy daily points drops and exclusive partner rewards. Start stacking
those points!
</Highlight>
),
video: step3Video,
},
]

const stepIndicatorStyleProps: StepIndicatorProps = {
sx: {
"[data-status=active] &": {
opacity: 1,
w: "4",
rounded: "6",
_after: {
content: '""',
position: "absolute",
opacity: "0.15",
w: "4",
h: "2.5",
rounded: "5",
left: "1.5",
bgColor: "orange.50",
},
},
"&[data-status=complete], [data-status=incomplete] &": {
bgColor: "orange.50",
},
},
border: "none",
w: "2.5",
h: "2.5",
rounded: "50%",
bgColor: "orange.50",
position: "relative",
opacity: "0.15",
}

function WelcomeModalBase({ closeModal }: BaseModalProps) {
// Cast to fix eslint error: `unbound-method`.
const { activeStep, goToNext } = useSteps({
index: 0,
count: steps.length,
}) as UseStepsReturn & { goToNext: () => void }
const { embeddedApp } = useIsEmbed()
Comment on lines +130 to +134
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably update the Chakra package, see here. But let's leave it as it is for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. But it's not a good time to update main packages just before the release.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add it to our backlog.


const isLastStep = activeStep + 1 === steps.length
const activeStepData = steps[activeStep]

return (
<>
<ModalHeader
display="flex"
flexDirection="column"
alignItems="center"
gap={3}
pb={8}
>
<H5 color="brand.400" fontWeight="semibold">
Welcome to your dashboard!
</H5>
<TextMd color="grey.600">It is time to stack up the rewards!</TextMd>
</ModalHeader>
<ModalBody gap={10}>
<Button size="lg" fontWeight="bold" onClick={handleCloseModal}>
Claim rewards
</Button>
<Flex
gap={4}
justifyContent="center"
alignItems="flex-start"
overflow="visible"
>
{BENEFITS.map(({ name, description, imageSrc }) => (
// 13.25rem -> 212px
<VStack key={name} w="13.25rem">
<Box
w="100%"
h="7.5rem" // 120px
border="1px solid white"
borderRadius="xl"
bgImage={confettiWebp}
bgSize="cover"
bgBlendMode="multiply"
bgColor="gold.200"
display="flex"
alignItems="flex-end"
>
<Image
src={imageSrc}
maxW="7.5rem" // 120px
mx="auto"
/>
</Box>
<TextLg fontWeight="bold" color="grey.700">
{name}
</TextLg>
<TextSm px={1} color="grey.600">
{description}
</TextSm>
</VStack>
))}
</Flex>
</ModalBody>
<ModalCloseButton />
<SimpleGrid columns={2} templateColumns="1fr auto">
<Box>
<ModalHeader gap={3} pb={8}>
<TextSm mb="12" color="neutral.70">
Welcome to Acre,
</TextSm>
{activeStepData.title}
</ModalHeader>
<ModalBody textAlign="left" display="block" color="brown.80" px="10">
{activeStepData.content(embeddedApp)}
</ModalBody>
<ModalFooter
display="flex"
flexDirection="row"
justifyContent="space-between"
mt="14"
>
<Stepper index={activeStep} gap="3">
{steps.map((step) => (
<Step key={step.id}>
<StepIndicator {...stepIndicatorStyleProps} />
</Step>
))}
</Stepper>
<Button
variant={isLastStep ? undefined : "outline"}
onClick={isLastStep ? closeModal : goToNext}
>
{isLastStep ? "Get started" : "Skip"}
</Button>
</ModalFooter>
</Box>
<Box
as="video"
src={activeStepData.video}
width="xs"
height="full"
autoPlay
muted
objectFit="cover"
rounded="xl"
/>
</SimpleGrid>
</>
)
}

const WelcomeModal = withBaseModal(WelcomeModalBase, { size: "xl" })
const WelcomeModal = withBaseModal(WelcomeModalBase, {
size: "xl",
})
export default WelcomeModal
9 changes: 7 additions & 2 deletions dapp/src/hooks/useAccessCode.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { useCallback } from "react"
import useLocalStorage from "./useLocalStorage"
import useLocalStorage, { getLocalStorageItem } from "./useLocalStorage"

const LOCAL_STORAGE_KEY = "acre.accessCode"

export function getAccessCodeFromLocalStorage() {
return getLocalStorageItem(LOCAL_STORAGE_KEY)
}
export default function useAccessCode() {
const [encodedCode, setAccessCode] = useLocalStorage<string | undefined>(
"acre.accessCode",
LOCAL_STORAGE_KEY,
undefined,
)

Expand Down
2 changes: 0 additions & 2 deletions dapp/src/hooks/useInitApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@ import useDetectEmbed from "./useDetectEmbed"
import useDetectReferral from "./useDetectReferral"
import { useDisconnectWallet } from "./useDisconnectWallet"
import { useFetchBTCPriceUSD } from "./useFetchBTCPriceUSD"
import useGatingDApp from "./useGatingDApp"

export function useInitApp() {
// TODO: Let's uncomment when dark mode is ready
// useDetectThemeMode()
useSentry()
useDetectEmbed()
useDetectReferral()
useGatingDApp()
kkosiorowska marked this conversation as resolved.
Show resolved Hide resolved
useInitializeAcreSdk()
useInitDataFromSdk()
useFetchBTCPriceUSD()
Expand Down
4 changes: 2 additions & 2 deletions dapp/src/hooks/useIsEmbed.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useCallback } from "react"
import referralProgram, { EmbedApp } from "#/utils/referralProgram"
import useLocalStorage from "./useLocalStorage"
import useLocalStorage, { parseLocalStorageValue } from "./useLocalStorage"

export default function useIsEmbed() {
const [embeddedApp, setEmbeddedApp] = useLocalStorage<EmbedApp | undefined>(
Expand Down Expand Up @@ -28,6 +28,6 @@ export default function useIsEmbed() {
enableIsEmbed,
disableIsEmbed,
isEmbed: referralProgram.isEmbedApp(embeddedApp),
embeddedApp,
embeddedApp: parseLocalStorageValue(embeddedApp) as EmbedApp | undefined,
}
}
15 changes: 12 additions & 3 deletions dapp/src/hooks/useLocalStorage.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
import { useLocalStorage as useRehooksLocalStorage } from "@rehooks/local-storage"

export const getLocalStorageItem = (key: string): string | undefined => {
const value = localStorage.getItem(key)
if (value === "undefined" || value === "null" || value === null)
export const parseLocalStorageValue = (value: string | null | undefined) => {
if (
value === "undefined" ||
value === "null" ||
value === null ||
value === undefined
)
return undefined

return value
}

export const getLocalStorageItem = (key: string): string | undefined => {
const value = localStorage.getItem(key)
return parseLocalStorageValue(value)
}

export default function useLocalStorage<T>(key: string, defaultValue: T) {
return useRehooksLocalStorage(key, defaultValue)
}
15 changes: 15 additions & 0 deletions dapp/src/pages/AccessPage/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import React from "react"
import { useAppNavigate } from "#/hooks"
import GateModal from "#/components/GateModal"

export default function AccessPage() {
const navigate = useAppNavigate()

return (
<GateModal
closeModal={() => {
navigate("/dashboard")
}}
/>
)
}
Loading
Loading