From 8138338fec0ab35fd9ebbb0dbebd83d1305a8993 Mon Sep 17 00:00:00 2001 From: Baptiste Arnaud Date: Tue, 26 Sep 2023 11:34:55 +0200 Subject: [PATCH] :passport_control: Improve editor authorization feedback Closes #844, closes #839 --- .../src/features/account/UserProvider.tsx | 12 +++++++- .../features/auth/components/SignInForm.tsx | 2 +- .../components/CredentialsDropdown.tsx | 25 +++++++++------- .../dashboard/components/DashboardHeader.tsx | 9 ++---- .../editor/providers/TypebotProvider.tsx | 14 ++++++--- .../results/components/table/ResultsTable.tsx | 30 +++++++++++-------- .../features/workspace/WorkspaceProvider.tsx | 7 ++++- 7 files changed, 62 insertions(+), 37 deletions(-) diff --git a/apps/builder/src/features/account/UserProvider.tsx b/apps/builder/src/features/account/UserProvider.tsx index 47bc533e51..4d3129428c 100644 --- a/apps/builder/src/features/account/UserProvider.tsx +++ b/apps/builder/src/features/account/UserProvider.tsx @@ -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) => 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, }} > diff --git a/apps/builder/src/features/auth/components/SignInForm.tsx b/apps/builder/src/features/auth/components/SignInForm.tsx index 1cd28b82fb..fd2daf570d 100644 --- a/apps/builder/src/features/auth/components/SignInForm.tsx +++ b/apps/builder/src/features/auth/components/SignInForm.tsx @@ -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 () => { diff --git a/apps/builder/src/features/credentials/components/CredentialsDropdown.tsx b/apps/builder/src/features/credentials/components/CredentialsDropdown.tsx index 9181fcc3e1..512a8cd5fa 100644 --- a/apps/builder/src/features/credentials/components/CredentialsDropdown.tsx +++ b/apps/builder/src/features/credentials/components/CredentialsDropdown.tsx @@ -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 & { 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={} onClick={onCreateNewClick} + isDisabled={currentRole === 'GUEST'} {...props} > Add {credentialsName} @@ -165,16 +168,18 @@ export const CredentialsDropdown = ({ /> ))} - } - onClick={onCreateNewClick} - > - Connect new - + {currentRole === 'GUEST' ? null : ( + } + onClick={onCreateNewClick} + > + Connect new + + )} diff --git a/apps/builder/src/features/dashboard/components/DashboardHeader.tsx b/apps/builder/src/features/dashboard/components/DashboardHeader.tsx index daf21a6ea4..3bfdf64962 100644 --- a/apps/builder/src/features/dashboard/components/DashboardHeader.tsx +++ b/apps/builder/src/features/dashboard/components/DashboardHeader.tsx @@ -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 = () => { diff --git a/apps/builder/src/features/editor/providers/TypebotProvider.tsx b/apps/builder/src/features/editor/providers/TypebotProvider.tsx index 387f80961b..a636a2ca6f 100644 --- a/apps/builder/src/features/editor/providers/TypebotProvider.tsx +++ b/apps/builder/src/features/editor/providers/TypebotProvider.tsx @@ -168,7 +168,7 @@ export const TypebotProvider = ({ const saveTypebot = useCallback( async (updates?: Partial) => { - 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, diff --git a/apps/builder/src/features/results/components/table/ResultsTable.tsx b/apps/builder/src/features/results/components/table/ResultsTable.tsx index dc2a43e4d5..85e9714014 100644 --- a/apps/builder/src/features/results/components/table/ResultsTable.tsx +++ b/apps/builder/src/features/results/components/table/ResultsTable.tsx @@ -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>({}) const [isTableScrolled, setIsTableScrolled] = useState(false) const bottomElement = useRef(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 ( - setRowSelection({})} - /> + {isReadOnly ? null : ( + setRowSelection({})} + /> + )} { - const { pathname, query, push } = useRouter() + const { pathname, query, push, isReady: isRouterReady } = useRouter() const { user } = useUser() const userId = user?.id const [workspaceId, setWorkspaceId] = useState() @@ -102,6 +102,8 @@ export const WorkspaceProvider = ({ useEffect(() => { if ( + pathname === '/signin' || + !isRouterReady || !workspaces || workspaces.length === 0 || workspaceId || @@ -122,7 +124,10 @@ export const WorkspaceProvider = ({ setWorkspaceIdInLocalStorage(newWorkspaceId) setWorkspaceId(newWorkspaceId) }, [ + isRouterReady, members, + pathname, + query, query.workspaceId, typebot?.workspaceId, typebotId,