Show friendly message for non-previewable file previews (#16372)

<img width="1507" height="807" alt="Screenshot 2025-12-06 at 12 50
42 AM"
src="https://github.com/user-attachments/assets/9e13d813-76fa-43e9-9dea-ead1dca4ca1f"
/>

Closes #15771
This commit is contained in:
Abdul Rahman
2025-12-10 17:20:29 +05:30
committed by GitHub
parent 30e3aa5961
commit d932ec28e3
2 changed files with 69 additions and 21 deletions
@@ -15,7 +15,6 @@ import styled from '@emotion/styled';
import { useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { PREVIEWABLE_EXTENSIONS } from '@/activities/files/const/previewable-extensions.const';
import { FileIcon } from '@/file/components/FileIcon';
import { useHasPermissionFlag } from '@/settings/roles/hooks/useHasPermissionFlag';
import { IconCalendar, OverflowingTextWithTooltip } from 'twenty-ui/display';
@@ -90,10 +89,6 @@ export const AttachmentRow = ({
const { name: originalFileName, extension: attachmentFileExtension } =
getFileNameAndExtension(attachment.name);
const fileExtension =
attachmentFileExtension?.toLowerCase().replace('.', '') ?? '';
const isPreviewable = PREVIEWABLE_EXTENSIONS.includes(fileExtension);
const [attachmentFileName, setAttachmentFileName] =
useState(originalFileName);
@@ -180,22 +175,14 @@ export const AttachmentRow = ({
/>
) : (
<StyledLinkContainer>
{isPreviewable ? (
<StyledLink
onClick={handleOpenDocument}
href={attachment.fullPath}
>
<OverflowingTextWithTooltip text={attachment.name} />
</StyledLink>
) : (
<StyledLink
href={attachment.fullPath}
target="_blank"
rel="noopener noreferrer"
>
<OverflowingTextWithTooltip text={attachment.name} />
</StyledLink>
)}
<StyledLink
onClick={handleOpenDocument}
href={attachment.fullPath}
target="_blank"
rel="noopener noreferrer"
>
<OverflowingTextWithTooltip text={attachment.name} />
</StyledLink>
</StyledLinkContainer>
)}
</StyledLeftContent>
@@ -1,5 +1,7 @@
import { PREVIEWABLE_EXTENSIONS } from '@/activities/files/const/previewable-extensions.const';
import { downloadFile } from '@/activities/files/utils/downloadFile';
import { fetchCsvPreview } from '@/activities/files/utils/fetchCsvPreview';
import { getFileType } from '@/activities/files/utils/getFileType';
import DocViewer, { DocViewerRenderers } from '@cyntler/react-doc-viewer';
import '@cyntler/react-doc-viewer/dist/index.css';
import { useTheme } from '@emotion/react';
@@ -7,6 +9,8 @@ import styled from '@emotion/styled';
import { Trans } from '@lingui/react/macro';
import { useEffect, useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
import { IconDownload } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input';
import { getFileNameAndExtension } from '~/utils/file/getFileNameAndExtension';
const StyledDocumentViewerContainer = styled.div`
@@ -37,6 +41,29 @@ const StyledDocumentViewerContainer = styled.div`
}
`;
const StyledUnavailablePreviewContainer = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
gap: ${({ theme }) => theme.spacing(4)};
padding: ${({ theme }) => theme.spacing(8)};
text-align: center;
`;
const StyledMessage = styled.div`
color: ${({ theme }) => theme.font.color.secondary};
font-size: ${({ theme }) => theme.font.size.lg};
max-width: 400px;
`;
const StyledTitle = styled.div`
color: ${({ theme }) => theme.font.color.primary};
font-size: ${({ theme }) => theme.font.size.xl};
font-weight: ${({ theme }) => theme.font.weight.semiBold};
`;
type DocumentViewerProps = {
documentName: string;
documentUrl: string;
@@ -77,6 +104,9 @@ export const DocumentViewer = ({
const { extension } = getFileNameAndExtension(documentName);
const fileExtension = extension?.toLowerCase().replace('.', '') ?? '';
const fileCategory = getFileType(documentName);
const isPreviewable = PREVIEWABLE_EXTENSIONS.includes(fileExtension);
const mimeType = PREVIEWABLE_EXTENSIONS.includes(fileExtension)
? MIME_TYPE_MAPPING[fileExtension]
: undefined;
@@ -89,6 +119,37 @@ export const DocumentViewer = ({
}
}, [documentUrl, fileExtension]);
if (!isPreviewable) {
return (
<StyledDocumentViewerContainer>
<StyledUnavailablePreviewContainer>
<StyledTitle>
<Trans>Preview Not Available</Trans>
</StyledTitle>
<StyledMessage>
{fileCategory === 'ARCHIVE' ? (
<Trans>
Archive files cannot be previewed. Please download the file to
access its contents.
</Trans>
) : (
<Trans>
This file type cannot be previewed. Please download the file to
view it.
</Trans>
)}
</StyledMessage>
<Button
Icon={IconDownload}
title="Download File"
onClick={() => downloadFile(documentUrl, documentName)}
variant="secondary"
/>
</StyledUnavailablePreviewContainer>
</StyledDocumentViewerContainer>
);
}
if (fileExtension === 'csv' && !isDefined(csvPreview))
return (
<StyledDocumentViewerContainer>