mirror of
https://github.com/supabase/supabase.git
synced 2026-10-10 20:05:06 +03:00
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Feature update. Resolves DEPR-553. ## What is the current behavior? Stripe Projects login still uses the older `APIAuthorizationLayout` surface, so it does not match the newer shared connect interstitial pattern used by organisation invites and CLI login. ## What is the new behavior? Moves `/partners/stripe/projects/login` onto the shared `InterstitialLayout` while preserving the existing `ar_id` account request lookup, confirmation mutation, wrong-account sign-out path, and missing-parameter redirect. The temporary reviewer mocks have been removed after approval. ## Testing instructions Automated checks run locally: - `pnpm --dir apps/studio exec prettier --write pages/partners/stripe/projects/login.tsx components/layouts/InterstitialLayout.tsx` - `pnpm --dir apps/studio exec eslint pages/partners/stripe/projects/login.tsx components/layouts/InterstitialLayout.tsx` - `git diff --check` `pnpm --dir apps/studio exec tsc --noEmit` was also run earlier on this branch, but still fails on existing unrelated issues in `components/interfaces/Integrations/Landing/useAvailableIntegrations.tsx` and `packages/common/marketplace-client.ts`. Manual Stripe Projects testing requires a real account request. Opening `/partners/stripe/projects/login` without an `ar_id` redirects to `/404` by design. If you need the real flow: 1. Use the Stripe staging provider. In the Stripe CLI flow, run `export DEV_MODE=true` so the provider is `Supabase_Staging_Env`. 2. From a local project directory, run `stripe projects init` and complete the Stripe setup flow. 3. Run `stripe projects add Supabase_Staging_Env`. 4. When the browser opens the Supabase authorization URL, keep the generated path and query string exactly as-is, including `ar_id`, but replace only the origin with this PR preview deployment origin. Note: the staging Stripe Projects flow can still incur real Stripe costs; use the staging provider and coordinate refunds with team billing if needed. ## Additional context This is a deliberately small stacked slice toward the broader shared connect interstitial work. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **User Interface** * Redesigned Stripe authorization login page with improved layout and visual state management * Enhanced account row component to support flexible action buttons and styling * Added clearer messaging and UI states for authorization scenarios (pending, success, errors, and account mismatches) <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/45862) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
145 lines
4.2 KiB
TypeScript
145 lines
4.2 KiB
TypeScript
import { motion } from 'framer-motion'
|
|
import { ArrowRightLeft } from 'lucide-react'
|
|
import type { PropsWithChildren, ReactNode } from 'react'
|
|
import { Card, CardContent, CardHeader, cn } from 'ui'
|
|
|
|
import { ProfileImage } from '@/components/ui/ProfileImage'
|
|
import { BASE_PATH } from '@/lib/constants'
|
|
|
|
const MotionCard = motion.create(Card)
|
|
|
|
interface InterstitialLayoutProps {
|
|
logo?: ReactNode
|
|
title?: ReactNode
|
|
description?: ReactNode
|
|
containerClassName?: string
|
|
cardClassName?: string
|
|
titleClassName?: string
|
|
descriptionClassName?: string
|
|
}
|
|
|
|
export const InterstitialLayout = ({
|
|
logo,
|
|
title,
|
|
description,
|
|
containerClassName,
|
|
cardClassName,
|
|
titleClassName,
|
|
descriptionClassName,
|
|
children,
|
|
}: PropsWithChildren<InterstitialLayoutProps>) => {
|
|
const TitleElement = typeof title === 'string' ? 'h1' : 'div'
|
|
const DescriptionElement = typeof description === 'string' ? 'p' : 'div'
|
|
|
|
return (
|
|
<div
|
|
className={cn(
|
|
'flex min-h-screen w-full items-center justify-center bg-studio px-2 py-6',
|
|
containerClassName
|
|
)}
|
|
>
|
|
<MotionCard
|
|
layout="size"
|
|
transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
|
|
className={cn('mx-auto w-full max-w-[400px] overflow-hidden', cardClassName)}
|
|
>
|
|
{(logo || title || description) && (
|
|
<CardHeader className="items-center gap-0 space-y-0 border-0 px-6 py-6 text-center font-normal [--card-padding-x:1.5rem]">
|
|
{logo && <div className="mb-4 flex justify-center">{logo}</div>}
|
|
{(title || description) && (
|
|
<div className="flex flex-col items-center gap-1">
|
|
{title && (
|
|
<TitleElement
|
|
className={cn(
|
|
'font-sans text-lg font-medium tracking-tight text-balance text-foreground',
|
|
titleClassName
|
|
)}
|
|
>
|
|
{title}
|
|
</TitleElement>
|
|
)}
|
|
{description && (
|
|
<DescriptionElement
|
|
className={cn(
|
|
'!m-0 px-3 text-sm leading-tight !text-balance text-foreground-lighter',
|
|
descriptionClassName
|
|
)}
|
|
>
|
|
{description}
|
|
</DescriptionElement>
|
|
)}
|
|
</div>
|
|
)}
|
|
</CardHeader>
|
|
)}
|
|
{children}
|
|
</MotionCard>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export const LogoBox = ({ children, className }: { children: ReactNode; className?: string }) => (
|
|
<div
|
|
className={cn(
|
|
'flex size-12 items-center justify-center overflow-hidden rounded-xl border bg-muted',
|
|
className
|
|
)}
|
|
>
|
|
{children}
|
|
</div>
|
|
)
|
|
|
|
export const LogoPair = ({ left, right }: { left: ReactNode; right: ReactNode }) => (
|
|
<div className="flex items-center justify-center gap-2.5">
|
|
{left}
|
|
<ArrowRightLeft className="size-4 text-foreground-muted" />
|
|
{right}
|
|
</div>
|
|
)
|
|
|
|
export const SupabaseLogo = () => (
|
|
<LogoBox>
|
|
<img alt="Supabase" src={`${BASE_PATH}/img/supabase-logo.svg`} className="size-7" />
|
|
</LogoBox>
|
|
)
|
|
|
|
export const PartnerLogo = ({ src, alt }: { src: string; alt: string }) => (
|
|
<LogoBox>
|
|
<img alt={alt} src={src} className="size-full object-cover" />
|
|
</LogoBox>
|
|
)
|
|
|
|
export const InterstitialAccountRow = ({
|
|
avatarUrl,
|
|
displayName,
|
|
action,
|
|
className,
|
|
}: {
|
|
avatarUrl?: string
|
|
displayName?: string
|
|
action?: ReactNode
|
|
className?: string
|
|
}) => (
|
|
<Card className={cn('shadow-none', !action && 'border-muted bg-surface-200/50', className)}>
|
|
<CardContent
|
|
className={cn(
|
|
'flex gap-3 border-none',
|
|
action ? 'items-center px-4 py-3' : 'items-start p-3'
|
|
)}
|
|
>
|
|
<ProfileImage
|
|
src={avatarUrl}
|
|
alt={displayName}
|
|
className="size-8 flex-shrink-0 rounded-full border border-muted"
|
|
/>
|
|
<div className="min-w-0 flex-1">
|
|
<p className="text-xs text-foreground-light">Signed in as</p>
|
|
<p className="truncate text-sm text-foreground">
|
|
{displayName || <span className="invisible">Loading account</span>}
|
|
</p>
|
|
</div>
|
|
{action}
|
|
</CardContent>
|
|
</Card>
|
|
)
|