Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

saved browsers settings #865

Merged
merged 22 commits into from
Apr 9, 2021
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 18 additions & 5 deletions packages/common-components/src/ExpansionPanel/ExpansionPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const useStyles = makeStyles(
}px`,
color: palette.additional["gray"][9],
cursor: "pointer",
display: "flex",
"&.basic": {
backgroundColor: palette.additional["gray"][2],
...overrides?.ExpansionPanel?.heading?.basic?.root,
Expand All @@ -56,6 +57,9 @@ const useStyles = makeStyles(
},
...overrides?.ExpansionPanel?.heading?.root
},
flexGrow: {
flex: 1
},
content: {
overflow: "hidden",
color: palette.additional["gray"][8],
Expand Down Expand Up @@ -90,15 +94,23 @@ export interface IExpansionPanelProps {
children?: ReactNode | ReactNode[] | null
active?: boolean
variant?: "basic" | "borderless"
iconPosition?: "left" | "right"
toggle?: (state: boolean) => void
injectedClasses?: {
root?: string
heading?: string
content?: string
}
}

const ExpansionPanel: React.FC<IExpansionPanelProps> = ({
children,
header,
iconPosition,
variant = "basic",
toggle,
active
active,
injectedClasses
}: IExpansionPanelProps) => {
const classes = useStyles()
const [activeInternal, setActive] = useState(!!active)
Expand All @@ -107,18 +119,19 @@ const ExpansionPanel: React.FC<IExpansionPanelProps> = ({
setActive(!activeInternal)
}
return (
<div className={clsx(classes.root, variant)}>
<div className={clsx(classes.root, variant, injectedClasses?.root)}>
<section
onClick={() => handleToggle()}
className={clsx(classes.heading, variant, {
className={clsx(classes.heading, variant, injectedClasses?.heading, {
["active"]: active != undefined ? active : activeInternal
})}
>
<DirectionalRightIcon className={classes.icon} />
{iconPosition === "left" && <DirectionalRightIcon className={classes.icon} />}
<Typography>{header}</Typography>
{iconPosition === "right" && <><div className={classes.flexGrow} /><DirectionalRightIcon className={classes.icon} /></>}
</section>
<section
className={clsx(classes.content, variant, {
className={clsx(classes.content, variant, injectedClasses?.content, {
["active"]: active != undefined ? active : activeInternal
})}
>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import React, { useState } from "react"
import {
makeStyles,
createStyles
} from "@chainsafe/common-theme"
import { CSFTheme } from "../../../../Themes/types"
import { Button, ExpansionPanel, Typography } from "@chainsafe/common-components"
import clsx from "clsx"
import { Trans } from "@lingui/macro"
import bowser from "bowser"
import dayjs from "dayjs"
import { useThresholdKey } from "../../../../Contexts/ThresholdKeyContext"

const useStyles = makeStyles(({ palette, constants, animation, breakpoints }: CSFTheme) =>
createStyles({
panelHeading: {
backgroundColor: palette.additional["gray"][4],
borderRadius: "10px",
padding: `${constants.generalUnit}px 0 ${constants.generalUnit}px ${constants.generalUnit * 2}px`,
transition: `border-radius ${animation.transform}ms`,
"&.active": {
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0
}
},
panelBody: {
backgroundColor: palette.additional["gray"][4],
padding: 0,
borderBottomLeftRadius: "10px",
borderBottomRightRadius: "10px",
marginTop: `-${constants.generalUnit}px`
},
panelContent: {
marginTop: constants.generalUnit,
marginBottom: constants.generalUnit,
color: palette.additional["gray"][9],
display: "flex",
flexDirection: "column"
},
subtitle: {
paddingBottom: constants.generalUnit,
lineHeight: "24px",
[breakpoints.down("md")]: {
fontSize: "14px",
lineHeight: "22px"
}
},
subtitleLast: {
lineHeight: "24px",
[breakpoints.down("md")]: {
fontSize: "14px",
lineHeight: "22px"
}
},
lightSubtitle: {
color: palette.additional["gray"][8],
paddingBottom: constants.generalUnit * 0.5,
[breakpoints.down("md")]: {
fontSize: "14px",
lineHeight: "22px"
}
},
actionBox: {
marginTop: constants.generalUnit * 2
}
})
)

interface IBrowserPanelProps {
browserInstance: bowser.Parser.ParsedResult
dateAdded: number
shareIndex: string
}

function download(filename: string, text: string) {
const element = document.createElement("a")
element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(text))
element.setAttribute("download", filename)
element.style.display = "none"
document.body.appendChild(element)
element.click()
document.body.removeChild(element)
}

const BrowserPanel: React.FC<IBrowserPanelProps> = ({
browserInstance, dateAdded, shareIndex
}) => {
const { deleteShare, getSerializedDeviceShare } = useThresholdKey()
const classes = useStyles()
const [showPanel, setShowPanel] = useState(false)
const [loadingDeleteShare, setLoadingDeleteShare] = useState(false)
const [loadingDownloadKey, setLoadingDownloadKey] = useState(false)

const onDeleteShare = async () => {
try {
setLoadingDeleteShare(true)
await deleteShare(shareIndex)
setLoadingDeleteShare(false)
setShowPanel(false)
} catch {
setLoadingDeleteShare(false)
}
}

const onDownloadKey = async () => {
try {
setLoadingDownloadKey(true)
const mnemonicKey = await getSerializedDeviceShare(shareIndex)
if (mnemonicKey) {
download(`Chainsafe Files - ${browserInstance.browser.name || ""} key.txt`, mnemonicKey)
}
setLoadingDownloadKey(false)
} catch {
setLoadingDownloadKey(false)
}
}

return (
<ExpansionPanel
header={browserInstance.browser.name || ""}
variant="borderless"
injectedClasses={{ heading: clsx(classes.panelHeading, showPanel && "active"), content: classes.panelBody }}
iconPosition={"right"}
active={showPanel}
toggle={() => setShowPanel(!showPanel)}
>
<div className={classes.panelBody}>
<div className={classes.panelContent}>
<Typography variant="body1" component="p" className={classes.subtitle}>
<Trans>Operating system:</Trans>&nbsp;{browserInstance.os.name}
</Typography>
<Typography variant="body1" component="p" className={classes.subtitle}>
<Trans>Browser: </Trans>&nbsp;{browserInstance.browser.name}&nbsp;{browserInstance.browser.version}
</Typography>
<Typography variant="body1" component="p" className={classes.subtitleLast}>
<Trans>Saved on: </Trans>&nbsp;{dayjs(dateAdded).format("DD MMM YYYY")}
</Typography>
<div className={classes.actionBox}>
<Typography variant="body1" component="p" className={classes.lightSubtitle}>
<Trans>Your recovery key can be used to restore your account in place of your backup phrase.</Trans>
</Typography>
<Button
size="small"
loading={loadingDownloadKey}
disabled={loadingDownloadKey}
onClick={onDownloadKey}
>
<Trans>Download recovery key</Trans>
</Button>
</div>
<div className={classes.actionBox}>
<Typography variant="body1" component="p" className={classes.lightSubtitle}>
<Trans>Forgetting this browser deletes this from your list of sign-in methods.
You will not be able to forget a browser if you only have two methods set up.</Trans>
</Typography>
<Button
size="small"
loading={loadingDeleteShare}
onClick={onDeleteShare}
disabled={loadingDeleteShare}
>
<Trans>Forget this browser</Trans>
</Button>
</div>
</div>
</div>
</ExpansionPanel>
)
}

export default BrowserPanel
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import React from "react"
import {
makeStyles,
createStyles
} from "@chainsafe/common-theme"
import { CSFTheme } from "../../../../Themes/types"
import { Typography } from "@chainsafe/common-components"
import BrowserPanel from "./BrowserPanel"
import { useThresholdKey } from "../../../../Contexts/ThresholdKeyContext"
import bowser from "bowser"

const useStyles = makeStyles(({ constants, breakpoints }: CSFTheme) =>
createStyles({
root: {
paddingBottom: constants.generalUnit,
[breakpoints.down("md")]: {
padding: `0 ${constants.generalUnit * 2}px`
}
},
title: {
fontSize: "16px",
lineHeight: "24px",
paddingBottom: constants.generalUnit * 2
},
expansionContainer: {
marginBottom: constants.generalUnit * 3
}
})
)

const SavedBrowsers: React.FC = () => {
const classes = useStyles()
const { keyDetails } = useThresholdKey()

const browserShareInstances: {
browserInstance: bowser.Parser.ParsedResult
dateAdded: number
shareIndex: string
}[] = []

if (keyDetails) {
Object.keys(keyDetails.shareDescriptions).forEach((shareIndex) => {
const share = JSON.parse(keyDetails.shareDescriptions[shareIndex][0])
if (share.module === "webStorage") {
try {
const browserInstance = bowser.parse(share.userAgent)
if (browserInstance) {
browserShareInstances.push({
browserInstance,
dateAdded: share.dateAdded,
shareIndex: shareIndex
})
}
} catch (e) {
console.error(e)
}
}
})
}
FSM1 marked this conversation as resolved.
Show resolved Hide resolved

return (
<div className={classes.root}>
<Typography component="p" variant="body1" className={classes.title}>
Saved Browsers
</Typography>
{browserShareInstances.map((browserShareInstance, i) => (
<div key={i} className={classes.expansionContainer}>
<BrowserPanel
browserInstance={browserShareInstance.browserInstance}
dateAdded={browserShareInstance.dateAdded}
shareIndex={browserShareInstance.shareIndex}
/>
</div>
))
}
</div>
)
}

export default SavedBrowsers
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import React, { useCallback, useMemo, useState } from "react"
import { CheckCircleSvg, CloseSvg, CrossOutlinedSvg, Grid, Typography } from "@chainsafe/common-components"
import { CheckCircleSvg, CloseSvg, CrossOutlinedSvg, Divider, Grid, Typography } from "@chainsafe/common-components"
import { makeStyles, createStyles, useThemeSwitcher } from "@chainsafe/common-theme"
import { CSFTheme } from "../../../../Themes/types"
import { t, Trans } from "@lingui/macro"
import { useThresholdKey } from "../../../../Contexts/ThresholdKeyContext"
import clsx from "clsx"
import PasswordForm from "../../../Elements/PasswordForm"
import MnemonicForm from "../../../Elements/MnemonicForm"
import SavedBrowsers from "../SavedBrowsers"

const useStyles = makeStyles(({ constants, breakpoints, palette, typography }: CSFTheme) =>
const useStyles = makeStyles(({ constants, breakpoints, palette, typography, zIndex }: CSFTheme) =>
createStyles({
root: {
paddingTop: constants.generalUnit * 2,
paddingBottom: constants.generalUnit * 3,
[breakpoints.down("md")]: {
padding: constants.generalUnit * 2
}
Expand Down Expand Up @@ -121,6 +123,9 @@ const useStyles = makeStyles(({ constants, breakpoints, palette, typography }: C
},
changeButton: {
marginLeft: "0.5rem"
},
divider: {
zIndex: zIndex?.layer1
}
})
)
Expand Down Expand Up @@ -347,6 +352,8 @@ const Security = ({ className }: SecurityProps) => {
</section>)
}
</div>
<Divider className={classes.divider} />
<SavedBrowsers />
</Grid>
</Grid>
)
Expand Down
Loading