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 <saltcod@gmail.com> * 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 <jordi.err@gmail.com> Co-authored-by: Jonathan Summers-Muir <MildTomato@users.noreply.github.com> Co-authored-by: Joshen Lim <joshenlimek@gmail.com>
No files matched your search
@@ -43,7 +43,7 @@ const FilterRow = ({ table, filter, filterIdx, onChange, onDelete, onKeyDown }:
|
||||
<IconChevronDown strokeWidth={1.5} size={14} />
|
||||
</div>
|
||||
}
|
||||
className="w-32"
|
||||
className="w-32 justify-start"
|
||||
>
|
||||
<span>{column?.name ?? ''}</span>
|
||||
</Button>
|
||||
|
||||
@@ -146,9 +146,11 @@ const SortOverlay = ({ table, sorts: sortsFromUrl, setParams }: SortOverlayProps
|
||||
) : (
|
||||
<p className="text-sm text-foreground-light">All columns have been added</p>
|
||||
)}
|
||||
<Button disabled={isEqual(sorts, initialSorts)} type="default" onClick={onApplySort}>
|
||||
Apply sorting
|
||||
</Button>
|
||||
<div className="flex items-center">
|
||||
<Button disabled={isEqual(sorts, initialSorts)} type="default" onClick={onApplySort}>
|
||||
Apply sorting
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -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 },
|
||||
]
|
||||
@@ -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<ConnectionType[]>(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 (
|
||||
<>
|
||||
<Dialog_Shadcn_>
|
||||
<DialogTrigger_Shadcn_ asChild>
|
||||
<Button type="primary">
|
||||
<span className="flex items-center gap-2 px-3">
|
||||
<Plug size={14} className="rotate-90" /> <span>Connect</span>
|
||||
</span>
|
||||
</Button>
|
||||
</DialogTrigger_Shadcn_>
|
||||
<DialogContent_Shadcn_ className={cn('sm:max-w-5xl p-0')}>
|
||||
<DialogHeader_Shadcn_ className="pb-0">
|
||||
<DialogTitle_Shadcn_>Connect to your project</DialogTitle_Shadcn_>
|
||||
<DialogDescription_Shadcn_>
|
||||
Get the connection strings and environment variables for your app
|
||||
</DialogDescription_Shadcn_>
|
||||
</DialogHeader_Shadcn_>
|
||||
|
||||
<Tabs_Shadcn_
|
||||
defaultValue="direct"
|
||||
onValueChange={(value) => handleConnectionType(value)}
|
||||
>
|
||||
<TabsList_Shadcn_ className={cn('flex gap-4', DIALOG_PADDING_X_Shadcn_)}>
|
||||
<TabsTrigger_Shadcn_ key="direct" value="direct" className="px-0">
|
||||
Connection String
|
||||
</TabsTrigger_Shadcn_>
|
||||
{CONNECTION_TYPES.map((type) => (
|
||||
<TabsTrigger_Shadcn_ key={type.key} value={type.key} className="px-0">
|
||||
{type.label}
|
||||
</TabsTrigger_Shadcn_>
|
||||
))}
|
||||
</TabsList_Shadcn_>
|
||||
|
||||
{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 (
|
||||
<TabsContent_Shadcn_
|
||||
key={`content-${type.key}`}
|
||||
value={type.key}
|
||||
className={cn(DIALOG_PADDING_X_Shadcn_, DIALOG_PADDING_Y_Shadcn_, '!mt-0')}
|
||||
>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex items-center gap-5">
|
||||
<ConnectDropdown
|
||||
state={selectedParent}
|
||||
updateState={handleParentChange}
|
||||
label={connectionObject === FRAMEWORKS ? 'Framework' : 'Tool'}
|
||||
items={connectionObject}
|
||||
/>
|
||||
{selectedParent && hasChildOptions && (
|
||||
<ConnectDropdown
|
||||
state={selectedChild}
|
||||
updateState={handleChildChange}
|
||||
label="Using"
|
||||
items={getChildOptions()}
|
||||
/>
|
||||
)}
|
||||
{selectedChild && hasGrandChildOptions && (
|
||||
<ConnectDropdown
|
||||
state={selectedGrandchild}
|
||||
updateState={handleGrandchildChange}
|
||||
label="With"
|
||||
items={getGrandchildrenOptions()}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{connectionObject.find((item) => item.key === selectedParent)?.guideLink && (
|
||||
<Button
|
||||
asChild
|
||||
type="default"
|
||||
icon={<IconExternalLink size={14} strokeWidth={1.5} />}
|
||||
>
|
||||
<a
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
href={
|
||||
connectionObject.find((item) => item.key === selectedParent)
|
||||
?.guideLink || ''
|
||||
}
|
||||
>
|
||||
{connectionObject.find((item) => item.key === selectedParent)?.label}{' '}
|
||||
guide
|
||||
</a>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-foreground-lighter my-3">
|
||||
Add the following files below to your application
|
||||
</p>
|
||||
<ConnectTabContent projectKeys={projectKeys} filePath={filePath} />
|
||||
</TabsContent_Shadcn_>
|
||||
)
|
||||
})}
|
||||
<TabsContent_Shadcn_
|
||||
key="direct"
|
||||
value="direct"
|
||||
className={cn(DIALOG_PADDING_X_Shadcn_, DIALOG_PADDING_Y_Shadcn_, '!mt-0')}
|
||||
>
|
||||
<DatabaseConnectionString appearance="minimal" />
|
||||
</TabsContent_Shadcn_>
|
||||
</Tabs_Shadcn_>
|
||||
</DialogContent_Shadcn_>
|
||||
</Dialog_Shadcn_>
|
||||
<PoolingModesModal />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default Connect
|
||||
@@ -0,0 +1,11 @@
|
||||
export type projectKeys = {
|
||||
apiUrl: string | null
|
||||
anonKey: string | null
|
||||
}
|
||||
|
||||
export interface ContentFileProps {
|
||||
projectKeys: {
|
||||
apiUrl: string
|
||||
anonKey: string
|
||||
}
|
||||
}
|
||||
@@ -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 ''
|
||||
}
|
||||
@@ -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 (
|
||||
<>
|
||||
<Popover_Shadcn_ open={open} onOpenChange={setOpen} modal={false}>
|
||||
<div className="flex ">
|
||||
<span className="flex items-center text-foreground-muted px-3 rounded-lg rounded-r-none text-xs border border-button border-r-0">
|
||||
{label}
|
||||
</span>
|
||||
<PopoverTrigger_Shadcn_ asChild>
|
||||
<Button size="small" type="default" className="gap-0 rounded-l-none">
|
||||
<div className="flex items-center gap-2">
|
||||
{selectedItem?.icon ? (
|
||||
<ConnectionIcon connection={selectedItem.icon} />
|
||||
) : (
|
||||
<Box size={12} />
|
||||
)}
|
||||
{selectedItem?.label}
|
||||
<ChevronDown className="text-muted" strokeWidth={1} size={12} />
|
||||
</div>
|
||||
</Button>
|
||||
</PopoverTrigger_Shadcn_>
|
||||
</div>
|
||||
<PopoverContent_Shadcn_ className="p-0 max-w-48" side="bottom" align="start">
|
||||
<Command_Shadcn_>
|
||||
<CommandInput_Shadcn_ placeholder="Search..." className="h-9" />
|
||||
<CommandList_Shadcn_>
|
||||
{items.map((item) => (
|
||||
<CommandItem_Shadcn_
|
||||
key={item.key}
|
||||
value={item.key}
|
||||
onSelect={() => {
|
||||
onSelectLib(item.key)
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2 py-0.5 w-full">
|
||||
{item.icon ? <ConnectionIcon connection={item.icon} /> : <Box size={12} />}
|
||||
|
||||
{item.label}
|
||||
|
||||
<Check
|
||||
size={15}
|
||||
className={cn('ml-auto ', item.key === state ? 'opacity-100' : 'opacity-0')}
|
||||
/>
|
||||
</div>
|
||||
</CommandItem_Shadcn_>
|
||||
))}
|
||||
</CommandList_Shadcn_>
|
||||
</Command_Shadcn_>
|
||||
</PopoverContent_Shadcn_>
|
||||
</Popover_Shadcn_>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConnectDropdown
|
||||
@@ -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<ConnectContentTabProps>(
|
||||
() => import(`./content/${filePath}/content`),
|
||||
{
|
||||
loading: () => (
|
||||
<div className="p-4 min-h-[331px]">
|
||||
<GenericSkeletonLoader />
|
||||
</div>
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
return (
|
||||
<div className="border rounded-lg">
|
||||
<ContentFile projectKeys={projectKeys} filePath={filePath} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConnectTabContentNew
|
||||
@@ -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 <Tabs_Shadcn_ defaultValue={defaultValue}>{children}</Tabs_Shadcn_>
|
||||
}
|
||||
|
||||
const ConnectTabTrigger = ({ value }: ConnectTabTriggerProps) => {
|
||||
return (
|
||||
<TabsTrigger_Shadcn_
|
||||
value={value}
|
||||
className="flex items-center gap-1 text-xs px-0 data-[state=active]:bg-transparent py-2.5"
|
||||
>
|
||||
<FileJson2 size={15} className="text-foreground-muted" />
|
||||
{value}
|
||||
</TabsTrigger_Shadcn_>
|
||||
)
|
||||
}
|
||||
|
||||
const ConnectTabTriggers = ({ children }: ConnectTabTriggersProps) => {
|
||||
return (
|
||||
<TabsList_Shadcn_ className="bg-surface-100 px-5 rounded-lg rounded-b-none gap-5">
|
||||
{children}
|
||||
</TabsList_Shadcn_>
|
||||
)
|
||||
}
|
||||
|
||||
export const ConnectTabContent = ({ value, children }: ConnectTabContentProps) => {
|
||||
return (
|
||||
<TabsContent_Shadcn_ value={value} className="p-3 mt-1 max-h-72 overflow-scroll">
|
||||
{children}
|
||||
</TabsContent_Shadcn_>
|
||||
)
|
||||
}
|
||||
|
||||
export { ConnectTabTrigger, ConnectTabTriggers, ConnectTabs }
|
||||
@@ -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 (
|
||||
<Image
|
||||
className="transition-all group-hover:scale-110"
|
||||
src={`${BASE_PATH}/img/libraries/${connection.toLowerCase()}${
|
||||
['expo', 'nextjs', 'prisma', 'drizzle'].includes(connection.toLowerCase())
|
||||
? resolvedTheme?.includes('dark')
|
||||
? '-dark'
|
||||
: ''
|
||||
: ''
|
||||
}-icon.svg`}
|
||||
alt={`${connection} logo`}
|
||||
width={14}
|
||||
height={14}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default ConnectionIcon
|
||||
@@ -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 (
|
||||
<ConnectTabs>
|
||||
<ConnectTabTriggers>
|
||||
<ConnectTabTrigger value=".env" />
|
||||
<ConnectTabTrigger value="drizzle/schema.tsx" />
|
||||
<ConnectTabTrigger value="index.tsx" />
|
||||
</ConnectTabTriggers>
|
||||
|
||||
<ConnectTabContent value=".env">
|
||||
<SimpleCodeBlock className="bash" parentClassName="min-h-72">
|
||||
{`
|
||||
DATABASE_URL='your-database-url'
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="drizzle/schema.tsx">
|
||||
<SimpleCodeBlock className="tsx" parentClassName="min-h-72">
|
||||
{`
|
||||
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 }),
|
||||
});
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="index.tsx">
|
||||
<SimpleCodeBlock className="tsx" parentClassName="min-h-72">
|
||||
{`
|
||||
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);
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
</ConnectTabs>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContentFile
|
||||
@@ -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 (
|
||||
<ConnectTabs>
|
||||
<ConnectTabTriggers>
|
||||
<ConnectTabTrigger value=".env.local" />
|
||||
<ConnectTabTrigger value="page.tsx" />
|
||||
<ConnectTabTrigger value="utils/supabase/server.ts" />
|
||||
<ConnectTabTrigger value="utils/supabase/client.ts" />
|
||||
<ConnectTabTrigger value="utils/supabase/middleware.ts" />
|
||||
</ConnectTabTriggers>
|
||||
|
||||
<ConnectTabContent value=".env.local">
|
||||
<SimpleCodeBlock className="bash" parentClassName="min-h-72">
|
||||
{`
|
||||
NEXT_PUBLIC_SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'}
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=${projectKeys.anonKey ?? 'your-anon-key'}
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="page.tsx">
|
||||
<SimpleCodeBlock className="tsx" parentClassName="min-h-72">
|
||||
{`
|
||||
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 (
|
||||
<ul>
|
||||
{todos?.map((todo) => (
|
||||
<li>{todo}</li>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="utils/supabase/server.ts">
|
||||
<SimpleCodeBlock className="ts" parentClassName="min-h-72">
|
||||
{`
|
||||
import { createServerClient, type CookieOptions } from "@supabase/ssr";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
export const createClient = (cookieStore: ReturnType<typeof cookies>) => {
|
||||
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.
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
<ConnectTabContent value="utils/supabase/client.ts">
|
||||
<SimpleCodeBlock className="ts" parentClassName="min-h-72">
|
||||
{`
|
||||
import { createBrowserClient } from "@supabase/ssr";
|
||||
|
||||
export const createClient = () =>
|
||||
createBrowserClient(
|
||||
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
||||
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
|
||||
);
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="utils/supabase/middleware.ts">
|
||||
<SimpleCodeBlock className="ts" parentClassName="min-h-72">
|
||||
{`
|
||||
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 };
|
||||
};
|
||||
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
</ConnectTabs>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContentFile
|
||||
@@ -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 (
|
||||
<ConnectTabs>
|
||||
<ConnectTabTriggers>
|
||||
<ConnectTabTrigger value=".env.local" />
|
||||
<ConnectTabTrigger value="utils/supabase.ts" />
|
||||
<ConnectTabTrigger value="_app.tsx" />
|
||||
</ConnectTabTriggers>
|
||||
|
||||
<ConnectTabContent value=".env.local">
|
||||
<SimpleCodeBlock className="bash" parentClassName="min-h-72">
|
||||
{`
|
||||
NEXT_PUBLIC_SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'}
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=${projectKeys.anonKey ?? 'your-anon-key'}
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="utils/supabase.ts">
|
||||
<SimpleCodeBlock className="ts" parentClassName="min-h-72">
|
||||
{`
|
||||
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);
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="_app.tsx">
|
||||
<SimpleCodeBlock className="tsx" parentClassName="min-h-72">
|
||||
{`
|
||||
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 (
|
||||
<div>
|
||||
{todos.map((todo) => (
|
||||
<li key={todo}>{todo}</li>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Page
|
||||
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
</ConnectTabs>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContentFile
|
||||
@@ -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 (
|
||||
<ConnectTabs>
|
||||
<ConnectTabTriggers>
|
||||
<ConnectTabTrigger value=".env.local" />
|
||||
<ConnectTabTrigger value="utils/supabase.ts" />
|
||||
<ConnectTabTrigger value="src/App.vue" />
|
||||
</ConnectTabTriggers>
|
||||
|
||||
<ConnectTabContent value=".env.local">
|
||||
<SimpleCodeBlock className="bash" parentClassName="min-h-72">
|
||||
{`
|
||||
SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'}
|
||||
SUPABASE_KEY=${projectKeys.anonKey ?? 'your-anon-key'}
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="utils/supabase.ts">
|
||||
<SimpleCodeBlock className="ts" parentClassName="min-h-72">
|
||||
{`
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
const supabaseUrl = process.env.SUPABASE_URL;
|
||||
const supabaseKey = process.env.SUPABASE_KEY;
|
||||
|
||||
export const supabase = createClient(supabaseUrl, supabaseKey);
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="src/App.vue">
|
||||
<SimpleCodeBlock className="jsx" parentClassName="min-h-72">
|
||||
{`
|
||||
<script setup>
|
||||
import { supabase } from '../utils/supabase'
|
||||
const todos = ref([])
|
||||
|
||||
async function getTodos() {
|
||||
const { data: todos } = await supabase.from('todos').select()
|
||||
countries.value = todos
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getTodos()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul>
|
||||
<li v-for="todo in todos" :key="todo.id">{{ todo.name }}</li>
|
||||
</ul>
|
||||
</template>
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
</ConnectTabs>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContentFile
|
||||
@@ -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 (
|
||||
<ConnectTabs>
|
||||
<ConnectTabTriggers>
|
||||
<ConnectTabTrigger value=".env.local" />
|
||||
<ConnectTabTrigger value="prisma/schema.prisma" />
|
||||
</ConnectTabTriggers>
|
||||
|
||||
<ConnectTabContent value=".env.local">
|
||||
<SimpleCodeBlock className="bash" parentClassName="min-h-72">
|
||||
{`
|
||||
# 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"
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="prisma/schema.prisma">
|
||||
<SimpleCodeBlock className="bash" parentClassName="min-h-72">
|
||||
{`
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
directUrl = env("DIRECT_URL")
|
||||
}
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
</ConnectTabs>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContentFile
|
||||
@@ -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 (
|
||||
<ConnectTabs>
|
||||
<ConnectTabTriggers>
|
||||
<ConnectTabTrigger value=".env.local" />
|
||||
<ConnectTabTrigger value="utils/supabase.ts" />
|
||||
<ConnectTabTrigger value="App.tsx" />
|
||||
</ConnectTabTriggers>
|
||||
|
||||
<ConnectTabContent value=".env.local">
|
||||
<SimpleCodeBlock className="bash" parentClassName="min-h-72">
|
||||
{`
|
||||
SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'}
|
||||
SUPABASE_KEY=${projectKeys.anonKey ?? 'your-anon-key'}
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="utils/supabase.ts">
|
||||
<SimpleCodeBlock className="ts" parentClassName="min-h-72">
|
||||
{`
|
||||
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);
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="App.tsx">
|
||||
<SimpleCodeBlock className="tsx" parentClassName="min-h-72">
|
||||
{`
|
||||
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 (
|
||||
<div>
|
||||
{todos.map((todo) => (
|
||||
<li key={todo}>{todo}</li>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
export default Page
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
</ConnectTabs>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContentFile
|
||||
@@ -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 (
|
||||
<ConnectTabs>
|
||||
<ConnectTabTriggers>
|
||||
<ConnectTabTrigger value=".env.local" />
|
||||
<ConnectTabTrigger value="src/utility/supabaseClient.ts" />
|
||||
<ConnectTabTrigger value="src/App.tsx" />
|
||||
</ConnectTabTriggers>
|
||||
|
||||
<ConnectTabContent value=".env.local">
|
||||
<SimpleCodeBlock className="bash" parentClassName="min-h-72">
|
||||
{`
|
||||
SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'}
|
||||
SUPABASE_ANON_KEY=${projectKeys.anonKey ?? 'your-anon-key'}
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="src/utility/supabaseClient.ts">
|
||||
<SimpleCodeBlock className="ts" parentClassName="min-h-72">
|
||||
{`
|
||||
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,
|
||||
},
|
||||
});
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="src/App.tsx">
|
||||
<SimpleCodeBlock className="tsx" parentClassName="min-h-72">
|
||||
{`
|
||||
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 (
|
||||
<BrowserRouter>
|
||||
<RefineKbarProvider>
|
||||
<Refine
|
||||
dataProvider={dataProvider(supabaseClient)}
|
||||
liveProvider={liveProvider(supabaseClient)}
|
||||
authProvider={authProvider}
|
||||
routerProvider={routerBindings}
|
||||
options={{
|
||||
syncWithLocation: true,
|
||||
warnWhenUnsavedChanges: true,
|
||||
}}
|
||||
resources={[{
|
||||
name: "countries",
|
||||
list: "/countries",
|
||||
create: "/countries/create",
|
||||
edit: "/countries/edit/:id",
|
||||
show: "/countries/show/:id"
|
||||
}]}>
|
||||
<Routes>
|
||||
<Route index
|
||||
element={<NavigateToResource resource="countries" />}
|
||||
/>
|
||||
<Route path="/countries">
|
||||
<Route index element={<CountriesList />} />
|
||||
<Route path="create" element={<CountriesCreate />} />
|
||||
<Route path="edit/:id" element={<CountriesEdit />} />
|
||||
<Route path="show/:id" element={<CountriesShow />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
<RefineKbar />
|
||||
<UnsavedChangesNotifier />
|
||||
<DocumentTitleHandler />
|
||||
</Refine>
|
||||
</RefineKbarProvider>
|
||||
</BrowserRouter>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
</ConnectTabs>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContentFile
|
||||
@@ -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 (
|
||||
<ConnectTabs>
|
||||
<ConnectTabTriggers>
|
||||
<ConnectTabTrigger value=".env.local" />
|
||||
<ConnectTabTrigger value="utils/supabase.ts" />
|
||||
<ConnectTabTrigger value="src/App.jsx" />
|
||||
</ConnectTabTriggers>
|
||||
|
||||
<ConnectTabContent value=".env.local">
|
||||
<SimpleCodeBlock className="bash" parentClassName="min-h-72">
|
||||
{`
|
||||
SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'}
|
||||
SUPABASE_ANON_KEY=${projectKeys.anonKey ?? 'your-anon-key'}
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="utils/supabase.ts">
|
||||
<SimpleCodeBlock className="ts" parentClassName="min-h-72">
|
||||
{`
|
||||
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);
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="src/App.jsx">
|
||||
<SimpleCodeBlock className="jsx" parentClassName="min-h-72">
|
||||
{`
|
||||
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 (
|
||||
<ul>
|
||||
<For each={todos()}>{(country) => <li>{todo.name}</li>}</For>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
</ConnectTabs>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContentFile
|
||||
@@ -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 (
|
||||
<ConnectTabs>
|
||||
<ConnectTabTriggers>
|
||||
<ConnectTabTrigger value=".env.local" />
|
||||
<ConnectTabTrigger value="src/lib/supabaseClient.js" />
|
||||
<ConnectTabTrigger value="src/routes/+page.server.js" />
|
||||
<ConnectTabTrigger value="src/routes/+page.svelte" />
|
||||
</ConnectTabTriggers>
|
||||
|
||||
<ConnectTabContent value=".env.local">
|
||||
<SimpleCodeBlock className="bash" parentClassName="min-h-72">
|
||||
{`
|
||||
SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'}
|
||||
SUPABASE_ANON_KEY=${projectKeys.anonKey ?? 'your-anon-key'}
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="src/lib/supabaseClient.js">
|
||||
<SimpleCodeBlock className="js" parentClassName="min-h-72">
|
||||
{`
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
const supabaseUrl = process.env.SUPABASE_URL;
|
||||
const supabaseKey = process.env.SUPABASE_KEY;
|
||||
|
||||
export const supabase = createClient(supabaseUrl, supabaseKey);
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="src/routes/+page.server.js">
|
||||
<SimpleCodeBlock className="js" parentClassName="min-h-72">
|
||||
{`
|
||||
import { supabase } from "$lib/supabaseClient";
|
||||
|
||||
export async function load() {
|
||||
const { data } = await supabase.from("countries").select();
|
||||
return {
|
||||
countries: data ?? [],
|
||||
};
|
||||
}
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="src/routes/+page.svelte">
|
||||
<SimpleCodeBlock className="html" parentClassName="min-h-72">
|
||||
{`
|
||||
<script>
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
<ul>
|
||||
{#each data.countries as country}
|
||||
<li>{country.name}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
</ConnectTabs>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContentFile
|
||||
@@ -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 (
|
||||
<ConnectTabs>
|
||||
<ConnectTabTriggers>
|
||||
<ConnectTabTrigger value=".env.local" />
|
||||
<ConnectTabTrigger value="utils/supabase.ts" />
|
||||
<ConnectTabTrigger value="App.vue" />
|
||||
</ConnectTabTriggers>
|
||||
|
||||
<ConnectTabContent value=".env.local">
|
||||
<SimpleCodeBlock className="bash" parentClassName="min-h-72">
|
||||
{`
|
||||
SUPABASE_URL=${projectKeys.apiUrl ?? 'your-project-url'}
|
||||
SUPABASE_KEY=${projectKeys.anonKey ?? 'your-anon-key'}
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="utils/supabase.ts">
|
||||
<SimpleCodeBlock className="ts" parentClassName="min-h-72">
|
||||
{`
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
|
||||
const supabaseUrl = process.env.SUPABASE_URL;
|
||||
const supabaseKey = process.env.SUPABASE_KEY;
|
||||
|
||||
export const supabase = createClient(supabaseUrl, supabaseKey);
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
|
||||
<ConnectTabContent value="App.vue">
|
||||
<SimpleCodeBlock className="jsx" parentClassName="min-h-72">
|
||||
{`
|
||||
<script setup>
|
||||
import { supabase } from '../utils/supabase'
|
||||
const todos = ref([])
|
||||
|
||||
async function getTodos() {
|
||||
const { data: todos } = await supabase.from('todos').select()
|
||||
countries.value = todos
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getTodos()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul>
|
||||
<li v-for="todo in todos" :key="todo.id">{{ todo.name }}</li>
|
||||
</ul>
|
||||
</template>
|
||||
`}
|
||||
</SimpleCodeBlock>
|
||||
</ConnectTabContent>
|
||||
</ConnectTabs>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContentFile
|
||||
@@ -119,54 +119,52 @@ const ServiceStatus = () => {
|
||||
const allServicesOperational = services.every((service) => service.isSuccess)
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Popover_Shadcn_ modal={false} open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger_Shadcn_ asChild>
|
||||
<Button
|
||||
type="default"
|
||||
icon={
|
||||
isLoadingChecks ? (
|
||||
<IconLoader className="animate-spin" />
|
||||
) : (
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
allServicesOperational ? 'bg-brand' : 'bg-amber-900'
|
||||
}`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
<Popover_Shadcn_ modal={false} open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger_Shadcn_ asChild>
|
||||
<Button
|
||||
type="default"
|
||||
icon={
|
||||
isLoadingChecks ? (
|
||||
<IconLoader className="animate-spin" />
|
||||
) : (
|
||||
<div
|
||||
className={`w-2 h-2 rounded-full ${
|
||||
allServicesOperational ? 'bg-brand' : 'bg-amber-900'
|
||||
}`}
|
||||
/>
|
||||
)
|
||||
}
|
||||
>
|
||||
{isBranch ? 'Preview Branch' : 'Project'} Status
|
||||
</Button>
|
||||
</PopoverTrigger_Shadcn_>
|
||||
<PopoverContent_Shadcn_ className="p-0 w-56" side="bottom" align="end">
|
||||
{services.map((service) => (
|
||||
<div
|
||||
key={service.name}
|
||||
className="px-4 py-2 text-xs flex items-center justify-between border-b last:border-none"
|
||||
>
|
||||
{isBranch ? 'Preview Branch' : 'Project'} Status
|
||||
</Button>
|
||||
</PopoverTrigger_Shadcn_>
|
||||
<PopoverContent_Shadcn_ className="p-0 w-56" side="bottom" align="end">
|
||||
{services.map((service) => (
|
||||
<div
|
||||
key={service.name}
|
||||
className="px-4 py-2 text-xs flex items-center justify-between border-b last:border-none"
|
||||
>
|
||||
<div>
|
||||
<p>{service.name}</p>
|
||||
<p className="text-foreground-light">
|
||||
{service.isLoading
|
||||
? 'Checking status'
|
||||
: service.isSuccess
|
||||
? 'No issues'
|
||||
: 'Unable to connect'}
|
||||
</p>
|
||||
</div>
|
||||
{service.isLoading ? (
|
||||
<IconLoader className="animate-spin" size="tiny" />
|
||||
) : service.isSuccess ? (
|
||||
<CheckCircle2 className="text-brand" size={18} strokeWidth={1.5} />
|
||||
) : (
|
||||
<AlertTriangle className="text-amber-900" size={18} strokeWidth={1.5} />
|
||||
)}
|
||||
<div>
|
||||
<p>{service.name}</p>
|
||||
<p className="text-foreground-light">
|
||||
{service.isLoading
|
||||
? 'Checking status'
|
||||
: service.isSuccess
|
||||
? 'No issues'
|
||||
: 'Unable to connect'}
|
||||
</p>
|
||||
</div>
|
||||
))}
|
||||
</PopoverContent_Shadcn_>
|
||||
</Popover_Shadcn_>
|
||||
</div>
|
||||
{service.isLoading ? (
|
||||
<IconLoader className="animate-spin" size="tiny" />
|
||||
) : service.isSuccess ? (
|
||||
<CheckCircle2 className="text-brand" size={18} strokeWidth={1.5} />
|
||||
) : (
|
||||
<AlertTriangle className="text-amber-900" size={18} strokeWidth={1.5} />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</PopoverContent_Shadcn_>
|
||||
</Popover_Shadcn_>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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 id="connection-string">
|
||||
<div id="connection-string" className="w-full">
|
||||
<Panel
|
||||
className="!m-0 [&>div:nth-child(1)]:!border-0 [&>div:nth-child(1)>div]:!p-0"
|
||||
className={cn(
|
||||
'!m-0 [&>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={
|
||||
<div ref={connectionStringsRef} className="w-full flex flex-col pt-4">
|
||||
<div className="flex items-center justify-between px-6 mb-2">
|
||||
<h5 key="panel-title" className="mb-0">
|
||||
Connection string
|
||||
</h5>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div
|
||||
ref={connectionStringsRef}
|
||||
className={cn('w-full flex flex-col', appearance === 'default' ? 'pt-4' : 'pt-0')}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center justify-between mb-2',
|
||||
appearance === 'default' && 'px-6'
|
||||
)}
|
||||
>
|
||||
{appearance === 'default' && (
|
||||
<h5 key="panel-title" className="mb-0">
|
||||
Connection string
|
||||
</h5>
|
||||
)}
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center gap-x-2 ml-auto',
|
||||
appearance === 'minimal' ? 'absolute -top-1 right-0' : ''
|
||||
)}
|
||||
>
|
||||
{readReplicasEnabled && <DatabaseSelector />}
|
||||
<Button asChild type="default" icon={<IconExternalLink strokeWidth={1.5} />}>
|
||||
<a href="https://supabase.com/docs/guides/database/connecting-to-postgres">
|
||||
@@ -177,7 +202,10 @@ export const DatabaseConnectionString = () => {
|
||||
type="underlined"
|
||||
size="tiny"
|
||||
activeId={selectedTab}
|
||||
baseClassNames="!space-y-0 px-6 -mb-[1px]"
|
||||
baseClassNames={cn(
|
||||
'!space-y-0 px-6 -mb-[1px]',
|
||||
appearance === 'minimal' && '-mt-1 px-0'
|
||||
)}
|
||||
onChange={setSelectedTab}
|
||||
>
|
||||
{CONNECTION_TYPES.map((type) => (
|
||||
@@ -188,7 +216,7 @@ export const DatabaseConnectionString = () => {
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<Panel.Content>
|
||||
<Panel.Content className={appearance === 'minimal' && 'px-0'}>
|
||||
{isLoading && <ShimmeringLoader className="h-8 w-full" />}
|
||||
{isError && <AlertError error={error} subject="Failed to retrieve database settings" />}
|
||||
{isSuccess && (
|
||||
@@ -219,7 +247,7 @@ export const DatabaseConnectionString = () => {
|
||||
{poolerConnStringSyntax.length > 0 && (
|
||||
<>
|
||||
<Separator />
|
||||
<Panel.Content className="!py-3 space-y-2">
|
||||
<Panel.Content className={cn('!py-3 space-y-2', appearance === 'minimal' && 'px-0')}>
|
||||
<Collapsible_Shadcn_>
|
||||
<CollapsibleTrigger_Shadcn_ className="group [&[data-state=open]>div>svg]:!-rotate-180">
|
||||
<div className="flex items-center gap-x-2 w-full">
|
||||
|
||||
@@ -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 (
|
||||
<Modal
|
||||
hideFooter
|
||||
size="xlarge"
|
||||
visible={snap.showPoolingModeHelper}
|
||||
header={
|
||||
<div className="w-full flex items-center justify-between">
|
||||
<p>Which pooling mode should I use?</p>
|
||||
<Button asChild type="default" icon={<IconExternalLink strokeWidth={1.5} />}>
|
||||
<a
|
||||
href="https://supabase.com/docs/guides/database/connecting-to-postgres#how-connection-pooling-works"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
onCancel={() => snap.setShowPoolingModeHelper(false)}
|
||||
>
|
||||
<Modal.Content className="py-4 text-sm flex flex-col gap-y-4">
|
||||
<Dialog_Shadcn_ open={snap.showPoolingModeHelper} onOpenChange={snap.setShowPoolingModeHelper}>
|
||||
<DialogContent_Shadcn_ className="sm:max-w-4xl">
|
||||
<DialogHeader_Shadcn_ className="pr-8">
|
||||
<DialogTitle_Shadcn_ className="mb-4">
|
||||
<div className="w-full flex items-center justify-between">
|
||||
<p className="text-lg max-w-2xl">Which pooling mode should I use?</p>
|
||||
<Button asChild type="default" icon={<IconExternalLink strokeWidth={1.5} />}>
|
||||
<a
|
||||
href="https://supabase.com/docs/guides/database/connecting-to-postgres#how-connection-pooling-works"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
>
|
||||
Documentation
|
||||
</a>
|
||||
</Button>
|
||||
</div>
|
||||
</DialogTitle_Shadcn_>
|
||||
<DialogDescription_Shadcn_ className="border-b pb-4 ">
|
||||
<p className="text-lg max-w-2xl">
|
||||
A connection pooler is a system (external to Postgres) which manages Postgres
|
||||
connections by allocating connections whenever clients make requests.
|
||||
</p>
|
||||
</DialogDescription_Shadcn_>
|
||||
</DialogHeader_Shadcn_>
|
||||
<Markdown
|
||||
className="max-w-full [&>h3]:text-sm"
|
||||
className="px-6 max-w-full [&>h3]:text-sm"
|
||||
content={`
|
||||
A connection pooler is a system (external to Postgres) which manages Postgres connections
|
||||
by allocating connections whenever clients make requests. Each pooling mode handles
|
||||
connections differently.
|
||||
Each pooling mode handles connections differently.
|
||||
|
||||
### Transaction mode
|
||||
This mode is recommended if you are connecting from *serverless environments*. A connection is assigned to the client for the duration of a transaction. Two consecutive transactions from the same client could be executed over two different connections. Some session-based Postgres features such as prepared statements are *not available* with this option.
|
||||
@@ -89,13 +96,12 @@ This mode is similar to connecting to your database directly. There is full supp
|
||||
</AlertDescription_Shadcn_>
|
||||
</Alert_Shadcn_>
|
||||
)}
|
||||
</Modal.Content>
|
||||
<Modal.Separator />
|
||||
<Modal.Content className="flex items-center justify-end pb-2">
|
||||
<Button type="default" onClick={() => snap.setShowPoolingModeHelper(false)}>
|
||||
Close
|
||||
</Button>
|
||||
</Modal.Content>
|
||||
</Modal>
|
||||
<DialogFooter_Shadcn_>
|
||||
<DialogClose_Shadcn_ onClick={() => snap.setShowPoolingModeHelper(false)}>
|
||||
<Button type="secondary">Close</Button>
|
||||
</DialogClose_Shadcn_>
|
||||
</DialogFooter_Shadcn_>
|
||||
</DialogContent_Shadcn_>
|
||||
</Dialog_Shadcn_>
|
||||
)
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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<SimpleCodeBlockProps>) => {
|
||||
const { resolvedTheme } = useTheme()
|
||||
const [showCopied, setShowCopied] = useState(false)
|
||||
const target = useRef(null)
|
||||
const button = useRef(null)
|
||||
@@ -62,7 +64,7 @@ const SimpleCodeBlock = ({
|
||||
return (
|
||||
<Highlight
|
||||
{...defaultProps}
|
||||
theme={(prism as any).theme || defaultTheme}
|
||||
theme={(prism as any).theme || resolvedTheme === 'dark' ? darkTheme : lightTheme}
|
||||
code={(children as string)?.trim() ?? ''}
|
||||
language={language as Language}
|
||||
>
|
||||
|
||||
@@ -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<PanelProps>) {
|
||||
let headerClasses: string[] = []
|
||||
|
||||
if (!props.hideHeaderStyling) {
|
||||
headerClasses = [`bg-surface-100 border-b border-overlay`]
|
||||
} else {
|
||||
headerClasses = [`bg-surface-100`]
|
||||
}
|
||||
|
||||
const content = (
|
||||
<div
|
||||
className={`
|
||||
${props.noHideOverflow ? '' : 'overflow-hidden'} rounded-md border
|
||||
border-overlay shadow-sm ${props.noMargin ? '' : 'mb-8'} ${props.className}`}
|
||||
className={cn(
|
||||
'rounded-md border border-overlay shadow-sm',
|
||||
props.noHideOverflow ? '' : 'overflow-hidden',
|
||||
props.noMargin ? '' : 'mb-8',
|
||||
props.className
|
||||
)}
|
||||
>
|
||||
{props.title && (
|
||||
<div className={headerClasses.join(' ')}>
|
||||
<div
|
||||
className={cn(
|
||||
props.hideHeaderStyling ? 'bg-surface-100 border-b border-overlay' : 'bg-surface-100',
|
||||
props.headerClasses
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center px-6 py-4">{props.title}</div>
|
||||
</div>
|
||||
)}
|
||||
<div className={`bg-surface-100 ${props.bodyClassName || ''}`}>{props.children}</div>
|
||||
<div className={cn('bg-surface-100', props.bodyClassName)}>{props.children}</div>
|
||||
|
||||
{props.footer && (
|
||||
<div className="bg-surface-100 border-t border-overlay">
|
||||
@@ -50,9 +52,7 @@ function Panel(props: PropsWithChildren<PanelProps>) {
|
||||
}
|
||||
|
||||
function Content({ children, className }: { children: ReactNode; className?: string | false }) {
|
||||
let classes = ['px-6 py-4']
|
||||
if (className) classes.push(className)
|
||||
return <div className={classes.join(' ')}>{children}</div>
|
||||
return <div className={cn('px-6 py-4', className)}>{children}</div>
|
||||
}
|
||||
|
||||
Panel.Content = Content
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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 = () => {
|
||||
<div className="w-full mx-auto my-16 space-y-16 max-w-7xl">
|
||||
<div className="flex items-center justify-between mx-6 space-x-6">
|
||||
<h1 className="text-3xl">{projectName}</h1>
|
||||
{IS_PLATFORM && project?.status === PROJECT_STATUS.ACTIVE_HEALTHY && <ServiceStatus />}
|
||||
<div className="flex items-center gap-x-3">
|
||||
{IS_PLATFORM && project?.status === PROJECT_STATUS.ACTIVE_HEALTHY && <ServiceStatus />}
|
||||
{IS_PLATFORM && project?.status === PROJECT_STATUS.ACTIVE_HEALTHY && <Connect />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mx-6">
|
||||
|
||||
@@ -22,7 +22,7 @@ const ProjectSettings: NextPageWithLayout = () => {
|
||||
<div className="space-y-10">
|
||||
<div className="flex flex-col gap-y-4">
|
||||
<DatabaseReadOnlyAlert />
|
||||
<DatabaseConnectionString />
|
||||
<DatabaseConnectionString appearance="default" />
|
||||
<DatabaseSettings />
|
||||
<ConnectionPooling />
|
||||
</div>
|
||||
|
||||
@@ -50,7 +50,6 @@ const ProjectsPage: NextPageWithLayout = () => {
|
||||
<Connecting />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!navLayoutV2 && (
|
||||
<div className="py-4 px-5">
|
||||
{IS_PLATFORM && projectCreationEnabled && isSuccess && organizations.length !== 0 && (
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="160" height="160" fill="none" viewBox="0 0 160 160"><rect width="9.631" height="40.852" fill="#fff" rx="4.816" transform="matrix(.87303 .48767 -.49721 .86763 43.48 67.304)"/><rect width="9.631" height="40.852" fill="#fff" rx="4.816" transform="matrix(.87303 .48767 -.49721 .86763 76.94 46.534)"/><rect width="9.631" height="40.852" fill="#fff" rx="4.816" transform="matrix(.87303 .48767 -.49721 .86763 128.424 46.535)"/><rect width="9.631" height="40.852" fill="#fff" rx="4.816" transform="matrix(.87303 .48767 -.49721 .86763 94.957 67.304)"/></svg>
|
||||
|
After Width: | Height: | Size: 596 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="160" height="160" fill="none" viewBox="0 0 160 160"><rect width="9.631" height="40.852" fill="currentColor" rx="4.816" transform="matrix(.87303 .48767 -.49721 .86763 43.48 67.304)"/><rect width="9.631" height="40.852" fill="currentColor" rx="4.816" transform="matrix(.87303 .48767 -.49721 .86763 76.94 46.534)"/><rect width="9.631" height="40.852" fill="currentColor" rx="4.816" transform="matrix(.87303 .48767 -.49721 .86763 128.424 46.535)"/><rect width="9.631" height="40.852" fill="currentColor" rx="4.816" transform="matrix(.87303 .48767 -.49721 .86763 94.957 67.304)"/></svg>
|
||||
|
After Width: | Height: | Size: 628 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="#e10098" style="fill:color(display-p3 .8824 0 .5961)" viewBox="0 0 100 100"><path fill-rule="evenodd" d="m50 6.903 37.323 21.549v43.096L50 93.097 12.677 71.548V28.451L50 6.903ZM16.865 30.87v31.656L44.28 15.041 16.864 30.87ZM50 13.51 18.398 68.246h63.205L50 13.509Zm27.415 58.924h-54.83L50 88.261l27.415-15.828Zm5.72-9.908L55.72 15.041 83.136 30.87v31.656Z" clip-rule="evenodd"/><circle cx="50" cy="9.321" r="8.82"/><circle cx="85.229" cy="29.66" r="8.82"/><circle cx="85.229" cy="70.34" r="8.82"/><circle cx="50" cy="90.679" r="8.82"/><circle cx="14.766" cy="70.34" r="8.82"/><circle cx="14.766" cy="29.66" r="8.82"/></svg>
|
||||
|
After Width: | Height: | Size: 669 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="32" viewBox="0 0 48 32"><path fill="#00DC82" fill-rule="nonzero" d="M26.88 32h17.76c.567 0 1.11-.2 1.6-.48.49-.28.997-.633 1.28-1.12.283-.486.48-1.038.48-1.6s-.196-1.114-.48-1.6l-12-20.64a2.918 2.918 0 0 0-1.12-1.12c-.49-.28-1.193-.48-1.76-.48s-1.11.2-1.6.48c-.49.28-.837.634-1.12 1.12l-3.04 5.28L20.8 1.6c-.284-.487-.63-1-1.12-1.28-.49-.28-1.033-.32-1.6-.32-.567 0-1.11.04-1.6.32s-.997.793-1.28 1.28L.32 27.2c-.284.486-.32 1.038-.32 1.6 0 .561.037 1.113.32 1.6.283.486.79.84 1.28 1.12.49.28 1.033.48 1.6.48h11.2c4.438 0 7.668-1.99 9.92-5.76l5.44-9.44 2.88-4.96 8.8 15.04H29.76L26.88 32Zm-12.64-5.12H6.4L18.08 6.72 24 16.8l-3.921 6.836c-1.496 2.446-3.201 3.244-5.839 3.244Z"/></svg>
|
||||
|
After Width: | Height: | Size: 741 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="576.095" height="593.844" viewBox="0 0 432.071 445.383"><g style="fill-rule:nonzero;clip-rule:nonzero;fill:none;stroke:#fff;stroke-width:12.4651;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4"><path d="M323.205 324.227c2.833-23.601 1.984-27.062 19.563-23.239l4.463.392c13.517.615 31.199-2.174 41.587-7 22.362-10.376 35.622-27.7 13.572-23.148-50.297 10.376-53.755-6.655-53.755-6.655 53.111-78.803 75.313-178.836 56.149-203.322-52.27-66.789-142.748-35.206-144.262-34.386l-.482.089c-9.938-2.062-21.06-3.294-33.554-3.496-22.761-.374-40.032 5.967-53.133 15.904 0 0-161.408-66.498-153.899 83.628 1.597 31.936 45.777 241.655 98.47 178.31 19.259-23.163 37.871-42.748 37.871-42.748 9.242 6.14 20.307 9.272 31.912 8.147l.897-.765c-.281 2.876-.157 5.689.359 9.019-13.572 15.167-9.584 17.83-36.723 23.416-27.457 5.659-11.326 15.734-.797 18.367 12.768 3.193 42.305 7.716 62.268-20.224l-.795 3.188c5.325 4.26 4.965 30.619 5.72 49.452.756 18.834 2.017 36.409 5.856 46.771 3.839 10.36 8.369 37.05 44.036 29.406 29.809-6.388 52.6-15.582 54.677-101.107" style="fill:#000;stroke:#000;stroke-width:37.3953;stroke-linecap:butt;stroke-linejoin:miter"/><path stroke="none" d="M402.395 271.23c-50.302 10.376-53.76-6.655-53.76-6.655 53.111-78.808 75.313-178.843 56.153-203.326-52.27-66.785-142.752-35.2-144.262-34.38l-.486.087c-9.938-2.063-21.06-3.292-33.56-3.496-22.761-.373-40.026 5.967-53.127 15.902 0 0-161.411-66.495-153.904 83.63 1.597 31.938 45.776 241.657 98.471 178.312 19.26-23.163 37.869-42.748 37.869-42.748 9.243 6.14 20.308 9.272 31.908 8.147l.901-.765c-.28 2.876-.152 5.689.361 9.019-13.575 15.167-9.586 17.83-36.723 23.416-27.459 5.659-11.328 15.734-.796 18.367 12.768 3.193 42.307 7.716 62.266-20.224l-.796 3.188c5.319 4.26 9.054 27.711 8.428 48.969-.626 21.259-1.044 35.854 3.147 47.254 4.191 11.4 8.368 37.05 44.042 29.406 29.809-6.388 45.256-22.942 47.405-50.555 1.525-19.631 4.976-16.729 5.194-34.28l2.768-8.309c3.192-26.611.507-35.196 18.872-31.203l4.463.392c13.517.615 31.208-2.174 41.591-7 22.358-10.376 35.618-27.7 13.573-23.148z" style="fill:#336791;stroke:none"/><path d="M215.866 286.484c-1.385 49.516.348 99.377 5.193 111.495 4.848 12.118 15.223 35.688 50.9 28.045 29.806-6.39 40.651-18.756 45.357-46.051 3.466-20.082 10.148-75.854 11.005-87.281M173.104 38.256S11.583-27.76 19.092 122.365c1.597 31.938 45.779 241.664 98.473 178.316 19.256-23.166 36.671-41.335 36.671-41.335M260.349 26.207c-5.591 1.753 89.848-34.889 144.087 34.417 19.159 24.484-3.043 124.519-56.153 203.329"/><path d="M348.282 263.953s3.461 17.036 53.764 6.653c22.04-4.552 8.776 12.774-13.577 23.155-18.345 8.514-59.474 10.696-60.146-1.069-1.729-30.355 21.647-21.133 19.96-28.739-1.525-6.85-11.979-13.573-18.894-30.338-6.037-14.633-82.796-126.849 21.287-110.183 3.813-.789-27.146-99.002-124.553-100.599-97.385-1.597-94.19 119.762-94.19 119.762" style="stroke-linejoin:bevel"/><path d="M188.604 274.334c-13.577 15.166-9.584 17.829-36.723 23.417-27.459 5.66-11.326 15.733-.797 18.365 12.768 3.195 42.307 7.718 62.266-20.229 6.078-8.509-.036-22.086-8.385-25.547-4.034-1.671-9.428-3.765-16.361 3.994z"/><path d="M187.715 274.069c-1.368-8.917 2.93-19.528 7.536-31.942 6.922-18.626 22.893-37.255 10.117-96.339-9.523-44.029-73.396-9.163-73.436-3.193-.039 5.968 2.889 30.26-1.067 58.548-5.162 36.913 23.488 68.132 56.479 64.938"/><path d="M172.517 141.7c-.288 2.039 3.733 7.48 8.976 8.207 5.234.73 9.714-3.522 9.998-5.559.284-2.039-3.732-4.285-8.977-5.015-5.237-.731-9.719.333-9.996 2.367z" style="fill:#fff;stroke-width:4.155;stroke-linecap:butt;stroke-linejoin:miter"/><path d="M331.941 137.543c.284 2.039-3.732 7.48-8.976 8.207-5.238.73-9.718-3.522-10.005-5.559-.277-2.039 3.74-4.285 8.979-5.015 5.239-.73 9.718.333 10.002 2.368z" style="fill:#fff;stroke-width:2.0775;stroke-linecap:butt;stroke-linejoin:miter"/><path d="M350.676 123.432c.863 15.994-3.445 26.888-3.988 43.914-.804 24.748 11.799 53.074-7.191 81.435"/></g></svg>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="23" height="28" viewBox="0 0 23 28"><path fill="#fff" fill-rule="evenodd" d="M.21 19.007a1.349 1.349 0 0 1-.011-1.427l10.13-16.523a1.349 1.349 0 0 1 2.374.14l9.254 20.074a1.349 1.349 0 0 1-.838 1.856L6.725 27.446a1.349 1.349 0 0 1-1.527-.57L.21 19.007ZM11.497 5.888a.506.506 0 0 1 .96-.1l6.383 14.77a.506.506 0 0 1-.32.685L8.575 24.21a.506.506 0 0 1-.64-.584l3.562-17.739Z"/></svg>
|
||||
|
After Width: | Height: | Size: 428 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="23" height="28" viewBox="0 0 23 28"><path fill="#2D3748" fill-rule="evenodd" d="M.21 19.007a1.349 1.349 0 0 1-.011-1.427l10.13-16.523a1.349 1.349 0 0 1 2.374.14l9.254 20.074a1.349 1.349 0 0 1-.838 1.856L6.725 27.446a1.349 1.349 0 0 1-1.527-.57L.21 19.007ZM11.497 5.888a.506.506 0 0 1 .96-.1l6.383 14.77a.506.506 0 0 1-.32.685L8.575 24.21a.506.506 0 0 1-.64-.584l3.562-17.739Z"/></svg>
|
||||
|
After Width: | Height: | Size: 431 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="36" height="38" viewBox="0 0 36 38"><g fill="none" fill-rule="evenodd"><path fill="#FFF" d="M15.923 1.343c-1.385.7-4.304 2.062-5.885 3.149-1.581 1.087-1.385 2.1-2.077 2.8-.692.7-2.77 2.449-4.154 3.498-1.385 1.05-1.039 3.85-1.385 4.9-.346 1.049-1.038 3.498-1.384 4.548-.347 1.05.692 2.45 1.384 3.85.693 1.399 2.423 5.248 3.116 6.647.692 1.4 2.77.7 4.5 2.1 1.73 1.4 4.154 3.15 5.885 4.199 1.73 1.05 2.423 1.05 4.154 0 1.73-1.05 4.154-2.8 5.885-4.2 1.73-1.399 3.808-.699 4.5-2.099.692-1.4 2.423-5.248 3.116-6.648.692-1.4 1.73-2.8 1.384-3.849-.346-1.05-1.038-3.5-1.384-4.549-.346-1.05 0-3.849-1.385-4.899-1.385-1.05-2.992-2.386-4.154-3.499s-.692-1.75-2.077-2.8c-1.385-1.049-4.5-2.448-5.885-3.148-1.385-.7-2.77-.7-4.154 0Z"/><path fill="#BF4722" d="m11.034 7.414 6.479 4.445a.913.913 0 0 0 .492.155.884.884 0 0 0 .492-.155l6.484-4.46a.891.891 0 0 0-.123-1.52l-6.479-3.224a.886.886 0 0 0-.778 0l-6.46 3.224a.89.89 0 0 0-.108 1.535h.001Zm9.172 5.84a.9.9 0 0 0 .384.738l5.194 3.568a.868.868 0 0 0 1.078-.07l4.357-3.917a.892.892 0 0 0-.04-1.36l-4.16-3.344a.878.878 0 0 0-1.043-.04l-5.386 3.703a.9.9 0 0 0-.384.723v-.001ZM7.287 18.108c.21.19.318.47.29.753a.873.873 0 0 1-.438.678l-3.101 1.869a.875.875 0 0 1-.998-.069.898.898 0 0 1-.292-.968l1.147-3.628a.881.881 0 0 1 .605-.588.849.849 0 0 1 .818.194l1.969 1.759Zm16.823.38-5.608-3.858a.877.877 0 0 0-.984 0l-5.608 3.857a.905.905 0 0 0-.384.678.923.923 0 0 0 .295.728l5.603 5.038c.333.3.839.3 1.172 0l5.602-5.039a.902.902 0 0 0 .29-.727.884.884 0 0 0-.378-.678v.001ZM9.153 17.49 4.8 13.573a.912.912 0 0 1-.296-.702.88.88 0 0 1 .33-.673l4.16-3.364a.887.887 0 0 1 1.049-.04l5.381 3.703c.25.165.4.446.4.748s-.15.582-.4.747l-5.189 3.568c-.335.23-.78.2-1.083-.07h.001ZM31.5 23.117l-4.431-2.666a.868.868 0 0 0-1.034.1l-5.416 4.858a.898.898 0 0 0 .256 1.495l7.513 3.06a.881.881 0 0 0 1.132-.464l2.33-5.242a.898.898 0 0 0-.35-1.141Zm.615-6.374 1.147 3.628h-.01a.901.901 0 0 1-.13.797.877.877 0 0 1-1.16.24l-3.106-1.87a.868.868 0 0 1-.424-.677.888.888 0 0 1 .29-.753l1.97-1.764c.223-.2.53-.273.817-.194a.896.896 0 0 1 .606.593ZM15.68 26.226a.893.893 0 0 0-.28-.817L9.985 20.55a.868.868 0 0 0-1.034-.1l-4.43 2.667a.908.908 0 0 0-.355 1.136l2.333 5.243a.877.877 0 0 0 1.133.463l7.508-3.06a.89.89 0 0 0 .541-.672l-.001-.001Zm2.659 1.495 6.026 2.452a.89.89 0 0 1 .531.728.886.886 0 0 1-.374.832l-6.03 4.211a.869.869 0 0 1-.493.16.884.884 0 0 1-.492-.16l-6.026-4.21a.891.891 0 0 1 .172-1.56l6.026-2.452a.886.886 0 0 1 .66 0v-.001Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><g fill="#868181" fill-rule="evenodd"><path d="M13.789.422a4 4 0 0 0-3.578 0l-8 4A4 4 0 0 0 0 8v8a4 4 0 0 0 2.211 3.578l8 4a4 4 0 0 0 3.578 0l8-4A4 4 0 0 0 24 16V8a4 4 0 0 0-2.211-3.578l-8-4ZM8 8a4 4 0 1 1 8 0v8a4 4 0 1 1-8 0V8Z"/><path fill-rule="nonzero" d="M14 8a2 2 0 1 1-4 0 2 2 0 0 1 4 0Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 390 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 166 155.3"><path fill="#76b3e1" d="M163 35S110-4 69 5l-3 1c-6 2-11 5-14 9l-2 3-15 26 26 5c11 7 25 10 38 7l46 9 18-30z"/><linearGradient id="a" x1="27.5" x2="152" y1="3" y2="63.5" gradientUnits="userSpaceOnUse"><stop offset=".1" stop-color="#76b3e1"/><stop offset=".3" stop-color="#dcf2fd"/><stop offset="1" stop-color="#76b3e1"/></linearGradient><path fill="url(#a)" d="M163 35S110-4 69 5l-3 1c-6 2-11 5-14 9l-2 3-15 26 26 5c11 7 25 10 38 7l46 9 18-30z" opacity=".3"/><path fill="#518ac8" d="m52 35-4 1c-17 5-22 21-13 35 10 13 31 20 48 15l62-21S92 26 52 35z"/><linearGradient id="b" x1="95.8" x2="74" y1="32.6" y2="105.2" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#76b3e1"/><stop offset=".5" stop-color="#4377bb"/><stop offset="1" stop-color="#1f3b77"/></linearGradient><path fill="url(#b)" d="m52 35-4 1c-17 5-22 21-13 35 10 13 31 20 48 15l62-21S92 26 52 35z" opacity=".3"/><linearGradient id="c" x1="18.4" x2="144.3" y1="64.2" y2="149.8" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#315aa9"/><stop offset=".5" stop-color="#518ac8"/><stop offset="1" stop-color="#315aa9"/></linearGradient><path fill="url(#c)" d="M134 80a45 45 0 0 0-48-15L24 85 4 120l112 19 20-36c4-7 3-15-2-23z"/><linearGradient id="d" x1="75.2" x2="24.4" y1="74.5" y2="260.8" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#4377bb"/><stop offset=".5" stop-color="#1a336b"/><stop offset="1" stop-color="#1a336b"/></linearGradient><path fill="url(#d)" d="M114 115a45 45 0 0 0-48-15L4 120s53 40 94 30l3-1c17-5 23-21 13-34z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="109" height="113" fill="none" viewBox="0 0 109 113"><path fill="url(#a)" d="M63.708 110.284c-2.86 3.601-8.658 1.628-8.727-2.97l-1.007-67.251h45.22c8.19 0 12.758 9.46 7.665 15.874l-43.151 54.347Z"/><path fill="url(#b)" fill-opacity=".2" d="M63.708 110.284c-2.86 3.601-8.658 1.628-8.727-2.97l-1.007-67.251h45.22c8.19 0 12.758 9.46 7.665 15.874l-43.151 54.347Z"/><path fill="#3ECF8E" d="M45.317 2.071c2.86-3.601 8.657-1.628 8.726 2.97l.442 67.251H9.83c-8.19 0-12.759-9.46-7.665-15.875L45.317 2.072Z"/><defs><linearGradient id="a" x1="53.974" x2="94.163" y1="54.974" y2="71.829" gradientUnits="userSpaceOnUse"><stop stop-color="#249361"/><stop offset="1" stop-color="#3ECF8E"/></linearGradient><linearGradient id="b" x1="36.156" x2="54.484" y1="30.578" y2="65.08" gradientUnits="userSpaceOnUse"><stop/><stop offset="1" stop-opacity="0"/></linearGradient></defs></svg>
|
||||
|
After Width: | Height: | Size: 910 B |
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<
|
||||
<DialogPrimitive.Overlay
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
'fixed inset-0 z-50 bg-background/90 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -40,7 +43,7 @@ const DialogContent = React.forwardRef<
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg md:w-full',
|
||||
'bg-surface-overlay border-overlay',
|
||||
'bg border-overlay',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
@@ -56,15 +59,25 @@ const DialogContent = React.forwardRef<
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName
|
||||
|
||||
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div className={cn('flex flex-col space-y-1.5 text-center sm:text-left', className)} {...props} />
|
||||
<div
|
||||
className={cn(
|
||||
'flex flex-col space-y-1.5 text-center sm:text-left',
|
||||
DIALOG_PADDING_X,
|
||||
DIALOG_PADDING_Y,
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
DialogHeader.displayName = 'DialogHeader'
|
||||
|
||||
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
const DialogFooter = ({ className, children, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
|
||||
{...props}
|
||||
/>
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
DialogFooter.displayName = 'DialogFooter'
|
||||
|
||||
@@ -72,11 +85,7 @@ const DialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn('text-lg font-semibold leading-none tracking-tight', className)}
|
||||
{...props}
|
||||
/>
|
||||
<DialogPrimitive.Title ref={ref} className={cn('text-lg leading-none', className)} {...props} />
|
||||
))
|
||||
DialogTitle.displayName = DialogPrimitive.Title.displayName
|
||||
|
||||
@@ -92,12 +101,40 @@ const DialogDescription = React.forwardRef<
|
||||
))
|
||||
DialogDescription.displayName = DialogPrimitive.Description.displayName
|
||||
|
||||
const DialogClose = React.forwardRef<
|
||||
React.ElementRef<typeof DialogPrimitive.Close>,
|
||||
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Close>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPrimitive.Close
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-foreground-muted',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</DialogPrimitive.Close>
|
||||
))
|
||||
DialogClose.displayName = DialogPrimitive.Close.displayName
|
||||
|
||||
const DialogSection = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
|
||||
({ className, children, ...props }, ref) => (
|
||||
<div ref={ref} {...props} className={cn('px-5 py-3', className)}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
)
|
||||
DialogSection.displayName = 'DialogSection'
|
||||
|
||||
export {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogClose,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
DialogSection,
|
||||
}
|
||||