From cc008ef98321c886c8b8a32836179b345f54400a Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Thu, 15 Feb 2024 09:25:17 -0330 Subject: [PATCH] Feat/homepage connect modal combined (#20328) * First * Update * Move connection string panel to its own component * Add connection strings * Format button * Add dummy files to get logic working * Add mdx packages * add file handling and change logic * Support connections * Fix icons, add direct and graphql * Remove mdx stuff * Add pooler toggle * Fix child logic * Fix grandchild logic * Refactor data object * Move back to dialog ui * Refactor to shadcn tabs * Find the files * Wire up icons * Loop over connection types * Reformat file paths * Remove from project home * fix tab rendering issue * Check for unique keys * Add new libs * comment * Add direct connection ui * Get project connection strings * Cleanup * Add project keys * Add types file * Undo update * Add next app files * Add files * Add next pages files * Add nuxt * Fix env vars * Add rest of frameworks, graphql and orms * Add orms * Fix types * Add guide links * Run prettier * Separate graphql * Move ui * Move direct connection to first position * Move to single files * Update styling * Fix locations * Fix next content * Fix dialog close button, add proper light mode theme * Fix dialog style issues * CleanupA * Pin to top * Feat/homepage connect modal combined styling update (#21079) * chore: update styling. also moved useStates as it was sort of duplicated * Cleanup --------- Co-authored-by: Terry Sutton * Close pooler modal * Make the db connection panel blend in * Allow for DatabaseSelector * Dont open by default * Some fixes * Set min height to loader * Fix * Small styling fixes for table editor sort filter popover * Make connect button stand out --------- Co-authored-by: Jordi Enric Co-authored-by: Jonathan Summers-Muir Co-authored-by: Joshen Lim --- .../components/header/filter/FilterRow.tsx | 2 +- .../components/header/sort/SortPopover.tsx | 8 +- .../Home/Connect/Connect.constants.ts | 152 ++++++++++ .../interfaces/Home/Connect/Connect.tsx | 273 ++++++++++++++++++ .../interfaces/Home/Connect/Connect.types.ts | 11 + .../interfaces/Home/Connect/Connect.utils.ts | 45 +++ .../Home/Connect/ConnectDropdown.tsx | 93 ++++++ .../Home/Connect/ConnectTabContent.tsx | 29 ++ .../interfaces/Home/Connect/ConnectTabs.tsx | 60 ++++ .../Home/Connect/ConnectionIcon.tsx | 29 ++ .../Home/Connect/content/drizzle/content.tsx | 63 ++++ .../content/nextjs/app/supabasejs/content.tsx | 181 ++++++++++++ .../nextjs/pages/supabasejs/content.tsx | 80 +++++ .../content/nuxt/supabasejs/content.tsx | 72 +++++ .../Home/Connect/content/prisma/content.tsx | 46 +++ .../content/react/supabasejs/content.tsx | 79 +++++ .../content/refine/supabasejs/content.tsx | 115 ++++++++ .../content/solidjs/supabasejs/content.tsx | 71 +++++ .../content/sveltekit/supabasejs/content.tsx | 77 +++++ .../content/vuejs/supabasejs/content.tsx | 72 +++++ .../interfaces/Home/ServiceStatus.tsx | 90 +++--- .../DatabaseConnectionString.tsx | 52 +++- .../Settings/Database/PoolingModesModal.tsx | 74 ++--- .../layouts/ProjectLayout/BuildingState.tsx | 1 + .../to-be-cleaned/SimpleCodeBlock.tsx | 8 +- apps/studio/components/ui/Panel.tsx | 34 +-- apps/studio/pages/api/connect/index.ts | 40 +++ apps/studio/pages/project/[ref]/index.tsx | 8 +- .../pages/project/[ref]/settings/database.tsx | 2 +- apps/studio/pages/projects.tsx | 1 - .../img/libraries/drizzle-dark-icon.svg | 1 + .../public/img/libraries/drizzle-icon.svg | 1 + .../public/img/libraries/graphql-icon.svg | 1 + .../studio/public/img/libraries/nuxt-icon.svg | 1 + .../public/img/libraries/postgres-icon.svg | 1 + .../public/img/libraries/prisma-dark-icon.svg | 1 + .../public/img/libraries/prisma-icon.svg | 1 + .../public/img/libraries/redwoodjs-icon.svg | 1 + .../public/img/libraries/refine-icon.svg | 1 + .../public/img/libraries/solidjs-icon.svg | 1 + .../public/img/libraries/supabase-icon.svg | 1 + .../{svelte-icon.svg => sveltekit-icon.svg} | 0 package-lock.json | 5 +- packages/ui/index.tsx | 4 + .../ui/src/components/shadcn/ui/dialog.tsx | 57 +++- 45 files changed, 1813 insertions(+), 132 deletions(-) create mode 100644 apps/studio/components/interfaces/Home/Connect/Connect.constants.ts create mode 100644 apps/studio/components/interfaces/Home/Connect/Connect.tsx create mode 100644 apps/studio/components/interfaces/Home/Connect/Connect.types.ts create mode 100644 apps/studio/components/interfaces/Home/Connect/Connect.utils.ts create mode 100644 apps/studio/components/interfaces/Home/Connect/ConnectDropdown.tsx create mode 100644 apps/studio/components/interfaces/Home/Connect/ConnectTabContent.tsx create mode 100644 apps/studio/components/interfaces/Home/Connect/ConnectTabs.tsx create mode 100644 apps/studio/components/interfaces/Home/Connect/ConnectionIcon.tsx create mode 100644 apps/studio/components/interfaces/Home/Connect/content/drizzle/content.tsx create mode 100644 apps/studio/components/interfaces/Home/Connect/content/nextjs/app/supabasejs/content.tsx create mode 100644 apps/studio/components/interfaces/Home/Connect/content/nextjs/pages/supabasejs/content.tsx create mode 100644 apps/studio/components/interfaces/Home/Connect/content/nuxt/supabasejs/content.tsx create mode 100644 apps/studio/components/interfaces/Home/Connect/content/prisma/content.tsx create mode 100644 apps/studio/components/interfaces/Home/Connect/content/react/supabasejs/content.tsx create mode 100644 apps/studio/components/interfaces/Home/Connect/content/refine/supabasejs/content.tsx create mode 100644 apps/studio/components/interfaces/Home/Connect/content/solidjs/supabasejs/content.tsx create mode 100644 apps/studio/components/interfaces/Home/Connect/content/sveltekit/supabasejs/content.tsx create mode 100644 apps/studio/components/interfaces/Home/Connect/content/vuejs/supabasejs/content.tsx create mode 100644 apps/studio/pages/api/connect/index.ts create mode 100644 apps/studio/public/img/libraries/drizzle-dark-icon.svg create mode 100644 apps/studio/public/img/libraries/drizzle-icon.svg create mode 100644 apps/studio/public/img/libraries/graphql-icon.svg create mode 100644 apps/studio/public/img/libraries/nuxt-icon.svg create mode 100644 apps/studio/public/img/libraries/postgres-icon.svg create mode 100644 apps/studio/public/img/libraries/prisma-dark-icon.svg create mode 100644 apps/studio/public/img/libraries/prisma-icon.svg create mode 100644 apps/studio/public/img/libraries/redwoodjs-icon.svg create mode 100644 apps/studio/public/img/libraries/refine-icon.svg create mode 100644 apps/studio/public/img/libraries/solidjs-icon.svg create mode 100644 apps/studio/public/img/libraries/supabase-icon.svg rename apps/studio/public/img/libraries/{svelte-icon.svg => sveltekit-icon.svg} (100%) diff --git a/apps/studio/components/grid/components/header/filter/FilterRow.tsx b/apps/studio/components/grid/components/header/filter/FilterRow.tsx index 3dcf9c383b2..b35d58b8b0a 100644 --- a/apps/studio/components/grid/components/header/filter/FilterRow.tsx +++ b/apps/studio/components/grid/components/header/filter/FilterRow.tsx @@ -43,7 +43,7 @@ const FilterRow = ({ table, filter, filterIdx, onChange, onDelete, onKeyDown }: } - className="w-32" + className="w-32 justify-start" > {column?.name ?? ''} diff --git a/apps/studio/components/grid/components/header/sort/SortPopover.tsx b/apps/studio/components/grid/components/header/sort/SortPopover.tsx index bbbed488808..ddba5c7134f 100644 --- a/apps/studio/components/grid/components/header/sort/SortPopover.tsx +++ b/apps/studio/components/grid/components/header/sort/SortPopover.tsx @@ -146,9 +146,11 @@ const SortOverlay = ({ table, sorts: sortsFromUrl, setParams }: SortOverlayProps ) : (

All columns have been added

)} - +
+ +
) diff --git a/apps/studio/components/interfaces/Home/Connect/Connect.constants.ts b/apps/studio/components/interfaces/Home/Connect/Connect.constants.ts new file mode 100644 index 00000000000..d1d8a399258 --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/Connect.constants.ts @@ -0,0 +1,152 @@ +export type ConnectionType = { + key: string + icon: string + label: string + guideLink?: string + children: ConnectionType[] +} + +export const FRAMEWORKS: ConnectionType[] = [ + { + key: 'nextjs', + label: 'Next.JS', + icon: 'nextjs', + guideLink: 'https://supabase.com/docs/guides/getting-started/quickstarts/nextjs', + children: [ + { + key: 'app', + label: 'App router', + icon: '', + children: [ + { + key: 'supabasejs', + label: 'supabase-js', + icon: 'supabase', + children: [], + }, + ], + }, + { + key: 'pages', + label: 'Pages router', + icon: '', + children: [ + { + key: 'supabasejs', + label: 'Supabase-js', + children: [], + icon: 'supabase', + }, + ], + }, + ], + }, + { + key: 'react', + label: 'React', + icon: 'react', + guideLink: 'https://supabase.com/docs/guides/getting-started/quickstarts/reactjs', + children: [ + { + key: 'supabasejs', + label: 'Supabase-js', + children: [], + icon: 'supabase', + }, + ], + }, + { + key: 'nuxt', + label: 'Nuxt', + icon: 'nuxt', + guideLink: 'https://supabase.com/docs/guides/getting-started/quickstarts/nuxtjs', + children: [ + { + key: 'supabasejs', + label: 'Supabase-js', + children: [], + icon: 'supabase', + }, + ], + }, + { + key: 'vuejs', + label: 'Vue.JS', + icon: 'vuejs', + guideLink: 'https://supabase.com/docs/guides/getting-started/quickstarts/vue', + children: [ + { + key: 'supabasejs', + label: 'Supabase-js', + children: [], + icon: 'supabase', + }, + ], + }, + + { + key: 'sveltekit', + label: 'SvelteKit', + icon: 'sveltekit', + guideLink: 'https://supabase.com/docs/guides/getting-started/quickstarts/sveltekit', + children: [ + { + key: 'supabasejs', + label: 'Supabase-js', + children: [], + icon: 'supabase', + }, + ], + }, + { + key: 'solidjs', + label: 'Solid.js', + icon: 'solidjs', + guideLink: 'https://supabase.com/docs/guides/getting-started/quickstarts/solidjs', + children: [ + { + key: 'supabasejs', + label: 'Supabase-js', + children: [], + icon: 'supabase', + }, + ], + }, + { + key: 'refine', + label: 'refine', + icon: 'refine', + guideLink: 'https://supabase.com/docs/guides/getting-started/quickstarts/refine', + children: [ + { + key: 'supabasejs', + label: 'Supabase-js', + children: [], + icon: 'supabase', + }, + ], + }, +] + +export const ORMS: ConnectionType[] = [ + { + key: 'prisma', + label: 'Prisma', + icon: 'prisma', + guideLink: 'https://supabase.com/partners/integrations/prisma', + children: [], + }, + { + key: 'drizzle', + label: 'Drizzle', + icon: 'drizzle', + guideLink: + 'https://supabase.com/docs/guides/database/connecting-to-postgres#connecting-with-drizzle', + children: [], + }, +] + +export const CONNECTION_TYPES = [ + { key: 'frameworks', label: 'App Frameworks', obj: FRAMEWORKS }, + { key: 'orms', label: 'ORMs', obj: ORMS }, +] diff --git a/apps/studio/components/interfaces/Home/Connect/Connect.tsx b/apps/studio/components/interfaces/Home/Connect/Connect.tsx new file mode 100644 index 00000000000..62739ae5821 --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/Connect.tsx @@ -0,0 +1,273 @@ +import { PermissionAction } from '@supabase/shared-types/out/constants' +import { useParams } from 'common' +import { Plug } from 'lucide-react' +import { useState } from 'react' +import { + Button, + DIALOG_PADDING_X_Shadcn_, + DIALOG_PADDING_Y_Shadcn_, + DialogContent_Shadcn_, + DialogDescription_Shadcn_, + DialogHeader_Shadcn_, + DialogTitle_Shadcn_, + DialogTrigger_Shadcn_, + Dialog_Shadcn_, + IconExternalLink, + TabsContent_Shadcn_, + TabsList_Shadcn_, + TabsTrigger_Shadcn_, + Tabs_Shadcn_, + cn, +} from 'ui' + +import { DatabaseConnectionString } from 'components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString' +import { PoolingModesModal } from 'components/interfaces/Settings/Database/PoolingModesModal' +import { useProjectApiQuery } from 'data/config/project-api-query' +import { useProjectSettingsQuery } from 'data/config/project-settings-query' +import { useCheckPermissions } from 'hooks' +import { DEFAULT_PROJECT_API_SERVICE_ID } from 'lib/constants' +import { CONNECTION_TYPES, ConnectionType, FRAMEWORKS, ORMS } from './Connect.constants' +import { getContentFilePath } from './Connect.utils' +import ConnectDropdown from './ConnectDropdown' +import ConnectTabContent from './ConnectTabContent' + +const Connect = () => { + const { ref: projectRef } = useParams() + + const [connectionObject, setConnectionObject] = useState(FRAMEWORKS) + const [selectedParent, setSelectedParent] = useState(connectionObject[0].key) // aka nextjs + const [selectedChild, setSelectedChild] = useState( + connectionObject.find((item) => item.key === selectedParent)?.children[0]?.key ?? '' + ) + const [selectedGrandchild, setSelectedGrandchild] = useState( + connectionObject + .find((item) => item.key === selectedParent) + ?.children.find((child) => child.key === selectedChild)?.children[0]?.key || '' + ) + + const handleParentChange = (value: string) => { + setSelectedParent(value) + + // check if parent has children + setSelectedChild(connectionObject.find((item) => item.key === value)?.children[0]?.key ?? '') + + // check if child has grandchildren + setSelectedGrandchild( + connectionObject.find((item) => item.key === value)?.children[0]?.children[0]?.key ?? '' + ) + } + + const handleChildChange = (value: string) => { + setSelectedChild(value) + + const parent = connectionObject.find((item) => item.key === selectedParent) + const child = parent?.children.find((child) => child.key === value) + + if (child && child.children.length > 0) { + setSelectedGrandchild(child.children[0].key) + } else { + setSelectedGrandchild('') + } + } + + const handleGrandchildChange = (value: string) => { + setSelectedGrandchild(value) + } + + // reset the parent/child/grandchild when the connection type (tab) changes + function handleConnectionTypeChange(connections: ConnectionType[]) { + setSelectedParent(connections[0].key) + + if (connections[0]?.children.length > 0) { + setSelectedChild(connections[0].children[0].key) + + if (connections[0].children[0]?.children.length > 0) { + setSelectedGrandchild(connections[0].children[0].children[0].key) + } else { + setSelectedGrandchild('') + } + } else { + setSelectedChild('') + setSelectedGrandchild('') + } + } + + function handleConnectionType(type: string) { + if (type === 'frameworks') { + setConnectionObject(FRAMEWORKS) + handleConnectionTypeChange(FRAMEWORKS) + } + + if (type === 'orms') { + setConnectionObject(ORMS) + handleConnectionTypeChange(ORMS) + } + } + + const getChildOptions = () => { + const parent = connectionObject.find((item) => item.key === selectedParent) + if (parent && parent.children.length > 0) { + return parent.children + } + return [] + } + + const getGrandchildrenOptions = () => { + const parent = connectionObject.find((item) => item.key === selectedParent) + const subCategory = parent?.children.find((child) => child.key === selectedChild) + if (subCategory && subCategory.children.length > 0) { + return subCategory.children + } + return [] + } + + const { data: projectSettings } = useProjectSettingsQuery({ projectRef }) + + const { data: apiSettings } = useProjectApiQuery({ + projectRef, + }) + + // Get the API service + const apiService = (projectSettings?.services ?? []).find( + (x: any) => x.app.id == DEFAULT_PROJECT_API_SERVICE_ID + ) + + const canReadAPIKeys = useCheckPermissions(PermissionAction.READ, 'service_api_keys') + + // Get the API service + const apiHost = canReadAPIKeys + ? `${apiSettings?.autoApiService?.protocol ?? 'https'}://${ + apiSettings?.autoApiService?.endpoint ?? '-' + }` + : '' + const apiUrl = canReadAPIKeys ? apiHost : null + + const anonKey = canReadAPIKeys + ? apiService?.service_api_keys.find((key) => key.tags === 'anon')?.api_key ?? null + : null + + const projectKeys = { apiUrl, anonKey } + + const filePath = getContentFilePath({ + connectionObject, + selectedParent, + selectedChild, + selectedGrandchild, + }) + + return ( + <> + + + + + + + Connect to your project + + Get the connection strings and environment variables for your app + + + + handleConnectionType(value)} + > + + + Connection String + + {CONNECTION_TYPES.map((type) => ( + + {type.label} + + ))} + + + {CONNECTION_TYPES.map((type) => { + const hasChildOptions = + (connectionObject.find((parent) => parent.key === selectedParent)?.children + .length || 0) > 0 + const hasGrandChildOptions = + (connectionObject + .find((parent) => parent.key === selectedParent) + ?.children.find((child) => child.key === selectedChild)?.children.length || 0) > 0 + + return ( + +
+
+ + {selectedParent && hasChildOptions && ( + + )} + {selectedChild && hasGrandChildOptions && ( + + )} +
+ {connectionObject.find((item) => item.key === selectedParent)?.guideLink && ( + + )} +
+

+ Add the following files below to your application +

+ +
+ ) + })} + + + +
+
+
+ + + ) +} + +export default Connect diff --git a/apps/studio/components/interfaces/Home/Connect/Connect.types.ts b/apps/studio/components/interfaces/Home/Connect/Connect.types.ts new file mode 100644 index 00000000000..8a5ba229dda --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/Connect.types.ts @@ -0,0 +1,11 @@ +export type projectKeys = { + apiUrl: string | null + anonKey: string | null +} + +export interface ContentFileProps { + projectKeys: { + apiUrl: string + anonKey: string + } +} diff --git a/apps/studio/components/interfaces/Home/Connect/Connect.utils.ts b/apps/studio/components/interfaces/Home/Connect/Connect.utils.ts new file mode 100644 index 00000000000..9c497fb095d --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/Connect.utils.ts @@ -0,0 +1,45 @@ +import { ConnectionType } from './Connect.constants' + +export function getProjectRef(url: string): string | null { + const regex: RegExp = /https:\/\/([^\.]+)\./ + const match: RegExpMatchArray | null = url.match(regex) + + if (match) { + return match[1] + } else { + return null + } +} + +export const getContentFilePath = ({ + connectionObject, + selectedParent, + selectedChild, + selectedGrandchild, +}: { + selectedParent: string + selectedChild: string + selectedGrandchild: string + connectionObject: ConnectionType[] +}) => { + const parent = connectionObject.find((item) => item.key === selectedParent) + + if (parent) { + const child = parent.children.find((child) => child.key === selectedChild) + + // check grandchild first, then child, then parent as the fallback + if (child) { + const grandchild = child.children.find((grandchild) => grandchild.key === selectedGrandchild) + + if (grandchild) { + return `${selectedParent}/${selectedChild}/${selectedGrandchild}` + } else { + return `${selectedParent}/${selectedChild}` + } + } else { + return selectedParent + } + } + + return '' +} diff --git a/apps/studio/components/interfaces/Home/Connect/ConnectDropdown.tsx b/apps/studio/components/interfaces/Home/Connect/ConnectDropdown.tsx new file mode 100644 index 00000000000..93b2e64fcec --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/ConnectDropdown.tsx @@ -0,0 +1,93 @@ +import { Box, Check, ChevronDown } from 'lucide-react' +import { useState } from 'react' +import { + Button, + CommandInput_Shadcn_, + CommandItem_Shadcn_, + CommandList_Shadcn_, + Command_Shadcn_, + PopoverContent_Shadcn_, + PopoverTrigger_Shadcn_, + Popover_Shadcn_, + cn, +} from 'ui' +import ConnectionIcon from './ConnectionIcon' + +interface ConnectDropdownProps { + state: string + updateState: (state: string) => void + label: string + items: any[] +} + +const ConnectDropdown = ({ + state, + updateState, + label, + + items, +}: ConnectDropdownProps) => { + const [open, setOpen] = useState(false) + + function onSelectLib(key: string) { + updateState(key) + setOpen(false) + } + + const selectedItem = items.find((item) => item.key === state) + + return ( + <> + +
+ + {label} + + + + +
+ + + + + {items.map((item) => ( + { + onSelectLib(item.key) + setOpen(false) + }} + > +
+ {item.icon ? : } + + {item.label} + + +
+
+ ))} +
+
+
+
+ + ) +} + +export default ConnectDropdown diff --git a/apps/studio/components/interfaces/Home/Connect/ConnectTabContent.tsx b/apps/studio/components/interfaces/Home/Connect/ConnectTabContent.tsx new file mode 100644 index 00000000000..c4b4c35628f --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/ConnectTabContent.tsx @@ -0,0 +1,29 @@ +import dynamic from 'next/dynamic' +import { projectKeys } from './Connect.types' +import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' + +interface ConnectContentTabProps { + projectKeys: projectKeys + filePath: string +} + +const ConnectTabContentNew = ({ projectKeys, filePath }: ConnectContentTabProps) => { + const ContentFile = dynamic( + () => import(`./content/${filePath}/content`), + { + loading: () => ( +
+ +
+ ), + } + ) + + return ( +
+ +
+ ) +} + +export default ConnectTabContentNew diff --git a/apps/studio/components/interfaces/Home/Connect/ConnectTabs.tsx b/apps/studio/components/interfaces/Home/Connect/ConnectTabs.tsx new file mode 100644 index 00000000000..f5871de3f91 --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/ConnectTabs.tsx @@ -0,0 +1,60 @@ +import { Tabs_Shadcn_ } from 'ui' +import { FileJson2 } from 'lucide-react' +import { TabsList_Shadcn_, TabsTrigger_Shadcn_ } from 'ui' +import { TabsContent_Shadcn_ } from 'ui' +import React, { ReactNode } from 'react' + +interface ConnectTabTriggerProps { + value: string +} +interface ConnectTabTriggersProps { + children: ReactNode[] +} + +interface ConnectFileTabProps { + children: ReactNode[] +} + +interface ConnectTabContentProps { + children: ReactNode + value: string +} +const ConnectTabs = ({ children }: ConnectFileTabProps) => { + const firstChild = children[0] + + const defaultValue = React.isValidElement(firstChild) + ? (firstChild.props as any)?.children[0]?.props?.value || '' + : null + + return {children} +} + +const ConnectTabTrigger = ({ value }: ConnectTabTriggerProps) => { + return ( + + + {value} + + ) +} + +const ConnectTabTriggers = ({ children }: ConnectTabTriggersProps) => { + return ( + + {children} + + ) +} + +export const ConnectTabContent = ({ value, children }: ConnectTabContentProps) => { + return ( + + {children} + + ) +} + +export { ConnectTabTrigger, ConnectTabTriggers, ConnectTabs } diff --git a/apps/studio/components/interfaces/Home/Connect/ConnectionIcon.tsx b/apps/studio/components/interfaces/Home/Connect/ConnectionIcon.tsx new file mode 100644 index 00000000000..de4541decae --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/ConnectionIcon.tsx @@ -0,0 +1,29 @@ +import { BASE_PATH } from 'lib/constants' + +import { useTheme } from 'next-themes' +import Image from 'next/image' +interface ConnectionIconProps { + connection: any +} + +const ConnectionIcon = ({ connection }: ConnectionIconProps) => { + const { resolvedTheme } = useTheme() + + return ( + {`${connection} + ) +} + +export default ConnectionIcon diff --git a/apps/studio/components/interfaces/Home/Connect/content/drizzle/content.tsx b/apps/studio/components/interfaces/Home/Connect/content/drizzle/content.tsx new file mode 100644 index 00000000000..3c4c6b210dd --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/content/drizzle/content.tsx @@ -0,0 +1,63 @@ +import { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types' + +import { + ConnectTabs, + ConnectTabTrigger, + ConnectTabTriggers, + ConnectTabContent, +} from 'components/interfaces/Home/Connect/ConnectTabs' +import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock' + +const ContentFile = ({ projectKeys }: ContentFileProps) => { + return ( + + + + + + + + + + {` +DATABASE_URL='your-database-url' + `} + + + + + + {` +import { pgTable, serial, text, varchar } from "drizzle-orm/pg-core"; + +export const users = pgTable('users', { + id: serial('id').primaryKey(), + fullName: text('full_name'), + phone: varchar('phone', { length: 256 }), +}); + `} + + + + + + {` +import { drizzle } from 'drizzle-orm/postgres-js' +import postgres from 'postgres' +import { users } from './schema' + +const connectionString = process.env.DATABASE_URL + +// Disable prefetch as it is not supported for "Transaction" pool mode +const client = postgres(connectionString, { prepare: false }) +const db = drizzle(client); + +const allUsers = await db.select().from(users); + `} + + + + ) +} + +export default ContentFile diff --git a/apps/studio/components/interfaces/Home/Connect/content/nextjs/app/supabasejs/content.tsx b/apps/studio/components/interfaces/Home/Connect/content/nextjs/app/supabasejs/content.tsx new file mode 100644 index 00000000000..cb9e66fe3c6 --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/content/nextjs/app/supabasejs/content.tsx @@ -0,0 +1,181 @@ +import { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types' + +import { + ConnectTabs, + ConnectTabTrigger, + ConnectTabTriggers, + ConnectTabContent, +} from 'components/interfaces/Home/Connect/ConnectTabs' +import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock' + +const ContentFile = ({ projectKeys }: ContentFileProps) => { + return ( + + + + + + + + + + + + {` +NEXT_PUBLIC_SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'} +NEXT_PUBLIC_SUPABASE_ANON_KEY=${projectKeys.anonKey ?? 'your-anon-key'} + `} + + + + + + {` +import { createClient } from '@/utils/supabase/server' +import { cookies } from 'next/headers' + +export default async function Page() { + const cookieStore = cookies() + const supabase = createClient(cookieStore) + + const { data: todos } = await supabase.from('todos').select() + + return ( +
    + {todos?.map((todo) => ( +
  • {todo}
  • + ))} +
+ ) +} +`} +
+
+ + + + {` +import { createServerClient, type CookieOptions } from "@supabase/ssr"; +import { cookies } from "next/headers"; + +export const createClient = (cookieStore: ReturnType) => { + return createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + get(name: string) { + return cookieStore.get(name)?.value; + }, + set(name: string, value: string, options: CookieOptions) { + try { + cookieStore.set({ name, value, ...options }); + } catch (error) { + // The \`set\` method was called from a Server Component. + // This can be ignored if you have middleware refreshing + // user sessions. + } + }, + remove(name: string, options: CookieOptions) { + try { + cookieStore.set({ name, value: "", ...options }); + } catch (error) { + // The \`delete\` method was called from a Server Component. + // This can be ignored if you have middleware refreshing + // user sessions. + } + }, + }, + }, + ); +}; +`} + + + + + {` +import { createBrowserClient } from "@supabase/ssr"; + +export const createClient = () => + createBrowserClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + ); +`} + + + + + + {` +import { createServerClient, type CookieOptions } from "@supabase/ssr"; +import { type NextRequest, NextResponse } from "next/server"; + +export const createClient = (request: NextRequest) => { + // Create an unmodified response + let response = NextResponse.next({ + request: { + headers: request.headers, + }, + }); + + const supabase = createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL!, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, + { + cookies: { + get(name: string) { + return request.cookies.get(name)?.value; + }, + set(name: string, value: string, options: CookieOptions) { + // If the cookie is updated, update the cookies for the request and response + request.cookies.set({ + name, + value, + ...options, + }); + response = NextResponse.next({ + request: { + headers: request.headers, + }, + }); + response.cookies.set({ + name, + value, + ...options, + }); + }, + remove(name: string, options: CookieOptions) { + // If the cookie is removed, update the cookies for the request and response + request.cookies.set({ + name, + value: "", + ...options, + }); + response = NextResponse.next({ + request: { + headers: request.headers, + }, + }); + response.cookies.set({ + name, + value: "", + ...options, + }); + }, + }, + }, + ); + + return { supabase, response }; +}; + +`} + + +
+ ) +} + +export default ContentFile diff --git a/apps/studio/components/interfaces/Home/Connect/content/nextjs/pages/supabasejs/content.tsx b/apps/studio/components/interfaces/Home/Connect/content/nextjs/pages/supabasejs/content.tsx new file mode 100644 index 00000000000..cf0d13d153a --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/content/nextjs/pages/supabasejs/content.tsx @@ -0,0 +1,80 @@ +import { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types' + +import { + ConnectTabs, + ConnectTabTrigger, + ConnectTabTriggers, + ConnectTabContent, +} from 'components/interfaces/Home/Connect/ConnectTabs' +import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock' + +const ContentFile = ({ projectKeys }: ContentFileProps) => { + return ( + + + + + + + + + + {` +NEXT_PUBLIC_SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'} +NEXT_PUBLIC_SUPABASE_ANON_KEY=${projectKeys.anonKey ?? 'your-anon-key'} + `} + + + + + + {` +import { createClient } from "@supabase/supabase-js"; + +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; +const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; + +export const supabase = createClient(supabaseUrl, supabaseKey); + `} + + + + + + {` +import { useState, useEffect } from 'react' +import { supabase } from '../utils/supabase' + +function Page() { + const [todos, setTodos] = useState([]) + + useEffect(() => { + function getTodos() { + const { data: todos } = await supabase.from('todos').select() + + if (todos.length > 1) { + setTodos(todos) + } + } + + getTodos() + }, []) + + return ( +
+ {todos.map((todo) => ( +
  • {todo}
  • + ))} +
    + ) +} +export default Page + +`} +
    +
    +
    + ) +} + +export default ContentFile diff --git a/apps/studio/components/interfaces/Home/Connect/content/nuxt/supabasejs/content.tsx b/apps/studio/components/interfaces/Home/Connect/content/nuxt/supabasejs/content.tsx new file mode 100644 index 00000000000..c42375e073e --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/content/nuxt/supabasejs/content.tsx @@ -0,0 +1,72 @@ +import { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types' + +import { + ConnectTabs, + ConnectTabTrigger, + ConnectTabTriggers, + ConnectTabContent, +} from 'components/interfaces/Home/Connect/ConnectTabs' +import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock' + +const ContentFile = ({ projectKeys }: ContentFileProps) => { + return ( + + + + + + + + + + {` +SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'} +SUPABASE_KEY=${projectKeys.anonKey ?? 'your-anon-key'} + `} + + + + + + {` +import { createClient } from "@supabase/supabase-js"; + +const supabaseUrl = process.env.SUPABASE_URL; +const supabaseKey = process.env.SUPABASE_KEY; + +export const supabase = createClient(supabaseUrl, supabaseKey); + `} + + + + + + {` + + + +`} + + + + ) +} + +export default ContentFile diff --git a/apps/studio/components/interfaces/Home/Connect/content/prisma/content.tsx b/apps/studio/components/interfaces/Home/Connect/content/prisma/content.tsx new file mode 100644 index 00000000000..17bdbb9d23b --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/content/prisma/content.tsx @@ -0,0 +1,46 @@ +import { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types' + +import { + ConnectTabs, + ConnectTabTrigger, + ConnectTabTriggers, + ConnectTabContent, +} from 'components/interfaces/Home/Connect/ConnectTabs' +import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock' + +const ContentFile = ({ projectKeys }: ContentFileProps) => { + return ( + + + + + + + + + {` +# Connect to Supabase via connection pooling with Supavisor. +DATABASE_URL="postgres://postgres.[your-supabase-project]:[password]@aws-0-eu-central-1.pooler.supabase.com:6543/postgres?pgbouncer=true" + +# Direct connection to the database. Used for migrations. +DIRECT_URL="postgres://postgres:[password]@db.[your-supabase-project].supabase.co:5432/postgres" + `} + + + + + + {` +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") + directUrl = env("DIRECT_URL") +} + `} + + + + ) +} + +export default ContentFile diff --git a/apps/studio/components/interfaces/Home/Connect/content/react/supabasejs/content.tsx b/apps/studio/components/interfaces/Home/Connect/content/react/supabasejs/content.tsx new file mode 100644 index 00000000000..dbd7d2fdae9 --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/content/react/supabasejs/content.tsx @@ -0,0 +1,79 @@ +import { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types' + +import { + ConnectTabs, + ConnectTabTrigger, + ConnectTabTriggers, + ConnectTabContent, +} from 'components/interfaces/Home/Connect/ConnectTabs' +import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock' + +const ContentFile = ({ projectKeys }: ContentFileProps) => { + return ( + + + + + + + + + + {` +SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'} +SUPABASE_KEY=${projectKeys.anonKey ?? 'your-anon-key'} + `} + + + + + + {` +import { createClient } from "@supabase/supabase-js"; + +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; +const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; + +export const supabase = createClient(supabaseUrl, supabaseKey); + `} + + + + + + {` +import { useState, useEffect } from 'react' +import { supabase } from '../utils/supabase' + +function Page() { + const [todos, setTodos] = useState([]) + + useEffect(() => { + function getTodos() { + const { data: todos } = await supabase.from('todos').select() + + if (todos.length > 1) { + setTodos(todos) + } + } + + getTodos() + }, []) + + return ( +
    + {todos.map((todo) => ( +
  • {todo}
  • + ))} +
    + ) +} +export default Page +`} +
    +
    +
    + ) +} + +export default ContentFile diff --git a/apps/studio/components/interfaces/Home/Connect/content/refine/supabasejs/content.tsx b/apps/studio/components/interfaces/Home/Connect/content/refine/supabasejs/content.tsx new file mode 100644 index 00000000000..1454a0c4692 --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/content/refine/supabasejs/content.tsx @@ -0,0 +1,115 @@ +import { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types' + +import { + ConnectTabs, + ConnectTabTrigger, + ConnectTabTriggers, + ConnectTabContent, +} from 'components/interfaces/Home/Connect/ConnectTabs' +import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock' + +const ContentFile = ({ projectKeys }: ContentFileProps) => { + return ( + + + + + + + + + + {` +SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'} +SUPABASE_ANON_KEY=${projectKeys.anonKey ?? 'your-anon-key'} + `} + + + + + + {` +import { createClient } from "@refinedev/supabase"; + +const SUPABASE_URL = process.env.SUPABASE_URL; +const SUPABASE_KEY = process.env.SUPABASE_KEY + +export const supabaseClient = createClient(SUPABASE_URL, SUPABASE_KEY, { + db: { + schema: "public", + }, + auth: { + persistSession: true, + }, +}); + `} + + + + + + {` +import { Refine, WelcomePage } from "@refinedev/core"; +import { RefineKbar, RefineKbarProvider } from "@refinedev/kbar"; +import routerBindings, { + DocumentTitleHandler, + NavigateToResource, + UnsavedChangesNotifier, +} from "@refinedev/react-router-v6"; +import { dataProvider, liveProvider } from "@refinedev/supabase"; +import { BrowserRouter, Route, Routes } from "react-router-dom"; + +import "./App.css"; +import authProvider from "./authProvider"; +import { supabaseClient } from "./utility"; +import { CountriesCreate, CountriesEdit, CountriesList, CountriesShow } from "./pages/countries"; + +function App() { + return ( + + + + + } + /> + + } /> + } /> + } /> + } /> + + + + + + + + + ); +} + +export default App; +`} + + + + ) +} + +export default ContentFile diff --git a/apps/studio/components/interfaces/Home/Connect/content/solidjs/supabasejs/content.tsx b/apps/studio/components/interfaces/Home/Connect/content/solidjs/supabasejs/content.tsx new file mode 100644 index 00000000000..a0e2c647066 --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/content/solidjs/supabasejs/content.tsx @@ -0,0 +1,71 @@ +import { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types' + +import { + ConnectTabs, + ConnectTabTrigger, + ConnectTabTriggers, + ConnectTabContent, +} from 'components/interfaces/Home/Connect/ConnectTabs' +import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock' + +const ContentFile = ({ projectKeys }: ContentFileProps) => { + return ( + + + + + + + + + + {` +SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'} +SUPABASE_ANON_KEY=${projectKeys.anonKey ?? 'your-anon-key'} + `} + + + + + + {` +import { createClient } from "@supabase/supabase-js"; + +const supabaseUrl = process.env.SUPABASE_URL; +const supabaseKey = process.env.SUPABASE_ANON_KEY; + +export const supabase = createClient(supabaseUrl, supabaseKey); + `} + + + + + + {` +import { supabase } from '../utils/supabase' +import { createResource, For } from "solid-js"; + +async function getTodos() { + const { data: todos } = await supabase.from("todos").select(); + return data; +} + +function App() { + const [todos] = createResource(getTodos); + + return ( +
      + {(country) =>
    • {todo.name}
    • }
      +
    + ); +} + +export default App; +`} +
    +
    +
    + ) +} + +export default ContentFile diff --git a/apps/studio/components/interfaces/Home/Connect/content/sveltekit/supabasejs/content.tsx b/apps/studio/components/interfaces/Home/Connect/content/sveltekit/supabasejs/content.tsx new file mode 100644 index 00000000000..6307039c60e --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/content/sveltekit/supabasejs/content.tsx @@ -0,0 +1,77 @@ +import { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types' + +import { + ConnectTabs, + ConnectTabTriggers, + ConnectTabTrigger, + ConnectTabContent, +} from 'components/interfaces/Home/Connect/ConnectTabs' +import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock' + +const ContentFile = ({ projectKeys }: ContentFileProps) => { + return ( + + + + + + + + + + + {` +SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'} +SUPABASE_ANON_KEY=${projectKeys.anonKey ?? 'your-anon-key'} + `} + + + + + + {` +import { createClient } from "@supabase/supabase-js"; + +const supabaseUrl = process.env.SUPABASE_URL; +const supabaseKey = process.env.SUPABASE_KEY; + +export const supabase = createClient(supabaseUrl, supabaseKey); + `} + + + + + + {` +import { supabase } from "$lib/supabaseClient"; + +export async function load() { + const { data } = await supabase.from("countries").select(); + return { + countries: data ?? [], + }; +} +`} + + + + + + {` + + +
      + {#each data.countries as country} +
    • {country.name}
    • + {/each} +
    +`} +
    +
    +
    + ) +} + +export default ContentFile diff --git a/apps/studio/components/interfaces/Home/Connect/content/vuejs/supabasejs/content.tsx b/apps/studio/components/interfaces/Home/Connect/content/vuejs/supabasejs/content.tsx new file mode 100644 index 00000000000..0bf5c3b5f4b --- /dev/null +++ b/apps/studio/components/interfaces/Home/Connect/content/vuejs/supabasejs/content.tsx @@ -0,0 +1,72 @@ +import { ContentFileProps } from 'components/interfaces/Home/Connect/Connect.types' + +import { + ConnectTabs, + ConnectTabTrigger, + ConnectTabTriggers, + ConnectTabContent, +} from 'components/interfaces/Home/Connect/ConnectTabs' +import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock' + +const ContentFile = ({ projectKeys }: ContentFileProps) => { + return ( + + + + + + + + + + {` +SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'} +SUPABASE_KEY=${projectKeys.anonKey ?? 'your-anon-key'} + `} + + + + + + {` +import { createClient } from "@supabase/supabase-js"; + +const supabaseUrl = process.env.SUPABASE_URL; +const supabaseKey = process.env.SUPABASE_KEY; + +export const supabase = createClient(supabaseUrl, supabaseKey); + `} + + + + + + {` + + + +`} + + + + ) +} + +export default ContentFile diff --git a/apps/studio/components/interfaces/Home/ServiceStatus.tsx b/apps/studio/components/interfaces/Home/ServiceStatus.tsx index 4673737ced8..5dfa42c51bc 100644 --- a/apps/studio/components/interfaces/Home/ServiceStatus.tsx +++ b/apps/studio/components/interfaces/Home/ServiceStatus.tsx @@ -119,54 +119,52 @@ const ServiceStatus = () => { const allServicesOperational = services.every((service) => service.isSuccess) return ( -
    - - - + + + {services.map((service) => ( +
    - {isBranch ? 'Preview Branch' : 'Project'} Status - - - - {services.map((service) => ( -
    -
    -

    {service.name}

    -

    - {service.isLoading - ? 'Checking status' - : service.isSuccess - ? 'No issues' - : 'Unable to connect'} -

    -
    - {service.isLoading ? ( - - ) : service.isSuccess ? ( - - ) : ( - - )} +
    +

    {service.name}

    +

    + {service.isLoading + ? 'Checking status' + : service.isSuccess + ? 'No issues' + : 'Unable to connect'} +

    - ))} - - -
    + {service.isLoading ? ( + + ) : service.isSuccess ? ( + + ) : ( + + )} +
    + ))} +
    +
    ) } diff --git a/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx b/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx index a12e52edc86..4da00202e86 100644 --- a/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx +++ b/apps/studio/components/interfaces/Settings/Database/DatabaseSettings/DatabaseConnectionString.tsx @@ -12,6 +12,7 @@ import { Input, Separator, Tabs, + cn, } from 'ui' import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext' @@ -45,7 +46,11 @@ const CONNECTION_TYPES = [ { id: 'python', label: 'Python' }, ] -export const DatabaseConnectionString = () => { +interface DatabaseConnectionStringProps { + appearance: 'default' | 'minimal' +} + +export const DatabaseConnectionString = ({ appearance }: DatabaseConnectionStringProps) => { const router = useRouter() const { project: projectDetails, isLoading: isProjectLoading } = useProjectContext() const { ref: projectRef, connectionString } = useParams() @@ -155,16 +160,36 @@ export const DatabaseConnectionString = () => { }, [poolingInfo?.pool_mode]) return ( -
    +
    div:nth-child(1)]:!border-0 [&>div:nth-child(1)>div]:!p-0', + appearance === 'minimal' && 'border-0 shadow-none' + )} + bodyClassName={cn(appearance === 'minimal' && 'bg-transparent')} + headerClasses={cn(appearance === 'minimal' && 'bg-transparent')} title={ -
    -
    -
    - Connection string -
    -
    +
    +
    + {appearance === 'default' && ( +
    + Connection string +
    + )} + } > - + {isLoading && } {isError && } {isSuccess && ( @@ -219,7 +247,7 @@ export const DatabaseConnectionString = () => { {poolerConnStringSyntax.length > 0 && ( <> - +
    diff --git a/apps/studio/components/interfaces/Settings/Database/PoolingModesModal.tsx b/apps/studio/components/interfaces/Settings/Database/PoolingModesModal.tsx index 1d0485dcfa6..c234c737219 100644 --- a/apps/studio/components/interfaces/Settings/Database/PoolingModesModal.tsx +++ b/apps/studio/components/interfaces/Settings/Database/PoolingModesModal.tsx @@ -8,8 +8,14 @@ import { AlertTitle_Shadcn_, Alert_Shadcn_, Button, + DialogClose_Shadcn_, + DialogContent_Shadcn_, + DialogDescription_Shadcn_, + DialogFooter_Shadcn_, + DialogHeader_Shadcn_, + DialogTitle_Shadcn_, + Dialog_Shadcn_, IconExternalLink, - Modal, } from 'ui' export const PoolingModesModal = () => { @@ -24,33 +30,34 @@ export const PoolingModesModal = () => { } return ( - -

    Which pooling mode should I use?

    -
    -
    - } - onCancel={() => snap.setShowPoolingModeHelper(false)} - > - + + + + +
    +

    Which pooling mode should I use?

    + +
    +
    + +

    + A connection pooler is a system (external to Postgres) which manages Postgres + connections by allocating connections whenever clients make requests. +

    +
    +
    )} -
    - - - - - + + snap.setShowPoolingModeHelper(false)}> + + + + + ) } diff --git a/apps/studio/components/layouts/ProjectLayout/BuildingState.tsx b/apps/studio/components/layouts/ProjectLayout/BuildingState.tsx index 22f7d1d3a79..b139e9a5d34 100644 --- a/apps/studio/components/layouts/ProjectLayout/BuildingState.tsx +++ b/apps/studio/components/layouts/ProjectLayout/BuildingState.tsx @@ -13,6 +13,7 @@ import { invalidateProjectsQuery } from 'data/projects/projects-query' import { useSelectedProject } from 'hooks' import { getWithTimeout } from 'lib/common/fetch' import { API_URL, PROJECT_STATUS } from 'lib/constants' +import Connect from 'components/interfaces/Home/Connect/Connect' const BuildingState = () => { const { ref } = useParams() diff --git a/apps/studio/components/to-be-cleaned/SimpleCodeBlock.tsx b/apps/studio/components/to-be-cleaned/SimpleCodeBlock.tsx index e1f0a6589d2..31750374f04 100644 --- a/apps/studio/components/to-be-cleaned/SimpleCodeBlock.tsx +++ b/apps/studio/components/to-be-cleaned/SimpleCodeBlock.tsx @@ -7,10 +7,11 @@ import rangeParser from 'parse-numeric-range' import Highlight, { Language, defaultProps } from 'prism-react-renderer' -import defaultTheme from 'prism-react-renderer/themes/palenight' +import darkTheme from 'prism-react-renderer/themes/vsDark' +import lightTheme from 'prism-react-renderer/themes/nightOwlLight' import { PropsWithChildren, useEffect, useRef, useState } from 'react' import { Button } from 'ui' - +import { useTheme } from 'next-themes' import { copyToClipboard } from 'lib/helpers' const highlightLinesRangeRegex = /{([\d,-]+)}/ @@ -33,6 +34,7 @@ const SimpleCodeBlock = ({ metastring, showCopy = true, }: PropsWithChildren) => { + const { resolvedTheme } = useTheme() const [showCopied, setShowCopied] = useState(false) const target = useRef(null) const button = useRef(null) @@ -62,7 +64,7 @@ const SimpleCodeBlock = ({ return ( diff --git a/apps/studio/components/ui/Panel.tsx b/apps/studio/components/ui/Panel.tsx index 2498f7b4dda..2352ebd31a0 100644 --- a/apps/studio/components/ui/Panel.tsx +++ b/apps/studio/components/ui/Panel.tsx @@ -1,5 +1,5 @@ import { PropsWithChildren, ReactNode } from 'react' -import { Loading } from 'ui' +import { Loading, cn } from 'ui' interface PanelProps { bodyClassName?: string @@ -11,28 +11,30 @@ interface PanelProps { title?: ReactNode | false wrapWithLoading?: boolean noHideOverflow?: boolean + headerClasses?: string } + function Panel(props: PropsWithChildren) { - let headerClasses: string[] = [] - - if (!props.hideHeaderStyling) { - headerClasses = [`bg-surface-100 border-b border-overlay`] - } else { - headerClasses = [`bg-surface-100`] - } - const content = (
    {props.title && ( -
    +
    {props.title}
    )} -
    {props.children}
    +
    {props.children}
    {props.footer && (
    @@ -50,9 +52,7 @@ function Panel(props: PropsWithChildren) { } function Content({ children, className }: { children: ReactNode; className?: string | false }) { - let classes = ['px-6 py-4'] - if (className) classes.push(className) - return
    {children}
    + return
    {children}
    } Panel.Content = Content diff --git a/apps/studio/pages/api/connect/index.ts b/apps/studio/pages/api/connect/index.ts new file mode 100644 index 00000000000..f20b785bcdf --- /dev/null +++ b/apps/studio/pages/api/connect/index.ts @@ -0,0 +1,40 @@ +import fs from 'fs' +import path from 'path' +import { NextApiRequest, NextApiResponse } from 'next' + +function getFilePaths(folderPath: string, baseFolder = ''): string[] { + const filepaths: string[] = [] + + const files = fs.readdirSync(folderPath) + for (const file of files) { + if (file === '.DS_Store') { + continue + } + + const filePath = path.join(folderPath, file) + const stats = fs.statSync(filePath) + + if (stats.isDirectory()) { + const subFolder = path.join(baseFolder, file) + const subFolderPaths = getFilePaths(filePath, subFolder) + filepaths.push(...subFolderPaths) + } else { + const filePathRelativeToBase = path.join(baseFolder, file) + if (file !== '.DS_Store') { + filepaths.push(filePathRelativeToBase) + } + filepaths.push(filePathRelativeToBase) + } + } + + return filepaths +} + +export default async function getFileNames(req: NextApiRequest, res: NextApiResponse) { + const folderPath = path.join(process.cwd(), 'components/interfaces/Home/Connect/content') + + const filepaths = getFilePaths(folderPath) + console.log({ filepaths }) + res.statusCode = 200 + res.json(filepaths) +} diff --git a/apps/studio/pages/project/[ref]/index.tsx b/apps/studio/pages/project/[ref]/index.tsx index b0ae789bcc6..0fd15adb53b 100644 --- a/apps/studio/pages/project/[ref]/index.tsx +++ b/apps/studio/pages/project/[ref]/index.tsx @@ -2,7 +2,9 @@ import { useEffect, useRef } from 'react' import { useParams } from 'common' import { ClientLibrary, ExampleProject } from 'components/interfaces/Home' +import Connect from 'components/interfaces/Home/Connect/Connect' import { CLIENT_LIBRARIES, EXAMPLE_PROJECTS } from 'components/interfaces/Home/Home.constants' +import ProjectUsageSection from 'components/interfaces/Home/ProjectUsageSection' import ServiceStatus from 'components/interfaces/Home/ServiceStatus' import { ProjectLayoutWithAuth } from 'components/layouts' import ProjectPausedState from 'components/layouts/ProjectLayout/ProjectPausedState' @@ -11,7 +13,6 @@ import { useSelectedProject } from 'hooks' import { IS_PLATFORM, PROJECT_STATUS } from 'lib/constants' import { useAppStateSnapshot } from 'state/app-state' import { NextPageWithLayout } from 'types' -import ProjectUsageSection from 'components/interfaces/Home/ProjectUsageSection' const Home: NextPageWithLayout = () => { const project = useSelectedProject() @@ -37,7 +38,10 @@ const Home: NextPageWithLayout = () => {

    {projectName}

    - {IS_PLATFORM && project?.status === PROJECT_STATUS.ACTIVE_HEALTHY && } +
    + {IS_PLATFORM && project?.status === PROJECT_STATUS.ACTIVE_HEALTHY && } + {IS_PLATFORM && project?.status === PROJECT_STATUS.ACTIVE_HEALTHY && } +
    diff --git a/apps/studio/pages/project/[ref]/settings/database.tsx b/apps/studio/pages/project/[ref]/settings/database.tsx index f52bcd36543..86395618170 100644 --- a/apps/studio/pages/project/[ref]/settings/database.tsx +++ b/apps/studio/pages/project/[ref]/settings/database.tsx @@ -22,7 +22,7 @@ const ProjectSettings: NextPageWithLayout = () => {
    - +
    diff --git a/apps/studio/pages/projects.tsx b/apps/studio/pages/projects.tsx index 4db32bd542e..08b3a08ead2 100644 --- a/apps/studio/pages/projects.tsx +++ b/apps/studio/pages/projects.tsx @@ -50,7 +50,6 @@ const ProjectsPage: NextPageWithLayout = () => {
    )} - {!navLayoutV2 && (
    {IS_PLATFORM && projectCreationEnabled && isSuccess && organizations.length !== 0 && ( diff --git a/apps/studio/public/img/libraries/drizzle-dark-icon.svg b/apps/studio/public/img/libraries/drizzle-dark-icon.svg new file mode 100644 index 00000000000..4976aafadf0 --- /dev/null +++ b/apps/studio/public/img/libraries/drizzle-dark-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/studio/public/img/libraries/drizzle-icon.svg b/apps/studio/public/img/libraries/drizzle-icon.svg new file mode 100644 index 00000000000..05d25f1cdbb --- /dev/null +++ b/apps/studio/public/img/libraries/drizzle-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/studio/public/img/libraries/graphql-icon.svg b/apps/studio/public/img/libraries/graphql-icon.svg new file mode 100644 index 00000000000..f57033c1858 --- /dev/null +++ b/apps/studio/public/img/libraries/graphql-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/studio/public/img/libraries/nuxt-icon.svg b/apps/studio/public/img/libraries/nuxt-icon.svg new file mode 100644 index 00000000000..22096977c69 --- /dev/null +++ b/apps/studio/public/img/libraries/nuxt-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/studio/public/img/libraries/postgres-icon.svg b/apps/studio/public/img/libraries/postgres-icon.svg new file mode 100644 index 00000000000..4371d2d1a90 --- /dev/null +++ b/apps/studio/public/img/libraries/postgres-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/studio/public/img/libraries/prisma-dark-icon.svg b/apps/studio/public/img/libraries/prisma-dark-icon.svg new file mode 100644 index 00000000000..1f32018b675 --- /dev/null +++ b/apps/studio/public/img/libraries/prisma-dark-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/studio/public/img/libraries/prisma-icon.svg b/apps/studio/public/img/libraries/prisma-icon.svg new file mode 100644 index 00000000000..ac2530cdf9f --- /dev/null +++ b/apps/studio/public/img/libraries/prisma-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/studio/public/img/libraries/redwoodjs-icon.svg b/apps/studio/public/img/libraries/redwoodjs-icon.svg new file mode 100644 index 00000000000..1e18e419cdd --- /dev/null +++ b/apps/studio/public/img/libraries/redwoodjs-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/studio/public/img/libraries/refine-icon.svg b/apps/studio/public/img/libraries/refine-icon.svg new file mode 100644 index 00000000000..81ceebe54a4 --- /dev/null +++ b/apps/studio/public/img/libraries/refine-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/studio/public/img/libraries/solidjs-icon.svg b/apps/studio/public/img/libraries/solidjs-icon.svg new file mode 100644 index 00000000000..ef329ccd3d9 --- /dev/null +++ b/apps/studio/public/img/libraries/solidjs-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/studio/public/img/libraries/supabase-icon.svg b/apps/studio/public/img/libraries/supabase-icon.svg new file mode 100644 index 00000000000..5eddab72892 --- /dev/null +++ b/apps/studio/public/img/libraries/supabase-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/studio/public/img/libraries/svelte-icon.svg b/apps/studio/public/img/libraries/sveltekit-icon.svg similarity index 100% rename from apps/studio/public/img/libraries/svelte-icon.svg rename to apps/studio/public/img/libraries/sveltekit-icon.svg diff --git a/package-lock.json b/package-lock.json index 90b3b9c612a..bbb88e2c2d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11998,8 +11998,9 @@ } }, "node_modules/@types/mdx": { - "version": "2.0.8", - "license": "MIT" + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.10.tgz", + "integrity": "sha512-Rllzc5KHk0Al5/WANwgSPl1/CwjqCy+AZrGd78zuK+jO9aDM6ffblZ+zIjgPNAaEBmlO0RYDvLNh7wD0zKVgEg==" }, "node_modules/@types/mime": { "version": "1.3.3", diff --git a/packages/ui/index.tsx b/packages/ui/index.tsx index 5e59e1a5a0f..99ca84fd22a 100644 --- a/packages/ui/index.tsx +++ b/packages/ui/index.tsx @@ -96,8 +96,12 @@ export { DialogContent as DialogContent_Shadcn_, DialogHeader as DialogHeader_Shadcn_, DialogFooter as DialogFooter_Shadcn_, + DialogClose as DialogClose_Shadcn_, DialogTitle as DialogTitle_Shadcn_, DialogDescription as DialogDescription_Shadcn_, + DialogSection as DialogSection_Shadcn_, + DIALOG_PADDING_X as DIALOG_PADDING_X_Shadcn_, + DIALOG_PADDING_Y as DIALOG_PADDING_Y_Shadcn_, } from './src/components/shadcn/ui/dialog' export { diff --git a/packages/ui/src/components/shadcn/ui/dialog.tsx b/packages/ui/src/components/shadcn/ui/dialog.tsx index 2be6bc965b2..f20af5f8a30 100644 --- a/packages/ui/src/components/shadcn/ui/dialog.tsx +++ b/packages/ui/src/components/shadcn/ui/dialog.tsx @@ -6,6 +6,9 @@ import * as React from 'react' import { cn } from '../../../lib/utils/cn' +export const DIALOG_PADDING_Y = 'py-6' +export const DIALOG_PADDING_X = 'px-7' + const Dialog = DialogPrimitive.Root const DialogTrigger = DialogPrimitive.Trigger @@ -22,7 +25,7 @@ const DialogOverlay = React.forwardRef< ) => ( -
    +
    ) DialogHeader.displayName = 'DialogHeader' -const DialogFooter = ({ className, ...props }: React.HTMLAttributes) => ( +const DialogFooter = ({ className, children, ...props }: React.HTMLAttributes) => (
    + > + {children} +
    ) DialogFooter.displayName = 'DialogFooter' @@ -72,11 +85,7 @@ const DialogTitle = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => ( - + )) DialogTitle.displayName = DialogPrimitive.Title.displayName @@ -92,12 +101,40 @@ const DialogDescription = React.forwardRef< )) DialogDescription.displayName = DialogPrimitive.Description.displayName +const DialogClose = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + {children} + +)) +DialogClose.displayName = DialogPrimitive.Close.displayName + +const DialogSection = React.forwardRef>( + ({ className, children, ...props }, ref) => ( +
    + {children} +
    + ) +) +DialogSection.displayName = 'DialogSection' + export { Dialog, DialogContent, DialogDescription, DialogFooter, + DialogClose, DialogHeader, DialogTitle, DialogTrigger, + DialogSection, }