| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476 |
- 'use client'
- import { IS_PLATFORM } from 'common'
- import {
- Clock5,
- Code2,
- KeyRound,
- Layers,
- ListChecks,
- Lock,
- LockKeyhole,
- Mail,
- MessageCircle,
- Plus,
- Rows,
- ShieldPlus,
- Table2,
- Telescope,
- UserCog,
- UserPlus,
- Vault,
- Webhook,
- Zap,
- } from 'lucide-react'
- import dynamic from 'next/dynamic'
- import { useMemo } from 'react'
- import type { CommandOptions, ICommand } from 'ui-patterns/CommandMenu'
- import {
- PageType,
- useRegisterCommands,
- useRegisterPage,
- useSetCommandMenuOpen,
- } from 'ui-patterns/CommandMenu'
- import { COMMAND_MENU_SECTIONS } from './CommandMenu.utils'
- import {
- getIntegrationCommandName,
- getIntegrationRoute,
- useCreateCommandsConfig,
- } from './CreateCommands.utils'
- import { SIDEBAR_KEYS } from '@/components/layouts/ProjectLayout/LayoutSidebar/LayoutSidebarProvider'
- const AiIconAnimation = dynamic(() => import('ui').then((mod) => mod.AiIconAnimation))
- const Badge = dynamic(() => import('ui').then((mod) => mod.Badge))
- const EdgeFunctions = dynamic(() => import('icons').then((mod) => mod.EdgeFunctions))
- const AnalyticsBucket = dynamic(() => import('icons').then((mod) => mod.AnalyticsBucket))
- const FilesBucket = dynamic(() => import('icons').then((mod) => mod.FilesBucket))
- const VectorBucket = dynamic(() => import('icons').then((mod) => mod.VectorBucket))
- const CREATE_STUDIO_ENTITY = 'Create Studio Entity'
- export function useCreateCommands(options?: CommandOptions) {
- const setIsOpen = useSetCommandMenuOpen()
- const {
- ref,
- setPage,
- openSidebar,
- snap,
- authEnabled,
- edgeFunctionsEnabled,
- storageEnabled,
- sendSmsHook,
- sendEmailHook,
- customAccessTokenHook,
- mfaVerificationHook,
- mfaVerificationHookEnabled,
- passwordVerificationHook,
- passwordVerificationHookEnabled,
- beforeUserCreatedHook,
- isVectorBucketsEnabled,
- isAnalyticsBucketsEnabled,
- installedIntegrationIds,
- allIntegrations,
- reportsEnabled,
- } = useCreateCommandsConfig()
- const databaseCommands = useMemo(
- () =>
- [
- {
- id: 'create-db-table',
- name: 'Create Table',
- route: `/project/${ref}/editor?create=table`,
- icon: () => <Table2 />,
- },
- {
- id: 'create-db-index',
- name: 'Create Index',
- route: `/project/${ref}/database/indexes?new=true`,
- icon: () => <Rows />,
- },
- {
- id: 'create-db-function',
- name: 'Create Database Function',
- route: `/project/${ref}/database/functions?new=true`,
- icon: () => <Code2 />,
- },
- {
- id: 'create-db-enum',
- name: 'Create Enumerated Type',
- route: `/project/${ref}/database/types?new=true`,
- icon: () => <ListChecks />,
- },
- {
- id: 'create-db-trigger',
- name: 'Create Database Trigger',
- route: `/project/${ref}/database/triggers?new=true`,
- icon: () => <Zap />,
- },
- {
- id: 'create-db-role',
- name: 'Create Database Role',
- route: `/project/${ref}/database/roles?new=true`,
- icon: () => <UserCog />,
- },
- ].filter(Boolean) as ICommand[],
- [ref]
- )
- const authCommands = useMemo(
- () =>
- authEnabled
- ? ([
- {
- id: 'create-auth-user',
- name: 'Create Auth User',
- route: `/project/${ref}/auth/users?new=true`,
- icon: () => <UserPlus />,
- },
- {
- id: 'create-rls-policy',
- name: 'Create RLS Policy',
- route: `/project/${ref}/auth/policies?new=true`,
- icon: () => <ShieldPlus />,
- },
- ...(IS_PLATFORM
- ? [
- {
- id: 'create-auth-hook-sms',
- name: 'Create Auth Hook (SMS)',
- route: `/project/${ref}/auth/hooks?hook=${sendSmsHook?.id}`,
- icon: () => <MessageCircle />,
- },
- {
- id: 'create-auth-hook-email',
- name: 'Create Auth Hook (Email)',
- route: `/project/${ref}/auth/hooks?hook=${sendEmailHook?.id}`,
- icon: () => <Mail />,
- },
- {
- id: 'create-auth-hook-custom-access-token',
- name: 'Create Auth Hook (Custom Access Token)',
- route: `/project/${ref}/auth/hooks?hook=${customAccessTokenHook?.id}`,
- icon: () => <KeyRound />,
- },
- {
- id: 'create-auth-hook-mfa-verification',
- name: 'Create Auth Hook (MFA Verification Attempt)',
- route: `/project/${ref}/auth/hooks?hook=${mfaVerificationHook?.id}`,
- icon: () => <ShieldPlus />,
- badge: () => (mfaVerificationHookEnabled ? <Badge>Team</Badge> : null),
- className: mfaVerificationHookEnabled
- ? 'opacity-50 cursor-not-allowed pointer-events-none'
- : '',
- },
- {
- id: 'create-auth-hook-password-verification',
- name: 'Create Auth Hook (Password Verification Attempt)',
- route: `/project/${ref}/auth/hooks?hook=${passwordVerificationHook?.id}`,
- icon: () => <Lock />,
- badge: () => (passwordVerificationHookEnabled ? <Badge>Team</Badge> : null),
- className: passwordVerificationHookEnabled
- ? 'opacity-50 cursor-not-allowed pointer-events-none'
- : '',
- },
- {
- id: 'create-auth-hook-before-user-created',
- name: 'Create Auth Hook (Before User Created)',
- route: `/project/${ref}/auth/hooks?hook=${beforeUserCreatedHook?.id}`,
- icon: () => <KeyRound />,
- },
- ]
- : []),
- ...(IS_PLATFORM
- ? [
- {
- id: 'create-oauth-app',
- name: 'Create OAuth App',
- route: `/project/${ref}/auth/oauth-apps?new=true`,
- icon: () => <KeyRound />,
- },
- ]
- : []),
- ].filter(Boolean) as ICommand[])
- : [],
- [
- ref,
- authEnabled,
- sendSmsHook,
- sendEmailHook,
- customAccessTokenHook,
- mfaVerificationHook,
- mfaVerificationHookEnabled,
- passwordVerificationHook,
- passwordVerificationHookEnabled,
- beforeUserCreatedHook,
- ]
- )
- const edgeFunctionsCommands = useMemo(
- () =>
- edgeFunctionsEnabled
- ? ([
- {
- id: 'create-edge-function-editor',
- name: 'Create Edge Function via Editor',
- route: `/project/${ref}/functions/new`,
- icon: () => <EdgeFunctions />,
- },
- {
- id: 'create-edge-function-cli',
- name: 'Create Edge Function via CLI',
- route: `/project/${ref}/functions?create=cli`,
- icon: () => <EdgeFunctions />,
- },
- {
- id: 'create-edge-function-ai',
- name: 'Create Edge Function via AI',
- action: () => {
- openSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
- snap.newChat({
- name: 'Create new edge function',
- initialInput: `Create a new edge function that ...`,
- suggestions: {
- title:
- 'I can help you create a new edge function. Here are a few example prompts to get you started:',
- prompts: [
- {
- label: 'Stripe Payments',
- description:
- 'Create a new edge function that processes payments with Stripe',
- },
- {
- label: 'Email with Resend',
- description: 'Create a new edge function that sends emails with Resend',
- },
- {
- label: 'PDF Generator',
- description:
- 'Create a new edge function that generates PDFs from HTML templates',
- },
- ],
- },
- })
- setIsOpen(false)
- },
- icon: () => (
- <AiIconAnimation
- allowHoverEffect={false}
- size={20}
- className="text-foreground-light"
- />
- ),
- },
- {
- id: 'create-edge-function-secret',
- name: 'Create Edge Function Secret',
- route: `/project/${ref}/functions/secrets`,
- icon: () => <LockKeyhole />,
- },
- ].filter(Boolean) as ICommand[])
- : [],
- [ref, edgeFunctionsEnabled, openSidebar, snap, setIsOpen]
- )
- const storageCommands = useMemo(
- () =>
- storageEnabled
- ? ([
- {
- id: 'create-storage-bucket-files',
- name: 'Create Storage Bucket (Files)',
- route: `/project/${ref}/storage/files?new=true`,
- icon: () => <FilesBucket />,
- },
- {
- id: 'create-storage-bucket-analytics',
- name: 'Create Storage Bucket (Analytics)',
- route: `/project/${ref}/storage/analytics?new=true`,
- icon: () => <AnalyticsBucket />,
- badge: () => <Badge variant="success">New</Badge>,
- className: !isAnalyticsBucketsEnabled
- ? 'opacity-50 cursor-not-allowed pointer-events-none'
- : '',
- enabled: isAnalyticsBucketsEnabled,
- },
- {
- id: 'create-storage-bucket-vectors',
- name: 'Create Storage Bucket (Vectors)',
- route: `/project/${ref}/storage/vectors?new=true`,
- icon: () => <VectorBucket />,
- badge: () => <Badge variant="success">New</Badge>,
- className: !isVectorBucketsEnabled
- ? 'opacity-50 cursor-not-allowed pointer-events-none'
- : '',
- enabled: isVectorBucketsEnabled,
- },
- ].filter(Boolean) as ICommand[])
- : [],
- [ref, storageEnabled, isAnalyticsBucketsEnabled, isVectorBucketsEnabled]
- )
- const integrationsCommands = useMemo(() => {
- // Sort integrations: Postgres modules (non-wrappers) first, then wrappers
- const sortedIntegrations = [...allIntegrations].sort((a, b) => {
- const aIsWrapper = a.type === 'wrapper' ? 1 : 0
- const bIsWrapper = b.type === 'wrapper' ? 1 : 0
- return aIsWrapper - bIsWrapper
- })
- return sortedIntegrations
- .map((integration) => {
- const route = getIntegrationRoute(integration, ref, installedIntegrationIds)
- if (!route) return null
- const isWrapper = integration.type === 'wrapper'
- // For wrappers, use the integration icon with wrapper styling
- // For Postgres modules, use plain icons
- const getIcon = () => {
- if (isWrapper) {
- return (
- <div className="w-6 h-6 relative bg-white border rounded-md flex items-center justify-center [&>img]:p-1! [&>svg]:p-1!">
- {integration.icon()}
- </div>
- )
- }
- // Use plain icons for Postgres modules
- switch (integration.id) {
- case 'vault':
- return <Vault />
- case 'cron':
- return <Clock5 />
- case 'webhooks':
- return <Webhook />
- case 'queues':
- return <Layers />
- default:
- // Fallback to integration icon for other Postgres modules
- return integration.icon()
- }
- }
- return {
- id: `create-integration-${integration.id}`,
- name: getIntegrationCommandName(integration),
- route,
- icon: getIcon,
- }
- })
- .filter(Boolean) as ICommand[]
- }, [ref, allIntegrations, installedIntegrationIds])
- // Observability commands are only available on the platform, not for self-hosted/CLI
- const observabilityCommands = useMemo(
- () => [
- ...(IS_PLATFORM
- ? reportsEnabled
- ? ([
- {
- id: 'create-observability-report',
- name: 'Create Custom Report',
- route: `/project/${ref}/observability/api-overview?newReport=true`,
- icon: () => <Telescope />,
- },
- ].filter(Boolean) as ICommand[])
- : []
- : []),
- ],
- [ref, reportsEnabled]
- )
- const sections = useMemo(
- () => [
- {
- id: 'create-database',
- name: 'Database',
- commands: databaseCommands,
- },
- ...(authCommands.length > 0
- ? [
- {
- id: 'create-auth',
- name: 'Auth',
- commands: authCommands,
- },
- ]
- : []),
- ...(edgeFunctionsCommands.length > 0
- ? [
- {
- id: 'create-edge-functions',
- name: 'Edge Functions',
- commands: edgeFunctionsCommands,
- },
- ]
- : []),
- ...(storageCommands.length > 0
- ? [
- {
- id: 'create-storage',
- name: 'Storage',
- commands: storageCommands,
- },
- ]
- : []),
- ...(observabilityCommands.length > 0
- ? [
- {
- id: 'create-observability',
- name: 'Observability',
- commands: observabilityCommands,
- },
- ]
- : []),
- ...(integrationsCommands.length > 0
- ? [
- {
- id: 'create-integrations',
- name: 'Integrations',
- commands: integrationsCommands,
- },
- ]
- : []),
- ],
- [
- databaseCommands,
- authCommands,
- edgeFunctionsCommands,
- storageCommands,
- integrationsCommands,
- observabilityCommands,
- ]
- )
- useRegisterPage(
- CREATE_STUDIO_ENTITY,
- {
- type: PageType.Commands,
- sections,
- },
- {
- deps: [sections],
- enabled: true,
- }
- )
- useRegisterCommands(
- COMMAND_MENU_SECTIONS.ACTIONS,
- [
- {
- id: 'create-studio-entity',
- name: 'Create...',
- action: () => setPage(CREATE_STUDIO_ENTITY),
- icon: () => <Plus />,
- },
- ],
- {
- ...options,
- orderSection: (sections) => sections,
- sectionMeta: { priority: 3 },
- enabled: true,
- }
- )
- }
|