InterstitialLayout.tsx144 lines · main
1import { motion } from 'framer-motion'
2import { ArrowRightLeft } from 'lucide-react'
3import type { PropsWithChildren, ReactNode } from 'react'
4import { Card, CardContent, CardHeader, cn } from 'ui'
5
6import { ProfileImage } from '@/components/ui/ProfileImage'
7import { BASE_PATH } from '@/lib/constants'
8
9const MotionCard = motion.create(Card)
10
11interface InterstitialLayoutProps {
12 logo?: ReactNode
13 title?: ReactNode
14 description?: ReactNode
15 containerClassName?: string
16 cardClassName?: string
17 titleClassName?: string
18 descriptionClassName?: string
19}
20
21export const InterstitialLayout = ({
22 logo,
23 title,
24 description,
25 containerClassName,
26 cardClassName,
27 titleClassName,
28 descriptionClassName,
29 children,
30}: PropsWithChildren<InterstitialLayoutProps>) => {
31 const TitleElement = typeof title === 'string' ? 'h1' : 'div'
32 const DescriptionElement = typeof description === 'string' ? 'p' : 'div'
33
34 return (
35 <div
36 className={cn(
37 'flex min-h-screen w-full items-center justify-center bg-studio px-2 py-6',
38 containerClassName
39 )}
40 >
41 <MotionCard
42 layout="size"
43 transition={{ duration: 0.3, ease: [0.16, 1, 0.3, 1] }}
44 className={cn('mx-auto w-full max-w-[400px] overflow-hidden', cardClassName)}
45 >
46 {(logo || title || description) && (
47 <CardHeader className="items-center gap-0 space-y-0 border-0 px-6 py-6 text-center font-normal [--card-padding-x:1.5rem]">
48 {logo && <div className="mb-4 flex justify-center">{logo}</div>}
49 {(title || description) && (
50 <div className="flex flex-col items-center gap-1">
51 {title && (
52 <TitleElement
53 className={cn(
54 'font-sans text-lg font-medium tracking-tight text-balance text-foreground',
55 titleClassName
56 )}
57 >
58 {title}
59 </TitleElement>
60 )}
61 {description && (
62 <DescriptionElement
63 className={cn(
64 '!m-0 px-3 text-sm leading-tight !text-balance text-foreground-lighter',
65 descriptionClassName
66 )}
67 >
68 {description}
69 </DescriptionElement>
70 )}
71 </div>
72 )}
73 </CardHeader>
74 )}
75 {children}
76 </MotionCard>
77 </div>
78 )
79}
80
81export const LogoBox = ({ children, className }: { children: ReactNode; className?: string }) => (
82 <div
83 className={cn(
84 'flex size-12 items-center justify-center overflow-hidden rounded-xl border bg-muted',
85 className
86 )}
87 >
88 {children}
89 </div>
90)
91
92export const LogoPair = ({ left, right }: { left: ReactNode; right: ReactNode }) => (
93 <div className="flex items-center justify-center gap-2.5">
94 {left}
95 <ArrowRightLeft className="size-4 text-foreground-muted" />
96 {right}
97 </div>
98)
99
100export const BrivenLogo = () => (
101 <LogoBox>
102 <img alt="Briven" src={`${BASE_PATH}/img/briven-logo.svg`} className="size-7" />
103 </LogoBox>
104)
105
106export const PartnerLogo = ({ src, alt }: { src: string; alt: string }) => (
107 <LogoBox>
108 <img alt={alt} src={src} className="size-full object-cover" />
109 </LogoBox>
110)
111
112export const InterstitialAccountRow = ({
113 avatarUrl,
114 displayName,
115 action,
116 className,
117}: {
118 avatarUrl?: string
119 displayName?: string
120 action?: ReactNode
121 className?: string
122}) => (
123 <Card className={cn('shadow-none', !action && 'border-muted bg-surface-200/50', className)}>
124 <CardContent
125 className={cn(
126 'flex gap-3 border-none',
127 action ? 'items-center px-4 py-3' : 'items-start p-3'
128 )}
129 >
130 <ProfileImage
131 src={avatarUrl}
132 alt={displayName}
133 className="size-8 flex-shrink-0 rounded-full border border-muted"
134 />
135 <div className="min-w-0 flex-1">
136 <p className="text-xs text-foreground-light">Signed in as</p>
137 <p className="truncate text-sm text-foreground">
138 {displayName || <span className="invisible">Loading account</span>}
139 </p>
140 </div>
141 {action}
142 </CardContent>
143 </Card>
144)