From fed82591c3bdaed945b29677c82d046913381d65 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Fri, 22 Jul 2022 15:46:22 +0800 Subject: [PATCH] Refactor org general settings to use new form component --- .../GeneralSettings/GeneralSettings.tsx | 118 ++++++++++++------ .../OrganizationDeletePanel.tsx | 5 - .../Organization/TeamSettings/MembersView.tsx | 4 +- studio/hooks/misc/withAuth.tsx | 4 +- studio/hooks/queries/usePermissions.ts | 2 + studio/hooks/queries/usePermissionsQuery.ts | 58 ++++++++- studio/lib/common/permissions.ts | 47 ------- 7 files changed, 142 insertions(+), 96 deletions(-) delete mode 100644 studio/lib/common/permissions.ts diff --git a/studio/components/interfaces/Organization/GeneralSettings/GeneralSettings.tsx b/studio/components/interfaces/Organization/GeneralSettings/GeneralSettings.tsx index 91ad7d39e08..53e392bf643 100644 --- a/studio/components/interfaces/Organization/GeneralSettings/GeneralSettings.tsx +++ b/studio/components/interfaces/Organization/GeneralSettings/GeneralSettings.tsx @@ -1,75 +1,117 @@ import { useContext } from 'react' -import { toJS } from 'mobx' import { observer } from 'mobx-react-lite' -import { AutoField } from 'uniforms-bootstrap4' +import { Form, Input } from '@supabase/ui' import { PermissionAction } from '@supabase/shared-types/out/constants' import { useStore } from 'hooks' +import { checkPermissions } from 'hooks/queries/usePermissionsQuery' import { API_URL } from 'lib/constants' import { patch } from 'lib/common/fetch' -import { checkPermissions } from 'lib/common/permissions' -import { pluckJsonSchemaFields, pluckObjectFields } from 'lib/helpers' -import { organizations } from 'stores/jsonSchema' + import OrganizationDeletePanel from './OrganizationDeletePanel' -import SchemaFormPanel from 'components/to-be-cleaned/forms/SchemaFormPanel' +import { + FormActions, + FormPanel, + FormSection, + FormSectionContent, + FormSectionLabel, +} from 'components/ui/Forms' import { PageContext } from 'pages/org/[slug]/settings' const GeneralSettings = observer(() => { - const PageState: any = useContext(PageContext) const { ui } = useStore() + const PageState: any = useContext(PageContext) + + const formId = 'org-general-settings' + const initialValues = { + name: PageState.organization.name, + billing_email: PageState.organization?.billing_email ?? '', + } + const canUpdateOrganization = checkPermissions( PermissionAction.SQL_UPDATE, 'postgres.public.organizations' ) - const formModel = toJS(PageState.organization) - // remove warning null value for controlled input - if (!formModel.billing_email) formModel.billing_email = '' - const BASIC_FIELDS = ['name', 'billing_email'] + const onUpdateOrganization = async (values: any, { setSubmitting, resetForm }: any) => { + if (!canUpdateOrganization) { + return ui.setNotification({ + category: 'error', + message: 'You do not have sufficient permissions to update your organization settings', + }) + } - const handleUpdateOrg = async (model: any) => { - const response = await patch(`${API_URL}/organizations/${PageState.organization.slug}`, model) + setSubmitting(true) + const response = await patch(`${API_URL}/organizations/${PageState.organization.slug}`, values) if (response.error) { ui.setNotification({ category: 'error', message: `Failed to update organization: ${response.error.message}`, }) } else { - const updatedOrg = response - PageState.onOrgUpdated(updatedOrg) + const { name, billing_email } = response + resetForm({ + values: { name, billing_email }, + initialValues: { name, billing_email }, + }) + + PageState.onOrgUpdated(response) ui.setNotification({ category: 'success', message: 'Successfully saved settings', }) } + setSubmitting(false) } return ( -
- handleUpdateOrg(pluckObjectFields(model, BASIC_FIELDS))} - > - - - +
+
+ {({ isSubmitting, handleReset, resetForm, values, initialValues }: any) => { + const hasChanges = JSON.stringify(values) !== JSON.stringify(initialValues) + return ( + + {!canUpdateOrganization && ( +

+ You need additional permissions to manage your organization's settings +

+ )} + +
+ } + > + General settings}> + + + + + + + ) + }} + - -
+ {canUpdateOrganization && } + ) }) diff --git a/studio/components/interfaces/Organization/GeneralSettings/OrganizationDeletePanel.tsx b/studio/components/interfaces/Organization/GeneralSettings/OrganizationDeletePanel.tsx index 6ca75ad5569..4a5926a6625 100644 --- a/studio/components/interfaces/Organization/GeneralSettings/OrganizationDeletePanel.tsx +++ b/studio/components/interfaces/Organization/GeneralSettings/OrganizationDeletePanel.tsx @@ -5,12 +5,7 @@ import { Alert } from '@supabase/ui' import Panel from 'components/ui/Panel' import DeleteOrganizationButton from './DeleteOrganizationButton' -import { PageContext } from 'pages/org/[slug]/settings' - const OrganizationDeletePanel = observer(() => { - const PageState: any = useContext(PageContext) - - if (!PageState.isOrgOwner) return null return ( { const role = (roles || []).find((role) => role.id === memberRoleId) const memberIsUser = x.primary_email == PageState.user.primary_email + console.log(x.gotrue_id, x.primary_email, x.role_ids) + return ( <> diff --git a/studio/hooks/misc/withAuth.tsx b/studio/hooks/misc/withAuth.tsx index f37e71367d9..5dd9788b094 100644 --- a/studio/hooks/misc/withAuth.tsx +++ b/studio/hooks/misc/withAuth.tsx @@ -2,7 +2,7 @@ import { useProfile, useStore } from 'hooks' import { ComponentType, useEffect } from 'react' import { NextRouter, useRouter } from 'next/router' import { IS_PLATFORM } from 'lib/constants' -import { usePermissions2 } from 'hooks/queries/usePermissionsQuery' +import { usePermissions } from 'hooks/queries/usePermissionsQuery' const PLATFORM_ONLY_PAGES = ['storage', 'reports', 'settings'] @@ -27,7 +27,7 @@ export function withAuth( const returning = app.projects.isInitialized && app.organizations.isInitialized ? 'minimal' : undefined const { profile, isLoading } = useProfile(returning) - const { permissions, isLoading: isPermissionLoading } = usePermissions2(returning) + const { permissions, isLoading: isPermissionLoading } = usePermissions(returning) const isAccessingBlockedPage = !IS_PLATFORM && PLATFORM_ONLY_PAGES.includes(page) const isRedirecting = diff --git a/studio/hooks/queries/usePermissions.ts b/studio/hooks/queries/usePermissions.ts index 7dd3c372700..8c91383bc0e 100644 --- a/studio/hooks/queries/usePermissions.ts +++ b/studio/hooks/queries/usePermissions.ts @@ -5,6 +5,8 @@ import { find } from 'lodash' import { useRouter } from 'next/router' import { Organization, Project } from 'types' +// [JOSHEN TODO] TO BE DEPRECATED + export function usePermissions(action: string, resource: string, data?: object) { if (!IS_PLATFORM) return true diff --git a/studio/hooks/queries/usePermissionsQuery.ts b/studio/hooks/queries/usePermissionsQuery.ts index 9069f6a8b93..618b63cd8a3 100644 --- a/studio/hooks/queries/usePermissionsQuery.ts +++ b/studio/hooks/queries/usePermissionsQuery.ts @@ -1,8 +1,17 @@ -import { get } from 'lib/common/fetch' -import { API_URL } from 'lib/constants' import useSWR from 'swr' +import jsonLogic from 'json-logic-js' +import { find } from 'lodash' +import { useRouter } from 'next/router' -export function usePermissions2(returning?: 'minimal') { +import { useStore } from 'hooks' +import { get } from 'lib/common/fetch' +import { API_URL, IS_PLATFORM } from 'lib/constants' +import { Organization, Project } from 'types' +import { toJS } from 'mobx' + +// [JOSHEN TODO] USE THIS INSTEAD RENAME TO usePermissions + +export function usePermissions(returning?: 'minimal') { let url = `${API_URL}/profile/permissions` if (returning) { @@ -21,3 +30,46 @@ export function usePermissions2(returning?: 'minimal') { isError: !!anyError, } } + +export function checkPermissions(action: string, resource: string, data?: object) { + if (!IS_PLATFORM) return true + + const { app, ui } = useStore() + const router = useRouter() + + let organization_id: number | undefined + const { ref, slug } = router.query + if (ref) { + const project = find(app.projects.list(), { ref }) as Project | undefined + organization_id = project?.organization_id + } else if (slug) { + const organization = find(app.projects.list(), { ref }) as Organization | undefined + organization_id = organization?.id + } + + // console.log('CheckPermissions', ui.permissions) + // console.log( + // toJS(ui.permissions).filter((p) => p.resources.indexOf('postgres.public.organizations') >= 0) + // ) + + return (ui?.permissions ?? []) + .filter( + (permission: { + actions: string[] + condition: jsonLogic.RulesLogic + organization_id: number + resources: string[] + }) => + permission.actions.some((act) => + action ? action.match(act.replace('.', '.').replace('%', '.*')) : null + ) && + permission.resources.some((res) => + resource.match(res.replace('.', '.').replace('%', '.*')) + ) && + permission.organization_id === ui?.selectedOrganization?.id + ) + .some( + ({ condition }: { condition: jsonLogic.RulesLogic }) => + condition === null || jsonLogic.apply(condition, data) + ) +} diff --git a/studio/lib/common/permissions.ts b/studio/lib/common/permissions.ts deleted file mode 100644 index 778714db59d..00000000000 --- a/studio/lib/common/permissions.ts +++ /dev/null @@ -1,47 +0,0 @@ -import jsonLogic from 'json-logic-js' -import { find } from 'lodash' -import { useRouter } from 'next/router' - -import { useStore } from 'hooks' -import { IS_PLATFORM } from 'lib/constants' -import { Organization, Project } from 'types' - -// [Joshen] Should we be using a hook here? - -export function checkPermissions(action: string, resource: string, data?: object) { - if (!IS_PLATFORM) return true - - const { app, ui } = useStore() - const router = useRouter() - - let organization_id: number | undefined - const { ref, slug } = router.query - if (ref) { - const project = find(app.projects.list(), { ref }) as Project | undefined - organization_id = project?.organization_id - } else if (slug) { - const organization = find(app.projects.list(), { ref }) as Organization | undefined - organization_id = organization?.id - } - - return (ui?.permissions ?? []) - .filter( - (permission: { - actions: string[] - condition: jsonLogic.RulesLogic - organization_id: number - resources: string[] - }) => - permission.actions.some((act) => - action ? action.match(act.replace('.', '.').replace('%', '.*')) : null - ) && - permission.resources.some((res) => - resource.match(res.replace('.', '.').replace('%', '.*')) - ) && - permission.organization_id === ui?.selectedOrganization?.id - ) - .some( - ({ condition }: { condition: jsonLogic.RulesLogic }) => - condition === null || jsonLogic.apply(condition, data) - ) -}