diff --git a/twake/backend/node/src/services/documents/utils.ts b/twake/backend/node/src/services/documents/utils.ts index fad1c2fa43..0f32b0dcaf 100644 --- a/twake/backend/node/src/services/documents/utils.ts +++ b/twake/backend/node/src/services/documents/utils.ts @@ -164,7 +164,7 @@ export const isCompanyGuest = async (context: CompanyExecutionContext): Promise< context.user?.id, ); - return userRole === "guest"; + return userRole === "guest" || !userRole; }; /** @@ -343,9 +343,10 @@ export const getAccessLevel = async ( repository: Repository, context: CompanyExecutionContext & { public_token?: string; twake_tab_token?: string }, ): Promise => { - if (!id || id === "root") return (await isCompanyGuest(context)) ? "read" : "manage"; + if (!id || id === "root") + return !context?.user?.id ? "none" : (await isCompanyGuest(context)) ? "read" : "manage"; if (id === "trash") - return (await isCompanyGuest(context)) + return (await isCompanyGuest(context)) || !context?.user?.id ? "none" : (await isCompanyAdmin(context)) ? "manage" @@ -377,6 +378,9 @@ export const getAccessLevel = async ( if (itemToken === publicToken) return itemLevel; } + //From there a user must be logged in + if (!context.user.id) return "none"; + const accessEntities = item.access_info.entities || []; //Users diff --git a/twake/backend/node/src/services/documents/web/controllers/documents.ts b/twake/backend/node/src/services/documents/web/controllers/documents.ts index 9979e28ac5..c4a3216588 100644 --- a/twake/backend/node/src/services/documents/web/controllers/documents.ts +++ b/twake/backend/node/src/services/documents/web/controllers/documents.ts @@ -308,9 +308,9 @@ export class DocumentsController { const { search = "", added = "", company_id = "", creator = "" } = request.body; const options: SearchDocumentsOptions = { + company_id: company_id || context.company.id, ...(search ? { search } : {}), ...(added ? { added } : {}), - ...(company_id ? { company_id } : {}), ...(creator ? { creator } : {}), }; diff --git a/twake/backend/node/src/services/user/services/companies.ts b/twake/backend/node/src/services/user/services/companies.ts index 508cf72b00..24693f8aa0 100644 --- a/twake/backend/node/src/services/user/services/companies.ts +++ b/twake/backend/node/src/services/user/services/companies.ts @@ -333,6 +333,7 @@ export class CompanyServiceImpl { } async getUserRole(companyId: uuid, userId: uuid): Promise { + if (!userId) return "guest"; const companyUser = await this.getCompanyUser({ id: companyId }, { id: userId }); if (!companyUser) { return "guest"; diff --git a/twake/frontend/src/app/components/search-popup/parts/drive-item-result.tsx b/twake/frontend/src/app/components/search-popup/parts/drive-item-result.tsx index 2e212aa9ac..4e444a1b61 100644 --- a/twake/frontend/src/app/components/search-popup/parts/drive-item-result.tsx +++ b/twake/frontend/src/app/components/search-popup/parts/drive-item-result.tsx @@ -26,6 +26,7 @@ import { openDriveItem, onDriveItemDownloadClick } from '../common'; import ResultContext from './result-context'; import { useCompanyApplications } from 'app/features/applications/hooks/use-company-applications'; import { DriveCurrentFolderAtom } from 'app/views/applications/drive/browser'; +import { FolderIcon } from '@heroicons/react/solid'; export default (props: { driveItem: DriveItem & { user?: UserType } }) => { const input = useRecoilValue(SearchInputState); @@ -122,6 +123,14 @@ export const FileResultMedia = (props: { let iconClassName = 'absolute left-0 top-0 bottom-0 right-0 m-auto w-8 h-8'; if (url) iconClassName = 'absolute bottom-1 left-1 w-6 h-6'; + if (file.is_directory) { + return ( +
+ +
+ ); + } + return (
( - `/internal/services/documents/v1/companies/${companyId}/tab/${tabId}`, + `/internal/services/documents/v1/companies/${companyId}/tabs/${tabId}`, ); } @@ -16,7 +16,7 @@ export class DriveTwakeApiClient { level: 'write' | 'read', ) { return await Api.post( - `/internal/services/documents/v1/companies/${companyId}/tab/${tabId}`, + `/internal/services/documents/v1/companies/${companyId}/tabs/${tabId}`, { company_id: companyId, tab_id: tabId, diff --git a/twake/frontend/src/app/features/drive/hooks/use-drive-item.tsx b/twake/frontend/src/app/features/drive/hooks/use-drive-item.tsx index 26e271efe8..ce9138bc88 100644 --- a/twake/frontend/src/app/features/drive/hooks/use-drive-item.tsx +++ b/twake/frontend/src/app/features/drive/hooks/use-drive-item.tsx @@ -89,13 +89,17 @@ export const useDriveItem = (id: string) => { }; }; -export const usePublicLink = (item?: DriveItem) => { +export const usePublicLink = (item?: DriveItem): string => { const translator = useRef(short()).current; - const publicLink = - `${document.location.protocol}//${document.location.host}` + - `/shared/${translator.fromUUID(item?.company_id || '')}` + - `/drive/${translator.fromUUID(item?.id || '')}` + - `/t/${item?.access_info?.public?.token}`; + let publicLink = `${document.location.protocol}//${document.location.host}`; + try { + publicLink += + `/shared/${translator.fromUUID(item?.company_id || '')}` + + `/drive/${translator.fromUUID(item?.id || '')}` + + `/t/${item?.access_info?.public?.token}`; + } catch (e) { + return publicLink; + } return publicLink; }; diff --git a/twake/frontend/src/app/features/viewer/hooks/use-viewer.ts b/twake/frontend/src/app/features/viewer/hooks/use-viewer.ts index df810d91a5..64e44aea25 100644 --- a/twake/frontend/src/app/features/viewer/hooks/use-viewer.ts +++ b/twake/frontend/src/app/features/viewer/hooks/use-viewer.ts @@ -44,17 +44,17 @@ export const useFileViewer = () => { loading: true, }); - const details = await ViewerAPIClient.getMessageFile( - status.file.company_id || '', - status.file.message_id || '', - status.file.id || '', - ); + const details = await ViewerAPIClient.getMessageFile( + status.file.company_id || '', + status.file.message_id || '', + status.file.id || '', + ); - setStatus({ - ...status, - details: details.resource || (details as unknown as MessageFileDetails), - loading: false, - }); + setStatus({ + ...status, + details: details.resource || (details as unknown as MessageFileDetails), + loading: false, + }); } }, [status.file?.id], diff --git a/twake/frontend/src/app/views/applications/drive/browser.tsx b/twake/frontend/src/app/views/applications/drive/browser.tsx index bd62468038..c46c48f1de 100644 --- a/twake/frontend/src/app/views/applications/drive/browser.tsx +++ b/twake/frontend/src/app/views/applications/drive/browser.tsx @@ -1,4 +1,4 @@ -import { ChevronDownIcon } from '@heroicons/react/outline'; +import { ChevronDownIcon, PlusIcon } from '@heroicons/react/outline'; import { Button } from 'app/atoms/button/button'; import { Base, BaseSmall, Info, Subtitle, Title } from 'app/atoms/text'; import Menu from 'app/components/menus/menu'; @@ -6,14 +6,16 @@ import { getFilesTree } from 'app/components/uploads/file-tree-utils'; import UploadZone from 'app/components/uploads/upload-zone'; import { setTwakeTabToken } from 'app/features/drive/api-client/api-client'; import { useDriveActions } from 'app/features/drive/hooks/use-drive-actions'; -import { useDriveItem } from 'app/features/drive/hooks/use-drive-item'; +import { useDriveItem, usePublicLink } from 'app/features/drive/hooks/use-drive-item'; import { useDriveRealtime } from 'app/features/drive/hooks/use-drive-realtime'; import { useDriveUpload } from 'app/features/drive/hooks/use-drive-upload'; import { DriveItemSelectedList } from 'app/features/drive/state/store'; import { formatBytes } from 'app/features/drive/utils'; +import { ToasterService } from 'app/features/global/services/toaster-service'; +import { copyToClipboard } from 'app/features/global/utils/CopyClipboard'; import useRouterCompany from 'app/features/router/hooks/use-router-company'; import _ from 'lodash'; -import { Suspense, useCallback, useEffect, useRef } from 'react'; +import { memo, Suspense, useCallback, useEffect, useRef } from 'react'; import { atomFamily, useRecoilState, useSetRecoilState } from 'recoil'; import { DrivePreview } from '../viewer/drive-preview'; import HeaderPath from './header-path'; @@ -32,311 +34,341 @@ export const DriveCurrentFolderAtom = atomFamily({ default: startingParentId => startingParentId || 'root', }); -export default ({ - initialParentId, - twakeTabContextToken, -}: { - initialParentId?: string; - twakeTabContextToken?: string; -}) => { - const companyId = useRouterCompany(); - setTwakeTabToken(twakeTabContextToken || null); +export default memo( + ({ + initialParentId, + twakeTabContextToken, + }: { + initialParentId?: string; + twakeTabContextToken?: string; + }) => { + const companyId = useRouterCompany(); + setTwakeTabToken(twakeTabContextToken || null); - const [parentId, setParentId] = useRecoilState(DriveCurrentFolderAtom(initialParentId || 'root')); + const [parentId, setParentId] = useRecoilState( + DriveCurrentFolderAtom(initialParentId || 'root'), + ); - const { download, downloadZip, update } = useDriveActions(); - const { access, item, inTrash, refresh, children, loading, path } = useDriveItem(parentId); - const { item: trash } = useDriveItem('trash'); - const { uploadTree, uploadFromUrl } = useDriveUpload(); - useDriveRealtime(parentId); + const { download, downloadZip, update } = useDriveActions(); + const { access, item, inTrash, refresh, children, loading, path } = useDriveItem(parentId); + const publicLink = usePublicLink(item); + const { item: trash } = useDriveItem('trash'); + const { uploadTree, uploadFromUrl } = useDriveUpload(); + useDriveRealtime(parentId); - const uploadZone = 'drive_' + companyId; - const uploadZoneRef = useRef(null); + const uploadZone = 'drive_' + companyId; + const uploadZoneRef = useRef(null); - const setCreationModalState = useSetRecoilState(CreateModalAtom); - const setSelectorModalState = useSetRecoilState(SelectorModalAtom); - const setConfirmDeleteModalState = useSetRecoilState(ConfirmDeleteModalAtom); - const setConfirmTrashModalState = useSetRecoilState(ConfirmTrashModalAtom); - const [checked, setChecked] = useRecoilState(DriveItemSelectedList); + const setCreationModalState = useSetRecoilState(CreateModalAtom); + const setSelectorModalState = useSetRecoilState(SelectorModalAtom); + const setConfirmDeleteModalState = useSetRecoilState(ConfirmDeleteModalAtom); + const setConfirmTrashModalState = useSetRecoilState(ConfirmTrashModalAtom); + const [checked, setChecked] = useRecoilState(DriveItemSelectedList); - useEffect(() => { - setChecked({}); - refresh(parentId); - refresh('trash'); - }, [parentId, refresh]); + useEffect(() => { + setChecked({}); + refresh(parentId); + refresh('trash'); + }, [parentId, refresh]); - const openItemModal = useCallback(() => { - if (item?.id) setCreationModalState({ open: true, parent_id: item.id }); - }, [item?.id, setCreationModalState]); + const openItemModal = useCallback(() => { + if (item?.id) setCreationModalState({ open: true, parent_id: item.id }); + }, [item?.id, setCreationModalState]); - const selectedCount = Object.values(checked).filter(v => v).length; - const folders = children.filter(i => i.is_directory).sort((a, b) => a.name.localeCompare(b.name)); - const documents = ( - item?.is_directory === false - ? //We use this hack for public shared single file - item - ? [item] - : [] - : children - ) - .filter(i => !i.is_directory) - .sort((a, b) => a.name.localeCompare(b.name)); - return ( - { - const tree = await getFilesTree(event); - setCreationModalState({ parent_id: '', open: false }); - uploadTree(tree, { - companyId, - parentId, - }); - }} - > - uploadZoneRef.current?.open()} - addFromUrl={(url, name) => - uploadFromUrl(url, name, { + const selectedCount = Object.values(checked).filter(v => v).length; + const folders = children + .filter(i => i.is_directory) + .sort((a, b) => a.name.localeCompare(b.name)); + const documents = ( + item?.is_directory === false + ? //We use this hack for public shared single file + item + ? [item] + : [] + : children + ) + .filter(i => !i.is_directory) + .sort((a, b) => a.name.localeCompare(b.name)); + return ( + { + const tree = await getFilesTree(event); + setCreationModalState({ parent_id: '', open: false }); + uploadTree(tree, { companyId, parentId, - }) - } - /> - - - - - - }> - - - -
- {(window as any).canary && ( -
- - This is the new Drive, your documents are not migrated here yet, you can exit canary - to see all your previous documents. Documents added here will not be visible yet on - production but will be kept after the final migration. - -
- )} -
- -
- {access !== 'read' && ( - {formatBytes(item?.size || 0)} used in this folder + uploadZoneRef.current?.open()} + addFromUrl={(url, name) => + uploadFromUrl(url, name, { + companyId, + parentId, + }) + } + /> + + + + + + }> + + + +
+ {document.location.origin.includes('canary') && ( +
+ + Welcome to the next version of Twake Drive. +
+ Your documents are not migrated yet, you can switch back to{' '} + https://web.twake.app to see all your documents. + Documents added here will not be visible yet on production but will be kept after + the final migration. + +
)} - - setSelectorModalState({ - open: true, - parent_id: inTrash ? 'root' : parentId, - title: 'Move ' + selectedCount + ' items', - mode: 'move', - onSelected: async ids => { - for (const item of children.filter(c => checked[c.id])) { - await update( - { - parent_id: ids[0], - }, - item.id, - item.parent_id, - ); - } - setChecked({}); - }, - }), - }, - { - type: 'menu', - text: 'Download ' + selectedCount + ' items', - onClick: () => - selectedCount === 1 - ? download(Object.keys(checked)[0]) - : downloadZip(Object.keys(checked)), - }, - { type: 'separator', hide: access === 'read' }, - { - type: 'menu', - text: 'Delete ' + selectedCount + ' items', - hide: !inTrash || access !== 'manage', - className: 'error', - onClick: () => { - setConfirmDeleteModalState({ - open: true, - items: children.filter(a => checked[a.id]), - }); +
+ +
+ {access !== 'read' && ( + {formatBytes(item?.size || 0)} used in this folder + )} + + setSelectorModalState({ + open: true, + parent_id: inTrash ? 'root' : parentId, + title: 'Move ' + selectedCount + ' items', + mode: 'move', + onSelected: async ids => { + for (const item of children.filter(c => checked[c.id])) { + await update( + { + parent_id: ids[0], + }, + item.id, + item.parent_id, + ); + } + setChecked({}); + }, + }), + }, + { + type: 'menu', + text: 'Download ' + selectedCount + ' items', + onClick: () => + selectedCount === 1 + ? download(Object.keys(checked)[0]) + : downloadZip(Object.keys(checked)), + }, + { type: 'separator', hide: access === 'read' }, + { + type: 'menu', + text: 'Delete ' + selectedCount + ' items', + hide: !inTrash || access !== 'manage', + className: 'error', + onClick: () => { + setConfirmDeleteModalState({ + open: true, + items: children.filter(a => checked[a.id]), + }); + }, + }, + { + type: 'menu', + text: 'Move ' + selectedCount + ' items to trash', + hide: inTrash || access === 'read', + className: 'error', + onClick: async () => + setConfirmTrashModalState({ + open: true, + items: children.filter(a => checked[a.id]), + }), }, - }, - { - type: 'menu', - text: 'Move ' + selectedCount + ' items to trash', - hide: inTrash || access === 'read', - className: 'error', - onClick: async () => - setConfirmTrashModalState({ - open: true, - items: children.filter(a => checked[a.id]), - }), - }, - ] - : inTrash - ? [ - { - type: 'menu', - text: 'Exit trash', - onClick: () => setParentId('root'), - }, - { type: 'separator' }, - { - type: 'menu', - text: 'Empty trash', - className: 'error', - hide: parentId != 'trash' || access !== 'manage', - onClick: () => { - setConfirmDeleteModalState({ - open: true, - items: children, //Fixme: Here it works because this menu is displayed only in the trash root folder - }); + ] + : inTrash + ? [ + { + type: 'menu', + text: 'Exit trash', + onClick: () => setParentId('root'), + }, + { type: 'separator' }, + { + type: 'menu', + text: 'Empty trash', + className: 'error', + hide: parentId != 'trash' || access !== 'manage', + onClick: () => { + setConfirmDeleteModalState({ + open: true, + items: children, //Fixme: Here it works because this menu is displayed only in the trash root folder + }); + }, + }, + ] + : [ + { + type: 'menu', + text: 'Add document or folder', + hide: inTrash || access === 'read', + onClick: () => openItemModal(), }, - }, - ] - : [ - { - type: 'menu', - text: 'Download folder', - hide: inTrash, - onClick: () => downloadZip([parentId]), - }, - { - type: 'menu', - text: 'Add document or folder', - hide: inTrash || access === 'read', - onClick: () => openItemModal(), - }, - { type: 'separator' }, - { - type: 'menu', - text: 'Go to trash', - hide: inTrash || access === 'read', - onClick: () => setParentId('trash'), - }, - ] - } - > - {' '} - - -
+ { + type: 'menu', + text: 'Download folder', + hide: inTrash, + onClick: () => downloadZip([parentId]), + }, + { + type: 'menu', + text: 'Copy public link', + hide: + !item?.access_info?.public?.level || + item?.access_info?.public?.level === 'none', + onClick: () => { + copyToClipboard(publicLink); + ToasterService.success('Public link copied to clipboard'); + }, + }, + { type: 'separator' }, + { + type: 'menu', + text: 'Go to trash', + hide: inTrash || access === 'read', + onClick: () => setParentId('trash'), + }, + ] + } + > + {' '} + -
-
- )} - {access !== 'read' && item?.id === 'root' && ( -
- Welcome to your company drive. -
- - {formatBytes(item?.size || 0)} - <Base> used, </Base> <Base>{formatBytes(trash?.size || 0)} in trash</Base> - -
+
- )} - {item?.id !== 'root' && item?.id !== 'trash' &&
} - -
- {folders.length > 0 && ( - <> - Folders - {folders.map((item, index) => ( - { - return setParentId(item.id); - }} - checked={checked[item.id] || false} - onCheck={v => setChecked(_.pickBy({ ...checked, [item.id]: v }, _.identity))} - parentAccess={access} - /> - ))} -
- + {!(inTrash || access === 'read') && ( +
openItemModal()} + > + +
)} - Documents - - {documents.length === 0 && !loading && ( -
- Nothing here. - {!inTrash && access != 'read' && ( - <> - - Drag and drop files to upload them or click on the 'Add document' button. - -
- - - )} + {item?.id === 'trash' && ( +
+ You are in the trash +
+ +
+
+ )} + {access !== 'read' && item?.id === 'root' && ( +
+ Welcome to your company drive. +
+ + {formatBytes(item?.size || 0)} + <Base> used, </Base> <Base>{formatBytes(trash?.size || 0)} in trash</Base> + +
)} + {item?.id !== 'root' && item?.id !== 'trash' &&
} - {documents.map((item, index) => ( - {}} - item={item} - checked={checked[item.id] || false} - onCheck={v => setChecked(_.pickBy({ ...checked, [item.id]: v }, _.identity))} - parentAccess={access} - /> - ))} +
+ {folders.length > 0 && ( + <> + Folders + + {folders.map((item, index) => ( + { + return setParentId(item.id); + }} + checked={checked[item.id] || false} + onCheck={v => setChecked(_.pickBy({ ...checked, [item.id]: v }, _.identity))} + parentAccess={access} + /> + ))} +
+ + )} + + Documents + + {documents.length === 0 && !loading && ( +
+ Nothing here. + {!inTrash && access != 'read' && ( + <> + + Drag and drop files to upload them or click on the 'Add document' button. + +
+ + + )} +
+ )} + + {documents.map((item, index) => ( + setChecked(_.pickBy({ ...checked, [item.id]: v }, _.identity))} + parentAccess={access} + /> + ))} +
-
- - ); -}; + + ); + }, +); diff --git a/twake/frontend/src/app/views/applications/drive/header-path.tsx b/twake/frontend/src/app/views/applications/drive/header-path.tsx index 23f607d579..3ec3d5f497 100644 --- a/twake/frontend/src/app/views/applications/drive/header-path.tsx +++ b/twake/frontend/src/app/views/applications/drive/header-path.tsx @@ -32,7 +32,7 @@ export const PathRender = ({ onClick: (id: string) => void; }) => { return ( - + <Title className="overflow-hidden whitespace-nowrap mr-2 pl-px inline-flex py-5"> {(path || [])?.map((a, i) => ( <PathItem key={a.id} @@ -64,7 +64,7 @@ const PathItem = ({ '-ml-px shrink overflow-hidden whitespace-nowrap last:flex-[1_0_auto_!important] first:flex-[0_0_auto] first:flex-shrink-0 ' + (!first ? 'rounded-l-none ' : '') + (!last ? 'rounded-r-none ' : '') + - ((!first && !last) ? 'max-w-[15ch] ' : '') + (!first && !last ? 'max-w-[15ch] ' : '') } onClick={() => { onClick(item?.id || ''); diff --git a/twake/frontend/src/app/views/applications/drive/item-row/common.tsx b/twake/frontend/src/app/views/applications/drive/item-row/common.tsx index fde7c3e7de..bc9d3a401e 100644 --- a/twake/frontend/src/app/views/applications/drive/item-row/common.tsx +++ b/twake/frontend/src/app/views/applications/drive/item-row/common.tsx @@ -7,7 +7,7 @@ export type DriveItemProps = { className: string; onCheck: (status: boolean) => void; checked: boolean; - onClick: () => void; + onClick?: () => void; inTrash?: boolean; parentAccess?: 'read' | 'write' | 'manage'; }; diff --git a/twake/frontend/src/app/views/applications/drive/item-row/document-row.tsx b/twake/frontend/src/app/views/applications/drive/item-row/document-row.tsx index 2bda63e20f..b11d1f4ae7 100644 --- a/twake/frontend/src/app/views/applications/drive/item-row/document-row.tsx +++ b/twake/frontend/src/app/views/applications/drive/item-row/document-row.tsx @@ -13,6 +13,7 @@ import { Base, BaseSmall } from 'app/atoms/text'; import Menu from 'app/components/menus/menu'; import { useDriveActions } from 'app/features/drive/hooks/use-drive-actions'; import { usePublicLink } from 'app/features/drive/hooks/use-drive-item'; +import { useDrivePreview } from 'app/features/drive/hooks/use-drive-preview'; import { formatBytes } from 'app/features/drive/utils'; import fileUploadApiClient from 'app/features/files/api/file-upload-api-client'; import { ToasterService } from 'app/features/global/services/toaster-service'; @@ -41,7 +42,7 @@ export const DocumentRow = ({ }: DriveItemProps) => { const [hover, setHover] = useState(false); const { download, update } = useDriveActions(); - const { open } = useFileViewerModal(); + const { open } = useDrivePreview(); const publicLink = usePublicLink(item); const setVersionModal = useSetRecoilState(VersionsModalAtom); @@ -75,13 +76,13 @@ export const DocumentRow = ({ onMouseLeave={() => setHover(false)} onClick={e => { if (e.shiftKey || e.ctrlKey) onCheck(!checked); - else onClick(); + else if (onClick) onClick(); + else preview(); }} > <div onClick={e => { e.stopPropagation(); - preview(); }} > <CheckableIcon @@ -150,7 +151,7 @@ export const DocumentRow = ({ }, { type: 'menu', - text: 'Public access', + text: 'Manage access', hide: parentAccess === 'read', onClick: () => setAccessModalState({ open: true, id: item.id }), }, diff --git a/twake/frontend/src/app/views/applications/drive/item-row/folder-row.tsx b/twake/frontend/src/app/views/applications/drive/item-row/folder-row.tsx index 86f01c1694..512c44590e 100644 --- a/twake/frontend/src/app/views/applications/drive/item-row/folder-row.tsx +++ b/twake/frontend/src/app/views/applications/drive/item-row/folder-row.tsx @@ -50,7 +50,7 @@ export const FolderRow = ({ onMouseLeave={() => setHover(false)} onClick={e => { if (e.shiftKey || e.ctrlKey) onCheck(!checked); - else onClick(); + else if (onClick) onClick(); }} > <div onClick={e => e.stopPropagation()}> @@ -90,7 +90,7 @@ export const FolderRow = ({ }, { type: 'menu', - text: 'Public access', + text: 'Manage access', hide: parentAccess === 'read', onClick: () => setAccessModalState({ open: true, id: item.id }), }, diff --git a/twake/frontend/src/app/views/applications/drive/modals/update-access/internal-access.tsx b/twake/frontend/src/app/views/applications/drive/modals/update-access/internal-access.tsx index bbbfe00de3..7ae3b860d2 100644 --- a/twake/frontend/src/app/views/applications/drive/modals/update-access/internal-access.tsx +++ b/twake/frontend/src/app/views/applications/drive/modals/update-access/internal-access.tsx @@ -93,7 +93,7 @@ export const InternalAccessManager = ({ id, disabled }: { id: string; disabled: <Base>Channel access</Base> <br /> <Info> - Channels ({channelEntities.length}) from Twake chat tabs have access to this item. + {channelEntities.length} channel(s) from Twake chat have access to this item. </Info> </div> <div className="shrink-0 ml-2"> diff --git a/twake/frontend/src/app/views/applications/drive/twake-tab-configuration.tsx b/twake/frontend/src/app/views/applications/drive/twake-tab-configuration.tsx index 476201de0a..336081f1b3 100644 --- a/twake/frontend/src/app/views/applications/drive/twake-tab-configuration.tsx +++ b/twake/frontend/src/app/views/applications/drive/twake-tab-configuration.tsx @@ -11,14 +11,15 @@ import { SelectorModalAtom } from './modals/selector'; export default ({ context }: { context?: EmbedContext }) => { const { tab, setTab, loading } = useDriveTwakeTab(context?.channelId || '', context?.tabId || ''); - const { item, loading: itemLoading } = useDriveItem(tab?.item_id || ''); - const [modifyConfiguration, setModifyConfiguration] = useState(false); + const { item, loading: itemLoading, refresh } = useDriveItem(tab?.item_id || ''); const setSelectorModalState = useSetRecoilState(SelectorModalAtom); - if (loading || itemLoading) return <></>; + useEffect(() => { + if (tab?.item_id) refresh(tab!.item_id); + }, [tab?.item_id, refresh]); // If nothing is configured, then show the selector and when selected the folder will give access to the whole channel - const modalOpen = !tab || !item || modifyConfiguration; + const modalOpen = (!tab || !item) && !loading && !itemLoading; const isConfigured = tab && item; useEffect(() => { @@ -35,6 +36,8 @@ export default ({ context }: { context?: EmbedContext }) => { } }, [modalOpen]); + if (!item && !tab && (loading || itemLoading)) return <></>; + // If configured then show the content of the tab and forward the fact that the access is done through a specific channel return ( <div> @@ -44,13 +47,29 @@ export default ({ context }: { context?: EmbedContext }) => { twakeTabContextToken={context?.channelId + '+' + context?.tabId} /> )} - {!isConfigured && ( - <div className="w-full text-center"> - <Info>This Documents tabs is not configured yet.</Info> - <br /> - <Button theme="outlined" className="mt-4" onClick={() => setModifyConfiguration(true)}> - Configure - </Button> + {!isConfigured && !loading && !itemLoading && ( + <div className="w-full h-full flex items-center justify-center"> + <div className="text-center"> + <Info>This Documents tabs is not configured yet.</Info> + <br /> + <Button + theme="outlined" + className="mt-4" + onClick={() => + setSelectorModalState({ + open: true, + parent_id: 'root', + mode: 'move', + title: `Select what folder this tab should display`, + onSelected: async ids => { + await setTab(ids[0], 'write'); + }, + }) + } + > + Configure + </Button> + </div> </div> )} </div> diff --git a/twake/frontend/src/app/views/client/main-view/AppView/AppView.tsx b/twake/frontend/src/app/views/client/main-view/AppView/AppView.tsx index 79447fcedb..bc1343deb9 100644 --- a/twake/frontend/src/app/views/client/main-view/AppView/AppView.tsx +++ b/twake/frontend/src/app/views/client/main-view/AppView/AppView.tsx @@ -27,6 +27,8 @@ const AppView: FC<PropsType> = props => { const app = props.viewService.getConfiguration().app; + if (!channel) return <NoApp />; + switch (app?.identity?.code) { case 'twake_drive': return (