diff --git a/about/package-lock.json b/about/package-lock.json index f867ddc58e2..1e189d2be29 100644 --- a/about/package-lock.json +++ b/about/package-lock.json @@ -13606,9 +13606,9 @@ } }, "url-parse": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", - "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "requires": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index cdb3fb7727b..e8879568c3c 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -37,7 +37,7 @@ services: auth: container_name: supabase-auth - image: supabase/gotrue:v2.5.8 + image: supabase/gotrue:v2.5.21 depends_on: - db restart: unless-stopped diff --git a/examples/angular-todo-list/package-lock.json b/examples/angular-todo-list/package-lock.json index 18a11279b7a..f152818b879 100644 --- a/examples/angular-todo-list/package-lock.json +++ b/examples/angular-todo-list/package-lock.json @@ -15068,9 +15068,9 @@ } }, "url-parse": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", - "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dev": true, "requires": { "querystringify": "^2.1.1", diff --git a/examples/nuxtjs-user-management/package-lock.json b/examples/nuxtjs-user-management/package-lock.json index 0bbbc5b770a..e6c1b3a9d52 100644 --- a/examples/nuxtjs-user-management/package-lock.json +++ b/examples/nuxtjs-user-management/package-lock.json @@ -6884,6 +6884,11 @@ "sha.js": "^2.4.8" } }, + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==" + }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", @@ -6916,27 +6921,18 @@ "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, "postcss": { - "version": "7.0.35", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.35.tgz", - "integrity": "sha512-3QT8bBJeX/S5zKTTjTCIjRF3If4avAT6kqxcASlTWEtAFCb9NH0OUxNDfgZSWdP5fJnBYCMEWkIFfWeugjzYMg==", + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", "requires": { - "chalk": "^2.4.2", - "source-map": "^0.6.1", - "supports-color": "^6.1.0" + "picocolors": "^0.2.1", + "source-map": "^0.6.1" }, "dependencies": { "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "requires": { - "has-flag": "^3.0.0" - } } } }, diff --git a/examples/react-todo-list-collaborative/package-lock.json b/examples/react-todo-list-collaborative/package-lock.json index 20b453698ba..788ebc0a3f3 100644 --- a/examples/react-todo-list-collaborative/package-lock.json +++ b/examples/react-todo-list-collaborative/package-lock.json @@ -21387,9 +21387,9 @@ } }, "url-parse": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", - "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "requires": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" diff --git a/examples/react-todo-list-collaborative/yarn.lock b/examples/react-todo-list-collaborative/yarn.lock index bd8b93d6563..b4ab86183dd 100644 --- a/examples/react-todo-list-collaborative/yarn.lock +++ b/examples/react-todo-list-collaborative/yarn.lock @@ -8546,9 +8546,9 @@ querystring@0.2.0: integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= querystringify@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.1.1.tgz#60e5a5fd64a7f8bfa4d2ab2ed6fdf4c85bad154e" - integrity sha512-w7fLxIRCRT7U8Qu53jQnJyPkYZIaR4n5151KMfcJlO/A9397Wxb1amJvROTK6TOnp7PfoAmg/qXiNHI+08jRfA== + version "2.2.0" + resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" + integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== raf@^3.4.1: version "3.4.1" @@ -10337,9 +10337,9 @@ url-loader@2.3.0: schema-utils "^2.5.0" url-parse@^1.4.3: - version "1.4.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278" - integrity sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg== + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" diff --git a/examples/react-todo-list/package-lock.json b/examples/react-todo-list/package-lock.json index 61705ce1a8e..8e9066a3e12 100644 --- a/examples/react-todo-list/package-lock.json +++ b/examples/react-todo-list/package-lock.json @@ -17009,9 +17009,9 @@ } }, "url-parse": { - "version": "1.5.7", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.7.tgz", - "integrity": "sha512-HxWkieX+STA38EDk7CE9MEryFeHCKzgagxlGvsdS7WBImq9Mk+PGwiT56w82WI3aicwJA8REp42Cxo98c8FZMA==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "requires": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" diff --git a/examples/react-todo-list/yarn.lock b/examples/react-todo-list/yarn.lock index c37c20da34d..48f3518fb26 100644 --- a/examples/react-todo-list/yarn.lock +++ b/examples/react-todo-list/yarn.lock @@ -10982,9 +10982,9 @@ url-loader@4.1.1: schema-utils "^3.0.0" url-parse@^1.4.3: - version "1.5.7" - resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.7.tgz#00780f60dbdae90181f51ed85fb24109422c932a" - integrity sha512-HxWkieX+STA38EDk7CE9MEryFeHCKzgagxlGvsdS7WBImq9Mk+PGwiT56w82WI3aicwJA8REp42Cxo98c8FZMA== + version "1.5.10" + resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1" + integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ== dependencies: querystringify "^2.1.1" requires-port "^1.0.0" diff --git a/examples/react-user-management/package-lock.json b/examples/react-user-management/package-lock.json index 1fe9db36890..375e76d3e5a 100644 --- a/examples/react-user-management/package-lock.json +++ b/examples/react-user-management/package-lock.json @@ -19486,9 +19486,9 @@ } }, "node_modules/url-parse": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", - "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dependencies": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" @@ -36791,9 +36791,9 @@ } }, "url-parse": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz", - "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "requires": { "querystringify": "^2.1.1", "requires-port": "^1.0.0" diff --git a/examples/vue3-ts-todo-list/package-lock.json b/examples/vue3-ts-todo-list/package-lock.json index 300434ce6f0..5dd1a045701 100644 --- a/examples/vue3-ts-todo-list/package-lock.json +++ b/examples/vue3-ts-todo-list/package-lock.json @@ -14207,9 +14207,9 @@ } }, "node_modules/url-parse": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", - "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dev": true, "dependencies": { "querystringify": "^2.1.1", @@ -27727,9 +27727,9 @@ } }, "url-parse": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz", - "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==", + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", "dev": true, "requires": { "querystringify": "^2.1.1", diff --git a/studio/components/interfaces/Authentication/Users/InviteUserModal.tsx b/studio/components/interfaces/Authentication/Users/InviteUserModal.tsx index f3d27cbd51b..23185950f55 100644 --- a/studio/components/interfaces/Authentication/Users/InviteUserModal.tsx +++ b/studio/components/interfaces/Authentication/Users/InviteUserModal.tsx @@ -72,29 +72,35 @@ const InviteUserModal = () => { size="small" key="invite-user-modal" visible={visible} - title="Invite new user" + header="Invite new user" hideFooter onCancel={handleToggle} closable > - } - autoFocus - // @ts-ignore - ref={inputRef} - value={emailValue} - onChange={onInputChange} - type="text" - name="email" - id="email" - placeholder="User email" - className="w-full" - /> - - +
+ + } + descriptionText="Type in the full email address" + autoFocus + // @ts-ignore + ref={inputRef} + value={emailValue} + onChange={onInputChange} + type="email" + name="email" + id="email" + placeholder="User email" + className="w-full" + /> + + + + +
) diff --git a/studio/components/interfaces/Authentication/Users/UserDropdown.tsx b/studio/components/interfaces/Authentication/Users/UserDropdown.tsx index 5a2e36dd827..48e333d73d6 100644 --- a/studio/components/interfaces/Authentication/Users/UserDropdown.tsx +++ b/studio/components/interfaces/Authentication/Users/UserDropdown.tsx @@ -115,6 +115,7 @@ const UserDropdown: FC<{ user: any }> = ({ user }) => { return ( {user.email !== null ? ( @@ -132,7 +133,7 @@ const UserDropdown: FC<{ user: any }> = ({ user }) => { Send OTP ) : null} - + }> Delete user diff --git a/studio/components/interfaces/Authentication/Users/Users.tsx b/studio/components/interfaces/Authentication/Users/Users.tsx index 3027d085782..d9463ff14c7 100644 --- a/studio/components/interfaces/Authentication/Users/Users.tsx +++ b/studio/components/interfaces/Authentication/Users/Users.tsx @@ -45,10 +45,10 @@ const Users = () => { return (
-
+
{ ]} />
-
- +
+ {inviteEnabled && }
-
+
diff --git a/studio/components/interfaces/Authentication/Users/UsersListItem.tsx b/studio/components/interfaces/Authentication/Users/UsersListItem.tsx index a504b94094e..471e226e733 100644 --- a/studio/components/interfaces/Authentication/Users/UsersListItem.tsx +++ b/studio/components/interfaces/Authentication/Users/UsersListItem.tsx @@ -1,7 +1,7 @@ import dayjs from 'dayjs' import { FC } from 'react' import { observer } from 'mobx-react-lite' -import { Badge, Typography } from '@supabase/ui' +import { Badge, IconUser, Typography } from '@supabase/ui' import Table from 'components/to-be-cleaned/Table' import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock' @@ -16,20 +16,22 @@ const UserListItem: FC = ({ user }) => { return ( - {!user.email ? '-' : user.email} +
+ {!user.email ? '-' : user.email} +
- {!user.phone ? '-' : user.phone} + {!user.phone ? '-' : user.phone} - + {user?.raw_app_meta_data?.provider || user?.app_metadata?.provider} - + - + {dayjs(user.created_at).format('DD MMM, YYYY HH:mm')} - + {!isUserConfirmed ? ( diff --git a/studio/components/interfaces/Authentication/Users/UsersPagination.tsx b/studio/components/interfaces/Authentication/Users/UsersPagination.tsx index 6d12f0974ea..51629e18e6b 100644 --- a/studio/components/interfaces/Authentication/Users/UsersPagination.tsx +++ b/studio/components/interfaces/Authentication/Users/UsersPagination.tsx @@ -18,13 +18,13 @@ const UsersPagination = () => { return (
} - /> + > + +

+ Please delete one of your free projects to downgrade this project back to free tier +

+
+
diff --git a/studio/components/interfaces/Database/Backups/BackupsList.tsx b/studio/components/interfaces/Database/Backups/BackupsList.tsx index 001e118c1ef..8ba7c4151bc 100644 --- a/studio/components/interfaces/Database/Backups/BackupsList.tsx +++ b/studio/components/interfaces/Database/Backups/BackupsList.tsx @@ -42,58 +42,50 @@ const BackupsList: FC = ({}) => { const { backups, tierId } = projectData const sortedBackups = backups.sort((a: any, b: any) => b.id - a.id) - return ( -
- {/*
- -
*/} - - {tierId === STRIPE_PRODUCT_IDS.FREE && ( -
-
-
- -
-
+ if (tierId === STRIPE_PRODUCT_IDS.FREE) { + return ( +
+
+
+ +
+
+
+

Free Plan does not include project backups.

- Free Plan does not include project backups. -
- - Please upgrade to Pro plan for up to 7 days of backups. - -
+

+ Please upgrade to Pro plan for up to 7 days of backups. +

- - -
+ + +
- )} +
+ ) + } + return ( +
{!sortedBackups.length && tierId !== STRIPE_PRODUCT_IDS.FREE ? (
- - No backups created yet. Check again tomorrow. - +

No backups created yet. Check again tomorrow.

) : ( - Date]} - > + {!sortedBackups && (
diff --git a/studio/components/interfaces/Database/Extensions/ExtensionCard.tsx b/studio/components/interfaces/Database/Extensions/ExtensionCard.tsx index 23f7e25f05d..83fde824d91 100644 --- a/studio/components/interfaces/Database/Extensions/ExtensionCard.tsx +++ b/studio/components/interfaces/Database/Extensions/ExtensionCard.tsx @@ -112,17 +112,17 @@ const ExtensionCard: FC = ({ extension }) => { flex flex-col h-full" >
- - {extension.comment} - +

+ {extension.comment} +

{isOn && extension.schema && (
- - +

+ Schema: {`${extension.schema}`} - +

)}
diff --git a/studio/components/interfaces/Database/Extensions/Extensions.tsx b/studio/components/interfaces/Database/Extensions/Extensions.tsx index 69aaad67ca0..d6d5584295b 100644 --- a/studio/components/interfaces/Database/Extensions/Extensions.tsx +++ b/studio/components/interfaces/Database/Extensions/Extensions.tsx @@ -26,7 +26,7 @@ const Extensions: FC = ({}) => {
setFilterString(e.target.value)} @@ -38,7 +38,7 @@ const Extensions: FC = ({}) => {
{enabledExtensions.length > 0 && (
- Enabled +

Enabled

{enabledExtensions.map((extension) => ( @@ -49,7 +49,7 @@ const Extensions: FC = ({}) => { {disabledExtensions.length > 0 && (
- Extensions +

Extensions

{disabledExtensions.map((extension) => ( diff --git a/studio/components/interfaces/Database/Functions/CreateFunction.tsx b/studio/components/interfaces/Database/Functions/CreateFunction.tsx index 4c54b15e5e2..07443b9bf8a 100644 --- a/studio/components/interfaces/Database/Functions/CreateFunction.tsx +++ b/studio/components/interfaces/Database/Functions/CreateFunction.tsx @@ -152,7 +152,7 @@ class CreateFunctionStore implements ICreateFunctionStore { get title() { return this.formState.id ? `Edit '${this.formState.originalName}' function` - : 'Add a new Function' + : 'Add a new function' } get isEditing() { @@ -350,13 +350,10 @@ const CreateFunction: FC = ({ func, visible, setVisible }) return ( <> setVisible(!visible)} - // @ts-ignore - contentStyle={{ padding: 0 }} - footerBackground={true} - title={_localState.title} + header={_localState.title} className="hooks-sidepanel transform transition-all duration-300 ease-in-out mr-0" loading={_localState.loading} onConfirm={handleSubmit} @@ -365,50 +362,73 @@ const CreateFunction: FC = ({ func, visible, setVisible })
{_localState.isEditing ? ( <> -
- - -
- -
+ +
+ + +
+
+ + + + + -
+ ) : (
- - - - - - - - - -
+ + + + + +
+ + +
+
+ + + + + + + + + + -
+
_localState.toggleAdvancedVisible()} label="Show advanced settings" + checked={_localState.advancedVisible} labelOptional="These are settings that might be familiar for postgres heavy users " />
{/* advanced selections */}
-
+
{_localState.advancedVisible && ( <> - {/* */} - - - - - - + +
+ + +
+
+ + + + + + + + )}
@@ -425,24 +445,22 @@ export default observer(CreateFunction) const InputName: FC = observer(({}) => { const _localState = useContext(CreateFunctionContext) return ( -
- - _localState!.onFormChange({ - key: 'name', - value: e.target.value, - }) - } - size="small" - error={_localState!.formState.name.error} - descriptionText="Name will also be used for the function name in postgres" - /> -
+ + _localState!.onFormChange({ + key: 'name', + value: e.target.value, + }) + } + size="small" + error={_localState!.formState.name.error} + descriptionText="Name will also be used for the function name in postgres" + /> ) }) @@ -462,18 +480,16 @@ const InputMultiArguments: FC = observer(({ readonly } } return ( -
+
- Arguments - +
Arguments
+

Arguments can be referenced in the function body using either names or numbers. - +

{readonly && isEmpty(_localState!.formState.args.value) && ( - - No argument for this function - + No argument for this function )} {_localState!.formState.args.value.map( (x: { name: string; type: string; error?: string }, idx: number) => ( @@ -489,7 +505,7 @@ const InputMultiArguments: FC = observer(({ readonly } )} {!readonly && (
-
@@ -538,7 +554,7 @@ const InputArgument: FC = observer(({ idx, name, type, error } return ( -
+
= observer(({ idx, name, type, error
@@ -658,7 +674,7 @@ const InputConfigParam: FC = observer(({ idx, name, value } return ( -
+
= observer(({ idx, name, value
diff --git a/studio/components/interfaces/Database/Tables/TableList.tsx b/studio/components/interfaces/Database/Tables/TableList.tsx index 3876f8b76de..8d6712672eb 100644 --- a/studio/components/interfaces/Database/Tables/TableList.tsx +++ b/studio/components/interfaces/Database/Tables/TableList.tsx @@ -13,7 +13,6 @@ import { import { useStore } from 'hooks' import Table from 'components/to-be-cleaned/Table' -import { confirmAlert } from 'components/to-be-cleaned/ModalsDeprecated/ConfirmModal' const Header: FC<{ filterString: string @@ -33,7 +32,7 @@ const Header: FC<{
{leftComponents}
setFilterString(e.target.value)} @@ -48,134 +47,109 @@ const Header: FC<{ const TableList: FC<{ onAddTable: () => void onEditTable: (table: any) => void + onDeleteTable: (table: any) => void onOpenTable: (table: any) => void -}> = observer(({ onAddTable = () => {}, onEditTable = () => {}, onOpenTable = () => {} }) => { - const { ui, meta } = useStore() - const [filterString, setFilterString] = useState('') - const tables = - filterString.length === 0 - ? meta.tables.list((table: any) => table.schema === 'public') - : meta.tables.list( - (table: any) => table.schema === 'public' && table.name.includes(filterString) - ) +}> = observer( + ({ + onAddTable = () => {}, + onEditTable = () => {}, + onDeleteTable = () => {}, + onOpenTable = () => {}, + }) => { + const { meta } = useStore() + const [filterString, setFilterString] = useState('') + const tables = + filterString.length === 0 + ? meta.tables.list((table: any) => table.schema === 'public') + : meta.tables.list( + (table: any) => table.schema === 'public' && table.name.includes(filterString) + ) - async function onDelete(table: any) { - confirmAlert({ - title: 'Confirm to delete', - message: `Are you sure you want to delete "${table.name}" table? This action cannot be undone.`, - onAsyncConfirm: async () => { - try { - const response: any = await meta.tables.del(table.id) - if (response.error) { - throw response.error - } else { - ui.setNotification({ - category: 'success', - message: `Successfully removed ${table.name}.`, - }) - } - } catch (error: any) { - ui.setNotification({ - category: 'error', - message: `Failed to delete ${table.name}: ${error.message}`, - }) - } - }, - }) + return ( + <> +
+ {/* @ts-ignore */} +
} onClick={() => onAddTable()}> + New + + } + /> +
+
+ Name, + Schema, + + Description + , + + Rows (Estimated) + , + + Size (Estimated) + , + , + ]} + body={tables.map((x: any, i: any) => ( + + + {x.name} + + + {x.schema} + + + {x.comment} + + + + {x.live_rows_estimate ?? x.live_row_count} + + + + + {x.size} + + + +
+ +
+
+
+ ))} + /> + + + ) } - - return ( - <> -
- {/* @ts-ignore */} -
} onClick={() => onAddTable()}> - New - - } - /> -
-
-
Name, - Schema, - - Description - , - - Rows (Estimated) - , - - Size (Estimated) - , - , - ]} - body={tables.map((x: any, i: any) => ( - - - {x.name} - - - {x.schema} - - - {x.comment} - - - - {x.live_rows_estimate ?? x.live_row_count} - - - - - {x.size} - - - -
- -
-
-
- ))} - /> - - - ) -}) +) export default TableList diff --git a/studio/components/interfaces/Database/Triggers/ChooseFunctionForm.tsx b/studio/components/interfaces/Database/Triggers/ChooseFunctionForm.tsx index f6b74870fc7..d5aaf807f6b 100644 --- a/studio/components/interfaces/Database/Triggers/ChooseFunctionForm.tsx +++ b/studio/components/interfaces/Database/Triggers/ChooseFunctionForm.tsx @@ -6,14 +6,15 @@ import { Transition } from '@headlessui/react' import { Button, IconChevronDown, + IconHelpCircle, IconTerminal, - IconTool, SidePanel, Typography, } from '@supabase/ui' import { Dictionary } from '@supabase/grid' import SqlEditor from 'components/to-be-cleaned/SqlEditor' import ProductEmptyState from 'components/to-be-cleaned/ProductEmptyState' +import InformationBox from 'components/ui/InformationBox' interface IChooseFunctionFormStore { functionSchemas: string[] @@ -55,16 +56,16 @@ const ChooseFunctionForm: React.FC = ({ return ( setVisible(!visible)} className="hooks-sidepanel" > {hasPublicSchemaFunctions ? ( -
+ {_localState.functionSchemas.map((schema: string) => ( ))} @@ -83,36 +84,22 @@ const NoticeBox: React.FC = ({}) => { const router = useRouter() const { ref } = router.query return ( -
-
-
-
- -
-
-
- - Only functions that return a trigger will be displayed below - -
- - You can make functions by using the Database Functions - -
-
- - -
-
-
+
+ } + title="Only functions that return a trigger will be displayed below" + description={`You can make functions by using the Database Functions`} + button={ + + } + />
) } @@ -130,9 +117,9 @@ const NoFunctionsState: React.FC = ({}) => { router.push(`/project/${ref}/database/functions`) }} > - +

You will need to create a trigger based function before you can add it to your trigger. - +

) } @@ -148,7 +135,7 @@ const SchemaFunctionGroup: React.FC = observer( const _functions = _pageState!.triggerFunctions.filter((x) => x.schema == schema) return (
-
+
schema @@ -186,8 +173,8 @@ const Function: React.FC = ({ id, completeStatement, name, onClic >
-
- +
+
{name} diff --git a/studio/components/interfaces/Database/Triggers/CreateTrigger.tsx b/studio/components/interfaces/Database/Triggers/CreateTrigger.tsx index 7a360c39481..09b9180799b 100644 --- a/studio/components/interfaces/Database/Triggers/CreateTrigger.tsx +++ b/studio/components/interfaces/Database/Triggers/CreateTrigger.tsx @@ -5,15 +5,14 @@ import { isEmpty, mapValues, has, without, union } from 'lodash' import { Input, SidePanel, - Divider, Checkbox, Listbox, Typography, IconPlayCircle, IconPauseCircle, IconTerminal, - IconTool, Badge, + Button, } from '@supabase/ui' import { Dictionary } from '@supabase/grid' import { useRouter } from 'next/router' @@ -325,13 +324,10 @@ const CreateTrigger: FC = ({ trigger, visible, setVisible }) return ( <> setVisible(!visible)} - // @ts-ignore - contentStyle={{ padding: 0 }} - footerBackground={true} - title={_localState.title} + header={_localState.title} hideFooter={!hasPublicTables} className={ _localState.chooseFunctionFormVisible @@ -342,9 +338,9 @@ const CreateTrigger: FC = ({ trigger, visible, setVisible }) onConfirm={handleSubmit} > {hasPublicTables ? ( -
+
-
+
{_localState.isEditing ? (
@@ -355,7 +351,7 @@ const CreateTrigger: FC = ({ trigger, visible, setVisible })
- +
Conditions to fire trigger @@ -363,7 +359,7 @@ const CreateTrigger: FC = ({ trigger, visible, setVisible })
- + )} @@ -397,9 +393,9 @@ const NoTableState: FC = ({}) => { router.push(`/project/${ref}/database/tables`) }} > - +

You will need to create a table first before you can make a trigger - +

) } @@ -448,52 +444,54 @@ const SelectEnabledMode: FC = observer(({}) => { { return ( -
+
) }} value="ORIGIN" label="Origin" > Origin - This is a default behaviour + This is a default behaviour
{ return ( -
+
) }} value="REPLICA" label="Replica" > Replica - Will only fire if the session is in “replica” mode + + Will only fire if the session is in “replica” mode +
{ return ( -
+
) }} value="ALWAYS" label="Always" > Always - + Will fire regardless of the current replication role
{ return ( -
+
) }} value="DISABLED" label="Disabled" > Disabled - Will not fire + Will not fire
) @@ -518,11 +516,11 @@ const SelectOrientation: FC = observer(({}) => { > Row - fires once for each processed row + fires once for each processed row Statement - fires once for each statement + fires once for each statement ) @@ -566,7 +564,7 @@ const ListboxTable: FC = observer(({}) => { value={x.id} label={x.name} addOnBefore={() => ( -
+
{ >
{x.name} - - + {x.schema}
@@ -617,19 +614,19 @@ const CheckboxEvents: FC = observer(({}) => { > @@ -660,14 +657,16 @@ const ListboxActivation: FC = observer(({}) => { value={'BEFORE'} label={'Before the event'} addOnBefore={() => ( -
+
)} >
{'before'} - Trigger fires before the operation is attempted + + Trigger fires before the operation is attempted +
{ value={'AFTER'} label={'After the event'} addOnBefore={() => ( -
+
)} >
{'after'} - Trigger fires after the operation has completed + + Trigger fires after the operation has completed +
@@ -714,19 +715,23 @@ const FunctionEmpty: FC = observer(({}) => { ) }) @@ -736,34 +741,38 @@ const FunctionWithArguments: FC = observer(({}) => { return ( <> - + +
) }) diff --git a/studio/components/interfaces/Home/ClientLibrary.tsx b/studio/components/interfaces/Home/ClientLibrary.tsx index fa85781ee08..00c357279d1 100644 --- a/studio/components/interfaces/Home/ClientLibrary.tsx +++ b/studio/components/interfaces/Home/ClientLibrary.tsx @@ -19,25 +19,25 @@ const ClientLibrary: FC = ({ language, releaseState, officialSupport, doc />
- +
{language} {releaseState && {`Public ${releaseState}`}} - - +
+

{officialSupport ? 'This library is officially supported' : 'This library is community supported'} - +

{docsUrl && ( - )} - diff --git a/studio/components/interfaces/Home/ExampleProject.tsx b/studio/components/interfaces/Home/ExampleProject.tsx index 88f5c64f6e3..39227ed3c47 100644 --- a/studio/components/interfaces/Home/ExampleProject.tsx +++ b/studio/components/interfaces/Home/ExampleProject.tsx @@ -13,26 +13,40 @@ const ExampleProject: FC = ({ framework, title, description, url }) => { -
- - Documentation - - + - - + Documentation + +
- - Give your database superpowers - - -

- Create a backend in less than 2 minutes. Start your project with a Postgres Database, - Authentication, instant APIs, and realtime subscriptions. -

-
-
- - diff --git a/studio/components/interfaces/Home/NewProjectPanel/GetStartedPanel.tsx b/studio/components/interfaces/Home/NewProjectPanel/GetStartedPanel.tsx index c5e6ca9c722..30dfe8bd910 100644 --- a/studio/components/interfaces/Home/NewProjectPanel/GetStartedPanel.tsx +++ b/studio/components/interfaces/Home/NewProjectPanel/GetStartedPanel.tsx @@ -15,17 +15,11 @@ const GetStartedPanel: FC = ({}) => {
- - Welcome to your new project - -
- -

- Your project has been deployed on its own instance, with its own url and API all - set up and ready to use. -

-
-
+

Welcome to your new project

+

+ Your project has been deployed on its own instance, with its own url and API all set + up and ready to use. +

@@ -34,17 +28,15 @@ const GetStartedPanel: FC = ({}) => {
- - - - - Database - +
+ +
+
Database
- +

Supabase is built on top of Postgres, an extremely scalable Relational Database. - +

@@ -67,17 +59,15 @@ const GetStartedPanel: FC = ({}) => {
- - - - - Auth - +
+ +
+
Auth
- +

Supabase makes it simple to manage your users. - +

@@ -95,17 +85,15 @@ const GetStartedPanel: FC = ({}) => {
- - - - - Storage - +
+ +
+
Storage
- +

Store and serve large files from multiple buckets. - +

diff --git a/studio/components/interfaces/Home/NewProjectPanel/NewProjectPanel.tsx b/studio/components/interfaces/Home/NewProjectPanel/NewProjectPanel.tsx index 9703431f573..fba67f8d2b9 100644 --- a/studio/components/interfaces/Home/NewProjectPanel/NewProjectPanel.tsx +++ b/studio/components/interfaces/Home/NewProjectPanel/NewProjectPanel.tsx @@ -16,17 +16,11 @@ const NewProjectPanel: FC = ({}) => (
- - Connecting to your new project - -
- -

- Your project has API keys for interacting with the database via Supabase client - libraries. -

-
-
+

Connecting to your new project

+ +

+ Your project has API keys for interacting with the database via Supabase client libraries. +

diff --git a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx index 84f69521a0d..e3c627806f4 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectCard.tsx @@ -1,34 +1,71 @@ import { FC } from 'react' -import Link from 'next/link' -import { Typography } from '@supabase/ui' + +import { Badge, Button, IconClock } from '@supabase/ui' import { Project } from 'types' +import CardButton from 'components/ui/CardButton' interface Props { project: Project + paused: boolean + onSelectRestore?: () => void + onSelectDelete?: () => void rewriteHref?: string } -const ProjectCard: FC = ({ project, rewriteHref }) => { +const ProjectCard: FC = ({ + project, + paused, + onSelectRestore, + onSelectDelete, + rewriteHref, +}) => { const { name, ref: projectRef, status } = project const desc = `${project.cloud_provider} | ${project.region}` + return ( -
  • - - -
    - {name} -
    - {desc} -
    +
  • + + {name} + {paused && ( + +
    + + Project paused +
    +
    + )}
  • - - + } + description={''} + footer={ +
    + {desc} + {paused && ( +
    +
    + + +
    +
    + )} +
    + } + > ) } diff --git a/studio/components/interfaces/Home/ProjectList/ProjectList.tsx b/studio/components/interfaces/Home/ProjectList/ProjectList.tsx index 57a07fd1315..540e544fa99 100644 --- a/studio/components/interfaces/Home/ProjectList/ProjectList.tsx +++ b/studio/components/interfaces/Home/ProjectList/ProjectList.tsx @@ -13,14 +13,12 @@ import PausedProjectCard from './PausedProjectCard' interface Props { onSelectRestore: (project: Project) => void onSelectDelete: (project: Project) => void - showInactiveProjects?: boolean rewriteHref?: (projectRef: string) => string } const ProjectList: FC = ({ onSelectRestore = () => {}, onSelectDelete = () => {}, - showInactiveProjects = true, rewriteHref, }) => { const router = useRouter() @@ -39,13 +37,17 @@ const ProjectList: FC = ({ return (
    - {name} +

    {name}

    -
      +
        {!isLoading && isEmpty && (
        - No projects. - Get started by creating a new project. +
        +

        No projects

        +

        + Get started by creating a new project. +

        +
        )} - {sortedProjects?.map((project: Project) => - project.status === PROJECT_STATUS.INACTIVE ? ( - showInactiveProjects && ( - onSelectDelete(project)} - onSelectRestore={() => onSelectRestore(project)} - /> - ) - ) : ( - - ) - )} + {sortedProjects?.map((project: Project) => ( + onSelectDelete(project)} + onSelectRestore={() => onSelectRestore(project)} + /> + ))}
    ) diff --git a/studio/components/interfaces/Home/ProjectUsage.tsx b/studio/components/interfaces/Home/ProjectUsage.tsx index 559e8ac45f5..e381e0f631f 100644 --- a/studio/components/interfaces/Home/ProjectUsage.tsx +++ b/studio/components/interfaces/Home/ProjectUsage.tsx @@ -84,37 +84,42 @@ const ProjectUsage: FC = ({ project }) => { } return (
    -
    - Statistics for past {selectedInterval.label} - +
    {logsUsageChartIntervals && ( {CHART_INTERVALS.map((i) => ( - {i.label} + {i.label} ))} } > - )} + + Statistics for past {selectedInterval.label} +
    {startDate && endDate && ( <> -
    +
    } + icon={ +
    + +
    + } title="Database" href={`/project/${ref}/editor`} /> @@ -129,7 +134,6 @@ const ProjectUsage: FC = ({ project }) => { customDateFormat={datetimeFormat} data={charts} isLoading={!charts && !error ? true : false} - onBarClick={(v) => handleBarClick(v, '/rest')} /> = ({ project }) => { } + icon={ +
    + +
    + } title="Auth" href={`/project/${ref}/auth/users`} /> @@ -156,7 +164,6 @@ const ProjectUsage: FC = ({ project }) => { customDateFormat={datetimeFormat} data={charts} isLoading={!charts && !error ? true : false} - onBarClick={(v) => handleBarClick(v, '/auth')} /> = ({ project }) => { />
    +
    +
    } + icon={ +
    + +
    + } title="Storage" href={`/project/${ref}/storage/buckets`} /> @@ -183,7 +196,6 @@ const ProjectUsage: FC = ({ project }) => { customDateFormat={datetimeFormat} data={charts} isLoading={!charts && !error ? true : false} - onBarClick={(v) => handleBarClick(v, '/storage')} /> = ({ project }) => {
    - } title="Realtime" /> + + +
    + } + title="Realtime" + /> = ({ project }) => { isLoading={!charts && !error ? true : false} onBarClick={(v) => handleBarClick(v, '/realtime')} /> - {/* Empty space just so the cards are of the same height */} -
    - {/* */} +
    - {logsUsageCodesPaths && ( -
    - - - - - - - - - -
    - Path - Count - Avg. Latency (ms) - - } - body={ - <> - {(pathsData?.result ?? []).map((row: PathsDatum) => ( - - -
    - - {row.method} - -
    - - {row.path} - -
    - {row.count} - {Number(row.avg_origin_time).toFixed(2)} -
    - ))} - - } - /> - - - - )} )} + {logsUsageCodesPaths && ( +
    + + + + + + + + + +
    + Path + Count + Avg. Latency (ms) + + } + body={ + <> + {(pathsData?.result ?? []).map((row: PathsDatum) => ( + + +

    {row.method}

    +

    {row.path}

    +
    + {row.count} + {Number(row.avg_origin_time).toFixed(2)} +
    + ))} + + } + /> + + + + )} ) @@ -304,15 +311,13 @@ const PanelHeader = (props: any) => {
    {props.icon} - - {props.title} - +

    {props.title}

    diff --git a/studio/components/interfaces/Organization/BillingSettings/AWSMarketplaceSubscription.tsx b/studio/components/interfaces/Organization/BillingSettings/AWSMarketplaceSubscription.tsx new file mode 100644 index 00000000000..5a7acdac437 --- /dev/null +++ b/studio/components/interfaces/Organization/BillingSettings/AWSMarketplaceSubscription.tsx @@ -0,0 +1,29 @@ +import { FC } from 'react' +import { Button, Typography } from '@supabase/ui' + +interface Props { + organization: any +} + +const AWSMarketplaceSubscription: FC = ({ organization }) => { + return ( +
    + + This organization is subscribed via AWS Marketplace, with a limit of{' '} + {organization.project_limit} projects and expiring on{' '} + {organization.aws_marketplace.Entitlements[0].ExpirationDate.slice(0, 10)}. + + + +
    + ) +} + +export default AWSMarketplaceSubscription diff --git a/studio/components/interfaces/Organization/BillingSettings/BillingAddress.tsx b/studio/components/interfaces/Organization/BillingSettings/BillingAddress.tsx new file mode 100644 index 00000000000..f8f5df52373 --- /dev/null +++ b/studio/components/interfaces/Organization/BillingSettings/BillingAddress.tsx @@ -0,0 +1,49 @@ +import { FC } from 'react' +import { Dictionary } from '@supabase/grid' +import { Input, Button } from '@supabase/ui' +import Panel from 'components/to-be-cleaned/Panel' + +interface Props { + loading: boolean + address: Dictionary + redirectToPortal: (url: string) => void +} + +const BillingAddress: FC = ({ loading, address, redirectToPortal }) => { + const { city, country, line1, line2, postal_code, state } = address + return ( +
    +
    +

    Billing address

    +

    This will be reflected in every invoice

    +
    + + +
    + } + > + + + +
    + + +
    + {city && } +
    + + + ) +} + +export default BillingAddress diff --git a/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx b/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx index ef35b1fd14d..0fe97138558 100644 --- a/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx +++ b/studio/components/interfaces/Organization/BillingSettings/BillingSettings.tsx @@ -1,24 +1,17 @@ -import Link from 'next/link' import { useRouter } from 'next/router' import { FC, useState, useEffect } from 'react' -import { - Typography, - Badge, - Button, - IconTrash, - IconCreditCard, - Input, - IconPlus, - IconEdit2, - IconLoader, -} from '@supabase/ui' import { useStore } from 'hooks' import { API_URL } from 'lib/constants' import { getURL } from 'lib/helpers' import { post } from 'lib/common/fetch' -import Panel from 'components/to-be-cleaned/Panel' -import ProjectSubscription from './ProjectSubscription' + +import AWSMarketplaceSubscription from './AWSMarketplaceSubscription' +import ProjectsSummary from './ProjectsSummary' +import CreditBalance from './CreditBalance' +import PaymentMethods from './PaymentMethods' +import BillingAddress from './BillingAddress' +import TaxID from './TaxID' interface Props { organization: any @@ -35,7 +28,7 @@ const BillingSettings: FC = ({ organization, projects = [] }) => { const [taxIds, setTaxIds] = useState(null) const [paymentMethods, setPaymentMethods] = useState(null) - const { stripe_customer_id, stripe_customer_object, name: orgName, slug: orgSlug } = organization + const { stripe_customer_id, stripe_customer_object } = organization const customerBalance = customer && customer.balance ? customer.balance / 100 : 0 const isCredit = customerBalance < 0 @@ -45,8 +38,6 @@ const BillingSettings: FC = ({ organization, projects = [] }) => { ? customerBalance.toString().replace('-', '') : customerBalance - const { city, country, line1, line2, postal_code, state } = stripe_customer_object?.address ?? {} - useEffect(() => { if (stripe_customer_id) { getStripeAccount() @@ -109,198 +100,27 @@ const BillingSettings: FC = ({ organization, projects = [] }) => { return (
    {organization.aws_marketplace ? ( -
    - - This organization is subscribed via AWS Marketplace, with a limit of{' '} - {organization.project_limit} projects and expiring on{' '} - {organization.aws_marketplace.Entitlements[0].ExpirationDate.slice(0, 10)}. - - - -
    + ) : ( <> -
    -
    - -
    - Billing Information - - These will be reflected in all invoices - -
    - -
    - } - > - - - {city && } - - - - - -
    -
    - Tax IDs -
    -
    - {!taxIds ? ( -
    - - Retrieving tax information... -
    - ) : (taxIds?.data ?? []).length === 0 ? ( - None - ) : ( - <> - {taxIds.data.map((taxId: any) => ( - - ))} - - )} -
    -
    -
    - -
    -
    - Balance} - > - - {isCredit && You have credits available} - {isDebt && Outstanding payments} - - - - $ - - - {balance} - - {isCredit && / credits} - - - - - - Payment methods - -
    - } - > - - {paymentMethods && paymentMethods.data.length >= 1 ? ( -
    - {paymentMethods.data.map((paymentMethod: any) => { - return ( -
    -
    - - - {paymentMethod.card.brand}{' '} - ending in{' '} - {paymentMethod.card.last4} - -
    -
    - - - {paymentMethod.card.exp_month} - - / - - {paymentMethod.card.exp_year} - - -
    - -
    - ) - })} -
    - ) : ( -
    -
    - -
    - -
    -
    No payment methods
    -
    -
    -
    - )} -
    -
    - - - -
    - {projects.length === 0 ? ( -
    - - You have no projects in organization {orgName} yet - -
    - - - -
    -
    - ) : ( - Projects in organization - )} -
    - {projects.map((project) => { - return - })} -
    +
    + + + + +
    )} diff --git a/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx b/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx new file mode 100644 index 00000000000..a973f47ad6c --- /dev/null +++ b/studio/components/interfaces/Organization/BillingSettings/CreditBalance.tsx @@ -0,0 +1,31 @@ +import { FC } from 'react' +import { Badge } from '@supabase/ui' + +interface Props { + balance: string | number + isCredit: boolean + isDebt: boolean +} + +const CreditBalance: FC = ({ balance, isCredit, isDebt }) => { + return ( +
    +
    +
    +

    Credit balance

    + {isCredit && You have credits available} + {isDebt && Outstanding payments} +
    +

    Charges will be deducted from your balance first

    +
    +
    + {isDebt &&

    -

    } +

    $

    +

    {balance}

    + {isCredit &&

    /credits

    } +
    +
    + ) +} + +export default CreditBalance diff --git a/studio/components/interfaces/Organization/BillingSettings/PaymentMethods.tsx b/studio/components/interfaces/Organization/BillingSettings/PaymentMethods.tsx new file mode 100644 index 00000000000..7f5016204e6 --- /dev/null +++ b/studio/components/interfaces/Organization/BillingSettings/PaymentMethods.tsx @@ -0,0 +1,81 @@ +import { FC } from 'react' +import Panel from 'components/to-be-cleaned/Panel' +import { IconPlus, IconEdit2, IconCreditCard, Button, Input, IconLoader } from '@supabase/ui' + +interface Props { + loading: boolean + paymentMethods: any[] + redirectToPortal: (url: string) => void +} + +const PaymentMethods: FC = ({ loading, paymentMethods, redirectToPortal }) => { + return ( +
    +
    +

    Payment methods

    +

    Charges will be deducted from the default card

    +
    + + +
    + } + > + + {loading && paymentMethods.length === 0 ? ( +
    + +

    Retrieving payment methods

    +
    + ) : paymentMethods.length >= 1 ? ( +
    + {paymentMethods.map((paymentMethod: any) => ( +
    +
    + + +

    + {paymentMethod.card.exp_month}/{paymentMethod.card.exp_year} +

    +
    + +
    + ))} +
    + ) : ( +
    +

    +

    + +
    +
    No payment methods
    +

    +
    + )} +
    + +
    + ) +} + +export default PaymentMethods diff --git a/studio/components/interfaces/Organization/BillingSettings/ProjectSubscription.tsx b/studio/components/interfaces/Organization/BillingSettings/ProjectSubscription.tsx deleted file mode 100644 index c6c7d604b37..00000000000 --- a/studio/components/interfaces/Organization/BillingSettings/ProjectSubscription.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import { FC, useState, useEffect } from 'react' -import dayjs from 'dayjs' -import { get as _get, maxBy } from 'lodash' -import utc from 'dayjs/plugin/utc' - -import { useStore, useSubscriptionStats } from 'hooks' -import { API_URL } from 'lib/constants' -import { Dictionary } from '@supabase/grid' -import { get, post } from 'lib/common/fetch' -import { DATE_FORMAT, STRIPE_PRODUCT_IDS } from 'lib/constants' -import { Subscription, StripeSubscription, chargeableProducts } from 'components/interfaces/Billing' - -dayjs.extend(utc) - -interface Props { - project: any -} - -const ProjectSubscription: FC = ({ project }) => { - const { ui } = useStore() - const subscriptionStats = useSubscriptionStats() - - const [loading, setLoading] = useState(true) - const [subscription, setSubscription] = useState() - const [paygStats, setPaygStats] = useState>() - - useEffect(() => { - getSubscription() - }, []) - - const getSubscription = async () => { - try { - setLoading(true) - const { data: subscription, error }: { data: StripeSubscription; error: any } = await post( - `${API_URL}/stripe/subscription`, - { - subscription_id: project.subscription_id, - } - ) - if (error) throw error - setSubscription(subscription) - - if (subscription.tier.prod_id === STRIPE_PRODUCT_IDS.PAYG) { - fetchPaygStatistics() - } - } catch (error: any) { - ui.setNotification({ - category: 'error', - message: `Failed to get project subscription: ${error.message}`, - }) - } finally { - setLoading(false) - } - } - - const fetchPaygStatistics = async () => { - const startDate = dayjs().utc().startOf('month').format(DATE_FORMAT) - const endDate = dayjs().utc().endOf('month').format(DATE_FORMAT) - const attributes = - 'total_db_size_bytes,total_db_egress_bytes,total_storage_size_bytes,total_storage_egress' - const url = `${API_URL}/projects/${ - project.ref - }/daily-stats?attribute=${attributes}&startDate=${encodeURIComponent( - startDate - )}&endDate=${encodeURIComponent(endDate)}&interval='1d'` - - const { data } = await get(url) - - const paygStats: any = {} - chargeableProducts.forEach((product: any) => { - product.features.forEach((feature: any) => { - paygStats[feature.attribute] = _get(maxBy(data, feature.attribute), feature.attribute) - }) - }) - setPaygStats(paygStats) - } - - return ( - - ) -} - -export default ProjectSubscription diff --git a/studio/components/interfaces/Organization/BillingSettings/ProjectsSummary.tsx b/studio/components/interfaces/Organization/BillingSettings/ProjectsSummary.tsx new file mode 100644 index 00000000000..1d2c2682435 --- /dev/null +++ b/studio/components/interfaces/Organization/BillingSettings/ProjectsSummary.tsx @@ -0,0 +1,116 @@ +import Link from 'next/link' +import dayjs from 'dayjs' +import utc from 'dayjs/plugin/utc' +import { FC, useState, useEffect } from 'react' +import { IconChevronRight, IconLoader } from '@supabase/ui' + +import { useStore } from 'hooks' +import { API_URL } from 'lib/constants' +import { post } from 'lib/common/fetch' +import Panel from 'components/to-be-cleaned/Panel' +import { StripeSubscription } from 'components/interfaces/Billing' + +dayjs.extend(utc) + +interface ProjectSummaryProps { + project: any +} + +const ProjectSummary: FC = ({ project }) => { + const { ui } = useStore() + + const [loading, setLoading] = useState(false) + const [subscription, setSubscription] = useState() + + const currentPeriodStart = subscription?.billing?.current_period_start ?? 0 + const currentPeriodEnd = subscription?.billing?.current_period_end ?? 0 + + useEffect(() => { + getSubscription() + }, []) + + const getSubscription = async () => { + try { + setLoading(true) + const { data: subscription, error }: { data: StripeSubscription; error: any } = await post( + `${API_URL}/stripe/subscription`, + { subscription_id: project.subscription_id } + ) + if (error) throw error + setSubscription(subscription) + } catch (error: any) { + ui.setNotification({ + category: 'error', + message: `Failed to get project subscription: ${error.message}`, + }) + } finally { + setLoading(false) + } + } + + return ( +
    +
    +

    {project.name}

    +
    + {loading ? ( +
    + +
    + ) : ( + <> +
    +

    {subscription?.tier.name ?? ''}

    +
    +
    +

    {dayjs.unix(currentPeriodStart).utc().format('MMM D, YYYY')}

    +

    -

    +

    {dayjs.unix(currentPeriodEnd).utc().format('MMM D, YYYY')}

    +
    + + + )} +
    + ) +} + +interface ProjectsSummaryProps { + projects: any +} + +const ProjectsSummary: FC = ({ projects }) => { + return ( +
    +

    Projects at a glance

    + +
    +

    Name

    +
    +
    +

    Plan

    +
    +
    +

    Billing cycle

    +
    +
    +
    + } + > + {projects.map((project: any) => ( + + ))} +
    +
    + ) +} + +export default ProjectsSummary diff --git a/studio/components/interfaces/Organization/BillingSettings/TaxID.tsx b/studio/components/interfaces/Organization/BillingSettings/TaxID.tsx new file mode 100644 index 00000000000..ecd4fbe3a5c --- /dev/null +++ b/studio/components/interfaces/Organization/BillingSettings/TaxID.tsx @@ -0,0 +1,59 @@ +import { FC } from 'react' +import { Input, Button, IconLoader } from '@supabase/ui' +import Panel from 'components/to-be-cleaned/Panel' + +interface Props { + loading: boolean + taxIds: any[] + redirectToPortal: (url: string) => void +} + +const TaxID: FC = ({ loading, taxIds, redirectToPortal }) => { + return ( +
    +
    +

    Tax ID

    +

    + If you would like to include specific tax ID(s) to your invoices +

    +
    + + +
    + } + > + + {loading && taxIds.length === 0 ? ( +
    + +

    Retrieving tax information

    +
    + ) : taxIds.length >= 1 ? ( +
    + {taxIds.map((taxId: any) => { + const taxIdType = taxId.type.replace('_', ' ').toUpperCase() + return ( +
    +

    {taxIdType}

    + +
    + ) + })} +
    + ) : ( +
    +

    No tax IDs

    +
    + )} +
    + + + ) +} + +export default TaxID diff --git a/studio/components/interfaces/Organization/InvoicesSettings/InvoicesSettings.tsx b/studio/components/interfaces/Organization/InvoicesSettings/InvoicesSettings.tsx index c0990bad66f..6f37fc272c7 100644 --- a/studio/components/interfaces/Organization/InvoicesSettings/InvoicesSettings.tsx +++ b/studio/components/interfaces/Organization/InvoicesSettings/InvoicesSettings.tsx @@ -130,20 +130,20 @@ const InvoicesSettings: FC = ({ organization }) => {
    - +

    Showing {offset + 1} to {offset + invoices.length} out of {count} invoices - +

    {!isCustomQuery && (
    - - Show event chart - +

    Show event chart

    )} @@ -218,7 +216,7 @@ const LogPanel: FC = ({ /> ), - , ]} diff --git a/studio/components/interfaces/Settings/Logs/LogTable.tsx b/studio/components/interfaces/Settings/Logs/LogTable.tsx index 4f61637ed34..9036e383825 100644 --- a/studio/components/interfaces/Settings/Logs/LogTable.tsx +++ b/studio/components/interfaces/Settings/Logs/LogTable.tsx @@ -45,7 +45,7 @@ const LogTable = ({ isCustomQuery, data = [] }: Props) => { className={[ 'block whitespace-wrap font-mono', `${hasLogDataFormat && row.id === focusedLog?.id ? 'font-bold' : ''}`, - `${hasLogDataFormat && v === 'timestamp' ? 'text-green-500' : ''}`, + `${hasLogDataFormat && v === 'timestamp' ? 'text-green-900' : ''}`, ].join(' ')} > {value} diff --git a/studio/components/interfaces/TableGridEditor/EmptyState.tsx b/studio/components/interfaces/TableGridEditor/EmptyState.tsx index a39629a8329..c245ca3d7f3 100644 --- a/studio/components/interfaces/TableGridEditor/EmptyState.tsx +++ b/studio/components/interfaces/TableGridEditor/EmptyState.tsx @@ -1,5 +1,5 @@ import { FC } from 'react' -import { Button, Typography } from '@supabase/ui' +import { Button } from '@supabase/ui' import { PostgresTable } from '@supabase/postgres-meta' import { useStore } from 'hooks' @@ -15,7 +15,7 @@ const EmptyState: FC = ({ selectedSchema, onAddTable }) => { const renderNoTablesCTA = () => { return (
    - There are no tables available in this schema +

    There are no tables available in this schema

    {selectedSchema === 'public' && }
    ) @@ -27,7 +27,7 @@ const EmptyState: FC = ({ selectedSchema, onAddTable }) => { renderNoTablesCTA() ) : (
    - Select a table or create a new one +

    Select a table or create a new one

    )} diff --git a/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx b/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx index 4cb7e7ba1a8..96f1d3a6311 100644 --- a/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx +++ b/studio/components/interfaces/TableGridEditor/GridHeaderActions.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' import Link from 'next/link' -import { Button, IconAlertCircle, Typography } from '@supabase/ui' +import { Badge, Button, IconAlertCircle, Typography } from '@supabase/ui' import { PostgresTable } from '@supabase/postgres-meta' import { useStore } from 'hooks' @@ -18,20 +18,9 @@ const GridHeaderActions: FC = ({ table }) => {
    {!table.rls_enabled && ( -
    -
    - - RLS not enabled -
    -
    + )} {/* {children} {applyFunction && ( diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx index 0ce938a7653..6b8b9e08842 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnEditor.tsx @@ -128,13 +128,14 @@ const ColumnEditor: FC = ({ return ( } - onCancel={closePanel} onConfirm={(resolve: () => void) => onSaveChanges(resolve)} + // @ts-ignore + header={} + onCancel={closePanel} customFooter={ = ({ /> } > - - onUpdateField({ name: event.target.value })} - /> - onUpdateField({ comment: event.target.value })} - /> - {isNewRecord && !hasPrimaryKey && ( -
    -
    - onUpdateField({ isPrimaryKey: !columnFields?.isPrimaryKey })} - /> -
    -
    - )} - setIsEditingRelation(true)} - onSelectRemoveRelation={() => onUpdateField({ foreignKey: undefined })} - /> - - onUpdateField({ format, defaultValue: '' })} - /> - {isUndefined(columnFields.foreignKey) && ( -
    - {columnFields.format.includes('int') && ( -
    - { - const isIdentity = !columnFields.isIdentity - const isArray = isIdentity ? false : columnFields.isArray - onUpdateField({ isIdentity, isArray }) - }} - /> -
    - )} - {!columnFields.isPrimaryKey && ( -
    - { - const isArray = !columnFields.isArray - const isIdentity = isArray ? false : columnFields.isIdentity - onUpdateField({ isArray, isIdentity }) - }} - /> -
    - )} -
    - )} - - {!columnFields.isPrimaryKey && ( - <> + +
    + onUpdateField({ name: event.target.value })} + /> + onUpdateField({ comment: event.target.value })} + /> +
    +
    + + + + +
    + {isNewRecord && !hasPrimaryKey && (
    onUpdateField({ isNullable: !columnFields.isNullable })} - /> -
    -
    - onUpdateField({ isUnique: !columnFields.isUnique })} + label={hasPrimaryKey ? 'Add to composite primary key' : 'Is Primary Key'} + description="A primary key indicates that a column or group of columns can be used as a unique identifier for rows in the table." + checked={columnFields?.isPrimaryKey ?? false} + onChange={() => onUpdateField({ isPrimaryKey: !columnFields?.isPrimaryKey })} />
    - - )} - - setIsEditingRelation(false)} - saveChanges={saveColumnForeignKey} - /> + )} + setIsEditingRelation(true)} + onSelectRemoveRelation={() => onUpdateField({ foreignKey: undefined })} + /> +
    +
    + + +
    + onUpdateField({ format, defaultValue: '' })} + /> + {isUndefined(columnFields.foreignKey) && ( +
    + {columnFields.format.includes('int') && ( +
    + { + const isIdentity = !columnFields.isIdentity + const isArray = isIdentity ? false : columnFields.isArray + onUpdateField({ isIdentity, isArray }) + }} + /> +
    + )} + {!columnFields.isPrimaryKey && ( +
    + { + const isArray = !columnFields.isArray + const isIdentity = isArray ? false : columnFields.isIdentity + onUpdateField({ isArray, isIdentity }) + }} + /> +
    + )} +
    + )} + + {!columnFields.isPrimaryKey && ( + <> +
    +
    + onUpdateField({ isNullable: !columnFields.isNullable })} + /> +
    +
    + onUpdateField({ isUnique: !columnFields.isUnique })} + /> +
    +
    + + )} + + setIsEditingRelation(false)} + saveChanges={saveColumnForeignKey} + /> +
    +
    ) } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx index e36cfe5186f..6bf4daddea2 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnForeignKey.tsx @@ -23,11 +23,9 @@ const ColumnForeignKey: FC = ({ const hasNoForeignKey = isUndefined(originalForeignKey) && isUndefined(column?.foreignKey) if (hasNoForeignKey) { return ( -
    - -
    + ) } @@ -90,18 +88,16 @@ const ColumnForeignKeyInformation: FC<{ block icon={} title={ -
    +
    - This column has the following foreign key relation: + This column has the following foreign key relation:
    - - {columnName} - + {columnName} - + {foreignKey?.target_table_schema}.{foreignKey?.target_table_name}. {foreignKey?.target_column_name} - +
    @@ -129,21 +125,19 @@ const ColumnForeignKeyAdded: FC<{ block icon={} title={ -
    +
    - + The following foreign key relation will be{' '} - added: - -
    - - {columnName} - + added: + +
    + {columnName} - + {foreignKey?.target_table_schema}.{foreignKey?.target_table_name}. {foreignKey?.target_column_name} - +
    @@ -174,7 +168,7 @@ const ColumnForeignKeyRemoved: FC<{
    The following foreign key relation will be{' '} - removed from this column: + removed from this column:
    diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx index 4918a3d3b2c..601dd0a745a 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/ColumnType.tsx @@ -34,19 +34,19 @@ const ColumnType: FC = ({ const inferIcon = (type: string) => { switch (type) { case 'number': - return + return case 'time': - return + return case 'text': - return + return case 'json': return ( -
    +
    {'{ }'}
    ) case 'bool': - return + return default: return
    } @@ -61,6 +61,7 @@ const ColumnType: FC = ({ error={error} className={`${className} ${disabled ? 'column-type-disabled' : ''}`} onChange={(value: string) => onOptionSelect(value)} + optionsWidth={480} > --- @@ -77,7 +78,7 @@ const ColumnType: FC = ({ value={enumType.name} label={enumType.name} addOnBefore={() => { - return
    + return
    }} >
    @@ -113,14 +114,8 @@ const ColumnType: FC = ({ addOnBefore={() => inferIcon(option.type)} >
    - -

    {option.name}

    -
    -

    - - {option.description} - -

    + {option.name} + {option.description}
    ))} diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/InputWithSuggestions.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/InputWithSuggestions.tsx index 6437ecfbaa8..8e97f235a4e 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/InputWithSuggestions.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ColumnEditor/InputWithSuggestions.tsx @@ -6,9 +6,8 @@ // the component over to the UI library import { FC, useEffect, useRef, useState } from 'react' -import { Input, Typography } from '@supabase/ui' +import { Button, Dropdown, IconList, Input } from '@supabase/ui' import { Suggestion } from './ColumnEditor.types' -import { timeout } from 'lib/helpers' const MAX_SUGGESTIONS = 3 const DEFAULT_SUGGESTIONS_WIDTH = 400 @@ -45,21 +44,8 @@ const InputWithSuggestions: FC = ({ onSelectSuggestion = () => {}, }) => { const ref = useRef(null) - const [isFocused, setIsFocused] = useState(false) - const [derviedSuggestionsWidth, setDerivedSuggestionsWidth] = - useState(DEFAULT_SUGGESTIONS_WIDTH) const [filteredSuggestions, setFilteredSuggestions] = useState(suggestions) - const showSuggestions = isFocused && filteredSuggestions.length > 0 - - useEffect(() => { - // This is super hacky but will go away when we bring it into the UI library - // It's mainly cause I can't pass a ref into Input - const parentInputFieldWidth = (ref?.current as any).children[0]?.children[0]?.children[ - layout === 'horizontal' ? 1 : 0 - ]?.offsetWidth - const width = suggestionsWidth || parentInputFieldWidth || DEFAULT_SUGGESTIONS_WIDTH - setDerivedSuggestionsWidth(width) - }, [ref.current]) + const showSuggestions = filteredSuggestions.length > 0 useEffect(() => { setFilteredSuggestions(suggestions.slice(0, MAX_SUGGESTIONS)) @@ -92,45 +78,34 @@ const InputWithSuggestions: FC = ({ className={className} type="text" value={value} - onFocus={() => setIsFocused(true)} - onBlur={async () => { - await timeout(100) - setIsFocused(false) - }} onChange={onInputChange} + actions={ + showSuggestions && ( + + Suggestions + + {filteredSuggestions.map((suggestion: Suggestion) => ( + onSelectSuggestion(suggestion)} + > +
    {suggestion.name}
    +
    {suggestion.description}
    +
    + ))} + + } + > + +
    + ) + } /> - {showSuggestions && ( -
    -
    -

    - - {suggestionsHeader} - -

    -
    - {filteredSuggestions.map((suggestion: Suggestion) => ( -
    onSelectSuggestion(suggestion)} - > -
    - {suggestion.name} -
    -
    - - {suggestion.description} - -
    -
    - ))} -
    -
    -
    - )}
    ) } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx index 7716ddf3b0a..d21b5db0d54 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/ForeignKeySelector/ForeignKeySelector.tsx @@ -105,15 +105,16 @@ const ForeignKeySelector: FC = ({ return ( + header={ + Edit foreign key relation for{' '} - {get(column, ['name'], '')} -
    + {get(column, ['name'], '')} + } customFooter={ = ({ /> } > -
    - } - title="What are foreign keys?" - description={ -

    - Foreign keys help maintain referential integrity of your data by ensuring that no one - can insert rows into the table that do not have a matching entry to another table -

    - } - url="https://www.postgresql.org/docs/current/tutorial-fk.html" - urlLabel="Postgres Foreign Key Documentation" - /> + +
    + } + title="What are foreign keys?" + description={`Foreign keys help maintain referential integrity of your data by ensuring that no + one can insert rows into the table that do not have a matching entry to another + table.`} + url="https://www.postgresql.org/docs/current/tutorial-fk.html" + urlLabel="Postgres Foreign Key Documentation" + /> - updateSelectedTable(Number(value))} - > - - --- - - {sortBy(tables, ['schema']).map((table: PostgresTable) => { - return ( - -
    - {/* For aria searching to target the table name instead of schema */} - {table.name} - - {table.schema} - - {table.name} -
    -
    - ) - })} -
    - - {selectedForeignKey?.table && ( - Select a column from{' '} - {selectedForeignKey?.table} to reference to -
    - } - error={errors.column} - onChange={(value: string) => updateSelectedColumn(value)} + label="Select a table to reference to" + value={selectedTable?.id} + error={errors.table} + onChange={(value: string) => updateSelectedTable(Number(value))} > - {(selectedTable?.columns ?? []).map((column: PostgresColumn) => ( - -
    - {column.name} - - {column.format} - -
    -
    - ))} + + --- + + {sortBy(tables, ['schema']).map((table: PostgresTable) => { + return ( + +
    + {/* For aria searching to target the table name instead of schema */} + {table.name} + {table.schema} + {table.name} +
    +
    + ) + })} - )} -
    + + {selectedForeignKey?.table && ( + + Select a column from{' '} + {selectedForeignKey?.table} to reference + to +
    + } + error={errors.column} + onChange={(value: string) => updateSelectedColumn(value)} + > + {(selectedTable?.columns ?? []).map((column: PostgresColumn) => ( + +
    + {column.name} + {column.format} +
    +
    + ))} + + )} +
    + ) } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/HeaderTitle.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/HeaderTitle.tsx index 9797a89a723..e1032242a70 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/HeaderTitle.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/HeaderTitle.tsx @@ -12,7 +12,7 @@ const HeaderTitle: FC = ({ isNewRecord, tableName }) => { return ( <> {header} - {tableName && {tableName}} + {tableName && {tableName}} ) } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/JsonEditor/JsonEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/JsonEditor/JsonEditor.tsx index f6a7d14b6c7..d28d0ebecf1 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/JsonEditor/JsonEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/JsonEditor/JsonEditor.tsx @@ -50,11 +50,10 @@ const JsonEdit: FC = ({ column, jsonString, visible, closePanel, return ( } > = ({ visible, referenceRow, closePanel }) => return ( Viewing reference row from{' '} - + {foreignKey?.target_table_schema ?? ''}.{foreignKey?.target_table_name ?? ''} - +
    } hideFooter={false} onCancel={closePanel} customFooter={} > - {loading ? ( -
    - + + {loading ? ( +
    - - Loading reference row -
    - ) : !row ? ( -
    - +

    Loading reference row

    +
    + ) : !row ? ( +
    - - - Unable to find the corresponding row in {foreignKey?.target_table_schema}. - {foreignKey?.target_table_name} - -
    - ) : ( - - {rowFields.map((field: RowField) => { - return - })} - - )} +

    + Unable to find the corresponding row in {foreignKey?.target_table_schema}. + {foreignKey?.target_table_name} +

    +
    + ) : ( +
    + {rowFields.map((field: RowField) => { + return + })} +
    + )} + ) } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx index 046e219f4b8..854d3183364 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/RowEditor/RowEditor.tsx @@ -1,6 +1,6 @@ import { FC, useEffect, useState } from 'react' import { isUndefined, partition, isEmpty } from 'lodash' -import { Divider, SidePanel, Space, Typography } from '@supabase/ui' +import { SidePanel } from '@supabase/ui' import { Dictionary, Query } from '@supabase/grid' import { PostgresTable } from '@supabase/postgres-meta' @@ -138,16 +138,14 @@ const RowEditor: FC = ({ return ( } + header={} className={`transition-all ease-in duration-100 ${ isEditingJson || isViewingReferenceRow ? ' mr-32' : '' }`} onCancel={closePanel} - onConfirm={(resolve: () => void) => onSaveChanges(resolve)} customFooter={ = ({ /> } > - - {requiredFields.map((field: RowField) => { - return ( - onViewForeignKey(field)} - /> - ) - })} - {optionalFields.length > 0 && ( - <> - -
    + +
    + {requiredFields.map((field: RowField) => { + return ( + onViewForeignKey(field)} + /> + ) + })} +
    +
    + {optionalFields.length > 0 && ( + <> + + +
    - Optional Fields +

    Optional Fields

    +

    + These are columns that do not need any value +

    - - These are columns that do not need any value - + {optionalFields.map((field: RowField) => { + return ( + onViewForeignKey(field)} + /> + ) + })}
    - - )} - {optionalFields.map((field: RowField) => { - return ( - onViewForeignKey(field)} - /> - ) - })} - +
    + + )} = ({ /> setIsClosingPanel(false)} onSelectConfirm={() => { @@ -267,6 +268,14 @@ const SidePanelEditor: FC = ({ setIsEdited(false) closePanel() }} + children={ + +

    + There are unsaved changes. Are you sure you want to close the panel? Your changes will + be lost. +

    +
    + } /> ) diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/Column.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/Column.tsx index 09531b487a2..390a1575586 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/Column.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/Column.tsx @@ -9,13 +9,16 @@ import { Popover, IconLink, IconSettings, + Button, + Listbox, } from '@supabase/ui' -import { ColumnField, EnumType } from '../SidePanelEditor.types' +import { ColumnField, EnumType, PostgresDataTypeOption } from '../SidePanelEditor.types' import ColumnType from '../ColumnEditor/ColumnType' import InputWithSuggestions from '../ColumnEditor/InputWithSuggestions' import { Suggestion } from '../ColumnEditor/ColumnEditor.types' import { typeExpressionSuggestions } from '../ColumnEditor/ColumnEditor.constants' +import { POSTGRES_DATA_TYPE_OPTIONS } from '../SidePanelEditor.constants' /** * [Joshen] For context: @@ -58,6 +61,13 @@ const Column: FC = ({ }) => { const suggestions: Suggestion[] = typeExpressionSuggestions?.[column.format] ?? [] + let settingsCount = 0 + + column.isNullable ? (settingsCount += 1) : null + column.isIdentity ? (settingsCount += 1) : null + column.isUnique ? (settingsCount += 1) : null + column.isArray ? (settingsCount += 1) : null + return (
    @@ -68,39 +78,60 @@ const Column: FC = ({
    -
    +
    onEditRelation(column)} + > + + + } onChange={(event: any) => onUpdateColumn({ name: event.target.value })} /> -
    onEditRelation(column)} - > - -
    + {/* console.log('foo')} defaultValue={1} className="lg:gap-0 "> + {POSTGRES_DATA_TYPE_OPTIONS.map((option: PostgresDataTypeOption, i) => { + if (i > 8) return + + return ( + inferIcon(option.type)} + > +
    + +

    {option.name}

    +
    +

    + + {option.description} + +

    +
    +
    + ) + })} +
    */} { onUpdateColumn({ format, defaultValue: '' }) @@ -115,7 +146,7 @@ const Column: FC = ({ size="small" value={column.defaultValue} disabled={column.format.includes('int') && column.isIdentity} - className={`bg-white dark:bg-transparent rounded ${ + className={`bg-white dark:bg-transparent rounded lg:gap-0 ${ column.format.includes('int') && column.isIdentity ? 'opacity-50' : '' }`} suggestions={suggestions} @@ -138,58 +169,87 @@ const Column: FC = ({
    {(!column.isPrimaryKey || column.format.includes('int')) && ( - - {!column.isPrimaryKey && ( - onUpdateColumn({ isNullable: !column.isNullable })} - /> + <> + +
    Extra options
    +
    + } + overlay={[ +
    + {!column.isPrimaryKey && ( + <> + onUpdateColumn({ isNullable: !column.isNullable })} + /> + + + )} + + {isNewRecord && ( + <> + onUpdateColumn({ isUnique: !column.isUnique })} + /> + + + )} + {column.format.includes('int') && ( + <> + { + const isIdentity = !column.isIdentity + const isArray = isIdentity ? false : column.isArray + onUpdateColumn({ isIdentity, isArray }) + }} + /> + + + )} + + {!column.isPrimaryKey && ( + { + const isArray = !column.isArray + const isIdentity = isArray ? false : column.isIdentity + onUpdateColumn({ isArray, isIdentity }) + }} + /> + )} +
    , + ]} + > +
    + {settingsCount > 0 && ( +
    + {settingsCount} +
    )} - {isNewRecord && ( - onUpdateColumn({ isUnique: !column.isUnique })} - /> - )} - {column.format.includes('int') && ( - { - const isIdentity = !column.isIdentity - const isArray = isIdentity ? false : column.isArray - onUpdateColumn({ isIdentity, isArray }) - }} - /> - )} - {!column.isPrimaryKey && ( - { - const isArray = !column.isArray - const isIdentity = isArray ? false : column.isIdentity - onUpdateColumn({ isArray, isIdentity }) - }} - /> - )} -
    , - ]} - > - - - - +
    + +
    +
    + + )}
    {!hasImportContent && ( diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/ColumnManagement.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/ColumnManagement.tsx index cb5e070743e..c95f4e4aabf 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/ColumnManagement.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/ColumnManagement.tsx @@ -17,6 +17,7 @@ import { DroppableProvided, DraggableProvided, } from 'react-beautiful-dnd' +import * as Tooltip from '@radix-ui/react-tooltip' import Column from './Column' import InformationBox from 'components/ui/InformationBox' @@ -143,7 +144,7 @@ const ColumnManagement: FC = ({ <> {hasImportContent ? (
    -
    ) : ( - )} @@ -192,21 +193,37 @@ const ColumnManagement: FC = ({ {/* Drag handle */} {isNewRecord &&
    }
    - Name +
    Name
    - Type +
    Type
    - Default Value -
    - - - -
    +
    Default Value
    + + + +
    + +
    +
    + + +
    + + Can be either a value or a SQL expression + +
    +
    +
    - Primary +
    Primary
    {/* Empty space */}
    @@ -222,7 +239,7 @@ const ColumnManagement: FC = ({ {(droppableProvided: DroppableProvided) => (
    @@ -291,7 +308,7 @@ const ColumnManagement: FC = ({
    {!hasImportContent && ( - )} diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadSheetFileUpload.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadSheetFileUpload.tsx index 319a1f68d3e..92e9f830473 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadSheetFileUpload.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadSheetFileUpload.tsx @@ -32,20 +32,18 @@ const SpreadSheetFileUpload: FC = ({ } return ( - <> - -

    +

    +
    +

    Upload a CSV or TSV file. The first row should be the headers of the table, and your headers should not include any special characters other than hyphens ( - -) or underscores ( - _). + -) or underscores (_ + ).

    - -

    - Tip: Datetime columns should be formatted as YYYY-MM-DD HH:mm:ss -

    -
    - +

    + Tip: Datetime columns should be formatted as YYYY-MM-DD HH:mm:ss +

    +
    {!uploadedFile ? (
    = ({ ) : (
    - - {uploadedFile.name} + +

    {uploadedFile.name}

    {parseProgress === 100 ? (
    )} - +
    ) } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadSheetTextInput.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadSheetTextInput.tsx index 8c0bb6a1840..62ac20dc83b 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadSheetTextInput.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadSheetTextInput.tsx @@ -1,5 +1,5 @@ import { FC } from 'react' -import { Typography } from '@supabase/ui' +import { Input, Typography } from '@supabase/ui' interface Props { input: string @@ -7,26 +7,27 @@ interface Props { } const SpreadSheetTextInput: FC = ({ input, onInputChange }) => ( - <> - -

    +

    +
    +

    Copy a table from a spreadsheet program such as Google Sheets or Excel and paste it in the field below. The first row should be the headers of the table, and your headers should not include any special characters other than hyphens (- ) or underscores (_).

    -

    +

    Tip: Datetime columns should be formatted as YYYY-MM-DD HH:mm:ss

    - - - + /> +
    ) export default SpreadSheetTextInput diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.tsx index df36c3807b4..ea9618c96f1 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/SpreadsheetImport/SpreadsheetImport.tsx @@ -123,15 +123,11 @@ const SpreadsheetImport: FC = ({ return ( { - // Only close if specifically hit the X button, this is to have the - // side panel work with toast messages (clicking on toast will close the panel) - if (event?.target) closePanel() - }} + header="Add content to new table" + onCancel={() => closePanel()} customFooter={ = ({ /> } > -
    - - {/* @ts-ignore */} - - - - {/* @ts-ignore */} - - - - -
    + +
    + + + + + + + + +
    - {spreadsheetData.headers.length > 0 && ( -
    -
    -
    - Content Preview - - Your table will have {spreadsheetData.rowCount.toLocaleString()} rows and the - following {spreadsheetData.headers.length} columns. - -
    - -
    - {errors.length > 0 && ( + {spreadsheetData.headers.length > 0 && ( +
    - Issues found in spreadsheet + Content Preview - Your table can still be created nonetheless despite issues in the following rows. + Your table will have {spreadsheetData.rowCount.toLocaleString()} rows and the + following {spreadsheetData.headers.length} columns.
    + +
    + {errors.length > 0 && (
    - {errors.map((error: any, idx: number) => { - const key = `import-error-${idx}` - const isExpanded = expandedErrors.includes(key) - return ( -
    -
    - onSelectExpandError(key)} - /> - Row: {error.row} - {error.message} - {error.data?.__parsed_extra && ( - <> - - Extra field(s): - {error.data?.__parsed_extra.map((value: any) => ( - - {value} - - ))} - +
    + Issues found in spreadsheet + + Your table can still be created nonetheless despite issues in the following + rows. + +
    +
    + {errors.map((error: any, idx: number) => { + const key = `import-error-${idx}` + const isExpanded = expandedErrors.includes(key) + return ( +
    +
    + onSelectExpandError(key)} + /> + Row: {error.row} + {error.message} + {error.data?.__parsed_extra && ( + <> + + Extra field(s): + {error.data?.__parsed_extra.map((value: any) => ( + + {value} + + ))} + + )} +
    + {isExpanded && ( + )}
    - {isExpanded && ( - - )} -
    - ) - })} + ) + })} +
    -
    - )} -
    - )} + )} +
    + )} +
    ) } diff --git a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx index 1a799c23ec1..9a32b3f4cc9 100644 --- a/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/SidePanelEditor/TableEditor/TableEditor.tsx @@ -1,6 +1,6 @@ import { FC, useEffect, useState } from 'react' import { isUndefined, isEmpty } from 'lodash' -import { Badge, Checkbox, Divider, SidePanel, Space, Input, Typography } from '@supabase/ui' +import { Badge, Checkbox, SidePanel, Input } from '@supabase/ui' import { PostgresTable } from '@supabase/postgres-meta' import { useStore } from 'hooks' @@ -22,6 +22,7 @@ import { generateTableFieldFromPostgresTable, formatImportedContentToColumnFields, } from './TableEditor.utils' +import SidePanelEditor from '..' interface Props { table?: PostgresTable @@ -131,14 +132,14 @@ const TableEditor: FC = ({ return ( } + header={} className={`transition-all ease-in duration-100 ${isImportingSpreadsheet ? ' mr-32' : ''}`} onCancel={closePanel} - onConfirm={(resolve: () => void) => onSaveChanges(resolve)} + onConfirm={() => (resolve: () => void) => onSaveChanges(resolve)} customFooter={ = ({ /> } > - - onUpdateField({ name: event.target.value })} - /> - onUpdateField({ comment: event.target.value })} - /> -
    - - Enable Row Level Security (RLS) - Recommended -
    - } - description="Restrict access to your table by enabling RLS and writing Postgres policies. If RLS is not enabled, anyone with the anon key can modify and delete your data." - checked={tableFields?.isRLSEnabled} - onChange={() => onUpdateField({ isRLSEnabled: !tableFields?.isRLSEnabled })} - size="medium" - /> - - {!isDuplicating && ( - onUpdateField({ columns })} - onSelectImportData={() => setIsImportingSpreadsheet(true)} - onClearImportContent={() => { - onUpdateField({ columns: DEFAULT_COLUMNS }) - setImportContent(undefined) - }} - /> - )} - {isDuplicating && ( - <> + <> + +
    + onUpdateField({ name: event.target.value })} + /> + onUpdateField({ comment: event.target.value })} + /> +
    +
    + + +
    setIsDuplicateRows(!isDuplicateRows)} + id="enable-rls" + // @ts-ignore + label={ +
    + Enable Row Level Security (RLS) + Recommended +
    + } + description="Restrict access to your table by enabling RLS and writing Postgres policies" + checked={tableFields?.isRLSEnabled} + onChange={() => onUpdateField({ isRLSEnabled: !tableFields?.isRLSEnabled })} size="medium" /> - - )} - - { - setImportContent(prefillData) - setIsImportingSpreadsheet(false) - }} - closePanel={() => setIsImportingSpreadsheet(false)} - /> +
    +
    + + +
    + {!isDuplicating && ( + onUpdateField({ columns })} + onSelectImportData={() => setIsImportingSpreadsheet(true)} + onClearImportContent={() => { + onUpdateField({ columns: DEFAULT_COLUMNS }) + setImportContent(undefined) + }} + /> + )} + {isDuplicating && ( + <> + setIsDuplicateRows(!isDuplicateRows)} + size="medium" + /> + + )} + + { + setImportContent(prefillData) + setIsImportingSpreadsheet(false) + }} + closePanel={() => setIsImportingSpreadsheet(false)} + /> +
    +
    +
    ) } diff --git a/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx b/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx index 9cc9e837410..5b8f12db0be 100644 --- a/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx +++ b/studio/components/interfaces/TableGridEditor/TableGridEditor.tsx @@ -10,6 +10,7 @@ import NotFoundState from './NotFoundState' import GridHeaderActions from './GridHeaderActions' import SidePanelEditor from './SidePanelEditor' import { SchemaView } from 'components/layouts/TableEditorLayout/TableEditorLayout.types' +import { SidePanel } from '@supabase/ui' interface Props { /** Theme for the editor */ diff --git a/studio/components/layouts/AccountLayout/WithSidebar.tsx b/studio/components/layouts/AccountLayout/WithSidebar.tsx index 5b98e223acc..817169ce1d8 100644 --- a/studio/components/layouts/AccountLayout/WithSidebar.tsx +++ b/studio/components/layouts/AccountLayout/WithSidebar.tsx @@ -85,7 +85,7 @@ export default WithSidebar const LinksWithHeaders: FC = ({ links, subitems, subitemsParentKey }) => { return links.map((x: any) => ( -
    +
    {x.versionLabel && (
    @@ -104,7 +104,7 @@ const LinksWithHeaders: FC = ({ links, subitems, subitemsParentKey }) => { } const LinksWithoutHeaders: FC = ({ links, subitems, subitemsParentKey }) => { return ( -