diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 95be718014..e387e475d5 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -11,4 +11,4 @@ Submission checklist: #### Theme - [ ] Components / elements inspected in light mode -- [ ] Components / elements inspected in dark mode \ No newline at end of file +- [ ] Components / elements inspected in dark mode diff --git a/packages/common-components/src/Breadcrumb/Breadcrumb.tsx b/packages/common-components/src/Breadcrumb/Breadcrumb.tsx index 4a4eeb4ead..5bcf51c909 100644 --- a/packages/common-components/src/Breadcrumb/Breadcrumb.tsx +++ b/packages/common-components/src/Breadcrumb/Breadcrumb.tsx @@ -13,6 +13,7 @@ export type Crumb = { export type BreadcrumbProps = { crumbs?: Crumb[] homeOnClick?: () => void + hideHome?: boolean className?: string showDropDown?: boolean } @@ -60,7 +61,7 @@ const useStyles = makeStyles( "&.clickable": { cursor: "pointer" }, - maxWidth: 100, + maxWidth: 120, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis", @@ -94,6 +95,7 @@ const useStyles = makeStyles( const Breadcrumb: React.FC = ({ crumbs = [], homeOnClick, + hideHome, className, showDropDown }: BreadcrumbProps) => { @@ -102,7 +104,6 @@ const Breadcrumb: React.FC = ({ const generateFullCrumbs = (crumbs: Crumb[]) => { return crumbs.map((item: Crumb, index: number) => ( -
(item.onClick ? item.onClick() : null)} @@ -112,6 +113,7 @@ const Breadcrumb: React.FC = ({ {item.text}
+ {index < (crumbs.length - 1) &&
} )) } @@ -120,7 +122,7 @@ const Breadcrumb: React.FC = ({ return ( = ({ if (crumbs.length < 3 || !showDropDown) { return generateFullCrumbs(crumbs) } else { - const dropdownCrumbs = crumbs.slice(0, length - 1) + const dropdownCrumbs = crumbs.slice(0, crumbs.length - 1) const lastCrumb = crumbs[crumbs.length - 1] return ( <> -
{generateDropdownCrumb(dropdownCrumbs)}
@@ -169,10 +170,14 @@ const Breadcrumb: React.FC = ({ return (
- (homeOnClick ? homeOnClick() : null)} - /> + {!hideHome && <> + (homeOnClick ? homeOnClick() : null)} + /> +
+ + } {generateCrumbs()}
) diff --git a/packages/common-components/src/Icons/icons/Bell.icon.tsx b/packages/common-components/src/Icons/icons/Bell.icon.tsx new file mode 100644 index 0000000000..908ec49afa --- /dev/null +++ b/packages/common-components/src/Icons/icons/Bell.icon.tsx @@ -0,0 +1,7 @@ +import * as React from "react" +import createSvgIcon from "../createSvgIcon" +import { ReactComponent as BellSvg } from "../svgs/bell.svg" + +export { BellSvg } + +export default createSvgIcon() diff --git a/packages/common-components/src/Icons/icons/CreditCard.icon.tsx b/packages/common-components/src/Icons/icons/CreditCard.icon.tsx new file mode 100644 index 0000000000..41b923f2e4 --- /dev/null +++ b/packages/common-components/src/Icons/icons/CreditCard.icon.tsx @@ -0,0 +1,7 @@ +import * as React from "react" +import createSvgIcon from "../createSvgIcon" +import { ReactComponent as CreditCardSvg } from "../svgs/credit-card.svg" + +export { CreditCardSvg } + +export default createSvgIcon() diff --git a/packages/common-components/src/Icons/icons/External.icon.tsx b/packages/common-components/src/Icons/icons/External.icon.tsx new file mode 100644 index 0000000000..6e14881102 --- /dev/null +++ b/packages/common-components/src/Icons/icons/External.icon.tsx @@ -0,0 +1,7 @@ +import * as React from "react" +import createSvgIcon from "../createSvgIcon" +import { ReactComponent as ExternalSvg } from "../svgs/external.svg" + +export { ExternalSvg } + +export default createSvgIcon() diff --git a/packages/common-components/src/Icons/icons/SubscriptionPlan.icon.tsx b/packages/common-components/src/Icons/icons/SubscriptionPlan.icon.tsx new file mode 100644 index 0000000000..6db4b2ae5d --- /dev/null +++ b/packages/common-components/src/Icons/icons/SubscriptionPlan.icon.tsx @@ -0,0 +1,7 @@ +import * as React from "react" +import createSvgIcon from "../createSvgIcon" +import { ReactComponent as SubscriptionSvg } from "../svgs/subscription-plan.svg" + +export { SubscriptionSvg } + +export default createSvgIcon() diff --git a/packages/common-components/src/Icons/index.ts b/packages/common-components/src/Icons/index.ts index e6286214f7..ee5a1f9dc8 100644 --- a/packages/common-components/src/Icons/index.ts +++ b/packages/common-components/src/Icons/index.ts @@ -5,6 +5,7 @@ export { default as AppleLogoIcon, AppleLogoSvg } from "./icons/AppleLogo.icon" export { default as AmexCardIcon, AmexCardSvg } from "./icons/AmexCard.icon" export { default as ArrowLeftIcon, ArrowLeftSvg } from "./icons/ArrowLeft.icon" export { default as ArrowRightIcon, ArrowRightSvg } from "./icons/ArrowRight.icon" +export { default as BellIcon, BellSvg } from "./icons/Bell.icon" export { default as BulbIcon, BulbSvg } from "./icons/Bulb.icon" export { default as CaretDownIcon, CaretDownSvg } from "./icons/CaretDown.icon" export { default as CaretUpIcon, CaretUpSvg } from "./icons/CaretUp.icon" @@ -14,6 +15,7 @@ export { default as CheckCircleIcon, CheckCircleSvg } from "./icons/CheckCircle. export { default as CloseIcon, CloseSvg } from "./icons/Close.icon" export { default as CloseCircleIcon, CloseCirceSvg } from "./icons/CloseCircle.icon" export { default as CopyIcon, CopySvg } from "./icons/Copy.icon" +export { default as CreditCardIcon, CreditCardSvg } from "./icons/CreditCard.icon" export { default as CrossIcon, CrossSvg } from "./icons/Cross.icon" export { default as CrossOutlinedIcon, CrossOutlinedSvg } from "./icons/CrossOutlined.icon" export { default as DatabaseIcon, DatabaseSvg } from "./icons/Database.icon" @@ -29,6 +31,7 @@ export { default as EthereumLogoIcon, EthereumLogoSvg } from "./icons/EthereumLo export { default as ExclamationCircleIcon, ExclamationCircleSvg } from "./icons/ExclamationCircle.icon" export { default as ExclamationCircleInverseIcon, ExclamationCircleInverseSvg } from "./icons/ExclamationCircleInverse.icon" export { default as ExportIcon, ExportSvg } from "./icons/Export.icon" +export { default as ExternalIcon, ExternalSvg } from "./icons/External.icon" export { default as EyeClosedIcon, EyeClosedSvg } from "./icons/EyeClosed.icon" export { default as EyeIcon, EyeSvg } from "./icons/Eye.icon" export { default as EyeOpenIcon, EyeOpenSvg } from "./icons/EyeOpen.icon" @@ -66,6 +69,7 @@ export { default as ShareAltIcon, ShareAltSvg } from "./icons/ShareAlt.icon" export { default as StarIcon, StarSvg } from "./icons/Star.icon" export { default as SortIcon, SortSvg } from "./icons/Sort.icon" export { default as SunIcon, SunSvg } from "./icons/Sun.icon" +export { default as SubscriptionPlanIcon, SubscriptionSvg } from "./icons/SubscriptionPlan.icon" export { default as TableIcon, TableSvg } from "./icons/Table.icon" export { default as UpdateIcon, UpdateSvg } from "./icons/Update.icon" export { default as UploadIcon, UploadSvg } from "./icons/Upload.icon" diff --git a/packages/common-components/src/Icons/svgs/bell.svg b/packages/common-components/src/Icons/svgs/bell.svg new file mode 100644 index 0000000000..c5bf627840 --- /dev/null +++ b/packages/common-components/src/Icons/svgs/bell.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/common-components/src/Icons/svgs/credit-card.svg b/packages/common-components/src/Icons/svgs/credit-card.svg new file mode 100644 index 0000000000..d163b081c7 --- /dev/null +++ b/packages/common-components/src/Icons/svgs/credit-card.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/common-components/src/Icons/svgs/document.svg b/packages/common-components/src/Icons/svgs/document.svg index b805583c15..5edd45259b 100644 --- a/packages/common-components/src/Icons/svgs/document.svg +++ b/packages/common-components/src/Icons/svgs/document.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/packages/common-components/src/Icons/svgs/external.svg b/packages/common-components/src/Icons/svgs/external.svg new file mode 100644 index 0000000000..493a90748b --- /dev/null +++ b/packages/common-components/src/Icons/svgs/external.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/packages/common-components/src/Icons/svgs/profile.svg b/packages/common-components/src/Icons/svgs/profile.svg index 30175f0200..996d119f4d 100644 --- a/packages/common-components/src/Icons/svgs/profile.svg +++ b/packages/common-components/src/Icons/svgs/profile.svg @@ -1 +1 @@ - \ No newline at end of file + diff --git a/packages/common-components/src/Icons/svgs/subscription-plan.svg b/packages/common-components/src/Icons/svgs/subscription-plan.svg new file mode 100644 index 0000000000..b08a23e621 --- /dev/null +++ b/packages/common-components/src/Icons/svgs/subscription-plan.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/common-components/src/MenuDropdown/MenuDropdown.tsx b/packages/common-components/src/MenuDropdown/MenuDropdown.tsx index e0e714d7e8..b5ac4c6e4f 100644 --- a/packages/common-components/src/MenuDropdown/MenuDropdown.tsx +++ b/packages/common-components/src/MenuDropdown/MenuDropdown.tsx @@ -209,7 +209,10 @@ interface IMenuDropdownProps { title?: string titleText?: string } + hideIndicator?: boolean testId?: string + children?: React.ReactNode + dropdown?: React.ReactNode } const MenuDropdown = ({ @@ -221,7 +224,10 @@ const MenuDropdown = ({ animation = "flip", title, classNames, - testId + testId, + children, + hideIndicator, + dropdown }: IMenuDropdownProps) => { const Icon = indicator const classes = useStyles() @@ -273,11 +279,13 @@ const MenuDropdown = ({ {title} )} - + } - {menuItems.map((item: IMenuItem, index: number) => ( + {menuItems && menuItems.map((item: IMenuItem, index: number) => (
))} + {dropdown}
) diff --git a/packages/common-components/src/RadioInput/RadioInput.tsx b/packages/common-components/src/RadioInput/RadioInput.tsx index 69a00e5c39..e413c9c6d7 100644 --- a/packages/common-components/src/RadioInput/RadioInput.tsx +++ b/packages/common-components/src/RadioInput/RadioInput.tsx @@ -13,7 +13,6 @@ const useStyles = makeStyles( cursor: "pointer", paddingLeft: constants.generalUnit * 3, paddingRight: constants.generalUnit * 3, - margin: `${constants.generalUnit}px 0`, ...overrides?.RadioInput?.radioContainer }, radioInput: { @@ -27,7 +26,7 @@ const useStyles = makeStyles( width: constants.generalUnit * 2, height: constants.generalUnit * 2, left: 0, - top: 0, + top: "auto", borderRadius: "50%", transition: `all ${animation.transform}ms ease`, ...overrides?.RadioInput?.radio?.root, @@ -83,6 +82,7 @@ const useStyles = makeStyles( export interface IRadioInputProps extends React.HTMLProps { className?: string + labelClassName?: string value: string label?: string name?: string @@ -94,6 +94,7 @@ export interface IRadioInputProps extends React.HTMLProps { const RadioInput: React.FC = ({ className, + labelClassName, value, name, label, @@ -130,7 +131,7 @@ const RadioInput: React.FC = ({ })} /> {label && ( - {label} + {label} )} {error &&
{error}
} diff --git a/packages/common-components/src/Tabs/Tabs.tsx b/packages/common-components/src/Tabs/Tabs.tsx index f70c2e0766..16b3abb065 100644 --- a/packages/common-components/src/Tabs/Tabs.tsx +++ b/packages/common-components/src/Tabs/Tabs.tsx @@ -48,15 +48,15 @@ interface TabInjectedClasses { tabBar?: string } -export interface ITabsProps { +export interface ITabsProps { className?: string - children: React.ReactElement | React.ReactElement[] - activeKey: string - onTabSelect: (key: string) => void + children: React.ReactElement> | React.ReactElement>[] + activeKey?: TabKey + onTabSelect: (key: TabKey) => void injectedClass?: TabInjectedClasses } -const Tabs: React.FC = ({ className, children, activeKey, injectedClass, onTabSelect }: ITabsProps) => { +const Tabs = ({ className, children, activeKey, injectedClass, onTabSelect }: ITabsProps) => { const classes = useStyles() const selectedChild = Array.isArray(children) ? children.find((child) => activeKey === child.props.tabKey) diff --git a/packages/common-components/src/ToggleSwitch/FormikToggleSwitch.tsx b/packages/common-components/src/ToggleSwitch/FormikToggleSwitch.tsx new file mode 100644 index 0000000000..21fa4c4e5f --- /dev/null +++ b/packages/common-components/src/ToggleSwitch/FormikToggleSwitch.tsx @@ -0,0 +1,28 @@ +import { useField } from "formik" +import React from "react" +import { IToggleSwitch, ToggleSwitch } from "." + + +interface IFormikToggleSwitch extends IToggleSwitch { + name: string +} + +const FormikToggleSwitch = ({ injectedClasses, disabled, name, left, right, size }: IFormikToggleSwitch) => { + const [field, meta, helpers] = useField(name) + const handleChange = (value: any) => { + helpers.setValue(value) + } + + return +} + +export default FormikToggleSwitch diff --git a/packages/common-components/src/ToggleSwitch/ToggleSwitch.tsx b/packages/common-components/src/ToggleSwitch/ToggleSwitch.tsx new file mode 100644 index 0000000000..147272f1d1 --- /dev/null +++ b/packages/common-components/src/ToggleSwitch/ToggleSwitch.tsx @@ -0,0 +1,174 @@ +import { createStyles, ITheme, makeStyles } from "@chainsafe/common-theme" +import clsx from "clsx" +import React, { ReactNode, useCallback, useMemo, useState } from "react" +import { Typography } from "../Typography" + +interface IStyleProps { + size: number +} + +const SIZES = { + small: 14, + medium: 16, + large: 20 +} + +const PADDING = 2 + +const useStyles = makeStyles( + ({ animation, breakpoints, constants, overrides, palette }: ITheme) => + createStyles({ + root : { + padding: `${constants.generalUnit}px 0`, + display: "flex", + flexDirection: "row", + justifyContent: "flex-start", + alignItems: "center", + cursor: "pointer", + "&.disabled": { + cursor: "initial" + }, + [breakpoints.down("md")]: { + marginBottom: 0 + }, + ...overrides?.Tabs?.root + }, + label: { + marginRight: constants.generalUnit, + ...overrides?.ToggleSwitch?.label + }, + background: ({ size }: IStyleProps) => ({ + boxSizing: "border-box", + display: "block", + position: "relative", + backgroundColor: palette.additional.gray[6], + borderRadius: size, + padding: PADDING, + height: size, + width: `calc(${size * 2}px - ${PADDING * 2}px)`, + transitionDuration: `${animation.transform}ms`, + ...overrides?.ToggleSwitch?.background + }), + dot: ({ size }: IStyleProps) => ({ + display: "block", + position: "absolute", + borderRadius: "100%", + height: `calc(${size}px - ${PADDING * 2}px)`, + width: `calc(${size}px - ${PADDING * 2}px)`, + backgroundColor: palette.additional.gray[1], + transitionDuration: `${animation.transform}ms`, + top: "50%", + transform: "translateY(-50%)", + "&.left": { + left: PADDING + }, + "&.right": { + left: "50%" + }, + "&.error": { + backgroundColor: palette.error.main + }, + ...overrides?.ToggleSwitch?.dot + }) + }) +) + +interface ToggleSwitchInjectedClasses { + root?: string + label?: string + background?: string + dot?: string +} + +// Could allow for a slider to be made from this +interface IToggleOption { + value: any + label?: string | ReactNode | number +} + +interface IToggleSwitch { + left: IToggleOption + right: IToggleOption + onChange(value: any): void + injectedClasses?: ToggleSwitchInjectedClasses + size?: "large" | "medium" | "small" | number + error?: string + disabled?: boolean + value?: any + // name?: string +} + +const ToggleSwitch = ({ injectedClasses, disabled, left, right, onChange, value, size, error }: IToggleSwitch) => { + const resolvedSize = useMemo(() => { + switch (size) { + case "large": + return SIZES.large + case "medium": + return SIZES.medium + case "small": + return SIZES.small + case undefined: + return SIZES.medium + default: + return size + } + }, [size]) + const classes = useStyles({ size: resolvedSize }) + const [side, setSide] = useState<"left" | "right">(value && right.value === value ? "right" : "left") + + const onToggle = useCallback(() => { + if (disabled) return + if (side === "left") { + setSide("right") + onChange(right.value) + } else { + setSide("left") + onChange(left.value) + } + }, [left, right, side, onChange, disabled]) + + return
+ { + side === "left" && left.label && ( + + { left.label } + + ) + } + { + side === "right" && right.label && ( + + { right.label } + + ) + } +
+
+
+
+
+} + +export default ToggleSwitch +export { IToggleSwitch, IToggleOption, ToggleSwitchInjectedClasses } diff --git a/packages/common-components/src/ToggleSwitch/index.tsx b/packages/common-components/src/ToggleSwitch/index.tsx new file mode 100644 index 0000000000..0bd778f9e6 --- /dev/null +++ b/packages/common-components/src/ToggleSwitch/index.tsx @@ -0,0 +1,6 @@ +export { + default as ToggleSwitch, + IToggleOption, + IToggleSwitch, + ToggleSwitchInjectedClasses +} from "./ToggleSwitch" diff --git a/packages/common-components/src/index.ts b/packages/common-components/src/index.ts index a6d8a17044..561e07dde4 100644 --- a/packages/common-components/src/index.ts +++ b/packages/common-components/src/index.ts @@ -34,6 +34,7 @@ export * from "./Tabs" export * from "./TagsInput" export * from "./Toasts" export * from "./ToggleHiddenText" +export * from "./ToggleSwitch" export * from "./TextInput" export * from "./TreeView" export * from "./Typography" diff --git a/packages/common-components/src/stories/Breadcrumb.stories.tsx b/packages/common-components/src/stories/Breadcrumb.stories.tsx index 8a4f3bd32e..ba9be0c287 100644 --- a/packages/common-components/src/stories/Breadcrumb.stories.tsx +++ b/packages/common-components/src/stories/Breadcrumb.stories.tsx @@ -38,6 +38,7 @@ export const BreadcrumbStory = (): React.ReactNode => { homeOnClick={() => actionsData.homeClicked()} showDropDown={boolean("show dropdown", true)} crumbs={crumbs} + hideHome={boolean("hide home", false)} /> )} diff --git a/packages/common-components/src/stories/ToggleSwitch.stories.tsx b/packages/common-components/src/stories/ToggleSwitch.stories.tsx new file mode 100644 index 0000000000..fae5dd6ad4 --- /dev/null +++ b/packages/common-components/src/stories/ToggleSwitch.stories.tsx @@ -0,0 +1,41 @@ +import { action } from "@storybook/addon-actions" +import { boolean, select, withKnobs } from "@storybook/addon-knobs" +import React, { useState } from "react" +import { ToggleSwitch } from ".." +import { SizeOption } from "./types" + +export default { + title: "ToggleSwitch", + component: ToggleSwitch, + excludeStories: /.*Data$/, + decorators: [withKnobs] +} + +const sizeOptions: SizeOption[] = ["large", "medium", "small"] + +const actionsData = { + onChange: action("onChange") +} + +export const ToggleSwitchDemo = (): React.ReactNode => { + const [state, setState] = useState(false) + return ( + { + setState(value) + actionsData.onChange(value) + }} + disabled={boolean("Disabled", false)} + size={select("Size", sizeOptions, "medium")} + /> + ) +} diff --git a/packages/common-theme/src/Overrides/ToggleSwitch.ts b/packages/common-theme/src/Overrides/ToggleSwitch.ts new file mode 100644 index 0000000000..73c8f7a798 --- /dev/null +++ b/packages/common-theme/src/Overrides/ToggleSwitch.ts @@ -0,0 +1,6 @@ +export interface IToggleSwitchOverride { + root?: Record + label?: Record + background?: Record + dot?: Record +} diff --git a/packages/common-theme/src/Overrides/index.ts b/packages/common-theme/src/Overrides/index.ts index e56d61266b..68749a6e2d 100644 --- a/packages/common-theme/src/Overrides/index.ts +++ b/packages/common-theme/src/Overrides/index.ts @@ -25,6 +25,7 @@ import { IToastsOverride } from "./Toasts" import { ITypographyOverride } from "./Typography" import { ITagsInputOverride } from "./TagsInput" import { IToggleHiddenText } from "./ToggleHiddenText" +import { IToggleSwitchOverride } from "./ToggleSwitch" export interface IComponentOverrides { Avatar?: IAvatarOverride @@ -52,6 +53,7 @@ export interface IComponentOverrides { TextInput?: ITextInputOverride Toasts?: IToastsOverride ToggleHiddenText?: IToggleHiddenText + ToggleSwitch?: IToggleSwitchOverride Typography?: ITypographyOverride TagsInput?: ITagsInputOverride } diff --git a/packages/files-ui/cypress/fixtures/filesTestData.ts b/packages/files-ui/cypress/fixtures/filesTestData.ts index d84d9b98f0..1cd64dca7c 100644 --- a/packages/files-ui/cypress/fixtures/filesTestData.ts +++ b/packages/files-ui/cypress/fixtures/filesTestData.ts @@ -6,4 +6,4 @@ export const sharedFolderName = "Share" export const sharedFolderEditedName = "Edited" export const validEthAddress = "0x2eab9cfa0b5e8e82a73da026254d50567f52d3ce" export const validUsername = "filesUserB" -export const validShareKey = "0x03b53864fc991432dd2a729ee1f876e79d20788efcbe978a4a00db3f9fb24590db" \ No newline at end of file +export const validShareKey = "0x03b53864fc991432dd2a729ee1f876e79d20788efcbe978a4a00db3f9fb24590db" diff --git a/packages/files-ui/package.json b/packages/files-ui/package.json index c906a834cb..c0e4f9b470 100644 --- a/packages/files-ui/package.json +++ b/packages/files-ui/package.json @@ -6,12 +6,15 @@ "@babel/core": "^7.12.10", "@babel/runtime": "^7.0.0", "@chainsafe/browser-storage-hooks": "^1.0.1", - "@chainsafe/files-api-client": "^1.18.20", + "@chainsafe/files-api-client": "^1.18.22", "@chainsafe/web3-context": "1.1.4", + "@emeraldpay/hashicon-react": "^0.5.1", "@lingui/core": "^3.7.2", "@lingui/react": "^3.7.2", "@material-ui/core": "^4.12.3", "@sentry/react": "^5.28.0", + "@stripe/react-stripe-js": "^1.4.1", + "@stripe/stripe-js": "^1.18.0", "@tkey/default": "3.14.2", "@tkey/security-questions": "3.14.2", "@tkey/web-storage": "3.14.2", diff --git a/packages/files-ui/src/Components/Elements/CardInputs/index.tsx b/packages/files-ui/src/Components/Elements/CardInputs/index.tsx index 75191994cf..20836290b3 100644 --- a/packages/files-ui/src/Components/Elements/CardInputs/index.tsx +++ b/packages/files-ui/src/Components/Elements/CardInputs/index.tsx @@ -1,36 +1,13 @@ import React from "react" import { formatCardNumber, formatExpiry, getCardTypeByValue } from "./utils" -import { TextInput, Typography } from "@chainsafe/common-components" +import { Grid, TextInput, Typography } from "@chainsafe/common-components" import { makeStyles, ITheme, createStyles } from "@chainsafe/common-theme" -const useStyles = makeStyles((theme: ITheme) => +const useStyles = makeStyles(({ constants, palette }: ITheme) => createStyles({ - container: { - margin: `${theme.constants.generalUnit * 2}px 0` - }, - cardNumber: { - margin: 0, - width: "100%", - "& input": { - borderRadius: 0 - } - }, - cardExpiry: { - margin: 0, - width: "70%", - "& input": { - borderRadius: 0 - } - }, - cardCvc: { - margin: 0, - width: "30%", - "& input": { - borderRadius: 0 - } - }, error: { - color: theme.palette.error.main + color: palette.error.main, + paddingLeft: constants.generalUnit } }) ) @@ -60,43 +37,70 @@ const CardInputs = (props: ICardInputsProps) => { const cardType = getCardTypeByValue(cardNumber) return ( -
- - val && handleChangeCardNumber(formatCardNumber(val.toString())) - } - className={classes.cardNumber} - size="large" - placeholder="1234 1234 1234 1234" - RightIcon={cardType && cardType.type ? cardType.icon : undefined} - label="Card information" - /> - - val && handleChangeCardExpiry(formatExpiry(val.toString())) - } - className={classes.cardExpiry} - size="large" - placeholder="MM/YY" - /> - val && handleChangeCardCvc(val.toString())} - className={classes.cardCvc} - size="large" - placeholder="CVC" - /> - {error && ( - - {error} - - )} -
+ + + + handleChangeCardNumber(formatCardNumber(val?.toString() || "")) + } + size="large" + placeholder="1234 1234 1234 1234" + RightIcon={cardType && cardType.type ? cardType.icon : undefined} + label="Card number" + /> + + + + handleChangeCardExpiry(formatExpiry(val?.toString() || "")) + } + size="large" + placeholder="MM/YY" + label="Card expiry" + /> + + + handleChangeCardCvc(val?.toString() || "")} + size="large" + placeholder="CVC" + label="Card CVC" + /> + + + {error && ( + + {error} + + )} + + ) } diff --git a/packages/files-ui/src/Components/Elements/CardInputs/utils/formatter.ts b/packages/files-ui/src/Components/Elements/CardInputs/utils/formatter.ts index 38287e3b47..f6cc6e9c26 100644 --- a/packages/files-ui/src/Components/Elements/CardInputs/utils/formatter.ts +++ b/packages/files-ui/src/Components/Elements/CardInputs/utils/formatter.ts @@ -1,7 +1,8 @@ import * as cardTypes from "./cardTypes" export const formatCardNumber = (cardNumber: string) => { - const cardType = cardTypes.getCardTypeByValue(cardNumber) + const cardType = cardTypes.getCardTypeByValue(cardNumber) || + cardTypes.CARD_TYPES.find((cardType) => cardType.type === "visa") if (!cardType) return (cardNumber.match(/\d+/g) || []).join("") diff --git a/packages/files-ui/src/Components/Elements/CardInputs/utils/validator.ts b/packages/files-ui/src/Components/Elements/CardInputs/utils/validator.ts index ba2ba151d3..17b9ec1875 100644 --- a/packages/files-ui/src/Components/Elements/CardInputs/utils/validator.ts +++ b/packages/files-ui/src/Components/Elements/CardInputs/utils/validator.ts @@ -1,5 +1,4 @@ import * as cardTypes from "./cardTypes" -import { ICardType } from "./cardTypes" const MONTH_REGEX = /(0[1-9]|1[0-2])/ @@ -46,6 +45,7 @@ export const validateLuhn = (cardNumber: string) => { 0 ) } + export const getCardNumberError = (cardNumber: string): string | undefined => { if (!cardNumber) { return CardNumberErrors.EMPTY_CARD_NUMBER @@ -92,7 +92,8 @@ export const getExpiryDateError = (expiryDate: string): string | undefined => { return CardExpiryErrors.INVALID_EXPIRY_DATE } -export const getCVCError = (cvc: string, cardType: ICardType | undefined) => { +export const getCVCError = (cvc: string, cardNumber: string) => { + const cardType = cardTypes.getCardTypeByValue(cardNumber) if (!cvc) { return CardCvcErrors.EMPTY_CVC } diff --git a/packages/files-ui/src/Components/Elements/Notifications/NotificationList.tsx b/packages/files-ui/src/Components/Elements/Notifications/NotificationList.tsx new file mode 100644 index 0000000000..634a7a4377 --- /dev/null +++ b/packages/files-ui/src/Components/Elements/Notifications/NotificationList.tsx @@ -0,0 +1,82 @@ +import React from "react" +import { Typography, ScrollbarWrapper } from "@chainsafe/common-components" +import { createStyles, ITheme, makeStyles } from "@chainsafe/common-theme" +import dayjs from "dayjs" +import relativeTime from "dayjs/plugin/relativeTime" +import { Notification } from "./NotificationsDropdown" +dayjs.extend(relativeTime) + +const useStyles = makeStyles(({ palette, constants }: ITheme) => + createStyles({ + notificationBody: { + padding: `${constants.generalUnit}px ${constants.generalUnit * 1.5}px`, + display: "flex", + alignItems: "center", + cursor: "pointer", + backgroundColor: "initial", + "&:hover": { + backgroundColor: palette.additional["gray"][3] + }, + "svg": { + fill: palette.additional["gray"][9] + }, + borderBottom: `1px solid ${palette.additional["gray"][5]}`, + ":last-child": { + border: "none" + } + }, + notificationTitle: { + color: palette.additional["gray"][9], + paddingRight: constants.generalUnit * 1.5, + width: 180 + }, + scrollContent: { + minWidth: 300 + }, + notificationTime: { + color: palette.additional["blue"][6] + } + }) +) + +interface INotificationListProps { + notifications: Notification[] +} + +const NotificationList = ({ notifications }: INotificationListProps) => { + const classes = useStyles() + + return ( + +
+ {notifications.map((n, i) => ( +
+ + {n.title} + + + {dayjs(n.createdAt).fromNow()} + +
+ ))} +
+
+ )} + +export default NotificationList \ No newline at end of file diff --git a/packages/files-ui/src/Components/Elements/Notifications/NotificationsDropdown.tsx b/packages/files-ui/src/Components/Elements/Notifications/NotificationsDropdown.tsx new file mode 100644 index 0000000000..14051419c8 --- /dev/null +++ b/packages/files-ui/src/Components/Elements/Notifications/NotificationsDropdown.tsx @@ -0,0 +1,66 @@ +import React from "react" +import { Button, BellIcon, MenuDropdown } from "@chainsafe/common-components" +import { createStyles, ITheme, makeStyles } from "@chainsafe/common-theme" +import NotificationList from "./NotificationList" + +const useStyles = makeStyles(({ palette, constants }: ITheme) => + createStyles({ + notificationsButton: { + position: "relative", + "span": { + transition: "none" + } + }, + badge: { + position: "absolute", + background: palette.additional["volcano"][6], + color: palette.additional["gray"][1], + top: "-2px", + left: "13px", + borderRadius: constants.generalUnit, + padding: `${constants.generalUnit * 0.25}px ${constants.generalUnit * 0.5}px`, + fontSize: "11px", + lineHeight: "11px", + height: "0.9rem", + minWidth: "1rem" + }, + icon: { + transition: "none" + } + }) +) + +export interface Notification { + title: string + createdAt: number + onClick?: () => void +} + +interface INotificationsDropdownProps { + notifications: Notification[] +} + +const NotificationsDropdown = ({ notifications }: INotificationsDropdownProps) => { + const classes = useStyles() + + return ( + } + hideIndicator={true} + anchor="bottom-right" + > + + + ) +} + +export default NotificationsDropdown \ No newline at end of file diff --git a/packages/files-ui/src/Components/Elements/RestrictedModeBanner.tsx b/packages/files-ui/src/Components/Elements/RestrictedModeBanner.tsx new file mode 100644 index 0000000000..c1b6bbc39f --- /dev/null +++ b/packages/files-ui/src/Components/Elements/RestrictedModeBanner.tsx @@ -0,0 +1,49 @@ +import { Typography, Button, useHistory } from "@chainsafe/common-components" +import { createStyles, makeStyles, useThemeSwitcher } from "@chainsafe/common-theme" +import { Trans } from "@lingui/macro" +import React from "react" +import { CSFTheme } from "../../Themes/types" +import { ROUTE_LINKS } from "../FilesRoutes" + +const useStyles = makeStyles( + ({ breakpoints, constants, palette }: CSFTheme) => { + return createStyles({ + accountRestrictedNotification: { + position: "fixed", + bottom: 0, + backgroundColor: palette.additional["gray"][10], + color: palette.additional["gray"][1], + padding: `${constants.generalUnit * 2}px ${constants.generalUnit * 3}px`, + left: 0, + width: "100vw", + [breakpoints.up("md")]: { + left: `${constants.navWidth}px`, + width:`calc(100vw - ${constants.navWidth}px)`, + display: "flex", + justifyContent: "space-between", + alignItems: "center" + } + } + }) + } +) + +const RestrictedModeBanner = () => { + const classes = useStyles() + const { desktop } = useThemeSwitcher() + const { redirect } = useHistory() + + return ( +
+ + You've got a payment due. Until you've settled up, we've placed your account in restricted mode + + +
) +} + +export default RestrictedModeBanner \ No newline at end of file diff --git a/packages/files-ui/src/Components/FilesRoutes.tsx b/packages/files-ui/src/Components/FilesRoutes.tsx index 70ae37185a..80a55e6ce0 100644 --- a/packages/files-ui/src/Components/FilesRoutes.tsx +++ b/packages/files-ui/src/Components/FilesRoutes.tsx @@ -6,29 +6,33 @@ import { useFilesApi } from "../Contexts/FilesApiContext" import DrivePage from "./Pages/DrivePage" import SearchPage from "./Pages/SearchPage" import BinPage from "./Pages/BinPage" -import PurchasePlanPage from "./Pages/PurchasePlanPage" import { useThresholdKey } from "../Contexts/ThresholdKeyContext" import ShareFilesPage from "./Pages/SharedFilesPage" import SharedFoldersOverview from "./Modules/FileBrowsers/SharedFoldersOverview" +import BillingHistory from "./Pages/BillingHistory" import { NonceResponsePermission } from "@chainsafe/files-api-client" import LinkSharingLanding from "./Pages/LinkSharingLanding" export const SETTINGS_BASE = "/settings" export const LINK_SHARING_BASE = "/link-sharing" +const CHAINSAFE_LANDING = "https://chainsafe.io/" export const ROUTE_LINKS = { Landing: "/", PrivacyPolicy: "https://files.chainsafe.io/privacy-policy", Terms: "https://files.chainsafe.io/terms-of-service", - ChainSafe: "https://chainsafe.io/", + ChainSafe: CHAINSAFE_LANDING, + ProductPlans: `${CHAINSAFE_LANDING}`, Drive: (rawCurrentPath: string) => `/drive${rawCurrentPath}`, Bin: (rawBinPath: string) => `/bin${rawBinPath}`, Search: (rawSearchTerm: string) => `/search/${rawSearchTerm}`, ApplyCryptography: "https://medium.com/chainsafe-systems/major-improvement-to-chainsafe-files-ab489d3e52a2", Settings: `${SETTINGS_BASE}/:path`, SettingsDefault: `${SETTINGS_BASE}`, - PurchasePlan: "/purchase", + SettingsPath: (settingsPath: SettingsPath) => `${SETTINGS_BASE}/${settingsPath}`, + BillingHistory: "/billing-history", UserSurvey: "https://calendly.com/colinschwarz/chainsafe-files-chat", + Plans: "/plans", SharedFolders: "/shared-overview", SharedFolderBrowserRoot: "/shared", SharingLink: (permission: NonceResponsePermission, jwt: string, bucketEncryptionKey: string) => @@ -61,6 +65,13 @@ const FilesRoutes = () => { component={LinkSharingLanding} redirectPath={ROUTE_LINKS.Landing} /> + { component={SettingsPage} redirectPath={ROUTE_LINKS.Landing} /> - { @@ -103,16 +100,6 @@ const useStyles = makeStyles( } } }, - accountControls: { - display: "flex", - justifyContent: "flex-end", - alignItems: "center", - flexDirection: "row", - - "& > *:first-child": { - marginRight: constants.generalUnit * 2 - } - }, searchModule: { [breakpoints.down("md")]: { height: constants.mobileHeaderHeight, @@ -121,31 +108,6 @@ const useStyles = makeStyles( zIndex: zIndex?.background } }, - options: { - backgroundColor: constants.header.optionsBackground, - color: constants.header.optionsTextColor, - border: `1px solid ${constants.header.optionsBorder}`, - minWidth: 145 - }, - menuItem: { - width: "100%", - display: "flex", - flexDirection: "row", - alignItems: "center", - color: constants.header.menuItemTextColor, - "& svg": { - width: constants.generalUnit * 2, - height: constants.generalUnit * 2, - marginRight: constants.generalUnit, - fill: palette.additional["gray"][7], - stroke: palette.additional["gray"][7] - } - }, - icon: { - "& svg": { - fill: constants.header.iconColor - } - }, title : { marginLeft: constants.generalUnit }, @@ -161,6 +123,13 @@ const useStyles = makeStyles( marginLeft: constants.generalUnit * 2 } } + }, + headerSection: { + display: "flex", + alignItems: "center" + }, + searchBox: { + flex: 1 } }) } @@ -175,21 +144,9 @@ const AppHeader = ({ navOpen, setNavOpen }: IAppHeader) => { const { desktop } = useThemeSwitcher() const classes = useStyles() const { isLoggedIn, secured } = useFilesApi() - const { publicKey, isNewDevice, shouldInitializeAccount, logout } = useThresholdKey() - const { getProfileTitle, removeUser } = useUser() + const { publicKey, isNewDevice, shouldInitializeAccount } = useThresholdKey() const [searchActive, setSearchActive] = useState(false) const [isTeamModalOpen, setIsTeamModalOpen] = useState(false) - const { history } = useHistory() - - const signOut = useCallback(async () => { - logout() - .catch(console.error) - .finally(() => { - removeUser() - history.replace("/", {}) - }) - - }, [logout, removeUser, history]) const onReportBugClick = useCallback(() => { window.open(ROUTE_LINKS.DiscordInvite, "_blank") @@ -217,8 +174,8 @@ const AppHeader = ({ navOpen, setNavOpen }: IAppHeader) => { !shouldInitializeAccount && ( <> {desktop ? ( - <> -
+
+
{ Start a team
-
- signOut(), - contents: ( -
- - - Sign Out - -
- ) - } - ]} +
+
- +
) : ( <> {!searchActive && ( diff --git a/packages/files-ui/src/Components/Layouts/AppNav.tsx b/packages/files-ui/src/Components/Layouts/AppNav.tsx index 45bd47098b..68caf6c3c9 100644 --- a/packages/files-ui/src/Components/Layouts/AppNav.tsx +++ b/packages/files-ui/src/Components/Layouts/AppNav.tsx @@ -5,7 +5,6 @@ import clsx from "clsx" import { Link, Typography, - ChainsafeFilesLogo, DatabaseSvg, SettingSvg, PowerDownSvg, @@ -13,6 +12,7 @@ import { formatBytes, DeleteSvg, UserShareSvg, + MenuDropdown, ScrollbarWrapper } from "@chainsafe/common-components" import { ROUTE_LINKS } from "../FilesRoutes" @@ -21,6 +21,8 @@ import { useThresholdKey } from "../../Contexts/ThresholdKeyContext" import { CSFTheme } from "../../Themes/types" import { useUser } from "../../Contexts/UserContext" import { useFilesApi } from "../../Contexts/FilesApiContext" +import { Hashicon } from "@emeraldpay/hashicon-react" + const useStyles = makeStyles( ({ palette, animation, breakpoints, constants, zIndex }: CSFTheme) => { return createStyles({ @@ -109,30 +111,9 @@ const useStyles = makeStyles( } }, logo: { - textDecoration: "none", - display: "flex", - flexDirection: "row", - alignItems: "center", - - [breakpoints.up("md")]: { - "& img": { - height: constants.generalUnit * 5, - width: "auto" - }, - "& > *:first-child": { - marginRight: constants.generalUnit - } - }, - [breakpoints.down("md")]: { - position: "absolute", - left: "50%", - top: "50%", - transform: "translate(-50%,-50%)", - "& img": { - height: constants.generalUnit * 3.25, - width: "auto" - } - } + width: constants.generalUnit * 2, + height: constants.generalUnit * 2, + marginRight: constants.generalUnit }, navMenu: { display: "flex", @@ -206,21 +187,48 @@ const useStyles = makeStyles( } }, menuItem: { - width: 100, + width: "100%", display: "flex", flexDirection: "row", alignItems: "center", + color: constants.header.menuItemTextColor, "& svg": { width: constants.generalUnit * 2, height: constants.generalUnit * 2, - marginRight: constants.generalUnit + marginRight: constants.generalUnit, + fill: palette.additional["gray"][7], + stroke: palette.additional["gray"][7] } }, spaceUsedMargin: { marginBottom: constants.generalUnit }, - betaCaption: { - marginBottom: constants.generalUnit * 0.5 + profileButton: { + borderRadius: 4, + display: "flex", + alignItems: "center", + padding: constants.generalUnit, + background: palette.additional["gray"][1], + boxShadow: constants.nav.profileButtonShadow + }, + options: { + backgroundColor: constants.header.optionsBackground, + color: constants.header.optionsTextColor, + border: `1px solid ${constants.header.optionsBorder}`, + minWidth: 145 + }, + icon: { + "& svg": { + fill: constants.header.iconColor + } + }, + hashIconContainer: { + marginRight: constants.generalUnit, + display: "flex", + alignItems: "center" + }, + menuTitle: { + padding: `${constants.generalUnit * 1.5}px 0` } }) } @@ -237,7 +245,7 @@ const AppNav = ({ navOpen, setNavOpen }: IAppNav) => { const { storageSummary } = useFiles() const { isLoggedIn, secured } = useFilesApi() const { publicKey, isNewDevice, shouldInitializeAccount, logout } = useThresholdKey() - const { removeUser } = useUser() + const { removeUser, getProfileTitle, profile } = useUser() const signOut = useCallback(() => { logout() @@ -250,6 +258,8 @@ const AppNav = ({ navOpen, setNavOpen }: IAppNav) => { } }, [desktop, navOpen, setNavOpen]) + const profileTitle = getProfileTitle() + return (
{ })} > {isLoggedIn && - secured && - !!publicKey && - !isNewDevice && - !shouldInitializeAccount && ( + secured && + !!publicKey && + !isNewDevice && + !shouldInitializeAccount && ( <> {desktop && ( -
- + + + + Sign Out + +
+ ) + } + ]} > - - - Files - -   - - beta - - -
+ {!!profileTitle && +
+
+ +
+ + {profileTitle} + +
+ } + + )}
@@ -296,9 +333,7 @@ const AppNav = ({ navOpen, setNavOpen }: IAppNav) => {
)} {!desktop && ( -
{ - handleOnClick() - signOut() - }} - > - - - Sign Out - -
+ <> +
setNavOpen(false)} + className={clsx(classes.blocker, { + active: navOpen + })} + /> +
{ + handleOnClick() + signOut() + }} + > + + + Sign Out + +
+ )} - {!desktop && ( -
setNavOpen(false)} - className={clsx(classes.blocker, { - active: navOpen - })} - >
- )} )} @@ -413,4 +447,4 @@ const AppNav = ({ navOpen, setNavOpen }: IAppNav) => { ) } -export default AppNav \ No newline at end of file +export default AppNav diff --git a/packages/files-ui/src/Components/Layouts/AppWrapper.tsx b/packages/files-ui/src/Components/Layouts/AppWrapper.tsx index e9da33b95c..90d6e5891a 100644 --- a/packages/files-ui/src/Components/Layouts/AppWrapper.tsx +++ b/packages/files-ui/src/Components/Layouts/AppWrapper.tsx @@ -24,7 +24,6 @@ const useStyles = makeStyles( padding: "0", "&.active": { // This moves the content areas based on the size of the nav bar - padding: `${0}px ${constants.contentPadding}px ${0}px ${ Number(constants.navWidth) + Number(constants.contentPadding) @@ -34,20 +33,18 @@ const useStyles = makeStyles( [breakpoints.down("md")]: {} }, content: { + height: "initial", [breakpoints.up("md")]: { height: "100%", minHeight: "100vh", transitionDuration: `${animation.translate}ms`, - padding: 0, "&.active": { - height: "initial", padding: `${constants.contentTopPadding}px 0 0` } }, [breakpoints.down("md")]: { minHeight: "100vh", "&.active": { - height: "initial", padding: `${constants.mobileHeaderHeight}px 0 0` } } @@ -84,14 +81,16 @@ const AppWrapper: React.FC = ({ children }: IAppWrapper) => { setNavOpen={setNavOpen} />
{children}
diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/CSFFileBrowser.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/CSFFileBrowser.tsx index e458f9b587..133088fe5a 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/CSFFileBrowser.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/CSFFileBrowser.tsx @@ -26,6 +26,7 @@ import getFilesFromDataTransferItems from "../../../Utils/getFilesFromDataTransf const CSFFileBrowser: React.FC = () => { const { downloadFile, uploadFiles, buckets } = useFiles() + const { accountRestricted } = useFilesApi() const { filesApiClient } = useFilesApi() const { addToast } = useToasts() const [loadingCurrentPath, setLoadingCurrentPath] = useState(false) @@ -155,12 +156,22 @@ const CSFFileBrowser: React.FC = () => { const handleUploadOnDrop = useCallback(async (files: File[], fileItems: DataTransferItemList, path: string) => { if (!bucket) return + + if (accountRestricted) { + addToast({ + type:"error", + title: t`Uploads disabled`, + subtitle: t`Oops! You need to pay for this month to upload more content.` + }) + return + } + const flattenedFiles = await getFilesFromDataTransferItems(fileItems) const paths = [...new Set(flattenedFiles.map(f => f.filepath))] paths.forEach(p => { uploadFiles(bucket, flattenedFiles.filter(f => f.filepath === p), getPathWithFile(path, p)) }) - }, [uploadFiles, bucket]) + }, [uploadFiles, bucket, accountRestricted, addToast]) const viewFolder = useCallback((cid: string) => { const fileSystemItem = pathContents.find(f => f.cid === cid) diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/ShareModal.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/ShareModal.tsx index 070b0e9531..39c6a8f567 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/ShareModal.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/ShareModal.tsx @@ -16,6 +16,7 @@ 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 @@ -139,6 +140,7 @@ interface IShareFileProps { const ShareModal = ({ onClose, fileSystemItems }: IShareFileProps) => { const { handleCreateSharedFolder } = useCreateOrEditSharedFolder() + const { accountRestricted } = useFilesApi() const [sharedFolderName, setSharedFolderName] = useState("") const [isUsingExistingBucket, setIsUsingExistingBucket] = useState(true) const [keepOriginalFile, setKeepOriginalFile] = useState(true) @@ -168,18 +170,19 @@ const ShareModal = ({ onClose, fileSystemItems }: IShareFileProps) => { return buckets .filter(buck => buck.type === "share" || buck.type === "csf") - // keep only alive buckets + // do not show any buckets being deleted .filter(buck => buck.status !== "deleting") - // filter out the current bucket + // 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)))) .map(buck => ({ label: buck.name || t`Home`, value: buck.id })) - } - , [bucket, buckets, profile]) + }, [bucket, buckets, profile, accountRestricted]) const hasNoSharedBucket = useMemo(() => bucketsOptions.length === 0, [bucketsOptions.length]) @@ -190,10 +193,10 @@ const ShareModal = ({ onClose, fileSystemItems }: IShareFileProps) => { // if the user has no shared bucket, we default to new folder creation useEffect(() => { - if (hasNoSharedBucket) { + if (hasNoSharedBucket && !accountRestricted) { setIsUsingExistingBucket(false) } - }, [hasNoSharedBucket]) + }, [hasNoSharedBucket, accountRestricted]) const onNameChange = useCallback((value?: string | number) => { if (value === undefined) return @@ -212,12 +215,12 @@ const ShareModal = ({ onClose, fileSystemItems }: IShareFileProps) => { }, []) const handleShare = useCallback(async () => { - if(!bucket) { + if (!bucket) { console.error("Bucket is undefined") return } - if(!destinationBucket && isUsingExistingBucket){ + if (!destinationBucket && isUsingExistingBucket) { return } @@ -228,7 +231,7 @@ const ShareModal = ({ onClose, fileSystemItems }: IShareFileProps) => { try { const newBucket = await handleCreateSharedFolder(sharedFolderName, [], []) - if(!newBucket){ + if (!newBucket) { return } bucketToUpload = newBucket @@ -239,7 +242,7 @@ const ShareModal = ({ onClose, fileSystemItems }: IShareFileProps) => { setIsFolderCreationLoading(false) } - if(!bucketToUpload){ + if (!bucketToUpload) { console.error("Bucket id to upload is undefined") return } @@ -275,7 +278,7 @@ const ShareModal = ({ onClose, fileSystemItems }: IShareFileProps) => { closePosition="none" maxWidth="sm" mobileStickyBottom={false} - subModal={bucketToUpload && ( + subModal={bucketToUpload && ( { value={keepOriginalFile} onChange={() => { captureEvent("copy or move files on share") - setKeepOriginalFile(!keepOriginalFile)} - } + setKeepOriginalFile(!keepOriginalFile) + }} label={t`Keep original files`} />
@@ -377,7 +380,7 @@ const ShareModal = ({ onClose, fileSystemItems }: IShareFileProps) => { loading={!isUsingExistingBucket && isFolderCreationLoading} disabled={isUsingExistingBucket ? !destinationBucket?.id - : !sharedFolderName || !!nameError + : !sharedFolderName || !!nameError } > {isUsingExistingBucket ? keepOriginalFile @@ -406,4 +409,3 @@ const ShareModal = ({ onClose, fileSystemItems }: IShareFileProps) => { } export default ShareModal - diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/SharedFileBrowser.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/SharedFileBrowser.tsx index 6f062582e5..1276d5c13d 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/SharedFileBrowser.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/SharedFileBrowser.tsx @@ -22,6 +22,7 @@ import DragAndDrop from "../../../Contexts/DnDContext" import FilesList from "./views/FilesList" import { createStyles, makeStyles } from "@chainsafe/common-theme" import { CSFTheme } from "../../../Themes/types" +import getFilesFromDataTransferItems from "../../../Utils/getFilesFromDataTransferItems" const useStyles = makeStyles(({ constants, palette }: CSFTheme) => createStyles({ @@ -42,9 +43,9 @@ const useStyles = makeStyles(({ constants, palette }: CSFTheme) => })) const SharedFileBrowser = () => { - const classes = useStyles() const { downloadFile, uploadFiles, buckets, getStorageSummary, refreshBuckets } = useFiles() - const { filesApiClient } = useFilesApi() + const { filesApiClient, accountRestricted } = useFilesApi() + const classes = useStyles() const { addToast } = useToasts() const [loadingCurrentPath, setLoadingCurrentPath] = useState(false) const [pathContents, setPathContents] = useState([]) @@ -129,7 +130,6 @@ const SharedFileBrowser = () => { if (!bucket) return const pathsToDelete = getAbsolutePathsFromCids(cids, currentPath, pathContents) - filesApiClient.removeBucketObject(bucket.id, { paths: pathsToDelete }) .then(() => { addToast({ @@ -200,23 +200,20 @@ const SharedFileBrowser = () => { const handleUploadOnDrop = useCallback(async (files: File[], fileItems: DataTransferItemList, path: string) => { if (!bucket) return - let hasFolder = false - for (let i = 0; i < files.length; i++) { - if (fileItems[i].webkitGetAsEntry()?.isDirectory) { - hasFolder = true - } - } - if (hasFolder) { + if (accountRestricted) { addToast({ - title: t`Folder uploads are not supported currently`, - type: "error" + type:"error", + title: t`Uploads disabled`, + subtitle: t`Oops! You need to pay for this month to upload more content.` }) - } else { - uploadFiles(bucket, files, path) - .then(() => refreshContents()) - .catch(console.error) + return } - }, [addToast, uploadFiles, bucket, refreshContents]) + const flattenedFiles = await getFilesFromDataTransferItems(fileItems) + const paths = [...new Set(flattenedFiles.map(f => f.filepath))] + paths.forEach(p => { + uploadFiles(bucket, flattenedFiles.filter(f => f.filepath === p), getPathWithFile(path, p)) + }) + }, [uploadFiles, bucket, accountRestricted, addToast]) const bulkOperations: IBulkOperations = useMemo(() => ({ [CONTENT_TYPES.Directory]: ["download", "move", "delete", "share"], diff --git a/packages/files-ui/src/Components/Modules/FileBrowsers/SharedFoldersOverview.tsx b/packages/files-ui/src/Components/Modules/FileBrowsers/SharedFoldersOverview.tsx index bd205bbf8a..b70972f564 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/SharedFoldersOverview.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/SharedFoldersOverview.tsx @@ -25,6 +25,8 @@ import { SharedFolderModalMode } from "./types" 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 = "50px 3fr 90px 140px 140px 45px !important" export const mobileSharedGridSettings = "3fr 80px 45px !important" @@ -36,7 +38,10 @@ const useStyles = makeStyles( position: "relative", [breakpoints.down("md")]: { marginLeft: constants.generalUnit * 2, - marginRight: constants.generalUnit * 2 + marginRight: constants.generalUnit * 2, + "&.bottomBanner": { + paddingBottom: constants.bottomBannerMobileHeight + } }, [breakpoints.up("md")]: { border: "1px solid transparent", @@ -45,6 +50,10 @@ const useStyles = makeStyles( minHeight: `calc(100vh - ${Number(constants.contentTopPadding)}px)`, "&.droppable": { borderColor: palette.additional["geekblue"][4] + }, + "&.bottomBanner": { + minHeight: `calc(100vh - ${Number(constants.contentTopPadding) + Number(constants.bottomBannerHeight)}px)`, + paddingBottom: constants.bottomBannerHeight } } }, @@ -109,7 +118,7 @@ type SortingType = "name" | "size" | "date_uploaded" const SharedFolderOverview = () => { const classes = useStyles() - const { filesApiClient } = useFilesApi() + const { filesApiClient, accountRestricted } = useFilesApi() const { buckets, isLoadingBuckets, refreshBuckets } = useFiles() const [createOrEditSharedFolderMode, setCreateOrEditSharedFolderMode] = useState(undefined) const [bucketToEdit, setBucketToEdit] = useState(undefined) @@ -165,11 +174,12 @@ const SharedFolderOverview = () => { const openSharedFolder = useCallback((bucketId: string) => { redirect(ROUTE_LINKS.SharedFolderExplorer(bucketId, "/")) }, [redirect]) - return ( <>
{ injectedClass={{ inner: classes.confirmDeletionDialog }} testId="shared-folder-deletion" /> + {accountRestricted && + + } ) } 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 a553878fd7..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, 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 2357eeb676..33cb28b9e1 100644 --- a/packages/files-ui/src/Components/Modules/FileBrowsers/views/FilesList.tsx +++ b/packages/files-ui/src/Components/Modules/FileBrowsers/views/FilesList.tsx @@ -37,7 +37,6 @@ import { BrowserView, FileOperation, MoveModalMode } from "../types" import { FileSystemItem as FileSystemItemType, useFiles } from "../../../../Contexts/FilesContext" import FileSystemItem from "./FileSystemItem/FileSystemItem" import FilePreviewModal from "../../FilePreviewModal" - import CreateFolderModal from "../CreateFolderModal" import UploadFileModule from "../UploadFileModal" import MoveFileModal from "../MoveFileModal" @@ -56,11 +55,13 @@ import Menu from "../../../../UI-components/Menu" import SharingExplainerModal from "../../../SharingExplainerModal" import { useSharingExplainerModalFlag } from "../hooks/useSharingExplainerModalFlag" import { ListItemIcon, ListItemText } from "@material-ui/core" +import { useFilesApi } from "../../../../Contexts/FilesApiContext" +import RestrictedModeBanner from "../../../Elements/RestrictedModeBanner" -const baseOperations: FileOperation[] = ["download", "info", "preview", "share"] +const baseOperations: FileOperation[] = ["download", "info", "preview", "share"] const readerOperations: FileOperation[] = [...baseOperations, "report"] const ownerOperations: FileOperation[] = [...baseOperations, "delete", "move", "rename", "recover"] -const csfOperations: FileOperation[] = [...ownerOperations, "share"] +const csfOperations: FileOperation[] = [...ownerOperations, "share"] const writerOperations: FileOperation[] = [...ownerOperations, "report"] interface IStyleProps { @@ -76,7 +77,10 @@ const useStyles = makeStyles( position: "relative", [breakpoints.down("md")]: { marginLeft: constants.generalUnit * 2, - marginRight: constants.generalUnit * 2 + marginRight: constants.generalUnit * 2, + "&.bottomBanner": { + paddingBottom: constants.bottomBannerMobileHeight + } }, [breakpoints.up("md")]: { border: "1px solid transparent", @@ -85,6 +89,10 @@ const useStyles = makeStyles( minHeight: `calc(100vh - ${Number(constants.contentTopPadding)}px)`, "&.droppable": { borderColor: palette.primary.main + }, + "&.bottomBanner": { + minHeight: `calc(100vh - ${Number(constants.contentTopPadding) + Number(constants.bottomBannerHeight)}px)`, + paddingBottom: constants.bottomBannerHeight } } }, @@ -287,7 +295,7 @@ const useStyles = makeStyles( display: "flex", justifyContent: "flex-end" }, - focusVisible:{ + focusVisible: { backgroundColor: "transparent !important" }, menuIcon: { @@ -324,6 +332,7 @@ const FilesList = ({ isShared = false }: Props) => { const [isReportFileModalOpen, setIsReportFileModalOpen] = useState(false) const [isFileInfoModalOpen, setIsFileInfoModalOpen] = useState(false) const [isShareModalOpen, setIsShareModalOpen] = useState(false) + const { accountRestricted } = useFilesApi() const { heading, @@ -367,7 +376,7 @@ const FilesList = ({ isShared = false }: Props) => { let temp = [] switch (column) { - // defaults to name sorting + // defaults to name sorting default: { temp = sourceFiles.sort((a, b) => { return a.name.localeCompare(b.name, selectedLocale, { @@ -520,7 +529,7 @@ const FilesList = ({ isShared = false }: Props) => { if (!!permission && isShared) { - switch(permission) { + switch (permission) { case "owner": fileOperations = ownerOperations break @@ -653,7 +662,8 @@ const FilesList = ({ isShared = false }: Props) => { ), - onClick: () => setCreateFolderModalOpen(true) + onClick: () => setCreateFolderModalOpen(true), + disabled: accountRestricted }, { contents: ( @@ -664,10 +674,11 @@ const FilesList = ({ isShared = false }: Props) => { ), - onClick: () => setIsUploadModalOpen(true) + onClick: () => setIsUploadModalOpen(true), + disabled: accountRestricted } ], - [classes.menuIcon]) + [classes.menuIcon, accountRestricted]) const mobileBulkActions = useMemo(() => { const menuOptions = [] @@ -763,7 +774,10 @@ const FilesList = ({ isShared = false }: Props) => {
{ {isShared && bucket && (
- +
)}
@@ -863,7 +877,7 @@ const FilesList = ({ isShared = false }: Props) => { } + icon={} options={mobileMenuItems} style={{ focusVisible: classes.focusVisible }} /> @@ -872,69 +886,69 @@ const FilesList = ({ isShared = false }: Props) => { )}
- { withSurvey && !isShared && isSurveyBannerVisible - ? + {withSurvey && !isShared && isSurveyBannerVisible + ? : } {desktop && (
{selectedItems.length > 0 && - <> - {validBulkOps.includes("download") && (selectedItems.length > 1 || selectionContainsAFolder) && ( - - )} - {validBulkOps.includes("move") && ( - - )} - {validBulkOps.includes("recover") && ( - - )} - {validBulkOps.includes("delete") && ( - - )} - {validBulkOps.includes("share") && ( - - )} - + <> + {validBulkOps.includes("download") && (selectedItems.length > 1 || selectionContainsAFolder) && ( + + )} + {validBulkOps.includes("move") && ( + + )} + {validBulkOps.includes("recover") && ( + + )} + {validBulkOps.includes("delete") && ( + + )} + {validBulkOps.includes("share") && ( + + )} + }
)} @@ -1152,17 +1166,18 @@ const FilesList = ({ isShared = false }: Props) => { }} reportFile={(filePath: string) => { setFilePath(filePath) - setIsReportFileModalOpen(true)} + setIsReportFileModalOpen(true) + } } showFileInfo={(filePath: string) => { setFilePath(filePath) setIsFileInfoModalOpen(true) }} + handleShare={onShare} showPreview={(fileIndex: number) => { setFileIndex(fileIndex) setIsPreviewOpen(true) }} - handleShare={onShare} /> ))} @@ -1209,7 +1224,8 @@ const FilesList = ({ isShared = false }: Props) => { browserView="grid" reportFile={(fileInfoPath: string) => { setFilePath(fileInfoPath) - setIsReportFileModalOpen(true)} + setIsReportFileModalOpen(true) + } } showFileInfo={(fileInfoPath: string) => { setFilePath(fileInfoPath) @@ -1234,8 +1250,8 @@ const FilesList = ({ isShared = false }: Props) => { other: `You are about to delete ${selectedCids.length} items.` }) } - rejectText = {t`Cancel`} - acceptText = {t`Confirm`} + rejectText={t`Cancel`} + acceptText={t`Confirm`} acceptButtonProps={{ loading: isDeletingFiles, disabled: isDeletingFiles, testId: "confirm-deletion" }} rejectButtonProps={{ disabled: isDeletingFiles, testId: "cancel-deletion" }} injectedClass={{ inner: classes.confirmDeletionDialog }} @@ -1283,7 +1299,7 @@ const FilesList = ({ isShared = false }: Props) => { filePath={isSearch && getPath ? getPath(files[fileIndex].cid) : currentPath} /> )} - { filePath && isReportFileModalOpen && + {filePath && isReportFileModalOpen && { @@ -1292,7 +1308,7 @@ const FilesList = ({ isShared = false }: Props) => { }} /> } - { filePath && isFileInfoModalOpen && + {filePath && isFileInfoModalOpen && { @@ -1301,7 +1317,7 @@ const FilesList = ({ isShared = false }: Props) => { }} /> } - { !showExplainerBeforeShare && isShareModalOpen && selectedItems.length && + {!showExplainerBeforeShare && isShareModalOpen && selectedItems.length && { setIsShareModalOpen(false) @@ -1310,6 +1326,9 @@ const FilesList = ({ isShared = false }: Props) => { fileSystemItems={selectedItems} /> } + {accountRestricted && + + } { ) } -export default FilesList +export default FilesList \ No newline at end of file diff --git a/packages/files-ui/src/Components/Modules/FilePreviewModal.tsx b/packages/files-ui/src/Components/Modules/FilePreviewModal.tsx index e7e0ef52c5..bbd278d157 100644 --- a/packages/files-ui/src/Components/Modules/FilePreviewModal.tsx +++ b/packages/files-ui/src/Components/Modules/FilePreviewModal.tsx @@ -325,7 +325,8 @@ const FilePreviewModal = ({ file, nextFile, previousFile, closePreview, filePath )} diff --git a/packages/files-ui/src/Components/Modules/LoginModule/InitialScreen.tsx b/packages/files-ui/src/Components/Modules/LoginModule/InitialScreen.tsx index e8eb37bc62..e683c51ddd 100644 --- a/packages/files-ui/src/Components/Modules/LoginModule/InitialScreen.tsx +++ b/packages/files-ui/src/Components/Modules/LoginModule/InitialScreen.tsx @@ -447,7 +447,10 @@ const InitialScreen = ({ className }: IInitialScreen) => { {error} + className={classes.error} + > + {error} + )} - -
-
-
-
- - - ) -} - -export default PlanView diff --git a/packages/files-ui/src/Components/Modules/Settings/LanguageSelection.tsx b/packages/files-ui/src/Components/Modules/Settings/ProfileTab/LanguageSelection.tsx similarity index 93% rename from packages/files-ui/src/Components/Modules/Settings/LanguageSelection.tsx rename to packages/files-ui/src/Components/Modules/Settings/ProfileTab/LanguageSelection.tsx index 4b29484a9e..c9d697684b 100644 --- a/packages/files-ui/src/Components/Modules/Settings/LanguageSelection.tsx +++ b/packages/files-ui/src/Components/Modules/Settings/ProfileTab/LanguageSelection.tsx @@ -1,8 +1,8 @@ import React, { useMemo } from "react" -import { useLanguageContext } from "../../../Contexts/LanguageContext" +import { useLanguageContext } from "../../../../Contexts/LanguageContext" import { MenuDropdown } from "@chainsafe/common-components" import { createStyles, makeStyles } from "@chainsafe/common-theme" -import { CSFTheme } from "../../../Themes/types" +import { CSFTheme } from "../../../../Themes/types" const useStyles = makeStyles( ({ constants, palette }: CSFTheme) => { diff --git a/packages/files-ui/src/Components/Modules/Settings/Profile.tsx b/packages/files-ui/src/Components/Modules/Settings/ProfileTab/index.tsx similarity index 97% rename from packages/files-ui/src/Components/Modules/Settings/Profile.tsx rename to packages/files-ui/src/Components/Modules/Settings/ProfileTab/index.tsx index 3c69e1a308..af5cbf18eb 100644 --- a/packages/files-ui/src/Components/Modules/Settings/Profile.tsx +++ b/packages/files-ui/src/Components/Modules/Settings/ProfileTab/index.tsx @@ -9,7 +9,8 @@ import { RadioInput, TextInput, CheckIcon, - CheckboxInput + CheckboxInput, + Divider } from "@chainsafe/common-components" import { makeStyles, @@ -19,13 +20,13 @@ import { } from "@chainsafe/common-theme" import { LockIcon, CopyIcon } from "@chainsafe/common-components" import { Form, useFormik, FormikProvider } from "formik" -import { useUser } from "../../../Contexts/UserContext" +import { useUser } from "../../../../Contexts/UserContext" import { t, Trans } from "@lingui/macro" -import { centerEllipsis } from "../../../Utils/Helpers" -import { CSFTheme } from "../../../Themes/types" +import { centerEllipsis } from "../../../../Utils/Helpers" +import { CSFTheme } from "../../../../Themes/types" import clsx from "clsx" import LanguageSelection from "./LanguageSelection" -import { useThresholdKey } from "../../../Contexts/ThresholdKeyContext" +import { useThresholdKey } from "../../../../Contexts/ThresholdKeyContext" import EthCrypto from "eth-crypto" const useStyles = makeStyles(({ constants, breakpoints, palette, typography }: CSFTheme) => @@ -43,11 +44,6 @@ const useStyles = makeStyles(({ constants, breakpoints, palette, typography }: C borderBottom: "none" } }, - header: { - fontSize: 28, - lineHeight: "32px", - marginBottom: constants.generalUnit * 5 - }, boxContainer: { marginBottom: constants.generalUnit * 4 }, @@ -131,7 +127,7 @@ const useStyles = makeStyles(({ constants, breakpoints, palette, typography }: C borderRadius: 4, paddingLeft: 20, paddingTop: 14, - margin: 6, + margin: "6px 0", [breakpoints.down("sm")]: { width: "100%" }, @@ -330,10 +326,10 @@ const ProfileView = () => { Profile settings + {profile?.publicAddress &&
- createStyles({ - container: { - padding: 0, - marginBottom: theme.constants.generalUnit * 6, - [theme.breakpoints.down("md")]: { - marginTop: theme.constants.generalUnit * 2, - padding: `0 ${theme.constants.generalUnit}px` - } - }, - headingContainer: { - marginBottom: theme.constants.generalUnit * 6, - [theme.breakpoints.down("md")]: { - marginBottom: theme.constants.generalUnit * 2 - } - }, - heading: { - marginBottom: theme.constants.generalUnit * 4, - [theme.breakpoints.down("md")]: { - marginBottom: theme.constants.generalUnit * 2 - } - }, - countryOrRegionContainer: { - margin: `${theme.constants.generalUnit}px 0` - }, - zipCodeInput: { - width: "100%", - margin: `${theme.constants.generalUnit * 2}px 0px` - }, - purchaseContainer: { - border: `1px solid ${theme.palette.additional["gray"][6]}`, - padding: theme.constants.generalUnit * 6, - [theme.breakpoints.down("md")]: { - border: "none", - padding: 0 - } - }, - backIcon: { - fontSize: "10px", - marginRight: theme.constants.generalUnit - }, - submitButton: { - width: "100%", - marginTop: theme.constants.generalUnit * 4 - }, - title: { - color: theme.palette.additional["gray"][8], - marginBottom: `${theme.constants.generalUnit * 2}px`, - fontWeight: 600, - [theme.breakpoints.down("md")]: { - marginBottom: 0 - } - }, - textInput: { - margin: `${theme.constants.generalUnit * 1}px 0`, - width: "100%" - }, - terms: { - maxWidth: 700, - margin: `${theme.constants.generalUnit}px 0 ${ - theme.constants.generalUnit * 3 - }px 0`, - color: theme.palette.additional["gray"][8] - }, - cardContainer: { - marginRight: theme.constants.generalUnit * 3, - maxWidth: 500, - display: "flex", - flexDirection: "column", - [theme.breakpoints.down("md")]: { - margin: 0, - maxWidth: "100%" - } - }, - countriesContainer: { - marginTop: theme.constants.generalUnit * 6, - marginLeft: theme.constants.generalUnit * 3, - marginBottom: theme.constants.generalUnit * 2, - display: "flex", - flexDirection: "column", - justifyContent: "space-between", - height: "100%", - maxWidth: 500, - [theme.breakpoints.down("md")]: { - margin: 0, - maxWidth: "100%" - } - }, - pricingContainer: { - padding: `${theme.constants.generalUnit * 2}px 0`, - borderBottom: `1px solid ${theme.palette.additional["gray"][7]}`, - [theme.breakpoints.down("md")]: { - border: "none", - padding: `${theme.constants.generalUnit * 2}px 0` - } - }, - pricing: { - display: "flex", - flexDirection: "column", - alignItems: "end", - [theme.breakpoints.down("md")]: { - alignItems: "start" - } - }, - pricingTitle: { - color: theme.palette.additional["gray"][8], - marginBottom: theme.constants.generalUnit - }, - actualPrice: { - color: theme.palette.additional["gray"][7], - fontSize: 16, - lineHeight: "24px" - }, - priceContainer: { - display: "flex", - alignItems: "center" - }, - price: { - color: theme.palette.additional["gray"][9], - marginRight: theme.constants.generalUnit - }, - priceSubtitle: { - color: theme.palette.additional["gray"][7] - } - }) -) - -const PurchasePlan: React.FC = () => { - const classes = useStyles() - const { profile } = useUser() - const { addCard, getCardTokenFromStripe } = useBilling() - const { desktop } = useThemeSwitcher() - - const validationSchema = yup.object().shape({ - name: yup.string().required("Name is required"), - email: yup.string().email("Email is invalid").required("Email is required"), - country: yup.string().when(["zipCode"], { - is: (zipCode: string | undefined | null) => !zipCode, - then: yup.string().required("Country or zip code is required") - }), - zipCode: yup.string() - }) - - return ( -
-
- {!desktop && ( - - - Back to plan settings - - )} - - {desktop ? "Purchase a Plus subscription" : "Checkout"} - - {desktop && ( - - - Back to plan settings - - )} -
- { - if (!STRIPE_PK) { - throw new Error("Stripe pk missing from environment") - } - const stripeResp = await getCardTokenFromStripe( - { - cardNumber: values.cardNumber, - cardExpiry: values.cardExpiry, - cardCvc: values.cardCvc - }, - STRIPE_PK - ) - // add card api for now - await addCard(stripeResp.data.id) - }} - validateOnChange={false} - validationSchema={validationSchema} - validate={(values) => { - const errors: any = {} - if (getCardNumberError(values.cardNumber)) { - errors.cardNumber = getCardNumberError(values.cardNumber) - } else if (getExpiryDateError(values.cardExpiry)) { - errors.cardExpiry = getExpiryDateError(values.cardExpiry) - } else if (getCVCError(values.cardCvc, undefined)) { - errors.cardCvc = getCVCError(values.cardCvc, undefined) - } - return errors - }} - enableReinitialize - > - {({ values, errors, handleChange, setValues }) => ( -
-
- - -
- {!desktop && ( -
- - Files plus subscription - individual - -
- - USD $107.67 - -
- - USD $83.45 - - - per year - -
-
-
- )} - - Pay with card - - - - - setValues({ ...values, cardNumber: value }) - } - handleChangeCardExpiry={(value) => - setValues({ ...values, cardExpiry: value }) - } - handleChangeCardCvc={(value) => - setValues({ ...values, cardCvc: value }) - } - error={ - errors.cardNumber || errors.cardExpiry || errors.cardCvc - } - /> -
-
- -
-
- - setValues({ ...values, country: value }) - } - options={countryList.map((country) => ({ - label: country.label, - value: country.label - }))} - isClearable={false} - name="country" - value={values.country} - size="large" - label="Country or region" - /> - { - handleChange(e) - }} - value={values.zipCode} - className={classes.zipCodeInput} - size="large" - captionMessage={errors.country || errors.zipCode} - label="Postal/Zip Code" - placeholder="Postal/Zip Code" - state={ - errors.country || errors.zipCode ? "error" : "normal" - } - /> -
- {desktop && ( -
- - Files plus subscription - individual - -
- - USD ${ACTUAL_PRICE} - -
- - USD ${FINAL_PRICE} - - - per year - -
-
-
- )} -
-
-
- - - By confirming your subscription, you allow ChainSafe Files to - charge your card for this payment and future payments in - accordance with their terms. - -
-
- )} -
-
- ) -} - -export default PurchasePlan diff --git a/packages/files-ui/src/Components/Modules/Settings/SavedBrowsers/BrowserPanel.tsx b/packages/files-ui/src/Components/Modules/Settings/SecurityTab/SavedBrowsers/BrowserPanel.tsx similarity index 97% rename from packages/files-ui/src/Components/Modules/Settings/SavedBrowsers/BrowserPanel.tsx rename to packages/files-ui/src/Components/Modules/Settings/SecurityTab/SavedBrowsers/BrowserPanel.tsx index f2b727cdbf..132fa85af1 100644 --- a/packages/files-ui/src/Components/Modules/Settings/SavedBrowsers/BrowserPanel.tsx +++ b/packages/files-ui/src/Components/Modules/Settings/SecurityTab/SavedBrowsers/BrowserPanel.tsx @@ -3,13 +3,13 @@ import { makeStyles, createStyles } from "@chainsafe/common-theme" -import { CSFTheme } from "../../../../Themes/types" +import { CSFTheme } from "../../../../../Themes/types" import { Button, ExpansionPanel, Typography } from "@chainsafe/common-components" import clsx from "clsx" import { Trans } from "@lingui/macro" import dayjs from "dayjs" -import { BrowserShare, useThresholdKey } from "../../../../Contexts/ThresholdKeyContext" -import CustomModal from "../../../Elements/CustomModal" +import { BrowserShare, useThresholdKey } from "../../../../../Contexts/ThresholdKeyContext" +import CustomModal from "../../../../Elements/CustomModal" const useStyles = makeStyles(({ palette, constants, animation, breakpoints }: CSFTheme) => createStyles({ diff --git a/packages/files-ui/src/Components/Modules/Settings/SavedBrowsers/index.tsx b/packages/files-ui/src/Components/Modules/Settings/SecurityTab/SavedBrowsers/index.tsx similarity index 91% rename from packages/files-ui/src/Components/Modules/Settings/SavedBrowsers/index.tsx rename to packages/files-ui/src/Components/Modules/Settings/SecurityTab/SavedBrowsers/index.tsx index 798a72449a..47d41ba69b 100644 --- a/packages/files-ui/src/Components/Modules/Settings/SavedBrowsers/index.tsx +++ b/packages/files-ui/src/Components/Modules/Settings/SecurityTab/SavedBrowsers/index.tsx @@ -3,10 +3,10 @@ import { makeStyles, createStyles } from "@chainsafe/common-theme" -import { CSFTheme } from "../../../../Themes/types" +import { CSFTheme } from "../../../../../Themes/types" import { Loading, Typography } from "@chainsafe/common-components" import BrowserPanel from "./BrowserPanel" -import { useThresholdKey } from "../../../../Contexts/ThresholdKeyContext" +import { useThresholdKey } from "../../../../../Contexts/ThresholdKeyContext" import { Trans } from "@lingui/macro" const useStyles = makeStyles(({ constants, breakpoints }: CSFTheme) => diff --git a/packages/files-ui/src/Components/Modules/Settings/Security/index.tsx b/packages/files-ui/src/Components/Modules/Settings/SecurityTab/index.tsx similarity index 98% rename from packages/files-ui/src/Components/Modules/Settings/Security/index.tsx rename to packages/files-ui/src/Components/Modules/Settings/SecurityTab/index.tsx index d751598441..b2b69aa33f 100644 --- a/packages/files-ui/src/Components/Modules/Settings/Security/index.tsx +++ b/packages/files-ui/src/Components/Modules/Settings/SecurityTab/index.tsx @@ -7,12 +7,11 @@ import { useThresholdKey } from "../../../../Contexts/ThresholdKeyContext" import clsx from "clsx" import PasswordForm from "../../../Elements/PasswordForm" import MnemonicForm from "../../../Elements/MnemonicForm" -import SavedBrowsers from "../SavedBrowsers" +import SavedBrowsers from "./SavedBrowsers" const useStyles = makeStyles(({ constants, breakpoints, palette, typography, zIndex }: CSFTheme) => createStyles({ root: { - paddingTop: constants.generalUnit * 2, paddingBottom: constants.generalUnit * 3, maxWidth: breakpoints.values["md"], [breakpoints.down("md")]: { @@ -160,11 +159,12 @@ const Security = ({ className }: SecurityProps) => { data-cy="settings-security-header" > Sign-in methods + {showWarning && ( { + return createStyles({ + root: { + flexDirection: "column" + }, + okButton: { + marginLeft: constants.generalUnit + }, + cancelButton: { + [breakpoints.down("md")]: { + position: "fixed", + bottom: 0, + left: 0, + width: "100%", + height: constants?.mobileButtonHeight + } + }, + label: { + fontSize: 14, + lineHeight: "22px" + }, + cardNumberInputs: { + marginBottom: constants.generalUnit * 2, + [breakpoints.down("md")]: { + marginTop: constants.generalUnit * 2, + marginBottom: constants.generalUnit * 2 + } + }, + cardInputs: { + border: `1px solid ${palette.additional["gray"][6]}`, + borderRadius: 2, + padding: constants.generalUnit * 1.5, + transitionDuration: `${animation.transform}ms`, + "&:hover": { + borderColor: palette.primary.border + } + }, + cardInputsFocus: { + borderColor: palette.primary.border, + boxShadow: constants.addCard.shadow + }, + expiryCvcContainer: { + display: "grid", + gridTemplateColumns: "1fr 1fr", + marginTop: constants.generalUnit, + gridColumnGap: constants.generalUnit, + [breakpoints.down("md")]: { + gridTemplateColumns: "1fr", + gridRowGap: constants.generalUnit * 2 + } + }, + error: { + marginTop: constants.generalUnit * 2, + color: palette.error.main + }, + backButton: { + flex: 1, + display: "flex", + alignItems: "center" + }, + linkButton: { + textDecoration: "underline", + cursor: "pointer" + } + }) + } +) + +interface IAddCardProps { + submitText: string + onCardAdd?: () => void + onClose?: () => void + goBack?: () => void + footerClassName?: string +} + + +const AddCard = ({ onClose, onCardAdd, footerClassName, submitText, goBack }: IAddCardProps) => { + const classes = useStyles() + const stripe = useStripe() + const elements = useElements() + const { addToast } = useToasts() + const { filesApiClient } = useFilesApi() + const { refreshDefaultCard, deleteCard, updateDefaultCard, defaultCard } = useBilling() + const [focusElement, setFocusElement] = useState<"number" | "expiry" | "cvc" | undefined>(undefined) + const [cardAddError, setCardAddError] = useState(undefined) + const theme: CSFTheme = useTheme() + const isUpdate = useMemo(() => !!defaultCard, [defaultCard]) + + const [loadingPaymentMethodAdd, setLoadingPaymentMethodAdd] = useState(false) + + const handleSubmitPaymentMethod = async (event: FormEvent) => { + event.preventDefault() + setCardAddError(undefined) + if (!stripe || !elements) return + try { + const cardNumberElement = elements.getElement(CardNumberElement) + if (!cardNumberElement) return + + setLoadingPaymentMethodAdd(true) + const { error, paymentMethod } = await stripe.createPaymentMethod({ + type: "card", + card: cardNumberElement + }) + + if (error || !paymentMethod) { + console.error(error) + setLoadingPaymentMethodAdd(false) + setCardAddError(t`Card inputs invalid`) + return + } + + const setupIntent = await filesApiClient.createSetupIntent() + const setupIntentResult = await stripe.confirmCardSetup(setupIntent.secret, { + payment_method: paymentMethod.id + }) + + if (setupIntentResult.error || !setupIntentResult.setupIntent) { + console.error(error) + setLoadingPaymentMethodAdd(false) + setCardAddError(t`Failed to add payment method`) + return + } + isUpdate && defaultCard && await deleteCard(defaultCard) + await updateDefaultCard(paymentMethod.id) + refreshDefaultCard() + onCardAdd && onCardAdd() + setLoadingPaymentMethodAdd(false) + addToast({ title: isUpdate ? t`Card updated` : t`Card added`, type: "success" }) + } catch (error) { + console.error(error) + setLoadingPaymentMethodAdd(false) + setCardAddError(t`Failed to add payment method`) + } + } + + return ( +
+
+ setFocusElement("number")} + onBlur={() => setFocusElement(undefined)} + onChange={() => setCardAddError(undefined)} + /> +
+ setFocusElement("expiry")} + onBlur={() => setFocusElement(undefined)} + onChange={() => setCardAddError(undefined)} + options={{ style: { + base: { + color: theme.constants.addCard.color + } + } }} + /> + setFocusElement("cvc")} + onBlur={() => setFocusElement(undefined)} + onChange={() => setCardAddError(undefined)} + options={{ style: { + base: { + color: theme.constants.addCard.color + } + } }} + /> +
+ {cardAddError && + {cardAddError} + + } + +
+ {goBack && + + Go back + + } +
+ {onClose && + + Cancel + + } + +
+
+
+ ) +} + +export default AddCard diff --git a/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/AddCard/AddCardModal.tsx b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/AddCard/AddCardModal.tsx new file mode 100644 index 0000000000..7c34316924 --- /dev/null +++ b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/AddCard/AddCardModal.tsx @@ -0,0 +1,86 @@ +import React, { useMemo } from "react" +import { createStyles, makeStyles } from "@chainsafe/common-theme" +import { CSFTheme } from "../../../../../Themes/types" +import CustomModal from "../../../../Elements/CustomModal" +import { t, Trans } from "@lingui/macro" +import AddCard from "./AddCard" +import { Typography } from "@chainsafe/common-components" +import { useBilling } from "../../../../../Contexts/BillingContext" + +const useStyles = makeStyles( + ({ breakpoints, constants, zIndex, typography }: CSFTheme) => { + return createStyles({ + root: { + padding: constants.generalUnit * 4 + }, + modalRoot: { + zIndex: zIndex?.blocker, + [breakpoints.down("md")]: {} + }, + modalInner: { + backgroundColor: constants.createFolder.backgroundColor, + color: constants.createFolder.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` + } + }, + heading: { + color: constants.createFolder.color, + fontWeight: typography.fontWeight.semibold, + textAlign: "left", + marginBottom: constants.generalUnit * 3 + }, + footer: { + marginTop: constants.generalUnit * 4 + } + }) + } +) + +interface IAddCardModalProps { + isModalOpen: boolean + onClose: () => void +} + +const AddCardModal = ({ isModalOpen, onClose }: IAddCardModalProps) => { + const classes = useStyles() + const { defaultCard } = useBilling() + const isUpdate = useMemo(() => !!defaultCard, [defaultCard]) + + return ( + +
+ + {isUpdate + ? Update your credit card + : Add a credit card + } + + +
+
+ ) +} + +export default AddCardModal diff --git a/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/BillingHistory.tsx b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/BillingHistory.tsx new file mode 100644 index 0000000000..a4a67364e6 --- /dev/null +++ b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/BillingHistory.tsx @@ -0,0 +1,46 @@ +import React from "react" +import { Typography, Link } from "@chainsafe/common-components" +import { makeStyles, ITheme, createStyles } from "@chainsafe/common-theme" +import { Trans } from "@lingui/macro" +import { ROUTE_LINKS } from "../../../FilesRoutes" + +const useStyles = makeStyles(({ constants }: ITheme) => + createStyles({ + container: { + padding: constants.generalUnit, + margin: `${constants.generalUnit * 1.5}px 0` + }, + link: { + display: "inline-block", + margin: `${constants.generalUnit * 2}px 0` + } + }) +) + +const BillingHistory = () => { + const classes = useStyles() + + return ( +
+ + Billing history + + + + View invoices + + +
+ ) +} + +export default BillingHistory diff --git a/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/ChangePlan/ChangePlanModal.tsx b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/ChangePlan/ChangePlanModal.tsx new file mode 100644 index 0000000000..e0817d328a --- /dev/null +++ b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/ChangePlan/ChangePlanModal.tsx @@ -0,0 +1,148 @@ +import React, { useEffect, useState } from "react" +import { makeStyles, createStyles } from "@chainsafe/common-theme" +import { CSFTheme } from "../../../../../Themes/types" +import { Modal } from "@chainsafe/common-components" +import SelectPlan from "./SelectPlan" +import PlanDetails from "./PlanDetails" +import PaymentMethod from "./PaymentMethod" +import ConfirmPlan from "./ConfirmPlan" +import { useBilling } from "../../../../../Contexts/BillingContext" +import { Product, ProductPrice } from "@chainsafe/files-api-client" +import PlanSuccess from "./PlanSuccess" + +const useStyles = makeStyles(({ constants, breakpoints }: CSFTheme) => + createStyles({ + root: { + "&:before": { + backgroundColor: constants.modalDefault.fadeBackground + } + }, + inner: { + borderRadius: `${constants.generalUnit / 2}px`, + [breakpoints.up("sm")]: { + minWidth: 480 + }, + [breakpoints.down("sm")]: { + width: "100%" + } + } + }) +) + +type ChangeModalSlides = "select" | +"planDetails" | +"paymentMethod" | +"confirmPlan" | +"planSuccess" + +interface IChangeProductModal { + onClose: () => void +} + +const ChangeProductModal = ({ onClose }: IChangeProductModal) => { + const classes = useStyles() + const { getAvailablePlans, changeSubscription } = useBilling() + const [selectedPlan, setSelectedPlan] = useState() + const [selectedPrice, setSelectedPrice] = useState() + const [slide, setSlide] = useState("select") + const [plans, setPlans] = useState() + const [isLoadingChangeSubscription, setIsLoadingChangeSubscription] = useState(false) + const [isSubscriptionError, setIsSubscriptionError] = useState(false) + + useEffect(() => { + if(!plans) { + getAvailablePlans() + .then((plans) => setPlans(plans)) + .catch(console.error) + } + }) + + const handleChangeSubscription = () => { + if (selectedPrice) { + setIsLoadingChangeSubscription(true) + changeSubscription(selectedPrice.id) + .then(() => { + setSlide("planSuccess") + }) + .catch(() => { + setIsSubscriptionError(true) + }) + .finally(() => setIsLoadingChangeSubscription(false)) + } + } + + return ( + + { + slide === "select" && { + setSelectedPlan(plan) + setSlide("planDetails") + }} + plans={plans} + /> + } + {slide === "planDetails" && selectedPlan && { + setSlide("select") + }} + onSelectPlanPrice={(planPrice: ProductPrice) => { + setSelectedPrice(planPrice) + setSlide("paymentMethod") + }} + /> + } + {slide === "paymentMethod" && { + setSlide("select") + }} + goToPlanDetails={() => { + setSlide("planDetails") + }} + onSelectPaymentMethod={() => { + setSlide("confirmPlan") + }} + /> + } + {slide === "confirmPlan" && selectedPlan && selectedPrice && { + setSlide("select") + }} + goToPlanDetails={() => { + setSlide("planDetails") + }} + goToPaymentMethod={() => { + setSlide("paymentMethod") + }} + loadingChangeSubscription={isLoadingChangeSubscription} + onChangeSubscription={handleChangeSubscription} + isSubscriptionError={isSubscriptionError} + /> + } + {slide === "planSuccess" && selectedPlan && selectedPrice && + } + + ) +} + +export default ChangeProductModal \ No newline at end of file diff --git a/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/ChangePlan/ConfirmPlan.tsx b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/ChangePlan/ConfirmPlan.tsx new file mode 100644 index 0000000000..a076d478a3 --- /dev/null +++ b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/ChangePlan/ConfirmPlan.tsx @@ -0,0 +1,285 @@ +import React from "react" +import { makeStyles, createStyles } from "@chainsafe/common-theme" +import { CSFTheme } from "../../../../../Themes/types" +import { Product, ProductPrice } from "@chainsafe/files-api-client" +import { Breadcrumb, Button, CreditCardIcon, Divider, formatBytes, Typography } from "@chainsafe/common-components" +import { t, Trans } from "@lingui/macro" +import dayjs from "dayjs" +import { useBilling } from "../../../../../Contexts/BillingContext" +import clsx from "clsx" + +const useStyles = makeStyles(({ constants, palette }: CSFTheme) => + createStyles({ + root: { + margin: `${constants.generalUnit * 2}px ${constants.generalUnit * 2}px` + }, + heading: { + marginTop: constants.generalUnit * 3, + marginBottom: constants.generalUnit * 2 + }, + subheading: { + marginBottom: constants.generalUnit * 3 + }, + boldText: { + fontWeight: "bold" + }, + normalWeightText: { + fontWeight: "normal" + }, + rowBox: { + display: "flex", + padding: `${constants.generalUnit * 0.5}px 0px` + }, + middleRowBox: { + display: "flex", + alignItems: "center", + padding: `${constants.generalUnit * 0.5}px 0px` + }, + pushRightBox: { + display: "flex", + flexDirection: "column", + alignItems: "flex-end", + flex: 1 + }, + buttons: { + display: "flex", + flexDirection: "row", + alignItems: "center", + justifyContent: "flex-end", + "& > *": { + marginLeft: constants.generalUnit + } + }, + bottomSection: { + display: "flex", + flexDirection: "row", + justifyContent: "flex-end", + alignItems: "center", + margin: `${constants.generalUnit * 3}px 0px` + }, + divider: { + margin: `${constants.generalUnit}px 0` + }, + textButton: { + color: palette.primary.background, + cursor: "pointer", + textDecoration: "underline" + }, + creditCardIcon: { + marginRight: constants.generalUnit, + fill: palette.additional["gray"][9] + }, + featuresBox: { + marginTop: constants.generalUnit, + marginBottom: constants.generalUnit * 2 + }, + creditCardRow: { + display: "flex", + alignItems: "center", + marginTop: constants.generalUnit, + marginBottom: constants.generalUnit + }, + featureSeparator: { + marginBottom: constants.generalUnit + }, + error: { + marginTop: constants.generalUnit, + color: palette.error.main + } + }) +) + +interface IConfirmPlan { + plan: Product + planPrice: ProductPrice + onClose: () => void + goToSelectPlan: () => void + goToPlanDetails: () => void + goToPaymentMethod: () => void + onChangeSubscription: () => void + loadingChangeSubscription: boolean + isSubscriptionError: boolean +} + +const ConfirmPlan = ({ + plan, + onClose, + planPrice, + goToSelectPlan, + goToPlanDetails, + goToPaymentMethod, + onChangeSubscription, + loadingChangeSubscription, + isSubscriptionError +}: IConfirmPlan) => { + const classes = useStyles() + const { defaultCard } = useBilling() + const currentPlanStorage = formatBytes(Number(planPrice?.metadata?.storage_size_bytes), 2) + + return ( +
+ + + Confirm plan change + + +
+ + {plan.name} + +
+ + Edit plan + +
+
+
+ + Features + +
+ + {planPrice?.metadata?.storage_size_bytes + ? {currentPlanStorage} of storage + : plan.description + } + + + {plan.description} + +
+
+ +
+ + Payment method + +
+ + Edit payment method + +
+
+ {defaultCard && +
+ + + •••• •••• •••• {defaultCard.last_four_digit} + +
+ } +
+ + Billing start time + +
+ + {dayjs().format("DD MMM YYYY")} + +
+
+ +
+ + Total + +
+ + {planPrice.unit_amount ? planPrice.currency : ""} {planPrice.unit_amount} + + {planPrice.recurring.interval === "month" ? t`/month` : t`/year`} + + +
+
+ {isSubscriptionError && + + Failed to change subscription + + } +
+
+ + +
+
+
+ ) +} + +export default ConfirmPlan \ No newline at end of file diff --git a/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/ChangePlan/PaymentMethod.tsx b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/ChangePlan/PaymentMethod.tsx new file mode 100644 index 0000000000..0271261699 --- /dev/null +++ b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/ChangePlan/PaymentMethod.tsx @@ -0,0 +1,190 @@ +import React, { useEffect, useState } from "react" +import { makeStyles, createStyles } from "@chainsafe/common-theme" +import { CSFTheme } from "../../../../../Themes/types" +import { Breadcrumb, Button, Divider, RadioInput, Typography } from "@chainsafe/common-components" +import { t, Trans } from "@lingui/macro" +import AddCard from "../AddCard/AddCard" +import { useBilling } from "../../../../../Contexts/BillingContext" + +const useStyles = makeStyles(({ constants, palette }: CSFTheme) => + createStyles({ + root: { + margin: `${constants.generalUnit * 2}px ${constants.generalUnit * 2}px` + }, + heading: { + marginTop: constants.generalUnit * 3, + marginBottom: constants.generalUnit * 2 + }, + subHeading: { + marginBottom: constants.generalUnit * 2, + color: palette.additional["gray"][8] + }, + boldText: { + fontWeight: "bold" + }, + normalWeightText: { + fontWeight: "normal" + }, + rowBox: { + display: "flex", + justifyContent: "space-between" + }, + pushRightBox: { + display: "flex", + flexDirection: "column", + alignItems: "flex-end", + flex: 1 + }, + buttons: { + display: "flex", + flexDirection: "row", + alignItems: "center", + justifyContent: "flex-end", + "& > *": { + marginLeft: constants.generalUnit + } + }, + bottomSection: { + display: "flex", + flexDirection: "row", + justifyContent: "flex-end", + alignItems: "center", + marginTop: constants.generalUnit * 4, + marginBottom: constants.generalUnit * 3 + }, + divider: { + margin: `${constants.generalUnit}px 0` + }, + radioLabel: { + fontSize: 14 + }, + textButton: { + color: palette.primary.background, + cursor: "pointer" + }, + linkButton: { + textDecoration: "underline", + cursor: "pointer" + }, + addCardWrapper: { + padding: `${constants.generalUnit * 2}px 0px` + }, + footer: { + marginTop: constants.generalUnit * 2 + } + }) +) + +interface IPaymentMethodProps { + onClose: () => void + goToSelectPlan: () => void + goToPlanDetails: () => void + onSelectPaymentMethod: () => void +} + +const PlanDetails = ({ onClose, goToSelectPlan, goToPlanDetails, onSelectPaymentMethod }: IPaymentMethodProps) => { + const classes = useStyles() + const [paymentMethod, setPaymentMethod] = useState<"creditCard" | "crypto" | undefined>() + const [view, setView] = useState<"selectPaymentMethod" | "addCard">("selectPaymentMethod") + const { defaultCard } = useBilling() + + useEffect(() => { + if (defaultCard) { + setPaymentMethod("creditCard") + } + }, [defaultCard]) + + return ( +
+ + + Select payment method + + + {view === "addCard" && This card will become your default payment method} + + + {view === "selectPaymentMethod" && <> +
+ setPaymentMethod("creditCard")} + checked={paymentMethod === "creditCard"} + labelClassName={classes.radioLabel} + disabled={!defaultCard} + /> + setView("addCard")} + > + {defaultCard + ? Update credit card + : Add credit card + } + +
+ + setPaymentMethod("crypto")} + checked={paymentMethod === "crypto"} + labelClassName={classes.radioLabel} + disabled={true} + /> + + } + {view === "addCard" &&
+ setView("selectPaymentMethod")} + goBack={() => setView("selectPaymentMethod")} + /> +
+ } + +
+
+ + +
+
+
+ ) +} + +export default PlanDetails \ No newline at end of file diff --git a/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/ChangePlan/PlanDetails.tsx b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/ChangePlan/PlanDetails.tsx new file mode 100644 index 0000000000..f764ff5137 --- /dev/null +++ b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/ChangePlan/PlanDetails.tsx @@ -0,0 +1,226 @@ +import React, { useState } from "react" +import { makeStyles, createStyles } from "@chainsafe/common-theme" +import { CSFTheme } from "../../../../../Themes/types" +import { Product, ProductPrice } from "@chainsafe/files-api-client" +import { Breadcrumb, Button, Divider, formatBytes, ToggleSwitch, Typography } from "@chainsafe/common-components" +import { t, Trans } from "@lingui/macro" +import dayjs from "dayjs" + +const useStyles = makeStyles(({ constants }: CSFTheme) => + createStyles({ + root: { + margin: `${constants.generalUnit * 2}px ${constants.generalUnit * 2}px` + }, + heading: { + marginTop: constants.generalUnit * 3, + marginBottom: constants.generalUnit + }, + subheading: { + marginBottom: constants.generalUnit * 3 + }, + boldText: { + fontWeight: "bold" + }, + normalWeightText: { + fontWeight: "normal" + }, + featureSeparator: { + marginBottom: constants.generalUnit + }, + rowBox: { + display: "flex", + padding: `${constants.generalUnit * 0.5}px 0px` + }, + middleRowBox: { + display: "flex", + alignItems: "center", + padding: `${constants.generalUnit * 0.5}px 0px` + }, + pushRightBox: { + display: "flex", + flexDirection: "column", + alignItems: "flex-end", + flex: 1 + }, + buttons: { + display: "flex", + flexDirection: "row", + alignItems: "center", + justifyContent: "flex-end", + "& > *": { + marginLeft: constants.generalUnit + } + }, + bottomSection: { + display: "flex", + flexDirection: "row", + justifyContent: "flex-end", + alignItems: "center", + margin: `${constants.generalUnit * 3}px 0px` + }, + divider: { + margin: `${constants.generalUnit}px 0` + } + }) +) + +interface IPlanDetails { + plan: Product + onClose: () => void + goToSelectPlan: () => void + onSelectPlanPrice: (planPrice: ProductPrice) => void +} + +const PlanDetails = ({ plan, onClose, goToSelectPlan, onSelectPlanPrice }: IPlanDetails) => { + const classes = useStyles() + const monthlyPrice = plan.prices.find((price) => price.recurring.interval === "month") + const yearlyPrice = plan.prices.find((price) => price.recurring.interval === "year") + const currentPlanStorage = formatBytes(Number(monthlyPrice?.metadata?.storage_size_bytes), 2) + + const [billingPeriod, setBillingPeriod] = useState<"monthly" | "yearly">(monthlyPrice ? "monthly" : "yearly") + + const handleSelectPlan = () => { + if(billingPeriod === "monthly" && monthlyPrice) { + onSelectPlanPrice(monthlyPrice) + } else if (yearlyPrice) { + onSelectPlanPrice(yearlyPrice) + } + } + + return ( +
+ + + {plan.name} + + + You get access to these features right now. + + +
+ + Features + +
+ + {monthlyPrice?.metadata?.storage_size_bytes + ? {currentPlanStorage} of storage + : plan.description + } + + + {plan.description} + +
+
+ +
+ + Billing start time + +
+ + {dayjs().format("DD MMM YYYY")} + +
+
+ + {monthlyPrice && yearlyPrice && + <> +
+ + {billingPeriod === "monthly" + ? Monthly billing + : Yearly billing + } + +
+ setBillingPeriod(billingPeriod === "monthly" ? "yearly" : "monthly")} + /> +
+
+ + + } +
+ + Total + +
+ + {billingPeriod === "monthly" + ? `${monthlyPrice?.unit_amount ? monthlyPrice?.currency : ""} ${monthlyPrice?.unit_amount}` + : `${yearlyPrice?.unit_amount ? yearlyPrice?.currency : ""} ${yearlyPrice?.unit_amount}` + }{billingPeriod ? t`/month` : t`/year`} + +
+
+
+
+ + +
+
+
+ ) +} + +export default PlanDetails \ No newline at end of file diff --git a/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/ChangePlan/PlanSuccess.tsx b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/ChangePlan/PlanSuccess.tsx new file mode 100644 index 0000000000..b55c30d97f --- /dev/null +++ b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/ChangePlan/PlanSuccess.tsx @@ -0,0 +1,174 @@ +import React from "react" +import { makeStyles, createStyles } from "@chainsafe/common-theme" +import { CSFTheme } from "../../../../../Themes/types" +import { Product, ProductPrice } from "@chainsafe/files-api-client" +import { Button, CheckCircleIcon, CheckIcon, Divider, formatBytes, Link, Typography } from "@chainsafe/common-components" +import { Trans } from "@lingui/macro" +import { ROUTE_LINKS } from "../../../../FilesRoutes" +import clsx from "clsx" + +const useStyles = makeStyles(({ constants, palette }: CSFTheme) => + createStyles({ + root: { + margin: `${constants.generalUnit * 2}px ${constants.generalUnit * 2}px` + }, + headingBadge: { + color: palette.additional["gray"][7], + marginTop: constants.generalUnit * 3 + }, + headingBox: { + marginTop: constants.generalUnit * 2, + marginBottom: constants.generalUnit * 2 + }, + rowBox: { + display: "flex", + padding: `${constants.generalUnit * 0.5}px 0px` + }, + middleRowBox: { + display: "flex", + alignItems: "center" + }, + featuresTitle: { + marginTop: constants.generalUnit * 2, + marginBottom: constants.generalUnit * 2 + }, + pushRightBox: { + display: "flex", + flexDirection: "column", + alignItems: "flex-start", + marginLeft: constants.generalUnit * 4 + }, + buttons: { + display: "flex", + flexDirection: "row", + alignItems: "center", + justifyContent: "flex-end", + "& > *": { + marginLeft: constants.generalUnit + } + }, + bottomSection: { + display: "flex", + flexDirection: "row", + justifyContent: "flex-end", + alignItems: "center", + margin: `${constants.generalUnit * 3}px 0px` + }, + divider: { + margin: `${constants.generalUnit}px 0` + }, + featuresBox: { + marginTop: constants.generalUnit, + marginBottom: constants.generalUnit * 2 + }, + featureTickBox: { + marginBottom: constants.generalUnit + }, + textLink: { + color: palette.primary.background + }, + checkCircleIcon: { + fill: palette.additional["gray"][7], + marginLeft: constants.generalUnit + }, + tickIcon: { + fill: palette.success.main + }, + invoiceText: { + marginTop: constants.generalUnit * 3, + marginBottom: constants.generalUnit + } + }) +) + +interface IPlanSuccess { + plan: Product + planPrice: ProductPrice + onClose: () => void +} + +const PlanSuccess = ({ plan, onClose, planPrice }: IPlanSuccess) => { + const classes = useStyles() + const newPlanCapacity = formatBytes(Number(planPrice?.metadata?.storage_size_bytes), 2) + + return ( +
+ + Confirmation + +
+ + Plan changed successfully + + +
+ +
+ + You now have: + +
+
+ + + {planPrice?.metadata?.storage_size_bytes + ? {newPlanCapacity} of storage + : plan.description + } + +
+
+ + + {plan.description} + +
+
+
+
+ + Access your billing history in settings or view your   + + invoices here + + +
+
+
+ +
+
+
+ ) +} + +export default PlanSuccess \ No newline at end of file diff --git a/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/ChangePlan/SelectPlan.tsx b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/ChangePlan/SelectPlan.tsx new file mode 100644 index 0000000000..30229862f1 --- /dev/null +++ b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/ChangePlan/SelectPlan.tsx @@ -0,0 +1,315 @@ +import React, { useState } from "react" +import { makeStyles, createStyles, useThemeSwitcher } from "@chainsafe/common-theme" +import clsx from "clsx" +import { Button, ExternalSvg, formatBytes, Loading, Typography } from "@chainsafe/common-components" +import { t, Trans } from "@lingui/macro" +import { CSFTheme } from "../../../../../Themes/types" +import { useBilling } from "../../../../../Contexts/BillingContext" +import { Product } from "@chainsafe/files-api-client" +import { ROUTE_LINKS } from "../../../../FilesRoutes" + +const useStyles = makeStyles(({ breakpoints, constants, palette, typography }: CSFTheme) => + createStyles({ + root: { + margin: `${constants.generalUnit * 2}px ${constants.generalUnit * 3}px`, + [breakpoints.down("md")]: { + margin: `${constants.generalUnit * 2}px ${constants.generalUnit * 2}px` + } + }, + header: { + display: "flex", + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + marginTop: constants.generalUnit * 2 + }, + loadingContainer: { + margin: `${constants.generalUnit * 4}px 0`, + display: "flex", + justifyContent: "center" + }, + panels: { + display: "grid", + gridColumnGap: constants.generalUnit * 1.5, + gridRowGap: constants.generalUnit * 1.5, + gridTemplateColumns: "1fr 1fr 1fr", + marginTop: constants.generalUnit * 2, + marginBottom: constants.generalUnit * 4, + [breakpoints.down("md")]: { + gridTemplateColumns: "1fr", + marginTop: constants.generalUnit * 3 + } + }, + planBox: { + border: `2px solid ${palette.additional["gray"][5]}`, + padding: `${constants.generalUnit * 3}px ${constants.generalUnit * 4}px `, + display: "flex", + flexDirection: "column", + alignItems: "center", + borderRadius: 5, + [breakpoints.down("md")]: { + flexDirection: "row", + padding: `${constants.generalUnit * 2}px ${constants.generalUnit * 2}px `, + justifyContent: "space-between" + }, + "&.active": { + borderColor: palette.primary.background + } + }, + priceSpace: { + height: 22 + }, + link: { + display: "flex", + justifyContent: "flex-start", + alignItems: "center", + "& svg": { + marginLeft: constants.generalUnit, + stroke: palette.additional.gray[10], + width: constants.generalUnit * 2, + height: constants.generalUnit * 2 + } + }, + buttons: { + display: "flex", + flexDirection: "row", + alignItems: "center", + justifyContent: "flex-end", + "& > *": { + marginLeft: constants.generalUnit + }, + [breakpoints.down("md")]: { + flex: 1 + } + }, + bottomSection: { + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center" + }, + planTitle: { + fontWeight: "bold", + marginBottom: constants.generalUnit + }, + priceSubtitle: { + ...typography.body2, + color: palette.additional["gray"][9] + }, + description: { + margin: `${constants.generalUnit * 3}px 0`, + [breakpoints.down("md")]: { + margin: 0 + } + }, + priceYearlyTitle: { + fontWeight: "bold", + [breakpoints.down("md")]: { + fontWeight: "normal", + marginTop: constants.generalUnit + } + }, + mobilePriceBox: { + display: "flex", + flexDirection: "column", + alignItems: "end", + height: "100%" + }, + selectButton: { + marginLeft: constants.generalUnit + } + }) +) + +interface ISelectPlan { + className?: string + plans?: Product[] + onClose: () => void + onSelectPlan: (plan: Product) => void +} + +const SelectPlan = ({ onClose, className, onSelectPlan, plans }: ISelectPlan) => { + const classes = useStyles() + const { currentSubscription } = useBilling() + const { desktop } = useThemeSwitcher() + const [tempSelectedPlan, setTempSelectedPlan] = useState() + + return ( +
+
+ + Switch Plans + +
+ {!plans && ( +
+ +
+ )} +
+ {plans && plans.map((plan) => { + const monthly = plan.prices.find((price) => price.recurring.interval === "month") + const yearly = plan.prices.find((price) => price.recurring.interval === "year") + const isPlanSelectable = plan.id !== currentSubscription?.product.id && + (monthly?.is_update_allowed || yearly?.is_update_allowed) + const currentStorage = formatBytes(Number(monthly?.metadata?.storage_size_bytes), 2) + + + return desktop ? ( +
+ + {plan.name} + + {monthly && ( + + {monthly.unit_amount + ? <> + {monthly.currency.toUpperCase()} {monthly.unit_amount} + + /month + + + : t`Free`} + + )} + {monthly && yearly + ? ( + + {yearly.currency.toUpperCase()} {yearly.unit_amount} + + /year + + + ) + :
+ } + + { + monthly?.metadata?.storage_size_bytes + ? {currentStorage} of storage + : plan.description + } + + +
+ ) + : ( +
isPlanSelectable && setTempSelectedPlan(plan)} + key={`plan-${plan.id}`} + > +
+ + {plan.name} + + + { + monthly?.metadata?.storage_size_bytes + ? {currentStorage} of storage + : plan.description + } + +
+
+ {monthly && ( + + {monthly.unit_amount + ? <> + {monthly.currency.toUpperCase()} {monthly.unit_amount} + /month + + : t`Free`} + + )} + {monthly && yearly + ? ( + + {yearly.currency.toUpperCase()} {yearly.unit_amount} + /year + + ) + :
+ } +
+
+ )})} +
+
+ {desktop && ( + + + Not sure what to pick? Learn more about our plans + + + + )} +
+ + {!desktop && ( + + )} +
+
+
+ ) +} + +export default SelectPlan \ No newline at end of file diff --git a/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/CurrentCard.tsx b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/CurrentCard.tsx new file mode 100644 index 0000000000..30d14b4af9 --- /dev/null +++ b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/CurrentCard.tsx @@ -0,0 +1,118 @@ +import React, { useState } from "react" +import { Typography, CreditCardIcon, Button, Dialog } from "@chainsafe/common-components" +import { makeStyles, ITheme, createStyles } from "@chainsafe/common-theme" +import { t, Trans } from "@lingui/macro" +import { useBilling } from "../../../../Contexts/BillingContext" +import AddCardModal from "./AddCard/AddCardModal" + +const useStyles = makeStyles(({ constants, palette }: ITheme) => + createStyles({ + container: { + padding: constants.generalUnit, + margin: `${constants.generalUnit * 1.5}px 0` + }, + noCard: { + margin: `${constants.generalUnit * 2}px 0` + }, + cardDetailsContainer: { + display: "flex", + margin: `${constants.generalUnit * 2}px 0` + }, + creditCardIcon: { + marginRight: constants.generalUnit, + fill: palette.additional["gray"][9] + }, + linkButton: { + textDecoration: "underline", + cursor: "pointer", + margin: `0 ${constants.generalUnit * 2}px` + }, + confirmDeletionDialog: { + top: "50%" + } + }) +) + +const CurrentCard: React.FC = () => { + const classes = useStyles() + const [isAddCardModalOpen, setIsAddCardModalOpen ] = useState(false) + const { defaultCard, deleteCard, refreshDefaultCard } = useBilling() + const [isDeleteCardModalOpen, setIsDeleteCardModalOpen] = useState(false) + const [isDeleteCardLoading, setIsDeleteCardLoading] = useState(false) + + const onRemoveCard = () => { + if (!defaultCard) return + setIsDeleteCardLoading(true) + deleteCard(defaultCard) + .then(refreshDefaultCard) + .catch(console.error) + .finally(() => { + setIsDeleteCardModalOpen(false) + setIsDeleteCardLoading(false) + }) + } + + return ( + <> +
+
+ + Credit card saved + +
+ {defaultCard + ?
+ + + •••• •••• •••• {defaultCard.last_four_digit} + + setIsDeleteCardModalOpen(true)} + > + Remove + +
+ : + No Card + + } + +
+ setIsAddCardModalOpen(false)} + /> + setIsDeleteCardModalOpen(false)} + accept={onRemoveCard} + requestMessage={t`Are you sure? This will delete your default payment method.`} + rejectText={t`Cancel`} + acceptText={t`Confirm`} + acceptButtonProps={{ loading: isDeleteCardLoading, disabled: isDeleteCardLoading }} + rejectButtonProps={{ disabled: isDeleteCardLoading }} + injectedClass={{ inner: classes.confirmDeletionDialog }} + /> + + ) +} + +export default CurrentCard diff --git a/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/CurrentPlan.tsx b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/CurrentPlan.tsx new file mode 100644 index 0000000000..3f12d30d9c --- /dev/null +++ b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/CurrentPlan.tsx @@ -0,0 +1,123 @@ +import React, { useState } from "react" +import { + Button, + formatBytes, + Loading, + ProgressBar, + Typography +} from "@chainsafe/common-components" +import { makeStyles, ITheme, createStyles } from "@chainsafe/common-theme" +import { useFiles } from "../../../../Contexts/FilesContext" +import { t, Trans } from "@lingui/macro" +import clsx from "clsx" +import { useBilling } from "../../../../Contexts/BillingContext" +import ChangeProductModal from "./ChangePlan/ChangePlanModal" + +const useStyles = makeStyles(({ breakpoints, constants }: ITheme) => + createStyles({ + root: { + padding: constants.generalUnit, + maxWidth: 240, + "& h2, & h5": { + marginBottom: constants.generalUnit, + fontWeight: 400 + }, + "& h5": { + } + }, + spaceUsedBox: { + [breakpoints.down("md")]: { + marginBottom: constants.generalUnit, + width: "inherit" + } + }, + usageBar: { + maxWidth: "70%", + marginBottom: constants.generalUnit, + marginTop: constants.generalUnit, + overflow: "hidden" + }, + buttons: { + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + "& > *:first-child": { + marginRight: constants.generalUnit + } + }, + link: { + display: "block", + width: "100%", + textDecoration: "none" + } + }) +) + +interface ICurrentProduct { + className?: string +} + +const CurrentProduct = ({ className }: ICurrentProduct) => { + const classes = useStyles() + const { storageSummary } = useFiles() + const { currentSubscription } = useBilling() + const [isChangeProductModalVisible, setChangeProductModalVisible] = useState(false) + + return (
+ + Your plan + + { + currentSubscription + ? + {currentSubscription?.product.name} + + : + } + {storageSummary && + <> +
+ + {t`${formatBytes(storageSummary.used_storage, 2)} of ${formatBytes( + storageSummary.total_storage, 2 + )} used`} ({((storageSummary.used_storage / storageSummary.total_storage) * 100).toFixed(1)}%) + + +
+
+ +
+ { + isChangeProductModalVisible && ( setChangeProductModalVisible(false)} + />) + } + + } + +
) +} + +export default CurrentProduct \ No newline at end of file diff --git a/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/index.tsx b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/index.tsx new file mode 100644 index 0000000000..8e314af8a6 --- /dev/null +++ b/packages/files-ui/src/Components/Modules/Settings/SubscriptionTab/index.tsx @@ -0,0 +1,45 @@ +import React from "react" +import CurrentCard from "./CurrentCard" +import { Divider, Typography } from "@chainsafe/common-components" +import { makeStyles, createStyles, ITheme } from "@chainsafe/common-theme" +import { Trans } from "@lingui/macro" +import BillingHistory from "./BillingHistory" +import { Elements } from "@stripe/react-stripe-js" +import { loadStripe } from "@stripe/stripe-js" +import CurrentProduct from "./CurrentPlan" + +const useStyles = makeStyles(({ breakpoints, constants }: ITheme) => + createStyles({ + root: { + [breakpoints.down("sm")]: { + paddingLeft: constants.generalUnit, + paddingRight: constants.generalUnit + } + } + }) +) + +const stripePromise = loadStripe(process.env.REACT_APP_STRIPE_PK || "") + +const PlanView: React.FC = () => { + const classes = useStyles() + + return ( + +
+ + Payment and Subscriptions + + + + + +
+
+ ) +} + +export default PlanView diff --git a/packages/files-ui/src/Components/Modules/Settings/index.tsx b/packages/files-ui/src/Components/Modules/Settings/index.tsx index 5b2d70bbe9..a4e0aa90d4 100644 --- a/packages/files-ui/src/Components/Modules/Settings/index.tsx +++ b/packages/files-ui/src/Components/Modules/Settings/index.tsx @@ -1,10 +1,10 @@ -import React, { useCallback, useMemo } from "react" -import Profile from "./Profile" -import { Tabs, +import React, { useCallback } from "react" +import ProfileTab from "./ProfileTab" +import { + Tabs, TabPane as TabPaneOrigin, - Typography, Divider, - Breadcrumb, - Crumb, + Typography, + Divider, useParams, useHistory, ITabPaneProps, @@ -12,18 +12,19 @@ import { Tabs, LockIcon } from "@chainsafe/common-components" import { makeStyles, ITheme, createStyles, useThemeSwitcher } from "@chainsafe/common-theme" -import { ROUTE_LINKS, SettingsPath, SETTINGS_BASE } from "../../FilesRoutes" +import { ROUTE_LINKS, SettingsPath } from "../../FilesRoutes" import { t, Trans } from "@lingui/macro" -// import Plan from "./Plan" -import { ProfileIcon } from "@chainsafe/common-components" +import SubscriptionTab from "./SubscriptionTab" +import { ProfileIcon, SubscriptionPlanIcon } from "@chainsafe/common-components" import clsx from "clsx" -import Security from "./Security" +import SecurityTab from "./SecurityTab" const TabPane = (props: ITabPaneProps) => TabPaneOrigin(props) const useStyles = makeStyles(({ constants, breakpoints, palette }: ITheme) => createStyles({ title: { marginTop: constants.generalUnit, + cursor: "pointer", [breakpoints.down("md")]: { fontSize: 20, lineHeight: "28px", @@ -56,7 +57,7 @@ const useStyles = makeStyles(({ constants, breakpoints, palette }: ITheme) => tabsContainer: { borderRadius: 10, backgroundColor: palette.additional["gray"][3], - marginTop: constants.generalUnit * 4, + marginTop: constants.generalUnit * 2, [breakpoints.down("md")]: { borderRadius: 0, marginTop: 0 @@ -64,16 +65,7 @@ const useStyles = makeStyles(({ constants, breakpoints, palette }: ITheme) => }, tabPane: { flex: 1, - padding: `${constants.generalUnit * 4}px ${constants.generalUnit * 4}px`, - "&.securityPane": { - [breakpoints.down("lg")]: { - paddingLeft: constants.generalUnit, - paddingRight: constants.generalUnit - } - }, - [breakpoints.down("md")]: { - padding: `${constants.generalUnit * 2}px` - }, + padding: `${constants.generalUnit * 2}px ${constants.generalUnit * 2}px`, [breakpoints.down("md")]: { padding: `${constants.generalUnit * 2}px 0` } @@ -136,32 +128,22 @@ const useStyles = makeStyles(({ constants, breakpoints, palette }: ITheme) => const Settings: React.FC = () => { const { desktop } = useThemeSwitcher() - const { path = desktop ? "profile" : "" } = useParams<{path: SettingsPath}>() + const { path = desktop ? "profile" : undefined } = useParams<{path: SettingsPath}>() const classes = useStyles() - const { redirect } = useHistory() - + const { redirect, history } = useHistory() const onSelectTab = useCallback( - (key: string) => redirect(`${SETTINGS_BASE}/${key}`) + (key: SettingsPath) => redirect(ROUTE_LINKS.SettingsPath(key)) , [redirect]) - const crumbs: Crumb[] = useMemo(() => [ - { - text: t`Settings` - } - ], []) - return (
- redirect(ROUTE_LINKS.Drive(""))} - /> history.push(ROUTE_LINKS.SettingsDefault)} > Settings @@ -192,21 +174,27 @@ const Settings: React.FC = () => { tabKey="profile" testId="profile-tab" > - + } iconRight={} title={t`Security`} tabKey="security" testId="security-tab" > - + + + } + iconRight={} + > + - {/* - - */}
} diff --git a/packages/files-ui/src/Components/Pages/BillingHistory.tsx b/packages/files-ui/src/Components/Pages/BillingHistory.tsx new file mode 100644 index 0000000000..116966c28b --- /dev/null +++ b/packages/files-ui/src/Components/Pages/BillingHistory.tsx @@ -0,0 +1,124 @@ +import React, { useEffect, useState } from "react" +import { makeStyles, createStyles } from "@chainsafe/common-theme" +import { CSFTheme } from "../../Themes/types" +import { useFilesApi } from "../../Contexts/FilesApiContext" +import { InvoiceResponse } from "@chainsafe/files-api-client" +import { Table, TableHead, TableHeadCell, TableBody, TableRow, TableCell, Typography, Loading } from "@chainsafe/common-components" +import { Trans } from "@lingui/macro" +import dayjs from "dayjs" + +const useStyles = makeStyles( + ({ constants, breakpoints }: CSFTheme) => + createStyles({ + heading: { + marginBottom: constants.generalUnit * 4, + [breakpoints.down("md")]: { + marginBottom: constants.generalUnit * 2 + } + }, + loader: { + marginTop: constants.generalUnit + }, + centered: { + textAlign: "center" + }, + root: { + [breakpoints.down("md")]: { + padding: `0 ${constants.generalUnit}px` + } + } + }) +) + +const BillingHistory = () => { + const classes = useStyles() + const { filesApiClient } = useFilesApi() + const [invoices, setInvoices] = useState([]) + const [subscriptionId, setSubscriptionId] = useState("") + const [isLoading, setIsLoading] = useState(true) + + useEffect(() => { + filesApiClient.getCurrentSubscription() + .then((subscription) => { + setSubscriptionId(subscription.id) + }) + .catch((e) => { + console.error(e) + setIsLoading(false) + }) + }, [filesApiClient]) + + useEffect(() => { + if(!subscriptionId) return + filesApiClient.getAllInvoices(subscriptionId) + .then(({ invoices }) => { + setInvoices(invoices) + }) + .catch(console.error) + .finally(() => setIsLoading(false)) + }, [filesApiClient, subscriptionId]) + + return ( +
+ + Billing history + + {isLoading && ( +
+ +
+ )} + {!invoices.length && !isLoading && ( +
+ + No invoice found + +
+ )} + {!!invoices.length && ( + + + + Date + Amount + Method + Receipt + + + + {invoices.map(({ paid_on, amount, payment_method, currency, uuid }, index) => + + {dayjs(paid_on).format("ddd D MMMM h:mm a")} + {amount} {currency} + {payment_method} + {uuid} + )} + +
+ )} + +
+ ) +} + +export default BillingHistory diff --git a/packages/files-ui/src/Components/Pages/PurchasePlanPage.tsx b/packages/files-ui/src/Components/Pages/PurchasePlanPage.tsx deleted file mode 100644 index c33c725537..0000000000 --- a/packages/files-ui/src/Components/Pages/PurchasePlanPage.tsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from "react" -import PurchasePlan from "../Modules/Settings/PurchasePlan" - -const PurchasePlanPage = () => { - return -} - -export default PurchasePlanPage diff --git a/packages/files-ui/src/Contexts/BillingContext.tsx b/packages/files-ui/src/Contexts/BillingContext.tsx index 331ad01100..bd2908f68e 100644 --- a/packages/files-ui/src/Contexts/BillingContext.tsx +++ b/packages/files-ui/src/Contexts/BillingContext.tsx @@ -1,72 +1,140 @@ import * as React from "react" -// import { useFilesApi } from "./FilesApiContext" -import axios, { AxiosResponse } from "axios" +import { useFilesApi } from "./FilesApiContext" +import { ReactNode, useEffect, useState } from "react" +import { Card, CurrentSubscription, Product } from "@chainsafe/files-api-client" +import { useCallback } from "react" +import { t } from "@lingui/macro" +import { PaymentMethod } from "@stripe/stripe-js" +import { useFiles } from "./FilesContext" type BillingContextProps = { - children: React.ReactNode | React.ReactNode[] + children: ReactNode | ReactNode[] } interface IBillingContext { - addCard(cardToken: string): Promise - getCardTokenFromStripe( - card: ICard, - stripePk: string, - ): Promise> + defaultCard: Card | undefined + refreshDefaultCard: () => void + currentSubscription: CurrentSubscription | undefined + changeSubscription: (newPriceId: string) => Promise + fetchCurrentSubscription: () => void + getAvailablePlans: () => Promise + deleteCard: (card: Card) => Promise + updateDefaultCard: (id: PaymentMethod["id"]) => Promise +} + +const ProductMapping: {[key: string]: { + name: string + description: string +}} = { + prod_JwRu6Ph25b1f2O: { + name: t`Free plan`, + description: t`This is the free product.` + }, + prod_JwS49Qfnr6vD3K: { + name: t`Standard plan`, + description: t`Standard plan` + }, + prod_JwSGHB8qFx7rRM: { + name: t`Premium plan`, + description: t`Premium plan` + } } const BillingContext = React.createContext( undefined ) -const STRIPE_API = "https://api.stripe.com/v1/tokens" +const BillingProvider = ({ children }: BillingContextProps) => { + const { filesApiClient, isLoggedIn } = useFilesApi() + const { refreshBuckets } = useFiles() + const [currentSubscription, setCurrentSubscription] = useState() + const [defaultCard, setDefaultCard] = useState(undefined) -interface ICard { - cardNumber: string - cardExpiry: string - cardCvc: string -} + const refreshDefaultCard = useCallback(() => { + filesApiClient.getDefaultCard().then((card) => { + setDefaultCard(card) + }).catch((err) => { + console.error(err) + setDefaultCard(undefined) + }) + }, [filesApiClient]) -interface IStripeResponse { - id: string -} + const deleteCard = useCallback((card: Card) => + filesApiClient.deleteCard(card.id) + , [filesApiClient]) -const BillingProvider = ({ children }: BillingContextProps) => { - // const { filesApiClient } = useFilesApi() - - const addCard = async ( - //cardToken: string - ) => { - try { - // await filesApiClient.addCard({ token: cardToken }) - return Promise.resolve() - } catch (error) { - return Promise.reject("There was an error adding card.") + useEffect(() => { + if (isLoggedIn) { + refreshDefaultCard() } - } + }, [refreshDefaultCard, isLoggedIn, filesApiClient]) + + const fetchCurrentSubscription = useCallback(() => { + filesApiClient.getCurrentSubscription() + .then((subscription) => { + subscription.product.name = ProductMapping[subscription.product.id].name + subscription.product.description = ProductMapping[subscription.product.id].description + setCurrentSubscription(subscription) + }) + .catch((error: any) => { + console.error(error) + }) + }, [filesApiClient]) + + useEffect(() => { + if (isLoggedIn && !currentSubscription) { + fetchCurrentSubscription() + } else if (!isLoggedIn) { + setCurrentSubscription(undefined) + } + }, [isLoggedIn, fetchCurrentSubscription, currentSubscription]) - const getCardTokenFromStripe = ( - data: ICard, - stripePk: string - ): Promise> => { - const cardExpiryMonth = data.cardExpiry.split("/")[0]?.trim() - const cardExpiryYear = data.cardExpiry.split("/")[1]?.trim() - - // eslint-disable-next-line max-len - const dataString = `card[number]=${data.cardNumber}&card[exp_month]=${cardExpiryMonth}&card[exp_year]=${cardExpiryYear}&card[cvc]=${data.cardCvc}` - - return axios.post(STRIPE_API, dataString, { - headers: { - Authorization: `Bearer ${stripePk}`, - "Content-Type": "application/x-www-form-urlencoded" - } + const getAvailablePlans = useCallback(() => { + return filesApiClient.getAllProducts() + .then((products) => { + return products.map(product => { + product.name = ProductMapping[product.id].name + product.description = ProductMapping[product.id].description + return product + }) + }) + .catch((error: any) => { + console.error(error) + return [] + }) + }, [filesApiClient]) + + const updateDefaultCard = useCallback((id: PaymentMethod["id"]) => + filesApiClient.updateDefaultCard({ id }) + , [filesApiClient]) + + const changeSubscription = useCallback((newPriceId: string) => { + if (!currentSubscription?.id) return Promise.resolve() + return filesApiClient.updateSubscription(currentSubscription.id, { + price_id: newPriceId, + payment_method: "stripe" }) - } + .then(() => { + fetchCurrentSubscription() + refreshBuckets() + }) + .catch((error) => { + console.error(error) + return Promise.reject() + }) + }, [filesApiClient, currentSubscription, fetchCurrentSubscription, refreshBuckets]) return ( {children} diff --git a/packages/files-ui/src/Contexts/FilesApiContext.tsx b/packages/files-ui/src/Contexts/FilesApiContext.tsx index 9f8286cd52..02bf865579 100644 --- a/packages/files-ui/src/Contexts/FilesApiContext.tsx +++ b/packages/files-ui/src/Contexts/FilesApiContext.tsx @@ -34,6 +34,7 @@ type FilesApiContext = { validateMasterPassword: (candidatePassword: string) => Promise encryptedEncryptionKey?: string isMasterPasswordSet: boolean + accountRestricted?: boolean } const FilesApiContext = React.createContext(undefined) @@ -62,6 +63,7 @@ const FilesApiProvider = ({ apiUrl, withLocalStorage = true, children }: FilesAp // access tokens const [accessToken, setAccessToken] = useState(undefined) const [secured, setSecured] = useState(undefined) + const [accountRestricted, setAccountRestricted] = useState(false) const [refreshToken, setRefreshToken] = useState(undefined) const [decodedRefreshToken, setDecodedRefreshToken] = useState< { exp: number; enckey?: string; mps?: string; uuid: string } | undefined @@ -216,7 +218,7 @@ const FilesApiProvider = ({ apiUrl, withLocalStorage = true, children }: FilesAp useEffect(() => { if (accessToken && accessToken.token && filesApiClient) { filesApiClient?.setToken(accessToken.token) - const decodedAccessToken = jwtDecode<{ perm: { secured?: string } }>( + const decodedAccessToken = jwtDecode<{ perm: { secured?: string; files?: string } }>( accessToken.token ) if (decodedAccessToken.perm.secured === "true") { @@ -224,6 +226,11 @@ const FilesApiProvider = ({ apiUrl, withLocalStorage = true, children }: FilesAp } else { setSecured(false) } + if (decodedAccessToken.perm.files === "restricted") { + setAccountRestricted(true) + } else { + setAccountRestricted(false) + } } }, [accessToken, filesApiClient]) @@ -308,7 +315,8 @@ const FilesApiProvider = ({ apiUrl, withLocalStorage = true, children }: FilesAp thresholdKeyLogin, secureThresholdKeyAccount, encryptedEncryptionKey: decodedRefreshToken?.enckey, - isMasterPasswordSet: !!decodedRefreshToken?.mps + isMasterPasswordSet: !!decodedRefreshToken?.mps, + accountRestricted }} > {children} diff --git a/packages/files-ui/src/Contexts/FilesContext.tsx b/packages/files-ui/src/Contexts/FilesContext.tsx index 3496777a16..fce4ef4b02 100644 --- a/packages/files-ui/src/Contexts/FilesContext.tsx +++ b/packages/files-ui/src/Contexts/FilesContext.tsx @@ -482,6 +482,8 @@ const FilesProvider = ({ children }: FilesContextProps) => { } } catch (error) { if (axios.isCancel(error)) { + // Do not propogate cancellation up the stack to ensure that the next + // download starts correctly return Promise.reject() } else { console.error(error) diff --git a/packages/files-ui/src/Contexts/UserContext.tsx b/packages/files-ui/src/Contexts/UserContext.tsx index cf94ee3430..6a886bfc18 100644 --- a/packages/files-ui/src/Contexts/UserContext.tsx +++ b/packages/files-ui/src/Contexts/UserContext.tsx @@ -52,7 +52,6 @@ const UserProvider = ({ children }: UserContextProps) => { const [localStore, _setLocalStore] = useState() const setLocalStore = useCallback((newData: ILocalStore, method: "update" | "overwrite" = "update") => { - const toStore = method === "update" ? { ...localStore, ...newData } : newData diff --git a/packages/files-ui/src/Media/sharingExplainer/step1.png b/packages/files-ui/src/Media/sharingExplainer/step1.png index 4e25c8fa75..432ce8c0a9 100644 Binary files a/packages/files-ui/src/Media/sharingExplainer/step1.png and b/packages/files-ui/src/Media/sharingExplainer/step1.png differ diff --git a/packages/files-ui/src/Media/sharingExplainer/step2.png b/packages/files-ui/src/Media/sharingExplainer/step2.png index 6fd35fe901..d689344736 100644 Binary files a/packages/files-ui/src/Media/sharingExplainer/step2.png and b/packages/files-ui/src/Media/sharingExplainer/step2.png differ diff --git a/packages/files-ui/src/Media/sharingExplainer/step3.png b/packages/files-ui/src/Media/sharingExplainer/step3.png index bd43adaa93..cacabf0569 100644 Binary files a/packages/files-ui/src/Media/sharingExplainer/step3.png and b/packages/files-ui/src/Media/sharingExplainer/step3.png differ diff --git a/packages/files-ui/src/Themes/Constants.ts b/packages/files-ui/src/Themes/Constants.ts index e36b09ecaa..d330f074a7 100644 --- a/packages/files-ui/src/Themes/Constants.ts +++ b/packages/files-ui/src/Themes/Constants.ts @@ -11,7 +11,9 @@ export const UI_CONSTANTS = { topPadding: 8 * 3, mobileNavWidth: 8 * 30, headerTopPadding: 8 * 3, - accountControlsPadding: 8 * 7 + accountControlsPadding: 8 * 7, + bottomBannerHeight: 80, + bottomBannerMobileHeight: 130 } export interface CsfColors extends IConstants { @@ -59,6 +61,7 @@ export interface CsfColors extends IConstants { itemColorHover: string itemIconColor: string itemIconColorHover: string + profileButtonShadow: string } createFolder: { backgroundColor: string @@ -167,7 +170,18 @@ export interface CsfColors extends IConstants { color: string backgroundOptionHover: string } + addCard: { + color: string + shadow: string + } cookieBanner: { backgroundColor: string } + changeProduct: { + currentBackground: string + selectedColor: string + currentTag: { + text: string + } + } } \ No newline at end of file diff --git a/packages/files-ui/src/Themes/DarkTheme.ts b/packages/files-ui/src/Themes/DarkTheme.ts index 28ca9b9ab5..6acadb4aa5 100644 --- a/packages/files-ui/src/Themes/DarkTheme.ts +++ b/packages/files-ui/src/Themes/DarkTheme.ts @@ -158,7 +158,8 @@ export const darkTheme = createTheme({ palette: { primary: { main: "var(--gray3)", - hover: "var(--gray10)" + hover: "var(--gray10)", + background: "var(--csf-primary)" }, secondary: { main: "var(--gray10)", @@ -379,7 +380,8 @@ export const darkTheme = createTheme({ itemColor: "var(--gray9)", itemColorHover: "var(--gray9)", itemIconColor: "var(--gray9)", - itemIconColorHover: "var(--gray9)" + itemIconColorHover: "var(--gray9)", + profileButtonShadow: "0px 1px 2px rgba(0, 0, 0, 0.25)" }, createFolder: { backgroundColor: "var(--gray2)", @@ -481,8 +483,19 @@ export const darkTheme = createTheme({ surveyBanner: { color: "var(--gray9)" }, + addCard: { + color: "#DBDBDB", + shadow: "0px 0px 4px rgba(24, 144, 255, 0.5)" + }, cookieBanner: { backgroundColor: "var(--gray9)" + }, + changeProduct: { + currentBackground: "initial", + selectedColor: "var(--csf-primary)", + currentTag: { + text: "var(--gray10)" + } } } as CsfColors) }, diff --git a/packages/files-ui/src/Themes/LightTheme.ts b/packages/files-ui/src/Themes/LightTheme.ts index 90f72c5dfa..95b20987c7 100644 --- a/packages/files-ui/src/Themes/LightTheme.ts +++ b/packages/files-ui/src/Themes/LightTheme.ts @@ -10,7 +10,8 @@ export const lightTheme = createTheme({ themeConfig: { palette: { primary: { - main: "var(--csf-primary)" + main: "var(--csf-primary)", + background: "var(--csf-primary)" }, secondary: { } @@ -66,7 +67,8 @@ export const lightTheme = createTheme({ itemColor: "inherit", itemColorHover: "var(--gray7)", itemIconColor: "inherit", - itemIconColorHover: "var(--gray7)" + itemIconColorHover: "var(--gray7)", + profileButtonShadow: "0px 1px 2px rgba(0, 0, 0, 0.25)" }, createFolder: { backgroundColor: "var(--gray1)", @@ -168,8 +170,19 @@ export const lightTheme = createTheme({ surveyBanner: { color: "var(--gray1)" }, + addCard: { + color: "#595959", + shadow: "0px 0px 4px rgba(24, 144, 255, 0.5)" + }, cookieBanner: { backgroundColor: "var(--csf-primary)" + }, + changeProduct: { + currentBackground: "#ECEFFF", + selectedColor: "var(--csf-primary)", + currentTag: { + text: "var(--gray1)" + } } } as CsfColors) }, diff --git a/packages/files-ui/src/UI-components/Menu.tsx b/packages/files-ui/src/UI-components/Menu.tsx index 3dd820857c..32451f15ef 100644 --- a/packages/files-ui/src/UI-components/Menu.tsx +++ b/packages/files-ui/src/UI-components/Menu.tsx @@ -7,9 +7,10 @@ import { CSFTheme } from "../Themes/types" interface Option { contents: ReactNode - onClick?: (e: React.MouseEvent) => void inset?: boolean testId?: string + onClick?: (e: React.MouseEvent) => void + disabled?: boolean } interface CustomClasses { @@ -84,6 +85,7 @@ export default function Menu({ icon, options, style, testId, anchorOrigin, trans }} focusVisibleClassName={clsx(style?.focusVisible)} className={classes.options} + disabled={option.disabled} > {option.contents} diff --git a/packages/files-ui/src/locales/de/messages.po b/packages/files-ui/src/locales/de/messages.po index 744a3b7884..5d7558eac4 100644 --- a/packages/files-ui/src/locales/de/messages.po +++ b/packages/files-ui/src/locales/de/messages.po @@ -16,6 +16,12 @@ msgstr "" msgid "({0}) items selected" msgstr "" +msgid "/month" +msgstr "" + +msgid "/year" +msgstr "" + 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!" msgstr "Es wird ein Sicherungsgeheimsatz generiert und für Ihr Konto verwendet.<0/>Wir speichern ihn nicht und <1>er kann nur einmal angezeigt werden. Speichern Sie ihn an einem sicheren Ort!" @@ -28,12 +34,21 @@ msgstr "Es existiert bereits eine Datei mit demselben Namen" msgid "Accept" msgstr "Akzeptieren" +msgid "Access your billing history in settings or view your" +msgstr "" + msgid "Account" msgstr "Konto" msgid "Active links" msgstr "" +msgid "Add Card" +msgstr "" + +msgid "Add a credit card" +msgstr "" + msgid "Add a username" msgstr "Einen Benutzernamen hinzufügen" @@ -43,6 +58,12 @@ msgstr "Fügen Sie mindestens eine weitere Authentifizierungsmethode hinzu, um I msgid "Add by sharing address, username or wallet address" msgstr "Durch Freigabe der Adresse, des Benutzernamens oder der Brieftaschenadresse hinzufügen" +msgid "Add card" +msgstr "" + +msgid "Add credit card" +msgstr "" + msgid "Add more files" msgstr "Weitere Dateien hinzufügen" @@ -55,6 +76,9 @@ 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:" @@ -64,12 +88,21 @@ msgstr "" msgid "Approve" msgstr "Genehmigen" +msgid "Are you sure? This will delete your default payment method." +msgstr "" + msgid "Backup secret phrase" msgstr "Sicherungsgeheimsatz" msgid "Backup secret phrase does not match user account, please double-check and try again." msgstr "Der Sicherungsgeheimsatz stimmt nicht mit dem Benutzerkonto überein, bitte überprüfen Sie dies und versuchen Sie es erneut." +msgid "Billing history" +msgstr "" + +msgid "Billing start time" +msgstr "" + msgid "Bin" msgstr "Papierkorb" @@ -94,12 +127,27 @@ msgstr "IID (Inhaltsidentifikator)" msgid "Cancel" msgstr "Abbrechen" +msgid "Card added" +msgstr "" + +msgid "Card inputs invalid" +msgstr "" + +msgid "Card updated" +msgstr "" + msgid "Change Password" msgstr "Passwort ändern" +msgid "Change Plan" +msgstr "" + msgid "Change password" msgstr "Passwort ändern" +msgid "Change plan" +msgstr "" + msgid "Check your inbox! We've sent another email." msgstr "Prüfen Sie Ihren Posteingang! Wir haben eine weitere E-Mail verschickt." @@ -115,6 +163,15 @@ msgstr "Bestätigen" msgid "Confirm Password:" msgstr "Passwort bestätigen:" +msgid "Confirm plan" +msgstr "" + +msgid "Confirm plan change" +msgstr "" + +msgid "Confirmation" +msgstr "" + msgid "Connect Wallet to Files" msgstr "Wallet mit Dateien verbinden" @@ -178,6 +235,9 @@ msgstr "" msgid "Create your public username in <0>Settings!" msgstr "" +msgid "Credit card saved" +msgstr "" + msgid "Dark Theme" msgstr "Dunkles Farbschema" @@ -193,6 +253,9 @@ msgstr "" msgid "Data restored successfully" msgstr "" +msgid "Date" +msgstr "" + msgid "Date Uploaded" msgstr "" @@ -247,6 +310,12 @@ msgstr "" msgid "Drop to upload files" msgstr "Zum Hochladen von Dateien ablegen" +msgid "Edit payment method" +msgstr "" + +msgid "Edit plan" +msgstr "" + msgid "Email is required" msgstr "E-Mail ist erforderlich" @@ -262,8 +331,11 @@ msgstr "Geben Sie das Passwort ein:" msgid "Enter the verification code:" msgstr "Geben Sie den Verifizierungscode ein:" -msgid "Essentials - Free" -msgstr "Essentials - Kostenlos" +msgid "Failed to add payment method" +msgstr "" + +msgid "Failed to change subscription" +msgstr "" msgid "Failed to get signature" msgstr "Signatur kann nicht abgerufen werden" @@ -277,6 +349,9 @@ msgid "" "sure you have activated your wallet." msgstr "" +msgid "Features" +msgstr "" + msgid "File Info" msgstr "Dateiinfos" @@ -301,9 +376,6 @@ msgstr "Vorname" msgid "Folder name is already in use" msgstr "" -msgid "Folder uploads are not supported currently" -msgstr "" - msgid "Folders" msgstr "Ordner" @@ -313,6 +385,12 @@ msgstr "Aus Sicherheitsgründen werden wir Sie bei jeder Anmeldung nach einer de msgid "Forget this browser" msgstr "Diesen Browser vergessen" +msgid "Free" +msgstr "" + +msgid "Free plan" +msgstr "" + msgid "General" msgstr "Allgemein" @@ -334,9 +412,15 @@ msgstr "" msgid "Give view-only permission to:" msgstr "" +msgid "Go back" +msgstr "" + msgid "Go back" msgstr "Zurück" +msgid "Go to Payments" +msgstr "" + msgid "Go to login" msgstr "" @@ -400,15 +484,18 @@ 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 "Lorem ipsum aenean et rutrum magna. Morbi nec placerat erat. Nunc elementum sed libero sit amet convallis. Quisque non arcu vitae ex fringilla molestie." -msgstr "Lorem ipsum aenean et rutrum magna. Morbi nec placerat erat. Nunc elementum sed libero sit amet convallis. Quisque non arcu vitae ex fringilla molestie." - msgid "Manage Access" msgstr "" msgid "Manage Shared Folder" msgstr "" +msgid "Method" +msgstr "" + +msgid "Monthly billing" +msgstr "" + msgid "Move" msgstr "Verschieben" @@ -442,6 +529,9 @@ msgstr "Nächste" msgid "Nice to see you again!" msgstr "Schön, Sie wiederzusehen!" +msgid "No Card" +msgstr "" + msgid "No file to download." msgstr "" @@ -457,6 +547,9 @@ msgstr "Keine Dateien zum Anzeigen" msgid "No folders" msgstr "Keine Ordner" +msgid "No invoice found" +msgstr "" + msgid "No search results for" msgstr "Keine Suchergebnisse für" @@ -466,6 +559,9 @@ msgstr "Nein danke" msgid "No user found for this query." msgstr "" +msgid "Not sure what to pick? Learn more about our plans" +msgstr "" + msgid "Number of copies (Replication Factor)" msgstr "Anzahl der Kopien (Replikationsfaktor)" @@ -478,6 +574,9 @@ msgstr "Eine Sekunde, die Dateien werden vorbereitet …" msgid "Only you can see this." msgstr "Nur Sie können dies sehen." +msgid "Oops! You need to pay for this month to upload more content." +msgstr "" + msgid "Operating system:" msgstr "Betriebssystem:" @@ -514,6 +613,18 @@ msgstr "Passwort:" msgid "Passwords must match" msgstr "Passwörter müssen übereinstimmen" +msgid "Payment and Subscriptions" +msgstr "" + +msgid "Payment method" +msgstr "" + +msgid "Plan changed successfully" +msgstr "" + +msgid "Plan details" +msgstr "" + msgid "Please enter a file name" msgstr "Bitte geben Sie einen Dateinamen ein" @@ -529,6 +640,9 @@ msgstr "Bitte geben Sie ein Passwort an" msgid "Please select a file to upload" msgstr "Bitte wählen Sie eine Datei zum Hochladen" +msgid "Premium plan" +msgstr "" + msgid "Preview" msgstr "Vorschau" @@ -547,6 +661,9 @@ msgstr "Profileinstellungen" msgid "Profile updated" msgstr "Profil aktualisiert" +msgid "Receipt" +msgstr "" + msgid "Recover" msgstr "Wiederherstellen" @@ -565,6 +682,9 @@ msgstr "Ablehnen" msgid "Reject all" msgstr "Alle ablehnen" +msgid "Remove" +msgstr "" + msgid "Rename" msgstr "Umbenennen" @@ -631,6 +751,15 @@ msgstr "" msgid "Select an existing shared folder or your home" msgstr "" +msgid "Select payment method" +msgstr "" + +msgid "Select plan" +msgstr "" + +msgid "Select this plan" +msgstr "" + msgid "Send another email" msgstr "Andere E-Mail senden" @@ -715,18 +844,24 @@ msgstr "Es ist etwas schief gelaufen. Wir konnten Ihre Datei nicht hochladen" msgid "Sort By:" msgstr "" +msgid "Standard plan" +msgstr "" + msgid "Start Upload" msgstr "Hochladen starten" msgid "Start a team" msgstr "" -msgid "Storage Plan" -msgstr "Speicherplan" - msgid "Stored by miner" msgstr "" +msgid "Subscription Plan" +msgstr "" + +msgid "Switch Plans" +msgstr "" + msgid "System maintenance is scheduled to start at {0}. The system will be unavailable." msgstr "" @@ -781,6 +916,12 @@ msgstr "" msgid "There was an error when setting username." msgstr "" +msgid "This card will become your default payment method" +msgstr "" + +msgid "This is the free product." +msgstr "" + msgid "This link is marlformed. Please verify that you copy/pasted it correctly." msgstr "" @@ -796,6 +937,9 @@ msgstr "" msgid "This website uses cookies that help the website function and track interactions for analytics purposes. You have the right to decline our use of cookies. For us to provide a customizable user experience to you, please click on the Accept button below.<0>Learn more" msgstr "" +msgid "Total" +msgstr "" + msgid "Try again" msgstr "Erneut versuchen" @@ -805,21 +949,39 @@ msgstr "Eine andere Methode versuchen" msgid "Update" msgstr "Aktualisieren" +msgid "Update Card" +msgstr "" + msgid "Update Shared Folder" msgstr "" +msgid "Update card" +msgstr "" + +msgid "Update credit card" +msgstr "" + +msgid "Update your credit card" +msgstr "" + msgid "Upload" msgstr "Hochladen" msgid "Uploads cancelled" msgstr "" +msgid "Uploads disabled" +msgstr "" + msgid "Use a different login method" msgstr "Eine andere Anmeldemethode verwenden" msgid "Use a saved browser" msgstr "Einen gespeicherten Browser verwenden" +msgid "Use this card" +msgstr "" + msgid "User {0} is both a reader and writer" msgstr "" @@ -850,6 +1012,9 @@ msgstr "" msgid "View folder" msgstr "Ordner anzeigen" +msgid "View invoices" +msgstr "" + msgid "Wallet address" msgstr "" @@ -877,6 +1042,9 @@ msgstr "Was für ein schöner Tag." msgid "What a fine night it is." msgstr "Was für eine schöne Nacht." +msgid "Yearly billing" +msgstr "" + msgid "Yes, save it" msgstr "Ja, es speichern" @@ -892,21 +1060,36 @@ msgstr "Sie können keine Ordner in diesen Pfad verschieben" msgid "You do not have access to this shared folder." msgstr "" +msgid "You get access to these features right now." +msgstr "" + msgid "You haven't set a username yet." msgstr "Sie haben noch keinen Benutzernamen festgelegt." +msgid "You now have:" +msgstr "" + msgid "You were added to the shared folder ({0}): {1}" msgstr "" msgid "You will need to sign a message in your wallet to complete sign in." msgstr "" +msgid "You've got a payment due. Until you've settled up, we've placed your account in restricted mode" +msgstr "" + +msgid "Your plan" +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." +msgstr "" msgid "can-edit" msgstr "" +msgid "invoices here" +msgstr "" + msgid "me" msgstr "ich" @@ -940,8 +1123,20 @@ msgstr "" msgid "{0} failed" msgstr "" +msgid "{0} of {1} used" +msgstr "" + msgid "{0} {fileProgress} - {1}" msgstr "" +msgid "{currentPlanStorage} of storage" +msgstr "" + +msgid "{currentStorage} of storage" +msgstr "" + +msgid "{newPlanCapacity} of storage" +msgstr "" + msgid "{successCount} files transferred successfully, {0} failed" msgstr "" diff --git a/packages/files-ui/src/locales/en/messages.po b/packages/files-ui/src/locales/en/messages.po index b6226b8f70..36a6845b22 100644 --- a/packages/files-ui/src/locales/en/messages.po +++ b/packages/files-ui/src/locales/en/messages.po @@ -16,6 +16,12 @@ msgstr "" msgid "({0}) items selected" msgstr "({0}) items selected" +msgid "/month" +msgstr "/month" + +msgid "/year" +msgstr "/year" + 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!" msgstr "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!" @@ -28,12 +34,21 @@ msgstr "A file with the same name already exists" msgid "Accept" msgstr "Accept" +msgid "Access your billing history in settings or view your" +msgstr "Access your billing history in settings or view your" + msgid "Account" msgstr "Account" msgid "Active links" msgstr "Active links" +msgid "Add Card" +msgstr "Add Card" + +msgid "Add a credit card" +msgstr "Add a credit card" + msgid "Add a username" msgstr "Add a username" @@ -43,6 +58,12 @@ msgstr "Add at least one more authentication method to protect your account. You msgid "Add by sharing address, username or wallet address" msgstr "Add by sharing address, username or wallet address" +msgid "Add card" +msgstr "Add card" + +msgid "Add credit card" +msgstr "Add credit card" + msgid "Add more files" msgstr "Add more files" @@ -55,6 +76,9 @@ 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:" @@ -64,12 +88,21 @@ msgstr "Anyone with the link can:" msgid "Approve" msgstr "Approve" +msgid "Are you sure? This will delete your default payment method." +msgstr "Are you sure? This will delete your default payment method." + msgid "Backup secret phrase" msgstr "Backup secret phrase" msgid "Backup secret phrase does not match user account, please double-check and try again." msgstr "Backup secret phrase does not match user account, please double-check and try again." +msgid "Billing history" +msgstr "Billing history" + +msgid "Billing start time" +msgstr "Billing start time" + msgid "Bin" msgstr "Bin" @@ -94,12 +127,27 @@ msgstr "CID (Content Identifier)" msgid "Cancel" msgstr "Cancel" +msgid "Card added" +msgstr "Card added" + +msgid "Card inputs invalid" +msgstr "Card inputs invalid" + +msgid "Card updated" +msgstr "Card updated" + msgid "Change Password" msgstr "Change Password" +msgid "Change Plan" +msgstr "Change Plan" + msgid "Change password" msgstr "Change password" +msgid "Change plan" +msgstr "Change plan" + msgid "Check your inbox! We've sent another email." msgstr "Check your inbox! We've sent another email." @@ -115,6 +163,15 @@ msgstr "Confirm" msgid "Confirm Password:" msgstr "Confirm Password:" +msgid "Confirm plan" +msgstr "Confirm plan" + +msgid "Confirm plan change" +msgstr "Confirm plan change" + +msgid "Confirmation" +msgstr "Confirmation" + msgid "Connect Wallet to Files" msgstr "Connect Wallet to Files" @@ -178,6 +235,9 @@ msgstr "Create link" msgid "Create your public username in <0>Settings!" msgstr "Create your public username in <0>Settings!" +msgid "Credit card saved" +msgstr "Credit card saved" + msgid "Dark Theme" msgstr "Dark Theme" @@ -193,6 +253,9 @@ msgstr "Data moved to bin successfully" msgid "Data restored successfully" msgstr "Data restored successfully" +msgid "Date" +msgstr "Date" + msgid "Date Uploaded" msgstr "Date Uploaded" @@ -247,6 +310,12 @@ msgstr "Downloads failed" msgid "Drop to upload files" msgstr "Drop to upload files" +msgid "Edit payment method" +msgstr "Edit payment method" + +msgid "Edit plan" +msgstr "Edit plan" + msgid "Email is required" msgstr "Email is required" @@ -262,8 +331,11 @@ msgstr "Enter password:" msgid "Enter the verification code:" msgstr "Enter the verification code:" -msgid "Essentials - Free" -msgstr "Essentials - Free" +msgid "Failed to add payment method" +msgstr "Failed to add payment method" + +msgid "Failed to change subscription" +msgstr "Failed to change subscription" msgid "Failed to get signature" msgstr "Failed to get signature" @@ -280,6 +352,9 @@ msgstr "" "If you are using a contract wallet, please make \n" "sure you have activated your wallet." +msgid "Features" +msgstr "Features" + msgid "File Info" msgstr "File Info" @@ -304,9 +379,6 @@ msgstr "First name" msgid "Folder name is already in use" msgstr "Folder name is already in use" -msgid "Folder uploads are not supported currently" -msgstr "Folder uploads are not supported currently" - msgid "Folders" msgstr "Folders" @@ -316,6 +388,12 @@ msgstr "For security reasons, each time you sign in we’ll ask you for one of t msgid "Forget this browser" msgstr "Forget this browser" +msgid "Free" +msgstr "Free" + +msgid "Free plan" +msgstr "Free plan" + msgid "General" msgstr "General" @@ -337,9 +415,15 @@ msgstr "Give edit permission to:" msgid "Give view-only permission to:" msgstr "Give view-only permission to:" +msgid "Go back" +msgstr "Go back" + msgid "Go back" msgstr "Go back" +msgid "Go to Payments" +msgstr "Go to Payments" + msgid "Go to login" msgstr "Go to login" @@ -403,15 +487,18 @@ msgstr "Loading your shared folders…" 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 "Lorem ipsum aenean et rutrum magna. Morbi nec placerat erat. Nunc elementum sed libero sit amet convallis. Quisque non arcu vitae ex fringilla molestie." -msgstr "Lorem ipsum aenean et rutrum magna. Morbi nec placerat erat. Nunc elementum sed libero sit amet convallis. Quisque non arcu vitae ex fringilla molestie." - msgid "Manage Access" msgstr "Manage Access" msgid "Manage Shared Folder" msgstr "Manage Shared Folder" +msgid "Method" +msgstr "Method" + +msgid "Monthly billing" +msgstr "Monthly billing" + msgid "Move" msgstr "Move" @@ -445,6 +532,9 @@ msgstr "Next" msgid "Nice to see you again!" msgstr "Nice to see you again!" +msgid "No Card" +msgstr "No Card" + msgid "No file to download." msgstr "No file to download." @@ -460,6 +550,9 @@ msgstr "No files to show" msgid "No folders" msgstr "No folders" +msgid "No invoice found" +msgstr "No invoice found" + msgid "No search results for" msgstr "No search results for" @@ -469,6 +562,9 @@ msgstr "No thanks" msgid "No user found for this query." msgstr "No user found for this query." +msgid "Not sure what to pick? Learn more about our plans" +msgstr "Not sure what to pick? Learn more about our plans" + msgid "Number of copies (Replication Factor)" msgstr "Number of copies (Replication Factor)" @@ -481,6 +577,9 @@ msgstr "One sec, getting files ready…" msgid "Only you can see this." msgstr "Only you can see this." +msgid "Oops! You need to pay for this month to upload more content." +msgstr "Oops! You need to pay for this month to upload more content." + msgid "Operating system:" msgstr "Operating system:" @@ -517,6 +616,18 @@ msgstr "Password:" msgid "Passwords must match" msgstr "Passwords must match" +msgid "Payment and Subscriptions" +msgstr "Payment and Subscriptions" + +msgid "Payment method" +msgstr "Payment method" + +msgid "Plan changed successfully" +msgstr "Plan changed successfully" + +msgid "Plan details" +msgstr "Plan details" + msgid "Please enter a file name" msgstr "Please enter a file name" @@ -532,6 +643,9 @@ msgstr "Please provide a password" msgid "Please select a file to upload" msgstr "Please select a file to upload" +msgid "Premium plan" +msgstr "Premium plan" + msgid "Preview" msgstr "Preview" @@ -550,6 +664,9 @@ msgstr "Profile settings" msgid "Profile updated" msgstr "Profile updated" +msgid "Receipt" +msgstr "Receipt" + msgid "Recover" msgstr "Recover" @@ -568,6 +685,9 @@ msgstr "Reject" msgid "Reject all" msgstr "Reject all" +msgid "Remove" +msgstr "Remove" + msgid "Rename" msgstr "Rename" @@ -634,6 +754,15 @@ msgstr "Select a wallet" msgid "Select an existing shared folder or your home" msgstr "Select an existing shared folder or your home" +msgid "Select payment method" +msgstr "Select payment method" + +msgid "Select plan" +msgstr "Select plan" + +msgid "Select this plan" +msgstr "Select this plan" + msgid "Send another email" msgstr "Send another email" @@ -718,18 +847,24 @@ msgstr "Something went wrong. We couldn't upload your file" msgid "Sort By:" msgstr "Sort By:" +msgid "Standard plan" +msgstr "Standard plan" + msgid "Start Upload" msgstr "Start Upload" msgid "Start a team" msgstr "Start a team" -msgid "Storage Plan" -msgstr "Storage Plan" - msgid "Stored by miner" msgstr "Stored by miner" +msgid "Subscription Plan" +msgstr "Subscription Plan" + +msgid "Switch Plans" +msgstr "Switch Plans" + msgid "System maintenance is scheduled to start at {0}. The system will be unavailable." msgstr "System maintenance is scheduled to start at {0}. The system will be unavailable." @@ -784,6 +919,12 @@ msgstr "There was an error restoring your data" msgid "There was an error when setting username." msgstr "There was an error when setting username." +msgid "This card will become your default payment method" +msgstr "This card will become your default payment method" + +msgid "This is the free product." +msgstr "This is the free product." + msgid "This link is marlformed. Please verify that you copy/pasted it correctly." msgstr "This link is marlformed. Please verify that you copy/pasted it correctly." @@ -799,6 +940,9 @@ msgstr "This website uses cookies" msgid "This website uses cookies that help the website function and track interactions for analytics purposes. You have the right to decline our use of cookies. For us to provide a customizable user experience to you, please click on the Accept button below.<0>Learn more" msgstr "This website uses cookies that help the website function and track interactions for analytics purposes. You have the right to decline our use of cookies. For us to provide a customizable user experience to you, please click on the Accept button below.<0>Learn more" +msgid "Total" +msgstr "Total" + msgid "Try again" msgstr "Try again" @@ -808,21 +952,39 @@ msgstr "Try another method" msgid "Update" msgstr "Update" +msgid "Update Card" +msgstr "Update Card" + msgid "Update Shared Folder" msgstr "Update Shared Folder" +msgid "Update card" +msgstr "Update card" + +msgid "Update credit card" +msgstr "Update credit card" + +msgid "Update your credit card" +msgstr "Update your credit card" + msgid "Upload" msgstr "Upload" msgid "Uploads cancelled" msgstr "Uploads cancelled" +msgid "Uploads disabled" +msgstr "Uploads disabled" + msgid "Use a different login method" msgstr "Use a different login method" msgid "Use a saved browser" msgstr "Use a saved browser" +msgid "Use this card" +msgstr "Use this card" + msgid "User {0} is both a reader and writer" msgstr "User {0} is both a reader and writer" @@ -853,6 +1015,9 @@ msgstr "Verifying the link..." msgid "View folder" msgstr "View folder" +msgid "View invoices" +msgstr "View invoices" + msgid "Wallet address" msgstr "Wallet address" @@ -880,6 +1045,9 @@ msgstr "What a fine day it is." msgid "What a fine night it is." msgstr "What a fine night it is." +msgid "Yearly billing" +msgstr "Yearly billing" + msgid "Yes, save it" msgstr "Yes, save it" @@ -895,21 +1063,36 @@ msgstr "You can't move folders to this path" msgid "You do not have access to this shared folder." msgstr "You do not have access to this shared folder." +msgid "You get access to these features right now." +msgstr "You get access to these features right now." + msgid "You haven't set a username yet." msgstr "You haven't set a username yet." +msgid "You now have:" +msgstr "You now have:" + msgid "You were added to the shared folder ({0}): {1}" msgstr "You were added to the shared folder ({0}): {1}" msgid "You will need to sign a message in your wallet to complete sign in." msgstr "You will need to sign a message in your wallet to complete sign in." +msgid "You've got a payment due. Until you've settled up, we've placed your account in restricted mode" +msgstr "You've got a payment due. Until you've settled up, we've placed your account in restricted mode" + +msgid "Your plan" +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 "can-edit" msgstr "can-edit" +msgid "invoices here" +msgstr "invoices here" + msgid "me" msgstr "me" @@ -943,8 +1126,20 @@ msgstr "{0} complete" msgid "{0} failed" msgstr "{0} failed" +msgid "{0} of {1} used" +msgstr "{0} of {1} used" + msgid "{0} {fileProgress} - {1}" msgstr "{0} {fileProgress} - {1}" +msgid "{currentPlanStorage} of storage" +msgstr "{currentPlanStorage} of storage" + +msgid "{currentStorage} of storage" +msgstr "{currentStorage} of storage" + +msgid "{newPlanCapacity} of storage" +msgstr "{newPlanCapacity} of storage" + msgid "{successCount} files transferred successfully, {0} failed" msgstr "{successCount} files transferred successfully, {0} failed" diff --git a/packages/files-ui/src/locales/es/messages.po b/packages/files-ui/src/locales/es/messages.po index b4cd8621d9..c5c2fd2657 100644 --- a/packages/files-ui/src/locales/es/messages.po +++ b/packages/files-ui/src/locales/es/messages.po @@ -17,6 +17,12 @@ msgstr "" msgid "({0}) items selected" msgstr "" +msgid "/month" +msgstr "" + +msgid "/year" +msgstr "" + 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!" msgstr "" @@ -29,12 +35,21 @@ msgstr "Ya existe un archivo con el mismo nombre" msgid "Accept" msgstr "" +msgid "Access your billing history in settings or view your" +msgstr "" + msgid "Account" msgstr "Cuenta" msgid "Active links" msgstr "" +msgid "Add Card" +msgstr "" + +msgid "Add a credit card" +msgstr "" + msgid "Add a username" msgstr "" @@ -44,6 +59,12 @@ msgstr "Agregue al menos un método de autenticación más para proteger su cuen msgid "Add by sharing address, username or wallet address" msgstr "" +msgid "Add card" +msgstr "" + +msgid "Add credit card" +msgstr "" + msgid "Add more files" msgstr "Agrega mas archivos" @@ -56,6 +77,9 @@ msgstr "" msgid "Allow lookup by sharing key, wallet address or username" msgstr "" +msgid "Amount" +msgstr "" + msgid "An error occurred:" msgstr "" @@ -65,12 +89,21 @@ msgstr "" msgid "Approve" msgstr "Aprobar" +msgid "Are you sure? This will delete your default payment method." +msgstr "" + msgid "Backup secret phrase" msgstr "" msgid "Backup secret phrase does not match user account, please double-check and try again." msgstr "" +msgid "Billing history" +msgstr "" + +msgid "Billing start time" +msgstr "" + msgid "Bin" msgstr "Papelera" @@ -95,12 +128,27 @@ msgstr "CID (Identificador de contenido)" msgid "Cancel" msgstr "Cancelar" +msgid "Card added" +msgstr "" + +msgid "Card inputs invalid" +msgstr "" + +msgid "Card updated" +msgstr "" + msgid "Change Password" msgstr "Cambiar la contraseña" +msgid "Change Plan" +msgstr "" + msgid "Change password" msgstr "Cambiar la contraseña" +msgid "Change plan" +msgstr "" + msgid "Check your inbox! We've sent another email." msgstr "" @@ -116,6 +164,15 @@ msgstr "Confirmar" msgid "Confirm Password:" msgstr "Confirmar Contraseña:" +msgid "Confirm plan" +msgstr "" + +msgid "Confirm plan change" +msgstr "" + +msgid "Confirmation" +msgstr "" + msgid "Connect Wallet to Files" msgstr "Conectar monedero a archivos" @@ -179,6 +236,9 @@ msgstr "" msgid "Create your public username in <0>Settings!" msgstr "" +msgid "Credit card saved" +msgstr "" + msgid "Dark Theme" msgstr "Tema oscuro" @@ -194,6 +254,9 @@ msgstr "" msgid "Data restored successfully" msgstr "" +msgid "Date" +msgstr "" + msgid "Date Uploaded" msgstr "" @@ -248,6 +311,12 @@ msgstr "" msgid "Drop to upload files" msgstr "Suelta para subir archivos" +msgid "Edit payment method" +msgstr "" + +msgid "Edit plan" +msgstr "" + msgid "Email is required" msgstr "" @@ -263,8 +332,11 @@ msgstr "Introducir la contraseña:" msgid "Enter the verification code:" msgstr "" -msgid "Essentials - Free" -msgstr "Esenciales - Gratis" +msgid "Failed to add payment method" +msgstr "" + +msgid "Failed to change subscription" +msgstr "" msgid "Failed to get signature" msgstr "No se pudo obtener la firma" @@ -281,6 +353,9 @@ msgstr "" "Si está utilizando una billetera de contrato, haga\n" "seguro que has activado tu billetera." +msgid "Features" +msgstr "" + msgid "File Info" msgstr "Información del archivo" @@ -305,9 +380,6 @@ msgstr "Primer Nombre" msgid "Folder name is already in use" msgstr "" -msgid "Folder uploads are not supported currently" -msgstr "" - msgid "Folders" msgstr "Archivos" @@ -317,6 +389,12 @@ msgstr "Por motivos de seguridad, cada vez que inicie sesión le pediremos uno d msgid "Forget this browser" msgstr "Olvida este navegador" +msgid "Free" +msgstr "" + +msgid "Free plan" +msgstr "" + msgid "General" msgstr "General" @@ -338,9 +416,15 @@ msgstr "" msgid "Give view-only permission to:" msgstr "" +msgid "Go back" +msgstr "" + msgid "Go back" msgstr "Regresar" +msgid "Go to Payments" +msgstr "" + msgid "Go to login" msgstr "" @@ -404,15 +488,18 @@ 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 "Lorem ipsum aenean et rutrum magna. Morbi nec placerat erat. Nunc elementum sed libero sit amet convallis. Quisque non arcu vitae ex fringilla molestie." -msgstr "Lorem ipsum Aenean, y un gran maquillaje. Usamos más y colocamos en ratas. Ahora elemento libre, pero mucho del valle. , Quien no se doblegue, de la vida de una fringilla molestie." - msgid "Manage Access" msgstr "" msgid "Manage Shared Folder" msgstr "" +msgid "Method" +msgstr "" + +msgid "Monthly billing" +msgstr "" + msgid "Move" msgstr "Moverse" @@ -446,6 +533,9 @@ msgstr "Próximo" msgid "Nice to see you again!" msgstr "Encantado de verte de nuevo!" +msgid "No Card" +msgstr "" + msgid "No file to download." msgstr "" @@ -461,6 +551,9 @@ msgstr "No hay archivos para mostrar" msgid "No folders" msgstr "Sin carpetas" +msgid "No invoice found" +msgstr "" + msgid "No search results for" msgstr "No hay resultados de búsqueda para" @@ -470,6 +563,9 @@ msgstr "No gracias" msgid "No user found for this query." msgstr "" +msgid "Not sure what to pick? Learn more about our plans" +msgstr "" + msgid "Number of copies (Replication Factor)" msgstr "Número de copias (factor de replicación)" @@ -482,6 +578,9 @@ msgstr "" msgid "Only you can see this." msgstr "" +msgid "Oops! You need to pay for this month to upload more content." +msgstr "" + msgid "Operating system:" msgstr "Sistema operativo:" @@ -518,6 +617,18 @@ msgstr "Contraseña:" msgid "Passwords must match" msgstr "Las contraseñas deben coincidir" +msgid "Payment and Subscriptions" +msgstr "" + +msgid "Payment method" +msgstr "" + +msgid "Plan changed successfully" +msgstr "" + +msgid "Plan details" +msgstr "" + msgid "Please enter a file name" msgstr "Ingrese un nombre de archivo" @@ -533,6 +644,9 @@ msgstr "Por favor ingrese una contraseña" msgid "Please select a file to upload" msgstr "" +msgid "Premium plan" +msgstr "" + msgid "Preview" msgstr "Avance" @@ -551,6 +665,9 @@ msgstr "" msgid "Profile updated" msgstr "Perfil actualizado" +msgid "Receipt" +msgstr "" + msgid "Recover" msgstr "Recuperar" @@ -569,6 +686,9 @@ msgstr "Rechazar" msgid "Reject all" msgstr "Rechazar todo" +msgid "Remove" +msgstr "" + msgid "Rename" msgstr "Renombrar" @@ -635,6 +755,15 @@ msgstr "Seleccione una billetera" msgid "Select an existing shared folder or your home" msgstr "" +msgid "Select payment method" +msgstr "" + +msgid "Select plan" +msgstr "" + +msgid "Select this plan" +msgstr "" + msgid "Send another email" msgstr "" @@ -719,18 +848,24 @@ msgstr "Algo salió mal. No pudimos subir tu archivo" msgid "Sort By:" msgstr "" +msgid "Standard plan" +msgstr "" + msgid "Start Upload" msgstr "Iniciar la subida" msgid "Start a team" msgstr "" -msgid "Storage Plan" -msgstr "Plan de almacenamiento" - msgid "Stored by miner" msgstr "Almacenado por el minero" +msgid "Subscription Plan" +msgstr "" + +msgid "Switch Plans" +msgstr "" + msgid "System maintenance is scheduled to start at {0}. The system will be unavailable." msgstr "" @@ -785,6 +920,12 @@ msgstr "" msgid "There was an error when setting username." msgstr "" +msgid "This card will become your default payment method" +msgstr "" + +msgid "This is the free product." +msgstr "" + msgid "This link is marlformed. Please verify that you copy/pasted it correctly." msgstr "" @@ -800,6 +941,9 @@ msgstr "" msgid "This website uses cookies that help the website function and track interactions for analytics purposes. You have the right to decline our use of cookies. For us to provide a customizable user experience to you, please click on the Accept button below.<0>Learn more" msgstr "" +msgid "Total" +msgstr "" + msgid "Try again" msgstr "Intentar otra vez" @@ -809,21 +953,39 @@ msgstr "Prueba con otro método" msgid "Update" msgstr "Actualizar" +msgid "Update Card" +msgstr "" + msgid "Update Shared Folder" msgstr "" +msgid "Update card" +msgstr "" + +msgid "Update credit card" +msgstr "" + +msgid "Update your credit card" +msgstr "" + msgid "Upload" msgstr "Subir" msgid "Uploads cancelled" msgstr "" +msgid "Uploads disabled" +msgstr "" + msgid "Use a different login method" msgstr "Utilice un método de inicio de sesión diferente" msgid "Use a saved browser" msgstr "Utilice un navegador guardado" +msgid "Use this card" +msgstr "" + msgid "User {0} is both a reader and writer" msgstr "" @@ -854,6 +1016,9 @@ msgstr "" msgid "View folder" msgstr "Utilice un navegador guardado" +msgid "View invoices" +msgstr "" + msgid "Wallet address" msgstr "Utilice un navegador guardado" @@ -881,6 +1046,9 @@ msgstr "Qué buen día es." msgid "What a fine night it is." msgstr "Qué hermosa noche es." +msgid "Yearly billing" +msgstr "" + msgid "Yes, save it" msgstr "Si guárdalo" @@ -896,21 +1064,36 @@ msgstr "" msgid "You do not have access to this shared folder." msgstr "" +msgid "You get access to these features right now." +msgstr "" + msgid "You haven't set a username yet." msgstr "" +msgid "You now have:" +msgstr "" + msgid "You were added to the shared folder ({0}): {1}" msgstr "" msgid "You will need to sign a message in your wallet to complete sign in." msgstr "Deberá firmar un mensaje en su billetera para completar el inicio de sesión." +msgid "You've got a payment due. Until you've settled up, we've placed your account in restricted mode" +msgstr "" + +msgid "Your plan" +msgstr "" + msgid "Your recovery key can be used to restore your account in place of your backup secret phrase." msgstr "" msgid "can-edit" msgstr "" +msgid "invoices here" +msgstr "" + msgid "me" msgstr "" @@ -944,8 +1127,20 @@ msgstr "" msgid "{0} failed" msgstr "" +msgid "{0} of {1} used" +msgstr "" + msgid "{0} {fileProgress} - {1}" msgstr "" +msgid "{currentPlanStorage} of storage" +msgstr "" + +msgid "{currentStorage} of storage" +msgstr "" + +msgid "{newPlanCapacity} of storage" +msgstr "" + msgid "{successCount} files transferred successfully, {0} failed" msgstr "" diff --git a/packages/files-ui/src/locales/fr/messages.po b/packages/files-ui/src/locales/fr/messages.po index b56b30620e..63b5726016 100644 --- a/packages/files-ui/src/locales/fr/messages.po +++ b/packages/files-ui/src/locales/fr/messages.po @@ -15,7 +15,13 @@ msgstr "" "Mime-Version: 1.0\n" msgid "({0}) items selected" -msgstr "({0}) éléments sélectionnés" +msgstr "" + +msgid "/month" +msgstr "" + +msgid "/year" +msgstr "" 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!" msgstr "Une phrase secrète de sauvegarde sera générée et utilisé pour ce compte.<0/>Nous ne la sauvergardons pas <1>elle ne peut être affichée qu’une seule fois. Gardez-la dans un endroit sûr !" @@ -29,12 +35,21 @@ msgstr "Un fichier avec ce nom existe déjà" msgid "Accept" msgstr "Accepter" +msgid "Access your billing history in settings or view your" +msgstr "" + msgid "Account" msgstr "Compte" msgid "Active links" msgstr "Liens actifs" +msgid "Add Card" +msgstr "" + +msgid "Add a credit card" +msgstr "" + msgid "Add a username" msgstr "Ajouter un nom d’utilisateur" @@ -44,6 +59,12 @@ msgstr "Ajoutez au moins une méthode d’authentification pour protéger ce com msgid "Add by sharing address, username or wallet address" msgstr "Ajouter par adresse de partage, nom d'utilisateur ou adresse de portefeuille" +msgid "Add card" +msgstr "" + +msgid "Add credit card" +msgstr "" + msgid "Add more files" msgstr "Ajouter d’autres fichiers" @@ -56,6 +77,9 @@ 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 :" @@ -65,12 +89,21 @@ msgstr "Toute personne ayant le lien peut le faire :" msgid "Approve" msgstr "Accepter" +msgid "Are you sure? This will delete your default payment method." +msgstr "" + msgid "Backup secret phrase" msgstr "Phrase secrète de sauvegarde" msgid "Backup secret phrase does not match user account, please double-check and try again." msgstr "La phrase secrète de sauvegarde est incorrecte, merci de vérifier et réessayer." +msgid "Billing history" +msgstr "" + +msgid "Billing start time" +msgstr "" + msgid "Bin" msgstr "Corbeille" @@ -95,12 +128,27 @@ msgstr "CID (Identifiant de contenu)" msgid "Cancel" msgstr "Annuler" +msgid "Card added" +msgstr "" + +msgid "Card inputs invalid" +msgstr "" + +msgid "Card updated" +msgstr "" + msgid "Change Password" msgstr "Changer le mot de passe" +msgid "Change Plan" +msgstr "" + msgid "Change password" msgstr "Changer le mot de passe" +msgid "Change plan" +msgstr "" + msgid "Check your inbox! We've sent another email." msgstr "Vérifiez votre boîte de réception ! Nous avons envoyé un autre courriel." @@ -116,6 +164,15 @@ msgstr "Confirmer" msgid "Confirm Password:" msgstr "Confirmer le mot de passe :" +msgid "Confirm plan" +msgstr "" + +msgid "Confirm plan change" +msgstr "" + +msgid "Confirmation" +msgstr "" + msgid "Connect Wallet to Files" msgstr "Connecter un wallet à Files" @@ -179,6 +236,9 @@ 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 !" +msgid "Credit card saved" +msgstr "" + msgid "Dark Theme" msgstr "Thème sombre" @@ -194,6 +254,9 @@ msgstr "Les données ont été déplacées vers la poubelle avec succès" msgid "Data restored successfully" msgstr "Données restaurées avec succès" +msgid "Date" +msgstr "" + msgid "Date Uploaded" msgstr "Téléversé le" @@ -248,6 +311,12 @@ msgstr "Les téléchargements ont échoué" msgid "Drop to upload files" msgstr "Faire glisser pour téléverser un fichier" +msgid "Edit payment method" +msgstr "" + +msgid "Edit plan" +msgstr "" + msgid "Email is required" msgstr "Un courriel est requis" @@ -263,8 +332,11 @@ msgstr "Entrez le mot de passe :" msgid "Enter the verification code:" msgstr "Entrez le code de vérification :" -msgid "Essentials - Free" -msgstr "Essentials - Gratuit" +msgid "Failed to add payment method" +msgstr "" + +msgid "Failed to change subscription" +msgstr "" msgid "Failed to get signature" msgstr "Échec de l’obtention de la signature" @@ -281,6 +353,9 @@ msgstr "" "Si vous utilisez un contract wallet, veuillez\n" "vérifier que vous avez activé votre wallet." +msgid "Features" +msgstr "" + msgid "File Info" msgstr "Infos du fichier" @@ -305,9 +380,6 @@ msgstr "Prénom" msgid "Folder name is already in use" msgstr "Le nom du dossier est déjà utilisé" -msgid "Folder uploads are not supported currently" -msgstr "Le téléversement de dossiers n'est pas actuellement pris en charge" - msgid "Folders" msgstr "Dossiers" @@ -317,6 +389,12 @@ msgstr "Pour des raisons de sécurité, chaque fois que vous vous connectez, nou msgid "Forget this browser" msgstr "Oublier ce navigateur" +msgid "Free" +msgstr "" + +msgid "Free plan" +msgstr "" + msgid "General" msgstr "Général" @@ -338,9 +416,15 @@ msgstr "Donner l’autorisation de modifier à :" msgid "Give view-only permission to:" msgstr "Donner l’accès en lecture seule à :" +msgid "Go back" +msgstr "" + msgid "Go back" msgstr "Retour" +msgid "Go to Payments" +msgstr "" + msgid "Go to login" msgstr "Aller à la connexion" @@ -404,15 +488,18 @@ msgstr "Chargement de vos dossiers partagés…" 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 "Lorem ipsum aenean et rutrum magna. Morbi nec placerat erat. Nunc elementum sed libero sit amet convallis. Quisque non arcu vitae ex fringilla molestie." -msgstr "Lorem ipsum aenean et rutrum magna. Morbi nec placerat erat. Nunc elementum sed libero sit amet convallis. Quisque non arcu vitae ex fringilla molestie." - msgid "Manage Access" msgstr "Gérer l’accès" msgid "Manage Shared Folder" msgstr "Gérer le dossier partagé" +msgid "Method" +msgstr "" + +msgid "Monthly billing" +msgstr "" + msgid "Move" msgstr "Déplacer" @@ -446,6 +533,9 @@ msgstr "Suivant" msgid "Nice to see you again!" msgstr "Ravi de te revoir !" +msgid "No Card" +msgstr "" + msgid "No file to download." msgstr "Aucun fichier à télécharger." @@ -456,11 +546,14 @@ msgid "No files to share" msgstr "Aucun fichier à partager" msgid "No files to show" -msgstr "Aucun fichier à afficher" +msgstr "" msgid "No folders" msgstr "Aucun dossier" +msgid "No invoice found" +msgstr "" + msgid "No search results for" msgstr "Aucun fichier à afficher" @@ -470,6 +563,9 @@ msgstr "Non merci" msgid "No user found for this query." msgstr "Aucun utilisateur n'a été trouvé pour cette requête." +msgid "Not sure what to pick? Learn more about our plans" +msgstr "" + msgid "Number of copies (Replication Factor)" msgstr "Nombre de copies (facteur de réplication)" @@ -482,6 +578,9 @@ msgstr "Un instant, nous préparons les fichiers…" msgid "Only you can see this." msgstr "Vous seul(e) pouvez voir ceci." +msgid "Oops! You need to pay for this month to upload more content." +msgstr "" + msgid "Operating system:" msgstr "Système d’exploitation :" @@ -518,6 +617,18 @@ msgstr "Mot de passe :" msgid "Passwords must match" msgstr "Les mots de passes de correspondent pas" +msgid "Payment and Subscriptions" +msgstr "" + +msgid "Payment method" +msgstr "" + +msgid "Plan changed successfully" +msgstr "" + +msgid "Plan details" +msgstr "" + msgid "Please enter a file name" msgstr "Veuillez entrer un nom de fichier" @@ -533,6 +644,9 @@ msgstr "Merci de donner un mot de passe" msgid "Please select a file to upload" msgstr "Merci de sélectionner un fichier à téléverser" +msgid "Premium plan" +msgstr "" + msgid "Preview" msgstr "Aperçu" @@ -551,6 +665,9 @@ msgstr "Paramètres du profil" msgid "Profile updated" msgstr "Profile mis à jour" +msgid "Receipt" +msgstr "" + msgid "Recover" msgstr "Récupérer" @@ -569,6 +686,9 @@ msgstr "Refuser" msgid "Reject all" msgstr "Refuser tous" +msgid "Remove" +msgstr "" + msgid "Rename" msgstr "Renommer" @@ -635,6 +755,15 @@ msgstr "Sélectionner un wallet" msgid "Select an existing shared folder or your home" msgstr "Sélectionnez un dossier partagé existant ou votre page d'accueil" +msgid "Select payment method" +msgstr "" + +msgid "Select plan" +msgstr "" + +msgid "Select this plan" +msgstr "" + msgid "Send another email" msgstr "Envoyer un nouveau courriel" @@ -719,18 +848,24 @@ msgstr "Un problème est survenu. Nous n’avons pas pu téléverser votre fichi msgid "Sort By:" msgstr "Trier par :" +msgid "Standard plan" +msgstr "" + msgid "Start Upload" msgstr "Démarrer le téléversement" msgid "Start a team" msgstr "Créer une équipe" -msgid "Storage Plan" -msgstr "Plan de stockage" - msgid "Stored by miner" msgstr "Sauvegardé par le mineur" +msgid "Subscription Plan" +msgstr "" + +msgid "Switch Plans" +msgstr "" + msgid "System maintenance is scheduled to start at {0}. The system will be unavailable." msgstr "Une maintenance du système est prévue pour démarrer à {0}. Le système sera indisponible." @@ -785,6 +920,12 @@ msgstr "Une erreur s'est produite lors de la restauration de vos données" 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 card will become your default payment method" +msgstr "" + +msgid "This is the free product." +msgstr "" + msgid "This link is marlformed. Please verify that you copy/pasted it correctly." msgstr "Ce lien est marlformé. Veuillez vérifier que vous l'avez copié/collé correctement." @@ -800,6 +941,9 @@ msgstr "Ce site web utilise des cookies" msgid "This website uses cookies that help the website function and track interactions for analytics purposes. You have the right to decline our use of cookies. For us to provide a customizable user experience to you, please click on the Accept button below.<0>Learn more" msgstr "Ce site web utilise des cookies qui l'aident à fonctionner et à suivre les interactions à des fins d'analyse. Vous avez le droit de refuser notre utilisation des cookies. Pour que nous puissions vous offrir une expérience utilisateur personnalisable, veuillez cliquer sur le bouton Accepter ci-dessous.<0>En savoir plus" +msgid "Total" +msgstr "" + msgid "Try again" msgstr "Essayer de nouveau" @@ -809,21 +953,39 @@ msgstr "Essayer une autre méthode" msgid "Update" msgstr "Mettre à jour" +msgid "Update Card" +msgstr "" + msgid "Update Shared Folder" msgstr "Mettre à jour le dossier partagé" +msgid "Update card" +msgstr "" + +msgid "Update credit card" +msgstr "" + +msgid "Update your credit card" +msgstr "" + msgid "Upload" msgstr "Téléverser" msgid "Uploads cancelled" msgstr "Téléversements annulés" +msgid "Uploads disabled" +msgstr "" + msgid "Use a different login method" msgstr "Utilisez une méthode de connexion différente" msgid "Use a saved browser" msgstr "Utiliser un navigateur enregistré" +msgid "Use this card" +msgstr "" + msgid "User {0} is both a reader and writer" msgstr "L'utilisateur {0} est dans les auteurs et lecteurs" @@ -854,6 +1016,9 @@ msgstr "Vérification du lien…" msgid "View folder" msgstr "Voir le dossier" +msgid "View invoices" +msgstr "" + msgid "Wallet address" msgstr "Addresse du wallet" @@ -881,6 +1046,9 @@ msgstr "Quelle belle journée." msgid "What a fine night it is." msgstr "Quelle belle nuit." +msgid "Yearly billing" +msgstr "" + msgid "Yes, save it" msgstr "Oui, l’enregistrer" @@ -894,23 +1062,38 @@ msgid "You can't move folders to this path" msgstr "Vous ne pouvez pas déplacer les dossiers vers ce chemin" msgid "You do not have access to this shared folder." -msgstr "Vous n'avez pas accès à ce dossier partagé." +msgstr "" + +msgid "You get access to these features right now." +msgstr "" msgid "You haven't set a username yet." msgstr "Vous n’avez pas encore défini de nom d’utilisateur." +msgid "You now have:" +msgstr "" + msgid "You were added to the shared folder ({0}): {1}" msgstr "Vous avez été ajouté(e) au dossier partagé ({0}) : {1}" msgid "You will need to sign a message in your wallet to complete sign in." msgstr "Vous devrez signer un message avec votre wallet pour terminer la procédure connexion." +msgid "You've got a payment due. Until you've settled up, we've placed your account in restricted mode" +msgstr "" + +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 "can-edit" msgstr "peut-modifier" +msgid "invoices here" +msgstr "" + msgid "me" msgstr "moi" @@ -944,8 +1127,20 @@ msgstr "{0} terminé" msgid "{0} failed" msgstr "échec de {0}" +msgid "{0} of {1} used" +msgstr "" + msgid "{0} {fileProgress} - {1}" msgstr "{0} {fileProgress} – {1}" +msgid "{currentPlanStorage} of storage" +msgstr "" + +msgid "{currentStorage} of storage" +msgstr "" + +msgid "{newPlanCapacity} of storage" +msgstr "" + msgid "{successCount} files transferred successfully, {0} failed" msgstr "{successCount} fichiers transférés avec succès, {0} échec(s)" diff --git a/packages/files-ui/src/locales/no/messages.po b/packages/files-ui/src/locales/no/messages.po index 646facb3c2..3cb14f72a7 100644 --- a/packages/files-ui/src/locales/no/messages.po +++ b/packages/files-ui/src/locales/no/messages.po @@ -16,6 +16,12 @@ msgstr "" msgid "({0}) items selected" msgstr "" +msgid "/month" +msgstr "" + +msgid "/year" +msgstr "" + 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!" msgstr "" @@ -28,12 +34,21 @@ msgstr "" msgid "Accept" msgstr "" +msgid "Access your billing history in settings or view your" +msgstr "" + msgid "Account" msgstr "Konto" msgid "Active links" msgstr "" +msgid "Add Card" +msgstr "" + +msgid "Add a credit card" +msgstr "" + msgid "Add a username" msgstr "Legg til et brukernavn" @@ -43,6 +58,12 @@ msgstr "" msgid "Add by sharing address, username or wallet address" msgstr "" +msgid "Add card" +msgstr "" + +msgid "Add credit card" +msgstr "" + msgid "Add more files" msgstr "Legg til flere filer" @@ -55,6 +76,9 @@ msgstr "" msgid "Allow lookup by sharing key, wallet address or username" msgstr "" +msgid "Amount" +msgstr "" + msgid "An error occurred:" msgstr "" @@ -62,7 +86,10 @@ msgid "Anyone with the link can:" msgstr "" msgid "Approve" -msgstr "Godkjenn" +msgstr "Godkjenn<<<<<<< HEAD<<<<<<< HEAD" + +msgid "Are you sure? This will delete your default payment method." +msgstr "" msgid "Backup secret phrase" msgstr "" @@ -70,6 +97,12 @@ msgstr "" msgid "Backup secret phrase does not match user account, please double-check and try again." msgstr "" +msgid "Billing history" +msgstr "" + +msgid "Billing start time" +msgstr "" + msgid "Bin" msgstr "" @@ -94,12 +127,27 @@ msgstr "" msgid "Cancel" msgstr "Avbryt" +msgid "Card added" +msgstr "" + +msgid "Card inputs invalid" +msgstr "" + +msgid "Card updated" +msgstr "" + msgid "Change Password" msgstr "Endre passord" +msgid "Change Plan" +msgstr "" + msgid "Change password" msgstr "Endre passord" +msgid "Change plan" +msgstr "" + msgid "Check your inbox! We've sent another email." msgstr "" @@ -115,6 +163,15 @@ msgstr "Bekreft" msgid "Confirm Password:" msgstr "Bekreft passord:" +msgid "Confirm plan" +msgstr "" + +msgid "Confirm plan change" +msgstr "" + +msgid "Confirmation" +msgstr "" + msgid "Connect Wallet to Files" msgstr "" @@ -176,6 +233,9 @@ msgid "Create link" msgstr "" msgid "Create your public username in <0>Settings!" +msgstr ">>>>>>> origin/dev" + +msgid "Credit card saved" msgstr "" msgid "Dark Theme" @@ -193,6 +253,9 @@ msgstr "" msgid "Data restored successfully" msgstr "" +msgid "Date" +msgstr "" + msgid "Date Uploaded" msgstr "" @@ -247,8 +310,14 @@ msgstr "" msgid "Drop to upload files" msgstr "Dra for å laste opp filer" +msgid "Edit payment method" +msgstr "<<<<<<< HEAD" + +msgid "Edit plan" +msgstr "" + msgid "Email is required" -msgstr "E-post kreves" +msgstr "E-post kreves=======>>>>>>> origin/dev" msgid "Enter backup secret phrase:" msgstr "" @@ -262,7 +331,10 @@ msgstr "Skriv inn passord:" msgid "Enter the verification code:" msgstr "Skriv inn bekreftelseskoden:" -msgid "Essentials - Free" +msgid "Failed to add payment method" +msgstr "" + +msgid "Failed to change subscription" msgstr "" msgid "Failed to get signature" @@ -275,7 +347,10 @@ msgid "" "Failed to validate signature.\n" "If you are using a contract wallet, please make \n" "sure you have activated your wallet." -msgstr "" +msgstr "<<<<<<< HEAD" + +msgid "Features" +msgstr "=======>>>>>>> origin/dev" msgid "File Info" msgstr "Filinfo" @@ -301,9 +376,6 @@ msgstr "Fornavn" msgid "Folder name is already in use" msgstr "" -msgid "Folder uploads are not supported currently" -msgstr "" - msgid "Folders" msgstr "Mapper" @@ -313,6 +385,12 @@ msgstr "" msgid "Forget this browser" msgstr "" +msgid "Free" +msgstr "" + +msgid "Free plan" +msgstr "" + msgid "General" msgstr "" @@ -334,9 +412,15 @@ msgstr "" msgid "Give view-only permission to:" msgstr "" +msgid "Go back" +msgstr "" + msgid "Go back" msgstr "Tilbake" +msgid "Go to Payments" +msgstr "" + msgid "Go to login" msgstr "" @@ -400,15 +484,18 @@ msgstr "" msgid "Looks like you’re signing in from a new browser. Please choose one of the following to continue:" msgstr "" -msgid "Lorem ipsum aenean et rutrum magna. Morbi nec placerat erat. Nunc elementum sed libero sit amet convallis. Quisque non arcu vitae ex fringilla molestie." -msgstr "" - msgid "Manage Access" msgstr "" msgid "Manage Shared Folder" msgstr "" +msgid "Method" +msgstr "<<<<<<< HEAD" + +msgid "Monthly billing" +msgstr "" + msgid "Move" msgstr "Flytt" @@ -442,6 +529,9 @@ msgstr "Neste" msgid "Nice to see you again!" msgstr "" +msgid "No Card" +msgstr "" + msgid "No file to download." msgstr "" @@ -457,6 +547,9 @@ msgstr "Ingen filer å vise" msgid "No folders" msgstr "Ingen mapper" +msgid "No invoice found" +msgstr "" + msgid "No search results for" msgstr "" @@ -466,6 +559,9 @@ msgstr "Nei takk" msgid "No user found for this query." msgstr "" +msgid "Not sure what to pick? Learn more about our plans" +msgstr "" + msgid "Number of copies (Replication Factor)" msgstr "" @@ -478,6 +574,9 @@ msgstr "" msgid "Only you can see this." msgstr "Kun du kan se dette." +msgid "Oops! You need to pay for this month to upload more content." +msgstr "" + msgid "Operating system:" msgstr "Operativsystem:" @@ -514,6 +613,18 @@ msgstr "Passord:" msgid "Passwords must match" msgstr "Passordene må samsvare" +msgid "Payment and Subscriptions" +msgstr "" + +msgid "Payment method" +msgstr "" + +msgid "Plan changed successfully" +msgstr "" + +msgid "Plan details" +msgstr "" + msgid "Please enter a file name" msgstr "Skriv inn et filnavn" @@ -529,6 +640,9 @@ msgstr "Angi et passord" msgid "Please select a file to upload" msgstr "Velg en fil å laste opp" +msgid "Premium plan" +msgstr "" + msgid "Preview" msgstr "Forhåndsvis" @@ -547,6 +661,9 @@ msgstr "Profilinnstillinger" msgid "Profile updated" msgstr "Profil oppdatert" +msgid "Receipt" +msgstr "" + msgid "Recover" msgstr "Gjenopprett" @@ -565,6 +682,9 @@ msgstr "Avslå" msgid "Reject all" msgstr "Avslå alle" +msgid "Remove" +msgstr "" + msgid "Rename" msgstr "Gi nytt navn" @@ -631,6 +751,15 @@ msgstr "" msgid "Select an existing shared folder or your home" msgstr "" +msgid "Select payment method" +msgstr "" + +msgid "Select plan" +msgstr "" + +msgid "Select this plan" +msgstr ">>>>>>> origin/dev" + msgid "Send another email" msgstr "Send en ny e-post" @@ -715,16 +844,22 @@ msgstr "" msgid "Sort By:" msgstr "" +msgid "Standard plan" +msgstr "" + msgid "Start Upload" msgstr "" msgid "Start a team" msgstr "" -msgid "Storage Plan" +msgid "Stored by miner" msgstr "" -msgid "Stored by miner" +msgid "Subscription Plan" +msgstr "" + +msgid "Switch Plans" msgstr "" msgid "System maintenance is scheduled to start at {0}. The system will be unavailable." @@ -779,8 +914,14 @@ msgid "There was an error restoring your data" msgstr "" msgid "There was an error when setting username." +msgstr "<<<<<<< HEAD<<<<<<< HEAD" + +msgid "This card will become your default payment method" msgstr "" +msgid "This is the free product." +msgstr "=======>>>>>>> ecce826c708fb0146bff649f12f0896b4d9ca89e=======>>>>>>> origin/dev" + msgid "This link is marlformed. Please verify that you copy/pasted it correctly." msgstr "" @@ -791,11 +932,14 @@ msgid "This username is already taken" msgstr "" msgid "This website uses cookies" -msgstr "" +msgstr "<<<<<<< HEAD" msgid "This website uses cookies that help the website function and track interactions for analytics purposes. You have the right to decline our use of cookies. For us to provide a customizable user experience to you, please click on the Accept button below.<0>Learn more" msgstr "" +msgid "Total" +msgstr "" + msgid "Try again" msgstr "Prøv igjen" @@ -805,20 +949,38 @@ msgstr "Prøv annen metode" msgid "Update" msgstr "Oppdater" +msgid "Update Card" +msgstr "" + msgid "Update Shared Folder" msgstr "" +msgid "Update card" +msgstr "" + +msgid "Update credit card" +msgstr "" + +msgid "Update your credit card" +msgstr "" + msgid "Upload" msgstr "Last opp" msgid "Uploads cancelled" msgstr "" +msgid "Uploads disabled" +msgstr "" + msgid "Use a different login method" msgstr "Bruk en annen innloggingsmetode" msgid "Use a saved browser" -msgstr "" +msgstr "<<<<<<< HEAD" + +msgid "Use this card" +msgstr "=======>>>>>>> origin/dev" msgid "User {0} is both a reader and writer" msgstr "" @@ -850,6 +1012,9 @@ msgstr "" msgid "View folder" msgstr "Vis mappe" +msgid "View invoices" +msgstr "" + msgid "Wallet address" msgstr "" @@ -877,6 +1042,9 @@ msgstr "" msgid "What a fine night it is." msgstr "" +msgid "Yearly billing" +msgstr "" + msgid "Yes, save it" msgstr "Ja, lagre" @@ -887,31 +1055,46 @@ msgid "You can now create shared folders to share a file." msgstr "" msgid "You can't move folders to this path" -msgstr "" +msgstr "<<<<<<< HEAD" msgid "You do not have access to this shared folder." msgstr "" +msgid "You get access to these features right now." +msgstr "" + msgid "You haven't set a username yet." -msgstr "Du har ikke satt noe brukernavn enda." +msgstr "Du har ikke satt noe brukernavn enda.<<<<<<< HEAD" -msgid "You were added to the shared folder ({0}): {1}" +msgid "You now have:" msgstr "" +msgid "You were added to the shared folder ({0}): {1}" +msgstr "<<<<<<< HEAD" + msgid "You will need to sign a message in your wallet to complete sign in." msgstr "" +msgid "You've got a payment due. Until you've settled up, we've placed your account in restricted mode" +msgstr "" + +msgid "Your plan" +msgstr "" + msgid "Your recovery key can be used to restore your account in place of your backup secret phrase." msgstr "" msgid "can-edit" -msgstr "" +msgstr ">>>>>>> origin/dev" + +msgid "invoices here" +msgstr "=======" msgid "me" -msgstr "" +msgstr "<<<<<<< HEAD<<<<<<< HEAD" msgid "on" -msgstr "" +msgstr ">>>>>>> origin/dev" msgid "unknown" msgstr "" @@ -940,8 +1123,20 @@ msgstr "" msgid "{0} failed" msgstr "" +msgid "{0} of {1} used" +msgstr "" + msgid "{0} {fileProgress} - {1}" msgstr "" +msgid "{currentPlanStorage} of storage" +msgstr "" + +msgid "{currentStorage} of storage" +msgstr "" + +msgid "{newPlanCapacity} of storage" +msgstr "" + msgid "{successCount} files transferred successfully, {0} failed" msgstr "" diff --git a/packages/storage-ui/package.json b/packages/storage-ui/package.json index 44a80fe987..11e8c3294c 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.20", + "@chainsafe/files-api-client": "^1.18.22", "@chainsafe/web3-context": "1.1.4", "@lingui/core": "^3.7.2", "@lingui/react": "^3.7.2", diff --git a/packages/storage-ui/src/Components/Modules/FilesList/FilesList.tsx b/packages/storage-ui/src/Components/Modules/FilesList/FilesList.tsx index 9d639e28a9..5e0d46027d 100644 --- a/packages/storage-ui/src/Components/Modules/FilesList/FilesList.tsx +++ b/packages/storage-ui/src/Components/Modules/FilesList/FilesList.tsx @@ -757,7 +757,8 @@ const FilesList = () => { + component="h4" + > No files to show diff --git a/yarn.lock b/yarn.lock index 6e4c5d59cb..f6085a8ef0 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.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== +"@chainsafe/files-api-client@^1.18.22": + version "1.18.22" + resolved "https://registry.yarnpkg.com/@chainsafe/files-api-client/-/files-api-client-1.18.22.tgz#7e66a43f5c8b4234a53745a2be8172c32a6f0b9d" + integrity sha512-m3RfBMcWKOITyH0goMl7jxg8NY//3N2cglzKS64ksrWhULKeaCARZxe5Auo8NXEfIlTkQCKmazHoozl146UR/g== dependencies: "@redocly/openapi-cli" "^1.0.0-beta.58" "@redocly/openapi-core" "^1.0.0-beta.58" @@ -2050,6 +2050,23 @@ debug "^3.1.0" lodash.once "^4.1.1" +"@emeraldpay/hashicon-react@^0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@emeraldpay/hashicon-react/-/hashicon-react-0.5.1.tgz#975c57fdad22b8f043ad24004a620eb1012ba728" + integrity sha512-WoLPBpdwseNexAQHDmr0f9nxc8HfNpQxNI2bw/04yYpoeQg61r52iwk3EHV/QCg5hnAcF6wKdyCnybyU8UBtHQ== + dependencies: + "@emeraldpay/hashicon" "^0.5.1" + react "^16.8.0" + +"@emeraldpay/hashicon@^0.5.1": + version "0.5.1" + resolved "https://registry.yarnpkg.com/@emeraldpay/hashicon/-/hashicon-0.5.1.tgz#f2fd30dace08d3fed9db66732f803b0e6f60ce05" + integrity sha512-pMvnz6CkCqzoB2srA3B/gCKIi6h24O1Chfj919OqyCC08Kc0zCCok6iQoJ2cP5T+P2utGeR/El7jE4Q2JgmP+A== + dependencies: + "@stablelib/blake2s" "^1.0.0" + js-sha3 "^0.8.0" + text-encoding "^0.7.0" + "@emotion/cache@^10.0.27", "@emotion/cache@^10.0.9": version "10.0.29" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0" @@ -4698,6 +4715,37 @@ semver "^6.3.0" tiny-secp256k1 "^1.1.6" +"@stablelib/binary@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/binary/-/binary-1.0.1.tgz#c5900b94368baf00f811da5bdb1610963dfddf7f" + integrity sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q== + dependencies: + "@stablelib/int" "^1.0.1" + +"@stablelib/blake2s@^1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/blake2s/-/blake2s-1.0.1.tgz#0a3de2e2780172cafc9675784251a87849cf41c0" + integrity sha512-Nnp7ULL65b4zEOkf3IdfL74xHhZXMCg7HBjBYO666a0o+DIY6GDEhUCqH6dws8nsSZgZO+V5+s2VyYKKGdFMZw== + dependencies: + "@stablelib/binary" "^1.0.1" + "@stablelib/hash" "^1.0.1" + "@stablelib/wipe" "^1.0.1" + +"@stablelib/hash@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/hash/-/hash-1.0.1.tgz#3c944403ff2239fad8ebb9015e33e98444058bc5" + integrity sha512-eTPJc/stDkdtOcrNMZ6mcMK1e6yBbqRBaNW55XA1jU8w/7QdnCF0CmMmOD1m7VSkBR44PWrMHU2l6r8YEQHMgg== + +"@stablelib/int@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/int/-/int-1.0.1.tgz#75928cc25d59d73d75ae361f02128588c15fd008" + integrity sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w== + +"@stablelib/wipe@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@stablelib/wipe/-/wipe-1.0.1.tgz#d21401f1d59ade56a62e139462a97f104ed19a36" + integrity sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg== + "@storybook/addon-actions@^5.3.21": version "5.3.21" resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.3.21.tgz#16eed3eb24996adfcbf70bd476a261324d6de593" @@ -5267,6 +5315,18 @@ telejson "^3.2.0" util-deprecate "^1.0.2" +"@stripe/react-stripe-js@^1.4.1": + version "1.4.1" + resolved "https://registry.yarnpkg.com/@stripe/react-stripe-js/-/react-stripe-js-1.4.1.tgz#884d59286fff00ba77389b32c045516f65d7a340" + integrity sha512-FjcVrhf72+9fUL3Lz3xi02ni9tzH1A1x6elXlr6tvBDgSD55oPJuodoP8eC7xTnBIKq0olF5uJvgtkJyDCdzjA== + dependencies: + prop-types "^15.7.2" + +"@stripe/stripe-js@^1.18.0": + version "1.18.0" + resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.18.0.tgz#687268d7cd68b44b92b86300d7c7f2a6e4df0b98" + integrity sha512-yBRHAMKHnF3kbzv0tpKB82kSow43wW5qXLK8ofg3V9NaaCyObSTO7wJfktWAtG/NBgkJOdUL+pV8dHBj0qvDkQ== + "@svgr/babel-plugin-add-jsx-attribute@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-4.2.0.tgz#dadcb6218503532d6884b210e7f3c502caaa44b1" @@ -21086,7 +21146,7 @@ react-zoom-pan-pinch@^1.6.1: resolved "https://registry.yarnpkg.com/react-zoom-pan-pinch/-/react-zoom-pan-pinch-1.6.1.tgz#da16267c258ab37e8ebcdc7c252794a9633e91ec" integrity sha512-J2eM0gZ04XiUWvmKZrOhSAB2zjyoK7kw2POIeN1X0yTTlmp6HPGV0zYfjnlkhgt8nQwpvXAbsF/oAnkuiwk1kA== -react@16.14.0, react@^16.14.0, react@^16.8.3: +react@16.14.0, react@^16.14.0, react@^16.8.0, react@^16.8.3: version "16.14.0" resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== @@ -23506,6 +23566,11 @@ testrpc@0.0.1: resolved "https://registry.yarnpkg.com/testrpc/-/testrpc-0.0.1.tgz#83e2195b1f5873aec7be1af8cbe6dcf39edb7aed" integrity sha512-afH1hO+SQ/VPlmaLUFj2636QMeDvPCeQMc/9RBMW0IfjNe9gFD9Ra3ShqYkB7py0do1ZcCna/9acHyzTJ+GcNA== +text-encoding@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.7.0.tgz#f895e836e45990624086601798ea98e8f36ee643" + integrity sha512-oJQ3f1hrOnbRLOcwKz0Liq2IcrvDeZRHXhd9RgLrsT+DjWY/nty1Hi7v3dtkaEYbPYe0mUoOfzRrMwfXXwgPUA== + text-table@0.2.0, text-table@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"