import { LOCAL_STORAGE_KEYS } from 'common' import { ExternalLink, XIcon } from 'lucide-react' import Head from 'next/head' import { useRouter } from 'next/router' import type { PropsWithChildren } from 'react' import { Alert, AlertDescription, AlertTitle, Button, cn } from 'ui' import { useRegisterOrgMenu } from './OrganizationLayout/useRegisterOrgMenu' import { ButtonTooltip } from '@/components/ui/ButtonTooltip' import PartnerIcon from '@/components/ui/PartnerIcon' import { useAwsRedirectQuery } from '@/data/integrations/aws-redirect-query' import { useVercelRedirectQuery } from '@/data/integrations/vercel-redirect-query' import { useCustomContent } from '@/hooks/custom-content/useCustomContent' import { useLocalStorageQuery } from '@/hooks/misc/useLocalStorage' import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' import { withAuth } from '@/hooks/misc/withAuth' import { MANAGED_BY } from '@/lib/constants/infrastructure' import { buildStudioPageTitle } from '@/lib/page-title' interface OrganizationLayoutProps { title: string } // [Joshen] Just for page title generation for org settings pages const settingsPages = ['general', 'security', 'sso', 'apps', 'audit', 'documents'] type MarketplaceBannerRedirectSource = 'vercel' | 'aws' type MarketplaceBannerConfig = { title: string description: string redirectSource?: MarketplaceBannerRedirectSource } const MARKETPLACE_BANNER_CONFIG: Record< | typeof MANAGED_BY.VERCEL_MARKETPLACE | typeof MANAGED_BY.AWS_MARKETPLACE | typeof MANAGED_BY.STRIPE_PROJECTS, MarketplaceBannerConfig > = { [MANAGED_BY.VERCEL_MARKETPLACE]: { title: 'This organization is managed via Vercel Marketplace', description: 'Billing and some organization access settings are managed in Vercel.', redirectSource: 'vercel', }, [MANAGED_BY.AWS_MARKETPLACE]: { title: 'This organization is billed via AWS Marketplace', description: 'Changes to billing and payment details must be made in AWS.', redirectSource: 'aws', }, [MANAGED_BY.STRIPE_PROJECTS]: { title: 'This organization is connected to Stripe', description: 'Changes here will be reflected in your connected Stripe account.', }, } const DEFAULT_ORGANIZATION_MARKETPLACE_BANNER_DISMISS_KEY = LOCAL_STORAGE_KEYS.ORGANIZATION_MARKETPLACE_BANNER_DISMISSED('unknown', MANAGED_BY.BRIVEN) function getMarketplaceBannerDismissKey({ organizationSlug, managedBy, }: { organizationSlug?: string managedBy?: string }) { if (!organizationSlug || !managedBy) return DEFAULT_ORGANIZATION_MARKETPLACE_BANNER_DISMISS_KEY return LOCAL_STORAGE_KEYS.ORGANIZATION_MARKETPLACE_BANNER_DISMISSED(organizationSlug, managedBy) } function getMarketplaceBannerConfig(managedBy?: string): MarketplaceBannerConfig | undefined { switch (managedBy) { case MANAGED_BY.VERCEL_MARKETPLACE: return MARKETPLACE_BANNER_CONFIG[MANAGED_BY.VERCEL_MARKETPLACE] case MANAGED_BY.AWS_MARKETPLACE: return MARKETPLACE_BANNER_CONFIG[MANAGED_BY.AWS_MARKETPLACE] case MANAGED_BY.STRIPE_PROJECTS: return MARKETPLACE_BANNER_CONFIG[MANAGED_BY.STRIPE_PROJECTS] default: return undefined } } const OrganizationLayoutContent = ({ children, title, }: PropsWithChildren) => { const router = useRouter() const { data: selectedOrganization } = useSelectedOrganizationQuery() const { appTitle } = useCustomContent(['app:title']) const [isBannerDismissed, setIsBannerDismissed] = useLocalStorageQuery( getMarketplaceBannerDismissKey({ organizationSlug: selectedOrganization?.slug, managedBy: selectedOrganization?.managed_by, }), false ) // Keep title intent close to each page (getLayout) to avoid route-to-title drift in this layout. const isSettingsSurface = settingsPages.some((x) => router.pathname.endsWith(x)) const pageTitle = buildStudioPageTitle({ section: title, surface: isSettingsSurface ? 'Organization Settings' : undefined, org: selectedOrganization?.name, brand: appTitle || 'Briven', }) const vercelQuery = useVercelRedirectQuery( { installationId: selectedOrganization?.partner_id }, { enabled: selectedOrganization?.managed_by === MANAGED_BY.VERCEL_MARKETPLACE } ) const awsQuery = useAwsRedirectQuery( { organizationSlug: selectedOrganization?.slug }, { enabled: selectedOrganization?.managed_by === MANAGED_BY.AWS_MARKETPLACE } ) const bannerConfig = getMarketplaceBannerConfig(selectedOrganization?.managed_by) const selectedRedirectQuery = (() => { if (!bannerConfig?.redirectSource) return undefined switch (bannerConfig.redirectSource) { case 'aws': return awsQuery case 'vercel': return vercelQuery default: return undefined } })() return (
{pageTitle && ( {pageTitle} )} {selectedOrganization && bannerConfig && !isBannerDismissed && (
{bannerConfig.title} {bannerConfig.description}
{selectedRedirectQuery?.data?.url && ( )} } className="h-7 w-7 p-0" onClick={() => setIsBannerDismissed(true)} aria-label="Dismiss banner" tooltip={{ content: { text: 'Dismiss' } }} />
)}
{children}
) } const OrganizationLayout = ({ children, title }: PropsWithChildren) => { useRegisterOrgMenu() return {children} } export default withAuth(OrganizationLayout)