Skip to content

Commit f650b5a

Browse files
MildTomatosaltcodjoshenlim
authored
Feat/connection string revamp (supabase#30572)
* add new page * moar * moar * added icons * improve icons * moved connect dialog to main header * update text * smaller screen support * moar * add python and sqlalchemyString * moar * add IPv4 warning * moar * Delete pooler-icons-v2.tsx * tidy * Delete DatabaseSettings.tsx * tidy * tidy * moar. Session pooler is de-prioritized * Update DatabaseConnectionString.tsx * type issue * moar * Update DatabaseConnectionString.tsx * Spelling * Clean up LayoutHeader * Clean up ConnectionPanel * Clean up ConnectionParameters * Last batch of clean up * Fix loading state padding * Shift old Connect files to new Connect folder outside of Home * Final clean up * Smol fix * FIX * Fix button link * Fixes * Lint --------- Co-authored-by: Terry Sutton <[email protected]> Co-authored-by: Joshen Lim <[email protected]>
1 parent 98797fc commit f650b5a

File tree

48 files changed

+2369
-338
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+2369
-338
lines changed

apps/studio/components/interfaces/Home/Connect/Connect.constants.ts renamed to apps/studio/components/interfaces/Connect/Connect.constants.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,63 @@
1+
import { CodeBlockLang } from 'ui'
2+
3+
export type DatabaseConnectionType =
4+
| 'uri'
5+
| 'psql'
6+
| 'golang'
7+
| 'jdbc'
8+
| 'dotnet'
9+
| 'nodejs'
10+
| 'php'
11+
| 'python'
12+
| 'sqlalchemy'
13+
14+
export const DATABASE_CONNECTION_TYPES: {
15+
id: DatabaseConnectionType
16+
label: string
17+
contentType: 'input' | 'code'
18+
lang: CodeBlockLang
19+
fileTitle: string | undefined
20+
}[] = [
21+
{ id: 'uri', label: 'URI', contentType: 'input', lang: 'bash', fileTitle: undefined },
22+
{ id: 'psql', label: 'PSQL', contentType: 'code', lang: 'bash', fileTitle: undefined },
23+
{ id: 'golang', label: 'Golang', contentType: 'code', lang: 'go', fileTitle: '.env' },
24+
{ id: 'jdbc', label: 'JDBC', contentType: 'input', lang: 'bash', fileTitle: undefined },
25+
{
26+
id: 'dotnet',
27+
label: '.NET',
28+
contentType: 'code',
29+
lang: 'csharp',
30+
fileTitle: 'appsettings.json',
31+
},
32+
{ id: 'nodejs', label: 'Node.js', contentType: 'code', lang: 'js', fileTitle: '.env' },
33+
{ id: 'php', label: 'PHP', contentType: 'code', lang: 'php', fileTitle: '.env' },
34+
{ id: 'python', label: 'Python', contentType: 'code', lang: 'python', fileTitle: '.env' },
35+
{ id: 'sqlalchemy', label: 'SQLAlchemy', contentType: 'code', lang: 'python', fileTitle: '.env' },
36+
]
37+
38+
export const CONNECTION_PARAMETERS = {
39+
host: {
40+
key: 'host',
41+
description: 'The hostname of your database',
42+
},
43+
port: {
44+
key: 'port',
45+
description: 'Port number for the connection',
46+
},
47+
database: {
48+
key: 'database',
49+
description: 'Default database name',
50+
},
51+
user: {
52+
key: 'user',
53+
description: 'Database user',
54+
},
55+
pool_mode: {
56+
key: 'pool_mode',
57+
description: 'Connection pooling behavior',
58+
},
59+
} as const
60+
161
export type ConnectionType = {
262
key: string
363
icon: string
@@ -283,6 +343,7 @@ export const ORMS: ConnectionType[] = [
283343
]
284344

285345
export const CONNECTION_TYPES = [
346+
{ key: 'direct', label: 'Connection String', obj: [] },
286347
{ key: 'frameworks', label: 'App Frameworks', obj: FRAMEWORKS },
287348
{ key: 'mobiles', label: 'Mobile Frameworks', obj: MOBILES },
288349
{ key: 'orms', label: 'ORMs', obj: ORMS },

apps/studio/components/interfaces/Home/Connect/Connect.tsx renamed to apps/studio/components/interfaces/Connect/Connect.tsx

Lines changed: 36 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@ import { useParams } from 'common'
33
import { ExternalLink, Plug } from 'lucide-react'
44
import { useState } from 'react'
55

6-
import { DatabaseConnectionString } from 'components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString'
7-
import { PoolingModesModal } from 'components/interfaces/Settings/Database/PoolingModesModal'
6+
import { DatabaseConnectionString } from 'components/interfaces/Connect/DatabaseConnectionString'
7+
import { DatabaseConnectionString as OldDatabaseConnectionString } from 'components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString'
8+
89
import Panel from 'components/ui/Panel'
910
import { getAPIKeys, useProjectSettingsV2Query } from 'data/config/project-settings-v2-query'
1011
import { useCheckPermissions } from 'hooks/misc/useCheckPermissions'
12+
import { useFlag } from 'hooks/ui/useFlag'
13+
import { useAppStateSnapshot } from 'state/app-state'
1114
import {
1215
Button,
1316
DIALOG_PADDING_X,
14-
DIALOG_PADDING_X_SMALL,
1517
DIALOG_PADDING_Y,
1618
Dialog,
1719
DialogContent,
@@ -31,7 +33,9 @@ import ConnectDropdown from './ConnectDropdown'
3133
import ConnectTabContent from './ConnectTabContent'
3234

3335
const Connect = () => {
36+
const state = useAppStateSnapshot()
3437
const { ref: projectRef } = useParams()
38+
const connectDialogUpdate = useFlag('connectDialogUpdate')
3539

3640
const [connectionObject, setConnectionObject] = useState<ConnectionType[]>(FRAMEWORKS)
3741
const [selectedParent, setSelectedParent] = useState(connectionObject[0].key) // aka nextjs
@@ -145,14 +149,18 @@ const Connect = () => {
145149

146150
return (
147151
<>
148-
<Dialog>
152+
<Dialog open={state.showConnectDialog} onOpenChange={state.setShowConnectDialog}>
149153
<DialogTrigger asChild>
150-
<Button type="primary" icon={<Plug className="rotate-90" />}>
154+
<Button
155+
type={connectDialogUpdate ? 'default' : 'primary'}
156+
className={cn(connectDialogUpdate && 'rounded-full')}
157+
icon={<Plug className="rotate-90" />}
158+
>
151159
<span>Connect</span>
152160
</Button>
153161
</DialogTrigger>
154162
<DialogContent className={cn('sm:max-w-5xl p-0')} centered={false}>
155-
<DialogHeader className="pb-3">
163+
<DialogHeader className={DIALOG_PADDING_X}>
156164
<DialogTitle>Connect to your project</DialogTitle>
157165
<DialogDescription>
158166
Get the connection strings and environment variables for your app
@@ -163,10 +171,7 @@ const Connect = () => {
163171
defaultValue="direct"
164172
onValueChange={(value) => handleConnectionType(value)}
165173
>
166-
<TabsList_Shadcn_ className={cn('flex gap-4', DIALOG_PADDING_X_SMALL)}>
167-
<TabsTrigger_Shadcn_ key="direct" value="direct" className="px-0">
168-
Connection String
169-
</TabsTrigger_Shadcn_>
174+
<TabsList_Shadcn_ className={cn('flex gap-x-4', DIALOG_PADDING_X)}>
170175
{CONNECTION_TYPES.map((type) => (
171176
<TabsTrigger_Shadcn_ key={type.key} value={type.key} className="px-0">
172177
{type.label}
@@ -183,14 +188,34 @@ const Connect = () => {
183188
.find((parent) => parent.key === selectedParent)
184189
?.children.find((child) => child.key === selectedChild)?.children.length || 0) > 0
185190

191+
if (type.key === 'direct') {
192+
return (
193+
<TabsContent_Shadcn_
194+
key="direct"
195+
value="direct"
196+
className={cn('!mt-0', 'p-0', 'flex flex-col gap-6')}
197+
>
198+
<div className={DIALOG_PADDING_Y}>
199+
{connectDialogUpdate ? (
200+
<DatabaseConnectionString />
201+
) : (
202+
<div className="px-7">
203+
<OldDatabaseConnectionString appearance="minimal" />
204+
</div>
205+
)}
206+
</div>
207+
</TabsContent_Shadcn_>
208+
)
209+
}
210+
186211
return (
187212
<TabsContent_Shadcn_
188213
key={`content-${type.key}`}
189214
value={type.key}
190215
className={cn(DIALOG_PADDING_X, DIALOG_PADDING_Y, '!mt-0')}
191216
>
192217
<div className="flex justify-between">
193-
<div className="flex items-center gap-5">
218+
<div className="flex items-center gap-3">
194219
<ConnectDropdown
195220
state={selectedParent}
196221
updateState={handleParentChange}
@@ -254,17 +279,9 @@ const Connect = () => {
254279
</TabsContent_Shadcn_>
255280
)
256281
})}
257-
<TabsContent_Shadcn_
258-
key="direct"
259-
value="direct"
260-
className={cn(DIALOG_PADDING_X_SMALL, DIALOG_PADDING_Y, '!mt-0')}
261-
>
262-
<DatabaseConnectionString appearance="minimal" />
263-
</TabsContent_Shadcn_>
264282
</Tabs_Shadcn_>
265283
</DialogContent>
266284
</Dialog>
267-
<PoolingModesModal />
268285
</>
269286
)
270287
}

apps/studio/components/interfaces/Home/Connect/Connect.types.ts renamed to apps/studio/components/interfaces/Connect/Connect.types.ts

File renamed without changes.

apps/studio/components/interfaces/Home/Connect/Connect.utils.ts renamed to apps/studio/components/interfaces/Connect/Connect.utils.ts

File renamed without changes.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { Box, Check, ChevronDown } from 'lucide-react'
2+
import { useState } from 'react'
3+
4+
import {
5+
Button,
6+
CommandEmpty_Shadcn_,
7+
CommandGroup_Shadcn_,
8+
CommandInput_Shadcn_,
9+
CommandItem_Shadcn_,
10+
CommandList_Shadcn_,
11+
Command_Shadcn_,
12+
PopoverContent_Shadcn_,
13+
PopoverTrigger_Shadcn_,
14+
Popover_Shadcn_,
15+
cn,
16+
} from 'ui'
17+
import { ConnectionIcon } from './ConnectionIcon'
18+
19+
interface ConnectDropdownProps {
20+
state: string
21+
updateState: (state: string) => void
22+
label: string
23+
items: any[]
24+
}
25+
26+
const ConnectDropdown = ({
27+
state,
28+
updateState,
29+
label,
30+
31+
items,
32+
}: ConnectDropdownProps) => {
33+
const [open, setOpen] = useState(false)
34+
35+
function onSelectLib(key: string) {
36+
updateState(key)
37+
setOpen(false)
38+
}
39+
40+
const selectedItem = items.find((item) => item.key === state)
41+
42+
return (
43+
<Popover_Shadcn_ open={open} onOpenChange={setOpen} modal={false}>
44+
<div className="flex ">
45+
<span className="flex items-center text-foreground-lighter px-3 rounded-lg rounded-r-none text-xs border border-button border-r-0">
46+
{label}
47+
</span>
48+
<PopoverTrigger_Shadcn_ asChild>
49+
<Button
50+
size="small"
51+
type="default"
52+
className="gap-0 rounded-l-none"
53+
iconRight={<ChevronDown strokeWidth={1.5} />}
54+
>
55+
<div className="flex items-center gap-2">
56+
{selectedItem?.icon ? (
57+
<ConnectionIcon connection={selectedItem.icon} />
58+
) : (
59+
<Box size={12} />
60+
)}
61+
{selectedItem?.label}
62+
</div>
63+
</Button>
64+
</PopoverTrigger_Shadcn_>
65+
</div>
66+
<PopoverContent_Shadcn_ className="p-0 max-w-48" side="bottom" align="start">
67+
<Command_Shadcn_>
68+
<CommandInput_Shadcn_ placeholder="Search..." />
69+
<CommandList_Shadcn_>
70+
<CommandEmpty_Shadcn_>No results found.</CommandEmpty_Shadcn_>
71+
<CommandGroup_Shadcn_>
72+
{items.map((item) => (
73+
<CommandItem_Shadcn_
74+
key={item.key}
75+
value={item.key}
76+
onSelect={() => {
77+
onSelectLib(item.key)
78+
setOpen(false)
79+
}}
80+
className="flex gap-2 items-center"
81+
>
82+
{item.icon ? <ConnectionIcon connection={item.icon} /> : <Box size={12} />}
83+
{item.label}
84+
<Check
85+
size={15}
86+
className={cn('ml-auto ', item.key === state ? 'opacity-100' : 'opacity-0')}
87+
/>
88+
</CommandItem_Shadcn_>
89+
))}
90+
</CommandGroup_Shadcn_>
91+
</CommandList_Shadcn_>
92+
</Command_Shadcn_>
93+
</PopoverContent_Shadcn_>
94+
</Popover_Shadcn_>
95+
)
96+
}
97+
98+
export default ConnectDropdown

apps/studio/components/interfaces/Home/Connect/ConnectTabContent.tsx renamed to apps/studio/components/interfaces/Connect/ConnectTabContent.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import dynamic from 'next/dynamic'
2-
import { forwardRef, HTMLAttributes } from 'react'
2+
import { forwardRef, HTMLAttributes, useMemo } from 'react'
33

44
import { useParams } from 'common'
55
import { getConnectionStrings } from 'components/interfaces/Settings/Database/DatabaseSettings/DatabaseSettings.utils'
@@ -49,16 +49,15 @@ const ConnectTabContentNew = forwardRef<HTMLDivElement, ConnectContentTabProps>(
4949
const connectionStringPoolerTransaction = connectionStringsPooler.uri
5050
const connectionStringPoolerSession = connectionStringsPooler.uri.replace('6543', '5432')
5151

52-
const ContentFile = dynamic<ConnectContentTabProps>(
53-
() => import(`./content/${filePath}/content`),
54-
{
52+
const ContentFile = useMemo(() => {
53+
return dynamic<ConnectContentTabProps>(() => import(`./content/${filePath}/content`), {
5554
loading: () => (
5655
<div className="p-4 min-h-[331px]">
5756
<GenericSkeletonLoader />
5857
</div>
5958
),
60-
}
61-
)
59+
})
60+
}, [filePath])
6261

6362
return (
6463
<div ref={ref} {...props} className={cn('border rounded-lg', props.className)}>

apps/studio/components/interfaces/Home/Connect/ConnectTabs.tsx renamed to apps/studio/components/interfaces/Connect/ConnectTabs.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import { Tabs_Shadcn_ } from 'ui'
21
import { FileJson2 } from 'lucide-react'
3-
import { TabsList_Shadcn_, TabsTrigger_Shadcn_ } from 'ui'
4-
import { TabsContent_Shadcn_ } from 'ui'
5-
import React, { ReactNode } from 'react'
2+
import { isValidElement, ReactNode } from 'react'
3+
4+
import { Tabs_Shadcn_, TabsContent_Shadcn_, TabsList_Shadcn_, TabsTrigger_Shadcn_ } from 'ui'
65

76
interface ConnectTabTriggerProps {
87
value: string
@@ -22,7 +21,7 @@ interface ConnectTabContentProps {
2221
const ConnectTabs = ({ children }: ConnectFileTabProps) => {
2322
const firstChild = children[0]
2423

25-
const defaultValue = React.isValidElement(firstChild)
24+
const defaultValue = isValidElement(firstChild)
2625
? (firstChild.props as any)?.children[0]?.props?.value || ''
2726
: null
2827

@@ -57,4 +56,4 @@ export const ConnectTabContent = ({ value, children }: ConnectTabContentProps) =
5756
)
5857
}
5958

60-
export { ConnectTabTrigger, ConnectTabTriggers, ConnectTabs }
59+
export { ConnectTabs, ConnectTabTrigger, ConnectTabTriggers }

apps/studio/components/interfaces/Home/Connect/ConnectionIcon.tsx renamed to apps/studio/components/interfaces/Connect/ConnectionIcon.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import { BASE_PATH } from 'lib/constants'
2-
31
import { useTheme } from 'next-themes'
42
import Image from 'next/image'
3+
4+
import { BASE_PATH } from 'lib/constants'
5+
56
interface ConnectionIconProps {
67
connection: any
78
}
89

9-
const ConnectionIcon = ({ connection }: ConnectionIconProps) => {
10+
export const ConnectionIcon = ({ connection }: ConnectionIconProps) => {
1011
const { resolvedTheme } = useTheme()
1112

1213
const imageFolder = ['ionic-angular'].includes(connection) ? 'icons/frameworks' : 'libraries'
@@ -28,5 +29,3 @@ const ConnectionIcon = ({ connection }: ConnectionIconProps) => {
2829
/>
2930
)
3031
}
31-
32-
export default ConnectionIcon

0 commit comments

Comments
 (0)