Przeglądaj źródła

update share screen design

Abhinav 3 lat temu
rodzic
commit
116f18601c
27 zmienionych plików z 814 dodań i 657 usunięć
  1. 50 0
      ManageLinkExpiry.tsx
  2. 0 607
      src/components/Collections/CollectionShare.tsx
  3. 22 0
      src/components/Collections/CollectionShare/container.tsx
  4. 46 0
      src/components/Collections/CollectionShare/emailShare.tsx
  5. 44 0
      src/components/Collections/CollectionShare/index.tsx
  6. 53 0
      src/components/Collections/CollectionShare/publicShare/changePassword.tsx
  7. 105 0
      src/components/Collections/CollectionShare/publicShare/control.tsx
  8. 52 0
      src/components/Collections/CollectionShare/publicShare/index.tsx
  9. 11 0
      src/components/Collections/CollectionShare/publicShare/link.tsx
  10. 35 0
      src/components/Collections/CollectionShare/publicShare/manage/deviceLimit.tsx
  11. 50 0
      src/components/Collections/CollectionShare/publicShare/manage/downloadAcess.tsx
  12. 102 0
      src/components/Collections/CollectionShare/publicShare/manage/index.tsx
  13. 48 0
      src/components/Collections/CollectionShare/publicShare/manage/linkPassword.tsx
  14. 61 0
      src/components/Collections/CollectionShare/publicShare/switch.tsx
  15. 48 0
      src/components/Collections/CollectionShare/sharees/index.tsx
  16. 20 0
      src/components/Collections/CollectionShare/sharees/row.tsx
  17. 8 0
      src/components/Collections/CollectionShare/styledComponents.tsx
  18. 19 0
      src/components/Collections/CollectionShare/styles.tsx
  19. 10 0
      src/components/Collections/CollectionShare/submitButton.tsx
  20. 0 30
      src/components/Collections/FloatingDrawer.tsx
  21. 1 1
      src/components/Collections/index.tsx
  22. 15 6
      src/components/SingleInputForm.tsx
  23. 2 2
      src/components/SubmitButton.tsx
  24. 0 3
      src/styles/global.css
  25. 6 0
      src/themes/darkThemeOptions.tsx
  26. 2 4
      src/utils/collection/index.ts
  27. 4 4
      src/utils/strings/englishConstants.tsx

+ 50 - 0
ManageLinkExpiry.tsx

@@ -0,0 +1,50 @@
+import { Box } from '@mui/material';
+import { DropdownStyle } from 'components/Collections/CollectionShare/styles';
+import React from 'react';
+import Select from 'react-select';
+import { shareExpiryOptions } from 'utils/collection';
+import constants from 'utils/strings/constants';
+import { dateStringWithMMH } from 'utils/time';
+
+const linkExpiryStyle = {
+    ...DropdownStyle,
+    placeholder: (style) => ({
+        ...style,
+        color: '#d1d1d1',
+        width: '100%',
+        textAlign: 'center',
+    }),
+};
+
+export function ManageLinkExpiry({
+    publicShareProp,
+    collection,
+    updatePublicShareURLHelper,
+}) {
+    const updateDeviceExpiry = async (optionFn) => {
+        return updatePublicShareURLHelper({
+            collectionID: collection.id,
+            validTill: optionFn(),
+        });
+    };
+    return (
+        <Box>
+            {constants.LINK_EXPIRY}
+            <Select
+                menuPosition="fixed"
+                options={shareExpiryOptions}
+                isSearchable={false}
+                value={null}
+                placeholder={
+                    publicShareProp?.validTill
+                        ? dateStringWithMMH(publicShareProp?.validTill)
+                        : 'never'
+                }
+                onChange={(e) => {
+                    updateDeviceExpiry(e.value);
+                }}
+                styles={linkExpiryStyle}
+            />
+        </Box>
+    );
+}

+ 0 - 607
src/components/Collections/CollectionShare.tsx

@@ -1,607 +0,0 @@
-import React, { useContext, useEffect, useState } from 'react';
-import Select from 'react-select';
-import constants from 'utils/strings/constants';
-import { Formik, FormikHelpers } from 'formik';
-import * as Yup from 'yup';
-import Form from 'react-bootstrap/Form';
-import FormControl from 'react-bootstrap/FormControl';
-import { Button, Col, Table } from 'react-bootstrap';
-import { DeadCenter, GalleryContext } from 'pages/gallery';
-import { User } from 'types/user';
-import {
-    shareCollection,
-    unshareCollection,
-    createShareableURL,
-    deleteShareableURL,
-    updateShareableURL,
-} from 'services/collectionService';
-import { getData, LS_KEYS } from 'utils/storage/localStorage';
-import SubmitButton from '../SubmitButton';
-import DialogBox from '../DialogBox';
-import { Collection, PublicURL, UpdatePublicURL } from 'types/collection';
-import {
-    appendCollectionKeyToShareURL,
-    selectIntOptions,
-    shareExpiryOptions,
-} from 'utils/collection';
-import { FlexWrapper, Label, Row, Value } from '../Container';
-import CodeBlock from '../CodeBlock';
-import { ButtonVariant, getVariantColor } from '../pages/gallery/LinkButton';
-import { handleSharingErrors } from 'utils/error';
-import { sleep } from 'utils/common';
-import { SelectStyles } from '../Search/styles';
-import CryptoWorker from 'utils/crypto';
-import { dateStringWithMMH } from 'utils/time';
-import styled from 'styled-components';
-import SingleInputForm from '../SingleInputForm';
-import { AppContext } from 'pages/_app';
-
-interface Props {
-    show: boolean;
-    onHide: () => void;
-    collection: Collection;
-}
-interface formValues {
-    email: string;
-}
-
-interface ShareeProps {
-    sharee: User;
-    collectionUnshare: (sharee: User) => void;
-}
-
-const DropdownStyle = {
-    ...SelectStyles,
-    dropdownIndicator: (style) => ({
-        ...style,
-        margin: '0px',
-    }),
-    singleValue: (style) => ({
-        ...style,
-        color: '#d1d1d1',
-        width: '240px',
-    }),
-    control: (style, { isFocused }) => ({
-        ...style,
-        ...SelectStyles.control(style, { isFocused }),
-        minWidth: '240px',
-    }),
-};
-
-const linkExpiryStyle = {
-    ...DropdownStyle,
-    placeholder: (style) => ({
-        ...style,
-        color: '#d1d1d1',
-        width: '100%',
-        textAlign: 'center',
-    }),
-};
-
-const OptionRow = styled(Row)`
-    flex-wrap: wrap;
-    justify-content: center;
-`;
-const OptionLabel = styled(Label)`
-    flex: 1 1 103px;
-    @media (min-width: 513px) {
-        text-align: left;
-    }
-    margin: 5px;
-`;
-const OptionValue = styled(Value)`
-    flex: 0 0 240px;
-    justify-content: center;
-    margin: 5px;
-`;
-
-function CollectionShare(props: Props) {
-    const [loading, setLoading] = useState(false);
-    const appContext = useContext(AppContext);
-    const galleryContext = useContext(GalleryContext);
-    const [sharableLinkError, setSharableLinkError] = useState(null);
-    const [publicShareUrl, setPublicShareUrl] = useState<string>(null);
-    const [publicShareProp, setPublicShareProp] = useState<PublicURL>(null);
-    const [configurePassword, setConfigurePassword] = useState(false);
-    const deviceLimitOptions = selectIntOptions(50);
-    const expiryOptions = shareExpiryOptions;
-
-    useEffect(() => {
-        const main = async () => {
-            if (props.collection?.publicURLs?.[0]?.url) {
-                const t = await appendCollectionKeyToShareURL(
-                    props.collection?.publicURLs?.[0]?.url,
-                    props.collection.key
-                );
-                setPublicShareUrl(t);
-                setPublicShareProp(
-                    props.collection?.publicURLs?.[0] as PublicURL
-                );
-            } else {
-                setPublicShareUrl(null);
-                setPublicShareProp(null);
-            }
-        };
-        main();
-    }, [props.collection]);
-
-    const collectionShare = async (
-        { email }: formValues,
-        { resetForm, setFieldError }: FormikHelpers<formValues>
-    ) => {
-        try {
-            setLoading(true);
-            appContext.startLoading();
-            const user: User = getData(LS_KEYS.USER);
-            if (email === user.email) {
-                setFieldError('email', constants.SHARE_WITH_SELF);
-            } else if (
-                props.collection?.sharees?.find(
-                    (value) => value.email === email
-                )
-            ) {
-                setFieldError('email', constants.ALREADY_SHARED(email));
-            } else {
-                await shareCollection(props.collection, email);
-                await sleep(2000);
-                await galleryContext.syncWithRemote(false, true);
-                resetForm();
-            }
-        } catch (e) {
-            const errorMessage = handleSharingErrors(e);
-            setFieldError('email', errorMessage);
-        } finally {
-            setLoading(false);
-            appContext.finishLoading();
-        }
-    };
-    const collectionUnshare = async (sharee) => {
-        try {
-            appContext.startLoading();
-            await unshareCollection(props.collection, sharee.email);
-            await sleep(2000);
-            await galleryContext.syncWithRemote(false, true);
-        } finally {
-            appContext.finishLoading();
-        }
-    };
-
-    const createSharableURLHelper = async () => {
-        try {
-            appContext.startLoading();
-            const publicURL = await createShareableURL(props.collection);
-            const sharableURL = await appendCollectionKeyToShareURL(
-                publicURL.url,
-                props.collection.key
-            );
-            setPublicShareUrl(sharableURL);
-            galleryContext.syncWithRemote(false, true);
-        } catch (e) {
-            const errorMessage = handleSharingErrors(e);
-            setSharableLinkError(errorMessage);
-        } finally {
-            appContext.finishLoading();
-        }
-    };
-
-    const disablePublicSharingHelper = async () => {
-        try {
-            appContext.startLoading();
-            await deleteShareableURL(props.collection);
-            setPublicShareUrl(null);
-            galleryContext.syncWithRemote(false, true);
-        } catch (e) {
-            const errorMessage = handleSharingErrors(e);
-            setSharableLinkError(errorMessage);
-        } finally {
-            appContext.finishLoading();
-        }
-    };
-
-    const savePassword = async (passphrase, setFieldError) => {
-        if (passphrase && passphrase.trim().length >= 1) {
-            await enablePublicUrlPassword(passphrase);
-            setConfigurePassword(false);
-            publicShareProp.passwordEnabled = true;
-        } else {
-            setFieldError('linkPassword', 'can not be empty');
-        }
-    };
-
-    const handlePasswordChangeSetting = async () => {
-        if (publicShareProp.passwordEnabled) {
-            await disablePublicUrlPassword();
-        } else {
-            setConfigurePassword(true);
-        }
-    };
-
-    const disablePublicUrlPassword = async () => {
-        appContext.setDialogMessage({
-            title: constants.DISABLE_PASSWORD,
-            content: constants.DISABLE_PASSWORD_MESSAGE,
-            close: { text: constants.CANCEL },
-            proceed: {
-                text: constants.DISABLE,
-                action: () =>
-                    updatePublicShareURLHelper({
-                        collectionID: props.collection.id,
-                        disablePassword: true,
-                    }),
-                variant: ButtonVariant.danger,
-            },
-        });
-    };
-
-    const enablePublicUrlPassword = async (password: string) => {
-        const cryptoWorker = await new CryptoWorker();
-        const kekSalt: string = await cryptoWorker.generateSaltToDeriveKey();
-        const kek = await cryptoWorker.deriveInteractiveKey(password, kekSalt);
-
-        return updatePublicShareURLHelper({
-            collectionID: props.collection.id,
-            passHash: kek.key,
-            nonce: kekSalt,
-            opsLimit: kek.opsLimit,
-            memLimit: kek.memLimit,
-        });
-    };
-
-    const disablePublicSharing = () => {
-        appContext.setDialogMessage({
-            title: constants.DISABLE_PUBLIC_SHARING,
-            content: constants.DISABLE_PUBLIC_SHARING_MESSAGE,
-            close: { text: constants.CANCEL },
-            proceed: {
-                text: constants.DISABLE,
-                action: disablePublicSharingHelper,
-                variant: ButtonVariant.danger,
-            },
-        });
-    };
-
-    const disableFileDownload = () => {
-        appContext.setDialogMessage({
-            title: constants.DISABLE_FILE_DOWNLOAD,
-            content: constants.DISABLE_FILE_DOWNLOAD_MESSAGE,
-            close: { text: constants.CANCEL },
-            proceed: {
-                text: constants.DISABLE,
-                action: () =>
-                    updatePublicShareURLHelper({
-                        collectionID: props.collection.id,
-                        enableDownload: false,
-                    }),
-                variant: ButtonVariant.danger,
-            },
-        });
-    };
-
-    const updatePublicShareURLHelper = async (req: UpdatePublicURL) => {
-        try {
-            galleryContext.setBlockingLoad(true);
-            const response = await updateShareableURL(req);
-            setPublicShareProp(response);
-            galleryContext.syncWithRemote(false, true);
-        } catch (e) {
-            const errorMessage = handleSharingErrors(e);
-            setSharableLinkError(errorMessage);
-        } finally {
-            galleryContext.setBlockingLoad(false);
-        }
-    };
-
-    const updateDeviceLimit = async (newLimit: number) => {
-        return updatePublicShareURLHelper({
-            collectionID: props.collection.id,
-            deviceLimit: newLimit,
-        });
-    };
-
-    const updateDeviceExpiry = async (optionFn) => {
-        return updatePublicShareURLHelper({
-            collectionID: props.collection.id,
-            validTill: optionFn(),
-        });
-    };
-
-    const handleCollectionPublicSharing = () => {
-        setSharableLinkError(null);
-        if (publicShareUrl) {
-            disablePublicSharing();
-        } else {
-            createSharableURLHelper();
-        }
-    };
-
-    const handleFileDownloadSetting = () => {
-        if (publicShareProp.enableDownload) {
-            disableFileDownload();
-        } else {
-            updatePublicShareURLHelper({
-                collectionID: props.collection.id,
-                enableDownload: true,
-            });
-        }
-    };
-
-    const ShareeRow = ({ sharee, collectionUnshare }: ShareeProps) => (
-        <tr>
-            <td>{sharee.email}</td>
-            <td>
-                <Button
-                    variant="outline-danger"
-                    style={{
-                        height: '25px',
-                        lineHeight: 0,
-                        padding: 0,
-                        width: '25px',
-                        fontSize: '1.2em',
-                        fontWeight: 900,
-                    }}
-                    onClick={() => collectionUnshare(sharee)}>
-                    -
-                </Button>
-            </td>
-        </tr>
-    );
-
-    if (!props.collection) {
-        return <></>;
-    }
-
-    return (
-        <DialogBox
-            open={props.show}
-            onClose={props.onHide}
-            attributes={{
-                title: constants.SHARE_COLLECTION,
-                staticBackdrop: true,
-            }}>
-            <DeadCenter style={{ width: '85%', margin: 'auto' }}>
-                <h6 style={{ marginTop: '8px' }}>
-                    {constants.SHARE_WITH_PEOPLE}
-                </h6>
-                <p />
-                <Formik<formValues>
-                    initialValues={{ email: '' }}
-                    validationSchema={Yup.object().shape({
-                        email: Yup.string()
-                            .email(constants.EMAIL_ERROR)
-                            .required(constants.REQUIRED),
-                    })}
-                    validateOnChange={false}
-                    validateOnBlur={false}
-                    onSubmit={collectionShare}>
-                    {({
-                        values,
-                        errors,
-                        touched,
-                        handleChange,
-                        handleSubmit,
-                    }) => (
-                        <Form noValidate onSubmit={handleSubmit}>
-                            <Form.Row>
-                                <Form.Group
-                                    as={Col}
-                                    xs={10}
-                                    controlId="formHorizontalEmail">
-                                    <Form.Control
-                                        type="email"
-                                        placeholder={constants.ENTER_EMAIL}
-                                        value={values.email}
-                                        onChange={handleChange('email')}
-                                        isInvalid={Boolean(
-                                            touched.email && errors.email
-                                        )}
-                                        autoFocus
-                                        disabled={loading}
-                                    />
-                                    <FormControl.Feedback type="invalid">
-                                        {errors.email}
-                                    </FormControl.Feedback>
-                                </Form.Group>
-                                <Form.Group
-                                    as={Col}
-                                    xs={2}
-                                    controlId="formHorizontalEmail">
-                                    <SubmitButton
-                                        loading={loading}
-                                        inline
-                                        buttonText="+"
-                                    />
-                                </Form.Group>
-                            </Form.Row>
-                        </Form>
-                    )}
-                </Formik>
-                {props.collection.sharees?.length > 0 && (
-                    <>
-                        <p>{constants.SHAREES}</p>
-
-                        <Table striped bordered hover variant="dark" size="sm">
-                            <tbody>
-                                {props.collection.sharees?.map((sharee) => (
-                                    <ShareeRow
-                                        key={sharee.email}
-                                        sharee={sharee}
-                                        collectionUnshare={collectionUnshare}
-                                    />
-                                ))}
-                            </tbody>
-                        </Table>
-                    </>
-                )}
-                <div
-                    style={{
-                        height: '1px',
-                        marginTop: '10px',
-                        marginBottom: '18px',
-                        background: '#444',
-                        width: '100%',
-                    }}
-                />
-                <div>
-                    <FlexWrapper>
-                        <FlexWrapper
-                            style={{ paddingTop: '5px', color: '#fff' }}>
-                            {constants.PUBLIC_SHARING}
-                        </FlexWrapper>
-                        <Form.Switch
-                            style={{ marginLeft: '20px' }}
-                            checked={!!publicShareUrl}
-                            id="collection-public-sharing-toggler"
-                            className="custom-switch-md"
-                            onChange={handleCollectionPublicSharing}
-                        />
-                    </FlexWrapper>
-                    {sharableLinkError && (
-                        <FlexWrapper
-                            style={{
-                                marginTop: '10px',
-                                color: getVariantColor(ButtonVariant.danger),
-                            }}>
-                            {sharableLinkError}
-                        </FlexWrapper>
-                    )}
-                </div>
-                {publicShareUrl ? (
-                    <>
-                        <CodeBlock
-                            wordBreak={'break-all'}
-                            code={publicShareUrl}
-                        />
-                        <details style={{ width: '100%' }}>
-                            <summary
-                                onClick={(e) => {
-                                    const lastOptionRow: Element =
-                                        e.currentTarget.nextElementSibling
-                                            .lastElementChild;
-                                    const main = async (
-                                        lastOptionRow: Element
-                                    ) => {
-                                        await sleep(0);
-                                        lastOptionRow.scrollIntoView(true);
-                                    };
-                                    main(lastOptionRow);
-                                }}
-                                className="manageLinkHeader"
-                                style={{ marginBottom: '20px' }}>
-                                {constants.MANAGE_LINK}
-                            </summary>
-                            <section>
-                                <OptionRow>
-                                    <OptionLabel>
-                                        {constants.LINK_DEVICE_LIMIT}
-                                    </OptionLabel>
-                                    <OptionValue>
-                                        <Select
-                                            menuPosition="fixed"
-                                            options={deviceLimitOptions}
-                                            isSearchable={false}
-                                            value={{
-                                                label: publicShareProp?.deviceLimit.toString(),
-                                                value: publicShareProp?.deviceLimit,
-                                            }}
-                                            onChange={(e) =>
-                                                updateDeviceLimit(e.value)
-                                            }
-                                            styles={DropdownStyle}
-                                        />
-                                    </OptionValue>
-                                </OptionRow>
-
-                                <OptionRow>
-                                    <OptionLabel
-                                        style={{ alignItems: 'center' }}>
-                                        {constants.LINK_EXPIRY}
-                                    </OptionLabel>
-                                    <OptionValue>
-                                        <Select
-                                            menuPosition="fixed"
-                                            options={expiryOptions}
-                                            isSearchable={false}
-                                            value={null}
-                                            placeholder={
-                                                publicShareProp?.validTill
-                                                    ? dateStringWithMMH(
-                                                          publicShareProp?.validTill
-                                                      )
-                                                    : 'never'
-                                            }
-                                            onChange={(e) => {
-                                                updateDeviceExpiry(e.value);
-                                            }}
-                                            styles={linkExpiryStyle}
-                                        />
-                                    </OptionValue>
-                                </OptionRow>
-                                <OptionRow>
-                                    <OptionLabel>
-                                        {constants.FILE_DOWNLOAD}
-                                    </OptionLabel>
-                                    <OptionValue>
-                                        <Form.Switch
-                                            style={{ marginLeft: '10px' }}
-                                            checked={
-                                                publicShareProp?.enableDownload ??
-                                                false
-                                            }
-                                            id="public-sharing-file-download-toggler"
-                                            className="custom-switch-md"
-                                            onChange={handleFileDownloadSetting}
-                                        />
-                                    </OptionValue>
-                                </OptionRow>
-
-                                <OptionRow>
-                                    <OptionLabel>
-                                        {constants.LINK_PASSWORD_LOCK}{' '}
-                                    </OptionLabel>
-                                    <OptionValue>
-                                        <Form.Switch
-                                            style={{ marginLeft: '10px' }}
-                                            checked={
-                                                publicShareProp?.passwordEnabled
-                                            }
-                                            id="public-sharing-file-password-toggler"
-                                            className="custom-switch-md"
-                                            onChange={
-                                                handlePasswordChangeSetting
-                                            }
-                                        />
-                                    </OptionValue>
-                                </OptionRow>
-                            </section>
-                            <DialogBox
-                                open={configurePassword}
-                                onClose={() => setConfigurePassword(false)}
-                                size="sm"
-                                attributes={{
-                                    title: constants.PASSWORD_LOCK,
-                                }}>
-                                <SingleInputForm
-                                    callback={savePassword}
-                                    placeholder={
-                                        constants.RETURN_PASSPHRASE_HINT
-                                    }
-                                    buttonText={constants.LOCK}
-                                    fieldType="password"
-                                />
-                            </DialogBox>
-                        </details>
-                    </>
-                ) : (
-                    <div
-                        style={{
-                            height: '1px',
-                            marginTop: '28px',
-                            width: '100%',
-                        }}
-                    />
-                )}
-            </DeadCenter>
-        </DialogBox>
-    );
-}
-export default CollectionShare;

+ 22 - 0
src/components/Collections/CollectionShare/container.tsx

@@ -0,0 +1,22 @@
+import { Dialog, styled } from '@mui/material';
+import PropTypes from 'prop-types';
+
+export const CollectionShareContainer = styled(Dialog)(({ theme }) => ({
+    '& .MuiDialog-container': {
+        justifyContent: 'flex-end',
+    },
+    '& .MuiPaper-root': {
+        maxWidth: '414px',
+    },
+    '& .MuiDialogTitle-root': {
+        padding: theme.spacing(4, 3, 3, 4),
+    },
+    '& .MuiDialogContent-root': {
+        padding: theme.spacing(3, 4),
+    },
+}));
+
+CollectionShareContainer.propTypes = {
+    children: PropTypes.node,
+    onClose: PropTypes.func.isRequired,
+};

+ 46 - 0
src/components/Collections/CollectionShare/emailShare.tsx

@@ -0,0 +1,46 @@
+import SingleInputForm from 'components/SingleInputForm';
+import { GalleryContext } from 'pages/gallery';
+import React, { useContext } from 'react';
+import { shareCollection } from 'services/collectionService';
+import { User } from 'types/user';
+import { sleep } from 'utils/common';
+import { handleSharingErrors } from 'utils/error';
+import { getData, LS_KEYS } from 'utils/storage/localStorage';
+import constants from 'utils/strings/constants';
+import { CollectionShareSharees } from './sharees';
+import CollectionShareSubmitButton from './submitButton';
+export default function EmailShare({ collection }) {
+    const galleryContext = useContext(GalleryContext);
+
+    const collectionShare = async (email, setFieldError) => {
+        try {
+            const user: User = getData(LS_KEYS.USER);
+            if (email === user.email) {
+                setFieldError('email', constants.SHARE_WITH_SELF);
+            } else if (
+                collection?.sharees?.find((value) => value.email === email)
+            ) {
+                setFieldError('email', constants.ALREADY_SHARED(email));
+            } else {
+                await shareCollection(collection, email);
+                await sleep(2000);
+                await galleryContext.syncWithRemote(false, true);
+            }
+        } catch (e) {
+            const errorMessage = handleSharingErrors(e);
+            setFieldError('email', errorMessage);
+        }
+    };
+    return (
+        <>
+            <SingleInputForm
+                callback={collectionShare}
+                placeholder={constants.ENTER_EMAIL}
+                fieldType="email"
+                buttonText={constants.SHARE}
+                customSubmitButton={CollectionShareSubmitButton}
+            />
+            <CollectionShareSharees collection={collection} />
+        </>
+    );
+}

+ 44 - 0
src/components/Collections/CollectionShare/index.tsx

@@ -0,0 +1,44 @@
+import EmailShare from './emailShare';
+import React from 'react';
+import constants from 'utils/strings/constants';
+import { Collection } from 'types/collection';
+import { dialogCloseHandler } from 'components/DialogBox/base';
+import DialogTitleWithCloseButton from 'components/DialogBox/titleWithCloseButton';
+import DialogContent from '@mui/material/DialogContent';
+import { Divider } from '@mui/material';
+
+import { CollectionShareContainer } from './container';
+import PublicShare from './publicShare';
+
+interface Props {
+    show: boolean;
+    onHide: () => void;
+    collection: Collection;
+}
+
+function CollectionShare(props: Props) {
+    const handleClose = dialogCloseHandler({
+        staticBackdrop: true,
+        onClose: props.onHide,
+    });
+
+    if (!props.collection) {
+        return <></>;
+    }
+
+    return (
+        <>
+            <CollectionShareContainer open={props.show} onClose={handleClose}>
+                <DialogTitleWithCloseButton onClose={handleClose}>
+                    {constants.SHARE_COLLECTION}
+                </DialogTitleWithCloseButton>
+                <DialogContent>
+                    <EmailShare collection={props.collection} />
+                    <Divider />
+                    <PublicShare collection={props.collection} />
+                </DialogContent>
+            </CollectionShareContainer>
+        </>
+    );
+}
+export default CollectionShare;

+ 53 - 0
src/components/Collections/CollectionShare/publicShare/changePassword.tsx

@@ -0,0 +1,53 @@
+import DialogBox from 'components/DialogBox';
+import SingleInputForm from 'components/SingleInputForm';
+import React from 'react';
+import CryptoWorker from 'utils/crypto';
+import constants from 'utils/strings/constants';
+export function PublicLinkChangePassword({
+    open,
+    onClose,
+    collection,
+    publicShareProp,
+    updatePublicShareURLHelper,
+    setChangePasswordView,
+}) {
+    const savePassword = async (passphrase, setFieldError) => {
+        if (passphrase && passphrase.trim().length >= 1) {
+            await enablePublicUrlPassword(passphrase);
+            setChangePasswordView(false);
+            publicShareProp.passwordEnabled = true;
+        } else {
+            setFieldError('linkPassword', 'can not be empty');
+        }
+    };
+
+    const enablePublicUrlPassword = async (password: string) => {
+        const cryptoWorker = await new CryptoWorker();
+        const kekSalt: string = await cryptoWorker.generateSaltToDeriveKey();
+        const kek = await cryptoWorker.deriveInteractiveKey(password, kekSalt);
+
+        return updatePublicShareURLHelper({
+            collectionID: collection.id,
+            passHash: kek.key,
+            nonce: kekSalt,
+            opsLimit: kek.opsLimit,
+            memLimit: kek.memLimit,
+        });
+    };
+    return (
+        <DialogBox
+            open={open}
+            onClose={onClose}
+            size="sm"
+            attributes={{
+                title: constants.PASSWORD_LOCK,
+            }}>
+            <SingleInputForm
+                callback={savePassword}
+                placeholder={constants.RETURN_PASSPHRASE_HINT}
+                buttonText={constants.LOCK}
+                fieldType="password"
+            />
+        </DialogBox>
+    );
+}

+ 105 - 0
src/components/Collections/CollectionShare/publicShare/control.tsx

@@ -0,0 +1,105 @@
+import { Box, Typography } from '@mui/material';
+import { FlexWrapper } from 'components/Container';
+import { ButtonVariant } from 'components/pages/gallery/LinkButton';
+import { GalleryContext } from 'pages/gallery';
+import { AppContext } from 'pages/_app';
+import React, { useContext } from 'react';
+import {
+    createShareableURL,
+    deleteShareableURL,
+} from 'services/collectionService';
+import { appendCollectionKeyToShareURL } from 'utils/collection';
+import { handleSharingErrors } from 'utils/error';
+import constants from 'utils/strings/constants';
+import PublicShareSwitch from './switch';
+export default function PublicShareControl({
+    publicShareUrl,
+    sharableLinkError,
+    collection,
+    setPublicShareUrl,
+    setSharableLinkError,
+}) {
+    const appContext = useContext(AppContext);
+    const galleryContext = useContext(GalleryContext);
+
+    const createSharableURLHelper = async () => {
+        try {
+            appContext.startLoading();
+            const publicURL = await createShareableURL(collection);
+            const sharableURL = await appendCollectionKeyToShareURL(
+                publicURL.url,
+                collection.key
+            );
+            setPublicShareUrl(sharableURL);
+            galleryContext.syncWithRemote(false, true);
+        } catch (e) {
+            const errorMessage = handleSharingErrors(e);
+            setSharableLinkError(errorMessage);
+        } finally {
+            appContext.finishLoading();
+        }
+    };
+
+    const disablePublicSharing = async () => {
+        try {
+            appContext.startLoading();
+            await deleteShareableURL(collection);
+            setPublicShareUrl(null);
+            galleryContext.syncWithRemote(false, true);
+        } catch (e) {
+            const errorMessage = handleSharingErrors(e);
+            setSharableLinkError(errorMessage);
+        } finally {
+            appContext.finishLoading();
+        }
+    };
+
+    const confirmDisablePublicSharing = () => {
+        appContext.setDialogMessage({
+            title: constants.DISABLE_PUBLIC_SHARING,
+            content: constants.DISABLE_PUBLIC_SHARING_MESSAGE,
+            close: { text: constants.CANCEL },
+            proceed: {
+                text: constants.DISABLE,
+                action: disablePublicSharing,
+                variant: ButtonVariant.danger,
+            },
+        });
+    };
+
+    const handleCollectionPublicSharing = () => {
+        setSharableLinkError(null);
+
+        if (publicShareUrl) {
+            confirmDisablePublicSharing();
+        } else {
+            createSharableURLHelper();
+        }
+    };
+    return (
+        <Box mt={3}>
+            <FlexWrapper>
+                <FlexWrapper>{constants.PUBLIC_SHARING}</FlexWrapper>
+
+                <PublicShareSwitch
+                    color="accent"
+                    sx={{
+                        ml: 2,
+                    }}
+                    checked={!!publicShareUrl}
+                    onChange={handleCollectionPublicSharing}
+                />
+            </FlexWrapper>
+            {sharableLinkError && (
+                <Typography
+                    variant="body2"
+                    sx={{
+                        color: (theme) => theme.palette.danger.main,
+                        mt: 0.5,
+                    }}>
+                    {sharableLinkError}
+                </Typography>
+            )}
+        </Box>
+    );
+}

+ 52 - 0
src/components/Collections/CollectionShare/publicShare/index.tsx

@@ -0,0 +1,52 @@
+import React, { useEffect, useState } from 'react';
+import { PublicURL } from 'types/collection';
+import { appendCollectionKeyToShareURL } from 'utils/collection';
+import PublicShareControl from './control';
+import PublicShareLink from './link';
+import PublicShareManage from './manage';
+
+export default function PublicShare({ collection }) {
+    const [sharableLinkError, setSharableLinkError] = useState(null);
+    const [publicShareUrl, setPublicShareUrl] = useState<string>(null);
+    const [publicShareProp, setPublicShareProp] = useState<PublicURL>(null);
+
+    useEffect(() => {
+        const main = async () => {
+            if (collection?.publicURLs?.[0]?.url) {
+                const t = await appendCollectionKeyToShareURL(
+                    collection?.publicURLs?.[0]?.url,
+                    collection.key
+                );
+                setPublicShareUrl(t);
+                setPublicShareProp(collection?.publicURLs?.[0] as PublicURL);
+            } else {
+                setPublicShareUrl(null);
+                setPublicShareProp(null);
+            }
+        };
+        main();
+    }, [collection]);
+
+    return (
+        <>
+            <PublicShareControl
+                setPublicShareUrl={setPublicShareUrl}
+                collection={collection}
+                publicShareUrl={publicShareUrl}
+                sharableLinkError={sharableLinkError}
+                setSharableLinkError={setSharableLinkError}
+            />
+            {publicShareUrl && (
+                <>
+                    <PublicShareLink publicShareUrl={publicShareUrl} />
+                    <PublicShareManage
+                        publicShareProp={publicShareProp}
+                        collection={collection}
+                        setPublicShareProp={setPublicShareProp}
+                        setSharableLinkError={setSharableLinkError}
+                    />
+                </>
+            )}
+        </>
+    );
+}

+ 11 - 0
src/components/Collections/CollectionShare/publicShare/link.tsx

@@ -0,0 +1,11 @@
+import { Box } from '@mui/material';
+import CodeBlock from 'components/CodeBlock';
+import React from 'react';
+
+export default function PublicShareLink({ publicShareUrl }) {
+    return (
+        <Box mt={2} mb={3}>
+            <CodeBlock wordBreak={'break-all'} code={publicShareUrl} />
+        </Box>
+    );
+}

+ 35 - 0
src/components/Collections/CollectionShare/publicShare/manage/deviceLimit.tsx

@@ -0,0 +1,35 @@
+import { Box } from '@mui/material';
+import React from 'react';
+import Select from 'react-select';
+import { getDeviceLimitOptions } from 'utils/collection';
+import constants from 'utils/strings/constants';
+import { DropdownStyle } from '../../styles';
+export function ManageDeviceLimit({
+    publicShareProp,
+    collection,
+    updatePublicShareURLHelper,
+}) {
+    const updateDeviceLimit = async (newLimit: number) => {
+        return updatePublicShareURLHelper({
+            collectionID: collection.id,
+            deviceLimit: newLimit,
+        });
+    };
+
+    return (
+        <Box>
+            {constants.LINK_DEVICE_LIMIT}
+            <Select
+                menuPosition="fixed"
+                options={getDeviceLimitOptions()}
+                isSearchable={false}
+                value={{
+                    label: publicShareProp?.deviceLimit.toString(),
+                    value: publicShareProp?.deviceLimit,
+                }}
+                onChange={(e) => updateDeviceLimit(e.value)}
+                styles={DropdownStyle}
+            />
+        </Box>
+    );
+}

+ 50 - 0
src/components/Collections/CollectionShare/publicShare/manage/downloadAcess.tsx

@@ -0,0 +1,50 @@
+import { Box, Typography } from '@mui/material';
+import { ButtonVariant } from 'components/pages/gallery/LinkButton';
+import { AppContext } from 'pages/_app';
+import React, { useContext } from 'react';
+import constants from 'utils/strings/constants';
+import PublicShareSwitch from '../switch';
+export function ManageDownloadAccess({
+    publicShareProp,
+    updatePublicShareURLHelper,
+    collection,
+}) {
+    const appContext = useContext(AppContext);
+
+    const handleFileDownloadSetting = () => {
+        if (publicShareProp.enableDownload) {
+            disableFileDownload();
+        } else {
+            updatePublicShareURLHelper({
+                collectionID: collection.id,
+                enableDownload: true,
+            });
+        }
+    };
+
+    const disableFileDownload = () => {
+        appContext.setDialogMessage({
+            title: constants.DISABLE_FILE_DOWNLOAD,
+            content: constants.DISABLE_FILE_DOWNLOAD_MESSAGE,
+            close: { text: constants.CANCEL },
+            proceed: {
+                text: constants.DISABLE,
+                action: () =>
+                    updatePublicShareURLHelper({
+                        collectionID: collection.id,
+                        enableDownload: false,
+                    }),
+                variant: ButtonVariant.danger,
+            },
+        });
+    };
+    return (
+        <Box>
+            <Typography>{constants.FILE_DOWNLOAD}</Typography>
+            <PublicShareSwitch
+                checked={publicShareProp?.enableDownload ?? false}
+                onChange={handleFileDownloadSetting}
+            />
+        </Box>
+    );
+}

+ 102 - 0
src/components/Collections/CollectionShare/publicShare/manage/index.tsx

@@ -0,0 +1,102 @@
+import { ManageLinkPassword } from './linkPassword';
+import { ManageDeviceLimit } from './deviceLimit';
+import { ManageLinkExpiry } from '../../../../../../ManageLinkExpiry';
+import { PublicLinkChangePassword } from '../changePassword';
+import { Stack } from '@mui/material';
+import { GalleryContext } from 'pages/gallery';
+import React, { useContext, useState } from 'react';
+import { updateShareableURL } from 'services/collectionService';
+import { UpdatePublicURL } from 'types/collection';
+import { sleep } from 'utils/common';
+import { handleSharingErrors } from 'utils/error';
+import constants from 'utils/strings/constants';
+import { ManageSectionLabel } from '../../styledComponents';
+import { ManageDownloadAccess } from './downloadAcess';
+
+export default function PublicShareManage({
+    publicShareProp,
+    collection,
+    setPublicShareProp,
+    setSharableLinkError,
+}) {
+    const galleryContext = useContext(GalleryContext);
+
+    const [changePasswordView, setChangePasswordView] = useState(false);
+
+    const closeConfigurePassword = () => setChangePasswordView(false);
+
+    const updatePublicShareURLHelper = async (req: UpdatePublicURL) => {
+        try {
+            galleryContext.setBlockingLoad(true);
+            const response = await updateShareableURL(req);
+            setPublicShareProp(response);
+            galleryContext.syncWithRemote(false, true);
+        } catch (e) {
+            const errorMessage = handleSharingErrors(e);
+            setSharableLinkError(errorMessage);
+        } finally {
+            galleryContext.setBlockingLoad(false);
+        }
+    };
+
+    const scrollToEnd = (e) => {
+        const lastOptionRow: Element =
+            e.currentTarget.nextElementSibling.lastElementChild;
+        const main = async (lastOptionRow: Element) => {
+            await sleep(0);
+            lastOptionRow.scrollIntoView(true);
+        };
+        main(lastOptionRow);
+    };
+
+    return (
+        <>
+            <details>
+                <ManageSectionLabel onClick={scrollToEnd}>
+                    {constants.MANAGE_LINK}
+                </ManageSectionLabel>
+                <section>
+                    <Stack spacing={1}>
+                        <ManageLinkExpiry
+                            collection={collection}
+                            publicShareProp={publicShareProp}
+                            updatePublicShareURLHelper={
+                                updatePublicShareURLHelper
+                            }
+                        />
+                        <ManageDeviceLimit
+                            collection={collection}
+                            publicShareProp={publicShareProp}
+                            updatePublicShareURLHelper={
+                                updatePublicShareURLHelper
+                            }
+                        />
+                        <ManageDownloadAccess
+                            collection={collection}
+                            publicShareProp={publicShareProp}
+                            updatePublicShareURLHelper={
+                                updatePublicShareURLHelper
+                            }
+                        />
+                        <ManageLinkPassword
+                            setChangePasswordView={setChangePasswordView}
+                            collection={collection}
+                            publicShareProp={publicShareProp}
+                            updatePublicShareURLHelper={
+                                updatePublicShareURLHelper
+                            }
+                        />
+                    </Stack>
+                </section>
+            </details>
+            <PublicLinkChangePassword
+                open={changePasswordView}
+                onClose={closeConfigurePassword}
+                collection={collection}
+                publicShareProp={publicShareProp}
+                updatePublicShareURLHelper={updatePublicShareURLHelper}
+                setChangePasswordView={setChangePasswordView}
+            />
+        </>
+    );
+}

+ 48 - 0
src/components/Collections/CollectionShare/publicShare/manage/linkPassword.tsx

@@ -0,0 +1,48 @@
+import { Box, Typography } from '@mui/material';
+import { ButtonVariant } from 'components/pages/gallery/LinkButton';
+import { AppContext } from 'pages/_app';
+import React, { useContext } from 'react';
+import constants from 'utils/strings/constants';
+import PublicShareSwitch from '../switch';
+export function ManageLinkPassword({
+    collection,
+    publicShareProp,
+    updatePublicShareURLHelper,
+    setChangePasswordView,
+}) {
+    const appContext = useContext(AppContext);
+
+    const handlePasswordChangeSetting = async () => {
+        if (publicShareProp.passwordEnabled) {
+            await disablePublicUrlPassword();
+        } else {
+            setChangePasswordView(true);
+        }
+    };
+
+    const disablePublicUrlPassword = async () => {
+        appContext.setDialogMessage({
+            title: constants.DISABLE_PASSWORD,
+            content: constants.DISABLE_PASSWORD_MESSAGE,
+            close: { text: constants.CANCEL },
+            proceed: {
+                text: constants.DISABLE,
+                action: () =>
+                    updatePublicShareURLHelper({
+                        collectionID: collection.id,
+                        disablePassword: true,
+                    }),
+                variant: ButtonVariant.danger,
+            },
+        });
+    };
+    return (
+        <Box>
+            <Typography> {constants.LINK_PASSWORD_LOCK}</Typography>
+            <PublicShareSwitch
+                checked={publicShareProp?.passwordEnabled}
+                onChange={handlePasswordChangeSetting}
+            />
+        </Box>
+    );
+}

+ 61 - 0
src/components/Collections/CollectionShare/publicShare/switch.tsx

@@ -0,0 +1,61 @@
+import React from 'react';
+import { SwitchProps, Switch } from '@mui/material';
+import styled from 'styled-components';
+
+const PublicShareSwitch = styled((props: SwitchProps) => (
+    <Switch
+        focusVisibleClassName=".Mui-focusVisible"
+        disableRipple
+        {...props}
+    />
+))(({ theme }) => ({
+    width: 40,
+    height: 24,
+    padding: 0,
+    '& .MuiSwitch-switchBase': {
+        padding: 0,
+        margin: 2,
+        transitionDuration: '300ms',
+        '&.Mui-checked': {
+            transform: 'translateX(16px)',
+            color: '#fff',
+            '& + .MuiSwitch-track': {
+                backgroundColor:
+                    theme.palette.mode === 'dark' ? '#2ECA45' : '#65C466',
+                opacity: 1,
+                border: 0,
+            },
+            '&.Mui-disabled + .MuiSwitch-track': {
+                opacity: 0.5,
+            },
+        },
+        '&.Mui-focusVisible .MuiSwitch-thumb': {
+            color: '#33cf4d',
+            border: '6px solid #fff',
+        },
+        '&.Mui-disabled .MuiSwitch-thumb': {
+            color:
+                theme.palette.mode === 'light'
+                    ? theme.palette.grey[100]
+                    : theme.palette.grey[600],
+        },
+        '&.Mui-disabled + .MuiSwitch-track': {
+            opacity: theme.palette.mode === 'light' ? 0.7 : 0.3,
+        },
+    },
+    '& .MuiSwitch-thumb': {
+        boxSizing: 'border-box',
+        width: 20,
+        height: 20,
+    },
+    '& .MuiSwitch-track': {
+        borderRadius: 22 / 2,
+        backgroundColor: theme.palette.mode === 'light' ? '#E9E9EA' : '#39393D',
+        opacity: 1,
+        transition: theme.transitions.create(['background-color'], {
+            duration: 500,
+        }),
+    },
+}));
+
+export default PublicShareSwitch;

+ 48 - 0
src/components/Collections/CollectionShare/sharees/index.tsx

@@ -0,0 +1,48 @@
+import { Box, Typography } from '@mui/material';
+import { GalleryContext } from 'pages/gallery';
+import { AppContext } from 'pages/_app';
+import React, { useContext } from 'react';
+import { unshareCollection } from 'services/collectionService';
+import { Collection } from 'types/collection';
+import { sleep } from 'utils/common';
+import constants from 'utils/strings/constants';
+import ShareeRow from './row';
+
+interface Iprops {
+    collection: Collection;
+}
+
+export function CollectionShareSharees({ collection }: Iprops) {
+    const appContext = useContext(AppContext);
+    const galleryContext = useContext(GalleryContext);
+
+    const collectionUnshare = async (sharee) => {
+        try {
+            appContext.startLoading();
+            await unshareCollection(collection, sharee.email);
+            await sleep(2000);
+            await galleryContext.syncWithRemote(false, true);
+        } finally {
+            appContext.finishLoading();
+        }
+    };
+
+    if (!collection.sharees?.length) {
+        return <></>;
+    }
+
+    return (
+        <Box mb={3}>
+            <Typography variant="body2" color="text.secondary">
+                {constants.SHAREES}
+            </Typography>
+            {collection.sharees?.map((sharee) => (
+                <ShareeRow
+                    key={sharee.email}
+                    sharee={sharee}
+                    collectionUnshare={collectionUnshare}
+                />
+            ))}
+        </Box>
+    );
+}

+ 20 - 0
src/components/Collections/CollectionShare/sharees/row.tsx

@@ -0,0 +1,20 @@
+import React from 'react';
+import { IconButton } from '@mui/material';
+import { SpaceBetweenFlex } from 'components/Container';
+import { User } from 'types/user';
+import MoreHorizIcon from '@mui/icons-material/MoreHoriz';
+
+interface IProps {
+    sharee: User;
+    collectionUnshare: (sharee: User) => void;
+}
+const ShareeRow = ({ sharee, collectionUnshare }: IProps) => (
+    <SpaceBetweenFlex>
+        {sharee.email}
+        <IconButton sx={{ ml: 2 }} onClick={() => collectionUnshare(sharee)}>
+            <MoreHorizIcon />
+        </IconButton>
+    </SpaceBetweenFlex>
+);
+
+export default ShareeRow;

+ 8 - 0
src/components/Collections/CollectionShare/styledComponents.tsx

@@ -0,0 +1,8 @@
+import styled from 'styled-components';
+
+export const ManageSectionLabel = styled.summary(
+    ({ theme }) => `
+    text-align: center;
+    margin-bottom:${theme.spacing(1)};
+`
+);

+ 19 - 0
src/components/Collections/CollectionShare/styles.tsx

@@ -0,0 +1,19 @@
+import { SelectStyles } from 'components/Search/styles';
+
+export const DropdownStyle = {
+    ...SelectStyles,
+    dropdownIndicator: (style) => ({
+        ...style,
+        margin: '0px',
+    }),
+    singleValue: (style) => ({
+        ...style,
+        color: '#d1d1d1',
+        width: '240px',
+    }),
+    control: (style, { isFocused }) => ({
+        ...style,
+        ...SelectStyles.control(style, { isFocused }),
+        minWidth: '240px',
+    }),
+};

+ 10 - 0
src/components/Collections/CollectionShare/submitButton.tsx

@@ -0,0 +1,10 @@
+import { FlexWrapper } from 'components/Container';
+import SubmitButton, { SubmitButtonProps } from 'components/SubmitButton';
+import React from 'react';
+export default function CollectionShareSubmitButton(props: SubmitButtonProps) {
+    return (
+        <FlexWrapper style={{ justifyContent: 'flex-end' }}>
+            <SubmitButton {...props} size="medium" inline sx={{ my: 2 }} />
+        </FlexWrapper>
+    );
+}

+ 0 - 30
src/components/Collections/FloatingDrawer.tsx

@@ -1,30 +0,0 @@
-import { Dialog, Slide, styled } from '@mui/material';
-import React from 'react';
-import PropTypes from 'prop-types';
-
-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),
-    },
-}));
-
-FloatingDrawer.propTypes = {
-    children: PropTypes.node,
-    onClose: PropTypes.func.isRequired,
-};
-
-export const Transition = (direction: 'left' | 'right' | 'up') =>
-    React.forwardRef(
-        (props: { children: React.ReactElement<any, any> }, ref) => {
-            return <Slide direction={direction} ref={ref} {...props} />;
-        }
-    );

+ 1 - 1
src/components/Collections/index.tsx

@@ -27,7 +27,7 @@ export default function Collections(props: Iprops) {
 
     const [allCollectionView, setAllCollectionView] = useState(false);
     const [collectionShareModalView, setCollectionShareModalView] =
-        useState(false);
+        useState(true);
     const collectionsMap = useRef<Map<number, Collection>>(new Map());
     const activeCollection = useRef<Collection>(null);
 

+ 15 - 6
src/components/SingleInputForm.tsx

@@ -5,6 +5,7 @@ import * as Yup from 'yup';
 import SubmitButton from './SubmitButton';
 import TextField from '@mui/material/TextField';
 import ShowHidePassword from './Form/ShowHidePassword';
+import { FlexWrapper } from './Container';
 
 interface formValues {
     inputValue: string;
@@ -17,6 +18,7 @@ export interface SingleInputFormProps {
     fieldType: 'text' | 'email' | 'password';
     placeholder: string;
     buttonText: string;
+    customSubmitButton?: any;
 }
 
 export default function SingleInputForm(props: SingleInputFormProps) {
@@ -97,12 +99,19 @@ export default function SingleInputForm(props: SingleInputFormProps) {
                             ),
                         }}
                     />
-
-                    <SubmitButton
-                        sx={{ mt: 2 }}
-                        buttonText={props.buttonText}
-                        loading={loading}
-                    />
+                    <FlexWrapper></FlexWrapper>
+                    {props.customSubmitButton ? (
+                        <props.customSubmitButton
+                            buttonText={props.buttonText}
+                            loading={loading}
+                        />
+                    ) : (
+                        <SubmitButton
+                            sx={{ mt: 2 }}
+                            buttonText={props.buttonText}
+                            loading={loading}
+                        />
+                    )}
                 </form>
             )}
         </Formik>

+ 2 - 2
src/components/SubmitButton.tsx

@@ -1,13 +1,13 @@
 import { Button, ButtonProps, CircularProgress } from '@mui/material';
 import React, { FC } from 'react';
 
-interface Props {
+export interface SubmitButtonProps {
     loading: boolean;
     buttonText: string;
     inline?: any;
     disabled?: boolean;
 }
-const SubmitButton: FC<ButtonProps<'button', Props>> = ({
+const SubmitButton: FC<ButtonProps<'button', SubmitButtonProps>> = ({
     loading,
     buttonText,
     inline,

+ 0 - 3
src/styles/global.css

@@ -488,6 +488,3 @@ div.otp-input input:not(:placeholder-shown) , div.otp-input input:focus{
     }
 }
 
-.manageLinkHeader:hover{
-    color:#bbb;
-}

+ 6 - 0
src/themes/darkThemeOptions.tsx

@@ -44,6 +44,12 @@ declare module '@mui/material/Typography' {
     }
 }
 
+declare module '@mui/material/Switch' {
+    interface SwitchPropsColorOverrides {
+        accent: true;
+    }
+}
+
 // Create a theme instance.
 const darkThemeOptions = createTheme({
     components: {

+ 2 - 4
src/utils/collection/index.ts

@@ -129,10 +129,8 @@ const _intSelectOption = (i: number) => {
     return { label: i.toString(), value: i };
 };
 
-export function selectIntOptions(upperLimit: number) {
-    return [...Array(upperLimit).reverse().keys()].map((i) =>
-        _intSelectOption(i + 1)
-    );
+export function getDeviceLimitOptions() {
+    return [2, 5, 10, 25, 50].map((i) => _intSelectOption(i));
 }
 
 export const shareExpiryOptions = [

+ 4 - 4
src/utils/strings/englishConstants.tsx

@@ -50,7 +50,7 @@ const englishConstants = {
     NAME: 'name',
     ENTER_NAME: 'your name',
     EMAIL: 'email',
-    ENTER_EMAIL: 'email',
+    ENTER_EMAIL: 'Email',
     DATA_DISCLAIMER: "we'll never share your data with anyone else.",
     SUBMIT: 'submit',
     EMAIL_ERROR: 'enter a valid email',
@@ -672,7 +672,7 @@ const englishConstants = {
     REPORT_SUBMIT_FAILED: 'failed to sent report, try again',
     INSTALL: 'install',
     ALBUM_URL: 'album url',
-    PUBLIC_SHARING: 'link sharing',
+    PUBLIC_SHARING: 'Public link',
     NOT_FOUND: '404 - not found',
     LINK_EXPIRED: 'this link has either expired or been disabled!',
     MANAGE_LINK: 'manage link',
@@ -680,8 +680,8 @@ const englishConstants = {
     DISABLE_PUBLIC_SHARING: "'disable public sharing",
     DISABLE_PUBLIC_SHARING_MESSAGE:
         'are you sure you want to disable public sharing?',
-    FILE_DOWNLOAD: 'file download',
-    LINK_PASSWORD_LOCK: 'password lock',
+    FILE_DOWNLOAD: 'Allow downloads',
+    LINK_PASSWORD_LOCK: 'Password lock',
     LINK_DEVICE_LIMIT: 'device limit',
     LINK_EXPIRY: 'link expiry',
     LINK_EXPIRY_NEVER: 'never',