🛂 Improve editor authorization feedback (#856)
Closes #844, closes #839 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ### Summary by CodeRabbit - New Feature: Added a `logOut` function to the user context for improved logout handling. - Refactor: Updated the redirect path in the `SignInForm` component for better user redirection after authentication. - New Feature: Enhanced the "Add" button and "Connect new" menu item in `CredentialsDropdown` with role-based access control. - Refactor: Replaced the `signOut` function with the `logOut` function from the `useUser` hook in `DashboardHeader`. - Bug Fix: Prevented execution of certain code blocks in `TypebotProvider` when `typebotData` is read-only. - Refactor: Optimized the `handleObserver` function in `ResultsTable` with a `useCallback` hook. - Bug Fix: Improved router readiness check in `WorkspaceProvider` to prevent premature execution of certain operations. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { useSession } from 'next-auth/react'
|
||||
import { signOut, useSession } from 'next-auth/react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { createContext, ReactNode, useEffect, useState } from 'react'
|
||||
import { isDefined, isNotDefined } from '@typebot.io/lib'
|
||||
@ -15,9 +15,13 @@ export const userContext = createContext<{
|
||||
user?: User
|
||||
isLoading: boolean
|
||||
currentWorkspaceId?: string
|
||||
logOut: () => void
|
||||
updateUser: (newUser: Partial<User>) => void
|
||||
}>({
|
||||
isLoading: false,
|
||||
logOut: () => {
|
||||
console.log('logOut not implemented')
|
||||
},
|
||||
updateUser: () => {
|
||||
console.log('updateUser not implemented')
|
||||
},
|
||||
@ -91,6 +95,11 @@ export const UserProvider = ({ children }: { children: ReactNode }) => {
|
||||
env.NEXT_PUBLIC_E2E_TEST ? 0 : debounceTimeout
|
||||
)
|
||||
|
||||
const logOut = () => {
|
||||
signOut()
|
||||
setUser(undefined)
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
saveUser.flush()
|
||||
@ -103,6 +112,7 @@ export const UserProvider = ({ children }: { children: ReactNode }) => {
|
||||
user,
|
||||
isLoading: status === 'loading',
|
||||
currentWorkspaceId,
|
||||
logOut,
|
||||
updateUser,
|
||||
}}
|
||||
>
|
||||
|
@ -55,7 +55,7 @@ export const SignInForm = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (status === 'authenticated') {
|
||||
router.replace(router.query.callbackUrl?.toString() ?? '/typebots')
|
||||
router.replace(router.query.redirectPath?.toString() ?? '/typebots')
|
||||
return
|
||||
}
|
||||
;(async () => {
|
||||
|
@ -15,6 +15,7 @@ import { useRouter } from 'next/router'
|
||||
import { useToast } from '../../../hooks/useToast'
|
||||
import { Credentials } from '@typebot.io/schemas'
|
||||
import { trpc } from '@/lib/trpc'
|
||||
import { useWorkspace } from '@/features/workspace/WorkspaceProvider'
|
||||
|
||||
type Props = Omit<ButtonProps, 'type'> & {
|
||||
type: Credentials['type']
|
||||
@ -38,6 +39,7 @@ export const CredentialsDropdown = ({
|
||||
}: Props) => {
|
||||
const router = useRouter()
|
||||
const { showToast } = useToast()
|
||||
const { currentRole } = useWorkspace()
|
||||
const { data, refetch } = trpc.credentials.listCredentials.useQuery({
|
||||
workspaceId,
|
||||
type,
|
||||
@ -107,6 +109,7 @@ export const CredentialsDropdown = ({
|
||||
textAlign="left"
|
||||
leftIcon={<PlusIcon />}
|
||||
onClick={onCreateNewClick}
|
||||
isDisabled={currentRole === 'GUEST'}
|
||||
{...props}
|
||||
>
|
||||
Add {credentialsName}
|
||||
@ -165,16 +168,18 @@ export const CredentialsDropdown = ({
|
||||
/>
|
||||
</MenuItem>
|
||||
))}
|
||||
<MenuItem
|
||||
maxW="500px"
|
||||
overflow="hidden"
|
||||
whiteSpace="nowrap"
|
||||
textOverflow="ellipsis"
|
||||
icon={<PlusIcon />}
|
||||
onClick={onCreateNewClick}
|
||||
>
|
||||
Connect new
|
||||
</MenuItem>
|
||||
{currentRole === 'GUEST' ? null : (
|
||||
<MenuItem
|
||||
maxW="500px"
|
||||
overflow="hidden"
|
||||
whiteSpace="nowrap"
|
||||
textOverflow="ellipsis"
|
||||
icon={<PlusIcon />}
|
||||
onClick={onCreateNewClick}
|
||||
>
|
||||
Connect new
|
||||
</MenuItem>
|
||||
)}
|
||||
</Stack>
|
||||
</MenuList>
|
||||
</Menu>
|
||||
|
@ -1,7 +1,6 @@
|
||||
import React from 'react'
|
||||
import { HStack, Flex, Button, useDisclosure } from '@chakra-ui/react'
|
||||
import { HardDriveIcon, SettingsIcon } from '@/components/icons'
|
||||
import { signOut } from 'next-auth/react'
|
||||
import { useUser } from '@/features/account/hooks/useUser'
|
||||
import { isNotDefined } from '@typebot.io/lib'
|
||||
import Link from 'next/link'
|
||||
@ -13,15 +12,11 @@ import { WorkspaceSettingsModal } from '@/features/workspace/components/Workspac
|
||||
|
||||
export const DashboardHeader = () => {
|
||||
const scopedT = useScopedI18n('dashboard.header')
|
||||
const { user } = useUser()
|
||||
const { user, logOut } = useUser()
|
||||
const { workspace, switchWorkspace, createWorkspace } = useWorkspace()
|
||||
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
|
||||
const handleLogOut = () => {
|
||||
signOut()
|
||||
}
|
||||
|
||||
const handleCreateNewWorkspace = () =>
|
||||
createWorkspace(user?.name ?? undefined)
|
||||
|
||||
@ -59,7 +54,7 @@ export const DashboardHeader = () => {
|
||||
</Button>
|
||||
<WorkspaceDropdown
|
||||
currentWorkspace={workspace}
|
||||
onLogoutClick={handleLogOut}
|
||||
onLogoutClick={logOut}
|
||||
onCreateNewWorkspaceClick={handleCreateNewWorkspace}
|
||||
onWorkspaceSelected={switchWorkspace}
|
||||
/>
|
||||
|
@ -168,7 +168,7 @@ export const TypebotProvider = ({
|
||||
|
||||
const saveTypebot = useCallback(
|
||||
async (updates?: Partial<Typebot>) => {
|
||||
if (!localTypebot || !typebot) return
|
||||
if (!localTypebot || !typebot || typebotData?.isReadOnly) return
|
||||
const typebotToSave = { ...localTypebot, ...updates }
|
||||
if (dequal(omit(typebot, 'updatedAt'), omit(typebotToSave, 'updatedAt')))
|
||||
return
|
||||
@ -180,7 +180,13 @@ export const TypebotProvider = ({
|
||||
setLocalTypebot({ ...newTypebot })
|
||||
return newTypebot
|
||||
},
|
||||
[localTypebot, setLocalTypebot, typebot, updateTypebot]
|
||||
[
|
||||
localTypebot,
|
||||
setLocalTypebot,
|
||||
typebot,
|
||||
typebotData?.isReadOnly,
|
||||
updateTypebot,
|
||||
]
|
||||
)
|
||||
|
||||
useAutoSave(
|
||||
@ -212,7 +218,7 @@ export const TypebotProvider = ({
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!localTypebot || !typebot) return
|
||||
if (!localTypebot || !typebot || typebotData?.isReadOnly) return
|
||||
if (!areTypebotsEqual(localTypebot, typebot)) {
|
||||
window.addEventListener('beforeunload', preventUserFromRefreshing)
|
||||
}
|
||||
@ -220,7 +226,7 @@ export const TypebotProvider = ({
|
||||
return () => {
|
||||
window.removeEventListener('beforeunload', preventUserFromRefreshing)
|
||||
}
|
||||
}, [localTypebot, typebot])
|
||||
}, [localTypebot, typebot, typebotData?.isReadOnly])
|
||||
|
||||
const updateLocalTypebot = async ({
|
||||
updates,
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
} from '@chakra-ui/react'
|
||||
import { AlignLeftTextIcon } from '@/components/icons'
|
||||
import { ResultHeaderCell, ResultsTablePreferences } from '@typebot.io/schemas'
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { LoadingRows } from './LoadingRows'
|
||||
import {
|
||||
useReactTable,
|
||||
@ -48,7 +48,7 @@ export const ResultsTable = ({
|
||||
onResultExpandIndex,
|
||||
}: ResultsTableProps) => {
|
||||
const background = useColorModeValue('white', colors.gray[900])
|
||||
const { updateTypebot } = useTypebot()
|
||||
const { updateTypebot, isReadOnly } = useTypebot()
|
||||
const [rowSelection, setRowSelection] = useState<Record<string, boolean>>({})
|
||||
const [isTableScrolled, setIsTableScrolled] = useState(false)
|
||||
const bottomElement = useRef<HTMLDivElement | null>(null)
|
||||
@ -185,6 +185,14 @@ export const ResultsTable = ({
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
})
|
||||
|
||||
const handleObserver = useCallback(
|
||||
(entities: IntersectionObserverEntry[]) => {
|
||||
const target = entities[0]
|
||||
if (target.isIntersecting) onScrollToBottom()
|
||||
},
|
||||
[onScrollToBottom]
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
if (!bottomElement.current) return
|
||||
const options: IntersectionObserverInit = {
|
||||
@ -197,21 +205,17 @@ export const ResultsTable = ({
|
||||
return () => {
|
||||
observer.disconnect()
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [bottomElement.current])
|
||||
|
||||
const handleObserver = (entities: IntersectionObserverEntry[]) => {
|
||||
const target = entities[0]
|
||||
if (target.isIntersecting) onScrollToBottom()
|
||||
}
|
||||
}, [handleObserver])
|
||||
|
||||
return (
|
||||
<Stack maxW="1600px" px="4" overflowY="hidden" spacing={6}>
|
||||
<HStack w="full" justifyContent="flex-end">
|
||||
<SelectionToolbar
|
||||
selectedResultsId={Object.keys(rowSelection)}
|
||||
onClearSelection={() => setRowSelection({})}
|
||||
/>
|
||||
{isReadOnly ? null : (
|
||||
<SelectionToolbar
|
||||
selectedResultsId={Object.keys(rowSelection)}
|
||||
onClearSelection={() => setRowSelection({})}
|
||||
/>
|
||||
)}
|
||||
<TableSettingsButton
|
||||
resultHeader={resultHeader}
|
||||
columnVisibility={columnsVisibility}
|
||||
|
@ -38,7 +38,7 @@ export const WorkspaceProvider = ({
|
||||
typebotId,
|
||||
children,
|
||||
}: WorkspaceContextProps) => {
|
||||
const { pathname, query, push } = useRouter()
|
||||
const { pathname, query, push, isReady: isRouterReady } = useRouter()
|
||||
const { user } = useUser()
|
||||
const userId = user?.id
|
||||
const [workspaceId, setWorkspaceId] = useState<string | undefined>()
|
||||
@ -102,6 +102,8 @@ export const WorkspaceProvider = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
pathname === '/signin' ||
|
||||
!isRouterReady ||
|
||||
!workspaces ||
|
||||
workspaces.length === 0 ||
|
||||
workspaceId ||
|
||||
@ -122,7 +124,9 @@ export const WorkspaceProvider = ({
|
||||
setWorkspaceIdInLocalStorage(newWorkspaceId)
|
||||
setWorkspaceId(newWorkspaceId)
|
||||
}, [
|
||||
isRouterReady,
|
||||
members,
|
||||
pathname,
|
||||
query.workspaceId,
|
||||
typebot?.workspaceId,
|
||||
typebotId,
|
||||
|
Reference in New Issue
Block a user