Merge branch 'ui-redesign' into upload-redesign

This commit is contained in:
Abhinav 2022-05-24 14:20:58 +05:30
commit 7eda72902b
115 changed files with 1669 additions and 1440 deletions

View file

@ -80,7 +80,7 @@
"@types/react-select": "^4.0.15",
"@types/react-window": "^1.8.2",
"@types/react-window-infinite-loader": "^1.0.3",
"@types/styled-components": "^5.1.3",
"@types/styled-components": "^5.1.25",
"@types/yup": "^0.29.7",
"babel-plugin-styled-components": "^1.11.1",
"eslint": "^7.27.0",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,5 +1,5 @@
import { Formik, FormikHelpers } from 'formik';
import React, { useContext, useEffect, useRef, useState } from 'react';
import React, { useContext, useRef, useState } from 'react';
import * as Yup from 'yup';
import constants from 'utils/strings/constants';
import SubmitButton from 'components/SubmitButton';
@ -11,27 +11,21 @@ import { PAGES } from 'constants/pages';
import { TextField } from '@mui/material';
import Container from './Container';
import LinkButton from './pages/gallery/LinkButton';
import { Alert } from 'react-bootstrap';
import FormPaperFooter from './Form/FormPaper/Footer';
interface formValues {
email: string;
ott?: string;
}
interface Props {
showMessage: (value: boolean) => void;
setEmail: (email: string) => void;
}
function ChangeEmailForm(props: Props) {
function ChangeEmailForm() {
const [loading, setLoading] = useState(false);
const [ottInputVisible, setShowOttInputVisibility] = useState(false);
const ottInputRef = useRef(null);
const appContext = useContext(AppContext);
useEffect(() => {
if (!ottInputVisible) {
props.showMessage(false);
}
}, [ottInputVisible]);
const [email, setEmail] = useState(null);
const [showMessage, setShowMessage] = useState(false);
const requestOTT = async (
{ email }: formValues,
@ -40,9 +34,9 @@ function ChangeEmailForm(props: Props) {
try {
setLoading(true);
await getOTTForEmailChange(email);
props.setEmail(email);
setEmail(email);
setShowOttInputVisibility(true);
props.showMessage(true);
setShowMessage(true);
setTimeout(() => {
ottInputRef.current?.focus();
}, 250);
@ -71,6 +65,8 @@ function ChangeEmailForm(props: Props) {
setLoading(false);
};
const goToGallery = () => router.push(PAGES.GALLERY);
return (
<Formik<formValues>
initialValues={{ email: '' }}
@ -78,23 +74,31 @@ function ChangeEmailForm(props: Props) {
email: Yup.string()
.email(constants.EMAIL_ERROR)
.required(constants.REQUIRED),
ott:
ottInputVisible &&
Yup.string().required(constants.REQUIRED),
})}
validateOnChange={false}
validateOnBlur={false}
onSubmit={!ottInputVisible ? requestOTT : requestEmailChange}>
{({ values, errors, handleChange, handleSubmit }) => (
<>
<form
noValidate
style={{ width: '100%' }}
onSubmit={handleSubmit}>
<Alert
variant="success"
show={showMessage}
style={{ paddingBottom: 0 }}
transition
dismissible
onClose={() => setShowMessage(false)}>
{constants.EMAIL_SENT({ email })}
</Alert>
<form noValidate onSubmit={handleSubmit}>
<Container>
<TextField
fullWidth
InputProps={{
readOnly: ottInputVisible,
}}
margin="normal"
type="email"
label={constants.ENTER_EMAIL}
value={values.email}
@ -117,6 +121,7 @@ function ChangeEmailForm(props: Props) {
/>
)}
<SubmitButton
sx={{ mt: 2 }}
loading={loading}
buttonText={
!ottInputVisible
@ -126,12 +131,23 @@ function ChangeEmailForm(props: Props) {
/>
</Container>
</form>
<FormPaperFooter
style={{
justifyContent: ottInputVisible && 'space-between',
}}>
{ottInputVisible && (
<LinkButton
onClick={() => setShowOttInputVisibility(false)}>
onClick={() =>
setShowOttInputVisibility(false)
}>
{constants.CHANGE_EMAIL}?
</LinkButton>
)}
<LinkButton onClick={goToGallery}>
{constants.GO_BACK}
</LinkButton>
</FormPaperFooter>
</>
)}
</Formik>

View file

@ -1,89 +0,0 @@
import styled from 'styled-components';
import { FreeFlowText, IconButton } from './Container';
import React, { useState } from 'react';
import { Tooltip, OverlayTrigger } from 'react-bootstrap';
import EnteSpinner from './EnteSpinner';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import DoneIcon from '@mui/icons-material/Done';
const Wrapper = styled.div`
position: relative;
margin: ${({ theme }) => theme.spacing(2)};
border-radius: ${({ theme }) => theme.shape.borderRadius}px;
`;
const CopyButtonWrapper = styled(IconButton)`
position: absolute;
top: 0px;
right: 0px;
background: none !important;
margin: 10px;
`;
export const CodeWrapper = styled.div`
display: flex;
align-items: center;
justify-content: center;
background: #1a1919;
padding: 37px 40px 20px 20px;
color: white;
background: ${({ theme }) => theme.palette.accent.dark};
border-radius: ${({ theme }) => theme.shape.borderRadius}px;
width: 100%;
`;
type Iprops = React.PropsWithChildren<{
code: string;
wordBreak?: 'normal' | 'break-all' | 'keep-all' | 'break-word';
}>;
export const CodeBlock = (props: Iprops) => {
const [copied, setCopied] = useState<boolean>(false);
const copyToClipboardHelper = (text: string) => () => {
navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 1000);
};
const RenderCopiedMessage = (props) => {
const { style, ...rest } = props;
return (
<Tooltip
{...rest}
style={{ ...style, zIndex: 2001 }}
id="button-tooltip">
copied
</Tooltip>
);
};
return (
<Wrapper>
<CodeWrapper>
{props.code ? (
<FreeFlowText style={{ wordBreak: props.wordBreak }}>
{props.code}
</FreeFlowText>
) : (
<EnteSpinner />
)}
</CodeWrapper>
{props.code && (
<OverlayTrigger
show={copied}
placement="bottom"
trigger={'click'}
overlay={RenderCopiedMessage}
delay={{ show: 200, hide: 800 }}>
<CopyButtonWrapper
onClick={copyToClipboardHelper(props.code)}>
{copied ? (
<DoneIcon fontSize="small" />
) : (
<ContentCopyIcon fontSize="small" />
)}
</CopyButtonWrapper>
</OverlayTrigger>
)}
</Wrapper>
);
};

View file

@ -0,0 +1,20 @@
import React from 'react';
import constants from 'utils/strings/constants';
import { CopyButtonWrapper } from './styledComponents';
import DoneIcon from '@mui/icons-material/Done';
import ContentCopyIcon from '@mui/icons-material/ContentCopy';
import { Tooltip } from '@mui/material';
export default function CopyButton({ code, copied, copyToClipboardHelper }) {
return (
<Tooltip arrow open={copied} title={constants.COPIED}>
<CopyButtonWrapper onClick={copyToClipboardHelper(code)}>
{copied ? (
<DoneIcon fontSize="small" />
) : (
<ContentCopyIcon fontSize="small" />
)}
</CopyButtonWrapper>
</Tooltip>
);
}

View file

@ -0,0 +1,47 @@
import { FreeFlowText } from '../Container';
import React, { useState } from 'react';
import EnteSpinner from '../EnteSpinner';
import { Wrapper, CodeWrapper } from './styledComponents';
import CopyButton from './CopyButton';
import { BoxProps } from '@mui/material';
type Iprops = React.PropsWithChildren<{
code: string;
wordBreak?: 'normal' | 'break-all' | 'keep-all' | 'break-word';
}>;
export default function CodeBlock({
code,
wordBreak,
...props
}: BoxProps<'div', Iprops>) {
const [copied, setCopied] = useState<boolean>(false);
const copyToClipboardHelper = (text: string) => () => {
navigator.clipboard.writeText(text);
setCopied(true);
setTimeout(() => setCopied(false), 1000);
};
if (!code) {
return (
<Wrapper>
<EnteSpinner />
</Wrapper>
);
}
return (
<Wrapper {...props}>
<CodeWrapper>
<FreeFlowText style={{ wordBreak: wordBreak }}>
{code}
</FreeFlowText>
</CodeWrapper>
<CopyButton
code={code}
copied={copied}
copyToClipboardHelper={copyToClipboardHelper}
/>
</Wrapper>
);
}

View file

@ -0,0 +1,21 @@
import { IconButton } from '@mui/material';
import { CenteredFlex } from 'components/Container';
import styled from 'styled-components';
export const Wrapper = styled(CenteredFlex)`
position: relative;
background: ${({ theme }) => theme.palette.accent.dark};
border-radius: ${({ theme }) => theme.shape.borderRadius}px;
min-height: 80px;
`;
export const CopyButtonWrapper = styled(IconButton)`
position: absolute;
top: 0px;
right: 0px;
margin: ${({ theme }) => theme.spacing(1)};
`;
export const CodeWrapper = styled.div`
padding: 36px 36px 16px 16px;
border-radius: ${({ theme }) => theme.shape.borderRadius}px;
`;

View file

@ -1,114 +0,0 @@
import { CollectionSummaries } from 'types/collection';
interface Iprops {
isOpen: boolean;
close: () => void;
collectionSummaries: CollectionSummaries;
setActiveCollection: (id?: number) => void;
}
import * as React from 'react';
import DialogContent from '@mui/material/DialogContent';
import Typography from '@mui/material/Typography';
import constants from 'utils/strings/constants';
import { FlexWrapper, SpaceBetweenFlex } from 'components/Container';
import { LargerCollectionTile } from './styledComponents';
import CollectionCard from './CollectionCard';
import Divider from '@mui/material/Divider';
import CollectionSort from 'components/pages/gallery/CollectionSort';
import { CollectionType, COLLECTION_SORT_BY } from 'constants/collection';
import { sortCollectionSummaries } from 'services/collectionService';
import {
Transition,
FloatingDrawer,
} from 'components/Collections/FloatingDrawer';
import { useLocalState } from 'hooks/useLocalState';
import { LS_KEYS } from 'utils/storage/localStorage';
import DialogTitleWithCloseButton from 'components/MessageDialog/TitleWithCloseButton';
import { DialogTitle } from '@mui/material';
const LeftSlideTransition = Transition('up');
export default function AllCollections(props: Iprops) {
const { collectionSummaries, isOpen, close, setActiveCollection } = props;
const onCollectionClick = (collectionID: number) => {
setActiveCollection(collectionID);
close();
};
const [collectionSortBy, setCollectionSortBy] =
useLocalState<COLLECTION_SORT_BY>(
LS_KEYS.COLLECTION_SORT_BY,
COLLECTION_SORT_BY.UPDATION_TIME_DESCENDING
);
return (
<>
<FloatingDrawer
position="right"
TransitionComponent={LeftSlideTransition}
onClose={close}
open={isOpen}>
<DialogTitleWithCloseButton onClose={close} sx={{ pb: 0 }}>
<Typography variant="h6">
<strong>{constants.ALL_ALBUMS}</strong>
</Typography>
</DialogTitleWithCloseButton>
<DialogTitle sx={{ pt: 0 }}>
<SpaceBetweenFlex>
<Typography variant="subtitle1">
{`${[...props.collectionSummaries.keys()].length} ${
constants.ALBUMS
}`}
</Typography>
<CollectionSort
activeSortBy={collectionSortBy}
setCollectionSortBy={setCollectionSortBy}
/>
</SpaceBetweenFlex>
</DialogTitle>
<Divider />
<DialogContent>
<FlexWrapper style={{ flexWrap: 'wrap' }}>
{sortCollectionSummaries(
[...collectionSummaries.values()].filter(
(x) =>
x.collectionAttributes.type !==
CollectionType.system
),
collectionSortBy
).map(
({
latestFile,
collectionAttributes,
fileCount,
}) => (
<CollectionCard
key={collectionAttributes.id}
latestFile={latestFile}
onClick={() =>
onCollectionClick(
collectionAttributes.id
)
}
customCollectionTile={LargerCollectionTile}>
<div>
<Typography>
<strong>
{collectionAttributes.name}
</strong>
</Typography>
<Typography>
{fileCount} {constants.PHOTOS}
</Typography>
</div>
</CollectionCard>
)
)}
</FlexWrapper>
</DialogContent>
</FloatingDrawer>
</>
);
}

View file

@ -0,0 +1,37 @@
import { Typography } from '@mui/material';
import constants from 'utils/strings/constants';
import React from 'react';
import CollectionCard from '../CollectionCard';
export default function AllCollectionCard({
onCollectionClick,
collectionAttributes,
latestFile,
fileCount,
}) {
return (
<CollectionCard
large
latestFile={latestFile}
onClick={() => onCollectionClick(collectionAttributes.id)}>
<div>
<Typography
css={`
font-size: 14px;
font-weight: 600;
line-height: 20px;
`}>
{collectionAttributes.name}
</Typography>
<Typography
css={`
font-size: 14px;
font-weight: 400;
line-height: 20px;
`}>
{fileCount} {constants.PHOTOS}
</Typography>
</div>
</CollectionCard>
);
}

View file

@ -0,0 +1,44 @@
import React, { useState } from 'react';
import { COLLECTION_SORT_BY } from 'constants/collection';
import Menu from '@mui/material/Menu';
import { IconButton, styled } from '@mui/material';
import SortIcon from '@mui/icons-material/Sort';
import CollectionSortOptions from './options';
export interface CollectionSortProps {
setCollectionSortBy: (sortBy: COLLECTION_SORT_BY) => void;
activeSortBy: COLLECTION_SORT_BY;
}
const StyledMenu = styled(Menu)`
& .MuiPaper-root {
box-shadow: 0px 0px 10px 0px rgba(0, 0, 0, 0.16);
}
`;
export default function CollectionSort(props: CollectionSortProps) {
const [sortByEl, setSortByEl] = useState(null);
const handleClose = () => setSortByEl(null);
return (
<>
<IconButton
onClick={(event) => setSortByEl(event.currentTarget)}
aria-controls={sortByEl ? 'collection-sort' : undefined}
aria-haspopup="true"
aria-expanded={sortByEl ? 'true' : undefined}>
<SortIcon />
</IconButton>
<StyledMenu
id="collection-sort"
anchorEl={sortByEl}
open={Boolean(sortByEl)}
onClose={handleClose}
MenuListProps={{
disablePadding: true,
'aria-labelledby': 'collection-sort',
}}>
<CollectionSortOptions {...props} close={handleClose} />
</StyledMenu>
</>
);
}

View file

@ -0,0 +1,35 @@
import React from 'react';
import { MenuItem, ListItemIcon, ListItemText } from '@mui/material';
import { COLLECTION_SORT_BY } from 'constants/collection';
import TickIcon from '@mui/icons-material/Done';
import { CollectionSortProps } from '.';
export interface SortOptionProps extends CollectionSortProps {
close: () => void;
}
const SortByOptionCreator =
({ setCollectionSortBy, activeSortBy, close }: SortOptionProps) =>
(props: { sortBy: COLLECTION_SORT_BY; children: any }) => {
const handleClick = () => {
setCollectionSortBy(props.sortBy);
close();
};
return (
<MenuItem onClick={handleClick} style={{ paddingLeft: '5px' }}>
<ListItemIcon style={{ minWidth: '25px' }}>
{activeSortBy === props.sortBy && (
<TickIcon
css={`
height: 16px;
width: 16px;
`}
/>
)}
</ListItemIcon>
<ListItemText>{props.children}</ListItemText>
</MenuItem>
);
};
export default SortByOptionCreator;

View file

@ -0,0 +1,26 @@
import React from 'react';
import { MenuList } from '@mui/material';
import { COLLECTION_SORT_BY } from 'constants/collection';
import constants from 'utils/strings/constants';
import SortByOptionCreator, { SortOptionProps } from './optionCreator';
export default function CollectionSortOptions(props: SortOptionProps) {
const SortByOption = SortByOptionCreator(props);
return (
<MenuList>
<SortByOption sortBy={COLLECTION_SORT_BY.NAME}>
{constants.SORT_BY_NAME}
</SortByOption>
<SortByOption sortBy={COLLECTION_SORT_BY.CREATION_TIME_DESCENDING}>
{constants.SORT_BY_CREATION_TIME_DESCENDING}
</SortByOption>
<SortByOption sortBy={COLLECTION_SORT_BY.CREATION_TIME_ASCENDING}>
{constants.SORT_BY_CREATION_TIME_ASCENDING}
</SortByOption>
<SortByOption sortBy={COLLECTION_SORT_BY.UPDATION_TIME_DESCENDING}>
{constants.SORT_BY_UPDATION_TIME_DESCENDING}
</SortByOption>
</MenuList>
);
}

View file

@ -0,0 +1,30 @@
import React from 'react';
import { DialogContent } from '@mui/material';
import { FlexWrapper } from 'components/Container';
import AllCollectionCard from './CollectionCard';
export default function AllCollectionContent({
sortedCollectionSummaries,
onCollectionClick,
}) {
return (
<DialogContent>
<FlexWrapper
style={{
flexWrap: 'wrap',
}}>
{sortedCollectionSummaries.map(
({ latestFile, collectionAttributes, fileCount }) => (
<AllCollectionCard
onCollectionClick={onCollectionClick}
collectionAttributes={collectionAttributes}
key={collectionAttributes.id}
latestFile={latestFile}
fileCount={fileCount}
/>
)
)}
</FlexWrapper>
</DialogContent>
);
}

View file

@ -0,0 +1,46 @@
import React from 'react';
import { DialogTitle, IconButton, Typography } from '@mui/material';
import { SpaceBetweenFlex } from 'components/Container';
import CollectionSort from 'components/Collections/AllCollections/CollectionSort';
import constants from 'utils/strings/constants';
import Close from '@mui/icons-material/Close';
export default function AllCollectionsHeader({
onClose,
collectionCount,
collectionSortBy,
setCollectionSortBy,
}) {
return (
<DialogTitle>
<SpaceBetweenFlex>
<Typography
css={`
font-size: 24px;
font-weight: 600;
line-height: 36px;
`}>
{constants.ALL_ALBUMS}
</Typography>
<IconButton onClick={onClose}>
<Close />
</IconButton>
</SpaceBetweenFlex>
<SpaceBetweenFlex>
<Typography
css={`
font-size: 24px;
font-weight: 600;
line-height: 36px;
`}
color={'text.secondary'}>
{`${collectionCount} ${constants.ALBUMS}`}
</Typography>
<CollectionSort
activeSortBy={collectionSortBy}
setCollectionSortBy={setCollectionSortBy}
/>
</SpaceBetweenFlex>
</DialogTitle>
);
}

View file

@ -0,0 +1,67 @@
import React, { useMemo } from 'react';
import Divider from '@mui/material/Divider';
import { CollectionType, COLLECTION_SORT_BY } from 'constants/collection';
import { sortCollectionSummaries } from 'services/collectionService';
import {
Transition,
FloatingDrawer,
} from 'components/Collections/FloatingDrawer';
import { useLocalState } from 'hooks/useLocalState';
import { LS_KEYS } from 'utils/storage/localStorage';
import AllCollectionsHeader from './header';
import { CollectionSummaries } from 'types/collection';
import AllCollectionContent from './content';
interface Iprops {
isOpen: boolean;
close: () => void;
collectionSummaries: CollectionSummaries;
setActiveCollection: (id?: number) => void;
}
const LeftSlideTransition = Transition('up');
export default function AllCollections(props: Iprops) {
const { collectionSummaries, isOpen, close, setActiveCollection } = props;
const [collectionSortBy, setCollectionSortBy] =
useLocalState<COLLECTION_SORT_BY>(
LS_KEYS.COLLECTION_SORT_BY,
COLLECTION_SORT_BY.UPDATION_TIME_DESCENDING
);
const sortedCollectionSummaries = useMemo(
() =>
sortCollectionSummaries(
[...collectionSummaries.values()].filter(
(x) => x.collectionAttributes.type !== CollectionType.system
),
collectionSortBy
),
[collectionSortBy, collectionSummaries]
);
const onCollectionClick = (collectionID: number) => {
setActiveCollection(collectionID);
close();
};
return (
<FloatingDrawer
TransitionComponent={LeftSlideTransition}
onClose={close}
open={isOpen}>
<AllCollectionsHeader
onClose={close}
collectionCount={props.collectionSummaries.size}
collectionSortBy={collectionSortBy}
setCollectionSortBy={setCollectionSortBy}
/>
<Divider />
<AllCollectionContent
sortedCollectionSummaries={sortedCollectionSummaries}
onCollectionClick={onCollectionClick}
/>
</FloatingDrawer>
);
}

View file

@ -1,7 +1,7 @@
import React from 'react';
import { EnteFile } from 'types/file';
import { CollectionTileWrapper, ActiveIndicator } from './styledComponents';
import CollectionCard from './CollectionCard';
import { CollectionTileWrapper, ActiveIndicator } from '../styledComponents';
import CollectionCard from '../CollectionCard';
const CollectionCardWithActiveIndicator = React.forwardRef(
(

View file

@ -1,6 +1,6 @@
import React from 'react';
import constants from 'utils/strings/englishConstants';
import { CollectionTitleWithDashedBorder } from './styledComponents';
import { CollectionTitleWithDashedBorder } from '../styledComponents';
export const CreateNewCollectionTile = (props) => {
return (

View file

@ -1,6 +1,6 @@
import React from 'react';
import styled, { css } from 'styled-components';
import NavigateNext from '../icons/NavigateNext';
import NavigateNextIcon from '@mui/icons-material/NavigateNext';
export enum SCROLL_DIRECTION {
LEFT = -1,
@ -8,15 +8,18 @@ export enum SCROLL_DIRECTION {
}
const Wrapper = styled.button<{ direction: SCROLL_DIRECTION }>`
position: absolute;
top: 7px;
height: 50px;
width: 50px;
border: none;
padding: 0;
margin: 0;
border-radius: 50%;
background-color: ${({ theme }) => theme.palette.background.paper};
border: none;
color: ${({ theme }) => theme.palette.text.primary};
position: absolute;
${(props) =>
props.direction === SCROLL_DIRECTION.LEFT
? css`
@ -38,15 +41,11 @@ const Wrapper = styled.button<{ direction: SCROLL_DIRECTION }>`
height: 30px;
width: 30px;
}
&:hover {
color: #fff;
}
`;
const NavigationButton = ({ scrollDirection, ...rest }) => (
<Wrapper direction={scrollDirection} {...rest}>
<NavigateNext />
<NavigateNextIcon />
</Wrapper>
);
export default NavigationButton;

View file

@ -1,6 +1,6 @@
import NavigationButton, {
SCROLL_DIRECTION,
} from 'components/Collections/NavigationButton';
} from 'components/Collections/CollectionBar/NavigationButton';
import React, { useEffect } from 'react';
import { Collection, CollectionSummaries } from 'types/collection';
import constants from 'utils/strings/constants';
@ -12,7 +12,7 @@ import {
ScrollContainer,
PaddedSpaceBetweenFlex,
} from 'components/Collections/styledComponents';
import CollectionCardWithActiveIndicator from 'components/Collections/CollectionCardWithActiveIndicator';
import CollectionCardWithActiveIndicator from 'components/Collections/CollectionBar/CollectionCardWithActiveIndicator';
import useComponentScroll from 'hooks/useComponentScroll';
import useWindowSize from 'hooks/useWindowSize';
import LinkButton from 'components/pages/gallery/LinkButton';

View file

@ -3,15 +3,15 @@ import { GalleryContext } from 'pages/gallery';
import { useState, useContext, useEffect } from 'react';
import downloadManager from 'services/downloadManager';
import { EnteFile } from 'types/file';
import { CollectionTile } from './styledComponents';
import { CollectionTile, LargerCollectionTile } from './styledComponents';
export default function CollectionCard(props: {
children?: any;
latestFile: EnteFile;
onClick: () => void;
customCollectionTile?: any;
large?: boolean;
}) {
const { latestFile: file, onClick, children, customCollectionTile } = props;
const { latestFile: file, onClick, children, large } = props;
const [coverImageURL, setCoverImageURL] = useState(null);
const galleryContext = useContext(GalleryContext);
@ -28,7 +28,7 @@ export default function CollectionCard(props: {
};
main();
}, [file]);
const UsedCollectionTile = customCollectionTile ?? CollectionTile;
const UsedCollectionTile = large ? LargerCollectionTile : CollectionTile;
return (
<UsedCollectionTile coverImgURL={coverImageURL} onClick={onClick}>
{children}

View file

@ -25,10 +25,19 @@ export default function collectionInfo(props: Iprops) {
return (
<PaddedSpaceBetweenFlex>
<div>
<Typography variant="h5">
<strong>{collectionAttributes.name}</strong>
<Typography
css={`
font-size: 24px;
font-weight: 600;
line-height: 36px;
`}>
{collectionAttributes.name}
</Typography>
<Typography variant="subtitle1">
<Typography
css={`
font-size: 14px;
line-height: 20px;
`}>
{fileCount} {constants.PHOTOS}
</Typography>
</div>

View file

@ -1,10 +1,17 @@
import React from 'react';
import { Dialog, DialogContent, TextField } from '@mui/material';
import {
Dialog,
DialogContent,
DialogTitle,
IconButton,
TextField,
} from '@mui/material';
import constants from 'utils/strings/constants';
import SubmitButton from 'components/SubmitButton';
import { Formik } from 'formik';
import * as Yup from 'yup';
import DialogTitleWithCloseButton from 'components/MessageDialog/TitleWithCloseButton';
import { SpaceBetweenFlex } from 'components/Container';
import Close from '@mui/icons-material/Close';
export interface CollectionNamerAttributes {
callback: (name) => void;
@ -37,9 +44,14 @@ export default function CollectionNamer({ attributes, ...props }: Props) {
return (
<Dialog open={props.show} onClose={props.onHide} maxWidth="xs">
<DialogTitleWithCloseButton onClose={props.onHide}>
<DialogTitle>
<SpaceBetweenFlex>
{attributes?.title}
</DialogTitleWithCloseButton>
<IconButton onClick={props.onHide}>
<Close />
</IconButton>
</SpaceBetweenFlex>
</DialogTitle>
<DialogContent>
<Formik<formValues>
initialValues={{

View file

@ -174,6 +174,7 @@ const CollectionOptions = (props: CollectionOptionsProps) => {
open={Boolean(optionEl)}
onClose={handleClose}
MenuListProps={{
disablePadding: true,
'aria-labelledby': 'collection-options',
}}>
<Paper>

View file

@ -17,7 +17,7 @@ import {
} from 'services/collectionService';
import { getData, LS_KEYS } from 'utils/storage/localStorage';
import SubmitButton from '../SubmitButton';
import MessageDialog from '../MessageDialog';
import DialogBox from '../DialogBox';
import { Collection, PublicURL, UpdatePublicURL } from 'types/collection';
import {
appendCollectionKeyToShareURL,
@ -25,7 +25,7 @@ import {
shareExpiryOptions,
} from 'utils/collection';
import { FlexWrapper, Label, Row, Value } from '../Container';
import { CodeBlock } from '../CodeBlock';
import CodeBlock from '../CodeBlock';
import { ButtonVariant, getVariantColor } from '../pages/gallery/LinkButton';
import { handleSharingErrors } from 'utils/error';
import { sleep } from 'utils/common';
@ -351,9 +351,9 @@ function CollectionShare(props: Props) {
}
return (
<MessageDialog
show={props.show}
onHide={props.onHide}
<DialogBox
open={props.show}
onClose={props.onHide}
attributes={{
title: constants.SHARE_COLLECTION,
staticBackdrop: true,
@ -573,9 +573,9 @@ function CollectionShare(props: Props) {
</OptionValue>
</OptionRow>
</section>
<MessageDialog
show={configurePassword}
onHide={() => setConfigurePassword(false)}
<DialogBox
open={configurePassword}
onClose={() => setConfigurePassword(false)}
size="sm"
attributes={{
title: constants.PASSWORD_LOCK,
@ -588,7 +588,7 @@ function CollectionShare(props: Props) {
buttonText={constants.LOCK}
fieldType="password"
/>
</MessageDialog>
</DialogBox>
</details>
</>
) : (
@ -601,7 +601,7 @@ function CollectionShare(props: Props) {
/>
)}
</DeadCenter>
</MessageDialog>
</DialogBox>
);
}
export default CollectionShare;

View file

@ -2,22 +2,20 @@ import { Dialog, Slide, styled } from '@mui/material';
import React from 'react';
import PropTypes from 'prop-types';
export const FloatingDrawer = styled(Dialog)<{ position: 'left' | 'right' }>(
({ position, theme }) => ({
export const FloatingDrawer = styled(Dialog)(({ theme }) => ({
'& .MuiDialog-container': {
justifyContent: 'flex-end',
},
'& .MuiPaper-root': {
maxWidth: '498px',
},
'& .MuiDialogTitle-root': {
padding: theme.spacing(3, 2),
},
'& .MuiDialogContent-root': {
padding: theme.spacing(2),
},
'& .MuiDialogActions-root': {
padding: theme.spacing(1),
},
'& .MuiPaper-root': {
maxWidth: '510px',
},
'& .MuiDialog-container': {
justifyContent: position === 'left' ? 'flex-start' : 'flex-end',
},
})
);
}));
FloatingDrawer.propTypes = {
children: PropTypes.node,

View file

@ -13,7 +13,7 @@ export const CollectionBarWrapper = styled.div`
@media (max-width: ${IMAGE_CONTAINER_MAX_WIDTH * 4}px) {
padding: 0 4px;
}
border-bottom: 1px solid ${({ theme }) => theme.palette.grey.A400};
border-bottom: 1px solid ${({ theme }) => theme.palette.grey.A200};
`;
export const PaddedSpaceBetweenFlex = styled(SpaceBetweenFlex)`
@ -37,7 +37,6 @@ export const ScrollContainer = styled.div`
export const CollectionTile = styled.div<{
coverImgURL?: string;
}>`
flex-shrink: 0;
display: flex;
width: 80px;
height: 64px;
@ -50,10 +49,12 @@ export const CollectionTile = styled.div<{
background-image: url(${({ coverImgURL }) => coverImgURL});
background-size: cover;
border: 1px solid ${({ theme }) => theme.palette.grey.A200};
font-size: 14px;
line-height: 20px;
`;
export const CollectionTileWrapper = styled.div`
margin-right: 6px;
margin-right: 4px;
`;
export const ActiveIndicator = styled.div`
@ -72,7 +73,7 @@ export const LargerCollectionTile = styled(CollectionTile)`
width: 150px;
height: 150px;
align-items: flex-start;
margin: 4px;
margin: 2px;
`;
export const CollectionTitleWithDashedBorder = styled(CollectionTile)`

View file

@ -1,8 +1,8 @@
import { Box } from '@mui/material';
import styled from 'styled-components';
import { default as MuiStyled } from '@mui/styled-engine';
import { Container as MuiContainer } from '@mui/material';
const Container = MuiStyled(MuiContainer)`
const VerticallyCentered = MuiStyled(Box)`
flex: 1;
display: flex;
align-items: center;
@ -12,7 +12,7 @@ const Container = MuiStyled(MuiContainer)`
overflow: auto;
`;
export default Container;
export default VerticallyCentered;
export const DisclaimerContainer = styled.div`
margin: 16px 0;
@ -56,9 +56,8 @@ export const Value = styled.div<{ width?: string }>`
color: #ddd;
`;
export const FlexWrapper = styled.div`
export const FlexWrapper = styled(Box)`
display: flex;
width: 100%;
align-items: center;
`;
@ -72,6 +71,14 @@ export const SpaceBetweenFlex = styled(FlexWrapper)`
justify-content: space-between;
`;
export const CenteredFlex = styled(FlexWrapper)`
justify-content: center;
`;
export const FluidContainer = styled(FlexWrapper)`
flex: 1;
`;
export const InvertedIconButton = styled(IconButton)`
background-color: ${({ theme }) => theme.palette.primary.main};
color: ${({ theme }) => theme.palette.background.default};

View file

@ -0,0 +1,26 @@
import { Dialog, styled } from '@mui/material';
const DialogBoxBase = styled(Dialog)(({ theme }) => ({
'& .MuiDialogTitle-root': {
padding: theme.spacing(4, 3, 2),
},
'& .MuiDialogContent-root': {
padding: theme.spacing(0, 3, 2),
},
'& .MuiDialogActions-root': {
padding: theme.spacing(4, 3),
},
'& .MuiDialogActions-root button': {
marginLeft: theme.spacing(2),
fontSize: '18px',
lineHeight: '21.78px',
padding: theme.spacing(2),
},
}));
DialogBoxBase.defaultProps = {
fullWidth: true,
maxWidth: 'sm',
};
export default DialogBoxBase;

View file

@ -0,0 +1,92 @@
import React from 'react';
import constants from 'utils/strings/constants';
import {
Breakpoint,
Button,
Dialog,
DialogActions,
DialogContent,
DialogProps,
} from '@mui/material';
import DialogTitleWithCloseButton from './titleWithCloseButton';
import MessageText from './messageText';
import DialogBoxBase from './base';
import { DialogBoxAttributes } from 'types/dialogBox';
type IProps = React.PropsWithChildren<
Omit<DialogProps, 'onClose' | 'maxSize'> & {
onClose: () => void;
attributes: DialogBoxAttributes;
size?: Breakpoint;
}
>;
export default function DialogBox({ attributes, children, ...props }: IProps) {
if (!attributes) {
return <Dialog open={false} />;
}
const handleClose: DialogProps['onClose'] = (_, reason) => {
if (attributes?.nonClosable) {
// no-op
} else if (attributes?.staticBackdrop && reason === 'backdropClick') {
// no-op
} else {
props.onClose();
}
};
return (
<DialogBoxBase
open={props.open}
maxWidth={props.size}
onClose={handleClose}
{...props}>
{attributes.title && (
<DialogTitleWithCloseButton
onClose={
!attributes?.nonClosable &&
attributes?.close?.titleCloseButton &&
handleClose
}>
{attributes.title}
</DialogTitleWithCloseButton>
)}
{(children || attributes?.content) && (
<DialogContent>
{children || (
<MessageText>{attributes.content}</MessageText>
)}
</DialogContent>
)}
{(attributes.close || attributes.proceed) && (
<DialogActions>
<>
{attributes.close && (
<Button
color={attributes.close?.variant ?? 'secondary'}
onClick={() => {
attributes.close.action &&
attributes.close?.action();
props.onClose();
}}>
{attributes.close?.text ?? constants.OK}
</Button>
)}
{attributes.proceed && (
<Button
color={attributes.proceed?.variant}
onClick={() => {
attributes.proceed.action();
props.onClose();
}}
disabled={attributes.proceed.disabled}>
{attributes.proceed.text}
</Button>
)}
</>
</DialogActions>
)}
</DialogBoxBase>
);
}

View file

@ -0,0 +1,9 @@
import { DialogContentText, styled } from '@mui/material';
const MessageText = styled(DialogContentText)(({ theme }) => ({
paddingBottom: theme.spacing(2),
fontSize: '20px',
lineHeight: '24.2px',
}));
export default MessageText;

View file

@ -0,0 +1,26 @@
import React from 'react';
import { DialogTitle, IconButton, Typography } from '@mui/material';
import CloseIcon from '@mui/icons-material/Close';
import { SpaceBetweenFlex } from 'components/Container';
const DialogTitleWithCloseButton = (props) => {
const { children, onClose, ...other } = props;
return (
<DialogTitle {...other}>
<SpaceBetweenFlex>
<Typography variant="title">{children}</Typography>
{onClose && (
<IconButton
aria-label="close"
onClick={onClose}
sx={{ float: 'right' }}>
<CloseIcon />
</IconButton>
)}
</SpaceBetweenFlex>
</DialogTitle>
);
};
export default DialogTitleWithCloseButton;

View file

@ -1,20 +1,6 @@
import React from 'react';
import { Spinner } from 'react-bootstrap';
import CircularProgress from '@mui/material/CircularProgress';
export default function EnteSpinner(props) {
const { style, ...others } = props ?? {};
return (
<Spinner
animation="border"
style={{
width: '36px',
height: '36px',
borderWidth: '0.20em',
color: '#51cd7c',
...(style && style),
}}
{...others}
role="status"
/>
);
return <CircularProgress color="accent" size={32} {...props} />;
}

View file

@ -17,7 +17,7 @@ import ExportInit from './ExportInit';
import ExportInProgress from './ExportInProgress';
import FolderIcon from './icons/FolderIcon';
import InProgressIcon from './icons/InProgressIcon';
import MessageDialog from './MessageDialog';
import DialogBox from './DialogBox';
import { ExportStage, ExportType } from 'constants/export';
const FolderIconWrapper = styled.div`
@ -312,9 +312,9 @@ export default function ExportModal(props: Props) {
};
return (
<MessageDialog
show={props.show}
onHide={props.onHide}
<DialogBox
open={props.show}
onClose={props.onHide}
attributes={{
title: constants.EXPORT_DATA,
}}>
@ -359,6 +359,6 @@ export default function ExportModal(props: Props) {
</Row>
</div>
<ExportDynamicState />
</MessageDialog>
</DialogBox>
);
}

View file

@ -1,5 +1,5 @@
import constants from 'utils/strings/constants';
import MessageDialog from '../MessageDialog';
import DialogBox from '../DialogBox';
import React, { useContext, useEffect, useState } from 'react';
import { updateCreationTimeWithExif } from 'services/updateCreationTimeWithExif';
import { GalleryContext } from 'pages/gallery';
@ -93,9 +93,9 @@ export default function FixCreationTime(props: Props) {
};
return (
<MessageDialog
show={props.isOpen}
onHide={props.hide}
<DialogBox
open={props.isOpen}
onClose={props.hide}
attributes={{
title:
fixState === FIX_STATE.RUNNING
@ -147,6 +147,6 @@ export default function FixCreationTime(props: Props) {
)}
</Formik>
</div>
</MessageDialog>
</DialogBox>
);
}

View file

@ -1,5 +1,5 @@
import constants from 'utils/strings/constants';
import MessageDialog from './MessageDialog';
import DialogBox from './DialogBox';
import React, { useEffect, useState } from 'react';
import { ProgressBar, Button } from 'react-bootstrap';
import { ComfySpan } from './ExportInProgress';
@ -136,9 +136,9 @@ export default function FixLargeThumbnails(props: Props) {
setData(LS_KEYS.THUMBNAIL_FIX_STATE, { state: fixState });
};
return (
<MessageDialog
show={props.isOpen}
onHide={props.hide}
<DialogBox
open={props.isOpen}
onClose={props.hide}
attributes={{
title: constants.COMPRESS_THUMBNAILS,
staticBackdrop: true,
@ -224,6 +224,6 @@ export default function FixLargeThumbnails(props: Props) {
)}
</div>
</div>
</MessageDialog>
</DialogBox>
);
}

View file

@ -1,14 +1,12 @@
import React from 'react';
import { ContainerProps } from '@mui/material';
import { FC } from 'react';
import Container from 'components/Container';
import { styled } from '@mui/material/styles';
import VerticallyCentered from 'components/Container';
const FormContainer: FC<ContainerProps> = ({ style, children, ...props }) => (
<Container
style={{ alignItems: 'flex-end', textAlign: 'left', ...style }}
{...props}>
{children}
</Container>
);
const FormContainer = styled(VerticallyCentered)(({ theme }) => ({
alignItems: 'flex-end',
paddingRight: theme.spacing(10),
[theme.breakpoints.down('md')]: {
paddingRight: theme.spacing(5),
},
}));
export default FormContainer;

View file

@ -1,11 +1,12 @@
import React, { FC } from 'react';
import { ContainerProps } from '@mui/material';
import { BoxProps, Divider } from '@mui/material';
import Container from 'components/Container';
const FormPaperFooter: FC<ContainerProps> = ({ sx, style, ...props }) => {
const FormPaperFooter: FC<BoxProps> = ({ sx, style, ...props }) => {
return (
<>
<Divider />
<Container
disableGutters
style={{ flexDirection: 'row', ...style }}
sx={{
mt: 3,
@ -14,6 +15,7 @@ const FormPaperFooter: FC<ContainerProps> = ({ sx, style, ...props }) => {
{...props}>
{props.children}
</Container>
</>
);
};

View file

@ -1,20 +0,0 @@
import React, { FC } from 'react';
import { Typography, TypographyProps } from '@mui/material';
const FormPaperHeaderText: FC<TypographyProps> = ({ sx, ...props }) => {
return (
<Typography
sx={{
fontSize: '32px',
fontWeight: '600',
textAlign: 'left',
mb: 8,
...sx,
}}
{...props}>
{props.children}
</Typography>
);
};
export default FormPaperHeaderText;

View file

@ -0,0 +1,12 @@
import React, { FC } from 'react';
import { Typography, TypographyProps } from '@mui/material';
const FormPaperTitle: FC<TypographyProps> = ({ sx, ...props }) => {
return (
<Typography variant="title" sx={{ mb: 8, ...sx }} {...props}>
{props.children}
</Typography>
);
};
export default FormPaperTitle;

View file

@ -1,11 +1,9 @@
import React from 'react';
import { Paper, PaperProps } from '@mui/material';
import { FC } from 'react';
const FormPaper: FC<PaperProps> = ({ sx, children, ...props }) => (
<Paper sx={{ maxWidth: '360px', py: 4, px: 2, ...sx }} {...props}>
{children}
</Paper>
);
import { Paper, styled } from '@mui/material';
const FormPaper = styled(Paper)(({ theme }) => ({
padding: theme.spacing(4, 2),
maxWidth: '360px',
width: '100%',
textAlign: 'left',
}));
export default FormPaper;

View file

@ -1,18 +0,0 @@
import React from 'react';
import constants from 'utils/strings/constants';
import MessageDialog from './MessageDialog';
export default function IncognitoWarning() {
return (
<MessageDialog
show={true}
onHide={() => null}
attributes={{
title: constants.LOCAL_STORAGE_NOT_ACCESSIBLE,
staticBackdrop: true,
nonClosable: true,
}}>
<div>{constants.LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE}</div>
</MessageDialog>
);
}

View file

@ -1,20 +1,13 @@
import constants from 'utils/strings/constants';
import { Formik, FormikHelpers } from 'formik';
import React, { useEffect, useState } from 'react';
import React, { useEffect } from 'react';
import { useRouter } from 'next/router';
import * as Yup from 'yup';
import { getOtt } from 'services/userService';
import { setData, LS_KEYS, getData } from 'utils/storage/localStorage';
import SubmitButton from 'components/SubmitButton';
import { PAGES } from 'constants/pages';
import FormPaperHeaderText from './Form/FormPaper/HeaderText';
import { Divider, TextField } from '@mui/material';
import FormPaperTitle from './Form/FormPaper/Title';
import FormPaperFooter from './Form/FormPaper/Footer';
import LinkButton from './pages/gallery/LinkButton';
interface formValues {
email: string;
}
import SingleInputForm, { SingleInputFormProps } from './SingleInputForm';
interface LoginProps {
signUp: () => void;
@ -22,8 +15,6 @@ interface LoginProps {
export default function Login(props: LoginProps) {
const router = useRouter();
const [waiting, setWaiting] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
const main = async () => {
@ -32,61 +23,33 @@ export default function Login(props: LoginProps) {
if (user?.email) {
await router.push(PAGES.VERIFY);
}
setLoading(false);
};
main();
}, []);
const loginUser = async (
{ email }: formValues,
{ setFieldError }: FormikHelpers<formValues>
const loginUser: SingleInputFormProps['callback'] = async (
email,
setFieldError
) => {
try {
setWaiting(true);
await getOtt(email);
setData(LS_KEYS.USER, { email });
router.push(PAGES.VERIFY);
} catch (e) {
setFieldError('email', `${constants.UNKNOWN_ERROR} ${e.message}`);
setFieldError(`${constants.UNKNOWN_ERROR} ${e.message}`);
}
setWaiting(false);
};
return (
<>
<FormPaperHeaderText>{constants.LOGIN}</FormPaperHeaderText>
<Formik<formValues>
initialValues={{ email: '' }}
validationSchema={Yup.object().shape({
email: Yup.string()
.email(constants.EMAIL_ERROR)
.required(constants.REQUIRED),
})}
validateOnChange={false}
validateOnBlur={false}
onSubmit={loginUser}>
{({ values, errors, handleChange, handleSubmit }) => (
<form noValidate onSubmit={handleSubmit}>
<TextField
fullWidth
type="email"
label={constants.ENTER_EMAIL}
value={values.email}
onChange={handleChange('email')}
error={Boolean(errors.email)}
helperText={errors.email}
autoFocus
disabled={loading}
<FormPaperTitle>{constants.LOGIN}</FormPaperTitle>
<SingleInputForm
callback={loginUser}
fieldType="email"
placeholder={constants.ENTER_EMAIL}
buttonText={constants.LOGIN}
/>
<SubmitButton
buttonText={constants.LOGIN}
loading={waiting}
/>
</form>
)}
</Formik>
<Divider />
<FormPaperFooter>
<LinkButton onClick={props.signUp}>
{constants.NO_ACCOUNT}

View file

@ -38,7 +38,7 @@ import { livePhotoBtnHTML } from 'components/LivePhotoBtn';
import { logError } from 'utils/sentry';
import CloseIcon from '@mui/icons-material/Close';
import TickIcon from 'components/icons/TickIcon';
import TickIcon from '@mui/icons-material/Done';
import { Formik } from 'formik';
import * as Yup from 'yup';
import EnteSpinner from 'components/EnteSpinner';

View file

@ -0,0 +1,74 @@
import React, { useEffect, useState } from 'react';
import { downloadAsFile } from 'utils/file';
import { getRecoveryKey } from 'utils/crypto';
import constants from 'utils/strings/constants';
import DialogBox from '../DialogBox';
import CodeBlock from '../CodeBlock';
import { ButtonProps, Typography } from '@mui/material';
import * as bip39 from 'bip39';
import { DashedBorderWrapper } from './styledComponents';
// mobile client library only supports english.
bip39.setDefaultWordlist('english');
interface Props {
show: boolean;
onHide: () => void;
somethingWentWrong: any;
}
function RecoveryKey({ somethingWentWrong, ...props }: Props) {
const [recoveryKey, setRecoveryKey] = useState(null);
useEffect(() => {
if (!props.show) {
return;
}
const main = async () => {
try {
const recoveryKey = await getRecoveryKey();
setRecoveryKey(bip39.entropyToMnemonic(recoveryKey));
} catch (e) {
somethingWentWrong();
props.onHide();
}
};
main();
}, [props.show]);
function onSaveClick() {
downloadAsFile(constants.RECOVERY_KEY_FILENAME, recoveryKey);
props.onHide();
}
const recoveryKeyDialogAttributes = {
title: constants.RECOVERY_KEY,
close: {
text: constants.SAVE_LATER,
variant: 'secondary' as ButtonProps['color'],
},
staticBackdrop: true,
proceed: {
text: constants.SAVE,
action: onSaveClick,
disabled: !recoveryKey,
variant: 'accent' as ButtonProps['color'],
},
};
return (
<DialogBox
open={props.show}
onClose={props.onHide}
size="sm"
attributes={recoveryKeyDialogAttributes}>
<Typography mb={3}>{constants.RECOVERY_KEY_DESCRIPTION}</Typography>
<DashedBorderWrapper>
<CodeBlock code={recoveryKey} />
<Typography m={2}>
{constants.KEY_NOT_STORED_DISCLAIMER}
</Typography>
</DashedBorderWrapper>
</DialogBox>
);
}
export default RecoveryKey;

View file

@ -0,0 +1,6 @@
import { Box, styled } from '@mui/material';
export const DashedBorderWrapper = styled(Box)(({ theme }) => ({
border: `1px dashed ${theme.palette.grey.A400}`,
borderRadius: theme.spacing(1),
}));

View file

@ -1,74 +0,0 @@
import React, { useEffect, useState } from 'react';
import { downloadAsFile } from 'utils/file';
import { getRecoveryKey } from 'utils/crypto';
import constants from 'utils/strings/constants';
import MessageDialog from './MessageDialog';
import { CodeBlock } from './CodeBlock';
import { Box, Paper, Typography } from '@mui/material';
const bip39 = require('bip39');
// mobile client library only supports english.
bip39.setDefaultWordlist('english');
interface Props {
show: boolean;
onHide: () => void;
somethingWentWrong: any;
}
function RecoveryKeyModal({ somethingWentWrong, ...props }: Props) {
const [recoveryKey, setRecoveryKey] = useState(null);
useEffect(() => {
if (!props.show) {
return;
}
const main = async () => {
try {
const recoveryKey = await getRecoveryKey();
setRecoveryKey(bip39.entropyToMnemonic(recoveryKey));
} catch (e) {
somethingWentWrong();
props.onHide();
}
};
main();
}, [props.show]);
function onSaveClick() {
downloadAsFile(constants.RECOVERY_KEY_FILENAME, recoveryKey);
onClose();
}
function onClose() {
props.onHide();
}
return (
<MessageDialog
show={props.show}
onHide={onClose}
size="xs"
attributes={{
title: constants.RECOVERY_KEY,
close: {
text: constants.SAVE_LATER,
variant: 'danger',
},
staticBackdrop: true,
proceed: {
text: constants.SAVE,
action: onSaveClick,
disabled: !recoveryKey,
variant: 'success',
},
}}>
<p>{constants.RECOVERY_KEY_DESCRIPTION}</p>
<Paper
component={Box}
border={'1px dashed'}
borderColor={'grey.A700'}>
<CodeBlock code={recoveryKey} />
<Typography m="20px">
{constants.KEY_NOT_STORED_DISCLAIMER}
</Typography>
</Paper>
</MessageDialog>
);
}
export default RecoveryKeyModal;

View file

@ -1,15 +1,17 @@
import React, { useState } from 'react';
import Container from 'components/Container';
import constants from 'utils/strings/constants';
import { Formik, FormikHelpers } from 'formik';
import { Formik } from 'formik';
import * as Yup from 'yup';
import SubmitButton from './SubmitButton';
import { TextField, Typography } from '@mui/material';
interface Props {
export interface SetPasswordFormProps {
callback: (
passphrase: string,
setFieldError: FormikHelpers<SetPasswordFormValues>['setFieldError']
setFieldError: (
field: keyof SetPasswordFormValues,
message: string
) => void
) => Promise<void>;
buttonText: string;
back: () => void;
@ -18,12 +20,19 @@ export interface SetPasswordFormValues {
passphrase: string;
confirm: string;
}
function SetPasswordForm(props: Props) {
function SetPasswordForm(props: SetPasswordFormProps) {
const [loading, setLoading] = useState(false);
const onSubmit = async (
values: SetPasswordFormValues,
{ setFieldError }: FormikHelpers<SetPasswordFormValues>
{
setFieldError,
}: {
setFieldError: (
field: keyof SetPasswordFormValues,
message: string
) => void;
}
) => {
setLoading(true);
try {
@ -51,21 +60,14 @@ function SetPasswordForm(props: Props) {
validateOnBlur={false}
onSubmit={onSubmit}>
{({ values, errors, handleChange, handleSubmit }) => (
<form
style={{ width: '100%' }}
noValidate
onSubmit={handleSubmit}>
<Container disableGutters>
<Typography mb={2}>
<form noValidate onSubmit={handleSubmit}>
<Typography mb={2} color="text.secondary" variant="body2">
{constants.ENTER_ENC_PASSPHRASE}
</Typography>
<Typography mb={2}>
{constants.PASSPHRASE_DISCLAIMER()}
</Typography>
<Container>
<TextField
margin="normal"
fullWidth
variant="filled"
type="password"
label={constants.PASSPHRASE_HINT}
value={values.passphrase}
@ -77,6 +79,7 @@ function SetPasswordForm(props: Props) {
/>
<TextField
fullWidth
variant="filled"
type="password"
label={constants.CONFIRM_PASSPHRASE}
value={values.confirm}
@ -85,12 +88,15 @@ function SetPasswordForm(props: Props) {
error={Boolean(errors.confirm)}
helperText={errors.confirm}
/>
<Typography my={2} variant="body2">
{constants.PASSPHRASE_DISCLAIMER()}
</Typography>
<SubmitButton
loading={loading}
buttonText={props.buttonText}
/>
</Container>
</Container>
</form>
)}
</Formik>

View file

@ -1,35 +1,38 @@
import React from 'react';
import React, { FC } from 'react';
import { Button, ButtonProps } from '@mui/material';
import NavigateNextIcon from '@mui/icons-material/NavigateNext';
import { FluidContainer } from 'components/Container';
interface IProps {
children: any;
bgDark?: boolean;
hideArrow?: boolean;
onClick: () => void;
color?: ButtonProps['color'];
smallerArrow?: boolean;
}
export default function SidebarButton({
const SidebarButton: FC<ButtonProps<'button', IProps>> = ({
children,
bgDark,
hideArrow,
smallerArrow,
sx,
...props
}: IProps) {
}) => {
return (
<Button
{...props}
variant="text"
sx={{
width: '100%',
marginBottom: '16px',
display: 'flex',
justifyContent: 'space-between',
bgcolor: bgDark && 'grey.800',
padding: '10px',
borderRadius: '8px',
fontSize: '18px',
}}>
{children}
{!hideArrow && <NavigateNextIcon />}
fullWidth
sx={{ my: 0.5, px: 1, py: '10px', ...sx }}
css={`
font-size: 16px;
font-weight: 600;
line-height: 24px;
letter-spacing: 0em;
`}
{...props}>
<FluidContainer>{children}</FluidContainer>
{!hideArrow && (
<NavigateNextIcon
fontSize={smallerArrow ? 'small' : 'medium'}
/>
)}
</Button>
);
}
};
export default SidebarButton;

View file

@ -2,6 +2,7 @@ import { Typography, IconButton } from '@mui/material';
import React from 'react';
import constants from 'utils/strings/constants';
import CloseIcon from '@mui/icons-material/Close';
import { SpaceBetweenFlex } from 'components/Container';
interface IProps {
closeSidebar: () => void;
@ -9,21 +10,18 @@ interface IProps {
export default function HeaderSection({ closeSidebar }: IProps) {
return (
<>
<Typography variant="h6">
<strong>{constants.ENTE}</strong>
<SpaceBetweenFlex>
<Typography
css={`
font-size: 18px;
font-weight: 600;
line-height: 24px;
`}>
{constants.ENTE}
</Typography>
<IconButton
aria-label="close"
onClick={closeSidebar}
sx={{
position: 'absolute',
right: 16,
top: 16,
color: (theme) => theme.palette.grey[400],
}}>
<CloseIcon />
<IconButton aria-label="close" onClick={closeSidebar}>
<CloseIcon fontSize="small" />
</IconButton>
</>
</SpaceBetweenFlex>
);
}

View file

@ -10,12 +10,16 @@ import { getToken } from 'utils/common/key';
import isElectron from 'is-electron';
import { downloadApp, initiateEmail } from 'utils/common';
import { AppContext } from 'pages/_app';
import { useLocalState } from 'hooks/useLocalState';
import { LS_KEYS } from 'utils/storage/localStorage';
import { UserDetails } from 'types/user';
export default function HelpSection({ userDetails }) {
const { setDialogMessage } = useContext(AppContext);
export default function HelpSection() {
const [userDetails] = useLocalState<UserDetails>(LS_KEYS.USER_DETAILS);
const [exportModalView, setExportModalView] = useState(false);
const { setDialogMessage } = useContext(AppContext);
function openFeedbackURL() {
const feedbackURL: string = `${getEndpoint()}/users/feedback?token=${encodeURIComponent(
getToken()

View file

@ -1,25 +0,0 @@
import React, { useState } from 'react';
import { Typography } from '@mui/material';
import { SpaceBetweenFlex } from 'components/Container';
import ThemeToggler from './ThemeToggler';
import { UserDetails } from 'types/user';
interface IProps {
userDetails: UserDetails;
}
export enum THEMES {
LIGHT,
DARK,
}
export default function InfoSection({ userDetails }: IProps) {
const [theme, setTheme] = useState<THEMES>(THEMES.DARK);
return (
<SpaceBetweenFlex style={{ marginBottom: '20px' }}>
<Typography pl="5px">{userDetails?.email}</Typography>
<ThemeToggler theme={theme} setTheme={setTheme} />
</SpaceBetweenFlex>
);
}

View file

@ -0,0 +1,40 @@
import React, { FC } from 'react';
import { Box, ButtonProps } from '@mui/material';
import SidebarButton from './Button';
import { DotSeparator } from './styledComponents';
interface IProps {
hideArrow?: boolean;
icon: JSX.Element;
label: JSX.Element | string;
count: number;
}
const NavigationButton: FC<ButtonProps<'button', IProps>> = ({
icon,
label,
count,
...props
}) => {
return (
<SidebarButton
smallerArrow
variant="contained"
color="secondary"
sx={{ px: '12px' }}
css={`
font-size: 14px;
line-height: 20px;
font-weight: 500;
`}
{...props}>
<Box mr={'12px'}>{icon}</Box>
{label}
<DotSeparator />
<Box component={'span'} sx={{ color: 'text.secondary' }}>
{count}
</Box>
</SidebarButton>
);
};
export default NavigationButton;

View file

@ -1,66 +1,46 @@
import React, { useContext } from 'react';
import SidebarButton from './Button';
import constants from 'utils/strings/constants';
import { GalleryContext } from 'pages/gallery';
import { ARCHIVE_SECTION, TRASH_SECTION } from 'constants/collection';
import DeleteIcon from '@mui/icons-material/Delete';
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff';
import { Box, Typography } from '@mui/material';
import { FlexWrapper } from 'components/Container';
import { CollectionSummaries } from 'types/collection';
import NavigationButton from './NavigationButton';
interface Iprops {
closeSidebar: () => void;
collectionSummaries: CollectionSummaries;
}
const DotSeparator = () => (
<Typography color="text.secondary" ml="10px" mr="10px" fontWeight={700}>
{'·'}
</Typography>
);
export default function NavigationSection({
closeSidebar,
collectionSummaries,
}: Iprops) {
const galleryContext = useContext(GalleryContext);
const openArchiveSection = () => {
galleryContext.setActiveCollection(ARCHIVE_SECTION);
closeSidebar();
};
const openTrashSection = () => {
galleryContext.setActiveCollection(TRASH_SECTION);
closeSidebar();
};
const openArchiveSection = () => {
galleryContext.setActiveCollection(ARCHIVE_SECTION);
closeSidebar();
};
return (
<>
<SidebarButton bgDark onClick={openTrashSection}>
<FlexWrapper>
<Box mr="10px">
<DeleteIcon />
</Box>
{constants.TRASH}
<DotSeparator />
<Typography color="text.secondary">
{collectionSummaries.get(TRASH_SECTION)?.fileCount}
</Typography>
</FlexWrapper>
</SidebarButton>
<SidebarButton bgDark onClick={openArchiveSection}>
<FlexWrapper>
<Box mr="10px">
<VisibilityOffIcon />
</Box>
{constants.ARCHIVE}
<DotSeparator />
<Typography color="text.secondary">
{collectionSummaries.get(ARCHIVE_SECTION)?.fileCount}
</Typography>
</FlexWrapper>
</SidebarButton>
<NavigationButton
icon={<DeleteIcon />}
label={constants.TRASH}
count={collectionSummaries.get(TRASH_SECTION)?.fileCount}
onClick={openTrashSection}
/>
<NavigationButton
icon={<VisibilityOffIcon />}
label={constants.ARCHIVE}
count={collectionSummaries.get(ARCHIVE_SECTION)?.fileCount}
onClick={openArchiveSection}
/>
</>
);
}

View file

@ -18,7 +18,12 @@ interface Iprops {
export default function SubscriptionDetails({ userDetails }: Iprops) {
return (
<Paper component={Box} bgcolor="accent.main" position={'relative'}>
<Box
display="flex"
flexDirection={'column'}
height={160}
bgcolor="accent.main"
position={'relative'}>
{userDetails ? (
<>
<Box padding={2}>
@ -52,7 +57,8 @@ export default function SubscriptionDetails({ userDetails }: Iprops) {
<Paper
component={Box}
position={'relative'}
zIndex="2"
zIndex="100"
height="64px"
bgcolor="accent.dark"
padding={2}>
<LinearProgress
@ -84,6 +90,6 @@ export default function SubscriptionDetails({ userDetails }: Iprops) {
<CircularProgress />
</Container>
)}
</Paper>
</Box>
);
}

View file

@ -2,19 +2,20 @@ import { ToggleButton, ToggleButtonGroup } from '@mui/material';
import React from 'react';
import DarkModeIcon from '@mui/icons-material/DarkMode';
import LightModeIcon from '@mui/icons-material/LightMode';
import { THEMES } from './InfoSection';
import { THEMES } from 'types/theme';
interface Iprops {
theme: THEMES;
setTheme: (theme: THEMES) => void;
}
export default function ThemeToggler({ theme, setTheme }: Iprops) {
export default function ThemeSwitcher({ theme, setTheme }: Iprops) {
const handleChange = (event, theme: THEMES) => {
if (theme !== null) {
setTheme(theme);
}
};
return (
<ToggleButtonGroup
color="primary"
size="small"
value={theme}
exclusive

View file

@ -1,8 +1,8 @@
import React, { useContext, useState } from 'react';
import SidebarButton from './Button';
import constants from 'utils/strings/constants';
import FixLargeThumbnails from 'components/FixLargeThumbnail';
import RecoveryKeyModal from 'components/RecoveryKeyModal';
// import FixLargeThumbnails from 'components/FixLargeThumbnail';
import RecoveryKey from 'components/RecoveryKey';
import TwoFactorModal from 'components/TwoFactor/Modal';
import { PAGES } from 'constants/pages';
import { useRouter } from 'next/router';
@ -14,7 +14,7 @@ export default function UtilitySection({ closeSidebar }) {
const [recoverModalView, setRecoveryModalView] = useState(false);
const [twoFactorModalView, setTwoFactorModalView] = useState(false);
const [fixLargeThumbsView, setFixLargeThumbsView] = useState(false);
// const [fixLargeThumbsView, setFixLargeThumbsView] = useState(false);
const openRecoveryKeyModal = () => setRecoveryModalView(true);
const closeRecoveryKeyModal = () => setRecoveryModalView(false);
@ -34,7 +34,7 @@ export default function UtilitySection({ closeSidebar }) {
const redirectToDeduplicatePage = () => router.push(PAGES.DEDUPLICATE);
const openThumbnailCompressModal = () => setFixLargeThumbsView(true);
// const openThumbnailCompressModal = () => setFixLargeThumbsView(true);
const somethingWentWrong = () =>
setDialogMessage({
@ -61,11 +61,11 @@ export default function UtilitySection({ closeSidebar }) {
{constants.DEDUPLICATE_FILES}
</SidebarButton>
<SidebarButton onClick={openThumbnailCompressModal}>
{/* <SidebarButton onClick={openThumbnailCompressModal}>
{constants.COMPRESS_THUMBNAILS}
</SidebarButton>
</SidebarButton> */}
<RecoveryKeyModal
<RecoveryKey
show={recoverModalView}
onHide={closeRecoveryKeyModal}
somethingWentWrong={somethingWentWrong}
@ -73,16 +73,15 @@ export default function UtilitySection({ closeSidebar }) {
<TwoFactorModal
show={twoFactorModalView}
onHide={closeTwoFactorModalView}
setDialogMessage={setDialogMessage}
closeSidebar={closeSidebar}
setLoading={startLoading}
/>
<FixLargeThumbnails
{/* <FixLargeThumbnails
isOpen={fixLargeThumbsView}
hide={() => setFixLargeThumbsView(false)}
show={() => setFixLargeThumbsView(true)}
/>
/> */}
</>
);
}

View file

@ -1,58 +1,39 @@
import React, { useContext, useEffect, useState } from 'react';
import { LS_KEYS, setData } from 'utils/storage/localStorage';
import React, { useContext } from 'react';
import { getUserDetails } from 'services/userService';
import { UserDetails } from 'types/user';
import { getLocalUserDetails } from 'utils/user';
import InfoSection from './InfoSection';
import NavigationSection from './NavigationSection';
import UtilitySection from './UtilitySection';
import HelpSection from './HelpSection';
import ExitSection from './ExitSection';
import DebugLogs from './DebugLogs';
import { DrawerSidebar, DividerWithMargin } from './styledComponents';
// import DebugLogs from './DebugLogs';
import { DrawerSidebar, PaddedDivider } from './styledComponents';
import { AppContext } from 'pages/_app';
import SubscriptionDetails from './SubscriptionDetails';
import HeaderSection from './Header';
import { CollectionSummaries } from 'types/collection';
import UserDetailsSection from './userDetailsSection';
interface Iprops {
collectionSummaries: CollectionSummaries;
}
export default function Sidebar({ collectionSummaries }: Iprops) {
const { sidebarView, closeSidebar } = useContext(AppContext);
const [userDetails, setUserDetails] = useState<UserDetails>(null);
useEffect(() => {
setUserDetails(getLocalUserDetails());
}, []);
useEffect(() => {
const main = async () => {
const userDetails = await getUserDetails();
setUserDetails(userDetails);
setData(LS_KEYS.USER_DETAILS, userDetails);
};
main();
}, [sidebarView]);
return (
<DrawerSidebar anchor="left" open={sidebarView} onClose={closeSidebar}>
<DrawerSidebar open={sidebarView} onClose={closeSidebar}>
<HeaderSection closeSidebar={closeSidebar} />
<DividerWithMargin />
<InfoSection userDetails={userDetails} />
<SubscriptionDetails userDetails={userDetails} />
<DividerWithMargin />
<PaddedDivider spaced />
<UserDetailsSection sidebarView={sidebarView} />
<PaddedDivider invisible />
<NavigationSection
closeSidebar={closeSidebar}
collectionSummaries={collectionSummaries}
/>
<UtilitySection closeSidebar={closeSidebar} />
<DividerWithMargin />
<HelpSection userDetails={userDetails} />
<DividerWithMargin />
<PaddedDivider />
<HelpSection />
<PaddedDivider />
<ExitSection />
<DividerWithMargin />
<DebugLogs />
{/* <PaddedDivider />
<DebugLogs /> */}
</DrawerSidebar>
);
}

View file

@ -1,14 +1,30 @@
import { Drawer, Divider } from '@mui/material';
import { Drawer, Divider, styled } from '@mui/material';
import { default as MuiStyled } from '@mui/styled-engine';
import CircleIcon from '@mui/icons-material/Circle';
export const DrawerSidebar = MuiStyled(Drawer)(() => ({
'& > .MuiPaper-root': {
export const DrawerSidebar = MuiStyled(Drawer)(({ theme }) => ({
'& .MuiPaper-root': {
width: '320px',
padding: '20px',
padding: theme.spacing(2, 1, 4, 1),
},
}));
export const DividerWithMargin = MuiStyled(Divider)(() => ({
marginTop: '20px',
marginBottom: '20px',
DrawerSidebar.defaultProps = { anchor: 'left' };
export const PaddedDivider = MuiStyled(Divider)<{
invisible?: boolean;
spaced?: boolean;
}>(({ theme, invisible, spaced }) => ({
margin: theme.spacing(spaced ? 2 : 1, 0),
opacity: invisible ? 0 : 1,
}));
export const DotSeparator = styled(CircleIcon)`
height: 4px;
width: 4px;
left: 86px;
top: 18px;
border-radius: 0px;
margin: 0 ${({ theme }) => theme.spacing(1)};
color: ${({ theme }) => theme.palette.text.secondary};
`;

View file

@ -0,0 +1,40 @@
import React, { useEffect } from 'react';
import { SpaceBetweenFlex } from 'components/Container';
import { PaddedDivider } from './styledComponents';
import SubscriptionDetails from './SubscriptionDetails';
import { getUserDetails } from 'services/userService';
import { UserDetails } from 'types/user';
import { LS_KEYS } from 'utils/storage/localStorage';
import { useLocalState } from 'hooks/useLocalState';
import { THEMES } from 'types/theme';
import ThemeSwitcher from './ThemeSwitcher';
import Typography from '@mui/material/Typography';
export default function UserDetailsSection({ sidebarView }) {
const [userDetails, setUserDetails] = useLocalState<UserDetails>(
LS_KEYS.USER_DETAILS
);
const [theme, setTheme] = useLocalState<THEMES>(LS_KEYS.THEME, THEMES.DARK);
useEffect(() => {
if (!sidebarView) {
return;
}
const main = async () => {
const userDetails = await getUserDetails();
setUserDetails(userDetails);
};
main();
}, [sidebarView]);
return (
<>
<SpaceBetweenFlex px={1}>
<Typography>{userDetails?.email}</Typography>
<ThemeSwitcher theme={theme} setTheme={setTheme} />
</SpaceBetweenFlex>
<PaddedDivider invisible />
<SubscriptionDetails userDetails={userDetails} />
</>
);
}

View file

@ -17,15 +17,14 @@ import { SESSION_KEYS } from 'utils/storage/sessionStorage';
import { PAGES } from 'constants/pages';
import {
Checkbox,
Container,
Divider,
FormControlLabel,
FormGroup,
TextField,
} from '@mui/material';
import FormPaperHeaderText from './Form/FormPaper/HeaderText';
import FormPaperTitle from './Form/FormPaper/Title';
import LinkButton from './pages/gallery/LinkButton';
import FormPaperFooter from './Form/FormPaper/Footer';
import VerticallyCentered from './Container';
interface FormValues {
email: string;
@ -93,7 +92,7 @@ export default function SignUp(props: SignUpProps) {
return (
<>
<FormPaperHeaderText> {constants.SIGN_UP}</FormPaperHeaderText>
<FormPaperTitle> {constants.SIGN_UP}</FormPaperTitle>
<Formik<FormValues>
initialValues={{
email: '',
@ -117,11 +116,9 @@ export default function SignUp(props: SignUpProps) {
handleSubmit,
}): JSX.Element => (
<form noValidate onSubmit={handleSubmit}>
<Container disableGutters sx={{ mb: 1 }}>
<VerticallyCentered sx={{ mb: 1 }}>
<TextField
variant="filled"
fullWidth
margin="dense"
type="email"
label={constants.ENTER_EMAIL}
value={values.email}
@ -134,8 +131,6 @@ export default function SignUp(props: SignUpProps) {
<TextField
fullWidth
variant="filled"
margin="dense"
type="password"
label={constants.PASSPHRASE_HINT}
value={values.passphrase}
@ -147,8 +142,6 @@ export default function SignUp(props: SignUpProps) {
<TextField
fullWidth
variant="filled"
margin="dense"
type="password"
label={constants.CONFIRM_PASSPHRASE}
value={values.confirm}
@ -157,27 +150,30 @@ export default function SignUp(props: SignUpProps) {
helperText={errors.confirm}
disabled={loading}
/>
<FormGroup>
<FormGroup sx={{ width: '100%' }}>
<FormControlLabel
sx={{
color: 'text.secondary',
ml: -1,
mt: 2,
}}
control={
<Checkbox
size="small"
disabled={loading}
checked={acceptTerms}
onChange={(e) =>
setAcceptTerms(e.target.checked)
}
color="success"
color="accent"
/>
}
label={constants.TERMS_AND_CONDITIONS()}
/>
</FormGroup>
</Container>
</VerticallyCentered>
<SubmitButton
sx={{ my: 4 }}
buttonText={constants.CREATE_ACCOUNT}
loading={loading}
disabled={!acceptTerms}
@ -185,9 +181,9 @@ export default function SignUp(props: SignUpProps) {
</form>
)}
</Formik>
<Divider />
<FormPaperFooter>
<LinkButton onClick={props.login} color={'text.secondary'}>
<LinkButton onClick={props.login}>
{constants.ACCOUNT_EXISTS}
</LinkButton>
</FormPaperFooter>

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useMemo, useState } from 'react';
import constants from 'utils/strings/constants';
import { Formik, FormikHelpers } from 'formik';
import * as Yup from 'yup';
@ -7,16 +7,19 @@ import TextField from '@mui/material/TextField';
import ShowHidePassword from './Form/ShowHidePassword';
interface formValues {
passphrase: string;
inputValue: string;
}
interface Props {
callback: (passphrase: string, setFieldError) => void;
fieldType: string;
export interface SingleInputFormProps {
callback: (
inputValue: string,
setFieldError: (errorMessage: string) => void
) => Promise<void>;
fieldType: 'text' | 'email' | 'password';
placeholder: string;
buttonText: string;
}
export default function SingleInputForm(props: Props) {
export default function SingleInputForm(props: SingleInputFormProps) {
const [loading, SetLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
@ -25,7 +28,9 @@ export default function SingleInputForm(props: Props) {
{ setFieldError }: FormikHelpers<formValues>
) => {
SetLoading(true);
await props.callback(values.passphrase, setFieldError);
await props.callback(values.inputValue, (message) =>
setFieldError('inputValue', message)
);
SetLoading(false);
};
@ -39,25 +44,43 @@ export default function SingleInputForm(props: Props) {
event.preventDefault();
};
const validationSchema = useMemo(() => {
switch (props.fieldType) {
case 'text':
return Yup.object().shape({
inputValue: Yup.string().required(constants.REQUIRED),
});
case 'password':
return Yup.object().shape({
inputValue: Yup.string().required(constants.REQUIRED),
});
case 'email':
return Yup.object().shape({
inputValue: Yup.string()
.email(constants.EMAIL_ERROR)
.required(constants.REQUIRED),
});
}
}, [props.fieldType]);
return (
<Formik<formValues>
initialValues={{ passphrase: '' }}
initialValues={{ inputValue: '' }}
onSubmit={submitForm}
validationSchema={Yup.object().shape({
passphrase: Yup.string().required(constants.REQUIRED),
})}
validationSchema={validationSchema}
validateOnChange={false}
validateOnBlur={false}>
{({ values, errors, handleChange, handleSubmit }) => (
<form noValidate onSubmit={handleSubmit}>
<TextField
variant="filled"
fullWidth
type={showPassword ? 'text' : props.fieldType}
label={props.placeholder}
value={values.passphrase}
onChange={handleChange('passphrase')}
error={Boolean(errors.passphrase)}
helperText={errors.passphrase}
value={values.inputValue}
onChange={handleChange('inputValue')}
error={Boolean(errors.inputValue)}
helperText={errors.inputValue}
disabled={loading}
autoFocus
InputProps={{
@ -76,11 +99,10 @@ export default function SingleInputForm(props: Props) {
/>
<SubmitButton
sx={{ mt: 2 }}
buttonText={props.buttonText}
loading={loading}
/>
<br />
</form>
)}
</Formik>

View file

@ -12,16 +12,19 @@ const SubmitButton: FC<ButtonProps<'button', Props>> = ({
buttonText,
inline,
disabled,
}: Props) => {
sx,
...props
}) => {
return (
<Button
size="large"
sx={{ my: 4, p: '12.25px', fontSize: '18px' }}
variant="contained"
color="success"
color="accent"
type="submit"
fullWidth={!inline}
disabled={loading || disabled}>
disabled={loading || disabled}
sx={{ my: 4, ...sx }}
{...props}>
{loading ? <CircularProgress size={25} /> : buttonText}
</Button>
);

View file

@ -57,7 +57,7 @@ export default function TwoFactorModalManageSection(props: Iprops) {
content: constants.UPDATE_TWO_FACTOR_MESSAGE,
close: { text: constants.CANCEL },
proceed: {
variant: 'success',
variant: 'accent',
text: constants.UPDATE,
action: reconfigureTwoFactor,
},
@ -78,12 +78,11 @@ export default function TwoFactorModalManageSection(props: Iprops) {
justifyContent="center"
textAlign={'center'}>
<Grid item sm={9} xs={12}>
{constants.UPDATE_TWO_FACTOR_HINT}
{constants.UPDATE_TWO_FACTOR_LABEL}
</Grid>
<Grid item sm={3} xs={12}>
<Button
variant="contained"
color={'success'}
color={'accent'}
onClick={warnTwoFactorReconfigure}
style={{ width: '100%' }}>
{constants.RECONFIGURE}
@ -97,12 +96,11 @@ export default function TwoFactorModalManageSection(props: Iprops) {
justifyContent="center"
textAlign={'center'}>
<Grid item sm={9} xs={12}>
{constants.DISABLE_TWO_FACTOR_HINT}{' '}
{constants.DISABLE_TWO_FACTOR_LABEL}{' '}
</Grid>
<Grid item sm={3} xs={12}>
<Button
variant="contained"
color={'danger'}
onClick={warnTwoFactorDisable}
style={{ width: '100%' }}>

View file

@ -3,30 +3,31 @@ import LockIcon from '@mui/icons-material/Lock';
import { PAGES } from 'constants/pages';
import { useRouter } from 'next/router';
import constants from 'utils/strings/constants';
import Container from 'components/Container';
import VerticallyCentered from 'components/Container';
import { Button, Typography } from '@mui/material';
interface Iprops {
closeSidebar: () => void;
close: () => void;
}
export default function TwoFactorModalSetupSection({ closeSidebar }: Iprops) {
export default function TwoFactorModalSetupSection({ close }: Iprops) {
const router = useRouter();
const redirectToTwoFactorSetup = () => {
closeSidebar();
close();
router.push(PAGES.TWO_FACTOR_SETUP);
};
return (
<Container disableGutters sx={{ mb: 2 }}>
<VerticallyCentered sx={{ mb: 2 }}>
<LockIcon sx={{ fontSize: (theme) => theme.spacing(5), mb: 2 }} />
<Typography mb={2}>{constants.TWO_FACTOR_INFO}</Typography>
<Typography mb={4}>{constants.TWO_FACTOR_INFO}</Typography>
<Button
variant="contained"
color="success"
color="accent"
size="large"
onClick={redirectToTwoFactorSetup}>
{constants.ENABLE_TWO_FACTOR}
</Button>
</Container>
</VerticallyCentered>
);
}

View file

@ -3,14 +3,13 @@ import { getTwoFactorStatus } from 'services/userService';
import { SetLoading } from 'types/gallery';
import { getData, LS_KEYS, setData } from 'utils/storage/localStorage';
import constants from 'utils/strings/constants';
import MessageDialog, { SetDialogMessage } from '../../MessageDialog';
import DialogBox from '../../DialogBox';
import TwoFactorModalSetupSection from './Setup';
import TwoFactorModalManageSection from './Manage';
interface Props {
show: boolean;
onHide: () => void;
setDialogMessage: SetDialogMessage;
setLoading: SetLoading;
closeSidebar: () => void;
}
@ -42,10 +41,11 @@ function TwoFactorModal(props: Props) {
};
return (
<MessageDialog
<DialogBox
size="xs"
show={props.show}
onHide={props.onHide}
fullWidth
open={props.show}
onClose={props.onHide}
attributes={{
title: constants.TWO_FACTOR_AUTHENTICATION,
staticBackdrop: true,
@ -54,12 +54,10 @@ function TwoFactorModal(props: Props) {
{isTwoFactorEnabled ? (
<TwoFactorModalManageSection close={close} />
) : (
<TwoFactorModalSetupSection
closeSidebar={props.closeSidebar}
/>
<TwoFactorModalSetupSection close={close} />
)}
</>
</MessageDialog>
</DialogBox>
);
}
export default TwoFactorModal;

View file

@ -1,4 +1,4 @@
import Container from 'components/Container';
import VerticallyCentered from 'components/Container';
import { SetupMode } from 'pages/two-factor/setup';
import SetupManualMode from 'pages/two-factor/setup/ManualMode';
import SetupQRMode from 'pages/two-factor/setup/QRMode';
@ -16,7 +16,7 @@ export function TwoFactorSetup({ twoFactorSecret }: Iprops) {
const changeToQRMode = () => setSetupMode(SetupMode.QR_CODE);
return (
<Container sx={{ mb: 3 }}>
<VerticallyCentered sx={{ mb: 3 }}>
{setupMode === SetupMode.QR_CODE ? (
<SetupQRMode
twoFactorSecret={twoFactorSecret}
@ -28,6 +28,6 @@ export function TwoFactorSetup({ twoFactorSecret }: Iprops) {
changeToQRMode={changeToQRMode}
/>
)}
</Container>
</VerticallyCentered>
);
}

View file

@ -4,7 +4,7 @@ import React, { FC, useRef, useState } from 'react';
import OtpInput from 'react-otp-input';
import constants from 'utils/strings/constants';
import SubmitButton from 'components/SubmitButton';
import Container from 'components/Container';
import VerticallyCentered, { CenteredFlex } from 'components/Container';
import { Box, Typography, TypographyProps } from '@mui/material';
import InvalidInputMessage from './InvalidInputMessage';
@ -54,10 +54,10 @@ export default function VerifyTwoFactor(props: Props) {
noValidate
onSubmit={handleSubmit}
style={{ width: '100%' }}>
<Typography mb={2} color="text.secondary">
<Typography mb={2} variant="body2" color="text.secondary">
{constants.ENTER_TWO_FACTOR_OTP}
</Typography>
<Box sx={{ my: 2 }}>
<Box my={2}>
<OtpInput
ref={otpInputRef}
shouldAutoFocus
@ -69,9 +69,11 @@ export default function VerifyTwoFactor(props: Props) {
className={'otp-input'}
/>
{errors.otp && (
<CenteredFlex sx={{ mt: 1 }}>
<InvalidInputMessage>
{constants.INCORRECT_CODE}
</InvalidInputMessage>
</CenteredFlex>
)}
</Box>
<SubmitButton

View file

@ -1,15 +1,19 @@
import Container from 'components/Container';
import VerticallyCentered from 'components/Container';
import styled from 'styled-components';
export const QRCode = styled.img`
export const QRCode = styled.img(
({ theme }) => `
height: 200px;
width: 200px;
margin: 1rem;
`;
margin: ${theme.spacing(2)};
`
);
export const LoadingQRCode = styled(Container)(({ theme }) => ({
flex: '0 0 200px',
border: `1px solid ${theme.palette.grey[700]}`,
width: 200,
margin: theme.spacing(2),
}));
export const LoadingQRCode = styled(VerticallyCentered)(
({ theme }) => `
width:200px;
aspect-ratio:1;
border: 1px solid ${theme.palette.grey.A200};
margin: ${theme.spacing(2)};
`
);

View file

@ -1,22 +0,0 @@
import React from 'react';
export default function NavigateNext(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height="40"
viewBox="0 0 24 24"
width="24px"
fill="currentColor"
{...props}>
<path d="M0 0h24v24H0z" fill="none" />
<path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" />
</svg>
);
}
NavigateNext.defaultProps = {
height: 24,
width: 24,
viewBox: '0 0 24 24',
};

View file

@ -1,23 +0,0 @@
import React from 'react';
export default function SortIcon(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height={props.height}
viewBox={props.viewBox}
width={props.width}>
<path d="M0 0h24v24H0V0z" fill="none" />
<path
d="M3 18h6v-2H3v2zM3 6v2h18V6H3zm0 7h12v-2H3v2z"
fill="currentColor"
/>
</svg>
);
}
SortIcon.defaultProps = {
height: 24,
width: 24,
viewBox: '0 0 24 24',
};

View file

@ -1,20 +0,0 @@
import React from 'react';
export default function TickIcon(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
height={props.height}
viewBox={props.viewBox}
width={props.width}
fill="currentColor">
<path d="M9 16.2L4.8 12l-1.4 1.4L9 19 21 7l-1.4-1.4L9 16.2z" />
</svg>
);
}
TickIcon.defaultProps = {
height: 20,
width: 20,
viewBox: '0 0 24 24',
};

View file

@ -1,90 +0,0 @@
import React, { useState } from 'react';
import { COLLECTION_SORT_BY } from 'constants/collection';
import Menu from '@mui/material/Menu';
import { IconButton, MenuItem } from '@mui/material';
import SortIcon from 'components/icons/SortIcon';
import TickIcon from 'components/icons/TickIcon';
import constants from 'utils/strings/constants';
import { ListItemIcon, ListItemText, MenuList, Paper } from '@mui/material';
interface Props {
setCollectionSortBy: (sortBy: COLLECTION_SORT_BY) => void;
activeSortBy: COLLECTION_SORT_BY;
}
interface OptionProps extends Props {
close: () => void;
}
const SortByOptionCreator =
({ setCollectionSortBy, activeSortBy, close }: OptionProps) =>
(props: { sortBy: COLLECTION_SORT_BY; children: any }) => {
const handleClick = () => {
setCollectionSortBy(props.sortBy);
close();
};
return (
<MenuItem onClick={handleClick}>
<ListItemIcon
sx={{
minWidth: '30px',
}}>
{activeSortBy === props.sortBy && <TickIcon />}
</ListItemIcon>
<ListItemText>{props.children}</ListItemText>
</MenuItem>
);
};
const CollectionSortOptions = (props: OptionProps) => {
const SortByOption = SortByOptionCreator(props);
return (
<Paper sx={{ maxWidth: '100%' }}>
<MenuList>
<SortByOption sortBy={COLLECTION_SORT_BY.NAME}>
{constants.SORT_BY_NAME}
</SortByOption>
<SortByOption
sortBy={COLLECTION_SORT_BY.CREATION_TIME_DESCENDING}>
{constants.SORT_BY_CREATION_TIME_DESCENDING}
</SortByOption>
<SortByOption
sortBy={COLLECTION_SORT_BY.CREATION_TIME_ASCENDING}>
{constants.SORT_BY_CREATION_TIME_ASCENDING}
</SortByOption>
<SortByOption
sortBy={COLLECTION_SORT_BY.UPDATION_TIME_DESCENDING}>
{constants.SORT_BY_UPDATION_TIME_DESCENDING}
</SortByOption>
</MenuList>
</Paper>
);
};
export default function CollectionSort(props: Props) {
const [sortByEl, setSortByEl] = useState(null);
const handleClose = () => setSortByEl(null);
return (
<>
<IconButton
onClick={(event) => setSortByEl(event.currentTarget)}
aria-controls={sortByEl ? 'collection-sort' : undefined}
aria-haspopup="true"
aria-expanded={sortByEl ? 'true' : undefined}>
<SortIcon />
</IconButton>
<Menu
id="collection-sort"
anchorEl={sortByEl}
open={Boolean(sortByEl)}
onClose={handleClose}
MenuListProps={{
'aria-labelledby': 'collection-sort',
}}>
<CollectionSortOptions {...props} close={handleClose} />
</Menu>
</>
);
}

View file

@ -20,7 +20,6 @@ import {
hasPaypalSubscription,
} from 'utils/billing';
import { reverseString } from 'utils/common';
import { SetDialogMessage } from 'components/MessageDialog';
import ArrowEast from 'components/icons/ArrowEast';
import LinkButton from './LinkButton';
import { DeadCenter, GalleryContext } from 'pages/gallery';
@ -79,7 +78,7 @@ export const PlanIcon = styled.div<{ currentlySubscribed: boolean }>`
interface Props {
modalView: boolean;
closeModal: any;
setDialogMessage: SetDialogMessage;
setLoading: SetLoading;
}
enum PLAN_PERIOD {
@ -128,7 +127,7 @@ function PlanSelector(props: Props) {
} catch (e) {
logError(e, 'plan selector modal open failed');
props.closeModal();
props.setDialogMessage({
appContext.setDialogMessage({
title: constants.OPEN_PLAN_SELECTOR_MODAL_FAILED,
content: constants.UNKNOWN_ERROR,
close: { text: 'close', variant: 'danger' },
@ -150,7 +149,7 @@ function PlanSelector(props: Props) {
hasMobileSubscription(subscription) &&
!isSubscriptionCancelled(subscription)
) {
props.setDialogMessage({
appContext.setDialogMessage({
title: constants.ERROR,
content: constants.CANCEL_SUBSCRIPTION_ON_MOBILE,
close: { variant: 'danger' },
@ -159,13 +158,13 @@ function PlanSelector(props: Props) {
hasPaypalSubscription(subscription) &&
!isSubscriptionCancelled(subscription)
) {
props.setDialogMessage({
appContext.setDialogMessage({
title: constants.MANAGE_PLAN,
content: constants.PAYPAL_MANAGE_NOT_SUPPORTED_MESSAGE(),
close: { variant: 'danger' },
});
} else if (hasStripeSubscription(subscription)) {
props.setDialogMessage({
appContext.setDialogMessage({
title: `${constants.CONFIRM} ${reverseString(
constants.UPDATE_SUBSCRIPTION
)}`,
@ -176,7 +175,7 @@ function PlanSelector(props: Props) {
action: updateSubscription.bind(
null,
plan,
props.setDialogMessage,
appContext.setDialogMessage,
props.setLoading,
props.closeModal
),
@ -190,7 +189,7 @@ function PlanSelector(props: Props) {
await billingService.buySubscription(plan.stripeID);
} catch (e) {
props.setLoading(false);
props.setDialogMessage({
appContext.setDialogMessage({
title: constants.ERROR,
content: constants.SUBSCRIPTION_PURCHASE_FAILED,
close: { variant: 'danger' },
@ -320,7 +319,7 @@ function PlanSelector(props: Props) {
<LinkButton
color={'success'}
onClick={() =>
props.setDialogMessage({
appContext.setDialogMessage({
title: constants.CONFIRM_ACTIVATE_SUBSCRIPTION,
content:
constants.ACTIVATE_SUBSCRIPTION_MESSAGE(
@ -331,7 +330,7 @@ function PlanSelector(props: Props) {
text: constants.ACTIVATE_SUBSCRIPTION,
action: activateSubscription.bind(
null,
props.setDialogMessage,
appContext.setDialogMessage,
props.closeModal,
props.setLoading
),
@ -348,7 +347,7 @@ function PlanSelector(props: Props) {
<LinkButton
color="danger"
onClick={() =>
props.setDialogMessage({
appContext.setDialogMessage({
title: constants.CONFIRM_CANCEL_SUBSCRIPTION,
content:
constants.CANCEL_SUBSCRIPTION_MESSAGE(),
@ -357,7 +356,7 @@ function PlanSelector(props: Props) {
text: constants.CANCEL_SUBSCRIPTION,
action: cancelSubscription.bind(
null,
props.setDialogMessage,
appContext.setDialogMessage,
props.closeModal,
props.setLoading
),
@ -375,7 +374,7 @@ function PlanSelector(props: Props) {
color="primary"
onClick={updatePaymentMethod.bind(
null,
props.setDialogMessage,
appContext.setDialogMessage,
props.setLoading
)}
style={{ marginTop: '20px' }}>

View file

@ -153,7 +153,6 @@ const Cont = styled.div<{ disabled: boolean }>`
overflow: hidden;
position: relative;
flex: 1;
border-radius: ${({ theme }) => theme.shape.borderRadius}px;
cursor: ${(props) => (props.disabled ? 'not-allowed' : 'pointer')};
& > img {

View file

@ -6,8 +6,8 @@ import React, { useContext } from 'react';
import styled from 'styled-components';
import { DeduplicateContext } from 'pages/deduplicate';
import LeftArrow from 'components/icons/LeftArrow';
import { SetDialogMessage } from 'components/MessageDialog';
import { IconWithMessage } from 'components/IconWithMessage';
import { AppContext } from 'pages/_app';
const VerticalLine = styled.div`
position: absolute;
@ -19,18 +19,17 @@ const VerticalLine = styled.div`
interface IProps {
deleteFileHelper: () => void;
setDialogMessage: SetDialogMessage;
close: () => void;
count: number;
}
export default function DeduplicateOptions({
setDialogMessage,
deleteFileHelper,
close,
count,
}: IProps) {
const deduplicateContext = useContext(DeduplicateContext);
const { setDialogMessage } = useContext(AppContext);
const trashHandler = () =>
setDialogMessage({

View file

@ -1,5 +1,4 @@
import { SetDialogMessage } from 'components/MessageDialog';
import React, { useEffect, useState } from 'react';
import React, { useContext, useEffect, useState } from 'react';
import { SetCollectionSelectorAttributes } from '../CollectionSelector';
import DeleteIcon from 'components/icons/DeleteIcon';
import CloseIcon from '@mui/icons-material/Close';
@ -25,13 +24,13 @@ import DownloadIcon from 'components/icons/DownloadIcon';
import { User } from 'types/user';
import { IconWithMessage } from 'components/IconWithMessage';
import { SelectionBar, SelectionContainer } from '.';
import { AppContext } from 'pages/_app';
interface Props {
addToCollectionHelper: (collection: Collection) => void;
moveToCollectionHelper: (collection: Collection) => void;
restoreToCollectionHelper: (collection: Collection) => void;
showCreateCollectionModal: (opsType: COLLECTION_OPS_TYPE) => () => void;
setDialogMessage: SetDialogMessage;
setCollectionSelectorAttributes: SetCollectionSelectorAttributes;
deleteFileHelper: (permanent?: boolean) => void;
removeFromCollectionHelper: () => void;
@ -52,7 +51,6 @@ const SelectedFileOptions = ({
showCreateCollectionModal,
removeFromCollectionHelper,
fixTimeHelper,
setDialogMessage,
setCollectionSelectorAttributes,
deleteFileHelper,
downloadHelper,
@ -63,8 +61,8 @@ const SelectedFileOptions = ({
activeCollection,
isFavoriteCollection,
}: Props) => {
const { setDialogMessage } = useContext(AppContext);
const [showFixCreationTime, setShowFixCreationTime] = useState(false);
useEffect(() => {
const user: User = getData(LS_KEYS.USER);
const showFixCreationTime =

View file

@ -3,7 +3,6 @@ import React, { useContext, useEffect, useRef, useState } from 'react';
import { syncCollections, createAlbum } from 'services/collectionService';
import constants from 'utils/strings/constants';
import { SetDialogMessage } from 'components/MessageDialog';
import UploadProgress from './UploadProgress';
import UploadStrategyChoiceModal from './UploadStrategyChoiceModal';
@ -35,7 +34,6 @@ interface Props {
setCollectionSelectorAttributes: SetCollectionSelectorAttributes;
setCollectionNamerAttributes: SetCollectionNamerAttributes;
setLoading: SetLoading;
setDialogMessage: SetDialogMessage;
setUploadInProgress: any;
showCollectionSelector: () => void;
fileRejections: FileRejection[];
@ -280,7 +278,7 @@ export default function Upload(props: Props) {
} catch (e) {
setProgressView(false);
logError(e, 'Failed to create album');
props.setDialogMessage({
appContext.setDialogMessage({
title: constants.ERROR,
staticBackdrop: true,
close: { variant: 'danger' },
@ -425,8 +423,8 @@ export default function Upload(props: Props) {
return (
<>
<UploadStrategyChoiceModal
show={choiceModalView}
onHide={() => setChoiceModalView(false)}
open={choiceModalView}
onClose={() => setChoiceModalView(false)}
uploadToSingleCollection={() =>
uploadToSingleNewCollection(
analysisResult.suggestedCollectionName

View file

@ -300,7 +300,6 @@ export default function UploadProgress(props: Props) {
height: '2px',
backgroundColor: 'transparent',
}}
color="negative"
variant="determinate"
value={props.now}
/>

View file

@ -1,12 +1,12 @@
import MessageDialog from 'components/MessageDialog';
import DialogBox from 'components/DialogBox';
import React from 'react';
import { Button } from 'react-bootstrap';
import constants from 'utils/strings/constants';
interface Props {
uploadToMultipleCollection: () => void;
show: boolean;
onHide: () => void;
open: boolean;
onClose: () => void;
uploadToSingleCollection: () => void;
}
function UploadStrategyChoiceModal({
@ -15,7 +15,7 @@ function UploadStrategyChoiceModal({
...props
}: Props) {
return (
<MessageDialog
<DialogBox
{...props}
attributes={{ title: constants.MULTI_FOLDER_UPLOAD }}>
<p
@ -37,7 +37,7 @@ function UploadStrategyChoiceModal({
<Button
variant="outline-success"
onClick={() => {
props.onHide();
props.onClose();
uploadToSingleCollection();
}}
style={{
@ -58,7 +58,7 @@ function UploadStrategyChoiceModal({
<Button
variant="outline-success"
onClick={() => {
props.onHide();
props.onClose();
uploadToMultipleCollection();
}}
style={{
@ -69,7 +69,7 @@ function UploadStrategyChoiceModal({
{constants.UPLOAD_STRATEGY_COLLECTION_PER_FOLDER}
</Button>
</div>
</MessageDialog>
</DialogBox>
);
}
export default UploadStrategyChoiceModal;

View file

@ -1,4 +1,4 @@
import MessageDialog from 'components/MessageDialog';
import DialogBox from 'components/DialogBox';
import SubmitButton from 'components/SubmitButton';
import { REPORT_REASON } from 'constants/publicCollection';
import { Formik, FormikHelpers } from 'formik';
@ -98,10 +98,10 @@ export function AbuseReportForm({ show, close, url }: Iprops) {
};
return (
<MessageDialog
show={show}
<DialogBox
open={show}
size="lg"
onHide={close}
onClose={close}
attributes={{
title: constants.ABUSE_REPORT,
staticBackdrop: true,
@ -557,6 +557,6 @@ export function AbuseReportForm({ show, close, url }: Iprops) {
)}
</Formik>
</Wrapper>
</MessageDialog>
</DialogBox>
);
}

View file

@ -1,102 +0,0 @@
import { createTheme } from '@mui/material/styles';
declare module '@mui/material/styles' {
interface Palette {
accent: Palette['primary'];
danger: Palette['primary'];
negative: Palette['primary'];
}
interface PaletteOptions {
accent?: PaletteOptions['primary'];
danger?: PaletteOptions['primary'];
negative?: PaletteOptions['primary'];
}
}
declare module '@mui/material/Button' {
export interface ButtonPropsColorOverrides {
danger: true;
negative: true;
accent: true;
}
}
declare module '@mui/material/LinearProgress' {
export interface LinearProgressPropsColorOverrides {
danger: true;
negative: true;
accent: true;
}
}
// Create a theme instance.
const darkThemeOptions = createTheme({
components: {
MuiPaper: {
styleOverrides: {
root: { backgroundColor: '#0f0f0f' },
},
},
MuiList: {
styleOverrides: {
root: { padding: 0 },
},
},
MuiLink: {
defaultProps: {
color: 'inherit',
},
styleOverrides: {
root: {
textDecorationColor: 'inherit',
'&:hover': {
color: 'hsla(141, 66%, 50%, 1)',
},
},
},
},
MuiButton: {
styleOverrides: {
root: {
fontSize: '18px',
lineHeight: '21.78px',
padding: '16px',
color: '#fff',
textTransform: 'none',
borderRadius: '8px',
},
},
},
},
palette: {
mode: 'dark',
primary: {
main: 'hsla(0, 0%, 11%, 1)',
},
negative: {
main: '#fff',
},
secondary: {
main: 'hsla(0, 0%, 100%, 0.2)',
},
text: {
primary: 'hsla(0, 0%, 100%, 1)',
secondary: 'hsla(0, 0%, 100%, 0.5)',
},
accent: {
main: 'hsla(141, 66%, 50%, 1)',
dark: 'hsla(141, 73%, 42%, 1)',
},
danger: {
main: '#c93f3f',
},
background: { default: '#000', paper: '#000' },
},
shape: {
borderRadius: 12,
},
});
export default darkThemeOptions;

View file

@ -1,4 +1,4 @@
import { SCROLL_DIRECTION } from 'components/Collections/NavigationButton';
import { SCROLL_DIRECTION } from 'components/Collections/CollectionBar/NavigationButton';
import { useRef, useState, useEffect } from 'react';
export default function useComponentScroll({
@ -15,14 +15,17 @@ export default function useComponentScroll({
}>({});
const updateScrollObj = () => {
if (componentRef.current) {
const { scrollLeft, scrollWidth, clientWidth } =
componentRef.current;
setScrollObj({ scrollLeft, scrollWidth, clientWidth });
if (!componentRef.current) {
return;
}
const { scrollLeft, scrollWidth, clientWidth } = componentRef.current;
setScrollObj({ scrollLeft, scrollWidth, clientWidth });
};
useEffect(() => {
if (!componentRef.current) {
return;
}
// Add event listener
componentRef.current?.addEventListener('scroll', updateScrollObj);

View file

@ -1,4 +1,4 @@
import Container from 'components/Container';
import VerticallyCentered from 'components/Container';
import React, { useContext, useEffect, useState } from 'react';
import constants from 'utils/strings/constants';
import { AppContext } from './_app';
@ -11,12 +11,12 @@ export default function NotFound() {
setLoading(false);
}, []);
return (
<Container>
<VerticallyCentered>
{loading ? (
<span className="sr-only">Loading...</span>
) : (
constants.NOT_FOUND
)}
</Container>
</VerticallyCentered>
);
}

View file

@ -3,7 +3,7 @@ import styled, { ThemeProvider as SThemeProvider } from 'styled-components';
import Navbar from 'components/Navbar';
import constants from 'utils/strings/constants';
import { useRouter } from 'next/router';
import Container from 'components/Container';
import VerticallyCentered from 'components/Container';
import 'bootstrap/dist/css/bootstrap.min.css';
import 'photoswipe/dist/photoswipe.css';
import 'styles/global.css';
@ -19,14 +19,14 @@ import { getAlbumSiteHost, PAGES } from 'constants/pages';
import GoToEnte from 'components/pages/sharedAlbum/GoToEnte';
import { logUploadInfo } from 'utils/upload';
import LoadingBar from 'react-top-loading-bar';
import MessageDialog, {
MessageAttributes,
SetDialogMessage,
} from 'components/MessageDialog';
import DialogBox from 'components/DialogBox';
import { ThemeProvider as MThemeProvider } from '@mui/material/styles';
import darkThemeOptions from 'darkThemeOptions';
import darkThemeOptions from 'themes/darkThemeOptions';
import { CssBaseline } from '@mui/material';
import SidebarToggler from 'components/Navbar/SidebarToggler';
// eslint-disable-next-line @typescript-eslint/no-unused-vars
import * as types from 'styled-components/cssprop';
import { SetDialogBoxAttributes, DialogBoxAttributes } from 'types/dialogBox';
export const LogoImage = styled.img`
max-height: 28px;
@ -64,7 +64,7 @@ type AppContextType = {
startLoading: () => void;
finishLoading: () => void;
closeMessageDialog: () => void;
setDialogMessage: SetDialogMessage;
setDialogMessage: SetDialogBoxAttributes;
sidebarView: boolean;
closeSidebar: () => void;
};
@ -100,7 +100,7 @@ export default function App({ Component, err }) {
const [isAlbumsDomain, setIsAlbumsDomain] = useState(false);
const isLoadingBarRunning = useRef(false);
const loadingBar = useRef(null);
const [dialogMessage, setDialogMessage] = useState<MessageAttributes>();
const [dialogMessage, setDialogMessage] = useState<DialogBoxAttributes>();
const [messageDialogView, setMessageDialogView] = useState(false);
const [sidebarView, setSidebarView] = useState(false);
@ -280,10 +280,9 @@ export default function App({ Component, err }) {
)}
<LoadingBar color="#51cd7c" ref={loadingBar} />
<MessageDialog
size="sm"
show={messageDialogView}
onHide={closeMessageDialog}
<DialogBox
open={messageDialogView}
onClose={closeMessageDialog}
attributes={dialogMessage}
/>
@ -303,11 +302,11 @@ export default function App({ Component, err }) {
closeSidebar,
}}>
{loading ? (
<Container>
<VerticallyCentered>
<EnteSpinner>
<span className="sr-only">Loading...</span>
</EnteSpinner>
</Container>
</VerticallyCentered>
) : (
<Component err={err} setLoading={setLoading} />
)}

View file

@ -1,20 +1,14 @@
import Container from 'components/Container';
import LogoImg from 'components/LogoImg';
import React, { useEffect, useState } from 'react';
import { Alert } from 'react-bootstrap';
import VerticallyCentered from 'components/Container';
import React, { useEffect } from 'react';
import constants from 'utils/strings/constants';
import router from 'next/router';
import ChangeEmailForm from 'components/ChangeEmail';
import { PAGES } from 'constants/pages';
import { getData, LS_KEYS } from 'utils/storage/localStorage';
import { Box, Card, CardContent } from '@mui/material';
import LinkButton from 'components/pages/gallery/LinkButton';
import FormPaper from 'components/Form/FormPaper';
import FormPaperTitle from 'components/Form/FormPaper/Title';
function ChangeEmailPage() {
const [email, setEmail] = useState(null);
const [showMessage, setShowMessage] = useState(false);
const [showBigDialog, setShowBigDialog] = useState(false);
useEffect(() => {
const user = getData(LS_KEYS.USER);
if (!user?.token) {
@ -22,40 +16,13 @@ function ChangeEmailPage() {
}
}, []);
const goToGallery = () => router.push(PAGES.GALLERY);
return (
<Container>
<Card sx={{ minWidth: showBigDialog ? '460px' : '320px' }}>
<CardContent>
<Container disableGutters sx={{ py: 2 }}>
<Box mb={2}>
<LogoImg src="/icon.svg" />
{constants.CHANGE_EMAIL}
</Box>
<Alert
variant="success"
show={showMessage}
style={{ paddingBottom: 0 }}
transition
dismissible
onClose={() => setShowMessage(false)}>
{constants.EMAIL_SENT({ email })}
</Alert>
<ChangeEmailForm
showMessage={(value) => {
setShowMessage(value);
setShowBigDialog(value);
}}
setEmail={setEmail}
/>
<LinkButton onClick={goToGallery}>
{constants.GO_BACK}
</LinkButton>
</Container>
</CardContent>
</Card>
</Container>
<VerticallyCentered>
<FormPaper>
<FormPaperTitle>{constants.CHANGE_EMAIL}</FormPaperTitle>
<ChangeEmailForm />
</FormPaper>
</VerticallyCentered>
);
}

View file

@ -10,16 +10,16 @@ import CryptoWorker, {
import { getActualKey } from 'utils/common/key';
import { setKeys } from 'services/userService';
import SetPasswordForm, {
SetPasswordFormValues,
SetPasswordFormProps,
} from 'components/SetPasswordForm';
import { SESSION_KEYS } from 'utils/storage/sessionStorage';
import { PAGES } from 'constants/pages';
import { KEK, UpdatedKey } from 'types/user';
import { FormikHelpers } from 'formik';
import Container from 'components/Container';
import { CardContent, Box, Card } from '@mui/material';
import LogoImg from 'components/LogoImg';
import LinkButton from 'components/pages/gallery/LinkButton';
import VerticallyCentered from 'components/Container';
import FormPaper from 'components/Form/FormPaper';
import FormPaperFooter from 'components/Form/FormPaper/Footer';
import FormPaperTitle from 'components/Form/FormPaper/Title';
export default function ChangePassword() {
const [token, setToken] = useState<string>();
@ -34,9 +34,9 @@ export default function ChangePassword() {
}
}, []);
const onSubmit = async (
passphrase: string,
setFieldError: FormikHelpers<SetPasswordFormValues>['setFieldError']
const onSubmit: SetPasswordFormProps['callback'] = async (
passphrase,
setFieldError
) => {
const cryptoWorker = await new CryptoWorker();
const key: string = await getActualKey();
@ -78,14 +78,9 @@ export default function ChangePassword() {
};
return (
<Container>
<Card sx={{ maxWidth: '520px' }}>
<CardContent>
<Container disableGutters sx={{ pt: 3 }}>
<Box mb={4}>
<LogoImg src="/icon.svg" />
{constants.CHANGE_PASSWORD}
</Box>
<VerticallyCentered>
<FormPaper>
<FormPaperTitle>{constants.CHANGE_PASSWORD}</FormPaperTitle>
<SetPasswordForm
callback={onSubmit}
buttonText={constants.CHANGE_PASSWORD}
@ -95,12 +90,13 @@ export default function ChangePassword() {
: null
}
/>
<LinkButton sx={{ mt: 2 }} onClick={router.back}>
<FormPaperFooter>
<LinkButton onClick={router.back}>
{constants.GO_BACK}
</LinkButton>
</Container>
</CardContent>
</Card>
</Container>
</FormPaperFooter>
</FormPaper>
</VerticallyCentered>
);
}

View file

@ -12,13 +12,15 @@ import CryptoWorker, {
} from 'utils/crypto';
import { logoutUser } from 'services/userService';
import { isFirstLogin } from 'utils/storage';
import SingleInputForm from 'components/SingleInputForm';
import SingleInputForm, {
SingleInputFormProps,
} from 'components/SingleInputForm';
import { AppContext } from 'pages/_app';
import { logError } from 'utils/sentry';
import { KeyAttributes } from 'types/user';
import FormContainer from 'components/Form/FormContainer';
import FormPaper from 'components/Form/FormPaper';
import FormPaperHeaderText from 'components/Form/FormPaper/HeaderText';
import FormPaperTitle from 'components/Form/FormPaper/Title';
import FormPaperFooter from 'components/Form/FormPaper/Footer';
import LinkButton from 'components/pages/gallery/LinkButton';
@ -48,7 +50,10 @@ export default function Credentials() {
appContext.showNavBar(false);
}, []);
const verifyPassphrase = async (passphrase, setFieldError) => {
const verifyPassphrase: SingleInputFormProps['callback'] = async (
passphrase,
setFieldError
) => {
try {
const cryptoWorker = await new CryptoWorker();
let kek: string = null;
@ -84,13 +89,10 @@ export default function Credentials() {
router.push(redirectURL ?? PAGES.GALLERY);
} catch (e) {
logError(e, 'user entered a wrong password');
setFieldError('passphrase', constants.INCORRECT_PASSPHRASE);
setFieldError(constants.INCORRECT_PASSPHRASE);
}
} catch (e) {
setFieldError(
'passphrase',
`${constants.UNKNOWN_ERROR} ${e.message}`
);
setFieldError(`${constants.UNKNOWN_ERROR} ${e.message}`);
}
};
@ -99,19 +101,20 @@ export default function Credentials() {
return (
<FormContainer>
<FormPaper style={{ minWidth: '320px' }}>
<FormPaperHeaderText> {constants.PASSWORD}</FormPaperHeaderText>
<FormPaperTitle>{constants.PASSWORD}</FormPaperTitle>
<SingleInputForm
callback={verifyPassphrase}
placeholder={constants.RETURN_PASSPHRASE_HINT}
buttonText={constants.VERIFY_PASSPHRASE}
fieldType="password"
/>
<FormPaperFooter style={{ justifyContent: 'space-between' }}>
<LinkButton onClick={redirectToRecoverPage}>
{constants.FORGOT_PASSWORD}
</LinkButton>
<LinkButton onClick={logoutUser}>
{constants.GO_BACK}
{constants.CHANGE_EMAIL}
</LinkButton>
</FormPaperFooter>
</FormPaper>

View file

@ -168,7 +168,6 @@ export default function Deduplicate() {
activeCollection={ALL_SECTION}
/>
<DeduplicateOptions
setDialogMessage={setDialogMessage}
deleteFileHelper={deleteFileHelper}
count={selected.count}
close={closeDeduplication}

View file

@ -596,7 +596,6 @@ export default function Gallery() {
<PlanSelector
modalView={planModalView}
closeModal={() => setPlanModalView(false)}
setDialogMessage={setDialogMessage}
setLoading={setBlockingLoad}
/>
<AlertBanner bannerMessage={bannerMessage} />
@ -658,7 +657,6 @@ export default function Gallery() {
)}
setLoading={setBlockingLoad}
setCollectionNamerAttributes={setCollectionNamerAttributes}
setDialogMessage={setDialogMessage}
setUploadInProgress={setUploadInProgress}
fileRejections={fileRejections}
setFiles={setFiles}
@ -719,7 +717,6 @@ export default function Gallery() {
showCreateCollectionModal={
showCreateCollectionModal
}
setDialogMessage={setDialogMessage}
setCollectionSelectorAttributes={
setCollectionSelectorAttributes
}

View file

@ -11,13 +11,16 @@ import {
} from 'utils/crypto';
import SetPasswordForm from 'components/SetPasswordForm';
import { justSignedUp, setJustSignedUp } from 'utils/storage';
import RecoveryKeyModal from 'components/RecoveryKeyModal';
import RecoveryKey from 'components/RecoveryKey';
import { PAGES } from 'constants/pages';
import Container from 'components/Container';
import VerticallyCentered from 'components/Container';
import EnteSpinner from 'components/EnteSpinner';
import { AppContext } from 'pages/_app';
import { logError } from 'utils/sentry';
import { KeyAttributes, User } from 'types/user';
import FormContainer from 'components/Form/FormContainer';
import FormPaper from 'components/Form/FormPaper';
import FormTitle from 'components/Form/FormPaper/Title';
export default function Generate() {
const [token, setToken] = useState<string>();
@ -78,13 +81,13 @@ export default function Generate() {
return (
<>
{loading ? (
<Container>
<VerticallyCentered>
<EnteSpinner>
<span className="sr-only">Loading...</span>
</EnteSpinner>
</Container>
</VerticallyCentered>
) : recoverModalView ? (
<RecoveryKeyModal
<RecoveryKey
show={recoverModalView}
onHide={() => {
setRecoveryModalView(false);
@ -93,11 +96,16 @@ export default function Generate() {
somethingWentWrong={() => null}
/>
) : (
<FormContainer>
<FormPaper>
<FormTitle>{constants.SET_PASSPHRASE}</FormTitle>
<SetPasswordForm
callback={onSubmit}
buttonText={constants.SET_PASSPHRASE}
back={logoutUser}
/>
</FormPaper>
</FormContainer>
)}
</>
);

View file

@ -10,7 +10,6 @@ import EnteSpinner from 'components/EnteSpinner';
import SignUp from 'components/SignUp';
import constants from 'utils/strings/constants';
import localForage from 'utils/storage/localForage';
import IncognitoWarning from 'components/IncognitoWarning';
import { logError } from 'utils/sentry';
import { getAlbumSiteHost, PAGES } from 'constants/pages';
@ -102,7 +101,7 @@ export default function LandingPage() {
const appContext = useContext(AppContext);
const [loading, setLoading] = useState(true);
const [showLogin, setShowLogin] = useState(true);
const [blockUsage, setBlockUsage] = useState(false);
useEffect(() => {
appContext.showNavBar(false);
const currentURL = new URL(window.location.href);
@ -143,7 +142,12 @@ export default function LandingPage() {
await localForage.ready();
} catch (e) {
logError(e, 'usage in incognito mode tried');
setBlockUsage(true);
appContext.setDialogMessage({
title: constants.LOCAL_STORAGE_NOT_ACCESSIBLE,
staticBackdrop: true,
nonClosable: true,
content: constants.LOCAL_STORAGE_NOT_ACCESSIBLE_MESSAGE,
});
} finally {
setLoading(false);
}
@ -216,7 +220,6 @@ export default function LandingPage() {
)}
</SideBox>
</DesktopBox>
{blockUsage && <IncognitoWarning />}
</>
)}
</Container>

View file

@ -3,7 +3,7 @@ import { useRouter } from 'next/router';
import EnteSpinner from 'components/EnteSpinner';
import { AppContext } from 'pages/_app';
import Login from 'components/Login';
import Container from 'components/Container';
import VerticallyCentered from 'components/Container';
import { getData, LS_KEYS } from 'utils/storage/localStorage';
import { PAGES } from 'constants/pages';
import FormContainer from 'components/Form/FormContainer';
@ -30,14 +30,14 @@ export default function Home() {
};
return loading ? (
<Container>
<VerticallyCentered>
<EnteSpinner>
<span className="sr-only">Loading...</span>
</EnteSpinner>
</Container>
</VerticallyCentered>
) : (
<FormContainer>
<FormPaper sx={{ minWidth: '320px' }}>
<FormPaper>
<Login signUp={register} />
</FormPaper>
</FormContainer>

View file

@ -13,14 +13,15 @@ import CryptoWorker, {
SaveKeyInSessionStore,
} from 'utils/crypto';
import SingleInputForm from 'components/SingleInputForm';
import MessageDialog from 'components/MessageDialog';
import Container from 'components/Container';
import { Card, Button } from 'react-bootstrap';
import VerticallyCentered from 'components/Container';
import { Button } from 'react-bootstrap';
import { AppContext } from 'pages/_app';
import LogoImg from 'components/LogoImg';
import { logError } from 'utils/sentry';
import { getKey, SESSION_KEYS } from 'utils/storage/sessionStorage';
import { KeyAttributes, User } from 'types/user';
import FormPaper from 'components/Form/FormPaper';
import FormPaperTitle from 'components/Form/FormPaper/Title';
import FormPaperFooter from 'components/Form/FormPaper/Footer';
const bip39 = require('bip39');
// mobile client library only supports english.
bip39.setDefaultWordlist('english');
@ -28,7 +29,6 @@ bip39.setDefaultWordlist('english');
export default function Recover() {
const router = useRouter();
const [keyAttributes, setKeyAttributes] = useState<KeyAttributes>();
const [messageDialogView, SetMessageDialogView] = useState(false);
const appContext = useContext(AppContext);
useEffect(() => {
@ -78,49 +78,33 @@ export default function Recover() {
}
};
const showNoRecoveryKeyMessage = () => {
appContext.setDialogMessage({
title: constants.SORRY,
close: {},
content: constants.NO_RECOVERY_KEY_MESSAGE,
});
};
return (
<>
<Container>
<Card style={{ minWidth: '320px' }} className="text-center">
<Card.Body style={{ padding: '40px 30px' }}>
<Card.Title style={{ marginBottom: '32px' }}>
<LogoImg src="/icon.svg" />
{constants.RECOVER_ACCOUNT}
</Card.Title>
<VerticallyCentered>
<FormPaper>
<FormPaperTitle>{constants.RECOVER_ACCOUNT}</FormPaperTitle>
<SingleInputForm
callback={recover}
fieldType="text"
placeholder={constants.RETURN_RECOVERY_KEY_HINT}
placeholder={constants.RECOVERY_KEY_HINT}
buttonText={constants.RECOVER}
/>
<div
style={{
display: 'flex',
flexDirection: 'column',
marginTop: '12px',
}}>
<Button
variant="link"
onClick={() => SetMessageDialogView(true)}>
<FormPaperFooter style={{ justifyContent: 'space-between' }}>
<Button variant="link" onClick={showNoRecoveryKeyMessage}>
{constants.NO_RECOVERY_KEY}
</Button>
<Button variant="link" onClick={router.back}>
{constants.GO_BACK}
</Button>
</div>
</Card.Body>
</Card>
</Container>
<MessageDialog
size="lg"
show={messageDialogView}
onHide={() => SetMessageDialogView(false)}
attributes={{
title: constants.SORRY,
close: {},
content: constants.NO_RECOVERY_KEY_MESSAGE,
}}
/>
</>
</FormPaperFooter>
</FormPaper>
</VerticallyCentered>
);
}

Some files were not shown because too many files have changed in this diff Show more