refactor AddViewer form

This commit is contained in:
Abhinav 2023-07-12 12:44:19 +05:30
parent f40c761163
commit daf2546aa4
5 changed files with 380 additions and 395 deletions

View file

@ -4,10 +4,18 @@ import { EnteDrawer } from 'components/EnteDrawer';
import { t } from 'i18next';
import { DialogProps } from '@mui/material';
import Titlebar from 'components/Titlebar';
import MenuSectionTitle from 'components/Menu/MenuSectionTitle';
import WorkspacesIcon from '@mui/icons-material/Workspaces';
import ViewerEmailShare from './ViewerEmailShare';
import AddViewerForm, { ViewerEmailShareOptionsProps } from './AddViewerForm';
import { GalleryContext } from 'pages/gallery';
import { useContext, useState, useEffect } from 'react';
import {
getLocalCollections,
shareCollection,
} from 'services/collectionService';
import { handleSharingErrors } from 'utils/error/ui';
import { getData, LS_KEYS } from 'utils/storage/localStorage';
import { getLocalFamilyData } from 'utils/user/family';
import { User } from 'types/user';
interface Iprops {
collection: Collection;
@ -22,14 +30,92 @@ export default function AddViewer({
onClose,
onRootClose,
}: Iprops) {
const handleRootClose = () => {
onClose();
onRootClose();
};
const handleDrawerClose: DialogProps['onClose'] = (_, reason) => {
if (reason === 'backdropClick') {
onRootClose();
handleRootClose();
} else {
onClose();
}
};
const galleryContext = useContext(GalleryContext);
const [updatedOptionsList, setUpdatedOptionsList] = useState(['']);
let updatedList = [];
useEffect(() => {
const getUpdatedOptionsList = async () => {
const ownerUser: User = getData(LS_KEYS.USER);
const collectionList = getLocalCollections();
const familyList = getLocalFamilyData();
const result = await collectionList;
const emails = result.flatMap((item) => {
if (item.owner.email && item.owner.id !== ownerUser.id) {
return [item.owner.email];
} else {
const shareeEmails = item.sharees.map(
(sharee) => sharee.email
);
return shareeEmails;
}
});
// adding family members
if (familyList) {
const family = familyList.members.map((member) => member.email);
emails.push(...family);
}
updatedList = Array.from(new Set(emails));
const shareeEmailsCollection = collection.sharees.map(
(sharees) => sharees.email
);
const filteredList = updatedList.filter(
(email) =>
!shareeEmailsCollection.includes(email) &&
email !== ownerUser.email
);
setUpdatedOptionsList(filteredList);
};
getUpdatedOptionsList();
}, []);
const collectionShare: ViewerEmailShareOptionsProps['callback'] = async (
emails,
setFieldError,
resetForm
) => {
try {
const user: User = getData(LS_KEYS.USER);
for (const email of emails) {
if (email === user.email) {
setFieldError(t('SHARE_WITH_SELF'));
break;
} else if (
collection?.sharees?.find((value) => value.email === email)
) {
setFieldError(t('ALREADY_SHARED', { email }));
break;
} else {
await shareCollection(collection, email, 'VIEWER');
await galleryContext.syncWithRemote(false, true);
}
}
resetForm();
} catch (e) {
const errorMessage = handleSharingErrors(e);
setFieldError(errorMessage);
}
};
return (
<>
<EnteDrawer anchor="right" open={open} onClose={handleDrawerClose}>
@ -37,19 +123,22 @@ export default function AddViewer({
<Titlebar
onClose={onClose}
title={t('ADD_VIEWERS')}
onRootClose={onRootClose}
onRootClose={handleRootClose}
caption={collection.name}
/>
<Stack py={'20px'} px={'8px'} spacing={'8px'}>
<MenuSectionTitle
title={t('ADD_NEW_EMAIL')}
icon={<WorkspacesIcon />}
/>
<ViewerEmailShare
collection={collection}
onClose={onClose}
/>
</Stack>
<AddViewerForm
onClose={onClose}
callback={collectionShare}
optionsList={updatedOptionsList}
placeholder={t('ENTER_EMAIL')}
fieldType="email"
buttonText={t('ADD_VIEWERS')}
submitButtonProps={{
size: 'large',
sx: { mt: 1, mb: 2 },
}}
disableAutoFocus
/>
</Stack>
</EnteDrawer>
</>

View file

@ -0,0 +1,248 @@
import React, { useEffect, useMemo, useState } from 'react';
import { Formik, FormikHelpers, FormikState } from 'formik';
import * as Yup from 'yup';
import SubmitButton from 'components/SubmitButton';
import TextField from '@mui/material/TextField';
import { FlexWrapper } from 'components/Container';
import { Button, FormHelperText, Stack } from '@mui/material';
import { t } from 'i18next';
import { MenuItemGroup } from 'components/Menu/MenuItemGroup';
import { EnteMenuItem } from 'components/Menu/EnteMenuItem';
import MenuItemDivider from 'components/Menu/MenuItemDivider';
import MenuSectionTitle from 'components/Menu/MenuSectionTitle';
import Avatar from 'components/pages/gallery/Avatar';
import DoneIcon from '@mui/icons-material/Done';
interface formValues {
inputValue: string;
selectedOptions: string[];
}
export interface ViewerEmailShareOptionsProps {
callback: (
emails: string[],
setFieldError: (errorMessage: string) => void,
resetForm: (nextState?: Partial<FormikState<formValues>>) => void
) => Promise<void>;
fieldType: 'text' | 'email' | 'password';
placeholder: string;
buttonText: string;
submitButtonProps?: any;
initialValue?: string;
secondaryButtonAction?: () => void;
disableAutoFocus?: boolean;
hiddenPreInput?: any;
caption?: any;
hiddenPostInput?: any;
autoComplete?: string;
blockButton?: boolean;
hiddenLabel?: boolean;
onClose?: () => void;
optionsList?: string[];
}
export default function AddViewerForm(props: ViewerEmailShareOptionsProps) {
const { submitButtonProps } = props;
const { sx: buttonSx, ...restSubmitButtonProps } = submitButtonProps ?? {};
const [updatedOptionsList, setUpdatedOptionsList] = useState<string[]>(
props.optionsList
);
const [disableInput, setDisableInput] = useState(false);
const [loading, SetLoading] = useState(false);
useEffect(() => {
setUpdatedOptionsList(props.optionsList);
}, [props.optionsList]);
const submitForm = async (
values: formValues,
{ setFieldError, resetForm }: FormikHelpers<formValues>
) => {
SetLoading(true);
if (values.inputValue !== '') {
await props.callback(
[values.inputValue],
(message) => setFieldError('inputValue', message),
resetForm
);
} else if (values.selectedOptions.length !== 0) {
await props.callback(
values.selectedOptions,
(message) => setFieldError('inputValue', message),
resetForm
);
}
setDisableInput(false);
SetLoading(false);
props.onClose();
};
const validationSchema = useMemo(() => {
switch (props.fieldType) {
case 'text':
return Yup.object().shape({
inputValue: Yup.string().required(t('REQUIRED')),
});
case 'email':
return Yup.object().shape({
inputValue: Yup.string().email(t('EMAIL_ERROR')),
});
}
}, [props.fieldType]);
const handleInputFieldClick = (setFieldValue) => {
setFieldValue('selectedOptions', []);
};
return (
<Formik<formValues>
initialValues={{
inputValue: props.initialValue ?? '',
selectedOptions: [],
}}
onSubmit={submitForm}
validationSchema={validationSchema}
validateOnChange={false}
validateOnBlur={false}>
{({
values,
errors,
handleChange,
handleSubmit,
setFieldValue,
}) => (
<form noValidate onSubmit={handleSubmit}>
<Stack spacing={'24px'} py={'20px'} px={'12px'}>
{props.hiddenPreInput}
<Stack>
<MenuSectionTitle title={t('ADD_NEW_EMAIL')} />
<TextField
sx={{ marginTop: 0 }}
hiddenLabel={props.hiddenLabel}
fullWidth
type={props.fieldType}
id={props.fieldType}
onChange={handleChange('inputValue')}
onClick={() =>
handleInputFieldClick(setFieldValue)
}
name={props.fieldType}
{...(props.hiddenLabel
? { placeholder: props.placeholder }
: { label: props.placeholder })}
error={Boolean(errors.inputValue)}
helperText={errors.inputValue}
value={values.inputValue}
disabled={loading || disableInput}
autoFocus={!props.disableAutoFocus}
autoComplete={props.autoComplete}
/>
</Stack>
<Stack>
<MenuSectionTitle title={t('OR_ADD_EXISTING')} />
<MenuItemGroup>
{updatedOptionsList.map((item, index) => (
<>
<EnteMenuItem
fontWeight="normal"
key={item}
onClick={() => {
if (
values.selectedOptions.includes(
item
)
) {
setFieldValue(
'selectedOptions',
values.selectedOptions.filter(
(selectedOption) =>
selectedOption !==
item
)
);
} else {
setFieldValue(
'selectedOptions',
[
...values.selectedOptions,
item,
]
);
}
}}
label={item}
startIcon={<Avatar email={item} />}
endIcon={
values.selectedOptions.includes(
item
) ? (
<DoneIcon />
) : null
}
/>
{index !==
props.optionsList.length - 1 && (
<MenuItemDivider />
)}
</>
))}
</MenuItemGroup>
</Stack>
<FormHelperText
sx={{
position: 'relative',
top: errors.inputValue ? '-22px' : '0',
float: 'right',
padding: '0 8px',
}}>
{props.caption}
</FormHelperText>
{props.hiddenPostInput}
</Stack>
<FlexWrapper
px={'8px'}
justifyContent={'center'}
flexWrap={
props.blockButton ? 'wrap-reverse' : 'nowrap'
}>
<Stack direction={'column'} px={'8px'} width={'100%'}>
{props.secondaryButtonAction && (
<Button
onClick={props.secondaryButtonAction}
size="large"
color="secondary"
sx={{
'&&&': {
mt: !props.blockButton ? 2 : 0.5,
mb: !props.blockButton ? 4 : 0,
mr: !props.blockButton ? 1 : 0,
...buttonSx,
},
}}
{...restSubmitButtonProps}>
{t('CANCEL')}
</Button>
)}
<SubmitButton
sx={{
'&&&': {
mt: 2,
...buttonSx,
},
}}
buttonText={props.buttonText}
loading={loading}
{...restSubmitButtonProps}
/>
</Stack>
</FlexWrapper>
</form>
)}
</Formik>
);
}

View file

@ -1,105 +0,0 @@
import { GalleryContext } from 'pages/gallery';
import React, { useContext, useState, useEffect } from 'react';
import { t } from 'i18next';
import { shareCollection } from 'services/collectionService';
import { User } from 'types/user';
import { handleSharingErrors } from 'utils/error/ui';
import { getData, LS_KEYS } from 'utils/storage/localStorage';
import { getLocalCollections } from 'services/collectionService';
import { getLocalFamilyData } from 'utils/user/family';
import ViewerEmailShareOptions, {
ViewerEmailShareOptionsProps,
} from './ViewerEmailShareOptions';
export default function ViewerEmailShare({ collection, onClose }) {
const galleryContext = useContext(GalleryContext);
const [updatedOptionsList, setUpdatedOptionsList] = useState(['']);
let updatedList = [];
useEffect(() => {
const getUpdatedOptionsList = async () => {
const ownerUser: User = getData(LS_KEYS.USER);
const collectionList = getLocalCollections();
const familyList = getLocalFamilyData();
const result = await collectionList;
const emails = result.flatMap((item) => {
if (item.owner.email && item.owner.id !== ownerUser.id) {
return [item.owner.email];
} else {
const shareeEmails = item.sharees.map(
(sharee) => sharee.email
);
return shareeEmails;
}
});
// adding family members
if (familyList) {
const family = familyList.members.map((member) => member.email);
emails.push(...family);
}
updatedList = Array.from(new Set(emails));
const shareeEmailsCollection = collection.sharees.map(
(sharees) => sharees.email
);
const filteredList = updatedList.filter(
(email) =>
!shareeEmailsCollection.includes(email) &&
email !== ownerUser.email
);
setUpdatedOptionsList(filteredList);
};
getUpdatedOptionsList();
}, []);
const collectionShare: ViewerEmailShareOptionsProps['callback'] = async (
emails,
setFieldError,
resetForm
) => {
try {
const user: User = getData(LS_KEYS.USER);
for (const email of emails) {
if (email === user.email) {
setFieldError(t('SHARE_WITH_SELF'));
break;
} else if (
collection?.sharees?.find((value) => value.email === email)
) {
setFieldError(t('ALREADY_SHARED', { email }));
break;
} else {
await shareCollection(collection, email, 'VIEWER');
await galleryContext.syncWithRemote(false, true);
}
}
resetForm();
} catch (e) {
const errorMessage = handleSharingErrors(e);
setFieldError(errorMessage);
}
};
return (
<>
<ViewerEmailShareOptions
onClose={onClose}
callback={collectionShare}
optionsList={updatedOptionsList}
placeholder={t('ENTER_EMAIL')}
fieldType="email"
buttonText={t('ADD_VIEWERS')}
submitButtonProps={{
size: 'large',
sx: { mt: 1, mb: 2 },
}}
disableAutoFocus
/>
</>
);
}

View file

@ -1,249 +0,0 @@
import React, { useEffect, useMemo, useState } from 'react';
import { Formik, FormikHelpers, FormikState } from 'formik';
import * as Yup from 'yup';
import SubmitButton from 'components/SubmitButton';
import TextField from '@mui/material/TextField';
import { FlexWrapper } from 'components/Container';
import { Button, FormHelperText, Stack } from '@mui/material';
import { t } from 'i18next';
import { MenuItemGroup } from 'components/Menu/MenuItemGroup';
import { EnteMenuItem } from 'components/Menu/EnteMenuItem';
import MenuItemDivider from 'components/Menu/MenuItemDivider';
import MenuSectionTitle from 'components/Menu/MenuSectionTitle';
import Avatar from 'components/pages/gallery/Avatar';
import DoneIcon from '@mui/icons-material/Done';
interface formValues {
inputValue: string;
selectedOptions: string[];
}
export interface ViewerEmailShareOptionsProps {
callback: (
emails: string[],
setFieldError: (errorMessage: string) => void,
resetForm: (nextState?: Partial<FormikState<formValues>>) => void
) => Promise<void>;
fieldType: 'text' | 'email' | 'password';
placeholder: string;
buttonText: string;
submitButtonProps?: any;
initialValue?: string;
secondaryButtonAction?: () => void;
disableAutoFocus?: boolean;
hiddenPreInput?: any;
caption?: any;
hiddenPostInput?: any;
autoComplete?: string;
blockButton?: boolean;
hiddenLabel?: boolean;
onClose?: () => void;
optionsList?: string[];
}
export default function ViewerEmailShareOptions(
props: ViewerEmailShareOptionsProps
) {
console.log('OptionList Intial', props.optionsList);
const { submitButtonProps } = props;
const { sx: buttonSx, ...restSubmitButtonProps } = submitButtonProps ?? {};
const [updatedOptionsList, setUpdatedOptionsList] = useState<string[]>(
props.optionsList
);
const [disableInput, setDisableInput] = useState(false);
const [loading, SetLoading] = useState(false);
useEffect(() => {
setUpdatedOptionsList(props.optionsList);
}, [props.optionsList]);
const submitForm = async (
values: formValues,
{ setFieldError, resetForm }: FormikHelpers<formValues>
) => {
SetLoading(true);
if (values.inputValue !== '') {
await props.callback(
[values.inputValue],
(message) => setFieldError('inputValue', message),
resetForm
);
} else if (values.selectedOptions.length !== 0) {
await props.callback(
values.selectedOptions,
(message) => setFieldError('inputValue', message),
resetForm
);
}
setDisableInput(false);
SetLoading(false);
props.onClose();
};
const validationSchema = useMemo(() => {
switch (props.fieldType) {
case 'text':
return Yup.object().shape({
inputValue: Yup.string().required(t('REQUIRED')),
});
case 'email':
return Yup.object().shape({
inputValue: Yup.string().email(t('EMAIL_ERROR')),
});
}
}, [props.fieldType]);
const handleInputFieldClick = (setFieldValue) => {
setFieldValue('selectedOptions', []);
};
return (
<Formik<formValues>
initialValues={{
inputValue: props.initialValue ?? '',
selectedOptions: [],
}}
onSubmit={submitForm}
validationSchema={validationSchema}
validateOnChange={false}
validateOnBlur={false}>
{({
values,
errors,
handleChange,
handleSubmit,
setFieldValue,
}) => (
<form noValidate onSubmit={handleSubmit}>
{props.hiddenPreInput}
<TextField
hiddenLabel={props.hiddenLabel}
fullWidth
type={props.fieldType}
id={props.fieldType}
onChange={handleChange('inputValue')}
onClick={() => handleInputFieldClick(setFieldValue)}
name={props.fieldType}
{...(props.hiddenLabel
? { placeholder: props.placeholder }
: { label: props.placeholder })}
error={Boolean(errors.inputValue)}
helperText={errors.inputValue}
value={values.inputValue}
disabled={loading || disableInput}
autoFocus={!props.disableAutoFocus}
autoComplete={props.autoComplete}
/>
<Stack py={'10px'} px={'8px'}>
{' '}
</Stack>
<MenuSectionTitle title={t('OR_ADD_EXISTING')} />
<MenuItemGroup>
{updatedOptionsList.map((item, index) => (
<>
<EnteMenuItem
//
fontWeight="normal"
key={item}
onClick={() => {
if (
values.selectedOptions.includes(
item
)
) {
setFieldValue(
'selectedOptions',
values.selectedOptions.filter(
(selectedOption) =>
selectedOption !== item
)
);
} else {
setFieldValue('selectedOptions', [
...values.selectedOptions,
item,
]);
}
}}
label={item}
startIcon={<Avatar email={item} />}
endIcon={
values.selectedOptions.includes(
item
) ? (
<DoneIcon />
) : null
}
/>
{index !== props.optionsList.length - 1 && (
<MenuItemDivider />
)}
</>
))}
</MenuItemGroup>
<FormHelperText
sx={{
position: 'relative',
top: errors.inputValue ? '-22px' : '0',
float: 'right',
padding: '0 8px',
}}>
{props.caption}
</FormHelperText>
{props.hiddenPostInput}
<Stack py={'20px'} px={'8px'} spacing={'8px'}>
{' '}
</Stack>
<Stack py={'5px'} px={'8px'}>
{' '}
</Stack>
<FlexWrapper
justifyContent={'center'}
flexWrap={
props.blockButton ? 'wrap-reverse' : 'nowrap'
}>
{props.secondaryButtonAction && (
<Button
onClick={props.secondaryButtonAction}
size="large"
color="secondary"
sx={{
'&&&': {
mt: !props.blockButton ? 2 : 0.5,
mb: !props.blockButton ? 4 : 0,
mr: !props.blockButton ? 1 : 0,
...buttonSx,
},
}}
{...restSubmitButtonProps}>
{t('CANCEL')}
</Button>
)}
<SubmitButton
sx={{
'&&&': {
mt: 2,
...buttonSx,
},
}}
buttonText={props.buttonText}
loading={loading}
{...restSubmitButtonProps}
/>
</FlexWrapper>
</form>
)}
</Formik>
);
}

View file

@ -30,42 +30,44 @@ export default function EmailShare({
const openAddCollab = () => setAddCollabView(true);
return (
<Stack>
<MenuSectionTitle
title={t('SHARE_WITH_PEOPLE')}
icon={<Workspaces />}
/>
<MenuItemGroup>
{collection.sharees.length > 0 ? (
<ManageParticipants
collection={collection}
onRootClose={onRootClose}
<>
<Stack>
<MenuSectionTitle
title={t('SHARE_WITH_PEOPLE')}
icon={<Workspaces />}
/>
<MenuItemGroup>
{collection.sharees.length > 0 ? (
<ManageParticipants
collection={collection}
onRootClose={onRootClose}
/>
) : null}
<EnteMenuItem
startIcon={<AddIcon />}
onClick={openAddViewer}
label={t('ADD_VIEWERS')}
/>
) : null}
<EnteMenuItem
startIcon={<AddIcon />}
onClick={openAddViewer}
label={t('ADD_VIEWERS')}
/>
<MenuItemDivider hasIcon />
<EnteMenuItem
startIcon={<AddIcon />}
onClick={openAddCollab}
label={t('ADD_COLLABORATORS')}
/>
</MenuItemGroup>
<MenuItemDivider hasIcon />
<EnteMenuItem
startIcon={<AddIcon />}
onClick={openAddCollab}
label={t('ADD_COLLABORATORS')}
/>
</MenuItemGroup>
</Stack>
<AddViewer
open={addViewerView}
onClose={closeAddCollab}
onClose={closeAddViewer}
onRootClose={onRootClose}
collection={collection}
/>
<AddCollab
open={addCollabView}
onClose={closeAddViewer}
onClose={closeAddCollab}
onRootClose={onRootClose}
collection={collection}
/>
</Stack>
</>
);
}