updated scaffolding

This commit is contained in:
Saxon Fletcher committed 2025-02-18 16:07:37 +10:00
1 parent 1c6614743c
commit 4e23985480
16 files changed
+160 -259

No files matched your search

@@ -10,11 +10,11 @@ import {
} from 'ui'
export const AuthAlert = ({
ref,
projectRef,
title,
isHookSendSMSEnabled,
}: {
ref?: string
projectRef: string
title: string
isHookSendSMSEnabled: boolean
}) => {
@@ -51,7 +51,7 @@ export const AuthAlert = ({
<AlertDescription_Shadcn_ className="flex flex-col gap-y-3">
<p>The SMS hook will be used in place of the SMS provider configured</p>
<Button asChild type="default" className="w-min" icon={<ExternalLink />}>
<Link href={`/project/${ref}/auth/hooks`}>View auth hooks</Link>
<Link href={`/project/${projectRef}/auth/hooks`}>View auth hooks</Link>
</Button>
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
@@ -16,6 +16,11 @@ import {
import { getPhoneProviderValidationSchema, PROVIDERS_SCHEMAS } from '../AuthProvidersFormValidation'
import type { Provider } from './AuthProvidersForm.types'
import ProviderForm from './ProviderForm'
import {
ScaffoldSection,
ScaffoldSectionDescription,
ScaffoldSectionTitle,
} from 'components/layouts/Scaffold'
export const AuthProvidersForm = () => {
const { ref: projectRef } = useParams()
@@ -28,11 +33,11 @@ export const AuthProvidersForm = () => {
} = useAuthConfigQuery({ projectRef })
return (
<div>
<SectionHeader
title="Auth Providers"
subtitle="Authenticate your users through a suite of providers and login methods"
/>
<ScaffoldSection isFullWidth>
<ScaffoldSectionTitle>Auth Providers</ScaffoldSectionTitle>
<ScaffoldSectionDescription className="mb-4">
Authenticate your users through a suite of providers and login methods
</ScaffoldSectionDescription>
<div className="-space-y-px">
{authConfig?.EXTERNAL_EMAIL_ENABLED && authConfig?.MAILER_OTP_EXP > 3600 && (
@@ -102,6 +107,6 @@ export const AuthProvidersForm = () => {
)}
</ResourceList>
</div>
</div>
</ScaffoldSection>
)
}
@@ -169,7 +169,7 @@ const ProviderForm = ({ config, provider, isActive }: ProviderFormProps) => {
<div className="flex-1 overflow-y-auto group py-6 px-4 md:px-6 text-foreground">
<div className="mx-auto my-2 md:my-6 max-w-lg space-y-6">
<AuthAlert
ref={projectRef}
projectRef={projectRef}
title={provider.title}
isHookSendSMSEnabled={config.HOOK_SEND_SMS_ENABLED}
/>
@@ -26,9 +26,9 @@ import {
Switch,
WarningIcon,
} from 'ui'
import { SectionHeader } from 'components/layouts/PageLayout'
import { NO_REQUIRED_CHARACTERS } from '../Auth.constants'
import { ScaffoldSection, ScaffoldSectionTitle } from 'components/layouts/Scaffold'
const schema = object({
DISABLE_SIGNUP: boolean().required(),
@@ -106,8 +106,8 @@ const BasicAuthSettingsForm = () => {
}
return (
<div>
<SectionHeader title="User Signups" />
<ScaffoldSection isFullWidth>
<ScaffoldSectionTitle className="mb-4">User Signups</ScaffoldSectionTitle>
<Form_Shadcn_ {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
@@ -252,7 +252,7 @@ const BasicAuthSettingsForm = () => {
</Card>
</form>
</Form_Shadcn_>
</div>
</ScaffoldSection>
)
}
@@ -24,7 +24,12 @@ import {
getIntegrationTypeLabel,
INTEGRATION_TYPES,
} from './ThirdPartyAuthForm.utils'
import {
ScaffoldSection,
ScaffoldSectionDescription,
ScaffoldSectionTitle,
} from 'components/layouts/Scaffold'
import { DocsButton } from 'components/ui/DocsButton'
export const ThirdPartyAuthForm = () => {
const { ref: projectRef } = useParams()
const {
@@ -53,26 +58,27 @@ export const ThirdPartyAuthForm = () => {
}
return (
<div className="pb-4">
<FormHeader
title="Third Party Auth"
className="mb-1"
description="Use third-party authentication (TPA) systems based on JWTs to access your project."
actions={
integrations.length !== 0 ? (
<ScaffoldSection isFullWidth>
<div className="flex justify-between gap-4">
<div>
<ScaffoldSectionTitle>Third Party Auth</ScaffoldSectionTitle>
<ScaffoldSectionDescription className="mb-6">
Use third-party authentication (TPA) systems based on JWTs to access your project.
<br />
Billing is based on the number of monthly active users (MAUs) requesting your API
throughout the billing period (50 included then you'll be charged{' '}
<span className="text-brand">$0.00325</span>
<span> per MAU).</span>
</ScaffoldSectionDescription>
</div>
<div className="flex items-center gap-2 ">
<DocsButton href="https://supabase.com/docs/guides/auth/third-party/overview" />
{integrations.length !== 0 && (
<AddIntegrationDropdown onSelectIntegrationType={setSelectedIntegration} />
) : null
}
docsUrl="https://supabase.com/docs/guides/auth/third-party/overview"
/>
<div className="prose text-sm mb-6 max-w-full">
<span>
Billing is based on the number of monthly active users (MAUs) requesting your API
throughout the billing period (50 included then you'll be charged{' '}
</span>
<span className="text-brand">$0.00325</span>
<span> per MAU).</span>
)}
</div>
</div>
{isLoading && (
<div
className={cn(
@@ -162,6 +168,6 @@ export const ThirdPartyAuthForm = () => {
{`Are you sure you want to delete the ${getIntegrationTypeLabel(getIntegrationType(selectedIntegrationForDeletion))} integration?`}
</p>
</ConfirmationModal>
</div>
</ScaffoldSection>
)
}
@@ -0,0 +1,38 @@
import { ReactNode } from 'react'
import DefaultLayout from 'components/layouts/DefaultLayout'
import AuthLayout from './AuthLayout'
import { PageLayout } from 'components/layouts/PageLayout'
interface AuthProvidersLayoutProps {
children: ReactNode
projectRef: string
}
const AuthProvidersLayout = ({ children, projectRef }: AuthProvidersLayoutProps) => {
const navItems = [
{
label: 'Supabase Auth',
href: `/project/${projectRef}/auth/providers`,
},
{
label: 'Third Party Auth',
href: `/project/${projectRef}/auth/third-party`,
},
]
return (
<DefaultLayout>
<AuthLayout>
<PageLayout
title="Sign In / Up"
subtitle="Configure authentication providers and login methods for your users"
navigationItems={navItems}
>
{children}
</PageLayout>
</AuthLayout>
</DefaultLayout>
)
}
export default AuthProvidersLayout
@@ -1,39 +0,0 @@
import Link from 'next/link'
import { ChevronRight } from 'lucide-react'
import { cn } from 'ui'
export interface BreadcrumbItem {
label: string
href?: string
onClick?: () => void
}
interface BreadcrumbsProps {
items: BreadcrumbItem[]
className?: string
}
const Breadcrumbs = ({ items, className }: BreadcrumbsProps) => {
return (
<div className={cn('flex items-center space-x-2 text-sm text-foreground-light', className)}>
{items.map((item, index) => (
<div key={item.label} className="flex items-center">
{index > 0 && <ChevronRight size={14} strokeWidth={1.5} className="mx-2" />}
{item.href ? (
<Link href={item.href} className="hover:text-foreground transition">
{item.label}
</Link>
) : item.onClick ? (
<button className="hover:text-foreground transition" onClick={item.onClick}>
{item.label}
</button>
) : (
<span>{item.label}</span>
)}
</div>
))}
</div>
)
}
export default Breadcrumbs
@@ -1,16 +0,0 @@
import { ReactNode } from 'react'
import { cn } from 'ui'
import { PAGE_SIZE_CLASSES, type PageSize } from 'ui/src/lib/constants'
interface PageContainerProps {
children?: ReactNode
className?: string
size?: PageSize
}
const PageContainer = ({ children, className, size = 'default' }: PageContainerProps) => {
return <div className={cn('mx-auto', PAGE_SIZE_CLASSES[size], className)}>{children}</div>
}
export default PageContainer
@@ -1,4 +1,5 @@
import { useParams } from 'common'
import { useRouter } from 'next/router'
import { Fragment, ReactNode } from 'react'
import { cn } from 'ui'
import {
@@ -36,14 +37,17 @@ export const PageHeader = ({
}: PageHeaderProps) => {
const { ref } = useParams()
const displayBreadcrumbs = isCompact && title ? [...breadcrumbs, { label: title }] : breadcrumbs
return (
<div className={cn('space-y-4', className)}>
{(breadcrumbs.length > 0 || (isCompact && (title || primaryActions || secondaryActions))) && (
{(displayBreadcrumbs.length > 0 ||
(isCompact && (title || primaryActions || secondaryActions))) && (
<div className={cn('flex items-center gap-4', isCompact ? 'justify-between' : 'mb-4')}>
{breadcrumbs.length > 0 ? (
{displayBreadcrumbs.length > 0 ? (
<Breadcrumb className={cn('text-foreground-muted', isCompact && 'text-base')}>
<BreadcrumbList className={isCompact ? 'text-base' : 'text-xs'}>
{breadcrumbs.map((item, index) => (
{displayBreadcrumbs.map((item, index) => (
<Fragment key={item.label}>
<BreadcrumbItem>
{item.href ? (
@@ -54,7 +58,7 @@ export const PageHeader = ({
<BreadcrumbPageItem>{item.label}</BreadcrumbPageItem>
)}
</BreadcrumbItem>
{index < breadcrumbs.length - 1 && <BreadcrumbSeparator />}
{index < displayBreadcrumbs.length - 1 && <BreadcrumbSeparator />}
</Fragment>
))}
</BreadcrumbList>
@@ -4,8 +4,8 @@ import { ReactNode } from 'react'
import { useParams } from 'common'
import { Button, cn, NavMenu, NavMenuItem } from 'ui'
import { PAGE_SIZE_CLASSES, type PageSize } from 'ui/src/lib/constants'
import { PageHeader } from '.'
import { ScaffoldContainer } from '../Scaffold'
export interface NavigationItem {
id?: string
@@ -28,7 +28,7 @@ interface PageLayoutProps {
secondaryActions?: ReactNode
navigationItems?: NavigationItem[]
className?: string
size?: PageSize
size?: 'default' | 'full'
isCompact?: boolean
}
@@ -69,15 +69,14 @@ const PageLayout = ({
isCompact = false,
}: PageLayoutProps) => {
const router = useRouter()
const { ref } = useParams()
return (
<div className="w-full">
<div
<ScaffoldContainer
className={cn(
'w-full mx-auto',
PAGE_SIZE_CLASSES[size],
size === 'full' && (isCompact ? 'px-6 border-b' : 'px-8 border-b'),
size === 'full' &&
(isCompact ? 'max-w-none !px-6 border-b' : 'max-w-none p!x-8 border-b'),
isCompact ? 'pt-4' : 'pt-12',
navigationItems.length === 0 && size === 'full' && (isCompact ? 'pb-4' : 'pb-8'),
className
@@ -100,18 +99,25 @@ const PageLayout = ({
{navigationItems.length > 0 && (
<NavMenu className={cn('mt-4', size === 'full' && 'border-none')}>
{navigationItems.map((item) => (
<NavMenuItem key={item.label} active={router.pathname === item.href}>
<NavMenuItem key={item.label} active={router.asPath === item.href}>
{item.href ? (
<Link
href={!!ref ? item.href.replace('[ref]', ref) : item.href}
className="inline-flex items-center gap-2"
href={item.href}
className={cn(
'inline-flex items-center gap-2',
router.asPath === item.href && 'text-foreground'
)}
onClick={item.onClick}
>
{item.icon && <span>{item.icon}</span>}
{item.label}
</Link>
) : (
<Button type="link" onClick={item.onClick}>
<Button
type="link"
onClick={item.onClick}
className={cn(router.pathname === item.href && 'text-foreground font-medium')}
>
{item.icon && <span className="mr-2">{item.icon}</span>}
{item.label}
</Button>
@@ -120,10 +126,10 @@ const PageLayout = ({
))}
</NavMenu>
)}
</div>
</ScaffoldContainer>
{/* Content section */}
<main>{children}</main>
{children}
</div>
)
}
@@ -1,33 +0,0 @@
import Link from 'next/link'
import { useCurrentPath } from 'hooks/misc/useCurrentPath'
import { cn } from 'ui'
import { NavMenu, NavMenuItem } from 'ui'
export interface NavigationItem {
label: string
href: string
}
interface PageNavigationProps {
items: NavigationItem[]
className?: string
}
const PageNavigation = ({ items, className }: PageNavigationProps) => {
const currentPath = useCurrentPath()
return (
<NavMenu
className={cn('border-none max-w-full overflow-y-hidden overflow-x-auto', className)}
aria-label="Page navigation"
>
{items.map((item) => (
<NavMenuItem key={item.label} active={currentPath === item.href}>
<Link href={item.href}>{item.label}</Link>
</NavMenuItem>
))}
</NavMenu>
)
}
export default PageNavigation
@@ -1,23 +0,0 @@
import React from 'react'
import { cn } from 'ui'
interface SectionHeaderProps {
title?: string
subtitle?: string
actions?: React.ReactNode
className?: string
}
const SectionHeader = ({ title, subtitle, actions, className }: SectionHeaderProps) => {
return (
<div className={cn('flex items-center justify-between gap-4 mt-8 mb-4', className)}>
<div className="space-y-1">
{title && <h3 className="text-foreground text-xl">{title}</h3>}
{subtitle && <p className="text-sm text-foreground-light">{subtitle}</p>}
</div>
{actions && <div className="flex items-center gap-2">{actions}</div>}
</div>
)
}
export default SectionHeader
@@ -1,9 +1,4 @@
export { default as PageLayout } from './PageLayout'
export { default as PageHeader } from './PageHeader'
export { default as PageContainer } from './PageContainer'
export { default as SectionHeader } from './SectionHeader'
export { default as PageNavigation } from './PageNavigation'
export { default as Breadcrumbs } from './Breadcrumbs'
export type { NavigationItem } from './PageLayout'
export type { BreadcrumbItem } from './Breadcrumbs'
+30 -3
View File
@@ -59,13 +59,22 @@ const ScaffoldDivider = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HT
}
)
const ScaffoldSection = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
({ className, ...props }, ref) => {
interface ScaffoldSectionProps extends React.HTMLAttributes<HTMLDivElement> {
isFullWidth?: boolean
topPadding?: boolean
}
const ScaffoldSection = React.forwardRef<HTMLDivElement, ScaffoldSectionProps>(
({ className, isFullWidth, topPadding, ...props }, ref) => {
return (
<div
ref={ref}
{...props}
className={cn('flex flex-col gap-3 py-6', 'lg:grid md:grid-cols-12 lg:py-12', className)}
className={cn(
'flex flex-col first:pt-12 py-6',
isFullWidth ? 'w-full' : 'gap-3 lg:grid md:grid-cols-12',
className
)}
/>
)
}
@@ -145,6 +154,20 @@ const ScaffoldContainerLegacy = React.forwardRef<
)
})
const ScaffoldSectionTitle = React.forwardRef<
HTMLHeadingElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => {
return <h3 ref={ref} {...props} className={cn('text-foreground text-xl', className)} />
})
const ScaffoldSectionDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
return <p ref={ref} {...props} className={cn('text-sm text-foreground-light', className)} />
})
ScaffoldHeader.displayName = 'ScaffoldHeader'
ScaffoldTitle.displayName = 'ScaffoldTitle'
ScaffoldDescription.displayName = 'ScaffoldDescription'
@@ -158,6 +181,8 @@ ScaffoldFilterAndContent.displayName = 'ScaffoldFilterAndContent'
ScaffoldActionsContainer.displayName = 'ScaffoldActionsContainer'
ScaffoldActionsGroup.displayName = 'ScaffoldActionsGroup'
ScaffoldContainerLegacy.displayName = 'ScaffoldContainerLegacy'
ScaffoldSectionTitle.displayName = 'ScaffoldSectionTitle'
ScaffoldSectionDescription.displayName = 'ScaffoldSectionDescription'
export {
ScaffoldHeader,
@@ -169,6 +194,8 @@ export {
ScaffoldColumn,
ScaffoldSectionDetail,
ScaffoldSectionContent,
ScaffoldSectionTitle,
ScaffoldSectionDescription,
ScaffoldFilterAndContent,
ScaffoldActionsContainer,
ScaffoldActionsGroup,
@@ -1,12 +1,13 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { AuthProvidersForm, BasicAuthSettingsForm } from 'components/interfaces/Auth'
import AuthLayout from 'components/layouts/AuthLayout/AuthLayout'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { PageContainer, PageLayout } from 'components/layouts/PageLayout'
import AuthProvidersLayout from 'components/layouts/AuthLayout/AuthProvidersLayout'
import NoPermission from 'components/ui/NoPermission'
import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions'
import { useParams } from 'common'
import type { NextPageWithLayout } from 'types'
import { ScaffoldContainer } from 'components/layouts/Scaffold'
import { useRouter } from 'next/router'
const ProvidersPage: NextPageWithLayout = () => {
const canReadAuthSettings = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue')
@@ -17,38 +18,16 @@ const ProvidersPage: NextPageWithLayout = () => {
}
return (
<PageContainer className="pb-16">
<ScaffoldContainer>
<BasicAuthSettingsForm />
<AuthProvidersForm />
</PageContainer>
</ScaffoldContainer>
)
}
ProvidersPage.getLayout = (page) => {
const navigationItems = [
{
label: 'Supabase Auth',
href: `/project/[ref]/auth/providers`,
},
{
label: 'Third Party Auth',
href: `/project/[ref]/auth/third-party`,
},
]
return (
<DefaultLayout>
<AuthLayout>
<PageLayout
title="Sign In / Up"
subtitle="Configure authentication providers and login methods for your users"
navigationItems={navigationItems}
>
{page}
</PageLayout>
</AuthLayout>
</DefaultLayout>
)
const { ref: projectRef } = useRouter().query
return <AuthProvidersLayout projectRef={projectRef as string}>{page}</AuthProvidersLayout>
}
export default ProvidersPage
@@ -1,79 +1,31 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import { ThirdPartyAuthForm } from 'components/interfaces/Auth'
import AuthLayout from 'components/layouts/AuthLayout/AuthLayout'
import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions'
import { useParams } from 'common'
import { useCurrentPath } from 'hooks/misc/useCurrentPath'
import Link from 'next/link'
import { NavMenu, NavMenuItem } from 'ui'
import {
ScaffoldContainer,
ScaffoldDescription,
ScaffoldDivider,
ScaffoldHeader,
ScaffoldTitle,
} from 'components/layouts/Scaffold'
import AuthProvidersLayout from 'components/layouts/AuthLayout/AuthProvidersLayout'
import NoPermission from 'components/ui/NoPermission'
import { useCheckPermissions, usePermissionsLoaded } from 'hooks/misc/useCheckPermissions'
import type { NextPageWithLayout } from 'types'
import DefaultLayout from 'components/layouts/DefaultLayout'
import { ScaffoldContainer } from 'components/layouts/Scaffold'
import { useRouter } from 'next/router'
const PageLayout: NextPageWithLayout = () => {
const ThirdPartyPage: NextPageWithLayout = () => {
const canReadAuthSettings = useCheckPermissions(PermissionAction.READ, 'custom_config_gotrue')
const isPermissionsLoaded = usePermissionsLoaded()
const { ref } = useParams()
const currentPath = useCurrentPath()
const navMenuItems = [
{
label: 'Supabase auth',
href: `/project/${ref}/auth/providers`,
},
{
label: 'Third Party auth',
href: `/project/${ref}/auth/third-party`,
},
]
if (isPermissionsLoaded && !canReadAuthSettings) {
return <NoPermission isFullPage resourceText="access your project's auth provider settings" />
}
return (
<>
<ScaffoldHeader className="pb-0">
<ScaffoldContainer id="auth-page-top">
<ScaffoldTitle>Sign in / up</ScaffoldTitle>
<ScaffoldDescription>
Configure authentication providers and login methods for your users
</ScaffoldDescription>
<NavMenu
className="border-none max-w-full overflow-y-hidden overflow-x-auto mt-4"
aria-label="Auth provider settings navigation"
>
{navMenuItems.map((item) => (
<NavMenuItem key={item.label} active={currentPath === item.href}>
<Link href={item.href}>{item.label}</Link>
</NavMenuItem>
))}
</NavMenu>
</ScaffoldContainer>
</ScaffoldHeader>
<ScaffoldDivider />
<ScaffoldContainer className="my-8">
<ThirdPartyAuthForm />
</ScaffoldContainer>
</>
<ScaffoldContainer className="pb-16">
<ThirdPartyAuthForm />
</ScaffoldContainer>
)
}
PageLayout.getLayout = (page) => {
return (
<DefaultLayout>
<AuthLayout>{page}</AuthLayout>
</DefaultLayout>
)
ThirdPartyPage.getLayout = (page) => {
const { ref: projectRef } = useRouter().query
return <AuthProvidersLayout projectRef={projectRef as string}>{page}</AuthProvidersLayout>
}
export default PageLayout
export default ThirdPartyPage