From c00c8fd90d3cd06185cb9fc73d4e0ac51da711b8 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Mon, 4 Jul 2022 15:54:32 +0800 Subject: [PATCH] Improve UX for storage settings file size upload limimt input field --- .../StorageSettings.constants.ts | 8 + .../StorageSettings/StorageSettings.tsx | 223 +++++++++++++----- .../StorageSettings/StorageSettings.utils.ts | 19 ++ studio/lib/constants/infrastructure.ts | 2 - 4 files changed, 192 insertions(+), 60 deletions(-) create mode 100644 studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.constants.ts create mode 100644 studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.utils.ts diff --git a/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.constants.ts b/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.constants.ts new file mode 100644 index 00000000000..ebadcfc2e50 --- /dev/null +++ b/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.constants.ts @@ -0,0 +1,8 @@ +export const STORAGE_FILE_SIZE_LIMIT_MAX_BYTES = 5 * 1024 * 1024 * 1024 // 5 GB + +export enum StorageSizeUnits { + BYTES = 'bytes', + KB = 'KB', + MB = 'MB', + GB = 'GB', +} diff --git a/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx b/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx index 983d97f6ac9..8ebb388a2a8 100644 --- a/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx +++ b/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.tsx @@ -1,29 +1,29 @@ import useSWR from 'swr' import { FC, useState } from 'react' -import { AutoField } from 'uniforms-bootstrap4' -import { Typography } from '@supabase/ui' +import { Button, Form, Input, Listbox } from '@supabase/ui' -import { API_URL, STORAGE_FILE_SIZE_LIMIT_MAX_BYTES } from 'lib/constants' -import { patch, get } from 'lib/common/fetch' import { useStore } from 'hooks' -import SchemaFormPanel from 'components/to-be-cleaned/forms/SchemaFormPanel' +import { patch, get } from 'lib/common/fetch' +import { API_URL } from 'lib/constants' import UpgradeToPro from 'components/ui/UpgradeToPro' +import { convertFromBytes, convertToBytes } from './StorageSettings.utils' +import { StorageSizeUnits, STORAGE_FILE_SIZE_LIMIT_MAX_BYTES } from './StorageSettings.constants' const StorageSettings: FC = ({ projectRef }) => { const { data, error } = useSWR(`${API_URL}/projects/${projectRef}/config?app=storage`, get) - if (error) { + if (error || data?.error) { return ( -
- Error loading storage settings +
+

Error loading storage settings

) } if (!data) { return ( -
- Loading... +
+

Loading...

) } @@ -32,64 +32,171 @@ const StorageSettings: FC = ({ projectRef }) => { } const StorageConfig = ({ config, projectRef }: any) => { + const { fileSizeLimit, isFreeTier } = config + const { value, unit } = convertFromBytes(fileSizeLimit) + const { ui } = useStore() + const [selectedUnit, setSelectedUnit] = useState(unit) + let initialValues = { fileSizeLimit: value, unformattedFileSizeLimit: fileSizeLimit } - const { isFreeTier } = config - const [updates, setUpdates] = useState(config) + const formattedMaxSizeBytes = `${new Intl.NumberFormat('en-US').format( + STORAGE_FILE_SIZE_LIMIT_MAX_BYTES + )} bytes` - const updateConfig = async (updatedConfig: any) => { - try { - const response = await patch( - `${API_URL}/projects/${projectRef}/config?app=storage`, - updatedConfig - ) - if (response.error) { - throw response.error + const onValidate = (values: any) => { + const errors = {} as any + if (values.fileSizeLimit > STORAGE_FILE_SIZE_LIMIT_MAX_BYTES) { + errors['fileSizeLimit'] = `Must be a number up to ${new Intl.NumberFormat('en-US').format( + STORAGE_FILE_SIZE_LIMIT_MAX_BYTES + )} bytes.` + } + return errors + } + + const onSubmit = async (values: any) => { + const payload = { fileSizeLimit: convertToBytes(values.fileSizeLimit, selectedUnit) } + const errors = onValidate(payload) + + if (errors.fileSizeLimit) { + ui.setNotification({ + category: 'error', + message: `Upload file size limit must be up to 5GB (${formattedMaxSizeBytes})`, + }) + } else { + const res = await patch(`${API_URL}/projects/${projectRef}/config?app=storage`, payload) + if (res?.error) { + ui.setNotification({ + category: 'error', + message: `Failed to update storage settings: ${res.error.message}`, + }) } else { - ui.setNotification({ category: 'success', message: 'Settings saved' }) + const updatedValue = convertFromBytes(res.fileSizeLimit) + initialValues = { + fileSizeLimit: updatedValue.value, + unformattedFileSizeLimit: res.fileSizeLimit, + } + ui.setNotification({ category: 'success', message: 'Sucessfully updated settings' }) } - } catch (error: any) { - ui.setNotification({ category: 'error', message: `Update config failed: ${error.message}` }) } } + // [Joshen] To be refactored using FormContainer, FormPanel, FormContent etc once + // Jonny's auth config refactor PR goes in return ( -
- +
+ {({ + values, + isSubmitting, + handleReset, + }: { + values: any + isSubmitting: boolean + handleReset: () => void + }) => { + const hasChanges = + initialValues.unformattedFileSizeLimit !== + convertToBytes(values.fileSizeLimit, selectedUnit) + return ( + <> +
+

Storage settings

+
+ Configure your project's storage settings +
+
+
+
+
+
+
+

Upload file size limit

+
+
+
+
+ +
+
+ + {Object.values(StorageSizeUnits).map((unit: string) => ( + +
{unit}
+
+ ))} +
+
+
+

+ {selectedUnit !== StorageSizeUnits.BYTES && + `Equivalent to ${convertToBytes( + values.fileSizeLimit, + selectedUnit + ).toLocaleString()} bytes. `} + Maximum size in bytes of a file that can be uploaded is 5 GB ( + {formattedMaxSizeBytes}). +

+
+
+
+ {isFreeTier && ( +
+ +
+ )} +
+
+
+
+ + +
+
+
+
+
+ + ) }} - model={updates} - onSubmit={(model: any) => updateConfig(model)} - onReset={() => setUpdates(config)} - > - - {isFreeTier && ( - - )} - +
) } diff --git a/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.utils.ts b/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.utils.ts new file mode 100644 index 00000000000..54b9745bfa5 --- /dev/null +++ b/studio/components/to-be-cleaned/Storage/StorageSettings/StorageSettings.utils.ts @@ -0,0 +1,19 @@ +import { StorageSizeUnits } from './StorageSettings.constants' + +const k = 1024 + +// [JOSHEN TODO] Write tests for this +export const convertFromBytes = (bytes: number) => { + // Up to GB since that's our storage upload limit + if (bytes === 0) return { value: 0, unit: StorageSizeUnits.BYTES } + + const i = Math.floor(Math.log(bytes) / Math.log(k)) + const unit = Object.values(StorageSizeUnits)[i] + const value = bytes / Math.pow(k, i) + return { value, unit } +} + +export const convertToBytes = (size: number, unit: StorageSizeUnits) => { + const i = Object.values(StorageSizeUnits).indexOf(unit) + return size * Math.pow(k, i) +} diff --git a/studio/lib/constants/infrastructure.ts b/studio/lib/constants/infrastructure.ts index 657c1d96381..d9a7f5a5475 100644 --- a/studio/lib/constants/infrastructure.ts +++ b/studio/lib/constants/infrastructure.ts @@ -140,6 +140,4 @@ export const PASSWORD_STRENGTH_PERCENTAGE = { export const DEFAULT_FREE_PROJECTS_LIMIT = 2 -export const STORAGE_FILE_SIZE_LIMIT_MAX_BYTES = 5 * 1024 * 1024 * 1024 // 5 GB - export const DEFAULT_PROJECT_API_SERVICE_ID = 1