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