mirror of
https://github.com/simstudioai/sim.git
synced 2026-09-24 15:45:35 +08:00
fix(envvars): use getEnv on the client-side when we need to inject vars for docker runtime, fix folder container & removed folder/subfolder creation modals (#751)
* use getEnv on the client-side when we need to inject vars for docker runtime, fix folder container & removed folder/subfolder creation modals * consolidated naming utils * additional type safety
This commit is contained in:
+4
-4
@@ -17,7 +17,7 @@ import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent } from '@/components/ui/card'
|
||||
import { CopyButton } from '@/components/ui/copy-button'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { env } from '@/lib/env'
|
||||
import { getEnv } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { ChatDeploy } from '@/app/workspace/[workspaceId]/w/[workflowId]/components/control-bar/components/deploy-modal/components/chat-deploy/chat-deploy'
|
||||
@@ -225,7 +225,7 @@ export function DeployModal({
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
const endpoint = `${env.NEXT_PUBLIC_APP_URL}/api/workflows/${workflowId}/execute`
|
||||
const endpoint = `${getEnv('NEXT_PUBLIC_APP_URL')}/api/workflows/${workflowId}/execute`
|
||||
const inputFormatExample = getInputFormatExample()
|
||||
|
||||
setDeploymentInfo({
|
||||
@@ -288,7 +288,7 @@ export function DeployModal({
|
||||
}
|
||||
|
||||
// Update the local deployment info
|
||||
const endpoint = `${env.NEXT_PUBLIC_APP_URL}/api/workflows/${workflowId}/execute`
|
||||
const endpoint = `${getEnv('NEXT_PUBLIC_APP_URL')}/api/workflows/${workflowId}/execute`
|
||||
const inputFormatExample = getInputFormatExample()
|
||||
|
||||
const newDeploymentInfo = {
|
||||
@@ -597,7 +597,7 @@ export function DeployModal({
|
||||
<DeployForm
|
||||
apiKeys={apiKeys}
|
||||
keysLoaded={keysLoaded}
|
||||
endpointUrl={`${env.NEXT_PUBLIC_APP_URL}/api/workflows/${workflowId}/execute`}
|
||||
endpointUrl={`${getEnv('NEXT_PUBLIC_APP_URL')}/api/workflows/${workflowId}/execute`}
|
||||
workflowId={workflowId || ''}
|
||||
onSubmit={onDeploy}
|
||||
getInputFormatExample={getInputFormatExample}
|
||||
|
||||
+2
-2
@@ -13,7 +13,7 @@ import {
|
||||
CommandList,
|
||||
} from '@/components/ui/command'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { env } from '@/lib/env'
|
||||
import { getEnv } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import {
|
||||
type Credential,
|
||||
@@ -301,7 +301,7 @@ export function GoogleDrivePicker({
|
||||
showUploadFolders: true,
|
||||
supportDrives: true,
|
||||
multiselect: false,
|
||||
appId: env.NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER,
|
||||
appId: getEnv('NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER'),
|
||||
// Enable folder selection when mimeType is folder
|
||||
setSelectFolderEnabled: !!mimeTypeFilter?.includes('folder'),
|
||||
callbackFunction: (data) => {
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { env } from '@/lib/env'
|
||||
import { getEnv } from '@/lib/env'
|
||||
import type { SubBlockConfig } from '@/blocks/types'
|
||||
import { useCollaborativeWorkflow } from '@/hooks/use-collaborative-workflow'
|
||||
import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
|
||||
@@ -169,8 +169,8 @@ export function FileSelectorInput({
|
||||
}
|
||||
|
||||
// For Google Drive
|
||||
const clientId = env.NEXT_PUBLIC_GOOGLE_CLIENT_ID || ''
|
||||
const apiKey = env.NEXT_PUBLIC_GOOGLE_API_KEY || ''
|
||||
const clientId = getEnv('NEXT_PUBLIC_GOOGLE_CLIENT_ID') || ''
|
||||
const apiKey = getEnv('NEXT_PUBLIC_GOOGLE_API_KEY') || ''
|
||||
|
||||
// Render Google Calendar selector
|
||||
if (isGoogleCalendar) {
|
||||
|
||||
+37
-64
@@ -5,10 +5,8 @@ import { logger } from '@sentry/nextjs'
|
||||
import { File, Folder, Plus, Upload } from 'lucide-react'
|
||||
import { useParams, useRouter } from 'next/navigation'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
|
||||
import { generateFolderName } from '@/lib/naming'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/w/components/providers/workspace-permissions-provider'
|
||||
import { useFolderStore } from '@/stores/folders/store'
|
||||
@@ -25,8 +23,6 @@ export function CreateMenu({
|
||||
isCollapsed,
|
||||
isCreatingWorkflow = false,
|
||||
}: CreateMenuProps) {
|
||||
const [showFolderDialog, setShowFolderDialog] = useState(false)
|
||||
const [folderName, setFolderName] = useState('')
|
||||
const [isCreating, setIsCreating] = useState(false)
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const [pressTimer, setPressTimer] = useState<NodeJS.Timeout | null>(null)
|
||||
@@ -61,10 +57,37 @@ export function CreateMenu({
|
||||
}
|
||||
}, [onCreateWorkflow, isCreatingWorkflow, router, workspaceId])
|
||||
|
||||
const handleCreateFolder = useCallback(() => {
|
||||
const handleCreateFolder = useCallback(async () => {
|
||||
setIsOpen(false)
|
||||
setShowFolderDialog(true)
|
||||
}, [])
|
||||
|
||||
if (isCreating) {
|
||||
logger.info('Folder creation already in progress, ignoring request')
|
||||
return
|
||||
}
|
||||
|
||||
if (!workspaceId) {
|
||||
logger.error('No workspaceId available for folder creation')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
setIsCreating(true)
|
||||
|
||||
// Generate folder name using fresh data from API
|
||||
const folderName = await generateFolderName(workspaceId)
|
||||
|
||||
await createFolder({
|
||||
name: folderName,
|
||||
workspaceId: workspaceId,
|
||||
})
|
||||
|
||||
logger.info(`Created folder: ${folderName}`)
|
||||
} catch (error) {
|
||||
logger.error('Failed to create folder:', { error })
|
||||
} finally {
|
||||
setIsCreating(false)
|
||||
}
|
||||
}, [createFolder, workspaceId])
|
||||
|
||||
const handleImportWorkflow = useCallback(() => {
|
||||
setIsOpen(false)
|
||||
@@ -146,30 +169,6 @@ export function CreateMenu({
|
||||
}
|
||||
}, [pressTimer])
|
||||
|
||||
const handleFolderSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (!folderName.trim() || !workspaceId) return
|
||||
|
||||
setIsCreating(true)
|
||||
try {
|
||||
await createFolder({
|
||||
name: folderName.trim(),
|
||||
workspaceId: workspaceId,
|
||||
})
|
||||
setFolderName('')
|
||||
setShowFolderDialog(false)
|
||||
} catch (error) {
|
||||
logger.error('Failed to create folder:', { error })
|
||||
} finally {
|
||||
setIsCreating(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
setFolderName('')
|
||||
setShowFolderDialog(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Popover open={isOpen} onOpenChange={setIsOpen}>
|
||||
@@ -221,11 +220,15 @@ export function CreateMenu({
|
||||
</button>
|
||||
|
||||
<button
|
||||
className='flex w-full cursor-pointer items-center gap-2 rounded-md px-3 py-2 font-[380] text-card-foreground text-sm outline-none hover:bg-secondary/50 focus:bg-secondary/50'
|
||||
className={cn(
|
||||
'flex w-full cursor-pointer items-center gap-2 rounded-md px-3 py-2 font-[380] text-card-foreground text-sm outline-none hover:bg-secondary/50 focus:bg-secondary/50',
|
||||
isCreating && 'cursor-not-allowed opacity-50'
|
||||
)}
|
||||
onClick={handleCreateFolder}
|
||||
disabled={isCreating}
|
||||
>
|
||||
<Folder className='h-4 w-4' />
|
||||
New folder
|
||||
{isCreating ? 'Creating...' : 'New folder'}
|
||||
</button>
|
||||
|
||||
{userPermissions.canEdit && (
|
||||
@@ -246,36 +249,6 @@ export function CreateMenu({
|
||||
disabled={!userPermissions.canEdit}
|
||||
onClose={() => setIsOpen(false)}
|
||||
/>
|
||||
|
||||
{/* Folder creation dialog */}
|
||||
<Dialog open={showFolderDialog} onOpenChange={setShowFolderDialog}>
|
||||
<DialogContent className='sm:max-w-[425px]'>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create New Folder</DialogTitle>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleFolderSubmit} className='space-y-4'>
|
||||
<div className='space-y-2'>
|
||||
<Label htmlFor='folder-name'>Folder Name</Label>
|
||||
<Input
|
||||
id='folder-name'
|
||||
value={folderName}
|
||||
onChange={(e) => setFolderName(e.target.value)}
|
||||
placeholder='Enter folder name...'
|
||||
autoFocus
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-end space-x-2'>
|
||||
<Button type='button' variant='outline' onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type='submit' disabled={!folderName.trim() || isCreating}>
|
||||
{isCreating ? 'Creating...' : 'Create Folder'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+36
-64
@@ -4,17 +4,15 @@ import { useState } from 'react'
|
||||
import { File, Folder, MoreHorizontal, Pencil, Trash2 } from 'lucide-react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { Input } from '@/components/ui/input'
|
||||
import { Label } from '@/components/ui/label'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { generateSubfolderName } from '@/lib/naming'
|
||||
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/w/components/providers/workspace-permissions-provider'
|
||||
import { useFolderStore } from '@/stores/folders/store'
|
||||
|
||||
@@ -37,8 +35,6 @@ export function FolderContextMenu({
|
||||
onStartEdit,
|
||||
level,
|
||||
}: FolderContextMenuProps) {
|
||||
const [showSubfolderDialog, setShowSubfolderDialog] = useState(false)
|
||||
const [subfolderName, setSubfolderName] = useState('')
|
||||
const [isCreating, setIsCreating] = useState(false)
|
||||
const params = useParams()
|
||||
const workspaceId = params.workspaceId as string
|
||||
@@ -46,14 +42,46 @@ export function FolderContextMenu({
|
||||
// Get user permissions for the workspace
|
||||
const userPermissions = useUserPermissionsContext()
|
||||
|
||||
const { createFolder, deleteFolder } = useFolderStore()
|
||||
const { createFolder, deleteFolder, setExpanded } = useFolderStore()
|
||||
|
||||
const handleCreateWorkflow = () => {
|
||||
// Ensure folder is expanded so user can see the new workflow
|
||||
setExpanded(folderId, true)
|
||||
onCreateWorkflow(folderId)
|
||||
}
|
||||
|
||||
const handleCreateSubfolder = () => {
|
||||
setShowSubfolderDialog(true)
|
||||
const handleCreateSubfolder = async () => {
|
||||
if (isCreating) {
|
||||
logger.info('Subfolder creation already in progress, ignoring request')
|
||||
return
|
||||
}
|
||||
|
||||
if (!workspaceId) {
|
||||
logger.error('No workspaceId available for subfolder creation')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
setIsCreating(true)
|
||||
|
||||
// Ensure parent folder is expanded so user can see the new subfolder
|
||||
setExpanded(folderId, true)
|
||||
|
||||
// Generate subfolder name using fresh data from API
|
||||
const subfolderName = await generateSubfolderName(workspaceId, folderId)
|
||||
|
||||
await createFolder({
|
||||
name: subfolderName,
|
||||
workspaceId: workspaceId,
|
||||
parentId: folderId,
|
||||
})
|
||||
|
||||
logger.info(`Created subfolder: ${subfolderName}`)
|
||||
} catch (error) {
|
||||
logger.error('Failed to create subfolder:', { error })
|
||||
} finally {
|
||||
setIsCreating(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleRename = () => {
|
||||
@@ -76,31 +104,6 @@ export function FolderContextMenu({
|
||||
}
|
||||
}
|
||||
|
||||
const handleSubfolderSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
if (!subfolderName.trim() || !workspaceId) return
|
||||
|
||||
setIsCreating(true)
|
||||
try {
|
||||
await createFolder({
|
||||
name: subfolderName.trim(),
|
||||
workspaceId: workspaceId,
|
||||
parentId: folderId,
|
||||
})
|
||||
setSubfolderName('')
|
||||
setShowSubfolderDialog(false)
|
||||
} catch (error) {
|
||||
logger.error('Failed to create subfolder:', { error })
|
||||
} finally {
|
||||
setIsCreating(false)
|
||||
}
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
setSubfolderName('')
|
||||
setShowSubfolderDialog(false)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<DropdownMenu>
|
||||
@@ -175,37 +178,6 @@ export function FolderContextMenu({
|
||||
)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
{/* Subfolder creation dialog */}
|
||||
<Dialog open={showSubfolderDialog} onOpenChange={setShowSubfolderDialog}>
|
||||
<DialogContent className='sm:max-w-[425px]' onClick={(e) => e.stopPropagation()}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create New Subfolder</DialogTitle>
|
||||
</DialogHeader>
|
||||
<form onSubmit={handleSubfolderSubmit} className='space-y-4'>
|
||||
<div className='space-y-2'>
|
||||
<Label htmlFor='subfolder-name'>Folder Name</Label>
|
||||
<Input
|
||||
id='subfolder-name'
|
||||
value={subfolderName}
|
||||
onChange={(e) => setSubfolderName(e.target.value)}
|
||||
placeholder='Enter folder name...'
|
||||
maxLength={50}
|
||||
autoFocus
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className='flex justify-end space-x-2'>
|
||||
<Button type='button' variant='outline' onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type='submit' disabled={!subfolderName.trim() || isCreating}>
|
||||
{isCreating ? 'Creating...' : 'Create Folder'}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
+4
-2
@@ -304,7 +304,7 @@ export function FolderItem({
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onBlur={handleInputBlur}
|
||||
className='flex-1 border-0 bg-transparent p-0 text-muted-foreground text-sm outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
className='min-w-0 flex-1 border-0 bg-transparent p-0 text-muted-foreground text-sm outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0'
|
||||
maxLength={50}
|
||||
disabled={isRenaming}
|
||||
onClick={(e) => e.stopPropagation()} // Prevent folder toggle when clicking input
|
||||
@@ -314,7 +314,9 @@ export function FolderItem({
|
||||
spellCheck='false'
|
||||
/>
|
||||
) : (
|
||||
<span className='flex-1 select-none truncate text-muted-foreground'>{folder.name}</span>
|
||||
<span className='min-w-0 flex-1 select-none truncate text-muted-foreground'>
|
||||
{folder.name}
|
||||
</span>
|
||||
)}
|
||||
|
||||
{!isEditing && (
|
||||
|
||||
+2
-2
@@ -225,7 +225,7 @@ export function WorkflowItem({
|
||||
onChange={(e) => setEditValue(e.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
onBlur={handleInputBlur}
|
||||
className={`flex-1 border-0 bg-transparent p-0 font-medium text-sm outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 ${
|
||||
className={`min-w-0 flex-1 border-0 bg-transparent p-0 font-medium text-sm outline-none focus:outline-none focus:ring-0 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 ${
|
||||
active && !isDragOver ? 'text-foreground' : 'text-muted-foreground'
|
||||
}`}
|
||||
maxLength={100}
|
||||
@@ -237,7 +237,7 @@ export function WorkflowItem({
|
||||
spellCheck='false'
|
||||
/>
|
||||
) : (
|
||||
<span className='flex-1 select-none truncate'>
|
||||
<span className='min-w-0 flex-1 select-none truncate'>
|
||||
{workflow.name}
|
||||
{isMarketplace && ' (Preview)'}
|
||||
</span>
|
||||
|
||||
+1
-1
@@ -567,7 +567,7 @@ export function FolderTree({
|
||||
marketplaceWorkflows.length === 0 &&
|
||||
folderTree.length === 0 &&
|
||||
!isCollapsed && (
|
||||
<div className='break-words px-2 py-1.5 text-muted-foreground text-xs'>
|
||||
<div className='break-words px-2 py-1.5 pr-12 text-muted-foreground text-xs'>
|
||||
No workflows or folders in {workspaceId ? 'this workspace' : 'your account'}. Create one
|
||||
to get started.
|
||||
</div>
|
||||
|
||||
+17
-14
@@ -16,6 +16,7 @@ import {
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Skeleton } from '@/components/ui/skeleton'
|
||||
import { isDev } from '@/lib/environment'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { useUserPermissionsContext } from '../../../providers/workspace-permissions-provider'
|
||||
@@ -251,20 +252,22 @@ export function WorkspaceSelector({
|
||||
|
||||
{/* Bottom Actions */}
|
||||
<div className='mt-2 flex items-center gap-2 border-t pt-2'>
|
||||
{/* Send Invite */}
|
||||
<Button
|
||||
variant='secondary'
|
||||
size='sm'
|
||||
onClick={userPermissions.canAdmin ? () => setShowInviteMembers(true) : undefined}
|
||||
disabled={!userPermissions.canAdmin}
|
||||
className={cn(
|
||||
'h-8 flex-1 justify-center gap-2 rounded-[8px] font-medium text-muted-foreground text-xs transition-colors hover:bg-muted-foreground/10 hover:text-muted-foreground',
|
||||
!userPermissions.canAdmin && 'cursor-not-allowed opacity-50'
|
||||
)}
|
||||
>
|
||||
<Send className='h-3 w-3' />
|
||||
<span>Invite</span>
|
||||
</Button>
|
||||
{/* Send Invite - Hide in development */}
|
||||
{!isDev && (
|
||||
<Button
|
||||
variant='secondary'
|
||||
size='sm'
|
||||
onClick={userPermissions.canAdmin ? () => setShowInviteMembers(true) : undefined}
|
||||
disabled={!userPermissions.canAdmin}
|
||||
className={cn(
|
||||
'h-8 flex-1 justify-center gap-2 rounded-[8px] font-medium text-muted-foreground text-xs transition-colors hover:bg-muted-foreground/10 hover:text-muted-foreground',
|
||||
!userPermissions.canAdmin && 'cursor-not-allowed opacity-50'
|
||||
)}
|
||||
>
|
||||
<Send className='h-3 w-3' />
|
||||
<span>Invite</span>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{/* Create Workspace */}
|
||||
<Button
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ScrollArea } from '@/components/ui/scroll-area'
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip'
|
||||
import { useSession } from '@/lib/auth-client'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { generateWorkspaceName } from '@/lib/naming'
|
||||
import { cn } from '@/lib/utils'
|
||||
import {
|
||||
getKeyboardShortcutText,
|
||||
@@ -287,13 +288,18 @@ export function Sidebar() {
|
||||
setIsCreatingWorkspace(true)
|
||||
logger.info('Creating new workspace')
|
||||
|
||||
// Generate workspace name using utility function
|
||||
const workspaceName = await generateWorkspaceName()
|
||||
|
||||
logger.info(`Generated workspace name: ${workspaceName}`)
|
||||
|
||||
const response = await fetch('/api/workspaces', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: 'Untitled workspace',
|
||||
name: workspaceName,
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from 'react'
|
||||
import { useParams } from 'next/navigation'
|
||||
import { io, type Socket } from 'socket.io-client'
|
||||
import { env } from '@/lib/env'
|
||||
import { getEnv } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
|
||||
const logger = createLogger('SocketContext')
|
||||
@@ -154,7 +154,7 @@ export function SocketProvider({ children, user }: SocketProviderProps) {
|
||||
// Generate initial token for socket authentication
|
||||
const token = await generateSocketToken()
|
||||
|
||||
const socketUrl = env.NEXT_PUBLIC_SOCKET_URL || 'http://localhost:3002'
|
||||
const socketUrl = getEnv('NEXT_PUBLIC_SOCKET_URL') || 'http://localhost:3002'
|
||||
|
||||
logger.info('Attempting to connect to Socket.IO server', {
|
||||
url: socketUrl,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { env } from '@/lib/env'
|
||||
import { getEnv } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { getAllBlocks } from '@/blocks'
|
||||
import type { BlockOutput } from '@/blocks/types'
|
||||
@@ -477,7 +477,7 @@ export class AgentBlockHandler implements BlockHandler {
|
||||
) {
|
||||
logger.info('Using HTTP provider request (browser environment)')
|
||||
|
||||
const url = new URL('/api/providers', env.NEXT_PUBLIC_APP_URL || '')
|
||||
const url = new URL('/api/providers', getEnv('NEXT_PUBLIC_APP_URL') || '')
|
||||
const response = await fetch(url.toString(), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
||||
@@ -18,13 +18,13 @@ import {
|
||||
linkedErrorsIntegration,
|
||||
makeFetchTransport,
|
||||
} from '@sentry/nextjs'
|
||||
import { env } from './lib/env'
|
||||
import { env, getEnv } from './lib/env'
|
||||
import { isProd } from './lib/environment'
|
||||
|
||||
// Only in production
|
||||
if (typeof window !== 'undefined' && isProd) {
|
||||
const client = new BrowserClient({
|
||||
dsn: env.NEXT_PUBLIC_SENTRY_DSN || undefined,
|
||||
dsn: getEnv('NEXT_PUBLIC_SENTRY_DSN') || undefined,
|
||||
environment: env.NODE_ENV || 'development',
|
||||
transport: makeFetchTransport,
|
||||
stackParser: defaultStackParser,
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
import { stripeClient } from '@better-auth/stripe/client'
|
||||
import { emailOTPClient, genericOAuthClient, organizationClient } from 'better-auth/client/plugins'
|
||||
import { createAuthClient } from 'better-auth/react'
|
||||
import { env } from '@/lib/env'
|
||||
import { env, getEnv } from '@/lib/env'
|
||||
import { isDev, isProd } from '@/lib/environment'
|
||||
|
||||
export function getBaseURL() {
|
||||
let baseURL
|
||||
|
||||
if (env.VERCEL_ENV === 'preview') {
|
||||
baseURL = `https://${env.NEXT_PUBLIC_VERCEL_URL}`
|
||||
baseURL = `https://${getEnv('NEXT_PUBLIC_VERCEL_URL')}`
|
||||
} else if (env.VERCEL_ENV === 'development') {
|
||||
baseURL = `https://${env.NEXT_PUBLIC_VERCEL_URL}`
|
||||
baseURL = `https://${getEnv('NEXT_PUBLIC_VERCEL_URL')}`
|
||||
} else if (env.VERCEL_ENV === 'production') {
|
||||
baseURL = env.BETTER_AUTH_URL || env.NEXT_PUBLIC_APP_URL
|
||||
baseURL = env.BETTER_AUTH_URL || getEnv('NEXT_PUBLIC_APP_URL')
|
||||
} else if (env.NODE_ENV === 'development') {
|
||||
baseURL = env.NEXT_PUBLIC_APP_URL || env.BETTER_AUTH_URL || 'http://localhost:3000'
|
||||
baseURL = getEnv('NEXT_PUBLIC_APP_URL') || env.BETTER_AUTH_URL || 'http://localhost:3000'
|
||||
}
|
||||
|
||||
return baseURL
|
||||
|
||||
@@ -128,7 +128,6 @@ export interface BillingSummary {
|
||||
billingStatus: 'ok' | 'warning' | 'exceeded'
|
||||
}
|
||||
|
||||
// API Response Types
|
||||
export interface SubscriptionAPIResponse {
|
||||
isPaid: boolean
|
||||
isPro: boolean
|
||||
|
||||
+9
-9
@@ -142,15 +142,15 @@ export const env = createEnv({
|
||||
},
|
||||
|
||||
experimental__runtimeEnv: {
|
||||
NEXT_PUBLIC_APP_URL: getEnv('NEXT_PUBLIC_APP_URL'),
|
||||
NEXT_PUBLIC_VERCEL_URL: process.env.VERCEL_URL, // Keep as-is (server-only)
|
||||
NEXT_PUBLIC_SENTRY_DSN: getEnv('NEXT_PUBLIC_SENTRY_DSN'),
|
||||
NEXT_PUBLIC_GOOGLE_CLIENT_ID: getEnv('NEXT_PUBLIC_GOOGLE_CLIENT_ID'),
|
||||
NEXT_PUBLIC_GOOGLE_API_KEY: getEnv('NEXT_PUBLIC_GOOGLE_API_KEY'),
|
||||
NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER: getEnv('NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER'),
|
||||
NEXT_PUBLIC_SOCKET_URL: getEnv('NEXT_PUBLIC_SOCKET_URL'),
|
||||
NODE_ENV: process.env.NODE_ENV, // Keep as-is (build-time)
|
||||
NEXT_TELEMETRY_DISABLED: process.env.NEXT_TELEMETRY_DISABLED, // Keep as-is (build-time)
|
||||
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
|
||||
NEXT_PUBLIC_VERCEL_URL: process.env.NEXT_PUBLIC_VERCEL_URL,
|
||||
NEXT_PUBLIC_SENTRY_DSN: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||
NEXT_PUBLIC_GOOGLE_CLIENT_ID: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID,
|
||||
NEXT_PUBLIC_GOOGLE_API_KEY: process.env.NEXT_PUBLIC_GOOGLE_API_KEY,
|
||||
NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER: process.env.NEXT_PUBLIC_GOOGLE_PROJECT_NUMBER,
|
||||
NEXT_PUBLIC_SOCKET_URL: process.env.NEXT_PUBLIC_SOCKET_URL,
|
||||
NODE_ENV: process.env.NODE_ENV,
|
||||
NEXT_TELEMETRY_DISABLED: process.env.NEXT_TELEMETRY_DISABLED,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* Utility functions for generating names for all entities (workspaces, folders, workflows)
|
||||
*/
|
||||
|
||||
import type { WorkflowFolder } from '@/stores/folders/store'
|
||||
import type { Workspace } from '@/stores/organization/types'
|
||||
|
||||
export interface NameableEntity {
|
||||
name: string
|
||||
}
|
||||
|
||||
interface WorkspacesApiResponse {
|
||||
workspaces: Workspace[]
|
||||
}
|
||||
|
||||
interface FoldersApiResponse {
|
||||
folders: WorkflowFolder[]
|
||||
}
|
||||
|
||||
const ADJECTIVES = [
|
||||
'Blazing',
|
||||
'Crystal',
|
||||
'Golden',
|
||||
'Silver',
|
||||
'Mystic',
|
||||
'Cosmic',
|
||||
'Electric',
|
||||
'Frozen',
|
||||
'Burning',
|
||||
'Shining',
|
||||
'Dancing',
|
||||
'Flying',
|
||||
'Roaring',
|
||||
'Whispering',
|
||||
'Glowing',
|
||||
'Sparkling',
|
||||
'Thunder',
|
||||
'Lightning',
|
||||
'Storm',
|
||||
'Ocean',
|
||||
'Mountain',
|
||||
'Forest',
|
||||
'Desert',
|
||||
'Arctic',
|
||||
'Tropical',
|
||||
'Midnight',
|
||||
'Dawn',
|
||||
'Sunset',
|
||||
'Rainbow',
|
||||
'Diamond',
|
||||
'Ruby',
|
||||
'Emerald',
|
||||
'Sapphire',
|
||||
'Pearl',
|
||||
'Jade',
|
||||
'Amber',
|
||||
'Coral',
|
||||
'Ivory',
|
||||
'Obsidian',
|
||||
'Marble',
|
||||
'Velvet',
|
||||
'Silk',
|
||||
'Satin',
|
||||
'Linen',
|
||||
'Cotton',
|
||||
'Wool',
|
||||
'Cashmere',
|
||||
'Denim',
|
||||
'Neon',
|
||||
'Pastel',
|
||||
'Vibrant',
|
||||
'Muted',
|
||||
'Bold',
|
||||
'Subtle',
|
||||
'Bright',
|
||||
'Dark',
|
||||
]
|
||||
|
||||
const NOUNS = [
|
||||
'Phoenix',
|
||||
'Dragon',
|
||||
'Eagle',
|
||||
'Wolf',
|
||||
'Lion',
|
||||
'Tiger',
|
||||
'Panther',
|
||||
'Falcon',
|
||||
'Hawk',
|
||||
'Raven',
|
||||
'Swan',
|
||||
'Dove',
|
||||
'Butterfly',
|
||||
'Firefly',
|
||||
'Dragonfly',
|
||||
'Hummingbird',
|
||||
'Galaxy',
|
||||
'Nebula',
|
||||
'Comet',
|
||||
'Meteor',
|
||||
'Star',
|
||||
'Moon',
|
||||
'Sun',
|
||||
'Planet',
|
||||
'Asteroid',
|
||||
'Constellation',
|
||||
'Aurora',
|
||||
'Eclipse',
|
||||
'Solstice',
|
||||
'Equinox',
|
||||
'Horizon',
|
||||
'Zenith',
|
||||
'Castle',
|
||||
'Tower',
|
||||
'Bridge',
|
||||
'Garden',
|
||||
'Fountain',
|
||||
'Palace',
|
||||
'Temple',
|
||||
'Cathedral',
|
||||
'Lighthouse',
|
||||
'Windmill',
|
||||
'Waterfall',
|
||||
'Canyon',
|
||||
'Valley',
|
||||
'Peak',
|
||||
'Ridge',
|
||||
'Cliff',
|
||||
'Ocean',
|
||||
'River',
|
||||
'Lake',
|
||||
'Stream',
|
||||
'Pond',
|
||||
'Bay',
|
||||
'Cove',
|
||||
'Harbor',
|
||||
'Island',
|
||||
'Peninsula',
|
||||
'Archipelago',
|
||||
'Atoll',
|
||||
'Reef',
|
||||
'Lagoon',
|
||||
'Fjord',
|
||||
'Delta',
|
||||
'Cake',
|
||||
'Cookie',
|
||||
'Muffin',
|
||||
'Cupcake',
|
||||
'Pie',
|
||||
'Tart',
|
||||
'Brownie',
|
||||
'Donut',
|
||||
'Pancake',
|
||||
'Waffle',
|
||||
'Croissant',
|
||||
'Bagel',
|
||||
'Pretzel',
|
||||
'Biscuit',
|
||||
'Scone',
|
||||
'Crumpet',
|
||||
]
|
||||
|
||||
/**
|
||||
* Generates the next incremental name for entities following pattern: "{prefix} {number}"
|
||||
*
|
||||
* @param existingEntities - Array of entities with name property
|
||||
* @param prefix - Prefix for the name (e.g., "Workspace", "Folder", "Subfolder")
|
||||
* @returns Next available name (e.g., "Workspace 3")
|
||||
*/
|
||||
export function generateIncrementalName<T extends NameableEntity>(
|
||||
existingEntities: T[],
|
||||
prefix: string
|
||||
): string {
|
||||
// Create regex pattern for the prefix (e.g., /^Workspace (\d+)$/)
|
||||
const pattern = new RegExp(`^${prefix.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')} (\\d+)$`)
|
||||
|
||||
// Extract numbers from existing entities that match the pattern
|
||||
const existingNumbers = existingEntities
|
||||
.map((entity) => entity.name.match(pattern))
|
||||
.filter((match) => match !== null)
|
||||
.map((match) => Number.parseInt(match![1], 10))
|
||||
|
||||
// Find next available number (highest + 1, or 1 if none exist)
|
||||
const nextNumber = existingNumbers.length > 0 ? Math.max(...existingNumbers) + 1 : 1
|
||||
|
||||
return `${prefix} ${nextNumber}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the next workspace name
|
||||
*/
|
||||
export async function generateWorkspaceName(): Promise<string> {
|
||||
const response = await fetch('/api/workspaces')
|
||||
const data = (await response.json()) as WorkspacesApiResponse
|
||||
const workspaces = data.workspaces || []
|
||||
|
||||
return generateIncrementalName(workspaces, 'Workspace')
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the next folder name for a workspace
|
||||
*/
|
||||
export async function generateFolderName(workspaceId: string): Promise<string> {
|
||||
const response = await fetch(`/api/folders?workspaceId=${workspaceId}`)
|
||||
const data = (await response.json()) as FoldersApiResponse
|
||||
const folders = data.folders || []
|
||||
|
||||
// Filter to only root-level folders (parentId is null)
|
||||
const rootFolders = folders.filter((folder) => folder.parentId === null)
|
||||
|
||||
return generateIncrementalName(rootFolders, 'Folder')
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the next subfolder name for a parent folder
|
||||
*/
|
||||
export async function generateSubfolderName(
|
||||
workspaceId: string,
|
||||
parentFolderId: string
|
||||
): Promise<string> {
|
||||
const response = await fetch(`/api/folders?workspaceId=${workspaceId}`)
|
||||
const data = (await response.json()) as FoldersApiResponse
|
||||
const folders = data.folders || []
|
||||
|
||||
// Filter to only subfolders of the specified parent
|
||||
const subfolders = folders.filter((folder) => folder.parentId === parentFolderId)
|
||||
|
||||
return generateIncrementalName(subfolders, 'Subfolder')
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a creative workflow name using random adjectives and nouns
|
||||
* @returns A creative workflow name like "blazing-phoenix" or "crystal-dragon"
|
||||
*/
|
||||
export function generateCreativeWorkflowName(): string {
|
||||
const adjective = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)]
|
||||
const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)]
|
||||
return `${adjective.toLowerCase()}-${noun.toLowerCase()}`
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import { env } from '@/lib/env'
|
||||
import { getEnv } from '@/lib/env'
|
||||
import { isProd } from '@/lib/environment'
|
||||
|
||||
/**
|
||||
@@ -10,7 +10,7 @@ export function getBaseUrl(): string {
|
||||
return window.location.origin
|
||||
}
|
||||
|
||||
const baseUrl = env.NEXT_PUBLIC_APP_URL
|
||||
const baseUrl = getEnv('NEXT_PUBLIC_APP_URL')
|
||||
if (baseUrl) {
|
||||
if (baseUrl.startsWith('http://') || baseUrl.startsWith('https://')) {
|
||||
return baseUrl
|
||||
@@ -32,7 +32,7 @@ export function getBaseDomain(): string {
|
||||
const url = new URL(getBaseUrl())
|
||||
return url.host // host includes port if specified
|
||||
} catch (_e) {
|
||||
const fallbackUrl = env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
|
||||
const fallbackUrl = getEnv('NEXT_PUBLIC_APP_URL') || 'http://localhost:3000'
|
||||
try {
|
||||
return new URL(fallbackUrl).host
|
||||
} catch {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { eq } from 'drizzle-orm'
|
||||
import { NextResponse } from 'next/server'
|
||||
import { env } from '@/lib/env'
|
||||
import { getEnv } from '@/lib/env'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { db } from '@/db'
|
||||
import { userStats, workflow as workflowTable } from '@/db/schema'
|
||||
@@ -24,7 +24,7 @@ export async function updateWorkflowRunCounts(workflowId: string, runs = 1) {
|
||||
|
||||
// Get the origin from the environment or use direct DB update as fallback
|
||||
const origin =
|
||||
env.NEXT_PUBLIC_APP_URL || (typeof window !== 'undefined' ? window.location.origin : '')
|
||||
getEnv('NEXT_PUBLIC_APP_URL') || (typeof window !== 'undefined' ? window.location.origin : '')
|
||||
|
||||
if (origin) {
|
||||
// Use absolute URL with origin
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { create } from 'zustand'
|
||||
import { devtools } from 'zustand/middleware'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { generateCreativeWorkflowName } from '@/lib/naming'
|
||||
import { clearWorkflowVariablesTracking } from '@/stores/panel/variables/store'
|
||||
import { API_ENDPOINTS } from '../../constants'
|
||||
import { useSubBlockStore } from '../subblock/store'
|
||||
import { useWorkflowStore } from '../workflow/store'
|
||||
import type { BlockState } from '../workflow/types'
|
||||
import type { DeploymentStatus, WorkflowMetadata, WorkflowRegistry } from './types'
|
||||
import { generateUniqueName, getNextWorkflowColor } from './utils'
|
||||
import { getNextWorkflowColor } from './utils'
|
||||
|
||||
const logger = createLogger('WorkflowRegistry')
|
||||
|
||||
@@ -568,7 +569,7 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
name: options.name || generateUniqueName(),
|
||||
name: options.name || generateCreativeWorkflowName(),
|
||||
description: options.description || 'New workflow',
|
||||
color: options.marketplaceId ? '#808080' : getNextWorkflowColor(),
|
||||
workspaceId,
|
||||
@@ -829,7 +830,7 @@ export const useWorkflowRegistry = create<WorkflowRegistry>()(
|
||||
// Generate workflow metadata with marketplace properties
|
||||
const newWorkflow: WorkflowMetadata = {
|
||||
id,
|
||||
name: metadata.name || generateUniqueName(),
|
||||
name: metadata.name || generateCreativeWorkflowName(),
|
||||
lastModified: new Date(),
|
||||
description: metadata.description || 'Imported from marketplace',
|
||||
color: metadata.color || getNextWorkflowColor(),
|
||||
|
||||
@@ -71,156 +71,6 @@ export const WORKFLOW_COLORS = [
|
||||
'#DF9F00', // Dark Amber
|
||||
]
|
||||
|
||||
// Random adjectives and nouns for generating creative workflow names
|
||||
const ADJECTIVES = [
|
||||
'Blazing',
|
||||
'Crystal',
|
||||
'Golden',
|
||||
'Silver',
|
||||
'Mystic',
|
||||
'Cosmic',
|
||||
'Electric',
|
||||
'Frozen',
|
||||
'Burning',
|
||||
'Shining',
|
||||
'Dancing',
|
||||
'Flying',
|
||||
'Roaring',
|
||||
'Whispering',
|
||||
'Glowing',
|
||||
'Sparkling',
|
||||
'Thunder',
|
||||
'Lightning',
|
||||
'Storm',
|
||||
'Ocean',
|
||||
'Mountain',
|
||||
'Forest',
|
||||
'Desert',
|
||||
'Arctic',
|
||||
'Tropical',
|
||||
'Midnight',
|
||||
'Dawn',
|
||||
'Sunset',
|
||||
'Rainbow',
|
||||
'Diamond',
|
||||
'Ruby',
|
||||
'Emerald',
|
||||
'Sapphire',
|
||||
'Pearl',
|
||||
'Jade',
|
||||
'Amber',
|
||||
'Coral',
|
||||
'Ivory',
|
||||
'Obsidian',
|
||||
'Marble',
|
||||
'Velvet',
|
||||
'Silk',
|
||||
'Satin',
|
||||
'Linen',
|
||||
'Cotton',
|
||||
'Wool',
|
||||
'Cashmere',
|
||||
'Denim',
|
||||
'Neon',
|
||||
'Pastel',
|
||||
'Vibrant',
|
||||
'Muted',
|
||||
'Bold',
|
||||
'Subtle',
|
||||
'Bright',
|
||||
'Dark',
|
||||
]
|
||||
|
||||
const NOUNS = [
|
||||
'Phoenix',
|
||||
'Dragon',
|
||||
'Eagle',
|
||||
'Wolf',
|
||||
'Lion',
|
||||
'Tiger',
|
||||
'Panther',
|
||||
'Falcon',
|
||||
'Hawk',
|
||||
'Raven',
|
||||
'Swan',
|
||||
'Dove',
|
||||
'Butterfly',
|
||||
'Firefly',
|
||||
'Dragonfly',
|
||||
'Hummingbird',
|
||||
'Galaxy',
|
||||
'Nebula',
|
||||
'Comet',
|
||||
'Meteor',
|
||||
'Star',
|
||||
'Moon',
|
||||
'Sun',
|
||||
'Planet',
|
||||
'Asteroid',
|
||||
'Constellation',
|
||||
'Aurora',
|
||||
'Eclipse',
|
||||
'Solstice',
|
||||
'Equinox',
|
||||
'Horizon',
|
||||
'Zenith',
|
||||
'Castle',
|
||||
'Tower',
|
||||
'Bridge',
|
||||
'Garden',
|
||||
'Fountain',
|
||||
'Palace',
|
||||
'Temple',
|
||||
'Cathedral',
|
||||
'Lighthouse',
|
||||
'Windmill',
|
||||
'Waterfall',
|
||||
'Canyon',
|
||||
'Valley',
|
||||
'Peak',
|
||||
'Ridge',
|
||||
'Cliff',
|
||||
'Ocean',
|
||||
'River',
|
||||
'Lake',
|
||||
'Stream',
|
||||
'Pond',
|
||||
'Bay',
|
||||
'Cove',
|
||||
'Harbor',
|
||||
'Island',
|
||||
'Peninsula',
|
||||
'Archipelago',
|
||||
'Atoll',
|
||||
'Reef',
|
||||
'Lagoon',
|
||||
'Fjord',
|
||||
'Delta',
|
||||
'Cake',
|
||||
'Cookie',
|
||||
'Muffin',
|
||||
'Cupcake',
|
||||
'Pie',
|
||||
'Tart',
|
||||
'Brownie',
|
||||
'Donut',
|
||||
'Pancake',
|
||||
'Waffle',
|
||||
'Croissant',
|
||||
'Bagel',
|
||||
'Pretzel',
|
||||
'Biscuit',
|
||||
'Scone',
|
||||
'Crumpet',
|
||||
]
|
||||
|
||||
// Generates a random name for a new workflow
|
||||
export function generateUniqueName(): string {
|
||||
const adjective = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)]
|
||||
const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)]
|
||||
return `${adjective.toLowerCase()}-${noun.toLowerCase()}`
|
||||
}
|
||||
|
||||
// Generates a random color for a new workflow
|
||||
export function getNextWorkflowColor(): string {
|
||||
// Simply return a random color from the available colors
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { env } from '@/lib/env'
|
||||
import { getEnv } from '@/lib/env'
|
||||
import { isTest } from '@/lib/environment'
|
||||
import { createLogger } from '@/lib/logs/console-logger'
|
||||
import { getBaseUrl } from '@/lib/urls/utils'
|
||||
@@ -15,7 +15,7 @@ const getReferer = (): string => {
|
||||
try {
|
||||
return getBaseUrl()
|
||||
} catch (_error) {
|
||||
return env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000'
|
||||
return getEnv('NEXT_PUBLIC_APP_URL') || 'http://localhost:3000'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user