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

Link-sharing landing page #1620

Merged
merged 12 commits into from
Oct 13, 2021
Merged
5 changes: 3 additions & 2 deletions packages/common-components/src/Router/ConditionalRoute.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const ConditionalRoute: React.FC<IConditionalRouteProps> = ({
exact,
...rest
}) => {
const { state, pathname } = useLocation<{from?: string} | undefined>()
const { state, pathname, hash } = useLocation<{from?: string} | undefined>()
const from = (state as any)?.from

return <Route
Expand All @@ -34,7 +34,8 @@ const ConditionalRoute: React.FC<IConditionalRouteProps> = ({
? <Redirect
to={{
pathname: redirectToSource && from ? from : redirectPath,
state: { from: pathname }
state: { from: pathname },
hash
}}
/>
// this may be converted into loading
Expand Down
2 changes: 1 addition & 1 deletion packages/files-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"@babel/core": "^7.12.10",
"@babel/runtime": "^7.0.0",
"@chainsafe/browser-storage-hooks": "^1.0.1",
"@chainsafe/files-api-client": "^1.18.12",
"@chainsafe/files-api-client": "^1.18.14",
"@chainsafe/web3-context": "1.1.4",
"@lingui/core": "^3.7.2",
"@lingui/react": "^3.7.2",
Expand Down
14 changes: 11 additions & 3 deletions packages/files-ui/src/Components/FilesRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,11 @@ import { useThresholdKey } from "../Contexts/ThresholdKeyContext"
import ShareFilesPage from "./Pages/SharedFilesPage"
import SharedFoldersOverview from "./Modules/FileBrowsers/SharedFoldersOverview"
import { NonceResponsePermission } from "@chainsafe/files-api-client"
import LinkSharingLanding from "./Pages/LinkSharingLanding"

export const SETTINGS_BASE = "/settings"
export const LINK_SHARING_BASE = "/link-sharing"

export const ROUTE_LINKS = {
Landing: "/",
PrivacyPolicy: "https://files.chainsafe.io/privacy-policy",
Expand All @@ -29,8 +32,7 @@ export const ROUTE_LINKS = {
SharedFolders: "/shared-overview",
SharedFolderBrowserRoot: "/shared",
SharingLink: (permission: NonceResponsePermission, jwt: string, bucketEncryptionKey: string) =>
// eslint-disable-next-line max-len
`${window.location.origin}/sharing-link/${permissionPath(permission)}/${encodeURIComponent(jwt)}#${encodeURIComponent(bucketEncryptionKey)}`,
`${LINK_SHARING_BASE}/${permissionPath(permission)}/${encodeURIComponent(jwt)}#${encodeURIComponent(bucketEncryptionKey)}`,
SharedFolderExplorer: (bucketId: string, rawCurrentPath: string) => {
// bucketId should not have a / at the end
// rawCurrentPath can be empty, or /
Expand All @@ -53,6 +55,12 @@ const FilesRoutes = () => {
[isLoggedIn, isNewDevice, publicKey, secured, shouldInitializeAccount])
return (
<Switch>
<ConditionalRoute
path={LINK_SHARING_BASE}
isAuthorized={isAuthorized}
component={LinkSharingLanding}
redirectPath={ROUTE_LINKS.Landing}
/>
<ConditionalRoute
exact
path={ROUTE_LINKS.SharedFolders}
Expand Down Expand Up @@ -106,7 +114,7 @@ const FilesRoutes = () => {
redirectPath={ROUTE_LINKS.Landing}
/>
<ConditionalRoute
path='/'
path={ROUTE_LINKS.Landing}
isAuthorized={!isAuthorized}
component={LoginPage}
redirectPath={ROUTE_LINKS.Drive("/")}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ interface Props {
bucketEncryptionKey: string
}

export const readMenu = t`read rights`
export const editMenu = t`edit rights`

const readRights = t`read rights`
const editRights = t`edit rights`
export const translatedPermission = (permission: NonceResponsePermission) => permission === "read" ? readRights : editRights

const LinkList = ({ bucketId, bucketEncryptionKey }: Props) => {
const classes = useStyles()
Expand Down Expand Up @@ -125,7 +125,7 @@ const LinkList = ({ bucketId, bucketEncryptionKey }: Props) => {
</Button>
<Trans>with</Trans>
<MenuDropdown
title={newLinkPermission === "read" ? readMenu : editMenu}
title={translatedPermission(newLinkPermission)}
anchor="bottom-right"
className={classes.permissionDropdown}
classNames={{
Expand All @@ -142,7 +142,7 @@ const LinkList = ({ bucketId, bucketEncryptionKey }: Props) => {
data-cy="menu-read"
className={classes.menuItem}
>
{readMenu}
{readRights}
</div>
)
},
Expand All @@ -153,7 +153,7 @@ const LinkList = ({ bucketId, bucketEncryptionKey }: Props) => {
data-cy="menu-write"
className={classes.menuItem}
>
{editMenu}
{editRights}
</div>
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { useFilesApi } from "../../../../Contexts/FilesApiContext"
import { useThresholdKey } from "../../../../Contexts/ThresholdKeyContext"
import { CSFTheme } from "../../../../Themes/types"
import { ROUTE_LINKS } from "../../../FilesRoutes"
import { editMenu, readMenu } from "./LinkList"
import { translatedPermission } from "./LinkList"

const useStyles = makeStyles(
({ constants }: CSFTheme) => {
Expand Down Expand Up @@ -82,7 +82,7 @@ const SharingLink = ({ nonce, bucketEncryptionKey, refreshNonces }: Props) => {
return
}

setLink(ROUTE_LINKS.SharingLink(nonce.permission, jwt, bucketEncryptionKey))
setLink(`${window.location.origin}${ROUTE_LINKS.SharingLink(nonce.permission, jwt, bucketEncryptionKey)}`)
}, [jwt, bucketEncryptionKey, nonce])

const debouncedSwitchCopied = debounce(() => setCopied(false), 3000)
Expand Down Expand Up @@ -115,7 +115,7 @@ const SharingLink = ({ nonce, bucketEncryptionKey, refreshNonces }: Props) => {
</div>
<div className={classes.permissionWrapper}>
<Typography className={classes.link}>
{nonce.permission === "read" ? readMenu : editMenu}
{translatedPermission(nonce.permission)}
</Typography>
</div>
<Button
Expand Down
158 changes: 158 additions & 0 deletions packages/files-ui/src/Components/Modules/LinkSharingModule.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import React, { useCallback, useEffect, useMemo, useState } from "react"
import { Button, CheckCircleIcon, Loading, Typography, useHistory, useLocation } from "@chainsafe/common-components"
import { getBucketDecryptionFromHash, getJWT } from "../../Utils/pathUtils"
import { useFilesApi } from "../../Contexts/FilesApiContext"
import { useThresholdKey } from "../../Contexts/ThresholdKeyContext"
import { Trans } from "@lingui/macro"
import { useFiles } from "../../Contexts/FilesContext"
import jwtDecode from "jwt-decode"
import { createStyles, makeStyles } from "@chainsafe/common-theme"
import { CSFTheme } from "../../Themes/types"
import { ROUTE_LINKS } from "../FilesRoutes"
import { translatedPermission } from "./FileBrowsers/LinkSharing/LinkList"
import { NonceResponsePermission } from "@chainsafe/files-api-client"

const useStyles = makeStyles(
({ constants, palette, breakpoints }: CSFTheme) =>
createStyles({
root:{
display: "flex",
flexDirection: "column",
alignItems: "center"
},
box: {
backgroundColor: constants.loginModule.background,
border: `1px solid ${constants.landing.border}`,
boxShadow: constants.landing.boxShadow,
borderRadius: 6,
maxWidth: constants.generalUnit * 70,
padding: constants.generalUnit * 5,
[breakpoints.down("md")]: {
justifyContent: "center",
width: "100%"
}
},
icon : {
display: "flex",
alignItems: "center",
fontSize: constants.generalUnit * 6,
"& svg": {
marginRight: constants.generalUnit
}
},
error: {
color: palette.error.main
},
messageWrapper: {
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center"
},
browseButton : {
marginTop: constants.generalUnit * 2
}
})
)

interface DecodedJwt {
bucket_id?: string
permission?: NonceResponsePermission
}
const LinkSharingModule = () => {
const { pathname, hash } = useLocation()
const { redirect } = useHistory()
const jwt = useMemo(() => getJWT(pathname), [pathname])
const bucketDecryptionKey = useMemo(() => getBucketDecryptionFromHash(hash), [hash])
const { filesApiClient } = useFilesApi()
const { refreshBuckets, buckets } = useFiles()
const { publicKey, encryptForPublicKey } = useThresholdKey()
const [encryptedEncryptionKey, setEncryptedEncryptionKey] = useState("")
const [error, setError] = useState("")
const classes = useStyles()
const { bucket_id: bucketId, permission } = useMemo(() => {
try {
return (jwt && jwtDecode<DecodedJwt>(jwt)) || {}
}catch (e) {
console.error(e)
return {}
}
}, [jwt])
const newBucket = useMemo(() => buckets.find((b) => b.id === bucketId), [bucketId, buckets])

useEffect(() => {
if(!publicKey || !bucketDecryptionKey) return

encryptForPublicKey(publicKey, bucketDecryptionKey)
.then(setEncryptedEncryptionKey)
.catch(console.error)

}, [bucketDecryptionKey, encryptForPublicKey, publicKey])

useEffect(() => {
if(!jwt || !encryptedEncryptionKey || !!newBucket) return

filesApiClient.verifyNonce({ jwt, encryption_key: encryptedEncryptionKey })
.catch((e:any) => {
console.error(error)
setError(e.message)
tanmoyAtb marked this conversation as resolved.
Show resolved Hide resolved
})
.finally(() => {
refreshBuckets()
})
Tbaut marked this conversation as resolved.
Show resolved Hide resolved
}, [encryptedEncryptionKey, error, filesApiClient, jwt, newBucket, refreshBuckets])

const onBrowseBucket = useCallback(() => {
newBucket && redirect(ROUTE_LINKS.SharedFolderExplorer(newBucket.id, "/"))
}, [newBucket, redirect])

return (
<div className={classes.root}>
<div className={classes.box}>
<div className={classes.messageWrapper}>
{!error && !newBucket && (
<>
<Loading
type="inherit"
size={48}
className={classes.icon}
/>
<Typography variant={"h4"} >
<Trans>Adding you to the shared folder...</Trans>
</Typography>
</>
)}
{!error && newBucket && permission && (
<>
<CheckCircleIcon
size={48}
className={classes.icon}
/>
<Typography variant={"h4"} >
<Trans>
You were added to the shared folder ({translatedPermission(permission)}): {newBucket.name}
</Trans>
</Typography>
<Button
className={classes.browseButton}
onClick={onBrowseBucket}
>
<Trans>Browse {newBucket.name}</Trans>
</Button>
</>
)}
</div>
{!!error && (
<Typography
variant="body2"
className={classes.error}
>
{error}
</Typography>
)}
</div>
</div>
)
}

export default LinkSharingModule
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback, useState } from "react"
import React, { useCallback, useMemo, useState } from "react"
import {
Button,
GithubLogoIcon,
Expand All @@ -7,7 +7,8 @@ import {
Loading,
Typography,
FormikTextInput,
EthereumLogoIcon
EthereumLogoIcon,
useLocation
} from "@chainsafe/common-components"
import { createStyles, makeStyles, useThemeSwitcher } from "@chainsafe/common-theme"
import { CSFTheme } from "../../../Themes/types"
Expand All @@ -16,7 +17,7 @@ import { useFilesApi } from "../../../Contexts/FilesApiContext"
import { useWeb3 } from "@chainsafe/web3-context"
import { useThresholdKey } from "../../../Contexts/ThresholdKeyContext"
import { LOGIN_TYPE } from "@toruslabs/torus-direct-web-sdk"
import { ROUTE_LINKS } from "../../FilesRoutes"
import { LINK_SHARING_BASE, ROUTE_LINKS } from "../../FilesRoutes"
import clsx from "clsx"
import { IdentityProvider } from "@chainsafe/files-api-client"
import PasswordlessEmail from "./PasswordlessEmail"
Expand Down Expand Up @@ -91,14 +92,14 @@ const useStyles = makeStyles(
maxWidth: 240
},
headerText: {
textAlign: "center",
[breakpoints.up("md")]: {
paddingTop: constants.generalUnit * 4,
paddingBottom: constants.generalUnit * 8
},
[breakpoints.down("md")]: {
paddingTop: constants.generalUnit * 3,
paddingBottom: constants.generalUnit * 3,
textAlign: "center"
paddingBottom: constants.generalUnit * 3
}
},
footer: {
Expand Down Expand Up @@ -173,6 +174,8 @@ const InitialScreen = ({ className }: IInitialScreen) => {
const [isConnecting, setIsConnecting] = useState(false)
const { filesApiClient } = useFilesApi()
const [email, setEmail] = useState("")
const { state } = useLocation<{from?: string}>()
const isSharing = useMemo(() => state?.from?.includes(LINK_SHARING_BASE), [state])

const handleSelectWalletAndConnect = async () => {
setError(undefined)
Expand Down Expand Up @@ -379,7 +382,10 @@ const InitialScreen = ({ className }: IInitialScreen) => {
component="h1"
className={classes.headerText}
>
<Trans>Get Started</Trans>
{isSharing
? <Trans>Sign in/up to access the shared folder</Trans>
: <Trans>Get Started</Trans>
}
</Typography>
)}
{!error && (
Expand Down
11 changes: 11 additions & 0 deletions packages/files-ui/src/Components/Pages/LinkSharingLanding.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import React from "react"
import { usePageTrack } from "../../Contexts/PosthogContext"
import LinkSharingModule from "../Modules/LinkSharingModule"

const LinkSharingLanding = () => {
usePageTrack()

return <LinkSharingModule/>
}

export default LinkSharingLanding
3 changes: 0 additions & 3 deletions packages/files-ui/src/Components/Pages/LoginPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import BottomDarkSVG from "../../Media/landing/layers/dark/Bottom.dark.svg"
import TopDarkSVG from "../../Media/landing/layers/dark/Top.dark.svg"
import BottomLightSVG from "../../Media/landing/layers/light/Bottom.light.svg"
import TopLightSVG from "../../Media/landing/layers/light/Top.light.svg"
// import { ForegroundSVG } from "../../Media/landing/layers/ForegroundSVG"
import MigrateAccount from "../Modules/LoginModule/MigrateAccount"
import InitializeAccount from "../Modules/LoginModule/InitializeAccount"
import { useFilesApi } from "../../Contexts/FilesApiContext"
Expand Down Expand Up @@ -167,8 +166,6 @@ const LoginPage = () => {
<ChainsafeFilesLogo className={classes.filesLogo} />
ChainSafe Files
</Typography>
<>
</>
{
themeKey === "dark"
? <>
Expand Down
Loading