Merge branch 'ui-redesign' into upload-redesign
This commit is contained in:
commit
7eda72902b
|
@ -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.
BIN
public/fonts/inter-v11-latin-700.woff
Normal file
BIN
public/fonts/inter-v11-latin-700.woff
Normal file
Binary file not shown.
BIN
public/fonts/inter-v11-latin-700.woff2
Normal file
BIN
public/fonts/inter-v11-latin-700.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-v11-latin-900.woff
Normal file
BIN
public/fonts/inter-v11-latin-900.woff
Normal file
Binary file not shown.
BIN
public/fonts/inter-v11-latin-900.woff2
Normal file
BIN
public/fonts/inter-v11-latin-900.woff2
Normal file
Binary file not shown.
BIN
public/fonts/inter-v11-latin-regular.woff
Normal file
BIN
public/fonts/inter-v11-latin-regular.woff
Normal file
Binary file not shown.
BIN
public/fonts/inter-v11-latin-regular.woff2
Normal file
BIN
public/fonts/inter-v11-latin-regular.woff2
Normal file
Binary file not shown.
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
};
|
20
src/components/CodeBlock/CopyButton.tsx
Normal file
20
src/components/CodeBlock/CopyButton.tsx
Normal 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>
|
||||
);
|
||||
}
|
47
src/components/CodeBlock/index.tsx
Normal file
47
src/components/CodeBlock/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
21
src/components/CodeBlock/styledComponents.tsx
Normal file
21
src/components/CodeBlock/styledComponents.tsx
Normal 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;
|
||||
`;
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
37
src/components/Collections/AllCollections/CollectionCard.tsx
Normal file
37
src/components/Collections/AllCollections/CollectionCard.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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;
|
|
@ -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>
|
||||
);
|
||||
}
|
30
src/components/Collections/AllCollections/content.tsx
Normal file
30
src/components/Collections/AllCollections/content.tsx
Normal 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>
|
||||
);
|
||||
}
|
46
src/components/Collections/AllCollections/header.tsx
Normal file
46
src/components/Collections/AllCollections/header.tsx
Normal 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>
|
||||
);
|
||||
}
|
67
src/components/Collections/AllCollections/index.tsx
Normal file
67
src/components/Collections/AllCollections/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
|
@ -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(
|
||||
(
|
|
@ -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 (
|
|
@ -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;
|
|
@ -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';
|
|
@ -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}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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={{
|
||||
|
|
|
@ -174,6 +174,7 @@ const CollectionOptions = (props: CollectionOptionsProps) => {
|
|||
open={Boolean(optionEl)}
|
||||
onClose={handleClose}
|
||||
MenuListProps={{
|
||||
disablePadding: true,
|
||||
'aria-labelledby': 'collection-options',
|
||||
}}>
|
||||
<Paper>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)`
|
||||
|
|
|
@ -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};
|
||||
|
|
26
src/components/DialogBox/base.tsx
Normal file
26
src/components/DialogBox/base.tsx
Normal 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;
|
92
src/components/DialogBox/index.tsx
Normal file
92
src/components/DialogBox/index.tsx
Normal 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>
|
||||
);
|
||||
}
|
9
src/components/DialogBox/messageText.tsx
Normal file
9
src/components/DialogBox/messageText.tsx
Normal 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;
|
26
src/components/DialogBox/titleWithCloseButton.tsx
Normal file
26
src/components/DialogBox/titleWithCloseButton.tsx
Normal 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;
|
|
@ -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} />;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
12
src/components/Form/FormPaper/Title.tsx
Normal file
12
src/components/Form/FormPaper/Title.tsx
Normal 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;
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -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}
|
||||
|
|
|
@ -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';
|
||||
|
|
74
src/components/RecoveryKey/index.tsx
Normal file
74
src/components/RecoveryKey/index.tsx
Normal 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;
|
6
src/components/RecoveryKey/styledComponents.tsx
Normal file
6
src/components/RecoveryKey/styledComponents.tsx
Normal 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),
|
||||
}));
|
|
@ -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;
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
40
src/components/Sidebar/NavigationButton.tsx
Normal file
40
src/components/Sidebar/NavigationButton.tsx
Normal 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;
|
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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)}
|
||||
/>
|
||||
/> */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
`;
|
||||
|
|
40
src/components/Sidebar/userDetailsSection.tsx
Normal file
40
src/components/Sidebar/userDetailsSection.tsx
Normal 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} />
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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%' }}>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)};
|
||||
`
|
||||
);
|
||||
|
|
|
@ -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',
|
||||
};
|
|
@ -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',
|
||||
};
|
|
@ -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',
|
||||
};
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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' }}>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -300,7 +300,6 @@ export default function UploadProgress(props: Props) {
|
|||
height: '2px',
|
||||
backgroundColor: 'transparent',
|
||||
}}
|
||||
color="negative"
|
||||
variant="determinate"
|
||||
value={props.now}
|
||||
/>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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} />
|
||||
)}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -168,7 +168,6 @@ export default function Deduplicate() {
|
|||
activeCollection={ALL_SECTION}
|
||||
/>
|
||||
<DeduplicateOptions
|
||||
setDialogMessage={setDialogMessage}
|
||||
deleteFileHelper={deleteFileHelper}
|
||||
count={selected.count}
|
||||
close={closeDeduplication}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue