♻️ (builder) Change to features-centric folder structure
This commit is contained in:
committed by
Baptiste Arnaud
parent
3686465a85
commit
643571fe7d
@@ -0,0 +1,159 @@
|
||||
import {
|
||||
Button,
|
||||
IconButton,
|
||||
Menu,
|
||||
MenuButton,
|
||||
MenuButtonProps,
|
||||
MenuItem,
|
||||
MenuList,
|
||||
Stack,
|
||||
Text,
|
||||
} from '@chakra-ui/react'
|
||||
import { ChevronLeftIcon, PlusIcon, TrashIcon } from '@/components/icons'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { CredentialsType } from 'models'
|
||||
import { useWorkspace } from '@/features/workspace'
|
||||
import { useToast } from '../../../hooks/useToast'
|
||||
import { deleteCredentialsQuery, useCredentials } from '@/features/credentials'
|
||||
|
||||
type Props = Omit<MenuButtonProps, 'type'> & {
|
||||
type: CredentialsType
|
||||
currentCredentialsId?: string
|
||||
onCredentialsSelect: (credentialId?: string) => void
|
||||
onCreateNewClick: () => void
|
||||
defaultCredentialLabel?: string
|
||||
refreshDropdownKey?: number
|
||||
}
|
||||
|
||||
export const CredentialsDropdown = ({
|
||||
type,
|
||||
currentCredentialsId,
|
||||
onCredentialsSelect,
|
||||
onCreateNewClick,
|
||||
defaultCredentialLabel,
|
||||
refreshDropdownKey,
|
||||
...props
|
||||
}: Props) => {
|
||||
const router = useRouter()
|
||||
const { workspace } = useWorkspace()
|
||||
const { showToast } = useToast()
|
||||
const { credentials, mutate } = useCredentials({
|
||||
workspaceId: workspace?.id,
|
||||
})
|
||||
const [isDeleting, setIsDeleting] = useState<string>()
|
||||
|
||||
const defaultCredentialsLabel = defaultCredentialLabel ?? `Select an account`
|
||||
|
||||
const credentialsList = useMemo(() => {
|
||||
return credentials.filter((credential) => credential.type === type)
|
||||
}, [type, credentials])
|
||||
|
||||
const currentCredential = useMemo(
|
||||
() => credentials.find((c) => c.id === currentCredentialsId),
|
||||
[currentCredentialsId, credentials]
|
||||
)
|
||||
|
||||
const handleMenuItemClick = (credentialsId: string) => () => {
|
||||
onCredentialsSelect(credentialsId)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if ((refreshDropdownKey ?? 0) > 0) mutate({ credentials })
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [refreshDropdownKey])
|
||||
|
||||
useEffect(() => {
|
||||
if (!router.isReady) return
|
||||
if (router.query.credentialsId) {
|
||||
handleMenuItemClick(router.query.credentialsId.toString())()
|
||||
clearQueryParams()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [router.isReady])
|
||||
|
||||
const clearQueryParams = () => {
|
||||
const hasQueryParams = router.asPath.includes('?')
|
||||
if (hasQueryParams)
|
||||
router.push(router.asPath.split('?')[0], undefined, { shallow: true })
|
||||
}
|
||||
|
||||
const handleDeleteDomainClick =
|
||||
(credentialsId: string) => async (e: React.MouseEvent) => {
|
||||
e.stopPropagation()
|
||||
if (!workspace?.id) return
|
||||
setIsDeleting(credentialsId)
|
||||
const { error } = await deleteCredentialsQuery(
|
||||
workspace.id,
|
||||
credentialsId
|
||||
)
|
||||
setIsDeleting(undefined)
|
||||
if (error)
|
||||
return showToast({ title: error.name, description: error.message })
|
||||
onCredentialsSelect(undefined)
|
||||
mutate({ credentials: credentials.filter((c) => c.id !== credentialsId) })
|
||||
}
|
||||
|
||||
return (
|
||||
<Menu isLazy placement="bottom-end" matchWidth>
|
||||
<MenuButton
|
||||
as={Button}
|
||||
rightIcon={<ChevronLeftIcon transform={'rotate(-90deg)'} />}
|
||||
colorScheme="gray"
|
||||
justifyContent="space-between"
|
||||
textAlign="left"
|
||||
{...props}
|
||||
>
|
||||
<Text noOfLines={1} overflowY="visible" h="20px">
|
||||
{currentCredential ? currentCredential.name : defaultCredentialsLabel}
|
||||
</Text>
|
||||
</MenuButton>
|
||||
<MenuList maxW="500px">
|
||||
<Stack maxH={'35vh'} overflowY="scroll" spacing="0">
|
||||
{defaultCredentialLabel && (
|
||||
<MenuItem
|
||||
maxW="500px"
|
||||
overflow="hidden"
|
||||
whiteSpace="nowrap"
|
||||
textOverflow="ellipsis"
|
||||
onClick={handleMenuItemClick('default')}
|
||||
>
|
||||
{defaultCredentialLabel}
|
||||
</MenuItem>
|
||||
)}
|
||||
{credentialsList.map((credentials) => (
|
||||
<MenuItem
|
||||
role="menuitem"
|
||||
minH="40px"
|
||||
key={credentials.id}
|
||||
onClick={handleMenuItemClick(credentials.id)}
|
||||
fontSize="16px"
|
||||
fontWeight="normal"
|
||||
rounded="none"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
{credentials.name}
|
||||
<IconButton
|
||||
icon={<TrashIcon />}
|
||||
aria-label="Remove credentials"
|
||||
size="xs"
|
||||
onClick={handleDeleteDomainClick(credentials.id)}
|
||||
isLoading={isDeleting === credentials.id}
|
||||
/>
|
||||
</MenuItem>
|
||||
))}
|
||||
<MenuItem
|
||||
maxW="500px"
|
||||
overflow="hidden"
|
||||
whiteSpace="nowrap"
|
||||
textOverflow="ellipsis"
|
||||
icon={<PlusIcon />}
|
||||
onClick={onCreateNewClick}
|
||||
>
|
||||
Connect new
|
||||
</MenuItem>
|
||||
</Stack>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { fetcher } from '@/utils/helpers'
|
||||
import { Credentials } from 'models'
|
||||
import { stringify } from 'qs'
|
||||
import useSWR from 'swr'
|
||||
|
||||
export const useCredentials = ({
|
||||
workspaceId,
|
||||
onError,
|
||||
}: {
|
||||
workspaceId?: string
|
||||
onError?: (error: Error) => void
|
||||
}) => {
|
||||
const { data, error, mutate } = useSWR<{ credentials: Credentials[] }, Error>(
|
||||
workspaceId ? `/api/credentials?${stringify({ workspaceId })}` : null,
|
||||
fetcher
|
||||
)
|
||||
if (error && onError) onError(error)
|
||||
return {
|
||||
credentials: data?.credentials ?? [],
|
||||
isLoading: !error && !data,
|
||||
mutate,
|
||||
}
|
||||
}
|
||||
4
apps/builder/src/features/credentials/index.ts
Normal file
4
apps/builder/src/features/credentials/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { CredentialsDropdown } from './components/CredentialsDropdown'
|
||||
export { useCredentials } from './hooks/useCredentials'
|
||||
export { createCredentialsQuery } from './queries/createCredentialsQuery'
|
||||
export { deleteCredentialsQuery } from './queries/deleteCredentialsQuery'
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Credentials } from 'models'
|
||||
import { stringify } from 'qs'
|
||||
import { sendRequest } from 'utils'
|
||||
|
||||
export const createCredentialsQuery = async (
|
||||
credentials: Omit<Credentials, 'id' | 'iv' | 'createdAt'>
|
||||
) =>
|
||||
sendRequest<{
|
||||
credentials: Credentials
|
||||
}>({
|
||||
url: `/api/credentials?${stringify({
|
||||
workspaceId: credentials.workspaceId,
|
||||
})}`,
|
||||
method: 'POST',
|
||||
body: credentials,
|
||||
})
|
||||
@@ -0,0 +1,14 @@
|
||||
import { Credentials } from 'db'
|
||||
import { stringify } from 'qs'
|
||||
import { sendRequest } from 'utils'
|
||||
|
||||
export const deleteCredentialsQuery = async (
|
||||
workspaceId: string,
|
||||
credentialsId: string
|
||||
) =>
|
||||
sendRequest<{
|
||||
credentials: Credentials
|
||||
}>({
|
||||
url: `/api/credentials/${credentialsId}?${stringify({ workspaceId })}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
Reference in New Issue
Block a user