IPv4StatusPanel.tsx175 lines · main
1import { ChevronRight, X } from 'lucide-react'
2import Link from 'next/link'
3import { ReactNode } from 'react'
4import { Button, cn, Collapsible, CollapsibleContent, CollapsibleTrigger, WarningIcon } from 'ui'
5
6import { IS_PLATFORM } from '@/lib/constants'
7
8const IPv4StatusIcon = ({ className, active }: { className?: string; active: boolean }) => {
9 return (
10 <div className={cn('relative inline-flex', className)}>
11 <svg
12 xmlns="http://www.w3.org/2000/svg"
13 fill="none"
14 viewBox="0 0 24 24"
15 strokeWidth="1"
16 stroke="currentColor"
17 className="size-6 stroke-foreground-lighter"
18 >
19 <path
20 strokeLinecap="round"
21 strokeLinejoin="round"
22 d="M12 21a9.004 9.004 0 0 0 8.716-6.747M12 21a9.004 9.004 0 0 1-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 0 1 7.843 4.582M12 3a8.997 8.997 0 0 0-7.843 4.582m15.686 0A11.953 11.953 0 0 1 12 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0 1 21 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0 1 12 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 0 1 3 12c0-1.605.42-3.113 1.157-4.418"
23 />
24 </svg>
25
26 {!active ? (
27 <div className="absolute -right-1.5 -top-1.5 bg-destructive rounded-sm w-4 h-4 flex items-center justify-center">
28 <X size={10} strokeWidth={4} className="text-white rounded-full" />
29 </div>
30 ) : (
31 <div className="absolute -right-1.5 -top-1.5 bg-brand-500 rounded-sm w-4 h-4 flex items-center justify-center">
32 <svg
33 width="10"
34 height="10"
35 viewBox="0 0 10 10"
36 fill="none"
37 xmlns="http://www.w3.org/2000/svg"
38 >
39 <path
40 d="M8.33325 2.5L3.74992 7.08333L1.66659 5"
41 stroke="white"
42 strokeWidth="2"
43 strokeLinecap="round"
44 strokeLinejoin="round"
45 />
46 </svg>
47 </div>
48 )}
49 </div>
50 )
51}
52
53export interface IPv4Status {
54 type: 'error' | 'success'
55 title: string
56 description?: string | ReactNode
57 links?: { text: string; url: string }[]
58}
59
60interface IPv4StatusPanelProps {
61 method: 'direct' | 'transaction' | 'session'
62 ipv4Status: IPv4Status
63 projectRef: string
64}
65
66export function IPv4StatusPanel({ method, ipv4Status, projectRef }: IPv4StatusPanelProps) {
67 if (!IS_PLATFORM) return null
68
69 const links = ipv4Status.links ?? []
70
71 return (
72 <div className="flex flex-col -space-y-px w-full">
73 {method === 'session' ? (
74 <div className="border border-muted px-5 flex gap-7 items-center py-3 rounded-sm bg-alternative/50">
75 <div className="flex w-6 h-6 rounded-sm items-center justify-center gap-2 shrink-0 bg-surface-100">
76 <WarningIcon />
77 </div>
78 <div className="flex flex-col">
79 <span className="text-xs text-foreground">Only use on a IPv4 network</span>
80 <div className="flex flex-col text-xs text-foreground-lighter">
81 <p>Session pooler connections are IPv4 proxied for free.</p>
82 <p>Use Direct Connection if connecting via an IPv6 network.</p>
83 </div>
84 </div>
85 </div>
86 ) : (
87 <>
88 <div
89 className={cn(
90 'border border-muted px-5 flex gap-7 items-center py-3 first:rounded-t',
91 ipv4Status.type === 'error' ? 'rounded-b-none' : 'last:rounded-b'
92 )}
93 >
94 <div className="flex items-center gap-2">
95 <IPv4StatusIcon active={ipv4Status.type === 'success'} />
96 </div>
97 <div className="flex flex-col">
98 <span className="text-xs text-foreground">{ipv4Status.title}</span>
99 {ipv4Status.description &&
100 (typeof ipv4Status.description === 'string' ? (
101 <span className="text-xs text-foreground-lighter">{ipv4Status.description}</span>
102 ) : (
103 ipv4Status.description
104 ))}
105 {links.length > 0 && (
106 <div className="flex items-center gap-x-2 mt-2">
107 {links.map((link) => (
108 <Button key={link.text} asChild type="default" size="tiny">
109 <Link href={link.url} className="text-xs text-light hover:text-foreground">
110 {link.text}
111 </Link>
112 </Button>
113 ))}
114 </div>
115 )}
116 </div>
117 </div>
118
119 {ipv4Status.type === 'error' && (
120 <Collapsible className="group -space-y-px">
121 <CollapsibleTrigger
122 asChild
123 className="group/collapse w-full justify-start rounded-t-none !last:rounded-b group-data-open:rounded-b-none border-muted"
124 >
125 <Button
126 type="default"
127 size="tiny"
128 className="text-foreground-lighter bg-dash-sidebar!"
129 icon={
130 <ChevronRight
131 className={cn(
132 'group-data-open/collapse:rotate-90 text-foreground-muted transition-transform'
133 )}
134 />
135 }
136 >
137 Some platforms are IPv4-only:
138 </Button>
139 </CollapsibleTrigger>
140 <CollapsibleContent className="bg-dash-sidebar rounded-b border px-3 py-2">
141 <div className="flex flex-col gap-2">
142 <p className="text-xs text-foreground-light max-w-xs">
143 A few major platforms are IPv4-only and may not work with a Direct Connection:
144 </p>
145 <div className="flex gap-4">
146 <div className="text-foreground text-xs">Vercel</div>
147 <div className="text-foreground text-xs">GitHub Actions</div>
148 <div className="text-foreground text-xs">Render</div>
149 <div className="text-foreground text-xs">Retool</div>
150 </div>
151 <p className="text-xs text-foreground-lighter max-w-xs">
152 If you wish to use a Direct Connection with these, please purchase{' '}
153 <Link
154 href={`/project/${projectRef}/settings/addons?panel=ipv4`}
155 className="text-xs text-light hover:text-foreground"
156 >
157 IPv4 support
158 </Link>
159 .
160 </p>
161 <p className="text-xs text-foreground-lighter max-w-xs">
162 You may also use the{' '}
163 <span className="text-foreground-light">Session Pooler</span> or{' '}
164 <span className="text-foreground-light">Transaction Pooler</span> if you are on
165 a IPv4 network.
166 </p>
167 </div>
168 </CollapsibleContent>
169 </Collapsible>
170 )}
171 </>
172 )}
173 </div>
174 )
175}