Revert "Feat/sql editor support bulk deletes" (#21003)

* Revert "Feat/sql editor support bulk deletes (#20927)"

This reverts commit 27c2cff395.

* ci: Autofix updates from GitHub workflow

* Lint

---------

Co-authored-by: github-tidy-bot <github-tidy-bot@supabase.com>
This commit is contained in:
Joshen Limandgithub-tidy-bot authored and GitHub committed 2024-02-05 14:01:24 +11:00
1 parent 27c2cff395
commit 30245c9490
17 files changed
+581 -686

No files matched your search

+5 -5
View File
@@ -15,15 +15,15 @@
"incremental": true,
"plugins": [
{
"name": "next",
},
"name": "next"
}
],
"paths": {
"@/*": ["./*"],
// handle ui package paths
"@ui/*": ["./../../packages/ui/src/*"],
},
"@ui/*": ["./../../packages/ui/src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"],
"exclude": ["node_modules"]
}
+3 -3
View File
@@ -5,9 +5,9 @@
"noImplicitAny": false,
"baseUrl": ".",
"paths": {
"~/*": ["./*"],
},
"~/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"],
"exclude": ["node_modules"]
}
+3 -3
View File
@@ -7,9 +7,9 @@
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"~/*": ["./*"],
},
"~/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"],
"exclude": ["node_modules"]
}
+3 -3
View File
@@ -7,9 +7,9 @@
"resolveJsonModule": true,
"baseUrl": ".",
"paths": {
"~/*": ["./*"],
},
"~/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"],
"exclude": ["node_modules"]
}
+4 -4
View File
@@ -5,7 +5,7 @@
"baseUrl": ".",
"paths": {
"~/*": ["./*"],
"@ui/*": ["./../../packages/ui/src/*"], // handle ui package paths
"@ui/*": ["./../../packages/ui/src/*"] // handle ui package paths
},
"target": "es2015",
"lib": ["dom", "dom.iterable", "esnext"],
@@ -19,14 +19,14 @@
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"module": "esnext",
"module": "esnext"
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"pages/guides/append-test.js",
"jest.config.mjs",
"jest.config.mjs"
],
"exclude": ["node_modules"],
"exclude": ["node_modules"]
}
@@ -44,7 +44,7 @@ const SavedQueriesItem = ({ item }: SavedQueriesItemProps) => {
console.error('Invalid project reference')
return
}
await deleteContent({ projectRef: ref, ids: [item.id] })
await deleteContent({ projectRef: ref, id: item.id })
setShowConfirmModal(false)
toast.success('Query deleted')
} catch (error) {
@@ -9,8 +9,8 @@ const ProductMenuBar = ({ title, children }: PropsWithChildren<ProductMenuBarPro
<div
className={[
'hide-scrollbar flex w-64 flex-col border-r', // Layout
'bg-background',
'border-default ',
'bg-background',
].join(' ')}
>
<div
@@ -19,7 +19,9 @@ const ProductMenuBar = ({ title, children }: PropsWithChildren<ProductMenuBarPro
>
<h4 className="text-lg">{title}</h4>
</div>
<div className="flex-grow overflow-y-auto">{children}</div>
<div className="flex-grow overflow-y-auto" style={{ maxHeight: 'calc(100vh - 96px)' }}>
{children}
</div>
</div>
)
}
@@ -1,19 +1,29 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import clsx from 'clsx'
import { useParams } from 'common'
import { noop } from 'lodash'
import { observer } from 'mobx-react-lite'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect, useRef, useState } from 'react'
import toast from 'react-hot-toast'
import {
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Alert_Shadcn_,
Checkbox,
Checkbox_Shadcn_,
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
IconAlertCircle,
IconAlertTriangle,
IconChevronDown,
IconCopy,
IconDownload,
IconEdit2,
IconEye,
IconShare,
IconTrash,
IconUnlock,
Modal,
TooltipContent_Shadcn_,
@@ -23,66 +33,28 @@ import {
import DownloadSnippetModal from 'components/interfaces/SQLEditor/DownloadSnippetModal'
import RenameQueryModal from 'components/interfaces/SQLEditor/RenameQueryModal'
import { createSqlSnippetSkeleton } from 'components/interfaces/SQLEditor/SQLEditor.utils'
import SimpleCodeBlock from 'components/to-be-cleaned/SimpleCodeBlock'
import ConfirmationModal from 'components/ui/ConfirmationModal'
import CopyButton from 'components/ui/CopyButton'
import { useContentDeleteMutation } from 'data/content/content-delete-mutation'
import { SqlSnippet } from 'data/content/sql-snippets-query'
import { useCheckPermissions, useSelectedProject, useStore } from 'hooks'
import { IS_PLATFORM } from 'lib/constants'
import { uuidv4 } from 'lib/helpers'
import { useProfile } from 'lib/profile'
import { useSqlEditorStateSnapshot } from 'state/sql-editor'
import { QueryItemActions } from './QueryItemActions'
export interface QueryItemProps {
tabInfo: SqlSnippet
isSelected?: boolean
hasQueriesSelected?: boolean
onSelectQuery?: (isShiftHeld: boolean) => void
onDeleteQuery?: (ids: string[]) => void
}
const QueryItem = ({
tabInfo,
isSelected = false,
hasQueriesSelected = false,
onSelectQuery = noop,
onDeleteQuery = noop,
}: QueryItemProps) => {
const [open, setOpen] = useState(false)
const [renameModalOpen, setRenameModalOpen] = useState(false)
const [shareModalOpen, setShareModalOpen] = useState(false)
const [deleteModalOpen, setDeleteModalOpen] = useState(false)
const [isDownloadSnippetModalOpen, setIsDownloadSnippetModalOpen] = useState(false)
const QueryItem = ({ tabInfo }: QueryItemProps) => {
const { ref, id: activeId } = useParams()
const snap = useSqlEditorStateSnapshot()
const { id, name, description, content, visibility } = tabInfo || {}
const { id, name, description, content } = tabInfo || {}
const isActive = id === activeId
const activeItemRef = useRef<HTMLElement | null>(null)
const hideTooltip =
open ||
isActive ||
renameModalOpen ||
shareModalOpen ||
deleteModalOpen ||
isDownloadSnippetModalOpen
const { mutate: deleteContent, isLoading: isDeleting } = useContentDeleteMutation({
onSuccess: (data) => onDeleteQuery(data),
onError: (error, data) => {
if (error.code === 404 && error.message.includes('Content not found')) {
onDeleteQuery(data.ids)
} else {
toast.error(`Failed to delete query: ${error.message}`)
}
},
})
const onConfirmDelete = async () => {
if (!ref) return console.error('Project ref is required')
if (!id) return console.error('Snippet ID is required')
deleteContent({ projectRef: ref, ids: [id] })
}
useEffect(() => {
// scroll to active item
if (isActive && activeItemRef.current) {
@@ -93,113 +65,262 @@ const QueryItem = ({
}
})
return (
<Tooltip_Shadcn_ delayDuration={100}>
<TooltipTrigger_Shadcn_ asChild>
<div
key={id}
className={clsx(
'flex items-center justify-between rounded-md group',
isActive ? 'bg-surface-300' : 'hover:bg-surface-200'
)}
ref={isActive ? (activeItemRef as React.RefObject<HTMLDivElement>) : null}
>
<Link href={`/project/${ref}/sql/${id}`} className="py-1 px-3 w-full overflow-hidden">
<p
title={description || name}
className={clsx(
isActive
? 'text-foreground'
: 'text-foreground-light group-hover:text-foreground/80',
'text-sm transition overflow-hidden text-ellipsis'
)}
>
{name}
</p>
</Link>
<div className="pr-1">{<QueryItemActions tabInfo={tabInfo} activeId={activeId} />}</div>
</div>
</TooltipTrigger_Shadcn_>
{!isActive && (
<TooltipContent_Shadcn_
side="right"
align="start"
className="w-96 flex flex-col gap-y-2 py-3 -translate-y-[4px]"
>
<p className="text-xs">Query preview:</p>
<div className="bg-surface-300 py-2 px-3 rounded relative">
{content.sql.trim() ? (
<SimpleCodeBlock
showCopy={false}
className="sql"
parentClassName="!p-0 [&>div>span]:text-xs [&>div>span]:tracking-tighter"
>
{content.sql.replaceAll('\n', ' ').replaceAll(/\s+/g, ' ').slice(0, 43) +
`${content.sql.length > 43 ? '...' : ''}`}
</SimpleCodeBlock>
) : (
<p className="text-xs text-foreground-lighter">This query is empty</p>
)}
{content.sql.trim() && (
<CopyButton
iconOnly
type="default"
className="px-1 absolute top-1.5 right-1.5"
text={content.sql}
/>
)}
</div>
</TooltipContent_Shadcn_>
)}
</Tooltip_Shadcn_>
)
}
export default QueryItem
interface QueryItemActionsProps {
tabInfo: SqlSnippet
activeId: string | undefined
}
const QueryItemActions = observer(({ tabInfo, activeId }: QueryItemActionsProps) => {
const { ui } = useStore()
const { ref } = useParams()
const router = useRouter()
const { profile } = useProfile()
const snap = useSqlEditorStateSnapshot()
const project = useSelectedProject()
const { id: snippetID } = tabInfo || {}
const snippet =
snippetID !== undefined && snap.snippets && snap.snippets[snippetID] !== undefined
? snap.snippets[snippetID]
: null
const isSnippetOwner = profile?.id === snippet?.snippet.owner_id
const { mutate: deleteContent, isLoading: isDeleting } = useContentDeleteMutation({
onSuccess(data) {
if (data.id) snap.removeSnippet(data.id)
const existingSnippetIds = (snap.orders[ref!] ?? []).filter((x) => x !== id)
if (existingSnippetIds.length === 0) {
router.push(`/project/${ref}/sql/new`)
} else {
router.push(`/project/${ref}/sql/${existingSnippetIds[0]}`)
}
},
onError(error, data) {
if (error.code === 404 && error.message.includes('Content not found')) {
if (data.id) snap.removeSnippet(data.id)
const existingSnippetIds = (snap.orders[ref!] ?? []).filter((x) => x !== id)
if (existingSnippetIds.length === 0) {
router.push(`/project/${ref}/sql/new`)
} else {
router.push(`/project/${ref}/sql/${existingSnippetIds[0]}`)
}
} else {
ui.setNotification({
category: 'error',
message: `Failed to delete query: ${error.message}`,
})
}
},
})
const { id, name, visibility, content } = tabInfo || {}
const [renameModalOpen, setRenameModalOpen] = useState(false)
const [shareModalOpen, setShareModalOpen] = useState(false)
const [deleteModalOpen, setDeleteModalOpen] = useState(false)
const [isDownloadSnippetModalOpen, setIsDownloadSnippetModalOpen] = useState(false)
const isActive = id === activeId
const canCreateSQLSnippet = useCheckPermissions(PermissionAction.CREATE, 'user_content', {
resource: { type: 'sql', owner_id: profile?.id },
subject: { id: profile?.id },
})
const onCloseRenameModal = () => {
setRenameModalOpen(false)
}
const onClickRename = (e: any) => {
e.stopPropagation()
setRenameModalOpen(true)
}
const onClickShare = (e: any) => {
e.stopPropagation()
setShareModalOpen(true)
}
const onClickDelete = (e: any) => {
e.stopPropagation()
setDeleteModalOpen(true)
}
const onConfirmShare = async () => {
if (id) {
try {
snap.shareSnippet(id, 'project')
return Promise.resolve()
} catch (error: any) {
toast.error(`Failed to share query: ${error.message}`)
ui.setNotification({
error,
category: 'error',
message: `Failed to share query: ${error.message}`,
})
}
}
}
const onConfirmDelete = async () => {
if (!ref) return console.error('Project ref is required')
if (!id) return console.error('Snippet ID is required')
deleteContent({ projectRef: ref, id })
}
const createPersonalCopy = async () => {
if (!ref) return console.error('Project ref is required')
if (!id) return console.error('Snippet ID is required')
try {
const snippet = createSqlSnippetSkeleton({
id: uuidv4(),
name,
sql: content.sql,
owner_id: profile?.id,
project_id: project?.id,
})
snap.addSnippet(snippet as SqlSnippet, ref)
snap.addNeedsSaving(snippet.id!)
router.push(`/project/${ref}/sql/${snippet.id}`)
} catch (error: any) {
ui.setNotification({
category: 'error',
message: `Failed to create a personal copy of this query: ${error.message}`,
})
}
}
return (
<>
<Tooltip_Shadcn_ delayDuration={100}>
<TooltipTrigger_Shadcn_ asChild>
<div
key={id}
className={clsx(
'h-7 pl-3 pr-2',
'flex items-center justify-between rounded-md group relative',
isActive ? 'bg-surface-300' : 'hover:bg-surface-200'
<div className="group [div&>button[data-state='open']>span]:text-foreground-lighter flex items-center">
{IS_PLATFORM ? (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<span
className={clsx(
'rounded p-1',
isActive
? 'text-foreground-light hover:bg-border-stronger'
: 'text-background hover:bg-overlay-hover group-hover:text-foreground-light'
)}
>
<IconChevronDown size="tiny" strokeWidth={2} />
</span>
</DropdownMenuTrigger>
<DropdownMenuContent side="bottom" align="end" className=" w-52 translate-x-[4px]">
{isSnippetOwner && (
<DropdownMenuItem onClick={onClickRename} className="space-x-2">
<IconEdit2 size="tiny" />
<p>Rename query</p>
</DropdownMenuItem>
)}
ref={isActive ? (activeItemRef as React.RefObject<HTMLDivElement>) : null}
>
{visibility === 'user' && (
<Checkbox
className={clsx(
'transition absolute left-2.5 [&>input]:border-foreground-lighter',
hasQueriesSelected ? '' : 'opacity-0 group-hover:opacity-100'
)}
checked={isSelected}
onChange={(event) => {
onSelectQuery((event.nativeEvent as KeyboardEvent).shiftKey)
}}
/>
{visibility === 'user' && canCreateSQLSnippet && (
<DropdownMenuItem onClick={onClickShare} className="space-x-2">
<IconShare size="tiny" />
<p>Share query</p>
</DropdownMenuItem>
)}
<div className="flex items-center justify-between w-full gap-x-2">
<Link
title={description || name}
href={`/project/${ref}/sql/${id}`}
className={clsx(
'w-full overflow-hidden truncate',
isActive
? 'text-foreground'
: 'text-foreground-light group-hover:text-foreground/80',
'text-sm transition-all overflow-hidden text-ellipsis',
hasQueriesSelected && visibility === 'user'
? 'ml-5'
: visibility === 'user'
? 'group-hover:ml-5'
: ''
)}
{visibility === 'project' && canCreateSQLSnippet && (
<DropdownMenuItem onClick={createPersonalCopy} className="space-x-2">
<IconCopy size="tiny" />
<p>Duplicate personal copy</p>
</DropdownMenuItem>
)}
{IS_PLATFORM && (
<DropdownMenuItem
onClick={() => setIsDownloadSnippetModalOpen(!isDownloadSnippetModalOpen)}
className="space-x-2"
>
{name}
</Link>
{!hasQueriesSelected && (
<QueryItemActions
tabInfo={tabInfo}
activeId={activeId}
open={open}
setOpen={setOpen}
onSelectDeleteQuery={() => setDeleteModalOpen(true)}
onSelectRenameQuery={() => setRenameModalOpen(true)}
onSelectShareQuery={() => setShareModalOpen(true)}
onSelectDownloadQuery={() => setIsDownloadSnippetModalOpen(true)}
/>
)}
</div>
</div>
</TooltipTrigger_Shadcn_>
{!hideTooltip && (
<TooltipContent_Shadcn_
side="right"
align="start"
className="w-96 flex flex-col gap-y-2 py-3 -translate-y-[4px]"
>
<p className="text-xs">Query preview:</p>
<div className="bg-surface-300 py-2 px-3 rounded relative">
{content.sql.trim() ? (
<SimpleCodeBlock
showCopy={false}
className="sql"
parentClassName="!p-0 [&>div>span]:text-xs [&>div>span]:tracking-tighter"
>
{content.sql.replaceAll('\n', ' ').replaceAll(/\s+/g, ' ').slice(0, 43) +
`${content.sql.length > 43 ? '...' : ''}`}
</SimpleCodeBlock>
) : (
<p className="text-xs text-foreground-lighter">This query is empty</p>
)}
{content.sql.trim() && (
<CopyButton
iconOnly
type="default"
className="px-1 absolute top-1.5 right-1.5"
text={content.sql}
/>
)}
</div>
</TooltipContent_Shadcn_>
)}
</Tooltip_Shadcn_>
<IconDownload size="tiny" />
<p>Download as migration file</p>
</DropdownMenuItem>
)}
{isSnippetOwner && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={onClickDelete} className="space-x-2">
<IconTrash size="tiny" />
<p>Delete query</p>
</DropdownMenuItem>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
) : (
<Button asChild disabled type="text" style={{ padding: '3px' }}>
<span></span>
</Button>
)}
<RenameQueryModal
snippet={tabInfo}
visible={renameModalOpen}
onCancel={() => setRenameModalOpen(false)}
onComplete={() => setRenameModalOpen(false)}
onCancel={onCloseRenameModal}
onComplete={onCloseRenameModal}
/>
<ConfirmationModal
header="Confirm to delete query"
@@ -272,8 +393,6 @@ const QueryItem = ({
visible={isDownloadSnippetModalOpen}
onCancel={() => setIsDownloadSnippetModalOpen(false)}
/>
</>
</div>
)
}
export default QueryItem
})
@@ -1,164 +0,0 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
import clsx from 'clsx'
import { IS_PLATFORM, useParams } from 'common'
import { useRouter } from 'next/router'
import {
Button,
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
IconChevronDown,
IconCopy,
IconDownload,
IconEdit2,
IconShare,
IconTrash,
} from 'ui'
import { createSqlSnippetSkeleton } from 'components/interfaces/SQLEditor/SQLEditor.utils'
import { SqlSnippet } from 'data/content/sql-snippets-query'
import { useCheckPermissions, useSelectedProject, useStore } from 'hooks'
import { uuidv4 } from 'lib/helpers'
import { useProfile } from 'lib/profile'
import { useSqlEditorStateSnapshot } from 'state/sql-editor'
interface QueryItemActionsProps {
tabInfo: SqlSnippet
activeId: string | undefined
open: boolean
setOpen: (value: boolean) => void
onSelectRenameQuery: () => void
onSelectDeleteQuery: () => void
onSelectShareQuery: () => void
onSelectDownloadQuery: () => void
}
export const QueryItemActions = ({
tabInfo,
activeId,
open,
setOpen,
onSelectRenameQuery,
onSelectDeleteQuery,
onSelectShareQuery,
onSelectDownloadQuery,
}: QueryItemActionsProps) => {
const { ui } = useStore()
const { ref } = useParams()
const router = useRouter()
const { profile } = useProfile()
const snap = useSqlEditorStateSnapshot()
const project = useSelectedProject()
const { id, name, visibility, content } = tabInfo || {}
const canCreateSQLSnippet = useCheckPermissions(PermissionAction.CREATE, 'user_content', {
resource: { type: 'sql', owner_id: profile?.id },
subject: { id: profile?.id },
})
const { id: snippetID } = tabInfo || {}
const snippet =
snippetID !== undefined && snap.snippets && snap.snippets[snippetID] !== undefined
? snap.snippets[snippetID]
: null
const isSnippetOwner = profile?.id === snippet?.snippet.owner_id
const onClickRename = (e: any) => {
e.stopPropagation()
onSelectRenameQuery()
}
const onClickShare = (e: any) => {
e.stopPropagation()
onSelectShareQuery()
}
const onClickDelete = (e: any) => {
e.stopPropagation()
onSelectDeleteQuery()
}
const createPersonalCopy = async () => {
if (!ref) return console.error('Project ref is required')
if (!id) return console.error('Snippet ID is required')
try {
const snippet = createSqlSnippetSkeleton({
id: uuidv4(),
name,
sql: content.sql,
owner_id: profile?.id,
project_id: project?.id,
})
snap.addSnippet(snippet as SqlSnippet, ref)
snap.addNeedsSaving(snippet.id!)
router.push(`/project/${ref}/sql/${snippet.id}`)
} catch (error: any) {
ui.setNotification({
category: 'error',
message: `Failed to create a personal copy of this query: ${error.message}`,
})
}
}
return (
<div className="group [div&>button[data-state='open']>span]:text-foreground-lighter flex items-center">
{IS_PLATFORM ? (
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger asChild>
<Button
type="text"
className="px-1 opacity-50 hover:opacity-100"
icon={<IconChevronDown size="tiny" strokeWidth={2} />}
/>
</DropdownMenuTrigger>
<DropdownMenuContent side="bottom" align="end" className=" w-52 translate-x-2">
{isSnippetOwner && (
<DropdownMenuItem onClick={onClickRename} className="space-x-2">
<IconEdit2 size="tiny" />
<p>Rename query</p>
</DropdownMenuItem>
)}
{visibility === 'user' && canCreateSQLSnippet && (
<DropdownMenuItem onClick={onClickShare} className="space-x-2">
<IconShare size="tiny" />
<p>Share query</p>
</DropdownMenuItem>
)}
{visibility === 'project' && canCreateSQLSnippet && (
<DropdownMenuItem onClick={createPersonalCopy} className="space-x-2">
<IconCopy size="tiny" />
<p>Duplicate personal copy</p>
</DropdownMenuItem>
)}
{IS_PLATFORM && (
<DropdownMenuItem onClick={() => onSelectDownloadQuery()} className="space-x-2">
<IconDownload size="tiny" />
<p>Download as migration file</p>
</DropdownMenuItem>
)}
{isSnippetOwner && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={onClickDelete} className="space-x-2">
<IconTrash size="tiny" />
<p>Delete query</p>
</DropdownMenuItem>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
) : (
<Button asChild disabled type="text" style={{ padding: '3px' }}>
<span />
</Button>
)}
</div>
)
}
@@ -1,23 +0,0 @@
import { uniq } from 'lodash'
export const selectItemsInRange = (id: string, snippets: any[], selectedSnippets: string[]) => {
const lastSelectedItemIndex = snippets.findIndex(
(x) => x.id === selectedSnippets[selectedSnippets.length - 1]
)
const selectedItemIndex = snippets.findIndex((x) => x.id === id)
// Get the start and end index of the range to select
const start = Math.min(selectedItemIndex, lastSelectedItemIndex)
const end = Math.max(selectedItemIndex, lastSelectedItemIndex)
// Get the range to select and reverse the order if necessary
const rangeToSelect = snippets.slice(start, end + 1)
if (selectedItemIndex < lastSelectedItemIndex) rangeToSelect.reverse()
if (selectedSnippets.includes(id)) {
const rangeToDeselectIds = rangeToSelect.map((item) => item.id)
return selectedSnippets.filter((x) => x === id || !rangeToDeselectIds.includes(x))
} else {
return uniq(selectedSnippets.concat(rangeToSelect.map((x) => x.id) as string[]))
}
}
@@ -3,50 +3,32 @@ import { useParams } from 'common'
import { observer } from 'mobx-react-lite'
import { useRouter } from 'next/router'
import { useMemo, useState } from 'react'
import toast from 'react-hot-toast'
import {
AlertDescription_Shadcn_,
AlertTitle_Shadcn_,
Alert_Shadcn_,
Button,
IconPlus,
IconSearch,
IconX,
Input,
Menu,
Modal,
Separator,
TooltipContent_Shadcn_,
TooltipTrigger_Shadcn_,
Tooltip_Shadcn_,
} from 'ui'
import { Button, IconPlus, IconSearch, IconX, Input, Menu, cn } from 'ui'
import { untitledSnippetTitle } from 'components/interfaces/SQLEditor/SQLEditor.constants'
import { createSqlSnippetSkeleton } from 'components/interfaces/SQLEditor/SQLEditor.utils'
import ConfirmationModal from 'components/ui/ConfirmationModal'
import { WarningIcon } from 'components/ui/Icons'
import ProductMenuItem from 'components/ui/ProductMenu/ProductMenuItem'
import ShimmeringLoader from 'components/ui/ShimmeringLoader'
import { useContentDeleteMutation } from 'data/content/content-delete-mutation'
import { SqlSnippet, useSqlSnippetsQuery } from 'data/content/sql-snippets-query'
import { useCheckPermissions, useSelectedProject, useStore } from 'hooks'
import { uuidv4 } from 'lib/helpers'
import { useProfile } from 'lib/profile'
import { useSnippets, useSqlEditorStateSnapshot } from 'state/sql-editor'
import QueryItem from './QueryItem'
import { partition, uniq, uniqBy } from 'lodash'
import { selectItemsInRange } from './SQLEditorLayout.utils'
const SideBarContent = observer(() => {
const { ui } = useStore()
const { ref, id: activeId } = useParams()
const { ref } = useParams()
const router = useRouter()
const { profile } = useProfile()
const project = useSelectedProject()
const [searchText, setSearchText] = useState('')
const [showDeleteModal, setShowDeleteModal] = useState(false)
const [selectedQueries, setSelectedQueries] = useState<string[]>([])
const [personalSnippetsFilterString, setPersonalSnippetsFilterString] = useState('')
const [projectSnippetsFilterString, setProjectSnippetsFilterString] = useState('')
const [favoritesFilterString, setFavoritesFilterString] = useState('')
const [isPersonalSnippetsFilterOpen, setIsPersonalSnippetsFilterOpen] = useState(false)
const [isProjectSnippetsFilterOpen, setIsProjectSnippetsFilterOpen] = useState(false)
const [isFavoritesFilterOpen, setIsFavoritesFilterOpen] = useState(false)
const snap = useSqlEditorStateSnapshot()
const { isLoading, isSuccess } = useSqlSnippetsQuery(ref, {
@@ -57,29 +39,6 @@ const SideBarContent = observer(() => {
},
})
const { mutate: deleteContent, isLoading: isDeleting } = useContentDeleteMutation({
onSuccess: (data) => postDeleteCleanup(data),
onError: (error, data) => {
if (error.code === 404 && error.message.includes('Content not found')) {
postDeleteCleanup(data.ids)
} else {
toast.error(`Failed to delete queries: ${error.message}`)
}
},
})
const postDeleteCleanup = (ids: string[]) => {
setShowDeleteModal(false)
setSelectedQueries([])
if (ids.length > 0) ids.forEach((id) => snap.removeSnippet(id))
const existingSnippetIds = (snap.orders[ref!] ?? []).filter((x) => !ids.includes(x))
if (existingSnippetIds.length === 0) {
router.push(`/project/${ref}/sql/new`)
} else if (ids.includes(activeId as string)) {
router.push(`/project/${ref}/sql/${existingSnippetIds[0]}`)
}
}
const snippets = useSnippets(ref)
const projectSnippets = useMemo(() => {
@@ -87,90 +46,45 @@ const SideBarContent = observer(() => {
}, [snippets])
const filteredProjectSnippets = useMemo(() => {
if (searchText.length > 0) {
if (projectSnippetsFilterString.length > 0) {
return projectSnippets.filter((tab) =>
tab.name.toLowerCase().includes(searchText.toLowerCase())
tab.name.toLowerCase().includes(projectSnippetsFilterString.toLowerCase())
)
}
return projectSnippets
}, [projectSnippets, searchText])
}, [projectSnippets, projectSnippetsFilterString])
const personalSnippets = useMemo(() => {
const ss = snippets.filter(
(snippet) => snippet.visibility === 'user' && !snippet.content.favorite
)
if (searchText.length > 0) {
return ss.filter((tab) => tab.name.toLowerCase().includes(searchText.toLowerCase()))
if (personalSnippetsFilterString.length > 0) {
return ss.filter((tab) =>
tab.name.toLowerCase().includes(personalSnippetsFilterString.toLowerCase())
)
}
return ss
}, [searchText, snippets])
}, [personalSnippetsFilterString, snippets])
const favoriteSnippets = useMemo(() => {
return snippets.filter((snippet) => snippet.content.favorite)
}, [snippets])
const filteredFavoriteSnippets = useMemo(() => {
if (searchText.length > 0) {
if (favoritesFilterString.length > 0) {
return favoriteSnippets.filter((tab) =>
tab.name.toLowerCase().includes(searchText.toLowerCase())
tab.name.toLowerCase().includes(favoritesFilterString.toLowerCase())
)
}
return favoriteSnippets
}, [favoriteSnippets, searchText])
}, [favoriteSnippets, favoritesFilterString])
const canCreateSQLSnippet = useCheckPermissions(PermissionAction.CREATE, 'user_content', {
resource: { type: 'sql', owner_id: profile?.id },
subject: { id: profile?.id },
})
const onSelectQuery = (id?: string, isShiftHeld: boolean = false) => {
if (id === undefined) return
if (isShiftHeld) {
const [selectedFavSnippets, selectedOtherSnippets] = partition(
selectedQueries,
(id) => snippets.find((x) => x.id === id)?.content.favorite
)
const selectedSnippet = snippets.find((x) => x.id === id)
const isFavourite = selectedSnippet?.content.favorite
if (isFavourite) {
if (selectedFavSnippets.length > 0) {
const updatedSelectedFavSnippets = selectItemsInRange(
id,
filteredFavoriteSnippets,
selectedFavSnippets
)
setSelectedQueries(updatedSelectedFavSnippets.concat(selectedOtherSnippets))
} else {
selectQuery(id)
}
} else {
if (selectedOtherSnippets.length > 0) {
const updatedSelectedOtherSnippets = selectItemsInRange(
id,
personalSnippets,
selectedOtherSnippets
)
setSelectedQueries(updatedSelectedOtherSnippets.concat(selectedFavSnippets))
} else {
selectQuery(id)
}
}
} else {
selectQuery(id)
}
}
const selectQuery = (id: string) => {
if (selectedQueries.includes(id)) {
setSelectedQueries(selectedQueries.filter((x) => x !== id))
} else {
setSelectedQueries(selectedQueries.concat([id]))
}
}
const handleNewQuery = async () => {
if (!ref) return console.error('Project is required')
if (!profile) return console.error('Profile is required')
@@ -192,7 +106,13 @@ const SideBarContent = observer(() => {
snap.addSnippet(snippet as SqlSnippet, ref)
router.push(`/project/${ref}/sql/${snippet.id}`)
setSearchText('')
// reset all search inputs when a new query is added
setPersonalSnippetsFilterString('')
setProjectSnippetsFilterString('')
setFavoritesFilterString('')
setIsPersonalSnippetsFilterOpen(false)
setIsProjectSnippetsFilterOpen(false)
setIsFavoritesFilterOpen(false)
} catch (error: any) {
ui.setNotification({
category: 'error',
@@ -201,206 +121,254 @@ const SideBarContent = observer(() => {
}
}
const onConfirmDelete = async () => {
if (!ref) return console.error('Project ref is required')
deleteContent({ projectRef: ref, ids: selectedQueries })
}
return (
<>
<div className="mt-6">
<Menu type="pills">
{isLoading ? (
<div className="px-5 my-4 space-y-2">
<ShimmeringLoader />
<ShimmeringLoader className="w-3/4" />
<ShimmeringLoader className="w-1/2" />
<div className="mt-6">
<Menu type="pills">
{isLoading ? (
<div className="px-5 my-4 space-y-2">
<ShimmeringLoader />
<ShimmeringLoader className="w-3/4" />
<ShimmeringLoader className="w-1/2" />
</div>
) : isSuccess ? (
<div className="space-y-6">
<div className="px-3 flex flex-col gap-2">
<Button
type="default"
className="mx-3 justify-start"
onClick={() => {
handleNewQuery()
}}
icon={<IconPlus size="tiny" />}
>
New query
</Button>
</div>
) : isSuccess ? (
<div className="space-y-6">
<div className="px-4 flex flex-col gap-2">
<Button
type="default"
className="justify-start"
onClick={() => handleNewQuery()}
icon={<IconPlus size="tiny" />}
>
New query
</Button>
<Input
className="table-editor-search border-none"
icon={
<IconSearch className="text-foreground-lighter" size={12} strokeWidth={1.5} />
}
placeholder="Search queries"
onChange={(e) => setSearchText(e.target.value.trim())}
value={searchText}
size="tiny"
actions={
searchText && (
<Button type="text" className="px-1" onClick={() => setSearchText('')}>
<IconX size={12} strokeWidth={2} />
</Button>
)
}
<div className="space-y-6 px-3">
<div>
<ProductMenuItem
name="Templates"
isActive={router.asPath === `/project/${ref}/sql/templates`}
url={`/project/${ref}/sql/templates`}
/>
<ProductMenuItem
name="Quickstarts"
isActive={router.asPath === `/project/${ref}/sql/quickstarts`}
url={`/project/${ref}/sql/quickstarts`}
/>
</div>
<div className="flex flex-col gap-y-6 px-2">
{searchText.length === 0 && (
<div>
<ProductMenuItem
name="Templates"
isActive={router.asPath === `/project/${ref}/sql/templates`}
url={`/project/${ref}/sql/templates`}
/>
<ProductMenuItem
name="Quickstarts"
isActive={router.asPath === `/project/${ref}/sql/quickstarts`}
url={`/project/${ref}/sql/quickstarts`}
/>
{favoriteSnippets.length >= 1 && (
<div className="editor-product-menu">
<div className="flex flex-row justify-between">
<Menu.Group title="Favorites" />
<button
className="flex items-center w-4 h-4 cursor-pointer mr-3"
onClick={() => {
setIsFavoritesFilterOpen(!isFavoritesFilterOpen)
}}
>
<IconSearch
className={cn(
'w-4',
'h-4',
'cursor-pointer',
isFavoritesFilterOpen ? 'text-foreground' : 'text-foreground-lighter'
)}
onClick={() => {
setFavoritesFilterString('')
setIsFavoritesFilterOpen((state) => !state)
}}
/>
</button>
</div>
)}
{searchText.length > 0 &&
filteredFavoriteSnippets.length === 0 &&
filteredProjectSnippets.length === 0 &&
personalSnippets.length === 0 && (
<div className="h-32 border border-dashed flex flex-col gap-y-3 items-center justify-center rounded px-3">
<div className="flex flex-col gap-y-1 items-center justify-center">
<p className="text-xs text-foreground">No queries found</p>
<p className="text-xs text-foreground-light text-center">
Your search for "{searchText}" did not return any results
</p>
</div>
<Button type="default" onClick={() => handleNewQuery()}>
New query
</Button>
{isFavoritesFilterOpen && (
<div className="pl-3 mb-2 mr-3">
<Input
autoFocus
size="tiny"
icon={<IconSearch size="tiny" />}
placeholder="Filter"
disabled={isLoading}
onChange={(e) => setFavoritesFilterString(e.target.value)}
value={favoritesFilterString}
onKeyDown={(e) => {
if (e.key === 'Escape') {
setIsFavoritesFilterOpen(false)
setFavoritesFilterString('')
}
}}
actions={
favoritesFilterString && (
<IconX
size={'tiny'}
className="mr-2 cursor-pointer"
onClick={() => setFavoritesFilterString('')}
/>
)
}
/>
</div>
)}
{filteredProjectSnippets.length > 0 ? (
<div className="editor-product-menu">
<Menu.Group title="Project queries" />
<div className="space-y-1">
{filteredProjectSnippets.map((tabInfo) => {
const { id } = tabInfo || {}
return (
<QueryItem
key={id}
tabInfo={tabInfo}
hasQueriesSelected={selectedQueries.length > 0}
/>
)
})}
</div>
</div>
) : null}
{selectedQueries.length > 0 && (
<>
<Separator />
<div className="px-2 flex items-center gap-x-2">
<Button block type="default" onClick={() => setShowDeleteModal(true)}>
Delete {selectedQueries.length.toLocaleString()} quer
{selectedQueries.length > 1 ? 'ies' : 'y'}
</Button>
<Tooltip_Shadcn_ delayDuration={100}>
<TooltipTrigger_Shadcn_ asChild>
<Button
type="default"
className="px-1"
icon={<IconX />}
onClick={() => setSelectedQueries([])}
/>
</TooltipTrigger_Shadcn_>
<TooltipContent_Shadcn_ side="bottom">
Clear selection
</TooltipContent_Shadcn_>
</Tooltip_Shadcn_>
</div>
</>
)}
{filteredFavoriteSnippets.length > 0 ? (
<div className="editor-product-menu">
<Menu.Group title="Favorites" />
{filteredFavoriteSnippets.length > 0 ? (
<div className="space-y-1">
{filteredFavoriteSnippets.map((tabInfo) => {
const { id } = tabInfo || {}
return (
<QueryItem
key={id}
tabInfo={tabInfo}
isSelected={selectedQueries.includes(id as string)}
hasQueriesSelected={selectedQueries.length > 0}
onSelectQuery={(isShiftHeld) => onSelectQuery(id, isShiftHeld)}
onDeleteQuery={postDeleteCleanup}
/>
)
return <QueryItem key={id} tabInfo={tabInfo} />
})}
</div>
</div>
) : null}
) : (
<div className="text-foreground text-sm h-32 border border-dashed flex flex-col gap-3 items-center justify-center px-3 mx-3 rounded">
<span className="text-foreground-lighter">No queries found</span>
</div>
)}
</div>
)}
{personalSnippets.length > 0 ? (
<div className="editor-product-menu">
<Menu.Group title="Your queries" />
<div className="space-y-1 pb-8">
{personalSnippets.map((tabInfo) => {
{projectSnippets.length >= 1 && (
<div className="editor-product-menu">
<div className="flex flex-row justify-between">
<Menu.Group title="Project queries" />
<button
className="flex items-center w-4 h-4 cursor-pointer mr-3"
onClick={() => {
setIsProjectSnippetsFilterOpen(!isProjectSnippetsFilterOpen)
}}
>
<IconSearch
className={cn(
'w-4',
'h-4',
'cursor-pointer',
isProjectSnippetsFilterOpen
? 'text-foreground'
: 'text-foreground-lighter'
)}
onClick={() => {
setProjectSnippetsFilterString('')
setIsProjectSnippetsFilterOpen((state) => !state)
}}
/>
</button>
</div>
{isProjectSnippetsFilterOpen && (
<div className="pl-3 mb-2 mr-3">
<Input
autoFocus
size="tiny"
icon={<IconSearch size="tiny" />}
placeholder="Filter"
disabled={isLoading}
onChange={(e) => setProjectSnippetsFilterString(e.target.value)}
value={projectSnippetsFilterString}
onKeyDown={(e) => {
if (e.key === 'Escape') {
setIsProjectSnippetsFilterOpen(false)
setProjectSnippetsFilterString('')
}
}}
actions={
projectSnippetsFilterString && (
<IconX
size={'tiny'}
className="mr-2 cursor-pointer"
onClick={() => setProjectSnippetsFilterString('')}
/>
)
}
/>
</div>
)}
{filteredProjectSnippets.length > 0 ? (
<div className="space-y-1">
{filteredProjectSnippets.map((tabInfo) => {
const { id } = tabInfo || {}
return (
<QueryItem
key={id}
tabInfo={tabInfo}
isSelected={selectedQueries.includes(id as string)}
hasQueriesSelected={selectedQueries.length > 0}
onSelectQuery={(isShiftHeld) => onSelectQuery(id, isShiftHeld)}
onDeleteQuery={postDeleteCleanup}
/>
)
return <QueryItem key={id} tabInfo={tabInfo} />
})}
</div>
) : (
<div className="text-foreground text-sm h-32 border border-dashed flex flex-col gap-3 items-center justify-center px-3 mx-3 rounded">
<span className="text-foreground-lighter">No queries found</span>
</div>
)}
</div>
)}
<div className="editor-product-menu">
<div className="flex flex-row justify-between">
<Menu.Group title="Your queries" />
<button
className="flex items-center w-4 h-4 cursor-pointer mr-3"
onClick={() => {
setIsPersonalSnippetsFilterOpen(!isPersonalSnippetsFilterOpen)
}}
>
<IconSearch
className={cn(
'w-4',
'h-4',
'cursor-pointer',
isPersonalSnippetsFilterOpen ? 'text-foreground' : 'text-foreground-lighter'
)}
onClick={() => {
setPersonalSnippetsFilterString('')
setIsPersonalSnippetsFilterOpen((state) => !state)
}}
/>
</button>
</div>
{isPersonalSnippetsFilterOpen && (
<div className="pl-3 mb-2 mr-3">
<Input
autoFocus
size="tiny"
icon={<IconSearch size="tiny" />}
placeholder="Filter"
disabled={isLoading}
onChange={(e) => setPersonalSnippetsFilterString(e.target.value)}
value={personalSnippetsFilterString}
onKeyDown={(e) => {
if (e.key === 'Escape') {
setIsPersonalSnippetsFilterOpen(false)
setPersonalSnippetsFilterString('')
}
}}
actions={
personalSnippetsFilterString && (
<IconX
size={'tiny'}
className="mr-2 cursor-pointer"
onClick={() => setPersonalSnippetsFilterString('')}
/>
)
}
/>
</div>
) : null}
</div>
</div>
) : (
<div></div>
)}
</Menu>
</div>
<ConfirmationModal
header="Confirm to delete query"
buttonLabel="Delete query"
buttonLoadingLabel="Deleting query"
size="medium"
loading={isDeleting}
visible={showDeleteModal}
onSelectConfirm={onConfirmDelete}
onSelectCancel={() => setShowDeleteModal(false)}
>
<Modal.Content>
<div className="my-6">
<div className="text-sm text-foreground-light grid gap-4">
<div className="grid gap-y-4">
<Alert_Shadcn_ variant="destructive">
<WarningIcon />
<AlertTitle_Shadcn_>This action cannot be undone</AlertTitle_Shadcn_>
<AlertDescription_Shadcn_>
The selected SQL snippets cannot be recovered once deleted
</AlertDescription_Shadcn_>
</Alert_Shadcn_>
<p>
Are you sure you want to delete the selected {selectedQueries.length} quer
{selectedQueries.length > 1 ? 'ies' : 'y'}?
</p>
)}
{personalSnippets.length > 0 ? (
<div className="space-y-1 pb-8">
{personalSnippets.map((tabInfo) => {
const { id } = tabInfo || {}
return <QueryItem key={id} tabInfo={tabInfo} />
})}
</div>
) : (
<div className="text-foreground text-sm h-32 border border-dashed flex flex-col gap-3 items-center justify-center px-3 mx-3 rounded">
{filteredFavoriteSnippets.length === 0 && (
<span className="text-foreground-lighter">No queries found</span>
)}
<Button type="default" onClick={() => handleNewQuery()}>
New Query
</Button>
</div>
)}
</div>
</div>
</div>
</Modal.Content>
</ConfirmationModal>
</>
) : (
<div></div>
)}
</Menu>
</div>
)
})
@@ -191,7 +191,7 @@ const TableEditorMenu = () => {
size="tiny"
actions={
searchText && (
<Button type="text" className="px-1" onClick={() => setSearchText('')}>
<Button size="tiny" type="text" onClick={() => setSearchText('')}>
<IconX size={12} strokeWidth={2} />
</Button>
)
@@ -13,13 +13,13 @@ const FileExplorerHeaderSelection = () => {
} = storageExplorerStore
return (
<div className="z-10 flex h-[40px] items-center rounded-t-md bg-brand-400 px-2 py-1 shadow [[data-theme*=dark]_&]:bg-brand-500">
<div className="z-10 flex h-[40px] items-center rounded-t-md bg-brand-400 px-2 py-1 shadow [[data-theme*=dark]_&]:bg-brand-600">
<Button
icon={<IconX size={16} strokeWidth={2} />}
type="text"
onClick={() => clearSelectedItems()}
/>
<div className="ml-1 flex items-center space-x-3">
<div className="ml-4 flex items-center space-x-3">
<p className="mb-0 text-sm text-foreground">
<span style={{ fontVariantNumeric: 'tabular-nums' }}>{selectedItems.length}</span> items
selected
@@ -37,7 +37,7 @@ const FileExplorerHeaderSelection = () => {
>
Download
</Button>
<div className="border-r border-green-900 py-3 opacity-50" />
<div className="border-r border-green-800 py-3 opacity-50" />
<Button
icon={<IconTrash2 size={16} strokeWidth={2} />}
type="primary"
+9 -16
View File
@@ -466,8 +466,8 @@ export interface paths {
put: operations['ContentController_updateWholeContent']
/** Creates project's content */
post: operations['ContentController_createContent']
/** Deletes project's contents */
delete: operations['ContentController_deleteContents']
/** Deletes project's content */
delete: operations['ContentController_deleteContent']
/** Updates project's content */
patch: operations['ContentController_updateContent']
}
@@ -1238,8 +1238,8 @@ export interface paths {
put: operations['ContentController_updateWholeContent']
/** Creates project's content */
post: operations['ContentController_createContent']
/** Deletes project's contents */
delete: operations['ContentController_deleteContents']
/** Deletes project's content */
delete: operations['ContentController_deleteContent']
/** Updates project's content */
patch: operations['ContentController_updateContent']
}
@@ -3677,9 +3677,6 @@ export interface components {
content?: Record<string, never>
owner_id?: number
}
BulkDeleteUserContentResponse: {
id: string
}
DatabaseDetailResponse: {
/** @enum {string} */
status:
@@ -8612,24 +8609,20 @@ export interface operations {
}
}
}
/** Deletes project's contents */
ContentController_deleteContents: {
/** Deletes project's content */
ContentController_deleteContent: {
parameters: {
query: {
ids: string[]
}
path: {
/** @description Project ref */
ref: string
id: string
}
}
responses: {
200: {
content: {
'application/json': components['schemas']['BulkDeleteUserContentResponse'][]
'application/json': components['schemas']['UserContentObject']
}
}
/** @description Failed to delete project's contents */
/** @description Failed to delete project's content */
500: {
content: never
}
@@ -1,30 +1,30 @@
import { useMutation, UseMutationOptions, useQueryClient } from '@tanstack/react-query'
import toast from 'react-hot-toast'
import { del } from 'data/fetchers'
import { ResponseError } from 'types'
import { delete_, isResponseOk } from 'lib/common/fetch'
import { API_URL } from 'lib/constants'
import { ResponseError, UserContent } from 'types'
import { contentKeys } from './keys'
type DeleteContentVariables = { projectRef: string; ids: string[] }
type DeleteContentVariables = { projectRef: string; id: string }
export async function deleteContents(
{ projectRef, ids }: DeleteContentVariables,
export async function deleteContent(
{ projectRef, id }: DeleteContentVariables,
signal?: AbortSignal
) {
const { data, error } = await del('/platform/projects/{ref}/content', {
headers: { Version: '2' },
params: {
path: { ref: projectRef },
query: { ids },
},
signal,
})
const response = await delete_<UserContent>(
`${API_URL}/projects/${projectRef}/content?id=${id}`,
undefined,
{ signal }
)
if (error) throw error
return data.map((x) => x.id)
if (!isResponseOk(response)) {
throw response.error
}
return response
}
type DeleteContentData = Awaited<ReturnType<typeof deleteContents>>
type DeleteContentData = Awaited<ReturnType<typeof deleteContent>>
export const useContentDeleteMutation = ({
onSuccess,
@@ -37,7 +37,7 @@ export const useContentDeleteMutation = ({
const queryClient = useQueryClient()
return useMutation<DeleteContentData, ResponseError, DeleteContentVariables>(
(args) => deleteContents(args),
(args) => deleteContent(args),
{
async onSuccess(data, variables, context) {
const { projectRef } = variables
@@ -46,7 +46,7 @@ export const useContentDeleteMutation = ({
},
async onError(data, variables, context) {
if (onError === undefined) {
toast.error(`Failed to delete contents: ${data.message}`)
toast.error(`Failed to delete content: ${data.message}`)
} else {
onError(data, variables, context)
}
+5 -5
View File
@@ -17,15 +17,15 @@
"downlevelIteration": true,
"incremental": true,
"paths": {
"@ui/*": ["./../../packages/ui/src/*"], // handle ui package paths
"@ui/*": ["./../../packages/ui/src/*"] // handle ui package paths
},
"useDefineForClassFields": true,
"plugins": [
{
"name": "next",
},
],
"name": "next"
}
]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"],
"exclude": ["node_modules"]
}
+5 -5
View File
@@ -3,7 +3,7 @@
"baseUrl": ".",
"paths": {
"~/*": ["./*"],
"@ui/*": ["./../../packages/ui/src/*"], // handle ui package paths
"@ui/*": ["./../../packages/ui/src/*"] // handle ui package paths
},
"downlevelIteration": true,
"target": "es5",
@@ -22,10 +22,10 @@
"incremental": true,
"plugins": [
{
"name": "next",
},
],
"name": "next"
}
]
},
"exclude": ["node_modules", "supabase"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"]
}