Skip to content

Commit

Permalink
Rarime app modal (#64)
Browse files Browse the repository at this point in the history
* Add app modal

* Detect mobile devices

* Extract app badge component
  • Loading branch information
ardier16 authored Mar 19, 2024
1 parent 56a34a3 commit c5507ce
Show file tree
Hide file tree
Showing 14 changed files with 261 additions and 22 deletions.
1 change: 1 addition & 0 deletions src/common/AppNavbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ const AppNavbar = () => {
px={4}
borderRight={1}
borderColor={palette.divider}
sx={{ display: { xs: 'none', md: 'flex' } }}
>
<Stack spacing={4}>
<Stack component={NavLink} to={RoutePaths.Dashboard} alignItems='center'>
Expand Down
54 changes: 54 additions & 0 deletions src/common/RarimeAppBadges.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Box, Stack, StackProps, useTheme } from '@mui/material'
import { useMemo } from 'react'

import { config } from '@/config'

type AppBadgeType = 'app-store' | 'google-play'
interface AppBadgeProps {
type: AppBadgeType
link: string
}

function AppBadge({ type, link }: AppBadgeProps) {
const { spacing } = useTheme()

const imgSrc = useMemo(() => {
const srcRecord: Record<AppBadgeType, string> = {
'app-store': '/imgs/app-store-badge.svg',
'google-play': '/imgs/google-play-badge.svg',
}

return srcRecord[type]
}, [type])

const imgAlt = useMemo(() => {
const altRecord: Record<AppBadgeType, string> = {
'app-store': 'App Store',
'google-play': 'Google Play',
}

return altRecord[type]
}, [type])

return link ? (
<Stack component='a' href={link} target='_blank' rel='noreferrer'>
<Box
component='img'
src={imgSrc}
alt={imgAlt}
height={spacing(10)}
width='auto'
sx={{ objectFit: 'contain' }}
/>
</Stack>
) : null
}

export default function RarimeAppBadges(props: StackProps) {
return (
<Stack direction='row' spacing={6} {...props}>
<AppBadge type='app-store' link={config.APP_STORE_APP_LINK} />
<AppBadge type='google-play' link={config.GOOGLE_PLAY_APP_LINK} />
</Stack>
)
}
53 changes: 53 additions & 0 deletions src/common/RarimeAppModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Dialog, DialogProps, Divider, Stack, Typography, useTheme } from '@mui/material'
import { QRCode } from 'react-qrcode-logo'

import { config } from '@/config'
import { RoutePaths } from '@/enums'
import { UiDialogContent, UiDialogTitle } from '@/ui'

import RarimeAppBadges from './RarimeAppBadges'

export default function RarimeAppModal(props: DialogProps) {
const { palette, spacing } = useTheme()

return (
<Dialog
{...props}
PaperProps={{
...props.PaperProps,
sx: { width: spacing(110) },
}}
>
<UiDialogTitle onClose={props.onClose}>Rarime App</UiDialogTitle>
<UiDialogContent>
<Stack spacing={6}>
<Stack spacing={4}>
<Stack
justifyContent='center'
alignItems='center'
width={spacing(45)}
height={spacing(45)}
mx='auto'
borderRadius={4}
border={1}
borderColor={palette.divider}
>
<QRCode value={config.APP_HOST_URL + RoutePaths.DownloadApp} size={140} />
</Stack>
<Typography
variant='body3'
color={palette.text.secondary}
maxWidth={spacing(60)}
mx='auto'
textAlign='center'
>
Scan this code with your phone to download Rarime app
</Typography>
</Stack>
<Divider />
<RarimeAppBadges justifyContent='center' />
</Stack>
</UiDialogContent>
</Dialog>
)
}
2 changes: 2 additions & 0 deletions src/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@ export { default as NoDataView } from './NoDataView'
export { default as PageListFilters } from './PageListFilters'
export { default as PageTitles } from './PageTitles'
export { default as ProfileMenu } from './ProfileMenu'
export { default as RarimeAppBadges } from './RarimeAppBadges'
export { default as RarimeAppModal } from './RarimeAppModal'
export { default as UserAvatar } from './UserAvatar'
export { default as VCGroupOverviewCard } from './VCGroupOverviewCard'
4 changes: 4 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export type Config = {
DEFAULT_CHAIN: SupportedChains
ROBOTORNOT_LINK: string
SUPPORT_LINK: string
APP_STORE_APP_LINK: string
GOOGLE_PLAY_APP_LINK: string
}

const FALLBACK_DEFAULT_CHAIN = Object.entries(FALLBACK_SUPPORTED_CHAINS)[0][0]
Expand All @@ -30,4 +32,6 @@ export const config: Config = {
DEFAULT_CHAIN: import.meta.env.VITE_DEFAULT_CHAIN || FALLBACK_DEFAULT_CHAIN,
ROBOTORNOT_LINK: 'https://robotornot.mainnet-beta.rarimo.com/',
SUPPORT_LINK: 'https://rarime.com',
APP_STORE_APP_LINK: 'https://apps.apple.com',
GOOGLE_PLAY_APP_LINK: 'https://play.google.com',
}
1 change: 1 addition & 0 deletions src/enums/routes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ export enum RoutePaths {
RewardsAbout = '/rewards/about',

RewardsInvitationAlias = '/r/:code',
DownloadApp = '/download-app',
}
11 changes: 11 additions & 0 deletions src/helpers/device.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export function isIos() {
return /iPhone|iPad|iPod/i.test(navigator.userAgent)
}

export function isAndroid() {
return /Android/i.test(navigator.userAgent)
}

export function isMobile() {
return isIos() || isAndroid() || /webOS|BlackBerry|Windows Phone/i.test(navigator.userAgent)
}
1 change: 1 addition & 0 deletions src/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './device'
export * from './error-handler'
export * from './event-bus'
export * from './format'
Expand Down
26 changes: 23 additions & 3 deletions src/layouts/AuthLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,21 @@ const PublicLayout = ({ children }: PropsWithChildren) => {

return (
<Stack direction='row' height={vh(100)}>
<Stack pl={14} pr={8} py={8} flex={1}>
<Stack
py={8}
flex={1}
sx={{
pl: { xs: 4, md: 14 },
pr: { xs: 4, md: 8 },
}}
>
<Box flex={1} position='relative'>
<Stack direction='row' justifyContent='space-between' alignItems='center'>
<Stack
direction='row'
justifyContent='space-between'
alignItems='center'
sx={{ display: { xs: 'none', md: 'flex' } }}
>
<Box component='img' src='/branding/logo-sign-in.svg' alt={config.APP_NAME} />
<Button
component='a'
Expand All @@ -38,7 +50,15 @@ const PublicLayout = ({ children }: PropsWithChildren) => {
</Box>
</Stack>

<Stack justifyContent='center' alignItems='end' pl={8} bgcolor={palette.additional.pureBlack}>
<Stack
justifyContent='center'
alignItems='end'
pl={8}
bgcolor={palette.additional.pureBlack}
sx={{
display: { xs: 'none', md: 'flex' },
}}
>
<Box
component='img'
src='/imgs/dashboard.png'
Expand Down
36 changes: 36 additions & 0 deletions src/pages/DownloadApp/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Paper, Stack, Typography, useTheme } from '@mui/material'
import { useEffect } from 'react'
import { Navigate } from 'react-router-dom'

import { RarimeAppBadges } from '@/common'
import { config } from '@/config'
import { RoutePaths } from '@/enums'
import { isAndroid, isIos, isMobile } from '@/helpers'

export default function DownloadApp() {
const { palette } = useTheme()

useEffect(() => {
if (isIos() && config.APP_STORE_APP_LINK) {
location.replace(config.APP_STORE_APP_LINK)
}
if (isAndroid() && config.GOOGLE_PLAY_APP_LINK) {
location.replace(config.GOOGLE_PLAY_APP_LINK)
}
}, [])

return isMobile() ? (
<Paper component={Stack} spacing={6} sx={{ borderRadius: 2 }}>
<Stack spacing={1}>
<Typography variant='subtitle2'>Download Rarime App</Typography>
<Typography variant='body3' color={palette.text.secondary}>
Manage your identity credentials, generate Zero-Knowledge Proofs and share without
exposing personal data
</Typography>
</Stack>
<RarimeAppBadges spacing={4} />
</Paper>
) : (
<Navigate to={RoutePaths.Dashboard} replace />
)
}
24 changes: 11 additions & 13 deletions src/pages/Wallet/pages/WalletRoot/index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
import {
Button,
Divider,
IconButton,
Paper,
Skeleton,
Stack,
Typography,
useTheme,
} from '@mui/material'
import { Button, Divider, Paper, Skeleton, Stack, Typography, useTheme } from '@mui/material'
import { useCallback, useMemo, useState } from 'react'
import { NavLink } from 'react-router-dom'

import { ErrorView, NoDataView, PageTitles } from '@/common'
import { ErrorView, NoDataView, PageTitles, RarimeAppModal } from '@/common'
import { Icons, RoutePaths } from '@/enums'
import { formatBalance } from '@/helpers'
import { useLoading } from '@/hooks'
Expand All @@ -27,6 +18,7 @@ export default function WalletRoot() {

const [isReceiveModalShown, setIsReceiveModalShown] = useState(false)
const [isSendModalShown, setIsSendModalShown] = useState(false)
const [isAppModalShown, setIsAppModalShown] = useState(false)

const mainBalance = useMemo(() => balances?.[0], [balances])

Expand Down Expand Up @@ -144,9 +136,15 @@ export default function WalletRoot() {
<Typography color={palette.text.secondary}>Scan your passport</Typography>
</Stack>

<IconButton sx={{ background: palette.primary.main, p: 1, ml: 'auto' }}>
<Button
color='primary'
sx={{ p: 1, ml: 'auto', minWidth: 'auto', height: 'auto' }}
onClick={() => setIsAppModalShown(true)}
>
<UiIcon name={Icons.CaretRight} size={5} />
</IconButton>
</Button>

<RarimeAppModal open={isAppModalShown} onClose={() => setIsAppModalShown(false)} />
</Stack>
</Paper>

Expand Down
22 changes: 16 additions & 6 deletions src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
import { RoutePaths } from '@/enums'
import { useAuth } from '@/hooks'

import { createDeepPath } from './helpers'
import { createDeepPath, isMobile } from './helpers'
import AuthLayout from './layouts/AuthLayout'
import MainLayout from './layouts/MainLayout'

Expand All @@ -25,18 +25,25 @@ export const AppRoutes = () => {
const Rewards = lazy(() => import('@/pages/Rewards'))
const Wallet = lazy(() => import('@/pages/Wallet'))
const RewardsInvitationAlias = lazy(() => import('@/pages/RewardsInvitationAlias'))
const DownloadApp = lazy(() => import('@/pages/DownloadApp'))

const { isAuthorized, logout } = useAuth()

const mobileGuard = useCallback(() => {
return isMobile() ? redirect(RoutePaths.DownloadApp) : null
}, [])

const signInGuard = useCallback(
({ request }: LoaderFunctionArgs) => {
const requestUrl = new URL(request.url)

const from = requestUrl.searchParams.get('from')

return isAuthorized ? redirect(from ? `${from}${requestUrl.search}` : RoutePaths.Root) : null
return isAuthorized
? redirect(from ? `${from}${requestUrl.search}` : RoutePaths.Root)
: mobileGuard()
},
[isAuthorized],
[isAuthorized, mobileGuard],
)
const authProtectedGuard = useCallback(
({ request }: LoaderFunctionArgs) => {
Expand All @@ -52,9 +59,9 @@ export const AppRoutes = () => {
return redirect(`${RoutePaths.SignIn}${requestUrl.search}`)
}

return null
return mobileGuard()
},
[isAuthorized, logout],
[isAuthorized, logout, mobileGuard],
)

const LayoutComponent = useMemo(() => {
Expand Down Expand Up @@ -101,7 +108,6 @@ export const AppRoutes = () => {
element: <UiKit />,
},
{
index: true,
path: createDeepPath(RoutePaths.SignIn),
loader: signInGuard,
element: <SignIn />,
Expand All @@ -114,6 +120,10 @@ export const AppRoutes = () => {
path: RoutePaths.RewardsInvitationAlias,
element: <RewardsInvitationAlias />,
},
{
path: RoutePaths.DownloadApp,
element: <DownloadApp />,
},
{
path: createDeepPath(RoutePaths.AcceptInvitation),
element: <AcceptInvitation />,
Expand Down
Loading

0 comments on commit c5507ce

Please sign in to comment.