diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000000..1f16343693 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,19 @@ +closes # + +--- + +Submission checklist: + +> Remove anything below that is not applicable + +- [x] Functionality +- - [x] Feature works as intended after change +- - [x] Applicable dependancies have been deployed + +- [x] Layout +- - [x] Change looks good in the desktop web ui +- - [x] Change looks good in the mobile web ui + +- [x] Theme +- - [x] Components / elements inspected in light mode +- - [x] Components / elements inspected in dark mode \ No newline at end of file diff --git a/.github/workflows/test-files-on-demand.yml b/.github/workflows/test-files-on-demand.yml new file mode 100644 index 0000000000..15b3349453 --- /dev/null +++ b/.github/workflows/test-files-on-demand.yml @@ -0,0 +1,62 @@ +name: Cypress tests - Files (On Demand) +on: [workflow_dispatch] +jobs: + cypress-run: + runs-on: ubuntu-latest + container: cypress/browsers:node14.17.0-chrome91-ff89 + steps: + - name: Checkout + uses: actions/checkout@v2 + + - uses: actions/cache@v2 + id: yarn-build-cache + with: + path: | + **/node_modules + ~/.cache/Cypress + **/build + key: ${{ runner.os }}-node_modules-files-build-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-node_modules-build- + + # Install NPM dependencies, cache them correctly + # and run all Cypress tests + - name: Cypress run + uses: cypress-io/github-action@v2 + env: + REACT_APP_API_URL: ${{ secrets.GH_REACT_APP_API_URL }} + REACT_APP_BLOCKNATIVE_ID: ${{ secrets.GH_REACT_APP_BLOCKNATIVE_ID }} + REACT_APP_FILES_VERIFIER_NAME: ${{ secrets.GH_REACT_APP_FILES_VERIFIER_NAME }} + REACT_APP_FILES_UUID_VERIFIER_NAME: 'chainsafe-uuid-testnet' + REACT_APP_TEST: 'true' + DEBUG: '@cypress/github-action' + with: + start: yarn start:files-ui + # wait for 10min for the server to be ready + wait-on: 'npx wait-on --timeout 600000 http://localhost:3000' + # custom test command to run + command: yarn test:ci:files-ui + # store the screenshots if the tests fail + - name: Store screenshots + uses: actions/upload-artifact@v1 + if: failure() + with: + name: cypress-screenshots + path: packages/files-ui/cypress/screenshots + # store the videos if the tests fail + # - name: Store videos + # uses: actions/upload-artifact@v1 + # if: failure() + # with: + # name: cypress-videos + # path: packages/files-ui/cypress/videos + + - name: Slack Notification + uses: rtCamp/action-slack-notify@v2.2.0 + env: + SLACK_TITLE: 'Files UI Test Suite On-Demand Result:' + SLACK_MESSAGE: ${{ job.status }} + SLACK_COLOR: ${{ job.status }} + MSG_MINIMAL: actions url + SLACK_WEBHOOK: ${{ secrets.SLACK_UI_WEBHOOK }} + SLACK_FOOTER: 'Test run ${{ github.run_number }} was executed on branch: ${{ github.ref }}' diff --git a/packages/common-components/src/Button/Button.tsx b/packages/common-components/src/Button/Button.tsx index 0900773aaf..05ae395162 100644 --- a/packages/common-components/src/Button/Button.tsx +++ b/packages/common-components/src/Button/Button.tsx @@ -93,7 +93,7 @@ const useStyles = makeStyles( fill: palette.common.white.main }, "&:hover": { - backgroundColor: palette.primary.main, + backgroundColor: palette.primary.hover, color: palette.common.white.main, ...overrides?.Button?.variants?.secondary?.hover }, @@ -109,6 +109,20 @@ const useStyles = makeStyles( }, ...overrides?.Button?.variants?.secondary?.root }, + text: { + backgroundColor: "transparent", + color: palette.additional["gray"][9], + "&:hover": { + ...overrides?.Button?.variants?.text?.hover + }, + "&:focus": { + ...overrides?.Button?.variants?.text?.focus + }, + "&:active": { + ...overrides?.Button?.variants?.text?.active + }, + ...overrides?.Button?.variants?.text?.root + }, tertiary: { backgroundColor: palette.additional["gray"][3], color: palette.common.black.main, @@ -293,7 +307,7 @@ interface IButtonProps extends Omit { className?: string children?: ReactNode | ReactNode[] fullsize?: boolean - variant?: "link" | "primary" | "secondary" |"tertiary" | "outline" | "dashed" | "danger" + variant?: "link" | "primary" | "secondary" |"tertiary" | "outline" | "dashed" | "danger" | "text" iconButton?: boolean size?: "large" | "medium" | "small" type?: "button" | "submit" | "reset" diff --git a/packages/common-components/src/CheckboxInput/CheckboxInput.tsx b/packages/common-components/src/CheckboxInput/CheckboxInput.tsx index 7d508154fa..8a1f4375a3 100644 --- a/packages/common-components/src/CheckboxInput/CheckboxInput.tsx +++ b/packages/common-components/src/CheckboxInput/CheckboxInput.tsx @@ -10,6 +10,7 @@ const useStyles = makeStyles( root: { cursor: "pointer", display: "flex", + alignItems: "center", ...overrides?.CheckboxInput?.root }, checkbox: { diff --git a/packages/common-components/src/Dialog/Dialog.tsx b/packages/common-components/src/Dialog/Dialog.tsx index 039d9690c8..d70fb04f80 100644 --- a/packages/common-components/src/Dialog/Dialog.tsx +++ b/packages/common-components/src/Dialog/Dialog.tsx @@ -7,13 +7,10 @@ import { Button, IButtonProps } from "../Button" const useStyles = makeStyles(({ breakpoints, constants }: ITheme) => createStyles({ - // JSS in CSS goes here root: {}, inner: { padding: constants.generalUnit * 2, borderRadius: 2, - transform: "translate(-50%, 0)", - top: constants.generalUnit * 2, [breakpoints.down("sm")]: { maxWidth: `calc(100% - ${constants.generalUnit * 3}px) !important` } diff --git a/packages/common-components/src/Icons/svgs/document.svg b/packages/common-components/src/Icons/svgs/document.svg index da7d381789..5edd45259b 100644 --- a/packages/common-components/src/Icons/svgs/document.svg +++ b/packages/common-components/src/Icons/svgs/document.svg @@ -1,3 +1 @@ - - - \ No newline at end of file + diff --git a/packages/common-components/src/Icons/svgs/ethereum-logo.svg b/packages/common-components/src/Icons/svgs/ethereum-logo.svg index 15eb15600a..7fe1413ff0 100644 --- a/packages/common-components/src/Icons/svgs/ethereum-logo.svg +++ b/packages/common-components/src/Icons/svgs/ethereum-logo.svg @@ -1,13 +1 @@ - - - - - - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/packages/common-components/src/MenuDropdown/MenuDropdown.tsx b/packages/common-components/src/MenuDropdown/MenuDropdown.tsx index f4b799141a..b5ac4c6e4f 100644 --- a/packages/common-components/src/MenuDropdown/MenuDropdown.tsx +++ b/packages/common-components/src/MenuDropdown/MenuDropdown.tsx @@ -1,9 +1,10 @@ -import React, { ReactNode, useRef, useState } from "react" +import React, { ReactNode, useCallback, useRef, useState } from "react" import { makeStyles, createStyles, ITheme, useOnClickOutside } from "@chainsafe/common-theme" import { Typography } from "../Typography" import clsx from "clsx" import { DirectionalDownIcon, SvgIcon } from "../Icons" import { Paper } from "../Paper" +import { useOnScroll } from "../Scroll/useOnScroll.hook" const useStyles = makeStyles( ({ constants, animation, typography, palette, overrides }: ITheme) => @@ -83,34 +84,64 @@ const useStyles = makeStyles( zIndex: 1000, padding: 0, "&.top-left": { - top: 0, + "&.up": { + bottom: 0 + }, + "&.down": { + top: 0 + }, left: 0, ...overrides?.MenuDropdown?.options?.position?.topLeft }, "&.top-center": { - top: 0, + "&.up": { + bottom: 0 + }, + "&.down": { + top: 0 + }, left: "50%", transform: "translateX(-50%)", ...overrides?.MenuDropdown?.options?.position?.topCenter }, "&.top-right": { - top: 0, + "&.up": { + bottom: 0 + }, + "&.down": { + top: 0 + }, right: 0, ...overrides?.MenuDropdown?.options?.position?.topRight }, "&.bottom-left": { - top: "100%", + "&.up": { + bottom: "100%" + }, + "&.down": { + top: "100%" + }, left: 0, ...overrides?.MenuDropdown?.options?.position?.bottomLeft }, "&.bottom-center": { - top: "100%", + "&.up": { + bottom: "100%" + }, + "&.down": { + top: "100%" + }, left: "50%", transform: "translateX(-50%)", ...overrides?.MenuDropdown?.options?.position?.bottomCenter }, "&.bottom-right": { - top: "100%", + "&.up": { + bottom: "100%" + }, + "&.down": { + top: "100%" + }, right: 0, ...overrides?.MenuDropdown?.options?.position?.bottomRight }, @@ -168,7 +199,8 @@ interface IMenuDropdownProps { | "bottom-left" | "bottom-center" | "bottom-right" - menuItems?: IMenuItem[] + dynamicAnchor?: boolean + menuItems: IMenuItem[] title?: string classNames?: { icon?: string @@ -208,6 +240,24 @@ const MenuDropdown = ({ } }) + const [dropDirection, setDropDirection] = useState<"up" | "down">("down") + + const managePosition = useCallback(() => { + // TODO: Debounce + const { offsetTop } = ref.current as unknown as HTMLDivElement + + if (offsetTop < window.pageYOffset + (window.innerHeight / 2)) { + setDropDirection("down") + } else { + setDropDirection("up") + } + }, [ref]) + + + useOnScroll({ + onScroll: managePosition + }) + return (
diff --git a/packages/common-components/src/Modal/Modal.tsx b/packages/common-components/src/Modal/Modal.tsx index 9a7dde4899..92d95e9bc4 100644 --- a/packages/common-components/src/Modal/Modal.tsx +++ b/packages/common-components/src/Modal/Modal.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode, useRef } from "react" +import React, { ReactNode, useEffect, useRef } from "react" import { ITheme, useOnClickOutside, makeStyles, createStyles } from "@chainsafe/common-theme" import clsx from "clsx" import { CloseSvg } from "../Icons/icons/Close.icon" @@ -43,6 +43,7 @@ const useStyles = makeStyles( "&.active": { visibility: "visible", opacity: 1, + zIndex: 2500, "& > *": { opacity: 1, visibility: "visible" @@ -51,17 +52,49 @@ const useStyles = makeStyles( }, ...overrides?.Modal?.root }, - inner: { + modalSection: { ...constants.modal.inner, - flexGrow: 1, - flexDirection: "column", - display: "flex", backgroundColor: palette.common?.white.main, - top: "50%", - left: "50%", + zIndex: 1, + "&.subModal": { + marginTop: "0.5rem" + } + }, + closeIcon: { + ...constants.icon, + width: 15, + height: 15, + display: "block", + top: 0, + cursor: "pointer", position: "absolute", - borderRadius: `${constants.generalUnit / 2}`, - transform: "translate(-50%, -50%)", + zIndex: 2, + "& svg": { + stroke: palette.common?.black.main + }, + "&.right": { + transform: "translate(-50%, 50%)", + right: 0, + ...overrides?.Modal?.closeIcon?.right + }, + "&.left": { + left: 0, + transform: "translate(50%, -50%)", + ...overrides?.Modal?.closeIcon?.left + }, + "&.none": { + display: "none" + }, + ...overrides?.Modal?.closeIcon?.root + }, + wrapper : { + position: "relative", + flexDirection: "column", + display: "flex", + margin: "auto", + maxHeight: "100%", + overflow: "auto", + alignItems: "center", "&.xs": { width: `calc(100% - ${constants.generalUnit * 2}px)`, maxWidth: breakpoints.width("xs"), @@ -88,32 +121,6 @@ const useStyles = makeStyles( ...overrides?.Modal?.inner?.size?.xl }, ...overrides?.Modal?.inner?.root - }, - closeIcon: { - ...constants.icon, - width: 15, - height: 15, - display: "block", - top: 0, - cursor: "pointer", - position: "absolute", - "& svg": { - stroke: palette.common?.black.main - }, - "&.right": { - transform: "translate(-50%, 50%)", - right: 0, - ...overrides?.Modal?.closeIcon?.right - }, - "&.left": { - left: 0, - transform: "translate(50%, -50%)", - ...overrides?.Modal?.closeIcon?.left - }, - "&.none": { - display: "none" - }, - ...overrides?.Modal?.closeIcon?.root } }) ) @@ -121,11 +128,12 @@ const useStyles = makeStyles( interface IModalClasses { inner?: string closeIcon?: string + subModalInner?: string } interface IModalProps { className?: string - active: boolean + active?: boolean injectedClass?: IModalClasses closePosition?: "left" | "right" | "none" children?: ReactNode | ReactNode[] @@ -134,6 +142,27 @@ interface IModalProps { onClickOutside?: (e?: React.MouseEvent) => void testId?: string onClose?: () => void + subModal?: ReactNode | ReactNode[] +} + +interface IModalBaseProps { + children?: ReactNode | ReactNode[] + injectedClassInner?: string +} + +const ModalBase = ({ children, injectedClassInner }: IModalBaseProps) => { + const classes = useStyles() + + return ( +
+ {children} +
+ ) } const Modal = ({ @@ -146,12 +175,22 @@ const Modal = ({ onModalBodyClick, testId, onClose, - onClickOutside + onClickOutside, + subModal }: IModalProps) => { const classes = useStyles() - const ref = useRef(null) + useEffect(() => { + if(!active) return + + document.body.style.overflow = "hidden" + + return () => { + document.body.style.overflow = "auto" + } + }, [active]) + const handleClose = () => { onClose && onClose() } @@ -164,10 +203,10 @@ const Modal = ({
{closePosition !== "none" && (
)} - {children} + + {children} + + {subModal && ( + + {subModal} + + )}
) diff --git a/packages/common-components/src/Scroll/index.ts b/packages/common-components/src/Scroll/index.ts index c130bc4b61..14719ea26d 100644 --- a/packages/common-components/src/Scroll/index.ts +++ b/packages/common-components/src/Scroll/index.ts @@ -1 +1,2 @@ export { useScrollToTop } from "./ScrollToTop.hook" +export { useOnScroll } from "./useOnScroll.hook" \ No newline at end of file diff --git a/packages/common-components/src/Scroll/useOnScroll.hook.tsx b/packages/common-components/src/Scroll/useOnScroll.hook.tsx new file mode 100644 index 0000000000..48619c71d8 --- /dev/null +++ b/packages/common-components/src/Scroll/useOnScroll.hook.tsx @@ -0,0 +1,35 @@ +import { useEffect, useState } from "react" + +interface IUseOnScroll { + onScroll?: (e: Event) => void + onScrollUp?: (e: Event) => void + onScrollDown?: (e: Event) => void +} + +export function useOnScroll({ + onScroll, + onScrollDown, + onScrollUp +}: IUseOnScroll) { + const [scrollTop, setScrollTop] = useState(0) + useEffect(() => { + function onScrollFunc(e: Event) { + const currentPosition = document.documentElement.scrollTop + if (onScroll && scrollTop != currentPosition) { + onScroll(e) + } + if (currentPosition > scrollTop) { + // downscroll code + onScrollDown && onScrollDown(e) + } else { + // upscroll code + onScrollUp && onScrollUp(e) + } + setScrollTop(currentPosition <= 0 ? 0 : currentPosition) + } + + window.addEventListener("scroll", onScrollFunc) + return () => window.removeEventListener("scroll", onScrollFunc) + }, [scrollTop, onScrollDown, onScrollUp, onScroll]) + +} \ No newline at end of file diff --git a/packages/common-components/src/SelectInput/SelectInput.tsx b/packages/common-components/src/SelectInput/SelectInput.tsx index 802587398d..2af294f69b 100644 --- a/packages/common-components/src/SelectInput/SelectInput.tsx +++ b/packages/common-components/src/SelectInput/SelectInput.tsx @@ -8,7 +8,6 @@ const useStyles = makeStyles( ({ animation, constants, palette, overrides }: ITheme) => createStyles({ root: { - margin: 5, display: "block", ...overrides?.SelectInput?.root }, diff --git a/packages/common-components/src/Table/TableCell.tsx b/packages/common-components/src/Table/TableCell.tsx index a0f8a2f570..ce3adcdfe5 100644 --- a/packages/common-components/src/Table/TableCell.tsx +++ b/packages/common-components/src/Table/TableCell.tsx @@ -31,6 +31,7 @@ export interface ITableCellProps { align?: AlignOption onClick?: (e?: React.MouseEvent) => void } + const TableCell = React.forwardRef( ( { diff --git a/packages/common-components/src/stories/Button.stories.tsx b/packages/common-components/src/stories/Button.stories.tsx index cffaf6a3b2..2f26a91e4e 100644 --- a/packages/common-components/src/stories/Button.stories.tsx +++ b/packages/common-components/src/stories/Button.stories.tsx @@ -15,12 +15,14 @@ export const actionsData = { onClick: action("onClickButton") } -type VariantOption = "primary" | "secondary" | "tertiary" | "outline" | "dashed" | "danger" | undefined; +type VariantOption = "primary" | "secondary" | "tertiary" | "link" | "text" | "outline" | "dashed" | "danger" | undefined; const variantOptions: VariantOption[] = [ "primary", "outline", "dashed", "danger", + "link", + "text", undefined ] diff --git a/packages/common-theme/src/Defaults/ThemeConfig.ts b/packages/common-theme/src/Defaults/ThemeConfig.ts index 86acc99a74..ebdaf7211f 100644 --- a/packages/common-theme/src/Defaults/ThemeConfig.ts +++ b/packages/common-theme/src/Defaults/ThemeConfig.ts @@ -34,7 +34,6 @@ const DefaultThemeConfig: IThemeConfig = { modal: { inner: { minWidth: 30, - minHeight: 30, maxWidth: defaultBreakpoints.keys["md"] }, backgroundFade: 0.4 diff --git a/packages/common-theme/src/Hooks/useLongPress.tsx b/packages/common-theme/src/Hooks/useLongPress.tsx index 0833e056ba..a5c305b0e8 100644 --- a/packages/common-theme/src/Hooks/useLongPress.tsx +++ b/packages/common-theme/src/Hooks/useLongPress.tsx @@ -1,4 +1,4 @@ -import { useCallback, useRef, useState } from "react" +import { useCallback, useRef } from "react" export interface LongPressEvents { onMouseDown: (e: React.MouseEvent) => void @@ -6,6 +6,30 @@ export interface LongPressEvents { onMouseUp: (e: React.MouseEvent) => void onMouseLeave: (e: React.MouseEvent) => void onTouchEnd: (e: React.TouchEvent) => void + onTouchMove: (e: React.TouchEvent) => void + onMouseMove: (e: React.MouseEvent) => void +} + +const MOVE_THRESHOLD = 5 + +type Coordinates = { + x: number + y: number +} | null; + +function getCurrentPosition(event: any): Coordinates { + if (isTouchEvent(event)) { + return { + x: event.touches[0].pageX, + y: event.touches[0].pageY + } + } + else { + return { + x: event.pageX, + y: event.pageY + } + } } export const useLongPress = ( @@ -13,9 +37,10 @@ export const useLongPress = ( onClick: ((e?: React.MouseEvent) => void) | null, delay = 300 ): LongPressEvents => { - const [longPressTriggered, setLongPressTriggered] = useState(false) + const shouldAllowClick = useRef(true) const timeout: any = useRef() const target: any = useRef() + const startPosition = useRef(null) const start = useCallback( (event: any) => { @@ -25,9 +50,11 @@ export const useLongPress = ( }) target.current = event.target } + shouldAllowClick.current = true + startPosition.current = getCurrentPosition(event) timeout.current = setTimeout(() => { onLongPress && onLongPress(event) - setLongPressTriggered(true) + shouldAllowClick.current = false }, delay) }, [onLongPress, delay] @@ -36,31 +63,55 @@ export const useLongPress = ( const clear = useCallback( (shouldTriggerClick = true) => { timeout.current && clearTimeout(timeout.current) - shouldTriggerClick && !longPressTriggered && onClick && onClick() - setLongPressTriggered(false) + shouldTriggerClick && shouldAllowClick.current && onClick && onClick() + shouldAllowClick.current = false if (target.current) { target.current.removeEventListener("touchend", preventDefault) } - }, [onClick, longPressTriggered] + }, [onClick] + ) + + const move = useCallback( + (event: any) => { + if (startPosition.current) { + const currentPosition = getCurrentPosition(event) + if (currentPosition) { + const movedDistance = { + x: Math.abs(currentPosition.x - startPosition.current.x), + y: Math.abs(currentPosition.y - startPosition.current.y) + } + + if (movedDistance.x > MOVE_THRESHOLD || movedDistance.y > MOVE_THRESHOLD) { + clear(false) + } + } + } + }, [clear] ) return { + // start onMouseDown: (e: React.MouseEvent) => start(e), onTouchStart: (e: React.TouchEvent) => start(e), - onMouseUp: (e: React.MouseEvent) => clear(e), + // end + onMouseUp: () => clear(true), + onTouchEnd: () => clear(true), + // leave onMouseLeave: () => clear(false), - onTouchEnd: (e: React.TouchEvent) => clear(e) + // move + onTouchMove: (e: React.TouchEvent) => move(e), + onMouseMove: (e: React.MouseEvent) => move(e) } } const isTouchEvent = (event: any) => { - return "touches" in event + return event && "touches" in event } const preventDefault = (event: any) => { if (!isTouchEvent(event)) return - if (event.touches.length < 2 && event.preventDefault) { + if (event.touches.length < 2 && event.preventDefault && event.cancelable) { event.preventDefault() } } diff --git a/packages/common-theme/src/Overrides/Button.ts b/packages/common-theme/src/Overrides/Button.ts index 106c92e11c..6f44e7e8a6 100644 --- a/packages/common-theme/src/Overrides/Button.ts +++ b/packages/common-theme/src/Overrides/Button.ts @@ -42,6 +42,18 @@ export interface IButtonOverride { focus?: Record active?: Record } + link?: { + root?: Record + hover?: Record + focus?: Record + active?: Record + } + text?: { + root?: Record + hover?: Record + focus?: Record + active?: Record + } } state?: { danger?: { diff --git a/packages/common-theme/src/Provider/ThemeSwitcherContext.tsx b/packages/common-theme/src/Provider/ThemeSwitcherContext.tsx index ffe8dc549a..aafb1d886c 100644 --- a/packages/common-theme/src/Provider/ThemeSwitcherContext.tsx +++ b/packages/common-theme/src/Provider/ThemeSwitcherContext.tsx @@ -10,6 +10,8 @@ import { useLocalStorage } from "@chainsafe/browser-storage-hooks" type ThemeSwitcherContext = { desktop: boolean + tablet: boolean + mobile: boolean themeKey: string availableThemes: string[] setTheme(themeName: string): void @@ -26,6 +28,8 @@ type ThemeSwitcherProps = { const ThemeSwitcher = ({ children, themes, storageKey = "cs.themeKey" }: ThemeSwitcherProps) => { const breakpoints = createBreakpoints({}) const desktop = useMediaQuery(breakpoints.up("md")) + const tablet = useMediaQuery(breakpoints.up("sm")) + const mobile = useMediaQuery(breakpoints.up("xs")) const { canUseLocalStorage, localStorageGet, localStorageSet } = useLocalStorage() // TODO: check min 1 theme @@ -60,6 +64,8 @@ const ThemeSwitcher = ({ children, themes, storageKey = "cs.themeKey" }: ThemeSw { cy.on("window:before:load", (win) => { @@ -96,11 +98,15 @@ Cypress.Commands.add( apiTestHelper.clearBucket("csf") } + if (deleteShareBucket) { + apiTestHelper.deleteSharedFolders() + } + if (clearTrashBucket) { apiTestHelper.clearBucket("trash") } - if(clearTrashBucket || clearCSFBucket){ + if(clearTrashBucket || clearCSFBucket || deleteShareBucket){ navigationMenu.binNavButton().click() navigationMenu.homeNavButton().click() } @@ -130,6 +136,7 @@ declare global { * @param {Boolean} options.saveBrowser - (default: false) - save the browser to localstorage. * @param {Boolean} options.clearCSFBucket - (default: false) - whether any file in the csf bucket should be deleted. * @param {Boolean} options.clearTrashBucket - (default: false) - whether any file in the trash bucket should be deleted. + * @param {Boolean} options.deleteShareBucket - (default: false) - whether any shared bucket should be deleted. * @example cy.web3Login({saveBrowser: true, url: 'http://localhost:8080'}) */ web3Login: (options?: Web3LoginOptions) => Chainable @@ -146,7 +153,7 @@ declare global { * https://github.com/cypress-io/cypress/issues/7306 * */ - safeClick: () => Chainable + safeClick: () => Chainable } } diff --git a/packages/files-ui/cypress/support/page-objects/basePage.ts b/packages/files-ui/cypress/support/page-objects/basePage.ts index 322ecccf84..8fadff7b66 100644 --- a/packages/files-ui/cypress/support/page-objects/basePage.ts +++ b/packages/files-ui/cypress/support/page-objects/basePage.ts @@ -6,5 +6,11 @@ export const basePage = { signOutDropdown: () => cy.get("[data-testid=menu-title-sign-out]"), signOutMenuOption: () => cy.get("[data-cy=menu-sign-out]"), // Mobile view only element - hamburgerMenuButton: () => cy.get("[data-testId=hamburger-menu]") + hamburgerMenuButton: () => cy.get("[data-testId=hamburger-menu]"), + + // helpers and convenience functions + awaitBucketRefresh() { + cy.intercept("POST", "**/bucket/*/ls").as("refresh") + cy.wait("@refresh") + } } diff --git a/packages/files-ui/cypress/support/page-objects/homePage.ts b/packages/files-ui/cypress/support/page-objects/homePage.ts index 337e56f6a4..23c70df645 100644 --- a/packages/files-ui/cypress/support/page-objects/homePage.ts +++ b/packages/files-ui/cypress/support/page-objects/homePage.ts @@ -15,8 +15,7 @@ export const homePage = { moveSelectedButton: () => cy.get("[data-testId=button-move-selected-file]"), deleteSelectedButton: () => cy.get("[data-testId=button-delete-selected-file]"), selectAllCheckbox: () => cy.get("[data-testId=checkbox-select-all]"), - fileRenameInput: () => cy.get("[data-cy=rename-form] input"), - fileRenameSubmitButton: () => cy.get("[data-cy=rename-submit-button]"), + fileRenameInput: () => cy.get("[data-cy=input-rename-file-or-folder]"), fileRenameErrorLabel: () => cy.get("[data-cy=rename-form] span.minimal.error"), // kebab menu elements diff --git a/packages/files-ui/cypress/support/page-objects/modals/createSharedFolderModal.ts b/packages/files-ui/cypress/support/page-objects/modals/createSharedFolderModal.ts new file mode 100644 index 0000000000..60659ab35d --- /dev/null +++ b/packages/files-ui/cypress/support/page-objects/modals/createSharedFolderModal.ts @@ -0,0 +1,11 @@ +export const createEditSharedFolderModal = { + body: () => cy.get("[data-testid=modal-container-create-or-edit-shared-folder]", { timeout: 10000 }), + cancelButton: () => cy.get("[data-cy=button-cancel-create-shared-folder]"), + createButton: () => cy.get("[data-cy=button-create-shared-folder]", { timeout: 10000 }), + editPermissionInput: () => cy.get("[data-cy=input-edit-permission]"), + folderNameInput: () => cy.get("[data-cy=input-shared-folder-name]"), + tagViewPermissionUser: () => cy.get("[data-cy=tag-view-permission-user]"), + tagEditPermissionUser: () => cy.get("[data-cy=tag-edit-permission-user]"), + updateButton: () => cy.get("[data-cy=button-update-shared-folder]", { timeout: 10000 }), + viewOnlyPermissionInput: () => cy.get("[data-cy=input-view-permission") +} \ No newline at end of file diff --git a/packages/files-ui/cypress/support/page-objects/modals/deleteSharedFolderModal.ts b/packages/files-ui/cypress/support/page-objects/modals/deleteSharedFolderModal.ts new file mode 100644 index 0000000000..cd14a6b791 --- /dev/null +++ b/packages/files-ui/cypress/support/page-objects/modals/deleteSharedFolderModal.ts @@ -0,0 +1,5 @@ +export const deleteSharedFolderModal = { + body: () => cy.get("[data-testid=modal-container-shared-folder-deletion]"), + cancelButton: () => cy.get("[data-testid=button-cancel-deletion]"), + confirmButton: () => cy.get("[data-testid=button-confirm-deletion]", { timeout: 10000 }) +} \ No newline at end of file diff --git a/packages/files-ui/cypress/support/page-objects/sharedPage.ts b/packages/files-ui/cypress/support/page-objects/sharedPage.ts new file mode 100644 index 0000000000..072482022d --- /dev/null +++ b/packages/files-ui/cypress/support/page-objects/sharedPage.ts @@ -0,0 +1,19 @@ +import { basePage } from "./basePage" +import { fileBrowser } from "./fileBrowser" + +export const sharedPage = { + ...basePage, + ...fileBrowser, + + createSharedFolderButton: () => cy.get("[data-cy=button-create-a-shared-folder]"), + sharedFolderItemName: () => cy.get("[data-cy=shared-folder-item-name]"), + sharedFolderItemRow: () => cy.get("[data-cy=shared-folder-item-row]", { timeout: 20000 }), + shareRenameInput: () => cy.get("[data-cy=input-rename-share]"), + + // kebab menu elements + deleteMenuOption: () => cy.get("[data-cy=menu-delete]"), + leaveMenuOption: () => cy.get("[data-cy=menu-leave]"), + manageAccessMenuOption: () => cy.get("[data-cy=menu-manage-access]"), + renameMenuOption: () => cy.get("[data-cy=menu-rename]"), + uploadButton: () => cy.get("[data-cy=button-upload-file]") +} diff --git a/packages/files-ui/cypress/support/utils/apiTestHelper.ts b/packages/files-ui/cypress/support/utils/apiTestHelper.ts index 06286bf47a..50cd2e053a 100644 --- a/packages/files-ui/cypress/support/utils/apiTestHelper.ts +++ b/packages/files-ui/cypress/support/utils/apiTestHelper.ts @@ -7,11 +7,35 @@ import { homePage } from "../page-objects/homePage" const API_BASE_USE = "https://stage.imploy.site/api/v1" const REFRESH_TOKEN_KEY = "csf.refreshToken" +const getApiClient = () => { + // Disable the internal Axios JSON deserialization as this is handled by the client + const axiosInstance = axios.create({ transformResponse: [] }) + const apiClient = new FilesApiClient({}, API_BASE_USE, axiosInstance) + + return apiClient +} export const apiTestHelper = { + deleteSharedFolders() { + const apiClient = getApiClient() + + return new Cypress.Promise(async (resolve) => { + cy.window() + .then(async (win) => { + const tokens = await apiClient.getRefreshToken({ refresh: win.sessionStorage.getItem(REFRESH_TOKEN_KEY) || "" }) + + await apiClient.setToken(tokens.access_token.token) + const buckets = await apiClient.listBuckets(["share"]) + buckets.forEach(async (bucket) => { + cy.log(`Deleting shared bucket: "${bucket.name}""`) + await apiClient.removeBucket(bucket.id) + }) + cy.log("Done deleting shared buckets.") + resolve() + }) + }) + }, clearBucket(bucketType: BucketType) { - // Disable the internal Axios JSON deserialization as this is handled by the client - const axiosInstance = axios.create({ transformResponse: [] }) - const apiClient = new FilesApiClient({}, API_BASE_USE, axiosInstance) + const apiClient = getApiClient() return new Cypress.Promise(async (resolve) => { cy.window() @@ -24,9 +48,9 @@ export const apiTestHelper = { const toDelete = items.map( ({ name }: { name: string }) => `/${name}` ) - cy.log(`Deleting bucket ${bucketType} ${JSON.stringify(toDelete)}`) + cy.log(`Clearing bucket ${bucketType} ${JSON.stringify(toDelete)}`) await apiClient.removeBucketObject(buckets[0].id, { paths: toDelete }) - cy.log("done deleting") + cy.log("Done clearing") resolve() }) }) @@ -34,9 +58,7 @@ export const apiTestHelper = { // create a folder with a full path like "/new folder" // you can create subfolders on the fly too with "/first/sub folder" createFolder(folderPath: string){ - // Disable the internal Axios JSON deserialization as this is handled by the client - const axiosInstance = axios.create({ transformResponse: [] }) - const apiClient = new FilesApiClient({}, API_BASE_USE, axiosInstance) + const apiClient = getApiClient() return new Cypress.Promise((resolve, reject) => { cy.window().then(async (win) => { diff --git a/packages/files-ui/cypress/tests/file-management-spec.ts b/packages/files-ui/cypress/tests/file-management-spec.ts index 24ebd14914..0162687d2a 100644 --- a/packages/files-ui/cypress/tests/file-management-spec.ts +++ b/packages/files-ui/cypress/tests/file-management-spec.ts @@ -37,7 +37,7 @@ describe("File management", () => { it("can add files and cancel modal", () => { cy.web3Login() - // upload a file and see it in the file list + // attach a file and see it in the file list homePage.uploadButton().click() fileUploadModal.body().attachFile("../fixtures/uploadedFiles/text-file.txt") fileUploadModal.fileList().should("have.length", 1) @@ -66,6 +66,9 @@ describe("File management", () => { homePage.moveSelectedButton().click() moveItemModal.folderList().contains(folderName).click() moveItemModal.moveButton().safeClick() + homePage.awaitBucketRefresh() + moveSuccessToast.body().should("be.visible") + moveSuccessToast.closeButton().click() // ensure there is only the folder in the Home directory homePage.fileItemRow().should("have.length", 1) @@ -85,6 +88,7 @@ describe("File management", () => { homePage.moveSelectedButton().click() moveItemModal.folderList().contains("Home").click() moveItemModal.moveButton().safeClick() + homePage.awaitBucketRefresh() moveSuccessToast.body().should("be.visible") moveSuccessToast.closeButton().click() diff --git a/packages/files-ui/cypress/tests/file-preview-spec.ts b/packages/files-ui/cypress/tests/file-preview-spec.ts index 6f2dc17cee..655e1c68e5 100644 --- a/packages/files-ui/cypress/tests/file-preview-spec.ts +++ b/packages/files-ui/cypress/tests/file-preview-spec.ts @@ -51,6 +51,80 @@ describe("File Preview", () => { homePage.appHeaderLabel().should("exist") }) + it("can navigate through preview using keyboard hotkeys", () => { + cy.web3Login({ clearCSFBucket: true }) + + // add files + homePage.uploadFile("../fixtures/uploadedFiles/chainsafe.png") + homePage.uploadFile("../fixtures/uploadedFiles/logo.png") + homePage.uploadFile("../fixtures/uploadedFiles/text-file.txt") + homePage.fileItemRow().should("have.length", 3) + + // store the 3 file names as cypress aliases for later comparison + homePage.fileItemName().eq(0).invoke("text").as("fileNameA") + homePage.fileItemName().eq(1).invoke("text").as("fileNameB") + homePage.fileItemName().eq(2).invoke("text").as("fileNameC") + + // navigate to the preview modal for the first file + homePage.fileItemKebabButton().first().click() + homePage.previewMenuOption().eq(0).click() + previewModal.body().should("exist") + + // ensure the correct file is being previewed + cy.get("@fileNameA").then(($fileNameA) => { + previewModal.fileNameLabel().should("contain.text", $fileNameA) + previewModal.contentContainer() + .should("be.visible") + .should("not.have.text", "Loading preview") + previewModal.unsupportedFileLabel().should("not.exist") + }) + + // browse to the 2nd file via the right arrow key + cy.get("body").type("{rightarrow}") + cy.get("@fileNameB").then(($fileNameB) => { + previewModal.fileNameLabel().should("contain.text", $fileNameB) + previewModal.contentContainer() + .should("be.visible") + .should("not.have.text", "Loading preview") + previewModal.unsupportedFileLabel().should("not.exist") + }) + + // browse to the 3rd file via the right arrow key + cy.get("body").type("{rightarrow}") + cy.get("@fileNameC").then(($fileNameC) => { + previewModal.fileNameLabel().should("contain.text", $fileNameC) + previewModal.contentContainer() + .should("be.visible") + .should("not.have.text", "Loading preview") + previewModal.unsupportedFileLabel().should("not.exist") + }) + + // return to the 2nd file via the left arrow key + cy.get("body").type("{leftarrow}") + cy.get("@fileNameB").then(($fileNameB) => { + previewModal.fileNameLabel().should("contain.text", $fileNameB) + previewModal.contentContainer() + .should("be.visible") + .should("not.have.text", "Loading preview") + previewModal.unsupportedFileLabel().should("not.exist") + }) + + // return to the 1st file via the left arrow key + cy.get("body").type("{leftarrow}") + cy.get("@fileNameA").then(($fileNameA) => { + previewModal.fileNameLabel().should("contain.text", $fileNameA) + previewModal.contentContainer() + .should("be.visible") + .should("not.have.text", "Loading preview") + previewModal.unsupportedFileLabel().should("not.exist") + }) + + // exit preview via the escape key + cy.get("body").type("{esc}") + previewModal.body().should("not.exist") + homePage.appHeaderLabel().should("exist") + }) + it("can see option to download file from the preview screen", () => { cy.web3Login({ clearCSFBucket: true }) homePage.uploadFile("../fixtures/uploadedFiles/logo.png") diff --git a/packages/files-ui/cypress/tests/file-sharing-spec.ts b/packages/files-ui/cypress/tests/file-sharing-spec.ts new file mode 100644 index 0000000000..0e6b04bc9a --- /dev/null +++ b/packages/files-ui/cypress/tests/file-sharing-spec.ts @@ -0,0 +1,63 @@ +import { createEditSharedFolderModal } from "../support/page-objects/modals/createSharedFolderModal" +import { deleteSharedFolderModal } from "../support/page-objects/modals/deleteSharedFolderModal" +import { fileUploadModal } from "../support/page-objects/modals/fileUploadModal" +import { navigationMenu } from "../support/page-objects/navigationMenu" +import { sharingExplainerKey, sharedFolderName, sharedFolderEditedName, validUsername } from "../fixtures/filesTestData" +import { sharedPage } from "../support/page-objects/sharedPage" +import { uploadCompleteToast } from "../support/page-objects/toasts/uploadCompleteToast" + +describe("File Sharing", () => { + + context("desktop", () => { + + it("can create, add, delete a shared folder", () => { + // intercept and stub the response to ensure the explainer is not displayed + cy.intercept("GET", "**/user/store", { + body: { [sharingExplainerKey]: "true" } + }) + + cy.web3Login({ deleteShareBucket: true }) + + // create a shared folder + navigationMenu.sharedNavButton().click() + sharedPage.createSharedFolderButton().click() + createEditSharedFolderModal.body().should("be.visible") + createEditSharedFolderModal.folderNameInput().type(sharedFolderName) + createEditSharedFolderModal.editPermissionInput().type(validUsername).type("{enter}") + createEditSharedFolderModal.createButton().safeClick() + createEditSharedFolderModal.body().should("not.exist") + sharedPage.sharedFolderItemRow().should("have.length", 1) + + // upload to a shared folder + sharedPage.sharedFolderItemName().contains(sharedFolderName) + .should("be.visible") + .dblclick() + sharedPage.uploadButton().click() + fileUploadModal.body().attachFile("../fixtures/uploadedFiles/text-file.txt") + fileUploadModal.fileList().should("have.length", 1) + fileUploadModal.uploadButton().safeClick() + fileUploadModal.body().should("not.exist") + uploadCompleteToast.body().should("be.visible") + uploadCompleteToast.closeButton().click() + sharedPage.fileItemRow().should("have.length", 1) + + // edit name of a shared folder + navigationMenu.sharedNavButton().click() + sharedPage.sharedFolderItemRow().should("have.length", 1) + sharedPage.fileItemKebabButton().click() + sharedPage.renameMenuOption().click() + sharedPage.shareRenameInput() + .type("{selectall}{del}") + .type(`${sharedFolderEditedName}{enter}`) + sharedPage.sharedFolderItemName().contains(sharedFolderEditedName) + + // delete a shared folder + sharedPage.fileItemKebabButton().click() + sharedPage.deleteMenuOption().click() + deleteSharedFolderModal.body().should("be.visible") + deleteSharedFolderModal.confirmButton().safeClick() + deleteSharedFolderModal.body().should("not.exist") + sharedPage.sharedFolderItemRow().should("not.exist") + }) + }) +}) diff --git a/packages/files-ui/cypress/tests/sharing-explainer-spec.ts b/packages/files-ui/cypress/tests/sharing-explainer-spec.ts index 0103251ab8..796ef9c04c 100644 --- a/packages/files-ui/cypress/tests/sharing-explainer-spec.ts +++ b/packages/files-ui/cypress/tests/sharing-explainer-spec.ts @@ -1,16 +1,15 @@ import { navigationMenu } from "../support/page-objects/navigationMenu" import { sharingExplainerModal } from "../support/page-objects/modals/sharingExplainerModal" +import { sharingExplainerKey } from "../fixtures/filesTestData" describe("Sharing Explainer", () => { context("desktop", () => { - const sharingKey = "csf.dismissedSharingExplainer" - it("User can view and dismiss the sharing explainer", () => { // intercept and stub the response to ensure the explainer is displayed cy.intercept("GET", "**/user/store", { - body: { [sharingKey]: "false" } + body: { [sharingExplainerKey]: "false" } }) cy.web3Login() @@ -28,14 +27,14 @@ describe("Sharing Explainer", () => { // intercept POST to ensure the key was updated after the explainer is dismissed cy.wait("@storePost").its("request.body").should("contain", { - [sharingKey]: "true" + [sharingExplainerKey]: "true" }) }) }) it("User should not see sharing explainer if previously dismissed", () => { cy.intercept("GET", "**/user/store", { - body: { [sharingKey]: "true" } + body: { [sharingExplainerKey]: "true" } }) cy.web3Login() diff --git a/packages/files-ui/package.json b/packages/files-ui/package.json index a8e0d81b90..e658ecb5a2 100644 --- a/packages/files-ui/package.json +++ b/packages/files-ui/package.json @@ -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.19", + "@chainsafe/files-api-client": "^1.18.20", "@chainsafe/web3-context": "1.1.4", "@emeraldpay/hashicon-react": "^0.5.1", "@lingui/core": "^3.7.2", @@ -77,7 +77,7 @@ "@types/yup": "^0.29.9", "@types/zxcvbn": "^4.4.0", "babel-plugin-macros": "^2.8.0", - "cypress": "^8.6", + "cypress": "^9.0", "cypress-file-upload": "^5.0.8", "cypress-pipe": "^2.0.0" }, diff --git a/packages/files-ui/public/index.html b/packages/files-ui/public/index.html index 98d6df81a3..badfe0806a 100644 --- a/packages/files-ui/public/index.html +++ b/packages/files-ui/public/index.html @@ -3,7 +3,7 @@ - + diff --git a/packages/files-ui/src/Components/Elements/CustomModal.tsx b/packages/files-ui/src/Components/Elements/CustomModal.tsx index 62d63fa07c..dbe5d76a25 100644 --- a/packages/files-ui/src/Components/Elements/CustomModal.tsx +++ b/packages/files-ui/src/Components/Elements/CustomModal.tsx @@ -13,7 +13,8 @@ const useStyles = makeStyles(({ constants, breakpoints }: CSFTheme) => }, inner: { backgroundColor: constants.modalDefault.backgroundColor, - color: constants.modalDefault.color + color: constants.modalDefault.color, + width: "100%" }, mobileStickyBottom: { [breakpoints.down("md")]: { @@ -51,7 +52,8 @@ const CustomModal = ({ className, children, injectedClass, mobileStickyBottom = className={clsx(classes.root, className)} injectedClass={{ closeIcon: clsx(classes.closeIcon, injectedClass?.closeIcon), - inner: clsx(classes.inner, mobileStickyBottom ? classes.mobileStickyBottom : undefined, injectedClass?.inner) + inner: clsx(classes.inner, mobileStickyBottom ? classes.mobileStickyBottom : undefined, injectedClass?.inner), + subModalInner: injectedClass?.subModalInner }} {...rest} > diff --git a/packages/files-ui/src/Components/Elements/MnemonicForm.tsx b/packages/files-ui/src/Components/Elements/MnemonicForm.tsx index 31e95cf972..067befa0da 100644 --- a/packages/files-ui/src/Components/Elements/MnemonicForm.tsx +++ b/packages/files-ui/src/Components/Elements/MnemonicForm.tsx @@ -136,8 +136,10 @@ const MnemonicForm = ({ buttonLabel, onComplete }: Props) => { component="p" className={classes.loader} > - + Generating… diff --git a/packages/files-ui/src/Components/Elements/NotificationsDropdown.tsx b/packages/files-ui/src/Components/Elements/NotificationsDropdown.tsx index bcbc88baf6..941e778bea 100644 --- a/packages/files-ui/src/Components/Elements/NotificationsDropdown.tsx +++ b/packages/files-ui/src/Components/Elements/NotificationsDropdown.tsx @@ -73,6 +73,7 @@ const NotificationsDropdown: React.FC = ({ notifica return ( modalInner: { padding: constants.generalUnit * 4, textAlign: "center", + display: "flex", + flexDirection: "column", "& img" : { width: "min-content", margin: "auto", diff --git a/packages/files-ui/src/Components/Elements/SharedUsers.tsx b/packages/files-ui/src/Components/Elements/SharedUsers.tsx index 6ea07668bb..c385295223 100644 --- a/packages/files-ui/src/Components/Elements/SharedUsers.tsx +++ b/packages/files-ui/src/Components/Elements/SharedUsers.tsx @@ -3,11 +3,15 @@ import { makeStyles, createStyles, useThemeSwitcher } from "@chainsafe/common-th import UserBubble from "./UserBubble" import { BucketKeyPermission, RichUserInfo } from "../../Contexts/FilesContext" import { getUserDisplayName } from "../../Utils/getUserDisplayName" +import { CSFTheme } from "../../Themes/types" -const useStyles = makeStyles(() => { +const useStyles = makeStyles(({ constants }: CSFTheme) => { return createStyles({ root: { display: "flex" + }, + bubble: { + marginRight: constants.generalUnit } }) }) @@ -55,6 +59,7 @@ const SharedUsers = ({ bucket }: Props) => {
1 ? classes.bubble : undefined} /> {userLabels.length > 2 && ( { +const UserBubble = ({ text, tooltip, className }: Props) => { const classes = useStyles() const [showTooltip, setShowTooltip] = useState(false) @@ -80,7 +78,7 @@ const UserBubble = ({ text, tooltip }: Props) => {
setShowTooltip(true)} onMouseLeave={() => setShowTooltip(false)} - className={classes.bubble} + className={clsx(classes.bubble, className)} onClick={toggleTooltip} >
diff --git a/packages/files-ui/src/Components/Layouts/AppNav.tsx b/packages/files-ui/src/Components/Layouts/AppNav.tsx index f0a43c9b92..71f61bb0c2 100644 --- a/packages/files-ui/src/Components/Layouts/AppNav.tsx +++ b/packages/files-ui/src/Components/Layouts/AppNav.tsx @@ -12,7 +12,8 @@ import { formatBytes, DeleteSvg, UserShareSvg, - MenuDropdown + MenuDropdown, + ScrollbarWrapper } from "@chainsafe/common-components" import { ROUTE_LINKS } from "../FilesRoutes" import { Trans } from "@lingui/macro" @@ -25,17 +26,45 @@ import { Hashicon } from "@emeraldpay/hashicon-react" const useStyles = makeStyles( ({ palette, animation, breakpoints, constants, zIndex }: CSFTheme) => { return createStyles({ + scrollRoot: { + zIndex: zIndex?.layer1 + }, root: { width: 0, - overflow: "auto", + overflowX: "hidden", + overflowY: "auto", transitionDuration: `${animation.translate}ms`, display: "flex", flexDirection: "column", position: "fixed", left: 0, opacity: 0, + "&:before": { + content: "''", + display: "block", + position: "absolute", + top: 0, + left: 0, + height: "100%", + width: "100%", + zIndex: 9999, + opacity: 0, + visibility: "visible", + transitionDuration: `${animation.translate}ms` + }, "&.active": { - opacity: 1 + opacity: 1, + "&:before": { + content: "''", + display: "block", + position: "absolute", + top: 0, + left: 0, + height: "100%", + width: "100%", + zIndex: -1, + visibility: "hidden" + } }, [breakpoints.up("md")]: { padding: `${constants.topPadding}px ${ @@ -53,12 +82,12 @@ const useStyles = makeStyles( top: `${constants.mobileHeaderHeight}px`, backgroundColor: constants.nav.mobileBackgroundColor, zIndex: zIndex?.layer1, - padding: `0 ${constants.generalUnit * 4}px`, maxWidth: "100vw", visibility: "hidden", "&.active": { visibility: "visible", - width: `${constants.mobileNavWidth}px` + width: `${constants.mobileNavWidth}px`, + padding: `0 ${constants.generalUnit * 4}px` } } }, @@ -102,6 +131,7 @@ const useStyles = makeStyles( marginBottom: constants.generalUnit * 2 }, [breakpoints.down("md")]: { + marginTop: constants.generalUnit * 2, transitionDuration: `${animation.translate}ms`, color: palette.additional["gray"][3], "&.active": {} @@ -129,7 +159,7 @@ const useStyles = makeStyles( } }, "& svg": { - "& path" : { + "& path": { fill: constants.nav.headingColor }, transitionDuration: `${animation.transform}ms`, @@ -149,9 +179,6 @@ const useStyles = makeStyles( "& svg": { fill: constants.nav.itemIconColorHover } - }, - [breakpoints.down("md")]: { - minWidth: Number(constants.mobileNavWidth) } }, navItemText: { @@ -234,53 +261,54 @@ const AppNav = ({ navOpen, setNavOpen }: IAppNav) => { const profileTitle = getProfileTitle() return ( -
+
- {isLoggedIn && + : navOpen + })} + > + {isLoggedIn && secured && !!publicKey && !isNewDevice && !shouldInitializeAccount && ( - <> - {desktop && ( -
- signOut(), - contents: ( -
- - - Sign Out - -
- ) - } - ]} - > - {!!profileTitle && + <> + {desktop && ( +
+ signOut(), + contents: ( +
+ + + Sign Out + +
+ ) + } + ]} + > + {!!profileTitle &&
{ {profileTitle}
- } - -
- )} -
- - Folders - -
+ )} +
+ + Folders + + - - {desktop ? Resources : Account} - - + + {desktop ? Resources : Account} + + -
-
- {desktop && ( -
- {storageSummary && ( - <> - {`${formatBytes(storageSummary.used_storage, 2)} of ${formatBytes( - storageSummary.total_storage, 2 - )} used`} - - - ) - } -
- )} - {!desktop && ( -
{ - handleOnClick() - signOut() - }} - > - - - Sign Out - -
- )} -
- {!desktop && ( -
setNavOpen(false)} - className={clsx(classes.blocker, { - active: navOpen - })} - >
- )} - - )} -
+ + + Settings + + + +
+
+ {desktop && ( +
+ {storageSummary && ( + <> + {`${formatBytes(storageSummary.used_storage, 2)} of ${formatBytes( + storageSummary.total_storage, 2 + )} used`} + + + ) + } +
+ )} + {!desktop && ( +
setNavOpen(false)} + className={clsx(classes.blocker, { + active: navOpen + })} + >
+ )} +
+ + )} + + ) } diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/CreateFolderModal.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/CreateFolderModal.tsx index 5ab03207c5..cb0841cd68 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/CreateFolderModal.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/CreateFolderModal.tsx @@ -30,7 +30,9 @@ const useStyles = makeStyles( }, modalRoot: { zIndex: zIndex?.blocker, - [breakpoints.down("md")]: {} + [breakpoints.down("md")]: { + paddingBottom: Number(constants?.mobileButtonHeight) + constants.generalUnit + } }, modalInner: { backgroundColor: constants.createFolder.backgroundColor, @@ -144,7 +146,7 @@ const CreateFolderModal = ({ modalOpen, close }: ICreateFolderModalProps) => { variant="h5" component="h5" > - Create Folder + New folder )} diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/CreateOrEditSharedFolderModal.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/CreateOrEditSharedFolderModal.tsx index 11f0295159..0424df0615 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/CreateOrEditSharedFolderModal.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/CreateOrEditSharedFolderModal.tsx @@ -13,7 +13,6 @@ 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) => { @@ -25,7 +24,10 @@ const useStyles = makeStyles( alignItems: "center" }, modalRoot: { - zIndex: zIndex?.blocker + zIndex: zIndex?.blocker, + [breakpoints.down("md")]: { + paddingBottom: Number(constants?.mobileButtonHeight) + constants.generalUnit + } }, modalInner: { backgroundColor: constants.modalDefault.backgroundColor, @@ -106,7 +108,8 @@ const useStyles = makeStyles( color: palette.error.main }, sharingLink: { - padding: constants.generalUnit * 1.25 + padding: constants.generalUnit * 2, + margin: 0 } }) } @@ -121,7 +124,7 @@ interface ICreateOrEditSharedFolderModalProps { const CreateOrEditSharedFolderModal = ({ mode, isModalOpen, onClose, bucketToEdit }: ICreateOrEditSharedFolderModalProps) => { const classes = useStyles() - const { desktop } = useThemeSwitcher() + const { desktop, tablet, mobile } = useThemeSwitcher() const { handleCreateSharedFolder, handleEditSharedFolder, isEditingSharedFolder, isCreatingSharedFolder } = useCreateOrEditSharedFolder() const [sharedFolderName, setSharedFolderName] = useState("") const { sharedFolderReaders, sharedFolderWriters, onNewUsers, handleLookupUser, usersError, resetUsers } = useLookupSharedFolderUser() @@ -200,6 +203,13 @@ const CreateOrEditSharedFolderModal = ({ mode, isModalOpen, onClose, bucketToEdi active={isModalOpen} closePosition="none" maxWidth="sm" + testId="create-or-edit-shared-folder" + subModal={mode === "edit" && !!bucketToEdit && ( + + )} >
@@ -224,6 +234,7 @@ const CreateOrEditSharedFolderModal = ({ mode, isModalOpen, onClose, bucketToEdi onChange={onNameChange} autoFocus state={nameError ? "error" : "normal"} + data-cy="input-shared-folder-name" /> {nameError && ( } -
+
{ setHasPermissionsChanged(true) @@ -252,13 +266,23 @@ const CreateOrEditSharedFolderModal = ({ mode, isModalOpen, onClose, bucketToEdi ...provided, minHeight: 90, alignContent: "start" + }), + valueContainer: (provided) => mobile && !tablet ? ({ + ...provided, + paddingBottom: 24 + }) : ({ + ...provided }) }} loadingMessage={t`Loading`} noOptionsMessage={t`No user found for this query.`} + data-cy="tag-view-permission-user" />
-
+
{ setHasPermissionsChanged(true) @@ -274,23 +298,19 @@ const CreateOrEditSharedFolderModal = ({ mode, isModalOpen, onClose, bucketToEdi ...provided, minHeight: 90, alignContent: "start" + }), + valueContainer: (provided) => mobile && !tablet ? ({ + ...provided, + paddingBottom: 24 + }) : ({ + ...provided }) }} loadingMessage={t`Loading`} noOptionsMessage={t`No user found for this query.`} + data-cy="tag-edit-permission-user" />
- {mode === "edit" && !!bucketToEdit && ( -
- - Sharing link - - -
- )} Cancel @@ -329,6 +350,7 @@ const CreateOrEditSharedFolderModal = ({ mode, isModalOpen, onClose, bucketToEdi loading={isCreatingSharedFolder || isEditingSharedFolder} onClick={mode === "create" ? onCreateSharedFolder : onEditSharedFolder} disabled={mode === "create" ? (!!usersError || !!nameError) : !hasPermissionsChanged || !!usersError} + data-cy={mode === "create" ? "button-create-shared-folder" : "button-update-shared-folder"} > {mode === "create" ? Create diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/CreateOrManageSharedFolder.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/CreateOrManageSharedFolder.tsx new file mode 100644 index 0000000000..489c4ccebd --- /dev/null +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/CreateOrManageSharedFolder.tsx @@ -0,0 +1,309 @@ +import { Button, ShareAltSvg, TagsInput, Typography, Grid, TextInput } from "@chainsafe/common-components" +import { createStyles, makeStyles, useThemeSwitcher } from "@chainsafe/common-theme" +import React, { useState, useCallback } from "react" +import { CSFTheme } from "../../../Themes/types" +import { BucketKeyPermission } from "../../../Contexts/FilesContext" +import CustomButton from "../../Elements/CustomButton" +import { t, Trans } from "@lingui/macro" +import { useEffect } from "react" +import { SharedFolderModalMode } from "./types" +import { useCreateOrEditSharedFolder } from "./hooks/useCreateOrEditSharedFolder" +import { useLookupSharedFolderUser } from "./hooks/useLookupUser" +import { nameValidator } from "../../../Utils/validationSchema" +import { getUserDisplayName } from "../../../Utils/getUserDisplayName" + +const useStyles = makeStyles( + ({ breakpoints, constants, typography, palette }: CSFTheme) => { + return createStyles({ + root: { + padding: constants.generalUnit * 2, + flexDirection: "column", + display: "flex", + alignItems: "center", + [breakpoints.down("sm")]: { + padding: constants.generalUnit + } + }, + okButton: { + marginLeft: constants.generalUnit + }, + cancelButton: { + [breakpoints.down("md")]: { + position: "fixed", + bottom: 0, + left: 0, + width: "100%", + height: constants?.mobileButtonHeight + } + }, + label: { + fontSize: 14, + lineHeight: "22px" + }, + heading: { + color: constants.modalDefault.color, + fontWeight: typography.fontWeight.semibold, + marginBottom: 10 + }, + iconBacking: { + backgroundColor: constants.modalDefault.iconBackingColor, + width: 48, + height: 48, + borderRadius: 24, + marginBottom: constants.generalUnit * 2, + marginTop: constants.generalUnit, + "& > svg": { + width: 16, + height: 16, + fill: palette.primary.main, + position: "relative", + display: "block", + transform: "translate(-50%, -50%)", + top: "50%", + left: "50%" + } + }, + modalFlexItem: { + width: "100%", + margin: 5 + }, + inputLabel: { + fontSize: 16, + fontWeight: 600 + }, + shareFolderNameInput: { + margin: `0 ${constants.generalUnit * 1.5}px ${constants.generalUnit}px`, + display: "block" + }, + footer: { + width: "100%", + padding: `${constants.generalUnit * 2}px ${constants.generalUnit * 2}px ${constants.generalUnit}px 0` + }, + errorText: { + marginLeft: constants.generalUnit * 1.5, + color: palette.error.main + } + }) + } +) + +interface ICreateOrManageSharedFolderProps { + mode?: SharedFolderModalMode + onClose: () => void + bucketToEdit?: BucketKeyPermission +} + +const CreateOrManageSharedFolder = ({ mode, onClose, bucketToEdit }: ICreateOrManageSharedFolderProps) => { + const classes = useStyles() + const { desktop } = useThemeSwitcher() + const { handleCreateSharedFolder, handleEditSharedFolder, isEditingSharedFolder, isCreatingSharedFolder } = useCreateOrEditSharedFolder() + const [sharedFolderName, setSharedFolderName] = useState("") + const { sharedFolderReaders, sharedFolderWriters, onNewUsers, handleLookupUser, usersError, resetUsers } = useLookupSharedFolderUser() + const [hasPermissionsChanged, setHasPermissionsChanged] = useState(false) + const [nameError, setNameError] = useState("") + + const onReset = useCallback(() => { + setSharedFolderName("") + setHasPermissionsChanged(false) + resetUsers() + }, [resetUsers]) + + useEffect(() => { + onReset() + + if (!bucketToEdit) return + + const newWriters = bucketToEdit.writers.map((writer) => ({ + label: getUserDisplayName(writer), + value: writer.uuid || "", + data: writer + }) + ) || [] + + const newReaders = bucketToEdit.readers.map((reader) => ({ + label: getUserDisplayName(reader), + value: reader.uuid || "", + data: reader + }) + ) || [] + + onNewUsers(newWriters, "write") + onNewUsers(newReaders, "read") + }, [bucketToEdit, onNewUsers, onReset]) + + const onNameChange = useCallback((value?: string | number) => { + if (value === undefined) return + + const name = value.toString() + setSharedFolderName(name) + + nameValidator + .validate({ name }) + .then(() => { + setNameError("") + }) + .catch((e: Error) => { + setNameError(e.message) + }) + }, []) + + const handleClose = useCallback(() => { + onReset() + onClose() + }, [onClose, onReset]) + + const onCreateSharedFolder = useCallback(() => { + handleCreateSharedFolder(sharedFolderName, sharedFolderReaders, sharedFolderWriters) + .catch(console.error) + .finally(handleClose) + }, [handleCreateSharedFolder, sharedFolderName, sharedFolderWriters, sharedFolderReaders, handleClose]) + + const onEditSharedFolder = useCallback(() => { + if (!bucketToEdit) return + handleEditSharedFolder(bucketToEdit, sharedFolderReaders, sharedFolderWriters) + .catch(console.error) + .finally(handleClose) + }, [handleEditSharedFolder, sharedFolderWriters, sharedFolderReaders, handleClose, bucketToEdit]) + + return ( +
+
+ +
+
+ + {mode === "create" + ? Create Shared Folder + : Manage Shared Folder + } + + +
+ {mode === "create" && +
+ + {nameError && ( + + {nameError} + + )} +
+ } +
+ { + setHasPermissionsChanged(true) + onNewUsers(values, "read") + }} + label={t`Give view-only permission to:`} + labelClassName={classes.inputLabel} + value={sharedFolderReaders} + fetchTags={(inputVal) => handleLookupUser(inputVal, "read")} + placeholder={t`Add by sharing address, username or wallet address`} + styles={{ + control: (provided) => ({ + ...provided, + minHeight: 90, + alignContent: "start" + }) + }} + loadingMessage={t`Loading`} + noOptionsMessage={t`No user found for this query.`} + data-cy="tag-view-permission-user" + /> +
+
+ { + setHasPermissionsChanged(true) + onNewUsers(values, "write") + }} + label={t`Give edit permission to:`} + labelClassName={classes.inputLabel} + value={sharedFolderWriters} + fetchTags={(inputVal) => handleLookupUser(inputVal, "write")} + placeholder={t`Add by sharing address, username or wallet address`} + styles={{ + control: (provided) => ({ + ...provided, + minHeight: 90, + alignContent: "start" + }) + }} + loadingMessage={t`Loading`} + noOptionsMessage={t`No user found for this query.`} + data-cy="tag-edit-permission-user" + /> +
+ + {!!usersError && ( + + {usersError} + + )} + + + Cancel + + + + +
+ ) +} + +export default CreateOrManageSharedFolder diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/CreateOrManageSharedFolderModal.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/CreateOrManageSharedFolderModal.tsx new file mode 100644 index 0000000000..94e3da4c65 --- /dev/null +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/CreateOrManageSharedFolderModal.tsx @@ -0,0 +1,76 @@ +import { createStyles, makeStyles } from "@chainsafe/common-theme" +import React from "react" +import CustomModal from "../../Elements/CustomModal" +import { CSFTheme } from "../../../Themes/types" +import { BucketKeyPermission } from "../../../Contexts/FilesContext" +import { SharedFolderModalMode } from "./types" +import LinkList from "./LinkSharing/LinkList" +import CreateOrManageSharedFolder from "./CreateOrManageSharedFolder" + +const useStyles = makeStyles( + ({ breakpoints, constants, zIndex }: CSFTheme) => { + return createStyles({ + modalRoot: { + zIndex: zIndex?.blocker, + [breakpoints.down("md")]: { + paddingBottom: Number(constants?.mobileButtonHeight) + constants.generalUnit + } + }, + modalInner: { + backgroundColor: constants.modalDefault.backgroundColor, + color: constants.modalDefault.color, + [breakpoints.down("md")]: { + bottom: + Number(constants?.mobileButtonHeight) + constants.generalUnit, + borderTopLeftRadius: `${constants.generalUnit * 1.5}px`, + borderTopRightRadius: `${constants.generalUnit * 1.5}px`, + maxWidth: `${breakpoints.width("md")}px !important` + } + }, + subModal: { + width: "100%" + } + }) + } +) + +interface ICreateOrManageSharedFolderModalProps { + mode?: SharedFolderModalMode + isModalOpen: boolean + onClose: () => void + bucketToEdit?: BucketKeyPermission +} + +const CreateOrManageSharedFolderModal = ( + { mode, isModalOpen, onClose, bucketToEdit }: ICreateOrManageSharedFolderModalProps +) => { + const classes = useStyles() + + return ( + + )} + > + + + ) +} + +export default CreateOrManageSharedFolderModal diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/FileInfoModal.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/FileInfoModal.tsx index 98e422c918..6bdf62a4f3 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/FileInfoModal.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/FileInfoModal.tsx @@ -9,7 +9,7 @@ import CustomButton from "../../Elements/CustomButton" import { Trans } from "@lingui/macro" import { FileFullInfo } from "../../../Contexts/FilesContext" import { - Button, + CopyIcon, formatBytes, Grid, Loading, @@ -27,7 +27,9 @@ const useStyles = makeStyles( return createStyles({ modalRoot: { zIndex: zIndex?.blocker, - [breakpoints.down("md")]: {} + [breakpoints.down("md")]: { + paddingBottom: Number(constants?.mobileButtonHeight) + constants.generalUnit + } }, modalInner: { backgroundColor: constants.fileInfoModal.background, @@ -42,7 +44,6 @@ const useStyles = makeStyles( }, closeButton: { flex: 1, - marginLeft: constants.generalUnit * 2, [breakpoints.down("md")]: { position: "fixed", bottom: 0, @@ -59,13 +60,6 @@ const useStyles = makeStyles( textAlign: "center" } }, - heading: { - fontWeight: typography.fontWeight.semibold, - textAlign: "left", - [breakpoints.down("md")]: { - textAlign: "center" - } - }, infoHeading: { fontWeight: typography.fontWeight.semibold, textAlign: "left" @@ -77,13 +71,18 @@ const useStyles = makeStyles( }px` }, infoBox: { - paddingLeft: constants.generalUnit + paddingLeft: constants.generalUnit, + maxWidth: "100%" }, subInfoBox: { padding: `${constants.generalUnit * 1}px 0` }, subSubtitle: { - color: palette.additional["gray"][8] + color: palette.additional["gray"][8], + whiteSpace: "nowrap", + overflow: "hidden", + textOverflow: "ellipsis", + marginRight: constants.generalUnit * 2 }, technicalContainer: { paddingTop: constants.generalUnit, @@ -108,7 +107,7 @@ const useStyles = makeStyles( alignItems: "center", justifyContent: "center", left: "50%", - bottom: "calc(100% + 5px)", + bottom: "calc(100% + 8px)", position: "absolute", transform: "translate(-50%, 0%)", zIndex: zIndex?.layer1, @@ -137,17 +136,20 @@ const useStyles = makeStyles( visibility: "visible" } }, - copyButton: { - width: "100%" - }, copyContainer: { - position: "relative", - flexBasis: "75%", - color: palette.additional["gray"][9], + position: "relative" + }, + copyIcon: { + fontSize: "16px", + fill: palette.additional["gray"][9], [breakpoints.down("md")]: { - flexBasis: "100%", - margin: `${constants.generalUnit * 2}px` + fontSize: "18px", + fill: palette.additional["gray"][9] } + }, + copyRow: { + display: "flex", + cursor: "pointer" } }) } @@ -180,15 +182,27 @@ const FileInfoModal = ({ filePath, close }: IFileInfoModuleProps) => { } , [bucket, filePath, filesApiClient]) - const [copied, setCopied] = useState(false) - const debouncedSwitchCopied = debounce(() => setCopied(false), 3000) + const [copiedCID, setCopiedCID] = useState(false) + const [copiedKey, setCopiedKey] = useState(false) + const debouncedSwitchCopiedCID = debounce(() => setCopiedCID(false), 3000) + const debouncedSwitchCopiedKey = debounce(() => setCopiedKey(false), 3000) const onCopyCID = () => { if (fullFileInfo?.content?.cid) { - navigator.clipboard.writeText(fullFileInfo?.content?.cid) + navigator.clipboard.writeText(fullFileInfo.content.cid) + .then(() => { + setCopiedCID(true) + debouncedSwitchCopiedCID() + }).catch(console.error) + } + } + + const onCopyKey = () => { + if (bucket?.encryptionKey) { + navigator.clipboard.writeText(bucket.encryptionKey) .then(() => { - setCopied(true) - debouncedSwitchCopied() + setCopiedKey(true) + debouncedSwitchCopiedKey() }).catch(console.error) } } @@ -354,13 +368,27 @@ const FileInfoModal = ({ filePath, close }: IFileInfoModuleProps) => { CID (Content Identifier)
- - {fullFileInfo.content?.cid} - + + {fullFileInfo.content?.cid} + +
+ +
+ + + Copied! + + +
+
+
{ Decryption key - - - {bucket?.encryptionKey} - - + + + {bucket?.encryptionKey} + + +
+ +
+ + + Copied! + + +
+
+
@@ -392,24 +434,6 @@ const FileInfoModal = ({ filePath, close }: IFileInfoModuleProps) => { flexDirection="row" className={classes.buttonsContainer} > -
- -
- - - Copied! - - -
-
close()} size="large" diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/LinkSharing/LinkList.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/LinkSharing/LinkList.tsx index f557c760d7..40c13a1822 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/LinkSharing/LinkList.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/LinkSharing/LinkList.tsx @@ -1,8 +1,8 @@ -import { Button, Loading, MenuDropdown } from "@chainsafe/common-components" +import { Button, Loading, MenuDropdown, Typography } 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 React, { ReactNode, useCallback, useEffect, useMemo, useState } from "react" import { useFilesApi } from "../../../../Contexts/FilesApiContext" import { CSFTheme } from "../../../../Themes/types" import SharingLink from "./SharingLink" @@ -11,6 +11,7 @@ const useStyles = makeStyles( ({ constants, palette }: CSFTheme) => { return createStyles({ root: { + padding: 2 * constants.generalUnit }, options: { backgroundColor: constants.header.optionsBackground, @@ -47,49 +48,131 @@ const useStyles = makeStyles( }, permissionDropdown: { padding: `0px ${constants.generalUnit}px`, - backgroundColor: palette.additional["gray"][5], - marginLeft: constants.generalUnit + backgroundColor: palette.additional["gray"][1], + marginLeft: constants.generalUnit, + borderColor: palette.additional["gray"][5], + borderWidth: "1px", + borderStyle: "solid", + borderRadius: "4px" }, - createLink: { - display: "flex", - alignItems: "center", - margin: `${constants.generalUnit * 2.5}px 0` + rightsText: { + display: "inline-block" }, createLinkButton: { - marginRight: constants.generalUnit + width: "100%" }, dropdownTitle: { padding: `${constants.generalUnit * 0.75}px ${constants.generalUnit}px` + }, + heading : { + marginBottom: constants.generalUnit + }, + loadingContainer: { + display: "flex", + flexDirection: "column", + justifyContent: "center", + alignItems: "center" + }, + grayWrapper: { + backgroundColor: palette.additional["gray"][3], + borderRadius: "4px", + display: "flex", + padding: constants.generalUnit * 2, + flexDirection: "column" + }, + creationWrapper: { + display: "flex", + alignItems: "center", + justifyContent: "center", + flexDirection: "column", + margin: "auto" + }, + rightSelection: { + marginBottom: constants.generalUnit + }, + loader: { + textAlign: "center" + }, + activeLinks: { + marginBottom: constants.generalUnit } }) } ) +const MAX_LINKS = 2 + interface Props { bucketId: string bucketEncryptionKey: string } -const readRights = t`read rights` -const editRights = t`edit rights` +interface LinkMenuItems { + id: NonceResponsePermission + onClick: () => void + contents: ReactNode +} + +const readRights = t`view-only` +const editRights = t`can-edit` export const translatedPermission = (permission: NonceResponsePermission) => permission === "read" ? readRights : editRights const LinkList = ({ bucketId, bucketEncryptionKey }: Props) => { const classes = useStyles() const { filesApiClient } = useFilesApi() const [nonces, setNonces] = useState([]) - const [isLoading, setIsLoading] = useState(false) - const [newLinkPermission, setNewLinkPermission] = useState("read") + const [isLoadingNonces, setIsLoadingNonces] = useState(false) + const [isLoadingCreation, setIsLoadingCreation] = useState(false) + const hasAReadNonce = useMemo(() => !!nonces.find(n => n.permission === "read"), [nonces]) + const [newLinkPermission, setNewLinkPermission] = useState(undefined) + const menuItems: LinkMenuItems[] = useMemo(() => [ + { + id: "read", + onClick: () => setNewLinkPermission("read"), + contents: ( +
+ {readRights} +
+ ) + }, + { + id: "write", + onClick: () => setNewLinkPermission("write"), + contents: ( +
+ {editRights} +
+ ) + } + ], [classes.menuItem]) + + const displayedItems = useMemo(() => nonces.length === 0 + ? menuItems + : hasAReadNonce + ? menuItems.filter(i => i.id === "write") + : menuItems.filter(i => i.id === "read") + , [hasAReadNonce, menuItems, nonces.length] + ) + + useEffect(() => { + setNewLinkPermission(displayedItems[0].id) + }, [displayedItems]) const refreshNonces = useCallback(() => { - setIsLoading(true) + setIsLoadingNonces(true) filesApiClient.getAllNonces() .then((res) => { const noncesForCurrentBucket = res.filter(n => n.bucket_id === bucketId) setNonces(noncesForCurrentBucket) }) .catch(console.error) - .finally(() => setIsLoading(false)) + .finally(() => setIsLoadingNonces(false)) }, [bucketId, filesApiClient]) useEffect(() => { @@ -98,77 +181,101 @@ const LinkList = ({ bucketId, bucketEncryptionKey }: Props) => { const onCreateNonce = useCallback(() => { - setIsLoading(true) + if (!newLinkPermission) { + console.error("Permission not set") + return + } + + setIsLoadingCreation(true) return filesApiClient .createNonce({ bucket_id: bucketId, permission: newLinkPermission }) .catch(console.error) .finally(() => { - setIsLoading(false) + setIsLoadingCreation(false) refreshNonces() }) }, [bucketId, filesApiClient, newLinkPermission, refreshNonces]) return (
-
-
+ )} + {isLoadingNonces && ( + - Create new link - - with - setNewLinkPermission("read"), - contents: ( -
- {readRights} -
- ) - }, - { - onClick: () => setNewLinkPermission("write"), - contents: ( -
- {editRights} -
- ) - } - ]} - /> -
- { - isLoading && - } - { - !isLoading && nonces.length > 0 && nonces.map((nonce) => - - ) - } + + )} + {nonces.length < MAX_LINKS && ( + <> + + Create a sharing link + +
+
+
+ + Anyone with the link can: + + +
+ +
+
+ + )}
) } diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/LinkSharing/SharingLink.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/LinkSharing/SharingLink.tsx index 882f8c64fd..adc8cca9f3 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/LinkSharing/SharingLink.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/LinkSharing/SharingLink.tsx @@ -1,5 +1,5 @@ -import { Button, DeleteSvg, Typography } from "@chainsafe/common-components" +import { CopyIcon, DeleteSvg, Loading, MoreIcon, Typography } from "@chainsafe/common-components" import { createStyles, debounce, makeStyles } from "@chainsafe/common-theme" import { NonceResponse } from "@chainsafe/files-api-client" import { Trans } from "@lingui/macro" @@ -7,19 +7,24 @@ import React, { useCallback, useEffect, useState } from "react" import { useFilesApi } from "../../../../Contexts/FilesApiContext" import { useThresholdKey } from "../../../../Contexts/ThresholdKeyContext" import { CSFTheme } from "../../../../Themes/types" +import Menu from "../../../../UI-components/Menu" import { ROUTE_LINKS } from "../../../FilesRoutes" import { translatedPermission } from "./LinkList" const useStyles = makeStyles( - ({ constants }: CSFTheme) => { + ({ constants, palette, zIndex, animation, typography }: CSFTheme) => { return createStyles({ root: { display: "flex", - marginBottom: constants.generalUnit * 0.5 + maxWidth: "100%", + position: "relative", + "&:not(:first-child)": { + marginTop: constants.generalUnit * 2 + } }, linkWrapper: { whiteSpace: "nowrap", - marginRight: constants.generalUnit * 2, + marginRight: constants.generalUnit * 3, display: "flex", alignItems: "center", overflow: "hidden" @@ -27,14 +32,18 @@ const useStyles = makeStyles( permissionWrapper: { display: "flex", alignItems: "center", - marginRight: constants.generalUnit, + marginRight: constants.generalUnit * 3, flex: 1, - whiteSpace: "nowrap" + whiteSpace: "nowrap", + textAlign: "right", + fontWeight: typography.fontWeight.regular }, copyButton: { - flex: 1, - whiteSpace: "nowrap", - marginRight: constants.generalUnit + display: "flex", + justifyContent: "center", + alignItems: "center", + marginRight: constants.generalUnit * 2, + cursor: "pointer" }, link: { textOverflow: "ellipsis", @@ -47,6 +56,69 @@ const useStyles = makeStyles( width: 20, marginRight: constants.generalUnit * 1.5, fill: constants.fileSystemItemRow.menuIcon + }, + copyIcon: { + fontSize: "18px", + fill: palette.additional["gray"][8] + }, + copiedFlag: { + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + left: "50%", + top: -15, + position: "absolute", + transform: "translate(-50%, -50%)", + zIndex: zIndex?.layer1, + transitionDuration: `${animation.transform}ms`, + backgroundColor: constants.loginModule.flagBg, + color: constants.loginModule.flagText, + padding: `${constants.generalUnit / 2}px ${constants.generalUnit}px`, + borderRadius: 2, + "&:after": { + transitionDuration: `${animation.transform}ms`, + content: "''", + position: "absolute", + top: "100%", + left: "50%", + transform: "translate(-50%,0)", + width: 0, + height: 0, + borderLeft: "5px solid transparent", + borderRight: "5px solid transparent", + borderTop: `5px solid ${constants.loginModule.flagBg}` + } + }, + dropdownIcon: { + width: 14, + height: 14, + padding: 0, + display: "flex", + justifyContent: "center", + alignItems: "center", + fontSize: "unset", + "& svg": { + fill: constants.fileSystemItemRow.dropdownIcon, + width: 14, + height: 14 + } + }, + focusVisible: { + backgroundColor: "transparent !important" + }, + menuRoot: { + zIndex: "2500 !important" as any + }, + loader: { + display: "flex", + alignItems: "center", + margin: "auto" + }, + menuWrapper: { + display: "flex", + alignItems: "center", + margin: "auto" } }) } @@ -65,7 +137,7 @@ const SharingLink = ({ nonce, bucketEncryptionKey, refreshNonces }: Props) => { const [jwt, setJwt] = useState("") const { createJWT } = useThresholdKey() const [copied, setCopied] = useState(false) - const [isLoading, setIsLoading] = useState(true) + const [isDeleting, setIsDeleting] = useState(false) useEffect(() => { if(!nonce?.bucket_id || !nonce?.id) { @@ -74,7 +146,6 @@ const SharingLink = ({ nonce, bucketEncryptionKey, refreshNonces }: Props) => { const newJwt = createJWT(nonce.bucket_id, nonce.id, nonce.permission) newJwt && setJwt(newJwt) - setIsLoading(false) }, [createJWT, nonce]) useEffect(() => { @@ -94,21 +165,74 @@ const SharingLink = ({ nonce, bucketEncryptionKey, refreshNonces }: Props) => { debouncedSwitchCopied() }) .catch(console.error) + + // //Create a textbox field where we can insert text to. + // const copyFrom = document.createElement("textarea") + + // //Set the text content to be the text you wished to copy. + // copyFrom.textContent = link + + // //Append the textbox field into the body as a child. + // //"execCommand()" only works when there exists selected text, and the text is inside + // //document.body (meaning the text is part of a valid rendered HTML element). + // document.body.appendChild(copyFrom) + + // //Select all the text! + // copyFrom.select() + + // //Execute command + // document.execCommand("copy") + + // //(Optional) De-select the text using blur(). + // copyFrom.blur() + + // //Remove the textbox field from the document.body, so no other JavaScript nor + // //other elements can get access to this. + // document.body.removeChild(copyFrom) + + setCopied(true) + debouncedSwitchCopied() }, [debouncedSwitchCopied, link]) const onDeleteNonce = useCallback(() => { - setIsLoading(true) + setIsDeleting(true) filesApiClient.revokeNonce(nonce.id) .catch(console.error) .finally(() => { refreshNonces() - setIsLoading(false) + setIsDeleting(false) }) }, [filesApiClient, nonce, refreshNonces]) + if (isDeleting) { + return ( + <> + + + + + ) + } + return (
-
+ {copied && ( +
+ + Copied! + +
+ )} +
{link} @@ -118,26 +242,30 @@ const SharingLink = ({ nonce, bucketEncryptionKey, refreshNonces }: Props) => { {translatedPermission(nonce.permission)}
- - + +
+
+ } + options={[{ + contents: ( + <> + + + Delete + + + ), + onClick: onDeleteNonce + }]} + style={{ focusVisible: classes.focusVisible, root: classes.menuRoot }} + /> +
) } diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/MoveFileModal.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/MoveFileModal.tsx index bdbf9b88ea..f8a9a6cf1d 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/MoveFileModal.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/MoveFileModal.tsx @@ -18,7 +18,9 @@ const useStyles = makeStyles( return createStyles({ modalRoot: { zIndex: zIndex?.blocker, - [breakpoints.down("md")]: {} + [breakpoints.down("md")]: { + paddingBottom: Number(constants?.mobileButtonHeight) + constants.generalUnit + } }, modalInner: { backgroundColor: constants.moveFileModal.background, diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/ReportFileModal.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/ReportFileModal.tsx index 8381fd0604..86548de9c0 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/ReportFileModal.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/ReportFileModal.tsx @@ -26,7 +26,9 @@ const useStyles = makeStyles( return createStyles({ modalRoot: { zIndex: zIndex?.blocker, - [breakpoints.down("md")]: {} + [breakpoints.down("md")]: { + paddingBottom: Number(constants?.mobileButtonHeight) + constants.generalUnit + } }, modalInner: { backgroundColor: constants.fileInfoModal.background, @@ -65,10 +67,12 @@ const useStyles = makeStyles( }px` }, infoBox: { - paddingLeft: constants.generalUnit + paddingLeft: constants.generalUnit, + maxWidth: "100%" }, subInfoBox: { - padding: `${constants.generalUnit * 1}px 0` + padding: `${constants.generalUnit * 1}px 0`, + maxWidth: "100%" }, subSubtitle: { color: palette.additional["gray"][8] @@ -135,14 +139,37 @@ const useStyles = makeStyles( }, decryptionKey: { width: "100%", - wordBreak: "break-all" + overflow: "hidden", + textOverflow: "ellipsis", + marginRight: constants.generalUnit * 2 }, infoText: { marginBottom: constants.generalUnit * 2 }, keysWrapper: { - maxHeight: constants.generalUnit * 10, - overflow: "scroll" + whiteSpace: "nowrap", + overflow: "hidden", + textOverflow: "ellipsis", + maxWidth: "100%", + display: "flex", + cursor: "pointer" + }, + copyIcon: { + fontSize: "16px", + fill: palette.additional["gray"][9], + marginRight: constants.generalUnit, + [breakpoints.down("md")]: { + fontSize: "18px", + fill: palette.additional["gray"][9] + } + }, + decryptionKeyTitle: { + display: "flex", + justifyContent: "space-between", + marginBottom: constants.generalUnit * 0.5 + }, + titleMargin: { + marginBottom: constants.generalUnit * 0.5 } }) } @@ -162,7 +189,7 @@ const ReportFileModal = ({ filePath, close }: IReportFileModalProps) => { const classes = useStyles() const { bucket } = useFileBrowser() const { encryptionKey, id } = bucket || {} - const [isLoadingAdminKey, setIsloadingAdminKey] = useState(true) + const [isLoadingAdminKey, setIsLoadingAdminKey] = useState(true) const [adminPubKeys, setAdminPubkeys] = useState([]) const [encryptedDecryptionKeyMap, setEncryptedDecryptionKeyMap] = useState([]) const { encryptForPublicKey } = useThresholdKey() @@ -197,7 +224,7 @@ const ReportFileModal = ({ filePath, close }: IReportFileModalProps) => { setEncryptedDecryptionKeyMap(map) }) .catch(console.error) - .finally(() => setIsloadingAdminKey(false)) + .finally(() => setIsLoadingAdminKey(false)) }, [adminPubKeys, encryptForPublicKey, encryptionKey]) @@ -285,12 +312,13 @@ const ReportFileModal = ({ filePath, close }: IReportFileModalProps) => { Bucket id {id} @@ -300,28 +328,33 @@ const ReportFileModal = ({ filePath, close }: IReportFileModalProps) => { File path {filePath}
- + + Decryption key + +
+
- Decryption key - -
{JSON.stringify(encryptedDecryptionKeyMap)} @@ -351,7 +384,7 @@ const ReportFileModal = ({ filePath, close }: IReportFileModalProps) => {
- Copied! + Copied!
diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/SearchFileBrowser.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/SearchFileBrowser.tsx index 392ecc47e3..055f11bc30 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/SearchFileBrowser.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/SearchFileBrowser.tsx @@ -10,6 +10,7 @@ import { ROUTE_LINKS } from "../../FilesRoutes" import { t } from "@lingui/macro" import { FileBrowserContext } from "../../../Contexts/FileBrowserContext" import { useFilesApi } from "../../../Contexts/FilesApiContext" +import { parseFileContentResponse } from "../../../Utils/Helpers" const SearchFileBrowser: React.FC = ({ controls = false }: IFileBrowserModuleProps) => { const { pathname } = useLocation() @@ -26,7 +27,8 @@ const SearchFileBrowser: React.FC = ({ controls = false try { if (!searchString || !bucket) return [] - const results = await filesApiClient.searchFiles({ bucket_id: bucket.id, query: searchString }) + const results = (await filesApiClient.searchFiles({ bucket_id: bucket.id, query: searchString })) + .map(se => ({ ...se, content: parseFileContentResponse(se.content) })) return results } catch (err) { addToast({ diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/ShareModal.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/ShareModal.tsx index 71424d9a1b..e1faa549b4 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/ShareModal.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/ShareModal.tsx @@ -2,11 +2,10 @@ import { createStyles, makeStyles } from "@chainsafe/common-theme" import React, { useState } from "react" import CustomModal from "../../Elements/CustomModal" import { t, Trans } from "@lingui/macro" -import { Button, CheckboxInput, SelectInput, ShareAltSvg, TagsInput, TextInput, Typography } from "@chainsafe/common-components" +import { Button, CheckboxInput, SelectInput, ShareAltSvg, TextInput, Typography } from "@chainsafe/common-components" import { CSFTheme } from "../../../Themes/types" import { useCallback } from "react" import { useCreateOrEditSharedFolder } from "./hooks/useCreateOrEditSharedFolder" -import { useLookupSharedFolderUser } from "./hooks/useLookupUser" import { useMemo } from "react" import { BucketKeyPermission, FileSystemItem, useFiles } from "../../../Contexts/FilesContext" import { useUser } from "../../../Contexts/UserContext" @@ -14,66 +13,38 @@ import { useFileBrowser } from "../../../Contexts/FileBrowserContext" import clsx from "clsx" import { useEffect } from "react" import { nameValidator } from "../../../Utils/validationSchema" +import CreateOrManageSharedFolder from "./CreateOrManageSharedFolder" +import LinkList from "./LinkSharing/LinkList" +import { usePosthogContext } from "../../../Contexts/PosthogContext" import { useFilesApi } from "../../../Contexts/FilesApiContext" +interface StyleProps { + width: number +} + const useStyles = makeStyles( ({ breakpoints, constants, palette, typography, zIndex }: CSFTheme) => { return createStyles({ - modalRoot: { - zIndex: zIndex?.blocker, - [breakpoints.down("md")]: {} - }, - modalInner: { - backgroundColor: constants.fileInfoModal.background, - color: constants.fileInfoModal.color - }, root: { - padding: constants.generalUnit * 4, + padding: constants.generalUnit * 3, flexDirection: "column", - display: "flex", - alignItems: "center" + display: "flex" }, - closeButton: { - flex: 1, - marginLeft: constants.generalUnit * 2, - [breakpoints.down("md")]: { - position: "fixed", - bottom: 0, - left: 0, - width: "100%", - height: constants?.mobileButtonHeight, - margin: 0 - } + modalRoot: { + zIndex: zIndex?.blocker }, - title: { - fontWeight: typography.fontWeight.semibold, - textAlign: "left", - [breakpoints.down("md")]: { - textAlign: "center" + modalInner: ({ width }: StyleProps) => ({ + backgroundColor: constants.fileInfoModal.background, + color: constants.fileInfoModal.color, + width, + [breakpoints.down("sm")]: { + width: "100%" } - }, - infoHeading: { - fontWeight: typography.fontWeight.semibold, - textAlign: "left" - }, - infoContainer: { - borderTop: constants.fileInfoModal.infoContainerBorderTop, - padding: `${constants.generalUnit * 2}px ${constants.generalUnit * 3}px` - }, - infoBox: { - paddingLeft: constants.generalUnit - }, - subInfoBox: { - padding: `${constants.generalUnit * 1}px 0` - }, - subSubtitle: { - color: palette.additional["gray"][8] - }, - paddedContainer: { - padding: `${constants.generalUnit * 2}px ${ - constants.generalUnit * 4 - }px`, - borderBottom: `1px solid ${palette.additional["gray"][3]}` + }), + topIconContainer: { + display: "flex", + flexDirection: "column", + alignItems: "center" }, buttonsArea: { display: "flex", @@ -87,29 +58,28 @@ const useStyles = makeStyles( }, buttonsContainer: { display: "flex", - justifyContent: "center", + flexDirection: "column", + alignItems: "center", marginTop: constants.generalUnit * 2 }, mainButton: { - width: "100%" + width: 240, + marginBottom: constants.generalUnit * 0.5 }, - sideBySideButton: { - minWidth: constants.generalUnit * 12, - "&:first-child": { - marginRight: constants.generalUnit * 2 - } + cancelButton: { + maxWidth: 100 }, heading: { color: constants.modalDefault.color, fontWeight: typography.fontWeight.semibold, - marginBottom: 10 + marginBottom: constants.generalUnit * 3 }, iconBacking: { backgroundColor: constants.modalDefault.iconBackingColor, width: 48, height: 48, borderRadius: 24, - marginBottom: 16, + marginBottom: 8, "& > svg": { width: 16, height: 16, @@ -122,83 +92,71 @@ const useStyles = makeStyles( } }, inputLabel: { - fontSize: 16, - fontWeight: 600 + fontSize: 14, + fontWeight: 600, + marginBottom: constants.generalUnit }, modalFlexItem: { width: "100%", - margin: 5, marginBottom: constants.generalUnit * 2 }, - loadingContainer: { - width: "100%", - paddingBottom: constants.generalUnit * 6, - display: "flex", - flexDirection: "column", - alignItems: "center", - "& svg": { - marginBottom: constants.generalUnit * 2 - } - }, - shareFolderNameInput: { - display: "block" + newFolderInput: { + margin: 0, + width: "100%" }, buttonLink: { color: palette.additional["gray"][10], outline: "none", textDecoration: "underline", cursor: "pointer", - textAlign: "center", + textAlign: "left", marginBottom: constants.generalUnit * 2 }, error: { color: palette.error.main, textAlign: "center" }, - checkIcon: { - marginRight: constants.generalUnit * 2 - }, - successBox: { - textAlign: "center", - marginBottom: constants.generalUnit * 4 - }, - successText: { - display: "flex", - marginBottom: constants.generalUnit * 2 - }, inputWrapper: { marginBottom: 0 }, errorText: { - marginLeft: constants.generalUnit * 2, + marginTop: constants.generalUnit * 1, color: palette.error.main }, - titleWrapper: { - padding: "0 5px" - } + subModal: ({ width }: StyleProps) => ({ + width, + [breakpoints.down("sm")]: { + width: "100%" + } + }) }) } ) interface IShareFileProps { fileSystemItems: FileSystemItem[] - close: () => void + onClose: () => void } -const ShareModal = ({ close, fileSystemItems }: IShareFileProps) => { - const classes = useStyles() +const ShareModal = ({ onClose, fileSystemItems }: IShareFileProps) => { const { handleCreateSharedFolder } = useCreateOrEditSharedFolder() const { accountRestricted } = useFilesApi() const [sharedFolderName, setSharedFolderName] = useState("") - const { sharedFolderReaders, sharedFolderWriters, handleLookupUser, onNewUsers, usersError, resetUsers } = useLookupSharedFolderUser() - const [isUsingCurrentBucket, setIsUsingCurrentBucket] = useState(true) + const [isUsingExistingBucket, setIsUsingExistingBucket] = useState(true) const [keepOriginalFile, setKeepOriginalFile] = useState(true) + const [bucketToUpload, setBucketToUpload] = useState() const [destinationBucket, setDestinationBucket] = useState() + const [isFolderCreationLoading, setIsFolderCreationLoading] = useState(false) const { buckets, transferFileBetweenBuckets } = useFiles() const { bucket, currentPath } = useFileBrowser() const { profile } = useUser() const [nameError, setNameError] = useState("") const inSharedBucket = useMemo(() => bucket?.type === "share", [bucket]) + + const classes = useStyles({ + width: bucketToUpload ? 600 : 500 + }) + const isReader = useMemo(() => { if (!bucket) return false @@ -212,9 +170,11 @@ const ShareModal = ({ close, fileSystemItems }: IShareFileProps) => { return buckets .filter(buck => buck.type === "share" || buck.type === "csf") - // filter out the current bucket + // do not show any buckets being deleted + .filter(buck => buck.status !== "deleting") + // Do not show the current bucket .filter(buck => buck.id !== bucket?.id) - // all buckets where the user is reader or writer + // Show only buckets where the user is owner or writer .filter(buck => !!buck.writers.find((w) => w.uuid === profile.userId) || !!buck.owners.find((o) => o.uuid === profile.userId)) // filter out CSF and share buckets where user is an owner if their account is restricted .filter(buck => !(!!accountRestricted && (buck.type === "csf" || !!buck.owners.find(o => o.uuid === profile.userId)))) @@ -227,15 +187,14 @@ const ShareModal = ({ close, fileSystemItems }: IShareFileProps) => { const hasNoSharedBucket = useMemo(() => bucketsOptions.length === 0, [bucketsOptions.length]) useEffect(() => { - resetUsers() setSharedFolderName("") setNameError("") - }, [resetUsers]) + }, []) // if the user has no shared bucket, we default to new folder creation useEffect(() => { if (hasNoSharedBucket && !accountRestricted) { - setIsUsingCurrentBucket(false) + setIsUsingExistingBucket(false) } }, [hasNoSharedBucket, accountRestricted]) @@ -256,22 +215,23 @@ const ShareModal = ({ close, fileSystemItems }: IShareFileProps) => { }, []) const handleShare = useCallback(async () => { - if(!bucket) { + if (!bucket) { console.error("Bucket is undefined") return } - if(!destinationBucket && isUsingCurrentBucket){ + if (!destinationBucket && isUsingExistingBucket) { return } let bucketToUpload: BucketKeyPermission | undefined = destinationBucket - if (!isUsingCurrentBucket) { + if (!isUsingExistingBucket) { + setIsFolderCreationLoading(true) try { - const newBucket = await handleCreateSharedFolder(sharedFolderName, sharedFolderReaders, sharedFolderWriters) + const newBucket = await handleCreateSharedFolder(sharedFolderName, [], []) - if(!newBucket){ + if (!newBucket) { return } bucketToUpload = newBucket @@ -279,77 +239,99 @@ const ShareModal = ({ close, fileSystemItems }: IShareFileProps) => { console.error(e) return } + setIsFolderCreationLoading(false) } - if(!bucketToUpload){ + if (!bucketToUpload) { console.error("Bucket id to upload is undefined") return } transferFileBetweenBuckets(bucket, fileSystemItems, currentPath, bucketToUpload, keepOriginalFile) - close() + setBucketToUpload(bucketToUpload) + if (isUsingExistingBucket) { + onClose() + } }, [ bucket, destinationBucket, handleCreateSharedFolder, - isUsingCurrentBucket, + isUsingExistingBucket, sharedFolderName, - sharedFolderReaders, - sharedFolderWriters, keepOriginalFile, - close, + onClose, transferFileBetweenBuckets, currentPath, fileSystemItems ]) + const { captureEvent } = usePosthogContext() + return ( + )} > -
-
- -
-
- - {inSharedBucket - ? t`Copy file` - : t`Share file` - } - -
-
- {isUsingCurrentBucket - ? ( -
- setDestinationBucket(buckets.find((bu) => bu.id === val))} - /> -
- ) - : ( - <> -
+ {bucketToUpload + ? + :
+
+
+ +
+
+ + {inSharedBucket + ? t`Copy file` + : t`Share file` + } + +
+
+
+ {isUsingExistingBucket + ? ( +
+ setDestinationBucket(buckets.find((bu) => bu.id === val))} + /> +
+ ) + : ( +
- {nameError && ( + {!!nameError && ( { )}
-
- onNewUsers(values, "read")} - label={t`Give view-only permission to:`} - labelClassName={classes.inputLabel} - value={sharedFolderReaders} - fetchTags={(inputVal) => handleLookupUser(inputVal, "read")} - placeholder={t`Add by sharing address, username or wallet address`} - styles={{ - control: (provided) => ({ - ...provided, - minHeight: 90, - alignContent: "start" - }) - }} - loadingMessage={t`Loading`} - noOptionsMessage={t`No user found for this query.`} - /> -
-
- onNewUsers(values, "write")} - label={t`Give edit permission to:`} - labelClassName={classes.inputLabel} - value={sharedFolderWriters} - fetchTags={(inputVal) => handleLookupUser(inputVal, "write")} - placeholder={t`Add by sharing address, username or wallet address`} - styles={{ - control: (provided) => ({ - ...provided, - minHeight: 90, - alignContent: "start" - }) - }} - loadingMessage={t`Loading...`} - noOptionsMessage={t`No user found for this query.`} - /> -
- {!!usersError && ( - - {usersError} - - )} - - )} -
-
+ )} +
{!hasNoSharedBucket && (
setIsUsingCurrentBucket(!isUsingCurrentBucket)} + onClick={() => setIsUsingExistingBucket(!isUsingExistingBucket)} > { - isUsingCurrentBucket - ? Create a new shared folder - : Use an existing shared folder + isUsingExistingBucket + ? Or Create a new shared folder + : Or Use an existing shared folder }
)} - {!isReader && ( -
- setKeepOriginalFile(!keepOriginalFile)} - label={t`Keep original files`} - /> +
+ {!isReader && ( +
+ { + captureEvent("copy or move files on share") + setKeepOriginalFile(!keepOriginalFile) + } + } + label={t`Keep original files`} + /> +
+ )} +
+ +
- )} -
- -
-
+ } + ) } -export default ShareModal +export default ShareModal \ No newline at end of file diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/SharedFoldersOverview.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/SharedFoldersOverview.tsx index 11043af19a..55662d53fa 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/SharedFoldersOverview.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/SharedFoldersOverview.tsx @@ -1,4 +1,4 @@ -import React, { useCallback, useMemo, useState } from "react" +import React, { useCallback, useEffect, useMemo, useState } from "react" import { Typography, Table, @@ -17,8 +17,7 @@ import { BucketKeyPermission, useFiles } from "../../../Contexts/FilesContext" import { t, Trans } from "@lingui/macro" import { createStyles, makeStyles, useThemeSwitcher } from "@chainsafe/common-theme" import { CSFTheme } from "../../../Themes/types" -import CreateOrEditSharedFolderModal from "./CreateOrEditSharedFolderModal" -import clsx from "clsx" +import CreateOrManageSharedFolderModal from "./CreateOrManageSharedFolderModal" import { useFilesApi } from "../../../Contexts/FilesApiContext" import { ROUTE_LINKS } from "../../FilesRoutes" import SharedFolderRow from "./views/FileSystemItem/SharedFolderRow" @@ -27,13 +26,13 @@ import SharingExplainerModal from "../../SharingExplainerModal" import { useSharingExplainerModalFlag } from "./hooks/useSharingExplainerModalFlag" import { usePageTrack } from "../../../Contexts/PosthogContext" import RestrictedModeBanner from "../../Elements/RestrictedModeBanner" +import clsx from "clsx" -export const desktopSharedGridSettings = "69px 3fr 120px 190px 150px 45px !important" +export const desktopSharedGridSettings = "50px 3fr 90px 140px 140px 45px !important" export const mobileSharedGridSettings = "3fr 80px 45px !important" const useStyles = makeStyles( ({ animation, breakpoints, constants, palette }: CSFTheme) => { - return createStyles({ root: { position: "relative", @@ -107,6 +106,9 @@ const useStyles = makeStyles( }, confirmDeletionDialog: { top: "50%" + }, + buttonWrap: { + whiteSpace: "nowrap" } }) } @@ -132,6 +134,10 @@ const SharedFolderOverview = () => { usePageTrack() + useEffect(() => { + refreshBuckets(true) + }, [refreshBuckets]) + const handleSortToggle = (targetColumn: SortingType) => { if (column !== targetColumn) { setColumn(targetColumn) @@ -190,21 +196,25 @@ const SharedFolderOverview = () => { setBucketToEdit(undefined) setCreateOrEditSharedFolderMode("create") }} - disabled={accountRestricted} + data-cy="button-create-a-shared-folder" > - Create a Shared Folder + + Create a Shared Folder +
{isLoadingBuckets && ( -
- - +
+ + Loading your shared folders…
@@ -281,7 +291,7 @@ const SharedFolderOverview = () => { showModal={!hasSeenSharingExplainerModal} onHide={hideModal} /> - { diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/hooks/useGetFile.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/hooks/useGetFile.tsx index 4f3b9363de..240494ab0c 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/hooks/useGetFile.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/hooks/useGetFile.tsx @@ -38,7 +38,6 @@ export const useGetFile = () => { const cancelToken = getSource().token setIsDownloading(true) setError("") - try { const content = await getFileContent( id, @@ -60,12 +59,11 @@ export const useGetFile = () => { return content } catch (error) { - setIsDownloading(false) - // If no error is thrown, this was due to a cancellation by the user. if (error) { console.error(error) setError(t`There was an error getting the preview.`) + setIsDownloading(false) } } }, [bucket, getFileContent]) diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/hooks/useLookupUser.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/hooks/useLookupUser.tsx index 918e956183..ab1f55def9 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/hooks/useLookupUser.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/hooks/useLookupUser.tsx @@ -1,4 +1,3 @@ -import { LookupUserRequest } from "@chainsafe/files-api-client" import { t } from "@lingui/macro" import { useCallback, useState } from "react" import { useFilesApi } from "../../../../Contexts/FilesApiContext" @@ -25,8 +24,7 @@ export const useLookupSharedFolderUser = () => { )) if (foundIntersectingUsers.length) { - setUsersError(t`User ${ - centerEllipsis(foundIntersectingUsers[0].label) + setUsersError(t`User ${centerEllipsis(foundIntersectingUsers[0].label) } is both a reader and writer`) } else { setUsersError("") @@ -36,7 +34,11 @@ export const useLookupSharedFolderUser = () => { const handleLookupUser = useCallback(async (inputVal: string, permission: SharedFolderUserPermission) => { if (inputVal === "") return [] - const lookupBody: LookupUserRequest = {} + const lookupBody = { + public_address: undefined as string | undefined, + identity_public_key: undefined as string | undefined, + username: undefined as string | undefined + } const ethAddressRegex = new RegExp("^0(x|X)[a-fA-F0-9]{40}$") // Eth Address Starting with 0x and 40 HEX chars const pubKeyRegex = new RegExp("^0(x|X)[a-fA-F0-9]{66}$") // Compressed public key, 66 chars long @@ -49,7 +51,7 @@ export const useLookupSharedFolderUser = () => { } try { - const result = await filesApiClient.lookupUser(lookupBody) + const result = await filesApiClient.lookupUser(lookupBody.username, lookupBody.public_address, lookupBody.identity_public_key) if (!result) return [] const usersList = permission === "read" ? sharedFolderReaders : sharedFolderWriters diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/hooks/useSharingExplainerModalFlag.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/hooks/useSharingExplainerModalFlag.tsx index b5c02d9318..d706cdc5d4 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/hooks/useSharingExplainerModalFlag.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/hooks/useSharingExplainerModalFlag.tsx @@ -7,7 +7,6 @@ export const DISMISSED_SHARING_EXPLAINER_KEY = "csf.dismissedSharingExplainer" export const useSharingExplainerModalFlag = () => { const { localStore, setLocalStore } = useUser() const [hasSeenSharingExplainerModal, setHasSeenSharingExplainerModal] = useState(true) - useEffect(() => { if (!localStore) { return diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/views/FileSystemItem/FileSystemGridItem.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/views/FileSystemItem/FileSystemGridItem.tsx index c9d140d722..62b1e5aeb7 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/views/FileSystemItem/FileSystemGridItem.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/views/FileSystemItem/FileSystemGridItem.tsx @@ -1,11 +1,12 @@ -import React, { useCallback, useEffect, useRef } from "react" +import React, { useCallback, useEffect, useMemo, useRef } from "react" import { makeStyles, createStyles, useThemeSwitcher, useOnClickOutside, LongPressEvents } from "@chainsafe/common-theme" import { t } from "@lingui/macro" import clsx from "clsx" import { FormikTextInput, IMenuItem, - MoreIcon + MoreIcon, + Typography } from "@chainsafe/common-components" import { CSFTheme } from "../../../../../Themes/types" import { FileSystemItem } from "../../../../../Contexts/FilesContext" @@ -64,9 +65,15 @@ const useStyles = makeStyles(({ breakpoints, constants, palette }: CSFTheme) => desktopRename: { display: "flex", flexDirection: "row", + alignItems: "center", "& svg": { width: 20, height: 20 + }, + "& > span": { + fontSize: 16, + lineHeight: "20px", + marginLeft: constants.generalUnit / 2 } }, dropdownIcon: { @@ -152,15 +159,45 @@ const FileSystemGridItem = React.forwardRef( const { desktop } = useThemeSwitcher() const formRef = useRef(null) + const { + fileName, + extension + } = useMemo(() => { + if (isFolder) { + return { + fileName : name, + extension: "" + } + } + const split = name.split(".") + const extension = `.${split[split.length - 1]}` + + if (split.length === 1) { + return { + fileName : name, + extension: "" + } + } + + return { + fileName: name.slice(0, name.length - extension.length), + extension: split[split.length - 1] + } + }, [name, isFolder]) + const formik = useFormik({ initialValues: { - name + name: fileName }, validationSchema: nameValidator, onSubmit: (values: { name: string }) => { - const newName = values.name.trim() + const newName = extension !== "" ? `${values.name.trim()}.${extension}` : values.name.trim() - newName && handleRename && handleRename(file.cid, newName) + if (newName !== name) { + newName && handleRename && handleRename(file.cid, newName) + } else { + stopEditing() + } }, enableReinitialize: true }) @@ -241,6 +278,13 @@ const FileSystemGridItem = React.forwardRef( } autoFocus={editing === cid} /> + { + !isFolder && extension !== "" && ( + + { `.${extension}` } + + ) + } ) diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/views/FileSystemItem/FileSystemItem.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/views/FileSystemItem/FileSystemItem.tsx index 0dabb8a43a..f86bfafdb4 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/views/FileSystemItem/FileSystemItem.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/views/FileSystemItem/FileSystemItem.tsx @@ -43,13 +43,12 @@ const useStyles = makeStyles(({ breakpoints, constants }: CSFTheme) => { width: "100%", [breakpoints.up("md")]: { margin: 0 - }, - [breakpoints.down("md")]: { - margin: `${constants.generalUnit * 4.2}px 0` } }, modalRoot: { - [breakpoints.down("md")]: {} + [breakpoints.down("md")]: { + paddingBottom: Number(constants?.mobileButtonHeight) + constants.generalUnit + } }, modalInner: { [breakpoints.down("md")]: { @@ -62,18 +61,34 @@ const useStyles = makeStyles(({ breakpoints, constants }: CSFTheme) => { maxWidth: `${breakpoints.width("md")}px !important` } }, + renameModal: { + padding: constants.generalUnit * 4 + }, renameHeader: { textAlign: "center" }, + renameInputWrapper: { + display: "flex", + flexDirection: "row", + alignItems: "flex-end", + [breakpoints.down("md")]: { + margin: `${constants.generalUnit * 4.2}px 0` + }, + "& > span": { + display: "block", + fontSize: 16, + lineHeight: "20px", + marginLeft: constants.generalUnit / 2, + marginBottom: (constants.generalUnit * 2.50), + transform: "translateY(50%)" + } + }, renameFooter: { display: "flex", flexDirection: "row", alignItems: "center", justifyContent: "flex-end" }, - renameModal: { - padding: constants.generalUnit * 4 - }, okButton: { marginLeft: constants.generalUnit }, @@ -152,19 +167,55 @@ const FileSystemItem = ({ const { downloadMultipleFiles } = useFiles() const { cid, name, isFolder, content_type } = file const inSharedFolder = useMemo(() => bucket?.type === "share", [bucket]) + + const { + fileName, + extension + } = useMemo(() => { + if (isFolder) { + return { + fileName : name, + extension: "" + } + } + const split = name.split(".") + const extension = `.${split[split.length - 1]}` + + if (split.length === 1) { + return { + fileName : name, + extension: "" + } + } + + return { + fileName: name.slice(0, name.length - extension.length), + extension: split[split.length - 1] + } + }, [name, isFolder]) + const formik = useFormik({ initialValues: { - name + name: fileName }, validationSchema: nameValidator, onSubmit: (values: { name: string }) => { - const newName = values.name.trim() + const newName = extension !== "" ? `${values.name.trim()}.${extension}` : values.name.trim() - newName && handleRename && handleRename(file.cid, newName) + if (newName !== name) { + newName && handleRename && handleRename(file.cid, newName) + } else { + stopEditing() + } }, enableReinitialize: true }) + const stopEditing = useCallback(() => { + setEditing(undefined) + formik.resetForm() + }, [formik, setEditing]) + let Icon if (isFolder) { Icon = FolderFilledSvg @@ -480,7 +531,7 @@ const FileSystemItem = ({ }} closePosition="none" active={editing === cid} - onClose={() => setEditing("")} + onClose={() => stopEditing()} >
@@ -494,13 +545,22 @@ const FileSystemItem = ({ : Rename file } - +
+ + { + !isFolder && extension !== "" && ( + + { `.${extension}` } + + ) + } +
+ +
+ +
+ + {name} + + ) } - - } - options={menuItems} - style={{ focusVisible: classes.focusVisible }} - /> - - + ) } diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/views/FilesList.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/views/FilesList.tsx index 0889c68958..8cdec63e6c 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/views/FilesList.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/views/FilesList.tsx @@ -53,7 +53,6 @@ import { useFilesApi } from "../../../../Contexts/FilesApiContext" import RestrictedModeBanner from "../../../Elements/RestrictedModeBanner" import clsx from "clsx" import EmptySvg from "../../../../Media/Empty.svg" -import { getPathWithFile } from "../../../../Utils/pathUtils" const baseOperations: FileOperation[] = ["download", "info", "preview", "share"] const readerOperations: FileOperation[] = [...baseOperations, "report"] @@ -302,6 +301,15 @@ const useStyles = makeStyles( width: 20, marginRight: constants.generalUnit * 1.5, fill: constants.previewModal.menuItemIconColor + }, + fileNameHeader: { + whiteSpace: "nowrap", + overflow: "hidden", + textOverflow: "ellipsis", + marginRight: constants.generalUnit * 2 + }, + buttonWrap: { + whiteSpace: "nowrap" } }) } @@ -610,8 +618,8 @@ const FilesList = ({ isShared = false }: Props) => { }, [setIsSurveyBannerVisible]) const handleViewFolder = useCallback((cid: string) => { - viewFolder && viewFolder(cid) - }, [viewFolder]) + !loadingCurrentPath && viewFolder && viewFolder(cid) + }, [viewFolder, loadingCurrentPath]) const handleOpenMoveFileDialog = useCallback((e: React.MouseEvent) => { e.preventDefault() @@ -638,7 +646,7 @@ const FilesList = ({ isShared = false }: Props) => { <> - Create folder + New folder ), @@ -1102,7 +1110,7 @@ const FilesList = ({ isShared = false }: Props) => { closePreview={closePreview} nextFile={fileIndex < files.length - 1 ? setNextPreview : undefined} previousFile={fileIndex > 0 ? setPreviousPreview : undefined} - filePath={isSearch && getPath ? getPath(files[fileIndex].cid) : getPathWithFile(currentPath, files[fileIndex].name)} + filePath={isSearch && getPath ? getPath(files[fileIndex].cid) : currentPath} /> )} { filePath && isReportFileModalOpen && @@ -1125,7 +1133,7 @@ const FilesList = ({ isShared = false }: Props) => { } { !showExplainerBeforeShare && isShareModalOpen && selectedItems.length && { + onClose={() => { setIsShareModalOpen(false) setFilePath(undefined) }} diff --git a/packages/files-ui/src/Components/Modules/FilePreviewModal.tsx b/packages/files-ui/src/Components/Modules/FilePreviewModal.tsx index 708a6c1493..b20e8a399a 100644 --- a/packages/files-ui/src/Components/Modules/FilePreviewModal.tsx +++ b/packages/files-ui/src/Components/Modules/FilePreviewModal.tsx @@ -162,7 +162,7 @@ const useStyles = makeStyles( position: "absolute" } }, - focusVisible:{ + focusVisible: { backgroundColor: "transparent !important" }, menuWrapper: { @@ -185,7 +185,7 @@ interface Props { const FilePreviewModal = ({ file, nextFile, previousFile, closePreview, filePath }: Props) => { const classes = useStyles() const { downloadFile } = useFiles() - const [fileContent, setFileContent] = useState() + const [fileContent, setFileContent] = useState() const { bucket } = useFileBrowser() const { buckets } = useFiles() const { desktop } = useThemeSwitcher() @@ -215,8 +215,8 @@ const FilePreviewModal = ({ file, nextFile, previousFile, closePreview, filePath bucketId = bucket.id } + setFileContent(undefined) if (previewRendererKey) { - setFileContent(undefined) getFile({ file, filePath: getPathWithFile(filePath, file.name), bucketId }) .then((content) => { setFileContent(content) @@ -241,11 +241,11 @@ const FilePreviewModal = ({ file, nextFile, previousFile, closePreview, filePath useHotkeys("Left,ArrowLeft", () => { previousFile && previousFile() - }) + }, [previousFile]) useHotkeys("Right,ArrowRight", () => { nextFile && nextFile() - }) + }, [nextFile]) const handleDownload = useCallback(() => { if (!name || !cid || !bucket) return @@ -296,7 +296,7 @@ const FilePreviewModal = ({ file, nextFile, previousFile, closePreview, filePath } + icon={} options={menuItems} style={{ focusVisible: classes.focusVisible, diff --git a/packages/files-ui/src/Components/Modules/LoginModule/PasswordlessEmail.tsx b/packages/files-ui/src/Components/Modules/LoginModule/PasswordlessEmail.tsx index a741b3debb..72f4434b40 100644 --- a/packages/files-ui/src/Components/Modules/LoginModule/PasswordlessEmail.tsx +++ b/packages/files-ui/src/Components/Modules/LoginModule/PasswordlessEmail.tsx @@ -95,13 +95,15 @@ const PasswordlessEmail = ({ resetLogin, email }: IPasswordlessEmail) => { const { login } = useThresholdKey() const [error, setError] = useState() - const onSubmitNonce = useCallback((values) => { + const onSubmitNonce = useCallback(({ nonce }: {nonce: string}) => { if (!email) return + setIsSubmitNonceLoading(true) setError(undefined) + filesApiClient.postIdentityEmailToken({ - email: email, - nonce: values.nonce + email, + nonce: nonce.trim() }).then(async (data) => { await login("email", { token: data.token || "", email }) setIsSubmitNonceLoading(false) diff --git a/packages/files-ui/src/Components/Modules/PreviewRenderers/VideoPreview.tsx b/packages/files-ui/src/Components/Modules/PreviewRenderers/VideoPreview.tsx index 8c6b688ea9..8b84d80b7e 100644 --- a/packages/files-ui/src/Components/Modules/PreviewRenderers/VideoPreview.tsx +++ b/packages/files-ui/src/Components/Modules/PreviewRenderers/VideoPreview.tsx @@ -5,7 +5,8 @@ import { makeStyles, createStyles } from "@chainsafe/common-theme" const useStyles = makeStyles(() => createStyles({ root: { - maxWidth: "100vw" + maxWidth: "100vw", + maxHeight: "100vh" } }) ) @@ -28,7 +29,7 @@ const VideoPreview: React.FC = ({ contents }) => { className={classes.root} src={videoUrl} controls - autoPlay/> + autoPlay /> ) } diff --git a/packages/files-ui/src/Components/Modules/Settings/ProfileTab/index.tsx b/packages/files-ui/src/Components/Modules/Settings/ProfileTab/index.tsx index 13ba3547a5..be30dcfd8a 100644 --- a/packages/files-ui/src/Components/Modules/Settings/ProfileTab/index.tsx +++ b/packages/files-ui/src/Components/Modules/Settings/ProfileTab/index.tsx @@ -9,7 +9,8 @@ import { RadioInput, TextInput, CheckIcon, - Divider + Divider, + CheckboxInput } from "@chainsafe/common-components" import { makeStyles, @@ -89,8 +90,7 @@ const useStyles = makeStyles(({ constants, breakpoints, palette, typography }: C }, button: { width: 200, - margin: `0px ${constants.generalUnit * 0.5}px ${ - constants.generalUnit * 4 + margin: `0px ${constants.generalUnit * 0.5}px ${constants.generalUnit * 4 }px` }, icon: { @@ -161,7 +161,7 @@ const useStyles = makeStyles(({ constants, breakpoints, palette, typography }: C }, usernameForm: { display: "flex", - marginBottom: constants.generalUnit * 4, + marginBottom: constants.generalUnit, "& svg": { fill: palette.success.main } @@ -184,14 +184,14 @@ const profileValidation = yup.object().shape({ const ProfileView = () => { const { themeKey, setTheme } = useThemeSwitcher() const { addToast } = useToasts() - const { profile, updateProfile, addUsername, lookupOnUsername } = useUser() + const { profile, updateProfile, addUsername, lookupOnUsername, toggleLookupConsent } = useUser() const { publicKey } = useThresholdKey() const [updatingProfile, setUpdatingProfile] = useState(false) const [showUsernameForm, setShowUsernameForm] = useState(false) const [username, setUsername] = useState("") const [usernameData, setUsernameData] = useState({ error: "", loading: false }) const formik = useFormik({ - initialValues:{ + initialValues: { firstName: profile?.firstName || "", lastName: profile?.lastName || "" // email: profile?.email || "" @@ -396,90 +396,89 @@ const ProfileView = () => {
} - {profile?.username - ?
- - Username - - - This username is public - -
- -
-
- :
- - Username - - {showUsernameForm - ?
- - Usernames are public and can't be changed after creation. - -
- -
- -
- +
+ {profile?.username + ? <> + + Username + +
+
- :
- - - You haven't set a username yet. - - {" "} - setShowUsernameForm(true)} + + : <> + + Username + + {showUsernameForm + ?
+ - Add a username - - -
- } -
- } + Usernames are public and can't be changed after creation. + +
+ +
+ +
+ +
+ :
+ + + You haven't set a username yet. + + {" "} + setShowUsernameForm(true)} + > + Add a username + + +
+ } + + } + +
@@ -636,7 +635,7 @@ const ProfileView = () => { > Language - +
diff --git a/packages/files-ui/src/Components/SharingExplainerModal.tsx b/packages/files-ui/src/Components/SharingExplainerModal.tsx index 39c97327a7..11ba933f11 100644 --- a/packages/files-ui/src/Components/SharingExplainerModal.tsx +++ b/packages/files-ui/src/Components/SharingExplainerModal.tsx @@ -24,9 +24,6 @@ const useStyles = makeStyles( textAlign: "center", marginBottom: constants.generalUnit * 3 }, - modalInner: { - maxWidth: "400px !important" - }, buttonLink: { outline: "none", textDecoration: "underline", @@ -147,7 +144,6 @@ const SharingExplainerModal = ({ showModal, onHide }: Props) => { return ( { let encryptionKey = "" - switch(bucket.type) { + switch (bucket.type) { case "csf": case "trash": { encryptionKey = personalEncryptionKey @@ -168,7 +168,8 @@ const FilesProvider = ({ children }: FilesContextProps) => { case "share": { encryptionKey = await getKeyForSharedBucket(bucket) break - }} + } + } return encryptionKey }, [getKeyForSharedBucket, personalEncryptionKey, userId]) @@ -493,7 +494,7 @@ const FilesProvider = ({ children }: FilesContextProps) => { itemsToDownload: FileSystemItem[], currentPath: string, bucketId: string - ): Promise => { + ): Promise => { return await itemsToDownload.reduce( async (acc: Promise, item: FileSystemItem): Promise => { if (item.isFolder) { @@ -566,7 +567,7 @@ const FilesProvider = ({ children }: FilesContextProps) => { } }) - if(file) { + if (file) { const fileArrayBuffer = await file.arrayBuffer() const fullPath = getPathWithFile(item.path, item.name) const relativeFilePath = getRelativePath(fullPath, currentPath) @@ -662,7 +663,9 @@ const FilesProvider = ({ children }: FilesContextProps) => { return Promise.resolve() } catch (error: any) { console.error(error) - let errorMessage = `${t`An error occurred: `} ${typeof(error) === "string" ? error : error.error.message ? error.error.message : ""}` + let errorMessage = `${t`An error occurred: `} ${typeof (error) === "string" + ? error + : error?.error?.message || ""}` if (axios.isCancel(error)) { errorMessage = t`Downloads cancelled` } @@ -678,7 +681,7 @@ const FilesProvider = ({ children }: FilesContextProps) => { } }, [getFileContent, addToast, updateToast]) - const createSharedFolder = useCallback(async (name: string, writerUsers?: SharedFolderUser[], readerUsers?: SharedFolderUser[]) => { + const createSharedFolder = useCallback(async (name: string, writerUsers?: SharedFolderUser[], readerUsers?: SharedFolderUser[]) => { if (!publicKey) return const bucketEncryptionKey = Buffer.from( @@ -804,7 +807,7 @@ const FilesProvider = ({ children }: FilesContextProps) => { } }) - if(file) { + if (file) { await encryptAndUploadFiles( destinationBucket, [new File([file], item.name, { type: item.content_type })], @@ -839,7 +842,7 @@ const FilesProvider = ({ children }: FilesContextProps) => { ? t`${successCount} files transferred successfully, ${totalFileNumber - successCount} failed` : t`${inSharedBucket ? "Copying" : "Sharing"} failed`, type: successCount ? "success" : "error", - progress: undefined, + progress: undefined, isClosable: true }, true) setTransfersInProgress(false) @@ -853,15 +856,14 @@ const FilesProvider = ({ children }: FilesContextProps) => { : t`${inSharedBucket ? "Copying" : "Sharing"} failed` if (axios.isCancel(error)) { errorMessage = successCount - ? t`${ - inSharedBucket ? "Copying" : "Sharing" + ? t`${inSharedBucket ? "Copying" : "Sharing" } cancelled - ${successCount} files ${inSharedBucket ? "copied" : "shared"} successfully` : t`${inSharedBucket ? "Copying" : "Sharing"} cancelled` } updateToast(toastId, { title: errorMessage, type: "error", - progress: undefined, + progress: undefined, isClosable: true }, true) } diff --git a/packages/files-ui/src/Contexts/PosthogContext.tsx b/packages/files-ui/src/Contexts/PosthogContext.tsx index 47be991ef7..3021ce91c7 100644 --- a/packages/files-ui/src/Contexts/PosthogContext.tsx +++ b/packages/files-ui/src/Contexts/PosthogContext.tsx @@ -10,6 +10,7 @@ import { useUser } from "./UserContext" export type PosthogContext = { hasOptedIn: boolean posthogInitialized: boolean + captureEvent: (eventName: string, properties?: posthog.Properties) => void } type PosthogProviderProps = posthog.Config & { @@ -18,7 +19,8 @@ type PosthogProviderProps = posthog.Config & { const PosthogContext = React.createContext({ hasOptedIn: false, - posthogInitialized: false + posthogInitialized: false, + captureEvent: () => undefined }) const useStyles = makeStyles( @@ -116,6 +118,12 @@ const PosthogProvider = ({ children }: PosthogProviderProps) => { } }, [posthogInitialized, touchCookieBanner]) + const captureEvent = useCallback((eventName: string, properties?: posthog.Properties) => { + if (posthogInitialized) { + posthog.capture(eventName, properties) + } + }, [posthogInitialized]) + useEffect(() => { if (profile) { posthogInitialized && posthog.identify(profile.userId) @@ -128,7 +136,8 @@ const PosthogProvider = ({ children }: PosthogProviderProps) => { {children} diff --git a/packages/files-ui/src/Contexts/UserContext.tsx b/packages/files-ui/src/Contexts/UserContext.tsx index 54fb81d282..6a886bfc18 100644 --- a/packages/files-ui/src/Contexts/UserContext.tsx +++ b/packages/files-ui/src/Contexts/UserContext.tsx @@ -19,6 +19,7 @@ export type Profile = { email?: string createdAt?: Date username?: string + lookupConsent: boolean } interface ILocalStore { @@ -39,6 +40,7 @@ interface IUserContext { addUsername: (username: string) => Promise removeUser(): void getProfileTitle(): string + toggleLookupConsent(): Promise } const UserContext = React.createContext(undefined) @@ -71,7 +73,8 @@ const UserProvider = ({ children }: UserContextProps) => { email: profileApiData.email, publicAddress: profileApiData.public_address?.toLowerCase(), createdAt: profileApiData.created_at, - username: profileApiData.username + username: profileApiData.username, + lookupConsent: profileApiData.user_lookup_consent || false } setProfile(profileState) return Promise.resolve() @@ -140,10 +143,11 @@ const UserProvider = ({ children }: UserContextProps) => { }) return Promise.resolve() } catch (error: any) { + console.error(error) return Promise.reject( - Array.isArray(error) && error[0] - ? error[0].message - : "There was an error updating profile." + Array.isArray(error.error.details) + ? error.error.details.map((e: Details) => e.message).join(",") + : t`There was an error when setting username.` ) } } @@ -154,7 +158,7 @@ const UserProvider = ({ children }: UserContextProps) => { if (!profile) return Promise.reject("Profile not initialized") try { - await filesApiClient.updateUser({ + const result = await filesApiClient.updateUser({ first_name: profile.firstName || "", last_name: profile.lastName || "", email: profile.email || "", @@ -163,7 +167,33 @@ const UserProvider = ({ children }: UserContextProps) => { setProfile({ ...profile, - username + username: result.username + }) + return Promise.resolve() + } catch (error: any) { + console.error(error) + return Promise.reject( + Array.isArray(error.error.details) + ? error.error.details.map((e: Details) => e.message).join(",") + : t`There was an error when setting username.` + ) + } + } + + const toggleLookupConsent = async () => { + if (!profile) return Promise.reject("Profile not initialized") + try { + const result = await filesApiClient.updateUser({ + first_name: profile.firstName || "", + last_name: profile.lastName || "", + email: profile.email || "", + username: profile.username, + lookup_consent_flag: !profile.lookupConsent + }) + + setProfile({ + ...profile, + lookupConsent: result.user_lookup_consent || false }) return Promise.resolve() } catch (error: any) { @@ -179,7 +209,7 @@ const UserProvider = ({ children }: UserContextProps) => { const lookupOnUsername = async (username: string) => { if (!profile) return false try { - const alreadyExists = await filesApiClient.lookupUser({ username }) + const alreadyExists = await filesApiClient.lookupUser(username) return !!alreadyExists } catch (error) { console.error(error) @@ -215,7 +245,8 @@ const UserProvider = ({ children }: UserContextProps) => { removeUser, addUsername, lookupOnUsername, - getProfileTitle + getProfileTitle, + toggleLookupConsent }} > {children} diff --git a/packages/files-ui/src/locales/de/messages.po b/packages/files-ui/src/locales/de/messages.po index e9aa28ce22..471472f20c 100644 --- a/packages/files-ui/src/locales/de/messages.po +++ b/packages/files-ui/src/locales/de/messages.po @@ -28,6 +28,9 @@ msgstr "Akzeptieren" msgid "Account" msgstr "Konto" +msgid "Active links" +msgstr "" + msgid "Add Card" msgstr "" @@ -55,12 +58,18 @@ msgstr "" msgid "Adding you to the shared folder..." msgstr "" +msgid "Allow lookup by sharing key, wallet address or username" +msgstr "" + msgid "Amount" msgstr "" msgid "An error occurred:" msgstr "Es ist ein Fehler aufgetreten:" +msgid "Anyone with the link can:" +msgstr "" + msgid "Approve" msgstr "Genehmigen" @@ -163,18 +172,12 @@ msgstr "Weiter mit Web3 Wallet" msgid "Copied!" msgstr "Kopiert!" -msgid "Copy CID" -msgstr "IID kopieren" - msgid "Copy file" msgstr "" msgid "Copy info" msgstr "" -msgid "Copy link" -msgstr "" - msgid "Copy over" msgstr "" @@ -187,22 +190,22 @@ msgstr "" msgid "Create" msgstr "Erstellen" -msgid "Create Folder" -msgstr "" - msgid "Create Shared Folder" msgstr "" msgid "Create a Shared Folder" msgstr "" -msgid "Create a new shared folder" +msgid "Create a sharing link" msgstr "" -msgid "Create folder" -msgstr "Ordner erstellen" +msgid "Create folder & Copy over" +msgstr "" + +msgid "Create folder & Move over" +msgstr "" -msgid "Create new link" +msgid "Create link" msgstr "" msgid "Create your public username in <0>Settings!" @@ -442,15 +445,15 @@ msgstr "Vorschau wird geladen" msgid "Loading your shared folders…" msgstr "" -msgid "Loading..." -msgstr "" - msgid "Looks like you’re signing in from a new browser. Please choose one of the following to continue:" msgstr "Sieht aus, als würden Sie sich über einen neuen Browser anmelden. Bitte wählen Sie eine der folgenden Möglichkeiten, um fortzufahren:" msgid "Manage Access" msgstr "" +msgid "Manage Shared Folder" +msgstr "" + msgid "Method" msgstr "" @@ -478,6 +481,9 @@ msgstr "Name zu lang" msgid "New folder" msgstr "Neuer Ordner" +msgid "New shared folder name" +msgstr "" + msgid "Next" msgstr "Nächste" @@ -535,6 +541,12 @@ msgstr "" msgid "Operating system:" msgstr "Betriebssystem:" +msgid "Or Create a new shared folder" +msgstr "" + +msgid "Or Use an existing shared folder" +msgstr "" + msgid "Or confirm by signing into your Files on any browser you’ve used before." msgstr "" @@ -634,6 +646,9 @@ msgstr "Datei umbenennen" msgid "Rename folder" msgstr "Ordner umbenennen" +msgid "Rename shared folder" +msgstr "" + msgid "Report" msgstr "Melden" @@ -724,6 +739,9 @@ msgstr "Geteilt" msgid "Shared Folder Name" msgstr "" +msgid "Shared folder name" +msgstr "" + msgid "Shared folders" msgstr "" @@ -733,9 +751,6 @@ msgstr "" msgid "Sharing files" msgstr "" -msgid "Sharing link" -msgstr "" - msgid "Sign Out" msgstr "Abmelden" @@ -856,9 +871,6 @@ msgstr "" msgid "This username is already taken" msgstr "" -msgid "This username is public" -msgstr "Dieser Benutzername ist öffentlich" - msgid "This website uses cookies" msgstr "" @@ -904,9 +916,6 @@ msgstr "Eine andere Anmeldemethode verwenden" msgid "Use a saved browser" msgstr "Einen gespeicherten Browser verwenden" -msgid "Use an existing shared folder" -msgstr "" - msgid "User {0} is both a reader and writer" msgstr "" @@ -997,7 +1006,7 @@ msgstr "" msgid "Your recovery key can be used to restore your account in place of your backup secret phrase." msgstr "Ihr Wiederherstellungsschlüssel kann zur Wiederherstellung Ihres Kontos anstelle Ihres Sicherungsgeheimsatz verwendet werden." -msgid "edit rights" +msgid "can-edit" msgstr "" msgid "me" @@ -1018,13 +1027,10 @@ msgstr "" msgid "per year" msgstr "" -msgid "read rights" -msgstr "" - msgid "unknown" msgstr "unbekannt" -msgid "with" +msgid "view-only" msgstr "" msgid "{0, plural, one {Downloading {1} file} other {Downloading {2} files}}" diff --git a/packages/files-ui/src/locales/en/messages.po b/packages/files-ui/src/locales/en/messages.po index 87d0367081..dfeaf3d75e 100644 --- a/packages/files-ui/src/locales/en/messages.po +++ b/packages/files-ui/src/locales/en/messages.po @@ -28,6 +28,9 @@ msgstr "Accept" msgid "Account" msgstr "Account" +msgid "Active links" +msgstr "Active links" + msgid "Add Card" msgstr "Add Card" @@ -55,12 +58,18 @@ msgstr "Add viewers and editors by username, sharing id or Ethereum address." msgid "Adding you to the shared folder..." msgstr "Adding you to the shared folder..." +msgid "Allow lookup by sharing key, wallet address or username" +msgstr "Allow lookup by sharing key, wallet address or username" + msgid "Amount" msgstr "Amount" msgid "An error occurred:" msgstr "An error occurred:" +msgid "Anyone with the link can:" +msgstr "Anyone with the link can:" + msgid "Approve" msgstr "Approve" @@ -163,18 +172,12 @@ msgstr "Continue with Web3 Wallet" msgid "Copied!" msgstr "Copied!" -msgid "Copy CID" -msgstr "Copy CID" - msgid "Copy file" msgstr "Copy file" msgid "Copy info" msgstr "Copy info" -msgid "Copy link" -msgstr "Copy link" - msgid "Copy over" msgstr "Copy over" @@ -187,23 +190,23 @@ msgstr "Copying files" msgid "Create" msgstr "Create" -msgid "Create Folder" -msgstr "Create Folder" - msgid "Create Shared Folder" msgstr "Create Shared Folder" msgid "Create a Shared Folder" msgstr "Create a Shared Folder" -msgid "Create a new shared folder" -msgstr "Create a new shared folder" +msgid "Create a sharing link" +msgstr "Create a sharing link" + +msgid "Create folder & Copy over" +msgstr "Create folder & Copy over" -msgid "Create folder" -msgstr "Create folder" +msgid "Create folder & Move over" +msgstr "Create folder & Move over" -msgid "Create new link" -msgstr "Create new link" +msgid "Create link" +msgstr "Create link" msgid "Create your public username in <0>Settings!" msgstr "Create your public username in <0>Settings!" @@ -445,15 +448,15 @@ msgstr "Loading preview" msgid "Loading your shared folders…" msgstr "Loading your shared folders…" -msgid "Loading..." -msgstr "Loading..." - msgid "Looks like you’re signing in from a new browser. Please choose one of the following to continue:" msgstr "Looks like you’re signing in from a new browser. Please choose one of the following to continue:" msgid "Manage Access" msgstr "Manage Access" +msgid "Manage Shared Folder" +msgstr "Manage Shared Folder" + msgid "Method" msgstr "Method" @@ -481,6 +484,9 @@ msgstr "Name too long" msgid "New folder" msgstr "New folder" +msgid "New shared folder name" +msgstr "New shared folder name" + msgid "Next" msgstr "Next" @@ -538,6 +544,12 @@ msgstr "Oops! You need to pay for this month to upload more content." msgid "Operating system:" msgstr "Operating system:" +msgid "Or Create a new shared folder" +msgstr "Or Create a new shared folder" + +msgid "Or Use an existing shared folder" +msgstr "Or Use an existing shared folder" + msgid "Or confirm by signing into your Files on any browser you’ve used before." msgstr "Or confirm by signing into your Files on any browser you’ve used before." @@ -637,6 +649,9 @@ msgstr "Rename file" msgid "Rename folder" msgstr "Rename folder" +msgid "Rename shared folder" +msgstr "Rename shared folder" + msgid "Report" msgstr "Report" @@ -727,6 +742,9 @@ msgstr "Shared" msgid "Shared Folder Name" msgstr "Shared Folder Name" +msgid "Shared folder name" +msgstr "Shared folder name" + msgid "Shared folders" msgstr "Shared folders" @@ -736,9 +754,6 @@ msgstr "Shared with" msgid "Sharing files" msgstr "Sharing files" -msgid "Sharing link" -msgstr "Sharing link" - msgid "Sign Out" msgstr "Sign Out" @@ -859,9 +874,6 @@ msgstr "This link is not valid any more." msgid "This username is already taken" msgstr "This username is already taken" -msgid "This username is public" -msgstr "This username is public" - msgid "This website uses cookies" msgstr "This website uses cookies" @@ -907,9 +919,6 @@ msgstr "Use a different login method" msgid "Use a saved browser" msgstr "Use a saved browser" -msgid "Use an existing shared folder" -msgstr "Use an existing shared folder" - msgid "User {0} is both a reader and writer" msgstr "User {0} is both a reader and writer" @@ -1000,8 +1009,8 @@ msgstr "Your plan" msgid "Your recovery key can be used to restore your account in place of your backup secret phrase." msgstr "Your recovery key can be used to restore your account in place of your backup secret phrase." -msgid "edit rights" -msgstr "edit rights" +msgid "can-edit" +msgstr "can-edit" msgid "me" msgstr "me" @@ -1021,14 +1030,11 @@ msgstr "per week" msgid "per year" msgstr "per year" -msgid "read rights" -msgstr "read rights" - msgid "unknown" msgstr "unknown" -msgid "with" -msgstr "with" +msgid "view-only" +msgstr "view-only" msgid "{0, plural, one {Downloading {1} file} other {Downloading {2} files}}" msgstr "{0, plural, one {Downloading {1} file} other {Downloading {2} files}}" diff --git a/packages/files-ui/src/locales/es/messages.po b/packages/files-ui/src/locales/es/messages.po index ae198a6697..6fe89ddda9 100644 --- a/packages/files-ui/src/locales/es/messages.po +++ b/packages/files-ui/src/locales/es/messages.po @@ -29,6 +29,9 @@ msgstr "" msgid "Account" msgstr "Cuenta" +msgid "Active links" +msgstr "" + msgid "Add Card" msgstr "" @@ -56,12 +59,18 @@ msgstr "" msgid "Adding you to the shared folder..." msgstr "" +msgid "Allow lookup by sharing key, wallet address or username" +msgstr "" + msgid "Amount" msgstr "" msgid "An error occurred:" msgstr "" +msgid "Anyone with the link can:" +msgstr "" + msgid "Approve" msgstr "Aprobar" @@ -164,18 +173,12 @@ msgstr "Continuar con Monedero Web3 " msgid "Copied!" msgstr "Copiado!" -msgid "Copy CID" -msgstr "Copiar CID" - msgid "Copy file" msgstr "" msgid "Copy info" msgstr "" -msgid "Copy link" -msgstr "" - msgid "Copy over" msgstr "" @@ -188,22 +191,22 @@ msgstr "" msgid "Create" msgstr "Crear" -msgid "Create Folder" -msgstr "" - msgid "Create Shared Folder" msgstr "" msgid "Create a Shared Folder" msgstr "" -msgid "Create a new shared folder" +msgid "Create a sharing link" msgstr "" -msgid "Create folder" -msgstr "Crear Carpeta" +msgid "Create folder & Copy over" +msgstr "" + +msgid "Create folder & Move over" +msgstr "" -msgid "Create new link" +msgid "Create link" msgstr "" msgid "Create your public username in <0>Settings!" @@ -446,15 +449,15 @@ msgstr "Cargando vista previa" msgid "Loading your shared folders…" msgstr "" -msgid "Loading..." -msgstr "" - msgid "Looks like you’re signing in from a new browser. Please choose one of the following to continue:" msgstr "Parece que está iniciando sesión desde un nuevo navegador. Elija una de las siguientes opciones para continuar:" msgid "Manage Access" msgstr "" +msgid "Manage Shared Folder" +msgstr "" + msgid "Method" msgstr "" @@ -482,6 +485,9 @@ msgstr "" msgid "New folder" msgstr "Nuevo Folder" +msgid "New shared folder name" +msgstr "" + msgid "Next" msgstr "Próximo" @@ -539,6 +545,12 @@ msgstr "" msgid "Operating system:" msgstr "Sistema operativo:" +msgid "Or Create a new shared folder" +msgstr "" + +msgid "Or Use an existing shared folder" +msgstr "" + msgid "Or confirm by signing into your Files on any browser you’ve used before." msgstr "O confirme iniciando sesión en sus Archivos en cualquier navegador que haya usado antes." @@ -638,6 +650,9 @@ msgstr "" msgid "Rename folder" msgstr "" +msgid "Rename shared folder" +msgstr "" + msgid "Report" msgstr "" @@ -728,6 +743,9 @@ msgstr "" msgid "Shared Folder Name" msgstr "" +msgid "Shared folder name" +msgstr "" + msgid "Shared folders" msgstr "" @@ -737,9 +755,6 @@ msgstr "" msgid "Sharing files" msgstr "" -msgid "Sharing link" -msgstr "" - msgid "Sign Out" msgstr "Desconectar" @@ -860,9 +875,6 @@ msgstr "" msgid "This username is already taken" msgstr "" -msgid "This username is public" -msgstr "" - msgid "This website uses cookies" msgstr "" @@ -908,9 +920,6 @@ msgstr "Utilice un método de inicio de sesión diferente" msgid "Use a saved browser" msgstr "Utilice un navegador guardado" -msgid "Use an existing shared folder" -msgstr "" - msgid "User {0} is both a reader and writer" msgstr "" @@ -1001,7 +1010,7 @@ msgstr "" msgid "Your recovery key can be used to restore your account in place of your backup secret phrase." msgstr "" -msgid "edit rights" +msgid "can-edit" msgstr "" msgid "me" @@ -1022,13 +1031,10 @@ msgstr "" msgid "per year" msgstr "" -msgid "read rights" -msgstr "" - msgid "unknown" msgstr "" -msgid "with" +msgid "view-only" msgstr "" msgid "{0, plural, one {Downloading {1} file} other {Downloading {2} files}}" diff --git a/packages/files-ui/src/locales/fr/messages.po b/packages/files-ui/src/locales/fr/messages.po index 71d2681c90..936d849039 100644 --- a/packages/files-ui/src/locales/fr/messages.po +++ b/packages/files-ui/src/locales/fr/messages.po @@ -3,7 +3,7 @@ msgstr "" "Project-Id-Version: \n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-04-23 11:05+0200\n" -"PO-Revision-Date: 2021-10-30 12:33+0000\n" +"PO-Revision-Date: 2021-11-21 00:53+0000\n" "Last-Translator: J. Lavoie \n" "Language-Team: French \n" "Language: fr\n" @@ -11,7 +11,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 4.9-dev\n" +"X-Generator: Weblate 4.9.1\n" "Mime-Version: 1.0\n" msgid "A backup secret phrase will be generated and used for your account.<0/>We do not store it and <1>it can only be displayed once. Save it somewhere safe!" @@ -29,6 +29,9 @@ msgstr "Accepter" msgid "Account" msgstr "Compte" +msgid "Active links" +msgstr "Liens actifs" + msgid "Add Card" msgstr "" @@ -56,12 +59,18 @@ msgstr "Ajoutez des personnes pouvant visualiser ou afficher par nom d'utilisate msgid "Adding you to the shared folder..." msgstr "Je vous ajoute au dossier partagé…" +msgid "Allow lookup by sharing key, wallet address or username" +msgstr "Permettre la recherche par clé de partage, adresse du portefeuille ou nom d'utilisateur" + msgid "Amount" msgstr "" msgid "An error occurred:" msgstr "Une erreur s'est produite :" +msgid "Anyone with the link can:" +msgstr "Toute personne ayant le lien peut le faire :" + msgid "Approve" msgstr "Accepter" @@ -164,18 +173,12 @@ msgstr "Continuer avec un wallet Web3" msgid "Copied!" msgstr "Copié !" -msgid "Copy CID" -msgstr "Copier le CID" - msgid "Copy file" msgstr "Copier le fichier" msgid "Copy info" msgstr "Copier les infos" -msgid "Copy link" -msgstr "Copier le lien" - msgid "Copy over" msgstr "Copier" @@ -188,23 +191,23 @@ msgstr "Copie de fichiers" msgid "Create" msgstr "Créer" -msgid "Create Folder" -msgstr "Créer un dossier" - msgid "Create Shared Folder" msgstr "Créer un dossier partagé" msgid "Create a Shared Folder" msgstr "Créer un dossier partagé" -msgid "Create a new shared folder" -msgstr "Créer un nouveau dossier partagé" +msgid "Create a sharing link" +msgstr "Créer un lien de partage" -msgid "Create folder" -msgstr "Créer un dossier" +msgid "Create folder & Copy over" +msgstr "Créer un dossier et le copier" -msgid "Create new link" -msgstr "Créer un nouveau lien" +msgid "Create folder & Move over" +msgstr "Créer un dossier et le déplacer" + +msgid "Create link" +msgstr "Créer un lien" msgid "Create your public username in <0>Settings!" msgstr "Créez votre nom d'utilisateur public dans <0>Paramètres !" @@ -387,7 +390,7 @@ msgid "Go to Payments" msgstr "" msgid "Go to login" -msgstr "" +msgstr "Aller à la connexion" msgid "Got it" msgstr "Compris" @@ -446,15 +449,15 @@ msgstr "Chargement de l’aperçu" msgid "Loading your shared folders…" msgstr "Chargement de vos dossiers partagés…" -msgid "Loading..." -msgstr "Chargement…" - msgid "Looks like you’re signing in from a new browser. Please choose one of the following to continue:" msgstr "Il semble que vous vous connectiez à partir d’un nouveau navigateur. Veuillez choisir une des options suivantes pour continuer :" msgid "Manage Access" msgstr "Gérer l’accès" +msgid "Manage Shared Folder" +msgstr "Gérer le dossier partagé" + msgid "Method" msgstr "" @@ -482,6 +485,9 @@ msgstr "Le nom est trop long" msgid "New folder" msgstr "Nouveau dossier" +msgid "New shared folder name" +msgstr "Nouveau nom de dossier partagé" + msgid "Next" msgstr "Suivant" @@ -495,10 +501,10 @@ msgid "No file to download." msgstr "Aucun fichier à télécharger." msgid "No files to copy" -msgstr "" +msgstr "Aucun fichier à copier" msgid "No files to share" -msgstr "" +msgstr "Aucun fichier à partager" msgid "No files to show" msgstr "" @@ -539,6 +545,12 @@ msgstr "" msgid "Operating system:" msgstr "Système d’exploitation :" +msgid "Or Create a new shared folder" +msgstr "Ou créer un nouveau dossier partagé" + +msgid "Or Use an existing shared folder" +msgstr "Ou utiliser un dossier partagé existant" + msgid "Or confirm by signing into your Files on any browser you’ve used before." msgstr "Ou accepte la requête de connexion depuis n’importe quel appareil ou navigateur utilisé auparavant." @@ -638,6 +650,9 @@ msgstr "Renommer le fichier" msgid "Rename folder" msgstr "Renommer le dossier" +msgid "Rename shared folder" +msgstr "" + msgid "Report" msgstr "Signaler" @@ -728,6 +743,9 @@ msgstr "Partagé" msgid "Shared Folder Name" msgstr "Nom du dossier partagé" +msgid "Shared folder name" +msgstr "Nom du dossier partagé" + msgid "Shared folders" msgstr "Dossiers partagés" @@ -737,9 +755,6 @@ msgstr "Partagé avec" msgid "Sharing files" msgstr "Partage des fichiers" -msgid "Sharing link" -msgstr "Lien de partage" - msgid "Sign Out" msgstr "Se déconnecter" @@ -813,7 +828,7 @@ msgid "The files are already in this folder" msgstr "Les fichiers sont déjà dans ce dossier" msgid "The link you typed in looks malformed. Please verify it." -msgstr "" +msgstr "Le lien que vous avez tapé semble malformé. Veuillez le vérifier." msgid "The username is too long" msgstr "Le nom d'utilisateur est trop long" @@ -840,7 +855,7 @@ msgid "There was an error getting the preview." msgstr "Une erreur s’est produite lors de la génération de l’aperçu." msgid "There was an error moving your data" -msgstr "" +msgstr "Une erreur s'est produite lors du transfert de vos données" msgid "There was an error restoring your data" msgstr "Une erreur s'est produite lors de la restauration de vos données" @@ -849,20 +864,17 @@ msgid "There was an error when setting username." msgstr "Une erreur s'est produite lors de la définition du nom d'utilisateur." msgid "This is the free product." -msgstr "Une erreur s'est produite lors du transfert de vos données" +msgstr "" msgid "This link is marlformed. Please verify that you copy/pasted it correctly." -msgstr "" +msgstr "Ce lien est marlformé. Veuillez vérifier que vous l'avez copié/collé correctement." msgid "This link is not valid any more." -msgstr "" +msgstr "Ce lien n'est plus valide." msgid "This username is already taken" msgstr "Ce nom d’utilisateur est déjà pris" -msgid "This username is public" -msgstr "Ce nom d’utilisateur est public" - msgid "This website uses cookies" msgstr "Ce site web utilise des cookies" @@ -908,9 +920,6 @@ msgstr "Utilisez une méthode de connexion différente" msgid "Use a saved browser" msgstr "Utiliser un navigateur enregistré" -msgid "Use an existing shared folder" -msgstr "Utiliser un dossier partagé existant" - msgid "User {0} is both a reader and writer" msgstr "L'utilisateur {0} est dans les auteurs et lecteurs" @@ -936,7 +945,7 @@ msgid "Verification code sent!" msgstr "Code de vérification envoyé !" msgid "Verifying the link..." -msgstr "" +msgstr "Vérification du lien…" msgid "View folder" msgstr "Voir le dossier" @@ -999,10 +1008,10 @@ msgid "Your plan" msgstr "" msgid "Your recovery key can be used to restore your account in place of your backup secret phrase." -msgstr "Votre clé de récupération peut être utilisée pour restaurer votre compte à la place de votre phrase de sauvegarde secrète." +msgstr "" -msgid "edit rights" -msgstr "droits de modification" +msgid "can-edit" +msgstr "peut-modifier" msgid "me" msgstr "moi" @@ -1022,14 +1031,11 @@ msgstr "" msgid "per year" msgstr "" -msgid "read rights" -msgstr "droits de lecture" - msgid "unknown" msgstr "inconnu" -msgid "with" -msgstr "avec" +msgid "view-only" +msgstr "affichage seul" msgid "{0, plural, one {Downloading {1} file} other {Downloading {2} files}}" msgstr "{0, plural, one {Téléchargement de {1} fichier} other {Téléchargement de {2} fichiers}}" diff --git a/packages/files-ui/src/locales/no/messages.po b/packages/files-ui/src/locales/no/messages.po index 181aa2fac7..421ffb8d44 100644 --- a/packages/files-ui/src/locales/no/messages.po +++ b/packages/files-ui/src/locales/no/messages.po @@ -28,6 +28,9 @@ msgstr "" msgid "Account" msgstr "Konto" +msgid "Active links" +msgstr "" + msgid "Add Card" msgstr "" @@ -55,12 +58,18 @@ msgstr "" msgid "Adding you to the shared folder..." msgstr "" +msgid "Allow lookup by sharing key, wallet address or username" +msgstr "" + msgid "Amount" msgstr "" msgid "An error occurred:" msgstr "" +msgid "Anyone with the link can:" +msgstr "" + msgid "Approve" msgstr "Godkjenn" @@ -163,18 +172,12 @@ msgstr "Fortsett med Web3-lommebok" msgid "Copied!" msgstr "Kopiert!" -msgid "Copy CID" -msgstr "Kopier CID" - msgid "Copy file" msgstr "" msgid "Copy info" msgstr "" -msgid "Copy link" -msgstr "" - msgid "Copy over" msgstr "" @@ -187,22 +190,22 @@ msgstr "" msgid "Create" msgstr "Opprett" -msgid "Create Folder" -msgstr "" - msgid "Create Shared Folder" msgstr "" msgid "Create a Shared Folder" msgstr "" -msgid "Create a new shared folder" +msgid "Create a sharing link" msgstr "" -msgid "Create folder" -msgstr "Opprett mappe" +msgid "Create folder & Copy over" +msgstr "" + +msgid "Create folder & Move over" +msgstr "" -msgid "Create new link" +msgid "Create link" msgstr "" msgid "Create your public username in <0>Settings!" @@ -442,15 +445,15 @@ msgstr "" msgid "Loading your shared folders…" msgstr "" -msgid "Loading..." -msgstr "" - msgid "Looks like you’re signing in from a new browser. Please choose one of the following to continue:" msgstr "" msgid "Manage Access" msgstr "" +msgid "Manage Shared Folder" +msgstr "" + msgid "Method" msgstr "" @@ -478,6 +481,9 @@ msgstr "Navnet er for langt" msgid "New folder" msgstr "Ny mappe" +msgid "New shared folder name" +msgstr "" + msgid "Next" msgstr "Neste" @@ -535,6 +541,12 @@ msgstr "" msgid "Operating system:" msgstr "Operativsystem:" +msgid "Or Create a new shared folder" +msgstr "" + +msgid "Or Use an existing shared folder" +msgstr "" + msgid "Or confirm by signing into your Files on any browser you’ve used before." msgstr "" @@ -634,6 +646,9 @@ msgstr "" msgid "Rename folder" msgstr "" +msgid "Rename shared folder" +msgstr "" + msgid "Report" msgstr "" @@ -724,6 +739,9 @@ msgstr "Delt" msgid "Shared Folder Name" msgstr "" +msgid "Shared folder name" +msgstr "" + msgid "Shared folders" msgstr "" @@ -733,9 +751,6 @@ msgstr "" msgid "Sharing files" msgstr "" -msgid "Sharing link" -msgstr "" - msgid "Sign Out" msgstr "Logg ut" @@ -856,9 +871,6 @@ msgstr "" msgid "This username is already taken" msgstr "" -msgid "This username is public" -msgstr "Dette brukernavnet er offentlig" - msgid "This website uses cookies" msgstr "" @@ -904,9 +916,6 @@ msgstr "Bruk en annen innloggingsmetode" msgid "Use a saved browser" msgstr "" -msgid "Use an existing shared folder" -msgstr "" - msgid "User {0} is both a reader and writer" msgstr "" @@ -997,7 +1006,7 @@ msgstr "" msgid "Your recovery key can be used to restore your account in place of your backup secret phrase." msgstr "" -msgid "edit rights" +msgid "can-edit" msgstr "" msgid "me" @@ -1018,13 +1027,10 @@ msgstr "" msgid "per year" msgstr "" -msgid "read rights" -msgstr "" - msgid "unknown" msgstr "" -msgid "with" +msgid "view-only" msgstr "" msgid "{0, plural, one {Downloading {1} file} other {Downloading {2} files}}" diff --git a/packages/gaming-ui/package.json b/packages/gaming-ui/package.json index bc30f7d52c..22f8021c47 100644 --- a/packages/gaming-ui/package.json +++ b/packages/gaming-ui/package.json @@ -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.19", + "@chainsafe/files-api-client": "^1.18.20", "@chainsafe/web3-context": "1.1.4", "@lingui/core": "^3.7.2", "@lingui/react": "^3.7.2", @@ -56,7 +56,7 @@ "@types/yup": "^0.29.9", "@types/zxcvbn": "^4.4.0", "babel-plugin-macros": "^2.8.0", - "cypress": "^8.6", + "cypress": "^9.0", "cypress-file-upload": "^5.0.8", "cypress-pipe": "^2.0.0" }, diff --git a/packages/gaming-ui/src/Components/GamingRoutes.tsx b/packages/gaming-ui/src/Components/GamingRoutes.tsx index 22d434014a..c8fb48f479 100644 --- a/packages/gaming-ui/src/Components/GamingRoutes.tsx +++ b/packages/gaming-ui/src/Components/GamingRoutes.tsx @@ -31,12 +31,6 @@ const GamingRoutes = () => { component={DashboardPage} redirectPath={ROUTE_LINKS.Landing} /> - { }).finally (() => setIsSubmitEmailLoading(false)) }, [gamingApiClient]) - const onSubmitNonce = useCallback((values: {nonce: string}) => { + const onSubmitNonce = useCallback(({ nonce }: {nonce: string}) => { if (!email) return + setIsSubmitNonceLoading(true) setError(undefined) + gamingApiClient.postIdentityEmailToken({ - email: email, - nonce: values.nonce + email, + nonce: nonce.trim() }).then(async (data) => { await login("email", { token: data || "", email }) }).catch ((e) => { diff --git a/packages/storage-ui/package.json b/packages/storage-ui/package.json index a912064d6b..e7ae0c3fba 100644 --- a/packages/storage-ui/package.json +++ b/packages/storage-ui/package.json @@ -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.19", + "@chainsafe/files-api-client": "^1.18.20", "@chainsafe/web3-context": "1.1.4", "@lingui/core": "^3.7.2", "@lingui/react": "^3.7.2", @@ -64,7 +64,7 @@ "@types/yup": "^0.29.9", "@types/zxcvbn": "^4.4.0", "babel-plugin-macros": "^2.8.0", - "cypress": "^8.6", + "cypress": "^9.0", "cypress-file-upload": "^5.0.8", "cypress-pipe": "^2.0.0" }, diff --git a/packages/storage-ui/src/Components/Modules/LoginModule.tsx b/packages/storage-ui/src/Components/Modules/LoginModule.tsx index f05306d874..f6cbe3da5d 100644 --- a/packages/storage-ui/src/Components/Modules/LoginModule.tsx +++ b/packages/storage-ui/src/Components/Modules/LoginModule.tsx @@ -32,7 +32,7 @@ const useStyles = makeStyles( alignItems: "center", borderRadius: 6, [breakpoints.up("md")]:{ - minHeight: "64vh", + minHeight: "72vh", justifyContent: "space-between", width: 440 }, diff --git a/packages/storage-ui/src/Components/Modules/LoginModule/PasswordlessEmail.tsx b/packages/storage-ui/src/Components/Modules/LoginModule/PasswordlessEmail.tsx index 77c3d0cb51..44da20a907 100644 --- a/packages/storage-ui/src/Components/Modules/LoginModule/PasswordlessEmail.tsx +++ b/packages/storage-ui/src/Components/Modules/LoginModule/PasswordlessEmail.tsx @@ -93,13 +93,15 @@ const PasswordlessEmail = ({ resetLogin, email }: IPasswordlessEmail) => { const [hasEmailResent, setHasEmailResent] = useState(false) const [error, setError] = useState() - const onSubmitNonce = useCallback((values) => { + const onSubmitNonce = useCallback(({ nonce }: {nonce: string}) => { if (!email) return + setIsSubmitNonceLoading(true) setError(undefined) + storageApiClient.postIdentityEmailToken({ - email: email, - nonce: values.nonce + email, + nonce: nonce.trim() }).then(async (data) => { await login("email", { token: data || "", email }) }).catch ((e) => { diff --git a/yarn.lock b/yarn.lock index 41eb650088..ce8b61ac3f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1925,10 +1925,10 @@ resolved "https://registry.yarnpkg.com/@chainsafe/browser-storage-hooks/-/browser-storage-hooks-1.0.1.tgz#26d32cde1999914db755a631e2643823c54959f7" integrity sha512-Q4b5gQAZnsRXKeADspd5isqfwwhhXjDk70y++YadufA6EZ3tf340oW0OVszp74KaGEw+CAYFGQR4X7bzpZ3x9Q== -"@chainsafe/files-api-client@^1.18.19": - version "1.18.19" - resolved "https://registry.yarnpkg.com/@chainsafe/files-api-client/-/files-api-client-1.18.19.tgz#2093d508d55b71abb3ec9cfb9c219fa62a4494c4" - integrity sha512-fFIGJg3XcS3hrNq4+UxjOEYYYiOtrYLTKxn0c/jXrlDFxA+Zjsn6G6d5O+KHJVfqoinRIyrRrMPXBHpBlFHpZg== +"@chainsafe/files-api-client@^1.18.20": + version "1.18.20" + resolved "https://registry.yarnpkg.com/@chainsafe/files-api-client/-/files-api-client-1.18.20.tgz#5b184946acfa4026b21c95faa2f0c1c9d9999481" + integrity sha512-OQN4V2bUe0981RsBirjNA2YrkaMzAV/7oWSzBwN27ejhAbM6fRbxDEYTZfLRVuick91/JfM2/TbKc3vHYQRDdg== dependencies: "@redocly/openapi-cli" "^1.0.0-beta.58" "@redocly/openapi-core" "^1.0.0-beta.58" @@ -2017,10 +2017,10 @@ react-qr-reader "^2.2.1" rxjs "^6.6.3" -"@cypress/request@^2.88.6": - version "2.88.6" - resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.6.tgz#a970dd675befc6bdf8a8921576c01f51cc5798e9" - integrity sha512-z0UxBE/+qaESAHY9p9sM2h8Y4XqtsbDCt0/DPOrqA/RZgKi4PkxdpXyK4wCCnSk1xHqWHZZAE+gV6aDAR6+caQ== +"@cypress/request@^2.88.7": + version "2.88.7" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.7.tgz#386d960ab845a96953723348088525d5a75aaac4" + integrity sha512-FTULIP2rnDJvZDT9t6B4nSfYR40ue19tVmv3wUcY05R9/FPCoMl1nAPJkzWzBCo7ltVn5ThQTbxiMoGBN7k0ig== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -9991,9 +9991,9 @@ color-name@^1.0.0, color-name@~1.1.4: integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== color-string@^1.5.2: - version "1.5.3" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.5.3.tgz#c9bbc5f01b58b5492f3d6857459cb6590ce204cc" - integrity sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw== + version "1.6.0" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.6.0.tgz#c3915f61fe267672cb7e1e064c9d692219f6c312" + integrity sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" @@ -10787,12 +10787,12 @@ cypress-pipe@^2.0.0: resolved "https://registry.yarnpkg.com/cypress-pipe/-/cypress-pipe-2.0.0.tgz#577df7a70a8603d89a96dfe4092a605962181af8" integrity sha512-KW9s+bz4tFLucH3rBGfjW+Q12n7S4QpUSSyxiGrgPOfoHlbYWzAGB3H26MO0VTojqf9NVvfd5Kt0MH5XMgbfyg== -cypress@^8.6: - version "8.6.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-8.6.0.tgz#8d02fa58878b37cfc45bbfce393aa974fa8a8e22" - integrity sha512-F7qEK/6Go5FsqTueR+0wEw2vOVKNgk5847Mys8vsWkzPoEKdxs+7N9Y1dit+zhaZCLtMPyrMwjfA53ZFy+lSww== +cypress@^9.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-9.0.0.tgz#8c496f7f350e611604cc2f77b663fb81d0c235d2" + integrity sha512-/93SWBZTw7BjFZ+I9S8SqkFYZx7VhedDjTtRBmXO0VzTeDbmxgK/snMJm/VFjrqk/caWbI+XY4Qr80myDMQvYg== dependencies: - "@cypress/request" "^2.88.6" + "@cypress/request" "^2.88.7" "@cypress/xvfb" "^1.2.4" "@types/node" "^14.14.31" "@types/sinonjs__fake-timers" "^6.0.2" @@ -10827,7 +10827,6 @@ cypress@^8.6: ospath "^1.2.2" pretty-bytes "^5.6.0" proxy-from-env "1.0.0" - ramda "~0.27.1" request-progress "^3.0.0" supports-color "^8.1.1" tmp "~0.2.1" @@ -11213,9 +11212,9 @@ dns-over-http-resolver@^1.0.0: receptacle "^1.3.2" dns-packet@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a" - integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg== + version "1.3.4" + resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.4.tgz#e3455065824a2507ba886c55a89963bb107dec6f" + integrity sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA== dependencies: ip "^1.1.0" safe-buffer "^5.0.1" @@ -14404,9 +14403,9 @@ home-or-tmp@^2.0.0: os-tmpdir "^1.0.1" hosted-git-info@^2.1.4: - version "2.8.8" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" - integrity sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg== + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== hotkeys-js@3.8.1: version "3.8.1" @@ -17410,9 +17409,9 @@ merge-class-names@^1.1.1: integrity sha512-k0Qaj36VBpKgdc8c188LEZvo6v/zzry/FUufwopWbMSp6/knfVFU/KIB55/hJjeIpg18IH2WskXJCRnM/1BrdQ== merge-deep@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/merge-deep/-/merge-deep-3.0.2.tgz#f39fa100a4f1bd34ff29f7d2bf4508fbb8d83ad2" - integrity sha512-T7qC8kg4Zoti1cFd8Cr0M+qaZfOwjlPDEdZIIPPB2JZctjaPM4fX+i7HOId69tAti2fvO6X5ldfYUONDODsrkA== + version "3.0.3" + resolved "https://registry.yarnpkg.com/merge-deep/-/merge-deep-3.0.3.tgz#1a2b2ae926da8b2ae93a0ac15d90cd1922766003" + integrity sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA== dependencies: arr-union "^3.1.0" clone-deep "^0.2.4" @@ -18961,9 +18960,9 @@ path-key@^3.0.0, path-key@^3.1.0: integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-to-regexp@0.1.7: version "0.1.7" @@ -20411,7 +20410,7 @@ ramda@^0.21.0: resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.21.0.tgz#a001abedb3ff61077d4ff1d577d44de77e8d0a35" integrity sha1-oAGr7bP/YQd9T/HVd9RN536NCjU= -ramda@^0.27.1, ramda@~0.27.1: +ramda@^0.27.1: version "0.27.1" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.27.1.tgz#66fc2df3ef873874ffc2da6aa8984658abacf5c9" integrity sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw== @@ -22800,9 +22799,9 @@ sshpk@^1.7.0: tweetnacl "~0.14.0" ssri@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" - integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== + version "6.0.2" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.2.tgz#157939134f20464e7301ddba3e90ffa8f7728ac5" + integrity sha512-cepbSq/neFK7xB6A50KHN0xHDotYzq58wWCa5LeWqnPrHG8GzfEjO/4O8kpmcGW+oaxkvhEJCWgbgNk4/ZV93Q== dependencies: figgy-pudding "^3.5.1" @@ -23559,9 +23558,9 @@ tmp@~0.2.1: rimraf "^3.0.0" tmpl@1.0.x: - version "1.0.4" - resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" - integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE= + version "1.0.5" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" + integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== to-arraybuffer@^1.0.0: version "1.0.1" @@ -24107,9 +24106,9 @@ url-loader@2.3.0, url-loader@^2.0.1: schema-utils "^2.5.0" url-parse@^1.4.3: - version "1.4.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== + version "1.5.3" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.3.tgz#71c1303d38fb6639ade183c2992c8cc0686df862" + integrity sha512-IIORyIQD9rvj0A4CLWsHkBBJuNqWpFQe224b6j9t/ABmquIS0qDU2pY6kl6AuOrL5OkCXHMCFNe1jBcuAggjvQ== dependencies: querystringify "^2.1.1" requires-port "^1.0.0"