From 03f35e7ddbbf8f5fa09047d1cc4e8bb5df00a28c Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 20 Sep 2023 14:16:16 +0800 Subject: [PATCH] Deprecate old org and project dropdowns, LayoutHeader to use the new ones under AppLayout --- .../AppLayout/OrganizationDropdown.tsx | 10 +- .../layouts/AppLayout/ProjectDropdown.tsx | 18 ++- .../LayoutHeader/LayoutHeader.tsx | 17 +-- .../LayoutHeader/OrgDropdown.tsx | 72 ----------- .../LayoutHeader/ProjectDropdown.tsx | 122 ------------------ studio/hooks/misc/useSelectedProject.ts | 4 +- studio/types/base.ts | 5 + 7 files changed, 37 insertions(+), 211 deletions(-) delete mode 100644 studio/components/layouts/ProjectLayout/LayoutHeader/OrgDropdown.tsx delete mode 100644 studio/components/layouts/ProjectLayout/LayoutHeader/ProjectDropdown.tsx diff --git a/studio/components/layouts/AppLayout/OrganizationDropdown.tsx b/studio/components/layouts/AppLayout/OrganizationDropdown.tsx index eb61f463219..d76925a3945 100644 --- a/studio/components/layouts/AppLayout/OrganizationDropdown.tsx +++ b/studio/components/layouts/AppLayout/OrganizationDropdown.tsx @@ -24,7 +24,11 @@ import { useOrganizationsQuery } from 'data/organizations/organizations-query' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useSelectedOrganization } from 'hooks' -const OrganizationDropdown = () => { +interface OrganizationDropdownProps { + alt?: boolean // To distinguish slight behaviour change between nav v1 and v2, true for former +} + +const OrganizationDropdown = ({ alt = false }: OrganizationDropdownProps) => { const router = useRouter() const selectedOrganization = useSelectedOrganization() const { data: organizations, isLoading: isLoadingOrganizations } = useOrganizationsQuery() @@ -56,7 +60,7 @@ const OrganizationDropdown = () => { } >
-

{orgName}

+

{orgName}

{isSuccess && {subscription?.plan.name}}
@@ -72,6 +76,8 @@ const OrganizationDropdown = () => { {organizations?.map((org) => { const href = router.pathname.includes('[slug]') ? router.pathname.replace('[slug]', org.slug) + : alt + ? `/org/${org.slug}/general` : `/org/${org.slug}` return ( diff --git a/studio/components/layouts/AppLayout/ProjectDropdown.tsx b/studio/components/layouts/AppLayout/ProjectDropdown.tsx index 66921550296..afe941ebdae 100644 --- a/studio/components/layouts/AppLayout/ProjectDropdown.tsx +++ b/studio/components/layouts/AppLayout/ProjectDropdown.tsx @@ -99,7 +99,11 @@ const ProjectLink = ({ ) } -const ProjectDropdown = () => { +interface ProjectDropdownProps { + alt?: boolean // To distinguish slight behaviour change between nav v1 and v2, true for former +} + +const ProjectDropdown = ({ alt = false }: ProjectDropdownProps) => { const router = useRouter() const { ref } = useParams() const projectDetails = useSelectedProject() @@ -116,9 +120,11 @@ const ProjectDropdown = () => { { projectRef: ref }, { enabled: !isOrgBilling } ) - const projects = allProjects - ?.filter((x) => x.organization_id === selectedOrganization?.id) - .sort((a, b) => a.name.localeCompare(b.name)) + const projects = alt + ? allProjects?.sort((a, b) => a.name.localeCompare(b.name)) + : allProjects + ?.filter((x) => x.organization_id === selectedOrganization?.id) + .sort((a, b) => a.name.localeCompare(b.name)) const selectedProject = isBranch ? parentProject : projectDetails || projects?.find((project) => project.ref === ref) @@ -139,7 +145,7 @@ const ProjectDropdown = () => { iconRight={} >
-

{selectedProject?.name}

+

{selectedProject?.name}

{isSuccess && !isOrgBilling && {subscription?.plan.name}}
@@ -192,7 +198,7 @@ const ProjectDropdown = () => { ) : ( ) } diff --git a/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx b/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx index e889996981b..17f08472885 100644 --- a/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx +++ b/studio/components/layouts/ProjectLayout/LayoutHeader/LayoutHeader.tsx @@ -5,12 +5,14 @@ import { Badge } from 'ui' import BranchDropdown from 'components/layouts/AppLayout/BranchDropdown' import EnableBranchingButton from 'components/layouts/AppLayout/EnableBranchingButton/EnableBranchingButton' -import { getResourcesExceededLimits } from 'components/ui/OveragesBanner/OveragesBanner.utils' +import OrganizationDropdown from 'components/layouts/AppLayout/OrganizationDropdown' +import ProjectDropdown from 'components/layouts/AppLayout/ProjectDropdown' +import { + getResourcesExceededLimits, + getResourcesExceededLimitsOrg, +} from 'components/ui/OveragesBanner/OveragesBanner.utils' import { useProjectSubscriptionV2Query } from 'data/subscriptions/project-subscription-v2-query' - -import { getResourcesExceededLimitsOrg } from 'components/ui/OveragesBanner/OveragesBanner.utils' import { useOrgUsageQuery } from 'data/usage/org-usage-query' - import { useProjectUsageQuery } from 'data/usage/project-usage-query' import { useFlag, useSelectedOrganization, useSelectedProject } from 'hooks' import { IS_PLATFORM } from 'lib/constants' @@ -18,8 +20,6 @@ import BreadcrumbsView from './BreadcrumbsView' import FeedbackDropdown from './FeedbackDropdown' import HelpPopover from './HelpPopover' import NotificationsPopover from './NotificationsPopover' -import OrgDropdown from './OrgDropdown' -import ProjectDropdown from './ProjectDropdown' const LayoutHeader = ({ customHeaderComponents, breadcrumbs = [], headerBorder = true }: any) => { const { ref: projectRef } = useParams() @@ -85,7 +85,7 @@ const LayoutHeader = ({ customHeaderComponents, breadcrumbs = [], headerBorder = {/* Organization is selected */} {projectRef && selectedOrganization ? ( <> - + {projectRef && ( <> @@ -104,7 +104,8 @@ const LayoutHeader = ({ customHeaderComponents, breadcrumbs = [], headerBorder = - + + {showOverUsageBadge && (
diff --git a/studio/components/layouts/ProjectLayout/LayoutHeader/OrgDropdown.tsx b/studio/components/layouts/ProjectLayout/LayoutHeader/OrgDropdown.tsx deleted file mode 100644 index 6e17d2db385..00000000000 --- a/studio/components/layouts/ProjectLayout/LayoutHeader/OrgDropdown.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { toJS } from 'mobx' -import { useRouter } from 'next/router' - -import { useOrganizationsQuery } from 'data/organizations/organizations-query' -import { useSelectedOrganization, useStore } from 'hooks' -import { IS_PLATFORM } from 'lib/constants' -import { Button, Dropdown, IconPlus } from 'ui' - -const OrgDropdown = () => { - const router = useRouter() - const { ui } = useStore() - - const { data: organizations } = useOrganizationsQuery() - const selectedOrganization = useSelectedOrganization() - - return IS_PLATFORM ? ( - - {organizations - ?.sort((a, b) => a.name.localeCompare(b.name)) - .map((x) => { - const slug = toJS(x.slug) - - return ( - { - if (!slug) { - // The user should not see this error as the page should - // be rerendered with the value of slug before they can click. - // It is just here in case they are the flash. - return ui.setNotification({ - category: 'error', - message: - 'Could not navigate to organization settings, please try again or contact support', - }) - } - - router.push({ - pathname: `/org/[slug]/general`, - query: { slug }, - hash: router.asPath.split('#')[1]?.toLowerCase(), - }) - }} - > - {x.name} - - ) - })} - - - } onClick={() => router.push(`/new`)}> - New organization - - - } - > - - - ) : ( - - ) -} - -export default OrgDropdown diff --git a/studio/components/layouts/ProjectLayout/LayoutHeader/ProjectDropdown.tsx b/studio/components/layouts/ProjectLayout/LayoutHeader/ProjectDropdown.tsx deleted file mode 100644 index ab1c78e7e74..00000000000 --- a/studio/components/layouts/ProjectLayout/LayoutHeader/ProjectDropdown.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import { useParams } from 'common' -import Link from 'next/link' -import { useRouter } from 'next/router' -import { ParsedUrlQuery } from 'querystring' -import { Button, Dropdown, IconPlus, Popover } from 'ui' - -import ShimmeringLoader from 'components/ui/ShimmeringLoader' -import { useOrganizationsQuery } from 'data/organizations/organizations-query' -import { useProjectsQuery } from 'data/projects/projects-query' -import { useSelectedOrganization, useSelectedProject } from 'hooks' -import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants' -import { Organization, Project } from 'types' - -// [Fran] the idea is to let users change projects without losing the current page, -// but at the same time we need to redirect correctly between urls that might be -// unique to a project e.g. '/project/projectRef/editor/tableId' -// Right now, I'm gonna assume that any router query after the projectId, -// is a unique project id/marker so we'll redirect the user to the -// highest common route with just projectRef in the router queries. - -export const sanitizeRoute = (route: string, routerQueries: ParsedUrlQuery) => { - const queryArray = Object.entries(routerQueries) - - if (queryArray.length > 1) { - // [Joshen] Ideally we shouldn't use hard coded numbers, but temp workaround - // for storage bucket route since its longer - const isStorageBucketRoute = 'bucketId' in routerQueries - return route - .split('/') - .slice(0, isStorageBucketRoute ? 5 : 4) - .join('/') - } else { - return route - } -} - -const ProjectLink = ({ - project, - organization, -}: { - project: Project - organization?: Organization -}) => { - const router = useRouter() - const selectedProject = useSelectedProject() - const sanitizedRoute = sanitizeRoute(router.route, router.query) - const isOrgBilling = !!organization?.subscription_id - - // [Joshen] Temp while we're interim between v1 and v2 billing - let href = sanitizedRoute?.replace('[ref]', project.ref) ?? `/project/${project.ref}` - if (href.endsWith('settings/addons') && !isOrgBilling) { - href = href.replace('settings/addons', 'settings/billing/subscription') - } else if (href.endsWith('settings/billing/subscription') && isOrgBilling) { - href = href.replace('settings/billing/subscription', 'settings/addons') - } else if (href.endsWith('settings/infrastructure') && !isOrgBilling) { - href = href.replace('settings/infrastructure', 'settings/billing/usage') - } else if (href.endsWith('settings/billing/usage') && !isOrgBilling) { - href = href.replace('settings/billing/usage', 'settings/infrastructure') - } - - return ( - - - - {project.name} - - - - ) -} - -const ProjectDropdown = () => { - const { ref } = useParams() - const selectedOrganization = useSelectedOrganization() - const { data: allOrganizations } = useOrganizationsQuery() - const { data: allProjects, isLoading: isLoadingProjects } = useProjectsQuery() - - const selectedProjectName = allProjects?.find((project) => project.ref === ref)?.name - const selectedOrganizationSlug = selectedOrganization?.slug - - if (isLoadingProjects) { - return - } - - return IS_PLATFORM ? ( - - {allProjects - ?.filter((x) => x.status !== PROJECT_STATUS.INACTIVE) - .sort((a, b) => a.name.localeCompare(b.name)) - .map((x) => { - const org = allOrganizations?.find((org) => org.id === x.organization_id) - return - })} - - - - }>New project - - - - } - > - - - ) : ( - - ) -} - -export default ProjectDropdown diff --git a/studio/hooks/misc/useSelectedProject.ts b/studio/hooks/misc/useSelectedProject.ts index 514d7adc20c..42725cfac1d 100644 --- a/studio/hooks/misc/useSelectedProject.ts +++ b/studio/hooks/misc/useSelectedProject.ts @@ -6,7 +6,9 @@ import { useMemo } from 'react' export function useSelectedProject() { const { ref } = useParams() const { data } = useProjectDetailQuery({ ref }) - return data + + const updatedData = { ...data, parentRef: data?.parent_project_ref ?? data?.ref ?? undefined } + return updatedData } export function useProjectByRef(ref?: string) { diff --git a/studio/types/base.ts b/studio/types/base.ts index e19dccda4e8..48d065bd000 100644 --- a/studio/types/base.ts +++ b/studio/types/base.ts @@ -42,6 +42,11 @@ export interface Project extends ProjectBase { * If not we will show ConnectingState and run a polling until it's back online */ postgrestStatus?: 'ONLINE' | 'OFFLINE' + /** + * Only available on client side only, for components that require the parentRef + * irregardless of being on any branch, such as ProjectDropdown and Vercel integration + * */ + parentRef?: string } export interface User {