Chore/default to new organization layout (#35515)

* Default to new organization layout + remove feature preview

* Remove feature preview

* Clean up LayoutUpdateBanner

* Clean up

* Fix

* Fixy fix

* Update comment

* Nit to fix layout shift
This commit is contained in:
Joshen Lim authored and GitHub committed 2025-05-13 11:25:35 +08:00
1 parent a322cdc679
commit fd98a08a4f
58 files changed
+491 -1298

No files matched your search

@@ -1,26 +1,16 @@
import { noop } from 'lodash'
import { FeatureFlagContext } from 'common'
import { useFlag } from 'hooks/ui/useFlag'
import { PropsWithChildren, createContext, useContext, useEffect, useState } from 'react'
import { FeatureFlagContext, LOCAL_STORAGE_KEYS } from 'common'
import { IS_PLATFORM } from 'lib/constants'
import { EMPTY_OBJ } from 'lib/void'
import { PropsWithChildren, createContext, useContext, useEffect, useState } from 'react'
import { APISidePanelPreview } from './APISidePanelPreview'
import { CLSPreview } from './CLSPreview'
import { InlineEditorPreview } from './InlineEditorPreview'
import { LayoutUpdatePreview } from './LayoutUpdatePreview'
import { SqlEditorTabsPreview } from './SqlEditorTabs'
import { TableEditorTabsPreview } from './TableEditorTabs'
import { LOCAL_STORAGE_KEYS } from 'common'
export const FEATURE_PREVIEWS = [
{
key: LOCAL_STORAGE_KEYS.UI_NEW_LAYOUT_PREVIEW,
name: 'Layout Update for Organizations',
content: <LayoutUpdatePreview />,
discussionsUrl: 'https://github.com/orgs/supabase/discussions/33670',
isNew: false,
isPlatformOnly: true,
},
{
key: LOCAL_STORAGE_KEYS.UI_PREVIEW_INLINE_EDITOR,
name: 'Directly edit database entities',
@@ -75,13 +65,10 @@ export const useFeaturePreviewContext = () => useContext(FeaturePreviewContext)
export const FeaturePreviewContextProvider = ({ children }: PropsWithChildren<{}>) => {
const { hasLoaded } = useContext(FeatureFlagContext)
const enableNewLayoutPreview = useFlag('newLayoutPreview')
// [Joshen] Similar logic to feature flagging previews, we can use flags to default opt in previews
const isDefaultOptIn = (feature: (typeof FEATURE_PREVIEWS)[number]) => {
switch (feature.key) {
case LOCAL_STORAGE_KEYS.UI_NEW_LAYOUT_PREVIEW:
return enableNewLayoutPreview
default:
return false
}
@@ -149,9 +136,3 @@ export const useIsSQLEditorTabsEnabled = () => {
if (!IS_PLATFORM) return false
return flags[LOCAL_STORAGE_KEYS.UI_SQL_EDITOR_TABS]
}
export const useIsNewLayoutEnabled = (): boolean => {
const { flags } = useFeaturePreviewContext()
if (!IS_PLATFORM) return false
return flags[LOCAL_STORAGE_KEYS.UI_NEW_LAYOUT_PREVIEW]
}
@@ -1,90 +0,0 @@
import { ButtonTooltip } from 'components/ui/ButtonTooltip'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { BASE_PATH } from 'lib/constants'
import { ExternalLink, X } from 'lucide-react'
import Image from 'next/image'
import { Alert_Shadcn_, AlertDescription_Shadcn_, AlertTitle_Shadcn_, Badge, Button } from 'ui'
import { useIsNewLayoutEnabled } from './FeaturePreviewContext'
import { LOCAL_STORAGE_KEYS } from 'common'
export const LayoutUpdatePreview = () => {
return (
<div className="space-y-2">
<Image
src={`${BASE_PATH}/img/previews/layout-update.png`}
width={1860}
height={970}
alt="api-docs-side-panel-preview"
className="rounded border"
/>
<p className="text-foreground-light text-sm">
The layout out of the dashboard is now scoped to the organization that you're currently on
instead of all organizations that you are a member of, in hopes to provide a clearer
navigation to your organization settings.
</p>
<p className="text-foreground-light text-sm">
This feature preview only updates the layout for the home page, organization settings pages,
and account pages. Layout for project pages will not have any changes.
</p>
<div className="space-y-2 !mt-4">
<p className="text-sm">Enabling this preview will:</p>
<ul className="list-disc pl-6 text-sm text-foreground-light space-y-1">
<li>
Display only the projects from the organization you are currently viewing on the home
page
</li>
<li>Update homepage sidebar links to go directly to the settings for the organization</li>
<li>
Access to account preferences, access tokens, security settings, and audit logs are
available via the user dropdown in the top navigation bar
</li>
</ul>
</div>
</div>
)
}
export const LayoutUpdateBanner = () => {
const newLayoutPreview = useIsNewLayoutEnabled()
const [newLayoutPreviewState] = useLocalStorageQuery(LOCAL_STORAGE_KEYS.UI_NEW_LAYOUT_PREVIEW, '')
const [newLayoutAcknowledged, setNewLayoutAcknowledged] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.NEW_LAYOUT_NOTICE_ACKNOWLEDGED,
false
)
const isDefaultOptedInNewLayout = newLayoutPreview && newLayoutPreviewState === ''
if (!isDefaultOptedInNewLayout || newLayoutAcknowledged) return null
return (
<Alert_Shadcn_ className="mb-4 relative">
<AlertTitle_Shadcn_>
<Badge variant="brand" className="mr-2">
NEW
</Badge>
Dashboard layout has been updated!
</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
We've updated the dashboard layout to make it easier to find and manage your organization
settings.
</AlertDescription_Shadcn_>
<AlertDescription_Shadcn_ className="mt-4 flex items-center gap-x-2">
<Button asChild type="default" icon={<ExternalLink />}>
<a
target="_blank"
rel="noreferrer noopener"
href="https://github.com/orgs/supabase/discussions/33670"
>
View announcement
</a>
</Button>
</AlertDescription_Shadcn_>
<ButtonTooltip
type="text"
icon={<X />}
className="absolute top-2 right-2 px-1"
onClick={() => setNewLayoutAcknowledged(true)}
tooltip={{ content: { side: 'bottom', text: 'Dismiss' } }}
/>
</Alert_Shadcn_>
)
}
@@ -10,13 +10,11 @@ import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { IS_PLATFORM } from 'lib/constants'
import { useAppStateSnapshot } from 'state/app-state'
import { useIsNewLayoutEnabled } from './FeaturePreview/FeaturePreviewContext'
// Ideally these could all be within a _middleware when we use Next 12
const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {
const router = useRouter()
const { ref, slug, id } = useParams()
const newLayoutPreview = useIsNewLayoutEnabled()
const isLoggedIn = useIsLoggedIn()
const snap = useAppStateSnapshot()
@@ -33,11 +31,7 @@ const RouteValidationWrapper = ({ children }: PropsWithChildren<{}>) => {
''
)
const DEFAULT_HOME = IS_PLATFORM
? newLayoutPreview
? '/organizations'
: '/projects'
: '/project/default'
const DEFAULT_HOME = IS_PLATFORM ? '/organizations' : '/project/default'
/**
* Array of urls/routes that should be ignored
@@ -32,7 +32,7 @@ import { getContentFilePath } from './Connect.utils'
import ConnectDropdown from './ConnectDropdown'
import ConnectTabContent from './ConnectTabContent'
const Connect = () => {
export const Connect = () => {
const { ref: projectRef } = useParams()
const selectedProject = useSelectedProject()
const isActiveHealthy = selectedProject?.status === PROJECT_STATUS.ACTIVE_HEALTHY
@@ -293,5 +293,3 @@ const Connect = () => {
</Dialog>
)
}
export default Connect
@@ -1,22 +1,16 @@
import { groupBy } from 'lodash'
import { Plus } from 'lucide-react'
import Link from 'next/link'
import { useParams } from 'common'
import AlertError from 'components/ui/AlertError'
import NoSearchResults from 'components/ui/NoSearchResults'
import PartnerIcon from 'components/ui/PartnerIcon'
import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query'
import { useOrgIntegrationsQuery } from 'data/integrations/integrations-query-org-only'
import {
OverdueInvoicesResponse,
useOverdueInvoicesQuery,
} from 'data/invoices/invoices-overdue-query'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { usePermissionsQuery } from 'data/permissions/permissions-query'
import { ProjectInfo, useProjectsQuery } from 'data/projects/projects-query'
import { ResourceWarning, useResourceWarningsQuery } from 'data/usage/resource-warnings-query'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { IS_PLATFORM } from 'lib/constants'
import { makeRandomString } from 'lib/helpers'
import type { Organization, ResponseError } from 'types'
@@ -29,8 +23,6 @@ export interface ProjectListProps {
search?: string
filterStatus?: string[]
resetFilterStatus?: () => void
// if true, only show projects for the current organization
filterToSlug?: boolean
}
const ProjectList = ({
@@ -38,12 +30,12 @@ const ProjectList = ({
rewriteHref,
filterStatus,
resetFilterStatus,
filterToSlug,
}: ProjectListProps) => {
const { slug } = useParams()
const { data: organizations, isLoading, isSuccess } = useOrganizationsQuery()
const organization = useSelectedOrganization()
const {
data: allProjects,
data: allProjects = [],
isLoading: isLoadingProjects,
isSuccess: isSuccessProjects,
isError: isErrorProjects,
@@ -55,15 +47,15 @@ const ProjectList = ({
error: permissionsError,
} = usePermissionsQuery()
const { data: resourceWarnings } = useResourceWarningsQuery()
const { data: allOverdueInvoices } = useOverdueInvoicesQuery()
const projectsByOrg = groupBy(allProjects, 'organization_id')
const orgProjects = allProjects.filter((x) => x.organization_slug === slug)
const isLoadingPermissions = IS_PLATFORM ? _isLoadingPermissions : false
const hasFilterStatusApplied = filterStatus !== undefined && filterStatus.length !== 2
const noResultsFromSearch =
search.length > 0 &&
isSuccessProjects &&
allProjects.filter((project) => {
orgProjects.filter((project) => {
return (
project.name.toLowerCase().includes(search.toLowerCase()) ||
project.ref.includes(search.toLowerCase())
@@ -72,9 +64,9 @@ const ProjectList = ({
const noResultsFromStatusFilter =
hasFilterStatusApplied &&
isSuccessProjects &&
allProjects.filter((project) => filterStatus.includes(project.status)).length === 0
orgProjects.filter((project) => filterStatus.includes(project.status)).length === 0
if (isLoading) {
if (isLoadingProjects || !organization) {
return (
<ul className="mx-auto grid grid-cols-1 gap-4 sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3">
<ShimmeringCard />
@@ -114,38 +106,22 @@ const ProjectList = ({
)
}
const filteredOrganizations = filterToSlug
? organizations?.filter((org) => org.slug === slug)
: organizations
return isSuccess && organizations && organizations?.length > 0 ? (
<>
{filteredOrganizations?.map((organization) => {
return (
<OrganizationProjects
key={organization.slug}
organization={organization}
projects={projectsByOrg[organization.id]}
overdueInvoices={(allOverdueInvoices ?? []).filter(
(it) => it.organization_id === organization.id
)}
resourceWarnings={resourceWarnings ?? []}
rewriteHref={rewriteHref}
isLoadingPermissions={isLoadingPermissions}
isErrorPermissions={isErrorPermissions}
permissionsError={permissionsError}
isLoadingProjects={isLoadingProjects}
isErrorProjects={isErrorProjects}
projectsError={projectsError}
search={search}
filterStatus={filterStatus}
filterToSlug={filterToSlug}
/>
)
})}
</>
) : (
<NoProjectsState slug={''} />
return (
<OrganizationProjects
key={organization.slug}
organization={organization}
projects={orgProjects}
resourceWarnings={resourceWarnings ?? []}
rewriteHref={rewriteHref}
isLoadingPermissions={isLoadingPermissions}
isErrorPermissions={isErrorPermissions}
permissionsError={permissionsError}
isLoadingProjects={isLoadingProjects}
isErrorProjects={isErrorProjects}
projectsError={projectsError}
search={search}
filterStatus={filterStatus}
/>
)
}
@@ -154,7 +130,6 @@ export default ProjectList
type OrganizationProjectsProps = {
organization: Organization
projects: ProjectInfo[]
overdueInvoices: OverdueInvoicesResponse[]
resourceWarnings: ResourceWarning[]
isLoadingPermissions: boolean
isErrorPermissions: boolean
@@ -165,13 +140,11 @@ type OrganizationProjectsProps = {
rewriteHref?: (projectRef: string) => string
search: string
filterStatus?: string[]
filterToSlug?: boolean
}
const OrganizationProjects = ({
organization,
projects,
overdueInvoices,
resourceWarnings,
isLoadingPermissions,
isErrorPermissions,
@@ -182,7 +155,6 @@ const OrganizationProjects = ({
rewriteHref,
search,
filterStatus,
filterToSlug,
}: OrganizationProjectsProps) => {
const isEmpty = !projects || projects.length === 0
const sortedProjects = [...(projects || [])].sort((a, b) => a.name.localeCompare(b.name))
@@ -234,44 +206,6 @@ const OrganizationProjects = ({
return (
<div className="space-y-3" key={organization.slug}>
{!filterToSlug && (
<div className="flex space-x-4 items-center">
<div className="flex items-center gap-2">
<h4 className="text-lg flex items-center">{organization.name}</h4>{' '}
<PartnerIcon organization={organization} />
</div>
{!!overdueInvoices.length && (
<div>
<Button asChild type="danger">
<Link href={`/org/${organization.slug}/billing#invoices`}>
Outstanding Invoices
</Link>
</Button>
</div>
)}
{organization?.restriction_status === 'grace_period' && (
<div>
<Button asChild type="warning">
<Link href={`/org/${organization.slug}/billing`}>Grace Period</Link>
</Button>
</div>
)}
{organization?.restriction_status === 'grace_period_over' && (
<div>
<Button asChild type="warning">
<Link href={`/org/${organization.slug}/billing`}>Grace Period Over</Link>
</Button>
</div>
)}
{organization?.restriction_status === 'restricted' && (
<div>
<Button asChild type="danger">
<Link href={`/org/${organization.slug}/billing`}>Services Restricted</Link>
</Button>
</div>
)}
</div>
)}
{isLoadingPermissions || isLoadingProjects ? (
<ul className="mx-auto grid grid-cols-1 gap-4 sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3">
<ShimmeringCard />
@@ -1,22 +1,12 @@
import { Filter, Search } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { IS_PLATFORM, useParams } from 'common'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useParams } from 'common'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { PROJECT_STATUS } from 'lib/constants'
import { EMPTY_ARR } from 'lib/void'
import {
Button,
Checkbox_Shadcn_,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
Input,
Label_Shadcn_,
PopoverContent_Shadcn_,
@@ -25,7 +15,6 @@ import {
} from 'ui'
interface HomePageActionsProps {
organizations?: { name: string; slug: string }[]
search: string
filterStatus: string[]
setSearch: (value: string) => void
@@ -33,57 +22,19 @@ interface HomePageActionsProps {
}
const HomePageActions = ({
organizations = EMPTY_ARR,
search,
filterStatus,
setSearch,
setFilterStatus,
}: HomePageActionsProps) => {
const newLayoutPreview = useIsNewLayoutEnabled()
const router = useRouter()
const { slug } = useParams()
const projectCreationEnabled = useIsFeatureEnabled('projects:create')
const organizationCreationEnabled = useIsFeatureEnabled('organizations:create')
const { isSuccess: orgsLoaded } = useOrganizationsQuery()
return (
<div className="flex flex-col gap-2 md:gap-3 md:flex-row">
{newLayoutPreview ? (
projectCreationEnabled ? (
<Link href={`/new/${slug}`}>
<Button type="primary">New project</Button>
</Link>
) : null
) : (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button type="primary">
<span>New project</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent side="bottom" align="start">
<>
<DropdownMenuLabel>Choose organization</DropdownMenuLabel>
<DropdownMenuSeparator />
{organizations
.sort((a, b) => a.name.localeCompare(b.name))
.map((x) => (
<DropdownMenuItem key={x.slug} onClick={() => router.push(`/new/${x.slug}`)}>
{x.name}
</DropdownMenuItem>
))}
</>
</DropdownMenuContent>
</DropdownMenu>
)}
{IS_PLATFORM && organizationCreationEnabled && orgsLoaded && !newLayoutPreview && (
<Button type="default" asChild>
<Link href="/new" className="flex items-center gap-2">
New organization
</Link>
{projectCreationEnabled && (
<Button asChild type="primary">
<Link href={`/new/${slug}`}>New project</Link>
</Button>
)}
@@ -1,4 +1,3 @@
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import {
ScaffoldContainer,
ScaffoldContainerLegacy,
@@ -18,7 +17,7 @@ import CreditBalance from './CreditBalance'
import PaymentMethods from './PaymentMethods/PaymentMethods'
import Subscription from './Subscription/Subscription'
const BillingSettings = () => {
export const BillingSettings = () => {
const {
billingAccountData: isBillingAccountDataEnabledOnProfileLevel,
billingPaymentMethods: isBillingPaymentMethodsEnabledOnProfileLevel,
@@ -31,8 +30,6 @@ const BillingSettings = () => {
'billing:invoices',
])
const newLayoutPreview = useIsNewLayoutEnabled()
const org = useSelectedOrganization()
const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: org?.slug })
const isNotOrgWithPartnerBilling = !subscription?.billing_via_partner
@@ -44,13 +41,11 @@ const BillingSettings = () => {
return (
<>
{newLayoutPreview && (
<ScaffoldContainerLegacy>
<ScaffoldTitle>Billing</ScaffoldTitle>
</ScaffoldContainerLegacy>
)}
<ScaffoldContainerLegacy>
<ScaffoldTitle>Billing</ScaffoldTitle>
</ScaffoldContainerLegacy>
<ScaffoldContainer id="subscription" className={cn(newLayoutPreview && '[&>div]:!pt-0')}>
<ScaffoldContainer id="subscription" className={cn('[&>div]:!pt-0')}>
<Subscription />
</ScaffoldContainer>
@@ -115,5 +110,3 @@ const BillingSettings = () => {
</>
)
}
export default BillingSettings
@@ -4,16 +4,14 @@ import { useState } from 'react'
import { toast } from 'sonner'
import { LOCAL_STORAGE_KEYS } from 'common'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useOrganizationDeleteMutation } from 'data/organizations/organization-delete-mutation'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { Button, Form, Input, Modal } from 'ui'
const DeleteOrganizationButton = () => {
export const DeleteOrganizationButton = () => {
const router = useRouter()
const newLayoutPreview = useIsNewLayoutEnabled()
const selectedOrganization = useSelectedOrganization()
const { slug: orgSlug, name: orgName } = selectedOrganization ?? {}
@@ -29,12 +27,8 @@ const DeleteOrganizationButton = () => {
const { mutate: deleteOrganization, isLoading: isDeleting } = useOrganizationDeleteMutation({
onSuccess: () => {
toast.success(`Successfully deleted ${orgName}`)
if (newLayoutPreview) {
setLastVisitedOrganization('')
router.push('/organizations')
} else {
router.push('/projects')
}
setLastVisitedOrganization('')
router.push('/organizations')
},
})
@@ -127,5 +121,3 @@ const DeleteOrganizationButton = () => {
</>
)
}
export default DeleteOrganizationButton
@@ -2,7 +2,7 @@ import Panel from 'components/ui/Panel'
import PartnerManagedResource from 'components/ui/PartnerManagedResource'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { Admonition } from 'ui-patterns'
import DeleteOrganizationButton from './DeleteOrganizationButton'
import { DeleteOrganizationButton } from './DeleteOrganizationButton'
const OrganizationDeletePanel = () => {
const selectedOrganization = useSelectedOrganization()
@@ -2,7 +2,6 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useCallback } from 'react'
import { toast } from 'sonner'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { EmptyIntegrationConnection } from 'components/interfaces/Integrations/VercelGithub/IntegrationPanels'
import { Markdown } from 'components/interfaces/Markdown'
import VercelSection from 'components/interfaces/Settings/Integrations/VercelIntegration/VercelSection'
@@ -43,8 +42,6 @@ const IntegrationImageHandler = ({ title }: { title: 'vercel' | 'github' }) => {
}
const IntegrationSettings = () => {
const newLayoutPreview = useIsNewLayoutEnabled()
const org = useSelectedOrganization()
const canReadGithubConnection = useCheckPermissions(
@@ -177,11 +174,9 @@ The GitHub app will watch for changes in your repository such as file changes, b
return (
<>
{newLayoutPreview && (
<ScaffoldContainerLegacy>
<ScaffoldTitle>Integrations</ScaffoldTitle>
</ScaffoldContainerLegacy>
)}
<ScaffoldContainerLegacy>
<ScaffoldTitle>Integrations</ScaffoldTitle>
</ScaffoldContainerLegacy>
<GitHubSection />
<ScaffoldDivider />
<VercelSection isProjectScoped={false} />
@@ -10,6 +10,7 @@ import { useEffect, useMemo, useState } from 'react'
import { toast } from 'sonner'
import { z } from 'zod'
import { LOCAL_STORAGE_KEYS } from 'common'
import SpendCapModal from 'components/interfaces/Billing/SpendCapModal'
import Panel from 'components/ui/Panel'
import { useOrganizationCreateMutation } from 'data/organizations/organization-create-mutation'
@@ -18,6 +19,7 @@ import {
useOrganizationsQuery,
} from 'data/organizations/organizations-query'
import { useProjectsQuery } from 'data/projects/projects-query'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { BASE_PATH, PRICING_TIER_LABELS_ORG } from 'lib/constants'
import { getURL } from 'lib/helpers'
import { useProfile } from 'lib/profile'
@@ -94,6 +96,11 @@ const NewOrgForm = ({ onPaymentMethodReset }: NewOrgFormProps) => {
const elements = useElements()
const queryClient = useQueryClient()
const [lastVisitedOrganization] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION,
''
)
const freeOrgs = (organizations || []).filter((it) => it.plan.id === 'free')
const projectsByOrg = useMemo(() => {
@@ -262,7 +269,10 @@ const NewOrgForm = ({ onPaymentMethodReset }: NewOrgFormProps) => {
<Button
type="default"
disabled={newOrgLoading}
onClick={() => router.push('/projects')}
onClick={() => {
if (!!lastVisitedOrganization) router.push(`/org/${lastVisitedOrganization}`)
else router.push('/organizations')
}}
>
Cancel
</Button>
@@ -4,7 +4,6 @@ import { useState } from 'react'
import { toast } from 'sonner'
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import {
ScaffoldActionsContainer,
ScaffoldActionsGroup,
@@ -29,8 +28,7 @@ import { InviteMemberButton } from './InviteMemberButton'
import MembersView from './MembersView'
import { hasMultipleOwners, useGetRolesManagementPermissions } from './TeamSettings.utils'
const TeamSettings = () => {
const newLayoutPreview = useIsNewLayoutEnabled()
export const TeamSettings = () => {
const [_, setLastVisitedOrganization] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION,
''
@@ -74,12 +72,8 @@ const TeamSettings = () => {
await refetchOrganizations()
toast.success(`Successfully left ${selectedOrganization?.name}`)
if (newLayoutPreview) {
setLastVisitedOrganization('')
router.push('/organizations')
} else {
router.push('/projects')
}
setLastVisitedOrganization('')
router.push('/organizations')
},
onError: (error) => {
setIsLeaving(false)
@@ -99,8 +93,7 @@ const TeamSettings = () => {
return (
<>
<ScaffoldContainerLegacy>
{newLayoutPreview && <ScaffoldTitle>Team</ScaffoldTitle>}
<ScaffoldTitle>Team</ScaffoldTitle>
<ScaffoldFilterAndContent>
<ScaffoldActionsContainer className="w-full flex-col md:flex-row gap-2 justify-between">
<Input
@@ -179,5 +172,3 @@ const TeamSettings = () => {
</>
)
}
export default TeamSettings
@@ -4,7 +4,6 @@ import Link from 'next/link'
import { useEffect, useMemo, useState } from 'react'
import { useParams } from 'common'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import {
ScaffoldContainer,
ScaffoldContainerLegacy,
@@ -29,8 +28,6 @@ import SizeAndCounts from './SizeAndCounts'
import TotalUsage from './TotalUsage'
const Usage = () => {
const newLayoutPreview = useIsNewLayoutEnabled()
const { slug, projectRef } = useParams()
const [dateRange, setDateRange] = useState<any>()
const [selectedProjectRef, setSelectedProjectRef] = useState<string>()
@@ -119,11 +116,9 @@ const Usage = () => {
return (
<>
{newLayoutPreview && (
<ScaffoldContainerLegacy>
<ScaffoldTitle>Usage</ScaffoldTitle>
</ScaffoldContainerLegacy>
)}
<ScaffoldContainerLegacy>
<ScaffoldTitle>Usage</ScaffoldTitle>
</ScaffoldContainerLegacy>
<div className="sticky top-0 border-b bg-studio z-[1] overflow-hidden ">
<ScaffoldContainer className="">
<div className="py-4 flex items-center space-x-4">
@@ -1,11 +1,9 @@
export { default as BillingSettings } from './BillingSettings/BillingSettings'
export { default as AuditLogs } from './AuditLogs/AuditLogs'
export { default as Documents } from './Documents/Documents'
export { default as GeneralSettings } from './GeneralSettings/GeneralSettings'
export { default as SecuritySettings } from './SecuritySettings/SecuritySettings'
export { default as IntegrationSettings } from './IntegrationSettings/IntegrationSettings'
export { default as InvoicesSettings } from './InvoicesSettings/InvoicesSettings'
export { default as NewOrgForm } from './NewOrg/NewOrgForm'
export { default as Usage } from './Usage/Usage'
export { default as AuditLogs } from './AuditLogs/AuditLogs'
export { default as OAuthApps } from './OAuthApps/OAuthApps'
export { default as TeamSettings } from './TeamSettings/TeamSettings'
export { default as Documents } from './Documents/Documents'
export { default as Usage } from './Usage/Usage'
@@ -33,7 +33,7 @@ export const OrganizationInvite = () => {
const { mutate: joinOrganization, isLoading: isJoining } =
useOrganizationAcceptInvitationMutation({
onSuccess: () => {
router.push('/projects')
router.push('/organizations')
},
onError: (error) => {
toast.error(`Failed to join organization: ${error.message}`)
+47 -143
View File
@@ -1,15 +1,6 @@
import { AnimatePresence, motion, MotionProps } from 'framer-motion'
import { isUndefined } from 'lodash'
import {
ArrowUpRight,
Blocks,
Boxes,
ChartArea,
PanelLeftDashed,
Receipt,
Settings,
Users,
} from 'lucide-react'
import { Blocks, Boxes, ChartArea, PanelLeftDashed, Receipt, Settings, Users } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { ComponentProps, ComponentPropsWithoutRef, FC, ReactNode, useEffect } from 'react'
@@ -22,7 +13,6 @@ import {
generateToolRoutes,
} from 'components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { ProjectIndexPageLink } from 'data/prefetchers/project.$ref'
import { useHideSidebar } from 'hooks/misc/useHideSidebar'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
@@ -45,17 +35,14 @@ import {
SidebarContent as SidebarContentPrimitive,
SidebarFooter,
SidebarGroup,
SidebarGroupLabel,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
Sidebar as SidebarPrimitive,
SidebarSeparator,
useSidebar,
} from 'ui'
import {
useIsAPIDocsSidePanelEnabled,
useIsNewLayoutEnabled,
useIsSQLEditorTabsEnabled,
} from './App/FeaturePreview/FeaturePreviewContext'
@@ -73,10 +60,6 @@ const SidebarMotion = motion(SidebarPrimitive) as FC<
export interface SidebarProps extends ComponentPropsWithoutRef<typeof SidebarPrimitive> {}
export const Sidebar = ({ className, ...props }: SidebarProps) => {
const newLayoutPreview = useIsNewLayoutEnabled()
const { ref } = useParams()
const { setOpen } = useSidebar()
const hideSideBar = useHideSidebar()
@@ -91,63 +74,56 @@ export const Sidebar = ({ className, ...props }: SidebarProps) => {
if (sidebarBehaviour === 'closed') setOpen(false)
}, [sidebarBehaviour, setOpen])
if (!newLayoutPreview && !ref) {
return null
}
return (
<>
<AnimatePresence>
{!hideSideBar && (
<SidebarMotion
{...props}
transition={{ delay: 0.4, duration: 0.4 }}
overflowing={sidebarBehaviour === 'expandable'}
collapsible="icon"
variant="sidebar"
onMouseEnter={() => {
if (sidebarBehaviour === 'expandable') setOpen(true)
}}
onMouseLeave={() => {
if (sidebarBehaviour === 'expandable') setOpen(false)
}}
>
<SidebarContent
footer={
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
type="text"
className={`w-min px-1.5 mx-0.5 ${sidebarBehaviour === 'open' ? '!px-2' : ''}`}
icon={<PanelLeftDashed size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />}
/>
</DropdownMenuTrigger>
<DropdownMenuContent side="top" align="start" className="w-40">
<DropdownMenuRadioGroup
value={sidebarBehaviour}
onValueChange={(value) => setSidebarBehaviour(value as SidebarBehaviourType)}
>
<DropdownMenuLabel>Sidebar control</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuRadioItem value="open">Expanded</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="closed">Collapsed</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="expandable">
Expand on hover
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
}
/>
</SidebarMotion>
)}
</AnimatePresence>
</>
<AnimatePresence>
{!hideSideBar && (
<SidebarMotion
{...props}
transition={{ delay: 0.4, duration: 0.4 }}
overflowing={sidebarBehaviour === 'expandable'}
collapsible="icon"
variant="sidebar"
onMouseEnter={() => {
if (sidebarBehaviour === 'expandable') setOpen(true)
}}
onMouseLeave={() => {
if (sidebarBehaviour === 'expandable') setOpen(false)
}}
>
<SidebarContent
footer={
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
type="text"
className={`w-min px-1.5 mx-0.5 ${sidebarBehaviour === 'open' ? '!px-2' : ''}`}
icon={<PanelLeftDashed size={ICON_SIZE} strokeWidth={ICON_STROKE_WIDTH} />}
/>
</DropdownMenuTrigger>
<DropdownMenuContent side="top" align="start" className="w-40">
<DropdownMenuRadioGroup
value={sidebarBehaviour}
onValueChange={(value) => setSidebarBehaviour(value as SidebarBehaviourType)}
>
<DropdownMenuLabel>Sidebar control</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuRadioItem value="open">Expanded</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="closed">Collapsed</DropdownMenuRadioItem>
<DropdownMenuRadioItem value="expandable">
Expand on hover
</DropdownMenuRadioItem>
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
}
/>
</SidebarMotion>
)}
</AnimatePresence>
)
}
export const SidebarContent = ({ footer }: { footer?: ReactNode }) => {
const newLayoutPreview = useIsNewLayoutEnabled()
const { ref: projectRef } = useParams()
return (
@@ -166,7 +142,7 @@ export const SidebarContent = ({ footer }: { footer?: ReactNode }) => {
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.2, ease: 'easeOut' }}
>
{newLayoutPreview ? <OrganizationLinks /> : <HomePageLinks />}
<OrganizationLinks />
</motion.div>
)}
</SidebarContentPrimitive>
@@ -371,78 +347,6 @@ const ProjectLinks = () => {
)
}
const HomePageLinks = () => {
const { data: organizations = [] } = useOrganizationsQuery()
const organizationLinks = organizations.map((org) => {
return { label: org.name, href: `/org/${org.slug}/general`, key: org.slug }
})
const accountLinks = [
{ label: 'Preferences', href: `/account/me`, key: 'preferences' },
{ label: 'Access Tokens', href: `/account/tokens`, key: 'account-tokens' },
{ label: 'Security', href: `/account/security`, key: 'security' },
{ label: 'Audit Logs', href: `/account/audit`, key: 'audit-logs' },
]
const docsLinks = [
{ label: 'Guides', href: `https://supabase.com/docs`, key: 'guides', icon: <ArrowUpRight /> },
{
label: 'API Reference',
href: `https://supabase.com/docs/guides/api`,
key: 'api-reference',
icon: <ArrowUpRight />,
},
]
return (
<SidebarMenu className="flex flex-col gap-y-1 items-start">
<SidebarGroupLabel className="px-4 h-auto pt-4">Organizations</SidebarGroupLabel>
<SidebarGroup className="gap-0.5">
{organizationLinks.map((x) => (
<SideBarNavLink
key={x.key}
active={false}
route={{
label: x.label,
link: x.href,
key: x.label,
}}
/>
))}
</SidebarGroup>
<SidebarSeparator className="!bg-border-overlay w-full mx-0" />
<SidebarGroupLabel className="px-4 h-auto pt-4">Account</SidebarGroupLabel>
<SidebarGroup className="gap-0.5">
{accountLinks.map((x) => (
<SideBarNavLink
key={x.key}
active={false}
route={{
label: x.label,
link: x.href,
key: x.label,
}}
/>
))}
</SidebarGroup>
<SidebarSeparator className="!bg-border-overlay w-full mx-0" />
<SidebarGroupLabel className="px-4 h-auto pt-4">Documentation</SidebarGroupLabel>
<SidebarGroup className="gap-0.5">
{docsLinks.map((x) => (
<SideBarNavLink
key={x.key}
active={false}
route={{
label: x.label,
link: x.href,
key: x.label,
icon: x.icon,
}}
/>
))}
</SidebarGroup>
</SidebarMenu>
)
}
const OrganizationLinks = () => {
const router = useRouter()
const { slug } = useParams()
@@ -23,7 +23,7 @@ const ResetPasswordForm = () => {
// logout all other sessions after changing password
await auth.signOut({ scope: 'others' })
await router.push('/projects')
await router.push('/organizations')
} else {
toast.error(`Failed to save password: ${error.message}`, { id: toastId })
if (!WHITELIST_ERRORS.some((e) => error.message.includes(e))) {
@@ -70,7 +70,7 @@ const SignInForm = () => {
await queryClient.resetQueries()
const returnTo = getReturnToPath()
// since we're already on the /sign-in page, prevent redirect loops
router.push(returnTo === '/sign-in' ? '/projects' : returnTo)
router.push(returnTo === '/sign-in' ? '/organizations' : returnTo)
} catch (error: any) {
toast.error(`Failed to sign in: ${(error as AuthError).message}`, { id: toastId })
Sentry.captureMessage('[CRITICAL] Failed to sign in via EP: ' + error.message)
@@ -4,11 +4,10 @@ import Link from 'next/link'
import { useRouter } from 'next/router'
import { PropsWithChildren, useEffect } from 'react'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { LOCAL_STORAGE_KEYS } from 'common'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { withAuth } from 'hooks/misc/withAuth'
import { IS_PLATFORM } from 'lib/constants'
import { LOCAL_STORAGE_KEYS } from 'common'
import { useAppStateSnapshot } from 'state/app-state'
import { cn, NavMenu, NavMenuItem } from 'ui'
import {
@@ -25,7 +24,6 @@ export interface AccountLayoutProps {
const AccountLayout = ({ children, title }: PropsWithChildren<AccountLayoutProps>) => {
const router = useRouter()
const appSnap = useAppStateSnapshot()
const newLayoutPreview = useIsNewLayoutEnabled()
const [lastVisitedOrganization] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION,
@@ -66,36 +64,32 @@ const AccountLayout = ({ children, title }: PropsWithChildren<AccountLayoutProps
<title>{title ? `${title} | Supabase` : 'Supabase'}</title>
<meta name="description" content="Supabase Studio" />
</Head>
<div className={cn(newLayoutPreview ? 'flex flex-col h-screen w-screen' : '')}>
{newLayoutPreview && (
<>
<ScaffoldContainerLegacy>
<Link
href={backToDashboardURL}
className="flex text-xs flex-row gap-2 items-center text-foreground-lighter focus-visible:text-foreground hover:text-foreground"
<div className={cn('flex flex-col h-screen w-screen')}>
<ScaffoldContainerLegacy>
<Link
href={backToDashboardURL}
className="flex text-xs flex-row gap-2 items-center text-foreground-lighter focus-visible:text-foreground hover:text-foreground"
>
<ArrowLeft strokeWidth={1.5} size={14} />
Back to dashboard
</Link>
<ScaffoldTitle>Account settings</ScaffoldTitle>
</ScaffoldContainerLegacy>
<div className="border-b">
<NavMenu
className={cn(PADDING_CLASSES, MAX_WIDTH_CLASSES, 'border-none')}
aria-label="Organization menu navigation"
>
{accountLinks.map((item, i) => (
<NavMenuItem
key={`${item.key}-${i}`}
active={(item.key === currentPath || item.keys?.includes(currentPath)) ?? false}
>
<ArrowLeft strokeWidth={1.5} size={14} />
Back to dashboard
</Link>
<ScaffoldTitle>Account settings</ScaffoldTitle>
</ScaffoldContainerLegacy>
<div className="border-b">
<NavMenu
className={cn(PADDING_CLASSES, MAX_WIDTH_CLASSES, 'border-none')}
aria-label="Organization menu navigation"
>
{accountLinks.map((item, i) => (
<NavMenuItem
key={`${item.key}-${i}`}
active={(item.key === currentPath || item.keys?.includes(currentPath)) ?? false}
>
<Link href={item.href}>{item.label}</Link>
</NavMenuItem>
))}
</NavMenu>
</div>
</>
)}
<Link href={item.href}>{item.label}</Link>
</NavMenuItem>
))}
</NavMenu>
</div>
{children}
</div>
</>
@@ -2,13 +2,10 @@ import Link from 'next/link'
import { usePathname } from 'next/navigation'
import { PropsWithChildren } from 'react'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { Button, cn } from 'ui'
import { ScaffoldContainerLegacy } from '../Scaffold'
const AccountSettingsLayout = ({ children }: PropsWithChildren) => {
const newLayoutPreview = useIsNewLayoutEnabled()
export const AccountSettingsLayout = ({ children }: PropsWithChildren) => {
const pathname = usePathname()
const links = [
{
@@ -32,10 +29,6 @@ const AccountSettingsLayout = ({ children }: PropsWithChildren) => {
},
]
if (!newLayoutPreview) {
return children
}
return (
<ScaffoldContainerLegacy className="flex flex-row py-8 gap-20">
<nav className="py-0">
@@ -63,5 +56,3 @@ const AccountSettingsLayout = ({ children }: PropsWithChildren) => {
</ScaffoldContainerLegacy>
)
}
export default AccountSettingsLayout
@@ -2,7 +2,7 @@ import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
import { useAppStateSnapshot } from 'state/app-state'
import { AiIconAnimation, Button } from 'ui'
const AssistantButton = () => {
export const AssistantButton = () => {
const snap = useAiAssistantStateSnapshot()
const { setEditorPanel } = useAppStateSnapshot()
@@ -21,5 +21,3 @@ const AssistantButton = () => {
</Button>
)
}
export default AssistantButton
@@ -4,7 +4,6 @@ import { useRouter } from 'next/router'
import { useState } from 'react'
import { useParams } from 'common'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { Branch, useBranchesQuery } from 'data/branches/branches-query'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
@@ -63,9 +62,7 @@ const BranchLink = ({
)
}
const BranchDropdown = () => {
const newLayoutPreview = useIsNewLayoutEnabled()
export const BranchDropdown = () => {
const router = useRouter()
const { ref } = useParams()
const projectDetails = useSelectedProject()
@@ -102,41 +99,23 @@ const BranchDropdown = () => {
{isSuccess && branches.length > 0 && (
<>
{newLayoutPreview && (
<Link
href={`/project/${ref}`}
className="flex items-center gap-2 flex-shrink-0 text-sm"
>
<span className="text-foreground max-w-32 lg:max-w-none truncate">
{selectedBranch?.name}
</span>
{selectedBranch?.is_default ? (
<Badge variant="warning">Production</Badge>
) : (
<Badge variant="brand">Preview Branch</Badge>
)}
</Link>
)}
<Link href={`/project/${ref}`} className="flex items-center gap-2 flex-shrink-0 text-sm">
<span className="text-foreground max-w-32 lg:max-w-none truncate">
{selectedBranch?.name}
</span>
{selectedBranch?.is_default ? (
<Badge variant="warning">Production</Badge>
) : (
<Badge variant="brand">Preview Branch</Badge>
)}
</Link>
<Popover_Shadcn_ open={open} onOpenChange={setOpen} modal={false}>
<PopoverTrigger_Shadcn_ asChild>
{newLayoutPreview ? (
<Button
type="text"
className={cn('px-0.25 [&_svg]:w-5 [&_svg]:h-5 ml-1')}
iconRight={<ChevronsUpDown strokeWidth={1.5} />}
/>
) : (
<Button type="text" className="pr-2" iconRight={<ChevronsUpDown />}>
<div className="flex items-center space-x-2">
<p className={'text-xs'}>{selectedBranch?.name}</p>
{selectedBranch?.is_default ? (
<Badge variant="warning">Production</Badge>
) : (
<Badge variant="brand">Preview Branch</Badge>
)}
</div>
</Button>
)}
<Button
type="text"
className={cn('px-0.25 [&_svg]:w-5 [&_svg]:h-5 ml-1')}
iconRight={<ChevronsUpDown strokeWidth={1.5} />}
/>
</PopoverTrigger_Shadcn_>
<PopoverContent_Shadcn_ className="p-0" side="bottom" align="start">
<Command_Shadcn_>
@@ -209,5 +188,3 @@ const BranchDropdown = () => {
</>
)
}
export default BranchDropdown
@@ -6,11 +6,7 @@ import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
import { useAppStateSnapshot } from 'state/app-state'
interface EnableBranchingButtonProps {
isNewNav?: boolean
}
const EnableBranchingButton = ({ isNewNav = false }: EnableBranchingButtonProps) => {
export const EnableBranchingButton = () => {
const snap = useAppStateSnapshot()
const project = useSelectedProject()
@@ -42,5 +38,3 @@ const EnableBranchingButton = ({ isNewNav = false }: EnableBranchingButtonProps)
</ButtonTooltip>
)
}
export default EnableBranchingButton
@@ -3,7 +3,7 @@ import { useAiAssistantStateSnapshot } from 'state/ai-assistant-state'
import { useAppStateSnapshot } from 'state/app-state'
import { Button } from 'ui'
const InlineEditorButton = () => {
export const InlineEditorButton = () => {
const { closeAssistant } = useAiAssistantStateSnapshot()
const { setEditorPanel, editorPanel } = useAppStateSnapshot()
@@ -22,5 +22,3 @@ const InlineEditorButton = () => {
</Button>
)
}
export default InlineEditorButton
@@ -4,7 +4,6 @@ import { useRouter } from 'next/router'
import { useState } from 'react'
import { useParams } from 'common'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import PartnerIcon from 'components/ui/PartnerIcon'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
@@ -27,9 +26,7 @@ import {
cn,
} from 'ui'
const OrganizationDropdown = () => {
const newLayoutPreview = useIsNewLayoutEnabled()
export const OrganizationDropdown = () => {
const router = useRouter()
const { slug: routeSlug } = useParams()
const selectedOrganization = useSelectedOrganization()
@@ -48,31 +45,20 @@ const OrganizationDropdown = () => {
return (
<>
{newLayoutPreview && (
<Link href={`/org/${slug}`} className="flex items-center gap-2 flex-shrink-0 text-sm">
<Boxes size={14} strokeWidth={1.5} className="text-foreground-lighter" />
<span className="text-foreground max-w-32 lg:max-w-none truncate hidden md:block">
{orgName}
</span>
<Badge variant="default">{selectedOrganization?.plan.name}</Badge>
</Link>
)}
<Link href={`/org/${slug}`} className="flex items-center gap-2 flex-shrink-0 text-sm">
<Boxes size={14} strokeWidth={1.5} className="text-foreground-lighter" />
<span className="text-foreground max-w-32 lg:max-w-none truncate hidden md:block">
{orgName}
</span>
<Badge variant="default">{selectedOrganization?.plan.name}</Badge>
</Link>
<Popover_Shadcn_ open={open} onOpenChange={setOpen} modal={false}>
<PopoverTrigger_Shadcn_ asChild>
{newLayoutPreview ? (
<Button
type="text"
className={cn('px-1.5 py-4 [&_svg]:w-5 [&_svg]:h-5 ml-1')}
iconRight={<ChevronsUpDown strokeWidth={1.5} />}
/>
) : (
<Button type="text" className="pr-2" iconRight={<ChevronsUpDown />}>
<div className="flex items-center space-x-2">
<p className={'text-xs'}>{orgName}</p>
<Badge variant="default">{selectedOrganization?.plan.name}</Badge>
</div>
</Button>
)}
<Button
type="text"
className={cn('px-1.5 py-4 [&_svg]:w-5 [&_svg]:h-5 ml-1')}
iconRight={<ChevronsUpDown strokeWidth={1.5} />}
/>
</PopoverTrigger_Shadcn_>
<PopoverContent_Shadcn_ className="p-0" side="bottom" align="start">
<Command_Shadcn_>
@@ -108,25 +94,21 @@ const OrganizationDropdown = () => {
})}
</ScrollArea>
</CommandGroup_Shadcn_>
{newLayoutPreview && (
<>
<CommandSeparator_Shadcn_ />
<CommandGroup_Shadcn_>
<CommandItem_Shadcn_
className="cursor-pointer w-full"
onSelect={(e) => {
setOpen(false)
router.push(`/organizations`)
}}
onClick={() => setOpen(false)}
>
<Link href="/organizations" className="flex items-center gap-2 w-full">
<p>All Organizations</p>
</Link>
</CommandItem_Shadcn_>
</CommandGroup_Shadcn_>
</>
)}
<CommandSeparator_Shadcn_ />
<CommandGroup_Shadcn_>
<CommandItem_Shadcn_
className="cursor-pointer w-full"
onSelect={(e) => {
setOpen(false)
router.push(`/organizations`)
}}
onClick={() => setOpen(false)}
>
<Link href="/organizations" className="flex items-center gap-2 w-full">
<p>All Organizations</p>
</Link>
</CommandItem_Shadcn_>
</CommandGroup_Shadcn_>
{organizationCreationEnabled && (
<>
<CommandSeparator_Shadcn_ />
@@ -154,5 +136,3 @@ const OrganizationDropdown = () => {
</>
)
}
export default OrganizationDropdown
@@ -5,7 +5,6 @@ import { ParsedUrlQuery } from 'querystring'
import { useState } from 'react'
import { useParams } from 'common'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { ProjectInfo, useProjectsQuery } from 'data/projects/projects-query'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
@@ -88,13 +87,7 @@ const ProjectLink = ({
)
}
interface ProjectDropdownProps {
isNewNav?: boolean
}
const ProjectDropdown = ({ isNewNav = true }: ProjectDropdownProps) => {
const newLayoutPreview = useIsNewLayoutEnabled()
export const ProjectDropdown = () => {
const router = useRouter()
const { ref } = useParams()
const projectDetails = useSelectedProject()
@@ -121,31 +114,21 @@ const ProjectDropdown = ({ isNewNav = true }: ProjectDropdownProps) => {
return IS_PLATFORM ? (
<>
{newLayoutPreview && (
<Link
href={`/project/${project?.ref}`}
className="flex items-center gap-2 flex-shrink-0 text-sm"
>
<Box size={14} strokeWidth={1.5} className="text-foreground-lighter" />
<span className="text-foreground max-w-32 lg:max-w-none truncate">{project?.name}</span>
</Link>
)}
<Link
href={`/project/${project?.ref}`}
className="flex items-center gap-2 flex-shrink-0 text-sm"
>
<Box size={14} strokeWidth={1.5} className="text-foreground-lighter" />
<span className="text-foreground max-w-32 lg:max-w-none truncate">{project?.name}</span>
</Link>
<Popover_Shadcn_ open={open} onOpenChange={setOpen} modal={false}>
<PopoverTrigger_Shadcn_ asChild>
{newLayoutPreview ? (
<Button
type="text"
size="tiny"
className={cn('px-1.5 py-4 [&_svg]:w-5 [&_svg]:h-5 ml-1')}
iconRight={<ChevronsUpDown strokeWidth={1.5} />}
/>
) : (
<Button type="text" className="pr-2" iconRight={<ChevronsUpDown />}>
<div className="flex items-center space-x-2">
<p className={isNewNav ? 'text-sm' : 'text-xs'}>{selectedProject?.name}</p>
</div>
</Button>
)}
<Button
type="text"
size="tiny"
className={cn('px-1.5 py-4 [&_svg]:w-5 [&_svg]:h-5 ml-1')}
iconRight={<ChevronsUpDown strokeWidth={1.5} />}
/>
</PopoverTrigger_Shadcn_>
<PopoverContent_Shadcn_ className="p-0" side="bottom" align="start">
<Command_Shadcn_>
@@ -192,9 +175,7 @@ const ProjectDropdown = ({ isNewNav = true }: ProjectDropdownProps) => {
</>
) : (
<Button type="text">
<span className={isNewNav ? 'text-sm' : 'text-xs'}>{selectedProject?.name}</span>
<span className="text-sm">{selectedProject?.name}</span>
</Button>
)
}
export default ProjectDropdown
@@ -3,9 +3,7 @@ import { PropsWithChildren } from 'react'
import { useParams } from 'common'
import { AppBannerWrapper } from 'components/interfaces/App'
import { AppBannerContextProvider } from 'components/interfaces/App/AppBannerWrapperContext'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { Sidebar } from 'components/interfaces/Sidebar'
import { useShowLayoutHeader } from 'hooks/misc/useShowLayoutHeader'
import { useRouter } from 'next/router'
import { SidebarProvider } from 'ui'
import { LayoutHeader } from './ProjectLayout/LayoutHeader'
@@ -27,9 +25,6 @@ export interface DefaultLayoutProps {
* - First level side navigation bar (e.g For navigating to Table Editor, SQL Editor, Database page, etc)
*/
const DefaultLayout = ({ children, headerTitle }: PropsWithChildren<DefaultLayoutProps>) => {
const newLayoutPreview = useIsNewLayoutEnabled()
const showLayoutHeader = useShowLayoutHeader()
const { ref } = useParams()
const router = useRouter()
const showProductMenu = !!ref && router.pathname !== '/project/[ref]'
@@ -43,9 +38,7 @@ const DefaultLayout = ({ children, headerTitle }: PropsWithChildren<DefaultLayou
<AppBannerWrapper />
<div className="flex-shrink-0">
<MobileNavigationBar />
{newLayoutPreview || showLayoutHeader ? (
<LayoutHeader showProductMenu={showProductMenu} headerTitle={headerTitle} />
) : null}
<LayoutHeader showProductMenu={showProductMenu} headerTitle={headerTitle} />
</div>
{/* Main Content Area */}
<div className="flex flex-1 w-full overflow-y-hidden">
@@ -1,25 +1,19 @@
import { ExternalLink } from 'lucide-react'
import { useRouter } from 'next/router'
import { type PropsWithChildren } from 'react'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import PartnerIcon from 'components/ui/PartnerIcon'
import { PARTNER_TO_NAME } from 'components/ui/PartnerManagedResource'
import { useVercelRedirectQuery } from 'data/integrations/vercel-redirect-query'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { withAuth } from 'hooks/misc/withAuth'
import { IS_PLATFORM } from 'lib/constants'
import { Alert_Shadcn_, AlertTitle_Shadcn_, Button, cn } from 'ui'
import { SidebarSection } from './AccountLayout/AccountLayout.types'
import WithSidebar from './AccountLayout/WithSidebar'
import LayoutHeader from './ProjectLayout/LayoutHeader/LayoutHeader'
const OrganizationLayoutContent = ({ children }: PropsWithChildren<{}>) => {
const selectedOrganization = useSelectedOrganization()
const { data, isSuccess } = useVercelRedirectQuery({
installationId: selectedOrganization?.partner_id,
})
return (
<div className={cn('w-full flex flex-col overflow-hidden')}>
{selectedOrganization && selectedOrganization?.managed_by !== 'supabase' && (
@@ -44,119 +38,7 @@ const OrganizationLayoutContent = ({ children }: PropsWithChildren<{}>) => {
}
const OrganizationLayout = ({ children }: PropsWithChildren<{}>) => {
const router = useRouter()
const newLayoutPreview = useIsNewLayoutEnabled()
const selectedOrganization = useSelectedOrganization()
const { data: organizations } = useOrganizationsQuery()
const organizationsLinks = (organizations ?? [])
.map((organization) => ({
isActive:
router.pathname.startsWith('/org/') && selectedOrganization?.slug === organization.slug,
label: organization.name,
href: `/org/${organization.slug}/general`,
key: organization.slug,
icon: <PartnerIcon organization={organization} />,
}))
.sort((a, b) => a.label.localeCompare(b.label))
const sectionsWithHeaders: SidebarSection[] = [
{
heading: 'Projects',
key: 'projects',
links: [
{
isActive: router.pathname === '/projects',
label: 'All projects',
href: '/projects',
key: 'all-projects-item',
},
],
},
...(IS_PLATFORM && organizationsLinks?.length > 0
? [
{
heading: 'Organizations',
key: 'organizations',
links: organizationsLinks,
},
]
: []),
...(IS_PLATFORM
? [
{
heading: 'Account',
key: 'account',
links: [
{
isActive: router.pathname === `/account/me`,
label: 'Preferences',
href: `/account/me`,
key: `/account/me`,
},
{
isActive: router.pathname === `/account/tokens`,
label: 'Access Tokens',
href: `/account/tokens`,
key: `/account/tokens`,
},
{
isActive: router.pathname === `/account/security`,
label: 'Security',
href: `/account/security`,
key: `/account/security`,
},
{
isActive: router.pathname === `/account/audit`,
label: 'Audit Logs',
href: `/account/audit`,
key: `/account/audit`,
},
],
},
]
: []),
{
heading: 'Documentation',
key: 'documentation',
links: [
{
key: 'ext-guides',
label: 'Guides',
href: 'https://supabase.com/docs',
isExternal: true,
},
{
key: 'ext-guides',
label: 'API Reference',
href: 'https://supabase.com/docs/guides/api',
isExternal: true,
},
],
},
]
if (newLayoutPreview) {
return (
<>
{!newLayoutPreview && <LayoutHeader />}
<OrganizationLayoutContent>{children}</OrganizationLayoutContent>
</>
)
}
return (
<WithSidebar
title={selectedOrganization?.name ?? 'Supabase'}
breadcrumbs={[{ key: `org-settings`, label: 'Settings' }]}
sections={sectionsWithHeaders}
>
{!newLayoutPreview && <LayoutHeader />}
<OrganizationLayoutContent>{children}</OrganizationLayoutContent>
</WithSidebar>
)
return <OrganizationLayoutContent>{children}</OrganizationLayoutContent>
}
export default withAuth(OrganizationLayout)
@@ -4,7 +4,7 @@ interface BreadcrumbsViewProps {
defaultValue: any
}
const BreadcrumbsView = ({ defaultValue: breadcrumbs }: BreadcrumbsViewProps) => {
export const BreadcrumbsView = ({ defaultValue: breadcrumbs }: BreadcrumbsViewProps) => {
return (
<>
{breadcrumbs?.length
@@ -42,5 +42,3 @@ const BreadcrumbsView = ({ defaultValue: breadcrumbs }: BreadcrumbsViewProps) =>
</>
)
}
export default BreadcrumbsView
@@ -16,7 +16,7 @@ import {
} from 'ui'
import { useProjectContext } from '../ProjectContext'
const HelpPopover = () => {
export const HelpPopover = () => {
const router = useRouter()
const { project } = useProjectContext()
const org = useSelectedOrganization()
@@ -144,5 +144,3 @@ const HelpPopover = () => {
</Popover_Shadcn_>
)
}
export default HelpPopover
@@ -2,17 +2,13 @@ import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { LOCAL_STORAGE_KEYS } from 'common'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useShowLayoutHeader } from 'hooks/misc/useShowLayoutHeader'
import { IS_PLATFORM } from 'lib/constants'
import { LOCAL_STORAGE_KEYS } from 'common'
export const HomeIcon = () => {
const newLayoutPreview = useIsNewLayoutEnabled()
const showLayoutHeader = useShowLayoutHeader()
const selectedOrganization = useSelectedOrganization()
const { data: organizations } = useOrganizationsQuery()
@@ -22,20 +18,14 @@ export const HomeIcon = () => {
''
)
if (!showLayoutHeader && !newLayoutPreview) return null
const getDefaultOrgRedirect = () => {
if (lastVisitedOrganization) return `/org/${lastVisitedOrganization}`
if (selectedOrganization?.slug) return `/org/${selectedOrganization.slug}`
if (organizations && organizations.length > 0) return `/org/${organizations[0].slug}`
return '/projects'
return '/organizations'
}
const href = IS_PLATFORM
? newLayoutPreview
? getDefaultOrgRedirect()
: `/projects`
: '/project/default'
const href = IS_PLATFORM ? getDefaultOrgRedirect() : '/project/default'
return (
<Link href={href} className="items-center justify-center flex-shrink-0 hidden md:flex">
@@ -3,30 +3,28 @@ import Link from 'next/link'
import { ReactNode, useMemo } from 'react'
import { useParams } from 'common'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import Connect from 'components/interfaces/Connect/Connect'
import { Connect } from 'components/interfaces/Connect/Connect'
import { LocalDropdown } from 'components/interfaces/LocalDropdown'
import { UserDropdown } from 'components/interfaces/UserDropdown'
import AssistantButton from 'components/layouts/AppLayout/AssistantButton'
import BranchDropdown from 'components/layouts/AppLayout/BranchDropdown'
import EnableBranchingButton from 'components/layouts/AppLayout/EnableBranchingButton/EnableBranchingButton'
import InlineEditorButton from 'components/layouts/AppLayout/InlineEditorButton'
import OrganizationDropdown from 'components/layouts/AppLayout/OrganizationDropdown'
import ProjectDropdown from 'components/layouts/AppLayout/ProjectDropdown'
import { AssistantButton } from 'components/layouts/AppLayout/AssistantButton'
import { BranchDropdown } from 'components/layouts/AppLayout/BranchDropdown'
import { EnableBranchingButton } from 'components/layouts/AppLayout/EnableBranchingButton/EnableBranchingButton'
import { InlineEditorButton } from 'components/layouts/AppLayout/InlineEditorButton'
import { OrganizationDropdown } from 'components/layouts/AppLayout/OrganizationDropdown'
import { ProjectDropdown } from 'components/layouts/AppLayout/ProjectDropdown'
import { getResourcesExceededLimitsOrg } from 'components/ui/OveragesBanner/OveragesBanner.utils'
import { useOrgUsageQuery } from 'data/usage/org-usage-query'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { useSelectedProject } from 'hooks/misc/useSelectedProject'
import { useShowLayoutHeader } from 'hooks/misc/useShowLayoutHeader'
import { IS_PLATFORM } from 'lib/constants'
import { useAppStateSnapshot } from 'state/app-state'
import { Badge, cn } from 'ui'
import BreadcrumbsView from './BreadcrumbsView'
import { BreadcrumbsView } from './BreadcrumbsView'
import { FeedbackDropdown } from './FeedbackDropdown'
import HelpPopover from './HelpPopover'
import { HelpPopover } from './HelpPopover'
import { HomeIcon } from './HomeIcon'
import { LocalVersionPopover } from './LocalVersionPopover'
import NotificationsPopoverV2 from './NotificationsPopoverV2/NotificationsPopover'
import { NotificationsPopoverV2 } from './NotificationsPopoverV2/NotificationsPopover'
const LayoutHeaderDivider = ({ className, ...props }: React.HTMLProps<HTMLSpanElement>) => (
<span className={cn('text-border-stronger pr-2', className)} {...props}>
@@ -59,9 +57,6 @@ const LayoutHeader = ({
headerTitle,
showProductMenu,
}: LayoutHeaderProps) => {
const newLayoutPreview = useIsNewLayoutEnabled()
const showLayoutHeader = useShowLayoutHeader()
const { ref: projectRef, slug } = useParams()
const selectedProject = useSelectedProject()
const selectedOrganization = useSelectedOrganization()
@@ -108,75 +103,17 @@ const LayoutHeader = ({
>
<div className="flex items-center text-sm">
<HomeIcon />
<>
<div className="flex items-center md:pl-2">
{showOrgSelection &&
// hides org dropdown for old layout
(newLayoutPreview || showLayoutHeader) &&
IS_PLATFORM ? (
<>
<LayoutHeaderDivider className="hidden md:block" />
<OrganizationDropdown />
</>
) : null}
<AnimatePresence>
{projectRef && (
<motion.div
className="flex items-center"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{
duration: 0.15,
ease: 'easeOut',
}}
>
<LayoutHeaderDivider />
<ProjectDropdown />
{exceedingLimits && (
<div className="ml-2">
<Link href={`/org/${selectedOrganization?.slug}/usage`}>
<Badge variant="destructive" className="whitespace-nowrap">
Exceeding usage limits
</Badge>
</Link>
</div>
)}
{selectedProject && isBranchingEnabled && (
<>
<LayoutHeaderDivider />
<BranchDropdown />
</>
)}
</motion.div>
)}
</AnimatePresence>
<AnimatePresence>
{headerTitle && (
<motion.div
className="flex items-center"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{
duration: 0.15,
ease: 'easeOut',
}}
>
<LayoutHeaderDivider />
<span className="text-foreground">{headerTitle}</span>
</motion.div>
)}
</AnimatePresence>
</div>
<div className="flex items-center md:pl-2">
{showOrgSelection && IS_PLATFORM ? (
<>
<LayoutHeaderDivider className="hidden md:block" />
<OrganizationDropdown />
</>
) : null}
<AnimatePresence>
{projectRef && (
<motion.div
className="ml-3 items-center gap-x-3 flex"
className="flex items-center"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
@@ -185,12 +122,65 @@ const LayoutHeader = ({
ease: 'easeOut',
}}
>
{<Connect />}
{!isBranchingEnabled && IS_PLATFORM && <EnableBranchingButton />}
<LayoutHeaderDivider />
<ProjectDropdown />
{exceedingLimits && (
<div className="ml-2">
<Link href={`/org/${selectedOrganization?.slug}/usage`}>
<Badge variant="destructive" className="whitespace-nowrap">
Exceeding usage limits
</Badge>
</Link>
</div>
)}
{selectedProject && isBranchingEnabled && (
<>
<LayoutHeaderDivider />
<BranchDropdown />
</>
)}
</motion.div>
)}
</AnimatePresence>
</>
<AnimatePresence>
{headerTitle && (
<motion.div
className="flex items-center"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{
duration: 0.15,
ease: 'easeOut',
}}
>
<LayoutHeaderDivider />
<span className="text-foreground">{headerTitle}</span>
</motion.div>
)}
</AnimatePresence>
</div>
<AnimatePresence>
{projectRef && (
<motion.div
className="ml-3 items-center gap-x-3 flex"
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
transition={{
duration: 0.15,
ease: 'easeOut',
}}
>
{<Connect />}
{!isBranchingEnabled && IS_PLATFORM && <EnableBranchingButton />}
</motion.div>
)}
</AnimatePresence>
<BreadcrumbsView defaultValue={breadcrumbs} />
</div>
<div className="flex items-center gap-x-2">
@@ -27,7 +27,7 @@ import {
import NotificationRow from './NotificationRow'
import { NotificationsFilter } from './NotificationsFilter'
const NotificationsPopoverV2 = () => {
export const NotificationsPopoverV2 = () => {
const [open, setOpen] = useState(false)
const [activeTab, setActiveTab] = useState<'inbox' | 'archived'>('inbox')
@@ -278,5 +278,3 @@ const NotificationsPopoverV2 = () => {
</Popover_Shadcn_>
)
}
export default NotificationsPopoverV2
@@ -29,7 +29,7 @@ const MobileNavigationBar = () => {
)}
>
<Link
href={IS_PLATFORM ? '/projects' : `/project/${projectRef}`}
href={IS_PLATFORM ? '/organizations' : `/project/${projectRef}`}
className="flex items-center h-[26px] w-[26px] min-w-[26px]"
>
<img
@@ -2,7 +2,6 @@ import Link from 'next/link'
import { PropsWithChildren } from 'react'
import { useParams } from 'common'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useCurrentPath } from 'hooks/misc/useCurrentPath'
import { useFlag } from 'hooks/ui/useFlag'
import { NavMenu, NavMenuItem } from 'ui'
@@ -10,28 +9,20 @@ import { ScaffoldContainerLegacy, ScaffoldTitle } from '../Scaffold'
function OrganizationSettingsLayout({ children }: PropsWithChildren) {
const { slug } = useParams()
const newLayoutPreview = useIsNewLayoutEnabled()
// [Joshen] RE Organization Settings - need to figure out how to enforce MFA across users before this goes live
const newSecurityPage = useFlag('showOrganizationSecuritySettings')
// Get the path without any hash values
const fullCurrentPath = useCurrentPath()
const [currentPath] = fullCurrentPath.split('#')
// hide these settings in the new layout
// when path equals `/org/${slug}/team`
// or `/org/${slug}/integrations`
// or `/org/${slug}/usage`
// make the function
// [Joshen] RE Organization Settings - need to figure out how to enforce MFA across users before this goes live
const newSecurityPage = useFlag('showOrganizationSecuritySettings')
// Hide these settings in the new layout on the following paths
const isHidden = (path: string) => {
return (
newLayoutPreview &&
(path === `/org/${slug}/team` ||
path === `/org/${slug}/integrations` ||
path === `/org/${slug}/usage` ||
path === `/org/${slug}/billing`)
path === `/org/${slug}/team` ||
path === `/org/${slug}/integrations` ||
path === `/org/${slug}/usage` ||
path === `/org/${slug}/billing`
)
}
@@ -48,22 +39,6 @@ function OrganizationSettingsLayout({ children }: PropsWithChildren) {
label: 'Security',
href: `/org/${slug}/security`,
},
!newLayoutPreview && {
label: 'Team',
href: `/org/${slug}/team`,
},
!newLayoutPreview && {
label: 'Integrations',
href: `/org/${slug}/integrations`,
},
!newLayoutPreview && {
label: 'Billing',
href: `/org/${slug}/billing`,
},
!newLayoutPreview && {
label: 'Usage',
href: `/org/${slug}/usage`,
},
{
label: 'OAuth Apps',
href: `/org/${slug}/apps`,
@@ -26,7 +26,7 @@ const ForgotPasswordLayout = ({
<nav className="relative flex items-center justify-between sm:h-10">
<div className="flex flex-shrink-0 flex-grow items-center lg:flex-grow-0">
<div className="flex w-full items-center justify-between md:w-auto">
<Link href={logoLinkToMarketingSite ? 'https://supabase.com' : '/projects'}>
<Link href={logoLinkToMarketingSite ? 'https://supabase.com' : '/organizations'}>
<Image
src={
resolvedTheme?.includes('dark')
@@ -91,7 +91,7 @@ const SignInLayout = ({
<nav className="relative flex items-center justify-between sm:h-10">
<div className="flex items-center flex-grow flex-shrink-0 lg:flex-grow-0">
<div className="flex items-center justify-between w-full md:w-auto">
<Link href={logoLinkToMarketingSite ? 'https://supabase.com' : '/projects'}>
<Link href={logoLinkToMarketingSite ? 'https://supabase.com' : '/organizations'}>
<Image
src={
resolvedTheme?.includes('dark')
@@ -1,23 +1,8 @@
import Link from 'next/link'
import { PropsWithChildren } from 'react'
import { withAuth } from 'hooks/misc/withAuth'
import { BASE_PATH } from 'lib/constants'
import { ChevronRight } from 'lucide-react'
import type { Organization, Project } from 'types'
import { FeedbackDropdown } from './ProjectLayout/LayoutHeader/FeedbackDropdown'
import HelpPopover from './ProjectLayout/LayoutHeader/HelpPopover'
interface WizardLayoutProps {
organization: Organization | null | undefined
project: Project | null
}
const WizardLayout = ({
organization,
project,
children,
}: PropsWithChildren<WizardLayoutProps>) => {
const WizardLayout = ({ children }: PropsWithChildren<{}>) => {
return (
<div className="flex w-full flex-col">
<div className="overflow-auto">
+1 -5
View File
@@ -1,9 +1,6 @@
import { usePathname } from 'next/navigation'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
export function useHideSidebar() {
const newLayoutPreview = useIsNewLayoutEnabled()
const pathname = usePathname() ?? ''
const shouldHide =
@@ -11,8 +8,7 @@ export function useHideSidebar() {
pathname.startsWith('/new') ||
pathname === '/support/new' ||
pathname === '/organizations' ||
pathname === '/sign-in' ||
(pathname === '/projects' && newLayoutPreview)
pathname === '/sign-in'
return shouldHide
}
@@ -1,6 +1,5 @@
import dayjs from 'dayjs'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { RESTRICTION_MESSAGES } from 'components/interfaces/Organization/restriction.constants'
import { useOverdueInvoicesQuery } from 'data/invoices/invoices-overdue-query'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
@@ -15,7 +14,6 @@ export type WarningBannerProps = {
export function useOrganizationRestrictions() {
const org = useSelectedOrganization()
const isNewLayout = useIsNewLayoutEnabled()
const { data: overdueInvoices } = useOverdueInvoicesQuery()
const { data: organizations } = useOrganizationsQuery()
@@ -38,7 +36,7 @@ export function useOrganizationRestrictions() {
})
}
if (overdueInvoicesFromOtherOrgs?.length && isNewLayout) {
if (overdueInvoicesFromOtherOrgs?.length) {
warnings.push({
type: 'danger',
title: RESTRICTION_MESSAGES.OVERDUE_INVOICES_FROM_OTHER_ORGS.title,
@@ -1,19 +0,0 @@
import { useParams } from 'common'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { useRouter } from 'next/router'
export function useShowLayoutHeader() {
const newLayoutPreview = useIsNewLayoutEnabled()
const { ref } = useParams()
const router = useRouter()
const pathname = router.pathname ?? ''
const shouldShow =
pathname.startsWith('/new') ||
pathname === '/organizations' ||
pathname === '/sign-in' ||
newLayoutPreview ||
Boolean(ref)
return shouldShow
}
+1 -2
View File
@@ -3,8 +3,7 @@ import { getAccessToken, gotrueClient, type User } from 'common'
export const auth = gotrueClient
export { getAccessToken }
// [Joshen] We can flip this to /organizations once the new nav layout is permanent
export const DEFAULT_FALLBACK_PATH = '/projects'
export const DEFAULT_FALLBACK_PATH = '/organizations'
export const validateReturnTo = (
returnTo: string,
+6 -1
View File
@@ -154,7 +154,7 @@ const nextConfig = {
},
{
source: '/',
destination: '/projects',
destination: '/org',
permanent: false,
},
{
@@ -463,6 +463,11 @@ const nextConfig = {
destination: '/org/:slug/billing#invoices',
permanent: true,
},
{
source: '/projects',
destination: '/organizations',
permanent: false,
},
...(process.env.NEXT_PUBLIC_BASE_PATH?.length
? [
+16 -3
View File
@@ -4,14 +4,21 @@ import Image from 'next/legacy/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { LOCAL_STORAGE_KEYS } from 'common'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { useSignOut } from 'lib/auth'
import { Button } from 'ui'
const Error500: NextPage = () => {
const router = useRouter()
const signOut = useSignOut()
const { resolvedTheme } = useTheme()
const signOut = useSignOut()
const [lastVisitedOrganization] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION,
''
)
const onClickLogout = async () => {
await signOut()
await router.push('/sign-in')
@@ -48,9 +55,15 @@ const Error500: NextPage = () => {
</p>
</div>
<div className="flex items-center space-x-4">
{router.pathname !== '/projects' ? (
{router.pathname !== '/organizations' ? (
<Button asChild>
<Link href="/projects">Head back</Link>
<Link
href={
!!lastVisitedOrganization ? `/org/${lastVisitedOrganization}` : '/organizations'
}
>
Head back
</Link>
</Button>
) : (
<Button onClick={onClickLogout}>Head back</Button>
+2 -18
View File
@@ -1,31 +1,15 @@
import { AuditLogs } from 'components/interfaces/Account'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import AccountLayout from 'components/layouts/AccountLayout/AccountLayout'
import AppLayout from 'components/layouts/AppLayout/AppLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
import OrganizationLayout from 'components/layouts/OrganizationLayout'
import {
ScaffoldContainer,
ScaffoldDescription,
ScaffoldHeader,
ScaffoldTitle,
} from 'components/layouts/Scaffold'
import { ScaffoldContainer } from 'components/layouts/Scaffold'
import type { NextPageWithLayout } from 'types'
import { cn } from 'ui'
const Audit: NextPageWithLayout = () => {
const newLayoutPreview = useIsNewLayoutEnabled()
return (
<ScaffoldContainer className={cn(newLayoutPreview ? '[&>div]:mt-8' : '')}>
{!newLayoutPreview && (
<ScaffoldHeader>
<ScaffoldTitle>Account Audit Logs</ScaffoldTitle>
<ScaffoldDescription>
View the audit log trail of actions made from your account
</ScaffoldDescription>
</ScaffoldHeader>
)}
<ScaffoldContainer className={cn('[&>div]:mt-8')}>
<AuditLogs />
</ScaffoldContainer>
)
+2 -25
View File
@@ -3,18 +3,11 @@ import { AccountIdentities } from 'components/interfaces/Account/Preferences/Acc
import { AnalyticsSettings } from 'components/interfaces/Account/Preferences/AnalyticsSettings'
import { ProfileInformation } from 'components/interfaces/Account/Preferences/ProfileInformation'
import { ThemeSettings } from 'components/interfaces/Account/Preferences/ThemeSettings'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import AccountLayout from 'components/layouts/AccountLayout/AccountLayout'
import AccountSettingsLayout from 'components/layouts/AccountLayout/AccountSettingsLayout'
import { AccountSettingsLayout } from 'components/layouts/AccountLayout/AccountSettingsLayout'
import AppLayout from 'components/layouts/AppLayout/AppLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
import OrganizationLayout from 'components/layouts/OrganizationLayout'
import {
ScaffoldContainer,
ScaffoldDescription,
ScaffoldHeader,
ScaffoldTitle,
} from 'components/layouts/Scaffold'
import AlertError from 'components/ui/AlertError'
import Panel from 'components/ui/Panel'
import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader'
@@ -23,23 +16,7 @@ import { useProfile } from 'lib/profile'
import type { NextPageWithLayout } from 'types'
const User: NextPageWithLayout = () => {
const newLayoutPreview = useIsNewLayoutEnabled()
if (newLayoutPreview) {
return <ProfileCard />
}
return (
<ScaffoldContainer>
<ScaffoldHeader>
<ScaffoldTitle>User Preferences</ScaffoldTitle>
<ScaffoldDescription>
Manage your profile, account settings, and preferences for your Supabase experience
</ScaffoldDescription>
</ScaffoldHeader>
<ProfileCard />
</ScaffoldContainer>
)
return <ProfileCard />
}
User.getLayout = (page) => (
+2 -27
View File
@@ -1,18 +1,11 @@
import { Smartphone } from 'lucide-react'
import { TOTPFactors } from 'components/interfaces/Account'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import AccountLayout from 'components/layouts/AccountLayout/AccountLayout'
import AccountSettingsLayout from 'components/layouts/AccountLayout/AccountSettingsLayout'
import { AccountSettingsLayout } from 'components/layouts/AccountLayout/AccountSettingsLayout'
import AppLayout from 'components/layouts/AppLayout/AppLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
import OrganizationLayout from 'components/layouts/OrganizationLayout'
import {
ScaffoldContainer,
ScaffoldDescription,
ScaffoldHeader,
ScaffoldTitle,
} from 'components/layouts/Scaffold'
import { useMfaListFactorsQuery } from 'data/profile/mfa-list-factors-query'
import type { NextPageWithLayout } from 'types'
import {
@@ -37,10 +30,9 @@ const collapsibleClasses = [
]
const Security: NextPageWithLayout = () => {
const newLayoutPreview = useIsNewLayoutEnabled()
const { data } = useMfaListFactorsQuery()
const PageContent = () => (
return (
<Collapsible_Shadcn_ className={cn(collapsibleClasses)}>
<CollapsibleTrigger_Shadcn_ asChild>
<button
@@ -64,23 +56,6 @@ const Security: NextPageWithLayout = () => {
</CollapsibleContent_Shadcn_>
</Collapsible_Shadcn_>
)
if (newLayoutPreview) {
return <PageContent />
}
return (
<ScaffoldContainer>
<ScaffoldHeader>
<ScaffoldTitle>Multi-Factor Authentication</ScaffoldTitle>
<ScaffoldDescription>
Add an additional layer of security to your account by requiring more than just a password
to sign in.
</ScaffoldDescription>
</ScaffoldHeader>
<PageContent />
</ScaffoldContainer>
)
}
Security.getLayout = (page) => (
+25 -78
View File
@@ -7,29 +7,44 @@ import {
NewAccessTokenButton,
NewTokenBanner,
} from 'components/interfaces/Account'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import AccountLayout from 'components/layouts/AccountLayout/AccountLayout'
import AccountSettingsLayout from 'components/layouts/AccountLayout/AccountSettingsLayout'
import { AccountSettingsLayout } from 'components/layouts/AccountLayout/AccountSettingsLayout'
import AppLayout from 'components/layouts/AppLayout/AppLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
import OrganizationLayout from 'components/layouts/OrganizationLayout'
import {
ScaffoldContainer,
ScaffoldDescription,
ScaffoldHeader,
ScaffoldTitle,
} from 'components/layouts/Scaffold'
import { NewAccessToken } from 'data/access-tokens/access-tokens-create-mutation'
import type { NextPageWithLayout } from 'types'
import { Button } from 'ui'
import { Admonition } from 'ui-patterns'
const UserAccessTokens: NextPageWithLayout = () => {
const newLayoutPreview = useIsNewLayoutEnabled()
const [newToken, setNewToken] = useState<NewAccessToken | undefined>()
const PageContent = () => (
return (
<>
<div className="flex items-center space-x-4 mb-6">
<div className="flex items-center space-x-2">
<Button asChild type="default" icon={<ExternalLink strokeWidth={1.5} />}>
<Link
href="https://supabase.com/docs/reference/api/introduction"
target="_blank"
rel="noreferrer"
>
API Docs
</Link>
</Button>
<Button asChild type="default" icon={<ExternalLink strokeWidth={1.5} />}>
<Link
href="https://supabase.com/docs/reference/cli/start"
target="_blank"
rel="noreferrer"
>
CLI docs
</Link>
</Button>
</div>
<NewAccessTokenButton onCreateToken={setNewToken} />
</div>
<div className="flex items-center justify-between">
<Admonition
type="warning"
@@ -43,74 +58,6 @@ const UserAccessTokens: NextPageWithLayout = () => {
</div>
</>
)
if (newLayoutPreview) {
return (
<>
<div className="flex items-center space-x-4 mb-6">
<div className="flex items-center space-x-2">
<Button asChild type="default" icon={<ExternalLink strokeWidth={1.5} />}>
<Link
href="https://supabase.com/docs/reference/api/introduction"
target="_blank"
rel="noreferrer"
>
API Docs
</Link>
</Button>
<Button asChild type="default" icon={<ExternalLink strokeWidth={1.5} />}>
<Link
href="https://supabase.com/docs/reference/cli/start"
target="_blank"
rel="noreferrer"
>
CLI docs
</Link>
</Button>
</div>
<NewAccessTokenButton onCreateToken={setNewToken} />
</div>
<PageContent />
</>
)
}
return (
<ScaffoldContainer>
<ScaffoldHeader className="flex flex-col md:flex-row md:items-center justify-between">
<div className="flex flex-col">
<ScaffoldTitle>Access Tokens</ScaffoldTitle>
<ScaffoldDescription>
Personal access tokens can be used with our Management API or CLI.
</ScaffoldDescription>
</div>
<div className="flex flex-col md:flex-row md:items-center gap-4">
<div className="flex items-center space-x-2">
<Button asChild type="default" icon={<ExternalLink strokeWidth={1.5} />}>
<Link
href="https://supabase.com/docs/reference/api/introduction"
target="_blank"
rel="noreferrer"
>
API Docs
</Link>
</Button>
<Button asChild type="default" icon={<ExternalLink strokeWidth={1.5} />}>
<Link
href="https://supabase.com/docs/reference/cli/start"
target="_blank"
rel="noreferrer"
>
CLI docs
</Link>
</Button>
</div>
<NewAccessTokenButton onCreateToken={setNewToken} />
</div>
</ScaffoldHeader>
<PageContent />
</ScaffoldContainer>
)
}
UserAccessTokens.getLayout = (page) => (
+1 -1
View File
@@ -52,7 +52,7 @@ const APIAuthorizationPage: NextPageWithLayout = () => {
const { mutate: declineRequest } = useApiAuthorizationDeclineMutation({
onSuccess: () => {
toast.success('Declined API authorization request')
router.push('/projects')
router.push('/organizations')
},
})
+12 -12
View File
@@ -11,7 +11,7 @@ import { z } from 'zod'
import { PopoverSeparator } from '@ui/components/shadcn/ui/popover'
import { components } from 'api-types'
import { useParams } from 'common'
import { LOCAL_STORAGE_KEYS, useParams } from 'common'
import {
FreeProjectLimitWarning,
NotOrganizationOwnerWarning,
@@ -46,6 +46,7 @@ import {
import { useProjectsQuery } from 'data/projects/projects-query'
import { useSendEventMutation } from 'data/telemetry/send-event-mutation'
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization'
import { withAuth } from 'hooks/misc/withAuth'
import { useFlag } from 'hooks/ui/useFlag'
@@ -140,6 +141,11 @@ const Wizard: NextPageWithLayout = () => {
const currentOrg = useSelectedOrganization()
const isFreePlan = currentOrg?.plan?.id === 'free'
const [lastVisitedOrganization] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION,
''
)
const { mutate: sendEvent } = useSendEventMutation()
const projectCreationDisabled = useFlag('disableProjectCreationAndUpdate')
@@ -557,7 +563,10 @@ const Wizard: NextPageWithLayout = () => {
<Button
type="default"
disabled={isCreatingNewProject || isSuccessNewProject}
onClick={() => router.push('/projects')}
onClick={() => {
if (!!lastVisitedOrganization) router.push(`/org/${lastVisitedOrganization}`)
else router.push('/organizations')
}}
>
Cancel
</Button>
@@ -993,16 +1002,7 @@ const instanceLabel = (instance: string | undefined): string => {
}
const PageLayout = withAuth(({ children }: PropsWithChildren) => {
const { slug } = useParams()
const { data: organizations } = useOrganizationsQuery()
const currentOrg = organizations?.find((o) => o.slug === slug)
return (
<WizardLayoutWithoutAuth organization={currentOrg} project={null}>
{children}
</WizardLayoutWithoutAuth>
)
return <WizardLayoutWithoutAuth>{children}</WizardLayoutWithoutAuth>
})
Wizard.getLayout = (page) => (
+1 -3
View File
@@ -108,9 +108,7 @@ const Wizard: NextPageWithLayout = () => {
Wizard.getLayout = (page) => (
<AppLayout>
<DefaultLayout headerTitle="New organization">
<WizardLayout organization={null} project={null}>
{page}
</WizardLayout>
<WizardLayout>{page}</WizardLayout>
</DefaultLayout>
</AppLayout>
)
+1 -1
View File
@@ -1,7 +1,7 @@
import { useEffect } from 'react'
import { useParams } from 'common'
import { BillingSettings } from 'components/interfaces/Organization'
import { BillingSettings } from 'components/interfaces/Organization/BillingSettings/BillingSettings'
import DefaultLayout from 'components/layouts/DefaultLayout'
import OrganizationLayout from 'components/layouts/OrganizationLayout'
import OrganizationSettingsLayout from 'components/layouts/ProjectLayout/OrganizationSettingsLayout'
+1 -21
View File
@@ -1,9 +1,5 @@
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import { useState } from 'react'
import { useParams } from 'common'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import { LayoutUpdateBanner } from 'components/interfaces/App/FeaturePreview/LayoutUpdatePreview'
import { ProjectList } from 'components/interfaces/Home/ProjectList'
import HomePageActions from 'components/interfaces/HomePageActions'
import DefaultLayout from 'components/layouts/DefaultLayout'
@@ -14,11 +10,6 @@ import { PROJECT_STATUS } from 'lib/constants'
import type { NextPageWithLayout } from 'types'
const ProjectsPage: NextPageWithLayout = () => {
const newLayoutPreview = useIsNewLayoutEnabled()
const hasWindowLoaded = typeof window !== 'undefined'
const router = useRouter()
const { slug } = useParams()
const [search, setSearch] = useState('')
const [filterStatus, setFilterStatus] = useState<string[]>([
PROJECT_STATUS.ACTIVE_HEALTHY,
@@ -27,19 +18,9 @@ const ProjectsPage: NextPageWithLayout = () => {
useAutoProjectsPrefetch()
useEffect(() => {
// handle old layout redirect
// this page should not be accessible in the old layout
if (hasWindowLoaded && !newLayoutPreview && slug && router) {
router.push(`/projects`)
}
}, [newLayoutPreview, router, slug])
return (
<ScaffoldContainerLegacy>
<div>
<LayoutUpdateBanner />
<HomePageActions
search={search}
setSearch={setSearch}
@@ -49,7 +30,6 @@ const ProjectsPage: NextPageWithLayout = () => {
<div className="my-6 space-y-8">
<ProjectList
filterToSlug
search={search}
filterStatus={filterStatus}
resetFilterStatus={() => setFilterStatus(['ACTIVE_HEALTHY', 'INACTIVE'])}
+3 -7
View File
@@ -1,5 +1,4 @@
import { TeamSettings } from 'components/interfaces/Organization'
import AppLayout from 'components/layouts/AppLayout/AppLayout'
import { TeamSettings } from 'components/interfaces/Organization/TeamSettings/TeamSettings'
import DefaultLayout from 'components/layouts/DefaultLayout'
import OrganizationLayout from 'components/layouts/OrganizationLayout'
import OrganizationSettingsLayout from 'components/layouts/ProjectLayout/OrganizationSettingsLayout'
@@ -12,11 +11,7 @@ const OrgTeamSettings: NextPageWithLayout = () => {
const { isLoading: isLoadingPermissions } = usePermissionsQuery()
const selectedOrganization = useSelectedOrganization()
return (
<>
{selectedOrganization === undefined && isLoadingPermissions ? <Loading /> : <TeamSettings />}
</>
)
return selectedOrganization === undefined && isLoadingPermissions ? <Loading /> : <TeamSettings />
}
OrgTeamSettings.getLayout = (page) => (
@@ -26,4 +21,5 @@ OrgTeamSettings.getLayout = (page) => (
</OrganizationLayout>
</DefaultLayout>
)
export default OrgTeamSettings
+45
View File
@@ -0,0 +1,45 @@
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { LOCAL_STORAGE_KEYS } from 'common'
import ShimmeringCard from 'components/interfaces/Home/ProjectList/ShimmeringCard'
import DefaultLayout from 'components/layouts/DefaultLayout'
import OrganizationLayout from 'components/layouts/OrganizationLayout'
import { ScaffoldContainerLegacy } from 'components/layouts/Scaffold'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import type { NextPageWithLayout } from 'types'
const OrgIndexPage: NextPageWithLayout = () => {
const router = useRouter()
const [lastVisitedOrganization, _, { isSuccess }] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION,
''
)
useEffect(() => {
if (isSuccess) {
if (lastVisitedOrganization.length > 0) router.push(`/org/${lastVisitedOrganization}`)
else router.push('/organizations')
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isSuccess])
return (
<ScaffoldContainerLegacy>
<div>
<ul className="my-6 mx-auto grid grid-cols-1 gap-4 sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3">
<ShimmeringCard />
<ShimmeringCard />
</ul>
</div>
</ScaffoldContainerLegacy>
)
}
OrgIndexPage.getLayout = (page) => (
<DefaultLayout>
<OrganizationLayout>{page}</OrganizationLayout>
</DefaultLayout>
)
export default OrgIndexPage
+69 -40
View File
@@ -1,23 +1,36 @@
import { Boxes } from 'lucide-react'
import { Boxes, Search } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { LayoutUpdateBanner } from 'components/interfaces/App/FeaturePreview/LayoutUpdatePreview'
import AppLayout from 'components/layouts/AppLayout/AppLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { ScaffoldContainerLegacy, ScaffoldTitle } from 'components/layouts/Scaffold'
import { ActionCard } from 'components/ui/ActionCard'
import AlertError from 'components/ui/AlertError'
import NoSearchResults from 'components/ui/NoSearchResults'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useProjectsQuery } from 'data/projects/projects-query'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { withAuth } from 'hooks/misc/withAuth'
import { NextPageWithLayout } from 'types'
import { Skeleton } from 'ui'
import { Button, Skeleton } from 'ui'
import { Input } from 'ui-patterns/DataInputs/Input'
const OrganizationsPage: NextPageWithLayout = () => {
const router = useRouter()
const [search, setSearch] = useState('')
const { data: organizations, isLoading, isError, error, isSuccess } = useOrganizationsQuery()
const { data: projects = [] } = useProjectsQuery()
const { data: organizations = [], error, isLoading, isError, isSuccess } = useOrganizationsQuery()
const organizationCreationEnabled = useIsFeatureEnabled('organizations:create')
const filteredOrganizations =
search.length === 0
? organizations
: organizations?.filter(
(x) => x.name.toLowerCase().includes(search) || x.slug.toLowerCase().includes(search)
)
useEffect(() => {
// If there are no organizations, force the user to create one
@@ -27,42 +40,58 @@ const OrganizationsPage: NextPageWithLayout = () => {
}, [isSuccess, organizations])
return (
<>
<ScaffoldContainerLegacy>
<LayoutUpdateBanner />
<ScaffoldTitle>Your Organizations</ScaffoldTitle>
</ScaffoldContainerLegacy>
<ScaffoldContainerLegacy>
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{isLoading && (
<>
<Skeleton className="h-[62px] rounded-md" />
<Skeleton className="h-[62px] rounded-md" />
<Skeleton className="h-[62px] rounded-md" />
</>
)}
{isError && <div>Error loading organizations</div>}
{isSuccess &&
organizations?.map((organization) => {
const numProjects = projects.filter(
(x) => x.organization_slug === organization.slug
).length
<ScaffoldContainerLegacy>
<ScaffoldTitle>Your Organizations</ScaffoldTitle>
return (
<ActionCard
bgColor="bg border"
className="[&>div]:items-center"
key={organization.id}
icon={<Boxes size={18} strokeWidth={1} className="text-foreground" />}
title={organization.name}
description={`${organization.plan.name} Plan${numProjects > 0 ? `${' '}•${' '}${numProjects} project${numProjects > 1 ? 's' : ''}` : ''}`}
onClick={() => router.push(`/org/${organization.slug}`)}
/>
)
})}
</div>
</ScaffoldContainerLegacy>
</>
<div className="flex items-center gap-x-2 md:gap-x-3">
{organizationCreationEnabled && (
<Button asChild type="primary" className="w-min">
<Link href={`/new`}>New organization</Link>
</Button>
)}
<Input
size="tiny"
placeholder="Search for an organization"
icon={<Search size={16} />}
className="w-full flex-1 md:w-64 [&>div>div>div>input]:!pl-7 [&>div>div>div>div]:!pl-2"
value={search}
onChange={(event) => setSearch(event.target.value)}
/>
</div>
{search.length > 0 && filteredOrganizations.length === 0 && (
<NoSearchResults searchString={search} />
)}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{isLoading && (
<>
<Skeleton className="h-[62px] rounded-md" />
<Skeleton className="h-[62px] rounded-md" />
<Skeleton className="h-[62px] rounded-md" />
</>
)}
{isError && <AlertError error={error} subject="Failed to load organizations" />}
{isSuccess &&
filteredOrganizations.map((organization) => {
const numProjects = projects.filter(
(x) => x.organization_slug === organization.slug
).length
return (
<ActionCard
bgColor="bg border"
className="[&>div]:items-center"
key={organization.id}
icon={<Boxes size={18} strokeWidth={1} className="text-foreground" />}
title={organization.name}
description={`${organization.plan.name} Plan${numProjects > 0 ? `${' '}•${' '}${numProjects} project${numProjects > 1 ? 's' : ''}` : ''}`}
onClick={() => router.push(`/org/${organization.slug}`)}
/>
)
})}
</div>
</ScaffoldContainerLegacy>
)
}
-98
View File
@@ -1,98 +0,0 @@
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import { ProjectList } from 'components/interfaces/Home/ProjectList'
import HomePageActions from 'components/interfaces/HomePageActions'
import { useIsNewLayoutEnabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext'
import DefaultLayout from 'components/layouts/DefaultLayout'
import OrganizationLayout from 'components/layouts/OrganizationLayout'
import { MAX_WIDTH_CLASSES, PADDING_CLASSES } from 'components/layouts/Scaffold'
import AlertError from 'components/ui/AlertError'
import { useOrganizationsQuery } from 'data/organizations/organizations-query'
import { useAutoProjectsPrefetch } from 'data/projects/projects-query'
import { useIsFeatureEnabled } from 'hooks/misc/useIsFeatureEnabled'
import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage'
import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants'
import type { NextPageWithLayout } from 'types'
import { cn } from 'ui'
import { LOCAL_STORAGE_KEYS } from 'common'
const ProjectsPage: NextPageWithLayout = () => {
const newLayoutPreview = useIsNewLayoutEnabled()
const [lastVisitedOrganization] = useLocalStorageQuery(
LOCAL_STORAGE_KEYS.LAST_VISITED_ORGANIZATION,
''
)
const router = useRouter()
const [search, setSearch] = useState('')
const [filterStatus, setFilterStatus] = useState<string[]>([
PROJECT_STATUS.ACTIVE_HEALTHY,
PROJECT_STATUS.INACTIVE,
])
const { data: organizations, isError, isSuccess } = useOrganizationsQuery()
useAutoProjectsPrefetch()
const projectCreationEnabled = useIsFeatureEnabled('projects:create')
const hasWindowLoaded = typeof window !== 'undefined'
useEffect(() => {
if (hasWindowLoaded && isSuccess) {
const hasNoOrg = organizations.length === 0
const hasShownNewPage = localStorage.getItem(LOCAL_STORAGE_KEYS.UI_ONBOARDING_NEW_PAGE_SHOWN)
if (hasNoOrg && !hasShownNewPage) {
localStorage.setItem(LOCAL_STORAGE_KEYS.UI_ONBOARDING_NEW_PAGE_SHOWN, 'true')
router.push('/new')
}
}
}, [hasWindowLoaded, isSuccess])
useEffect(() => {
// [Joshen] Adding the redirect here, but once the nav changes are permanenet
// we should be doing the redirect in next.config instead
if (hasWindowLoaded && newLayoutPreview) {
if (lastVisitedOrganization) router.push(`/org/${lastVisitedOrganization}`)
else router.push('/organizations')
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [hasWindowLoaded, newLayoutPreview])
return (
<div className={cn(newLayoutPreview && [MAX_WIDTH_CLASSES, PADDING_CLASSES])}>
{isError && (
<div className="p-4 md:px-5">
<AlertError subject="Failed to retrieve organizations" />
</div>
)}
<div className="p-4 md:p-5">
{IS_PLATFORM && projectCreationEnabled && isSuccess && (
<HomePageActions
search={search}
filterStatus={filterStatus}
setSearch={setSearch}
setFilterStatus={setFilterStatus}
organizations={organizations}
/>
)}
<div className="my-6 space-y-8">
<ProjectList
search={search}
filterStatus={filterStatus}
resetFilterStatus={() => setFilterStatus(['ACTIVE_HEALTHY', 'INACTIVE'])}
/>
</div>
</div>
</div>
)
}
ProjectsPage.getLayout = (page) => (
<DefaultLayout headerTitle="All Projects">
<OrganizationLayout>{page}</OrganizationLayout>
</DefaultLayout>
)
export default ProjectsPage
+1 -1
View File
@@ -29,7 +29,7 @@ const SignInFlyTos = () => {
})
const { mutateAsync: getOrgByFlyOrgId } = useOrganizationByFlyOrgIdMutation({
onSuccess: () => {
router.replace('/projects')
router.replace('/organizations')
},
onError: () => {
setLoading(false)