Files
supabase/apps/studio/pages/organizations.tsx
Danny White ee931e49a1 fix(studio): send new signups to org creation directly (#41041)
## What kind of change does this PR introduce?

Bug fix. Resolves DEPR-318.

## What is the current behavior?

New users who confirm their email land on `/sign-in`, then
`/organizations`, then get bounced to `/new` via a `useEffect`.
Cancelling org creation with zero orgs sends them back to
`/organizations`, which immediately redirects into `/new` again.

## What is the new behavior?

- Signup email verification redirects to `/new` directly
- `/organizations` with zero orgs shows the existing empty state instead
of force-redirecting

## To test

### One-time setup

Assuming you don’t already have a staging account with **zero** orgs:

1. On **supabase.green**, sign up with a fresh email and confirm it
2. Stop at org creation. Do **not** create an org

### On this PR

Using the [studio-staging
preview](https://studio-staging-git-dnywh-fixremove-org-redirect-supabase.vercel.app/)
from Vercel checks:

4. Sign in on the preview with that account
5. Open `/dashboard/organizations`. Expect the **Create an
organization** empty state, with no redirect to `/new`
6. Open `/dashboard/new`, click **Cancel**. Expect to land on
`/organizations` and stay there

### Compare on supabase.green

Optional. Just to show what happens currently on `master`:

7. Repeat steps 3–5 on **supabase.green**. `/organizations` should
bounce to `/new`, and **Cancel** should send you back into org creation

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
* Improved signup redirects by preserving valid destinations and
relevant query parameters.
* Added safer fallback behavior for missing, invalid, or unsupported
destinations.
  * Improved handling of signup redirects provided in multiple formats.
* Prevented automatic redirection from the organizations page when no
organizations exist.

* **Style**
  * Updated the organizations page title capitalization for consistency.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-08-24 17:23:25 +10:00

111 lines
4.0 KiB
TypeScript

import { Plus, Search } from 'lucide-react'
import Head from 'next/head'
import Link from 'next/link'
import { useState } from 'react'
import { Button, Skeleton } from 'ui'
import { Input } from 'ui-patterns/DataInputs/Input'
import { NoOrganizationsState } from '@/components/interfaces/Home/ProjectList/EmptyStates'
import { OrganizationCard } from '@/components/interfaces/Organization/OrganizationCard'
import { AppLayout } from '@/components/layouts/AppLayout/AppLayout'
import { DefaultLayout } from '@/components/layouts/DefaultLayout'
import { PageLayout } from '@/components/layouts/PageLayout/PageLayout'
import { ScaffoldContainer, ScaffoldSection } from '@/components/layouts/Scaffold'
import { AlertError } from '@/components/ui/AlertError'
import { NoSearchResults } from '@/components/ui/NoSearchResults'
import { useOrganizationsQuery } from '@/data/organizations/organizations-query'
import { useCustomContent } from '@/hooks/custom-content/useCustomContent'
import { useIsFeatureEnabled } from '@/hooks/misc/useIsFeatureEnabled'
import { withAuth } from '@/hooks/misc/withAuth'
import { buildStudioPageTitle } from '@/lib/page-title'
import type { NextPageWithLayout } from '@/types'
const OrganizationsPage: NextPageWithLayout = () => {
const { appTitle } = useCustomContent(['app:title'])
const [search, setSearch] = useState('')
const pageTitle = buildStudioPageTitle({
section: 'Organizations',
brand: appTitle || 'Supabase',
})
const {
data: organizations = [],
error,
isPending: 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)
)
return (
<>
<Head>
<title>{pageTitle}</title>
<meta name="description" content="Supabase Studio" />
</Head>
<ScaffoldContainer>
<ScaffoldSection isFullWidth className="flex flex-col gap-y-4">
{organizations.length > 0 && (
<div className="flex items-center justify-between gap-x-2 md:gap-x-3">
<Input
size="tiny"
placeholder="Search for an organization"
icon={<Search />}
className="w-full flex-1 md:w-64"
value={search}
onChange={(event) => setSearch(event.target.value)}
/>
{organizationCreationEnabled && (
<Button asChild icon={<Plus />} variant="primary" className="w-min">
<Link href={`/new`}>New organization</Link>
</Button>
)}
</div>
)}
{isSuccess && organizations.length === 0 && !isError && <NoOrganizationsState />}
{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-[70px] rounded-md" />
<Skeleton className="h-[70px] rounded-md" />
<Skeleton className="h-[70px] rounded-md" />
</>
)}
{isError && <AlertError error={error} subject="Failed to load organizations" />}
{isSuccess &&
filteredOrganizations.map((org) => (
<OrganizationCard key={org.id} organization={org} />
))}
</div>
</ScaffoldSection>
</ScaffoldContainer>
</>
)
}
OrganizationsPage.getLayout = (page) => (
<AppLayout>
<DefaultLayout hideMobileMenu headerTitle="Organizations">
<PageLayout title="Your organizations" className="max-w-[1200px] lg:px-6 mx-auto">
{page}
</PageLayout>
</DefaultLayout>
</AppLayout>
)
export default withAuth(OrganizationsPage)