commit
549232c26c
|
@ -55,3 +55,9 @@ export const Value = styled.div<{ width?: string }>`
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #ddd;
|
color: #ddd;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const FlexWrapper = styled.div`
|
||||||
|
display: flex;
|
||||||
|
text-align: center;
|
||||||
|
justify-content: center;
|
||||||
|
`;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
import { slide as Menu } from 'react-burger-menu';
|
import { slide as Menu } from 'react-burger-menu';
|
||||||
import constants from 'utils/strings/constants';
|
import constants from 'utils/strings/constants';
|
||||||
|
@ -8,7 +8,6 @@ import { getEndpoint } from 'utils/common/apiUtil';
|
||||||
import { Button } from 'react-bootstrap';
|
import { Button } from 'react-bootstrap';
|
||||||
import {
|
import {
|
||||||
isSubscriptionActive,
|
isSubscriptionActive,
|
||||||
convertBytesToGBs,
|
|
||||||
getUserSubscription,
|
getUserSubscription,
|
||||||
isOnFreePlan,
|
isOnFreePlan,
|
||||||
isSubscriptionCancelled,
|
isSubscriptionCancelled,
|
||||||
|
@ -28,7 +27,7 @@ import EnteSpinner from './EnteSpinner';
|
||||||
import RecoveryKeyModal from './RecoveryKeyModal';
|
import RecoveryKeyModal from './RecoveryKeyModal';
|
||||||
import TwoFactorModal from './TwoFactorModal';
|
import TwoFactorModal from './TwoFactorModal';
|
||||||
import ExportModal from './ExportModal';
|
import ExportModal from './ExportModal';
|
||||||
import { SetLoading } from 'pages/gallery';
|
import { GalleryContext, SetLoading } from 'pages/gallery';
|
||||||
import InProgressIcon from './icons/InProgressIcon';
|
import InProgressIcon from './icons/InProgressIcon';
|
||||||
import exportService from 'services/exportService';
|
import exportService from 'services/exportService';
|
||||||
import { Subscription } from 'services/billingService';
|
import { Subscription } from 'services/billingService';
|
||||||
|
@ -51,6 +50,8 @@ export default function Sidebar(props: Props) {
|
||||||
const [recoverModalView, setRecoveryModalView] = useState(false);
|
const [recoverModalView, setRecoveryModalView] = useState(false);
|
||||||
const [twoFactorModalView, setTwoFactorModalView] = useState(false);
|
const [twoFactorModalView, setTwoFactorModalView] = useState(false);
|
||||||
const [exportModalView, setExportModalView] = useState(false);
|
const [exportModalView, setExportModalView] = useState(false);
|
||||||
|
const galleryContext = useContext(GalleryContext);
|
||||||
|
galleryContext.showPlanSelectorModal = props.showPlanSelectorModal;
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
|
@ -186,7 +187,7 @@ export default function Sidebar(props: Props) {
|
||||||
{usage ? (
|
{usage ? (
|
||||||
constants.USAGE_INFO(
|
constants.USAGE_INFO(
|
||||||
usage,
|
usage,
|
||||||
Number(convertBytesToGBs(subscription?.storage))
|
convertToHumanReadable(subscription?.storage)
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<div style={{ textAlign: 'center' }}>
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
|
27
src/components/icons/WarningIcon.tsx
Normal file
27
src/components/icons/WarningIcon.tsx
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function WarningIcon(props) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
color: 'red',
|
||||||
|
display: 'inline-block',
|
||||||
|
padding: '0 10px',
|
||||||
|
}}>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
height={props.height}
|
||||||
|
viewBox={props.viewBox}
|
||||||
|
width={props.width}
|
||||||
|
fill="currentColor">
|
||||||
|
<path d="M12 2c5.514 0 10 4.486 10 10s-4.486 10-10 10-10-4.486-10-10 4.486-10 10-10zm0-2c-6.627 0-12 5.373-12 12s5.373 12 12 12 12-5.373 12-12-5.373-12-12-12zm-1 6h2v8h-2v-8zm1 12.25c-.69 0-1.25-.56-1.25-1.25s.56-1.25 1.25-1.25 1.25.56 1.25 1.25-.56 1.25-1.25 1.25z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
WarningIcon.defaultProps = {
|
||||||
|
height: 24,
|
||||||
|
width: 24,
|
||||||
|
viewBox: '0 0 24 24',
|
||||||
|
};
|
|
@ -1,30 +1,28 @@
|
||||||
|
import { FlexWrapper } from 'components/Container';
|
||||||
|
import WarningIcon from 'components/icons/WarningIcon';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import Alert from 'react-bootstrap/Alert';
|
import styled from 'styled-components';
|
||||||
import { getVariantColor } from './LinkButton';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
bannerMessage?: any;
|
bannerMessage?: any;
|
||||||
variant?: string;
|
variant?: string;
|
||||||
children?: any;
|
|
||||||
style?: any;
|
|
||||||
}
|
}
|
||||||
|
const Banner = styled.div`
|
||||||
|
border: 1px solid #71662e;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 16px 28px;
|
||||||
|
color: #eee;
|
||||||
|
margin-top: 10px;
|
||||||
|
`;
|
||||||
export default function AlertBanner(props: Props) {
|
export default function AlertBanner(props: Props) {
|
||||||
return (
|
return props.bannerMessage ? (
|
||||||
<Alert
|
<FlexWrapper>
|
||||||
variant={props.variant ?? 'danger'}
|
<Banner>
|
||||||
style={{
|
<WarningIcon />
|
||||||
display:
|
{props.bannerMessage && props.bannerMessage}
|
||||||
props.bannerMessage || props.children ? 'block' : 'none',
|
</Banner>
|
||||||
textAlign: 'center',
|
</FlexWrapper>
|
||||||
border: 'none',
|
) : (
|
||||||
background: 'none',
|
<></>
|
||||||
borderRadius: '0px',
|
|
||||||
color: getVariantColor(props.variant),
|
|
||||||
padding: 0,
|
|
||||||
margin: 0,
|
|
||||||
...props.style,
|
|
||||||
}}>
|
|
||||||
{props.bannerMessage ? props.bannerMessage : props.children}
|
|
||||||
</Alert>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import UploadProgress from './UploadProgress';
|
||||||
import ChoiceModal from './ChoiceModal';
|
import ChoiceModal from './ChoiceModal';
|
||||||
import { SetCollectionNamerAttributes } from './CollectionNamer';
|
import { SetCollectionNamerAttributes } from './CollectionNamer';
|
||||||
import { SetCollectionSelectorAttributes } from './CollectionSelector';
|
import { SetCollectionSelectorAttributes } from './CollectionSelector';
|
||||||
import { SetFiles, SetLoading } from 'pages/gallery';
|
import { GalleryContext, SetFiles, SetLoading } from 'pages/gallery';
|
||||||
import { AppContext } from 'pages/_app';
|
import { AppContext } from 'pages/_app';
|
||||||
import { logError } from 'utils/sentry';
|
import { logError } from 'utils/sentry';
|
||||||
import { FileRejection } from 'react-dropzone';
|
import { FileRejection } from 'react-dropzone';
|
||||||
|
@ -22,6 +22,7 @@ import UploadManager, {
|
||||||
} from 'services/upload/uploadManager';
|
} from 'services/upload/uploadManager';
|
||||||
import uploadManager from 'services/upload/uploadManager';
|
import uploadManager from 'services/upload/uploadManager';
|
||||||
import { METADATA_FOLDER_NAME } from 'services/exportService';
|
import { METADATA_FOLDER_NAME } from 'services/exportService';
|
||||||
|
import { getUserFacingErrorMessage } from 'utils/common/errorUtil';
|
||||||
|
|
||||||
const FIRST_ALBUM_NAME = 'My First Album';
|
const FIRST_ALBUM_NAME = 'My First Album';
|
||||||
|
|
||||||
|
@ -75,6 +76,7 @@ export default function Upload(props: Props) {
|
||||||
const [fileAnalysisResult, setFileAnalysisResult] =
|
const [fileAnalysisResult, setFileAnalysisResult] =
|
||||||
useState<AnalysisResult>(null);
|
useState<AnalysisResult>(null);
|
||||||
const appContext = useContext(AppContext);
|
const appContext = useContext(AppContext);
|
||||||
|
const galleryContext = useContext(GalleryContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
UploadManager.initUploader(
|
UploadManager.initUploader(
|
||||||
|
@ -280,7 +282,11 @@ export default function Upload(props: Props) {
|
||||||
collections
|
collections
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
props.setBannerMessage(err.message);
|
const message = getUserFacingErrorMessage(
|
||||||
|
err.message,
|
||||||
|
galleryContext.showPlanSelectorModal
|
||||||
|
);
|
||||||
|
props.setBannerMessage(message);
|
||||||
setProgressView(false);
|
setProgressView(false);
|
||||||
throw err;
|
throw err;
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -296,8 +302,12 @@ export default function Upload(props: Props) {
|
||||||
await props.syncWithRemote(true, true);
|
await props.syncWithRemote(true, true);
|
||||||
await uploadManager.retryFailedFiles();
|
await uploadManager.retryFailedFiles();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
const message = getUserFacingErrorMessage(
|
||||||
|
err.message,
|
||||||
|
galleryContext.showPlanSelectorModal
|
||||||
|
);
|
||||||
appContext.resetSharedFiles();
|
appContext.resetSharedFiles();
|
||||||
props.setBannerMessage(err.message);
|
props.setBannerMessage(message);
|
||||||
setProgressView(false);
|
setProgressView(false);
|
||||||
} finally {
|
} finally {
|
||||||
props.setUploadInProgress(false);
|
props.setUploadInProgress(false);
|
||||||
|
|
|
@ -10,8 +10,8 @@ import {
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { DESKTOP_APP_DOWNLOAD_URL } from 'utils/common';
|
import { DESKTOP_APP_DOWNLOAD_URL } from 'utils/common';
|
||||||
import constants from 'utils/strings/constants';
|
import constants from 'utils/strings/constants';
|
||||||
import AlertBanner from './AlertBanner';
|
|
||||||
import { Collapse } from 'react-collapse';
|
import { Collapse } from 'react-collapse';
|
||||||
|
import { ButtonVariant, getVariantColor } from './LinkButton';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
fileCounter;
|
fileCounter;
|
||||||
|
@ -65,6 +65,14 @@ const Content = styled.div`
|
||||||
padding-right: 30px;
|
padding-right: 30px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const NotUploadSectionHeader = styled.div`
|
||||||
|
margin-top: 30px;
|
||||||
|
text-align: center;
|
||||||
|
color: ${getVariantColor(ButtonVariant.warning)};
|
||||||
|
border-bottom: 1px solid ${getVariantColor(ButtonVariant.warning)};
|
||||||
|
margin: 0 20px;
|
||||||
|
`;
|
||||||
|
|
||||||
interface ResultSectionProps {
|
interface ResultSectionProps {
|
||||||
fileUploadResultMap: Map<FileUploadResults, string[]>;
|
fileUploadResultMap: Map<FileUploadResults, string[]>;
|
||||||
fileUploadResult: FileUploadResults;
|
fileUploadResult: FileUploadResults;
|
||||||
|
@ -136,12 +144,6 @@ const InProgressSection = (props: InProgressProps) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const NotUploadSectionHeader = () => (
|
|
||||||
<AlertBanner variant="warning" style={{ marginTop: '30px' }}>
|
|
||||||
{constants.FILE_NOT_UPLOADED_LIST}
|
|
||||||
</AlertBanner>
|
|
||||||
);
|
|
||||||
|
|
||||||
export default function UploadProgress(props: Props) {
|
export default function UploadProgress(props: Props) {
|
||||||
const fileProgressStatuses = [] as FileProgresses[];
|
const fileProgressStatuses = [] as FileProgresses[];
|
||||||
const fileUploadResultMap = new Map<FileUploadResults, string[]>();
|
const fileUploadResultMap = new Map<FileUploadResults, string[]>();
|
||||||
|
@ -214,7 +216,11 @@ export default function UploadProgress(props: Props) {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{props.uploadStage === UPLOAD_STAGES.FINISH &&
|
{props.uploadStage === UPLOAD_STAGES.FINISH &&
|
||||||
filesNotUploaded && <NotUploadSectionHeader />}
|
filesNotUploaded && (
|
||||||
|
<NotUploadSectionHeader>
|
||||||
|
{constants.FILE_NOT_UPLOADED_LIST}
|
||||||
|
</NotUploadSectionHeader>
|
||||||
|
)}
|
||||||
|
|
||||||
<ResultSection
|
<ResultSection
|
||||||
fileUploadResultMap={fileUploadResultMap}
|
fileUploadResultMap={fileUploadResultMap}
|
||||||
|
@ -241,6 +247,12 @@ export default function UploadProgress(props: Props) {
|
||||||
sectionTitle={constants.UNSUPPORTED_FILES}
|
sectionTitle={constants.UNSUPPORTED_FILES}
|
||||||
sectionInfo={constants.UNSUPPORTED_INFO}
|
sectionInfo={constants.UNSUPPORTED_INFO}
|
||||||
/>
|
/>
|
||||||
|
<ResultSection
|
||||||
|
fileUploadResultMap={fileUploadResultMap}
|
||||||
|
fileUploadResult={FileUploadResults.TOO_LARGE}
|
||||||
|
sectionTitle={constants.TOO_LARGE_UPLOADS}
|
||||||
|
sectionInfo={constants.TOO_LARGE_INFO}
|
||||||
|
/>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
{props.uploadStage === UPLOAD_STAGES.FINISH && (
|
{props.uploadStage === UPLOAD_STAGES.FINISH && (
|
||||||
<Modal.Footer style={{ border: 'none' }}>
|
<Modal.Footer style={{ border: 'none' }}>
|
||||||
|
|
|
@ -98,11 +98,13 @@ export interface SearchStats {
|
||||||
type GalleryContextType = {
|
type GalleryContextType = {
|
||||||
thumbs: Map<number, string>;
|
thumbs: Map<number, string>;
|
||||||
files: Map<number, string>;
|
files: Map<number, string>;
|
||||||
|
showPlanSelectorModal: () => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultGalleryContext: GalleryContextType = {
|
const defaultGalleryContext: GalleryContextType = {
|
||||||
thumbs: new Map(),
|
thumbs: new Map(),
|
||||||
files: new Map(),
|
files: new Map(),
|
||||||
|
showPlanSelectorModal: () => null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const GalleryContext = createContext<GalleryContextType>(
|
export const GalleryContext = createContext<GalleryContextType>(
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { getToken } from 'utils/common/key';
|
||||||
import { logError } from 'utils/sentry';
|
import { logError } from 'utils/sentry';
|
||||||
import { UploadFile, UploadURL } from './uploadService';
|
import { UploadFile, UploadURL } from './uploadService';
|
||||||
import { File } from '../fileService';
|
import { File } from '../fileService';
|
||||||
import { CustomError } from 'utils/common/errorUtil';
|
import { CustomError, handleUploadError } from 'utils/common/errorUtil';
|
||||||
import { retryAsyncFunction } from 'utils/network';
|
import { retryAsyncFunction } from 'utils/network';
|
||||||
import { MultipartUploadURLs } from './multiPartUploadService';
|
import { MultipartUploadURLs } from './multiPartUploadService';
|
||||||
|
|
||||||
|
@ -20,10 +20,12 @@ class UploadHttpClient {
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const response = await retryAsyncFunction(() =>
|
const response = await retryAsyncFunction(
|
||||||
|
() =>
|
||||||
HTTPService.post(`${ENDPOINT}/files`, uploadFile, null, {
|
HTTPService.post(`${ENDPOINT}/files`, uploadFile, null, {
|
||||||
'X-Auth-Token': token,
|
'X-Auth-Token': token,
|
||||||
})
|
}),
|
||||||
|
handleUploadError
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -29,6 +29,7 @@ export enum FileUploadResults {
|
||||||
SKIPPED = -2,
|
SKIPPED = -2,
|
||||||
UNSUPPORTED = -3,
|
UNSUPPORTED = -3,
|
||||||
BLOCKED = -4,
|
BLOCKED = -4,
|
||||||
|
TOO_LARGE = -5,
|
||||||
UPLOADED = 100,
|
UPLOADED = 100,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { encryptFiledata } from './encryptionService';
|
||||||
import { ENCRYPTION_CHUNK_SIZE } from 'types';
|
import { ENCRYPTION_CHUNK_SIZE } from 'types';
|
||||||
import { uploadStreamUsingMultipart } from './multiPartUploadService';
|
import { uploadStreamUsingMultipart } from './multiPartUploadService';
|
||||||
import UIService from './uiService';
|
import UIService from './uiService';
|
||||||
import { parseError } from 'utils/common/errorUtil';
|
import { handleUploadError } from 'utils/common/errorUtil';
|
||||||
import { MetadataMap } from './uploadManager';
|
import { MetadataMap } from './uploadManager';
|
||||||
import { fileIsHEIC } from 'utils/file';
|
import { fileIsHEIC } from 'utils/file';
|
||||||
|
|
||||||
|
@ -258,10 +258,8 @@ class UploadService {
|
||||||
await this.fetchUploadURLs();
|
await this.fetchUploadURLs();
|
||||||
// checking for any subscription related errors
|
// checking for any subscription related errors
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const { parsedError, parsed } = parseError(e);
|
logError(e, 'prefetch uploadURL failed');
|
||||||
if (parsed) {
|
handleUploadError(e);
|
||||||
throw parsedError;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { File, FILE_TYPE } from 'services/fileService';
|
import { File, FILE_TYPE } from 'services/fileService';
|
||||||
import { sleep } from 'utils/common';
|
import { sleep } from 'utils/common';
|
||||||
import { handleError, CustomError } from 'utils/common/errorUtil';
|
import { handleUploadError, CustomError } from 'utils/common/errorUtil';
|
||||||
import { decryptFile } from 'utils/file';
|
import { decryptFile } from 'utils/file';
|
||||||
import { logError } from 'utils/sentry';
|
import { logError } from 'utils/sentry';
|
||||||
import { fileAlreadyInCollection } from 'utils/upload';
|
import { fileAlreadyInCollection } from 'utils/upload';
|
||||||
|
@ -19,7 +19,7 @@ import uploadService from './uploadService';
|
||||||
import { FileTypeInfo, getFileType } from './readFileService';
|
import { FileTypeInfo, getFileType } from './readFileService';
|
||||||
|
|
||||||
const TwoSecondInMillSeconds = 2000;
|
const TwoSecondInMillSeconds = 2000;
|
||||||
|
const FIVE_GB_IN_BYTES = 5 * 1024 * 1024 * 1024;
|
||||||
interface UploadResponse {
|
interface UploadResponse {
|
||||||
fileUploadResult: FileUploadResults;
|
fileUploadResult: FileUploadResults;
|
||||||
file?: File;
|
file?: File;
|
||||||
|
@ -40,6 +40,15 @@ export default async function uploader(
|
||||||
let fileWithMetadata: FileWithMetadata = null;
|
let fileWithMetadata: FileWithMetadata = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (rawFile.size >= FIVE_GB_IN_BYTES) {
|
||||||
|
UIService.setFileProgress(
|
||||||
|
rawFile.name,
|
||||||
|
FileUploadResults.TOO_LARGE
|
||||||
|
);
|
||||||
|
// wait two second before removing the file from the progress in file section
|
||||||
|
await sleep(TwoSecondInMillSeconds);
|
||||||
|
return { fileUploadResult: FileUploadResults.TOO_LARGE };
|
||||||
|
}
|
||||||
fileTypeInfo = await getFileType(worker, rawFile);
|
fileTypeInfo = await getFileType(worker, rawFile);
|
||||||
if (fileTypeInfo.fileType === FILE_TYPE.OTHERS) {
|
if (fileTypeInfo.fileType === FILE_TYPE.OTHERS) {
|
||||||
throw Error(CustomError.UNSUPPORTED_FILE_FORMAT);
|
throw Error(CustomError.UNSUPPORTED_FILE_FORMAT);
|
||||||
|
@ -94,12 +103,11 @@ export default async function uploader(
|
||||||
file: decryptedFile,
|
file: decryptedFile,
|
||||||
};
|
};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e);
|
|
||||||
const fileFormat =
|
const fileFormat =
|
||||||
fileTypeInfo.exactType ?? rawFile.name.split('.')[-1];
|
fileTypeInfo.exactType ?? rawFile.name.split('.')[-1];
|
||||||
logError(e, 'file upload failed', { fileFormat });
|
logError(e, 'file upload failed', { fileFormat });
|
||||||
handleError(e);
|
const error = handleUploadError(e);
|
||||||
switch (e.message) {
|
switch (error.message) {
|
||||||
case CustomError.ETAG_MISSING:
|
case CustomError.ETAG_MISSING:
|
||||||
UIService.setFileProgress(
|
UIService.setFileProgress(
|
||||||
rawFile.name,
|
rawFile.name,
|
||||||
|
@ -112,6 +120,13 @@ export default async function uploader(
|
||||||
FileUploadResults.UNSUPPORTED
|
FileUploadResults.UNSUPPORTED
|
||||||
);
|
);
|
||||||
return { fileUploadResult: FileUploadResults.UNSUPPORTED };
|
return { fileUploadResult: FileUploadResults.UNSUPPORTED };
|
||||||
|
|
||||||
|
case CustomError.FILE_TOO_LARGE:
|
||||||
|
UIService.setFileProgress(
|
||||||
|
rawFile.name,
|
||||||
|
FileUploadResults.TOO_LARGE
|
||||||
|
);
|
||||||
|
return { fileUploadResult: FileUploadResults.TOO_LARGE };
|
||||||
default:
|
default:
|
||||||
UIService.setFileProgress(
|
UIService.setFileProgress(
|
||||||
rawFile.name,
|
rawFile.name,
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
import constants from 'utils/strings/constants';
|
import constants from 'utils/strings/constants';
|
||||||
|
|
||||||
export const ServerErrorCodes = {
|
export const ServerErrorCodes = {
|
||||||
|
@ -8,51 +9,81 @@ export const ServerErrorCodes = {
|
||||||
FILE_TOO_LARGE: '413',
|
FILE_TOO_LARGE: '413',
|
||||||
};
|
};
|
||||||
|
|
||||||
export const CustomError = {
|
export enum CustomError {
|
||||||
SUBSCRIPTION_VERIFICATION_ERROR: 'Subscription verification failed',
|
UNKNOWN_ERROR = 'unknown error',
|
||||||
THUMBNAIL_GENERATION_FAILED: 'thumbnail generation failed',
|
SUBSCRIPTION_VERIFICATION_ERROR = 'Subscription verification failed',
|
||||||
VIDEO_PLAYBACK_FAILED: 'video playback failed',
|
THUMBNAIL_GENERATION_FAILED = 'thumbnail generation failed',
|
||||||
ETAG_MISSING: 'no header/etag present in response body',
|
VIDEO_PLAYBACK_FAILED = 'video playback failed',
|
||||||
KEY_MISSING: 'encrypted key missing from localStorage',
|
ETAG_MISSING = 'no header/etag present in response body',
|
||||||
FAILED_TO_LOAD_WEB_WORKER: 'failed to load web worker',
|
KEY_MISSING = 'encrypted key missing from localStorage',
|
||||||
CHUNK_MORE_THAN_EXPECTED: 'chunks more than expected',
|
FAILED_TO_LOAD_WEB_WORKER = 'failed to load web worker',
|
||||||
UNSUPPORTED_FILE_FORMAT: 'unsupported file formats',
|
CHUNK_MORE_THAN_EXPECTED = 'chunks more than expected',
|
||||||
};
|
UNSUPPORTED_FILE_FORMAT = 'unsupported file formats',
|
||||||
|
FILE_TOO_LARGE = 'file too large',
|
||||||
|
SUBSCRIPTION_EXPIRED = 'subscription expired',
|
||||||
|
STORAGE_QUOTA_EXCEEDED = 'storage quota exceeded',
|
||||||
|
SESSION_EXPIRED_MESSAGE = 'session expired',
|
||||||
|
}
|
||||||
|
|
||||||
export function parseError(error) {
|
function parseUploadError(error: AxiosResponse) {
|
||||||
let parsedMessage = null;
|
let parsedMessage = null;
|
||||||
if (error?.status) {
|
if (error?.status) {
|
||||||
const errorCode = error.status.toString();
|
const errorCode = error.status.toString();
|
||||||
switch (errorCode) {
|
switch (errorCode) {
|
||||||
case ServerErrorCodes.NO_ACTIVE_SUBSCRIPTION:
|
case ServerErrorCodes.NO_ACTIVE_SUBSCRIPTION:
|
||||||
parsedMessage = constants.SUBSCRIPTION_EXPIRED;
|
parsedMessage = CustomError.SUBSCRIPTION_EXPIRED;
|
||||||
break;
|
break;
|
||||||
case ServerErrorCodes.STORAGE_LIMIT_EXCEEDED:
|
case ServerErrorCodes.STORAGE_LIMIT_EXCEEDED:
|
||||||
parsedMessage = constants.STORAGE_QUOTA_EXCEEDED;
|
parsedMessage = CustomError.STORAGE_QUOTA_EXCEEDED;
|
||||||
break;
|
break;
|
||||||
case ServerErrorCodes.SESSION_EXPIRED:
|
case ServerErrorCodes.SESSION_EXPIRED:
|
||||||
parsedMessage = constants.SESSION_EXPIRED_MESSAGE;
|
parsedMessage = CustomError.SESSION_EXPIRED_MESSAGE;
|
||||||
break;
|
break;
|
||||||
case ServerErrorCodes.FILE_TOO_LARGE:
|
case ServerErrorCodes.FILE_TOO_LARGE:
|
||||||
parsedMessage = constants.FILE_TOO_LARGE;
|
parsedMessage = CustomError.FILE_TOO_LARGE;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (parsedMessage) {
|
if (parsedMessage) {
|
||||||
return { parsedError: new Error(parsedMessage), parsed: true };
|
return {
|
||||||
|
parsedError: new Error(parsedMessage),
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
parsedError: new Error(`${constants.UNKNOWN_ERROR} ${error}`),
|
parsedError: new Error(CustomError.UNKNOWN_ERROR),
|
||||||
parsed: false,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function handleError(error) {
|
export function handleUploadError(error: AxiosResponse | Error): Error {
|
||||||
const { parsedError, parsed } = parseError(error);
|
let parsedError: Error = null;
|
||||||
if (parsed) {
|
if ('status' in error) {
|
||||||
throw parsedError;
|
parsedError = parseUploadError(error).parsedError;
|
||||||
} else {
|
} else {
|
||||||
// swallow error don't break the caller flow
|
parsedError = error;
|
||||||
|
}
|
||||||
|
// breaking errors
|
||||||
|
switch (parsedError.message) {
|
||||||
|
case CustomError.SUBSCRIPTION_EXPIRED:
|
||||||
|
case CustomError.STORAGE_QUOTA_EXCEEDED:
|
||||||
|
case CustomError.SESSION_EXPIRED_MESSAGE:
|
||||||
|
throw parsedError;
|
||||||
|
}
|
||||||
|
return parsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUserFacingErrorMessage(
|
||||||
|
err: CustomError,
|
||||||
|
action: () => void
|
||||||
|
) {
|
||||||
|
switch (err) {
|
||||||
|
case CustomError.SESSION_EXPIRED_MESSAGE:
|
||||||
|
return constants.SESSION_EXPIRED_MESSAGE;
|
||||||
|
case CustomError.SUBSCRIPTION_EXPIRED:
|
||||||
|
return constants.SUBSCRIPTION_EXPIRED(action);
|
||||||
|
case CustomError.STORAGE_QUOTA_EXCEEDED:
|
||||||
|
return constants.STORAGE_QUOTA_EXCEEDED(action);
|
||||||
|
default:
|
||||||
|
return constants.UNKNOWN_ERROR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,10 @@ import { sleep } from 'utils/common';
|
||||||
|
|
||||||
const retrySleepTimeInMilliSeconds = [2000, 5000, 10000];
|
const retrySleepTimeInMilliSeconds = [2000, 5000, 10000];
|
||||||
|
|
||||||
export async function retryAsyncFunction(func: () => Promise<any>) {
|
export async function retryAsyncFunction(
|
||||||
|
func: () => Promise<any>,
|
||||||
|
checkForBreakingError?: (error) => void
|
||||||
|
) {
|
||||||
const retrier = async (
|
const retrier = async (
|
||||||
func: () => Promise<any>,
|
func: () => Promise<any>,
|
||||||
attemptNumber: number = 0
|
attemptNumber: number = 0
|
||||||
|
@ -11,6 +14,9 @@ export async function retryAsyncFunction(func: () => Promise<any>) {
|
||||||
const resp = await func();
|
const resp = await func();
|
||||||
return resp;
|
return resp;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
if (checkForBreakingError) {
|
||||||
|
checkForBreakingError(e);
|
||||||
|
}
|
||||||
if (attemptNumber < retrySleepTimeInMilliSeconds.length) {
|
if (attemptNumber < retrySleepTimeInMilliSeconds.length) {
|
||||||
await sleep(retrySleepTimeInMilliSeconds[attemptNumber]);
|
await sleep(retrySleepTimeInMilliSeconds[attemptNumber]);
|
||||||
await retrier(func, attemptNumber + 1);
|
await retrier(func, attemptNumber + 1);
|
||||||
|
|
|
@ -23,6 +23,14 @@ const Logo = styled.img`
|
||||||
margin-top: -3px;
|
margin-top: -3px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const Trigger = styled.span`
|
||||||
|
:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
color: #51cd7c;
|
||||||
|
`;
|
||||||
|
|
||||||
const englishConstants = {
|
const englishConstants = {
|
||||||
HERO_HEADER: () => (
|
HERO_HEADER: () => (
|
||||||
<div>
|
<div>
|
||||||
|
@ -120,9 +128,18 @@ const englishConstants = {
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
SUBSCRIPTION_EXPIRED: 'your subscription has expired, please renew',
|
SUBSCRIPTION_EXPIRED: (action) => (
|
||||||
STORAGE_QUOTA_EXCEEDED:
|
<>
|
||||||
'you have exceeded your storage quota, please upgrade your plan',
|
your subscription has expired, please a{' '}
|
||||||
|
<Trigger onClick={action}>renew</Trigger>
|
||||||
|
</>
|
||||||
|
),
|
||||||
|
STORAGE_QUOTA_EXCEEDED: (action) => (
|
||||||
|
<>
|
||||||
|
you have exceeded your storage quota, please{' '}
|
||||||
|
<Trigger onClick={action}>upgrade</Trigger> your plan
|
||||||
|
</>
|
||||||
|
),
|
||||||
INITIAL_LOAD_DELAY_WARNING: 'the first load may take some time',
|
INITIAL_LOAD_DELAY_WARNING: 'the first load may take some time',
|
||||||
USER_DOES_NOT_EXIST: 'sorry, could not find a user with that email',
|
USER_DOES_NOT_EXIST: 'sorry, could not find a user with that email',
|
||||||
UPLOAD_BUTTON_TEXT: 'upload',
|
UPLOAD_BUTTON_TEXT: 'upload',
|
||||||
|
@ -520,8 +537,9 @@ const englishConstants = {
|
||||||
UNSUPPORTED_INFO: 'ente does not support these file formats yet',
|
UNSUPPORTED_INFO: 'ente does not support these file formats yet',
|
||||||
BLOCKED_UPLOADS: 'blocked uploads',
|
BLOCKED_UPLOADS: 'blocked uploads',
|
||||||
INPROGRESS_UPLOADS: 'uploads in progress',
|
INPROGRESS_UPLOADS: 'uploads in progress',
|
||||||
FILE_TOO_LARGE:
|
TOO_LARGE_UPLOADS: 'large files',
|
||||||
'the file you are trying to upload is larger than the storage available, please upgrade your plan and try again',
|
TOO_LARGE_INFO:
|
||||||
|
'these files were not uploaded as they exceed the maximum size limit for your storage plan',
|
||||||
UPLOAD_TO_COLLECTION: 'upload to album',
|
UPLOAD_TO_COLLECTION: 'upload to album',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue