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

Create a link sharing from a shared folder manage access #1619

Merged
merged 16 commits into from
Oct 14, 2021
5 changes: 4 additions & 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.11",
"@chainsafe/files-api-client": "^1.18.12",
"@chainsafe/web3-context": "1.1.4",
"@lingui/core": "^3.7.2",
"@lingui/react": "^3.7.2",
Expand All @@ -31,6 +31,8 @@
"ethers": "^5.4.3",
"fflate": "^0.7.1",
"formik": "^2.2.5",
"jsrsasign": "^10.4.1",
"key-encoder": "^2.0.3",
"mime-matcher": "^1.0.5",
"posthog-js": "^1.13.10",
"react": "^16.14.0",
Expand Down Expand Up @@ -61,6 +63,7 @@
"@testing-library/react": "^11.2.2",
"@testing-library/user-event": "^12.5.0",
"@types/jest": "^26.0.16",
"@types/jsrsasign": "^8.0.13",
"@types/node": "^14.14.10",
"@types/react": "^17.0.0",
"@types/react-beforeunload": "^2.1.0",
Expand Down
5 changes: 5 additions & 0 deletions packages/files-ui/src/Components/FilesRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import PurchasePlanPage from "./Pages/PurchasePlanPage"
import { useThresholdKey } from "../Contexts/ThresholdKeyContext"
import ShareFilesPage from "./Pages/SharedFilesPage"
import SharedFoldersOverview from "./Modules/FileBrowsers/SharedFoldersOverview"
import { NonceResponsePermission } from "@chainsafe/files-api-client"

export const SETTINGS_BASE = "/settings"
export const ROUTE_LINKS = {
Expand All @@ -27,6 +28,9 @@ export const ROUTE_LINKS = {
UserSurvey: "https://calendly.com/colinschwarz/chainsafe-files-chat",
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)}`,
FSM1 marked this conversation as resolved.
Show resolved Hide resolved
SharedFolderExplorer: (bucketId: string, rawCurrentPath: string) => {
// bucketId should not have a / at the end
// rawCurrentPath can be empty, or /
Expand All @@ -37,6 +41,7 @@ export const ROUTE_LINKS = {
TeamSignup: "https://shrl.ink/cgQy"
}

export const permissionPath = (permission: NonceResponsePermission) => permission === "read" ? "read" : "edit"
export const SETTINGS_PATHS = ["profile", "plan", "security"] as const
export type SettingsPath = typeof SETTINGS_PATHS[number]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { useCreateOrEditSharedFolder } from "./hooks/useCreateOrEditSharedFolder
import { useLookupSharedFolderUser } from "./hooks/useLookupUser"
import { nameValidator } from "../../../Utils/validationSchema"
import { getUserDisplayName } from "../../../Utils/getUserDisplayName"
import LinkList from "./LinkSharing/LinkList"
import clsx from "clsx"

const useStyles = makeStyles(
({ breakpoints, constants, typography, zIndex, palette }: CSFTheme) => {
Expand Down Expand Up @@ -102,6 +104,9 @@ const useStyles = makeStyles(
errorText: {
marginLeft: constants.generalUnit * 1.5,
color: palette.error.main
},
sharingLink: {
padding: 10
Tbaut marked this conversation as resolved.
Show resolved Hide resolved
}
})
}
Expand Down Expand Up @@ -186,6 +191,8 @@ const CreateOrEditSharedFolderModal = ({ mode, isModalOpen, onClose, bucketToEdi
.finally(handleClose)
}, [handleEditSharedFolder, sharedFolderWriters, sharedFolderReaders, handleClose, bucketToEdit])

if (!bucketToEdit) return null

return (
<CustomModal
className={classes.modalRoot}
Expand Down Expand Up @@ -275,6 +282,17 @@ const CreateOrEditSharedFolderModal = ({ mode, isModalOpen, onClose, bucketToEdi
noOptionsMessage={t`No user found for this query.`}
/>
</div>
{mode === "edit" && (
<div className={clsx(classes.modalFlexItem, classes.sharingLink)}>
<Typography className={classes.inputLabel}>
<Trans>Sharing link</Trans>
</Typography>
<LinkList
bucketEncryptionKey={bucketToEdit.encryptionKey}
bucketId={bucketToEdit.id}
/>
</div>
)}
<Grid
item
flexDirection="row"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@

Tbaut marked this conversation as resolved.
Show resolved Hide resolved
import { Button, MenuDropdown } from "@chainsafe/common-components"
import { createStyles, makeStyles } from "@chainsafe/common-theme"
import { NonceResponse, NonceResponsePermission } from "@chainsafe/files-api-client"
import { t, Trans } from "@lingui/macro"
import React, { useCallback, useEffect, useState } from "react"
import { useFilesApi } from "../../../../Contexts/FilesApiContext"
import { CSFTheme } from "../../../../Themes/types"
import SharingLink from "./SharingLink"

const useStyles = makeStyles(
({ constants, palette }: CSFTheme) => {
return createStyles({
root: {

Tbaut marked this conversation as resolved.
Show resolved Hide resolved
},
options: {
backgroundColor: constants.header.optionsBackground,
color: constants.header.optionsTextColor,
border: `1px solid ${constants.header.optionsBorder}`,
minWidth: 145
tanmoyAtb marked this conversation as resolved.
Show resolved Hide resolved
},
menuItem: {
width: "100%",
display: "flex",
flexDirection: "row",
alignItems: "center",
color: constants.header.menuItemTextColor,
"& svg": {
width: constants.generalUnit * 2,
height: constants.generalUnit * 2,
marginRight: constants.generalUnit,
fill: palette.additional["gray"][7],
stroke: palette.additional["gray"][7]
}
},
icon: {
"& svg": {
fill: constants.header.iconColor
}
},
menuIcon: {
display: "flex",
justifyContent: "center",
alignItems: "center",
width: 20,
marginRight: constants.generalUnit * 1.5,
fill: constants.fileSystemItemRow.menuIcon
},
permissionDropdown: {
padding: `0px ${constants.generalUnit}px`,
backgroundColor: palette.additional["gray"][5],
marginLeft: constants.generalUnit
},
createLink: {
display: "flex",
alignItems: "center",
margin: `${constants.generalUnit * 2.5}px 0`
},
createLinkButton: {
marginRight: constants.generalUnit
},
dropdownTitle: {
padding: "6px 8px"
Tbaut marked this conversation as resolved.
Show resolved Hide resolved
}
})
}
)

interface Props {
bucketId: string
bucketEncryptionKey: string
}

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


const LinkList = ({ bucketId, bucketEncryptionKey }: Props) => {
const classes = useStyles()
const { filesApiClient } = useFilesApi()
const [nonces, setNonces] = useState<NonceResponse[]>([])
const [isLoading, setIsLoading] = useState(false)
const [newLinkPermission, setNewLinkPermission] = useState<NonceResponsePermission>("read")

const refreshNonces = useCallback(() => {
setIsLoading(true)
filesApiClient.getAllNonces()
.then((res) => {
const noncesForCurrentBucket = res.filter(n => n.bucket_id === bucketId)
setNonces(noncesForCurrentBucket)
})
.catch(console.error)
.finally(() => setIsLoading(false))
}, [bucketId, filesApiClient])

useEffect(() => {
refreshNonces()
}, [filesApiClient, refreshNonces])

const onCreateNonce = useCallback(() => {

setIsLoading(true)

return filesApiClient
.createNonce({ bucket_id: bucketId, permission: newLinkPermission })
.then((n) => {
setNonces((olderNonces) => [...olderNonces, n])
})
.catch(console.error)
.finally(() => setIsLoading(false))
}, [bucketId, filesApiClient, newLinkPermission])

return (
<div className={classes.root}>
<div className={classes.createLink}>
<Button
className={classes.createLinkButton}
onClick={onCreateNonce}
loading={isLoading}
disabled={isLoading}
>
{ isLoading
? <Trans>Loading...</Trans>
: <Trans>Create new link</Trans>
}
</Button>
<Trans>with</Trans>
<MenuDropdown
title={newLinkPermission === "read" ? readMenu : editMenu}
anchor="bottom-right"
className={classes.permissionDropdown}
classNames={{
icon: classes.icon,
options: classes.options,
title: classes.dropdownTitle
}}
testId="permission"
menuItems={[
{
onClick: () => setNewLinkPermission("read"),
contents: (
<div
data-cy="menu-read"
className={classes.menuItem}
>
{readMenu}
</div>
)
},
{
onClick: () => setNewLinkPermission("write"),
contents: (
<div
data-cy="menu-write"
className={classes.menuItem}
>
{editMenu}
</div>
)
}
]}
/>
</div>
{
nonces.length > 0 && nonces.map((nonce) =>
<SharingLink
key={nonce.id}
refreshNonces={refreshNonces}
bucketEncryptionKey={bucketEncryptionKey}
nonce={nonce}
/>
)
}

Tbaut marked this conversation as resolved.
Show resolved Hide resolved
</div>
)
}

export default LinkList
Loading