mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
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:
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
|
||||
+3
-11
@@ -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
|
||||
+1
-1
@@ -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()
|
||||
|
||||
+3
-8
@@ -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}`)
|
||||
|
||||
@@ -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
|
||||
+1
-7
@@ -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">
|
||||
|
||||
+1
-3
@@ -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,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
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
? [
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -52,7 +52,7 @@ const APIAuthorizationPage: NextPageWithLayout = () => {
|
||||
const { mutate: declineRequest } = useApiAuthorizationDeclineMutation({
|
||||
onSuccess: () => {
|
||||
toast.success('Declined API authorization request')
|
||||
router.push('/projects')
|
||||
router.push('/organizations')
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -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) => (
|
||||
|
||||
@@ -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,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,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'])}
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -29,7 +29,7 @@ const SignInFlyTos = () => {
|
||||
})
|
||||
const { mutateAsync: getOrgByFlyOrgId } = useOrganizationByFlyOrgIdMutation({
|
||||
onSuccess: () => {
|
||||
router.replace('/projects')
|
||||
router.replace('/organizations')
|
||||
},
|
||||
onError: () => {
|
||||
setLoading(false)
|
||||
|
||||
Reference in new issue
Block a user