diff --git a/src/pages/home/report/LinkPreviewer.js b/src/pages/home/report/LinkPreviewer.js
deleted file mode 100644
index f62295a6fb71..000000000000
--- a/src/pages/home/report/LinkPreviewer.js
+++ /dev/null
@@ -1,139 +0,0 @@
-import {uniqBy} from 'lodash';
-import PropTypes from 'prop-types';
-import React from 'react';
-import {Image, View} from 'react-native';
-import _ from 'underscore';
-import Text from '@components/Text';
-import TextLink from '@components/TextLink';
-import useStyleUtils from '@hooks/useStyleUtils';
-import useTheme from '@hooks/useTheme';
-import useThemeStyles from '@hooks/useThemeStyles';
-import variables from '@styles/variables';
-
-const IMAGE_TYPES = ['jpg', 'jpeg', 'png'];
-const MAX_IMAGE_HEIGHT = 180;
-const MAX_IMAGE_WIDTH = 340;
-
-const propTypes = {
- /** Data about links provided in message. */
- linkMetadata: PropTypes.arrayOf(
- PropTypes.shape({
- /** The URL of the link. */
- url: PropTypes.string,
-
- /** A description of the link. */
- description: PropTypes.string,
-
- /** The title of the link. */
- title: PropTypes.string,
-
- /** The publisher of the link. */
- publisher: PropTypes.string,
-
- /** The image associated with the link. */
- image: PropTypes.shape({
- /** The height of the image. */
- height: PropTypes.number,
-
- /** The width of the image. */
- width: PropTypes.number,
-
- /** The URL of the image. */
- url: PropTypes.string,
- }),
-
- /** The provider logo associated with the link. */
- logo: PropTypes.shape({
- /** The height of the logo. */
- height: PropTypes.number,
-
- /** The width of the logo. */
- width: PropTypes.number,
-
- /** The URL of the logo. */
- url: PropTypes.string,
- }),
- }),
- ),
-
- /** Maximum amount of visible link previews. -1 means unlimited amount of previews */
- maxAmountOfPreviews: PropTypes.number,
-};
-
-const defaultProps = {
- linkMetadata: [],
- maxAmountOfPreviews: -1,
-};
-
-function LinkPreviewer(props) {
- const theme = useTheme();
- const styles = useThemeStyles();
- const StyleUtils = useStyleUtils();
- return _.map(
- _.take(uniqBy(props.linkMetadata, 'url'), props.maxAmountOfPreviews >= 0 ? Math.min(props.maxAmountOfPreviews, props.linkMetadata.length) : props.linkMetadata.length),
- (linkData) => {
- if (_.isArray(linkData)) {
- return;
- }
- const {description, image, title, logo, publisher, url} = linkData;
-
- return (
- linkData && (
-
-
- {!_.isEmpty(logo) && (
-
- )}
- {!_.isEmpty(publisher) && (
-
- {publisher}
-
- )}
-
- {!_.isEmpty(title) && (
-
- {title}
-
- )}
- {!_.isEmpty(description) && {description}}
- {!_.isEmpty(image) && IMAGE_TYPES.includes(image.type) && (
-
- )}
-
- )
- );
- },
- );
-}
-
-LinkPreviewer.propTypes = propTypes;
-LinkPreviewer.defaultProps = defaultProps;
-LinkPreviewer.displayName = 'ReportLinkPreview';
-
-export default LinkPreviewer;
diff --git a/src/pages/home/report/LinkPreviewer.tsx b/src/pages/home/report/LinkPreviewer.tsx
new file mode 100644
index 000000000000..209096d2fa15
--- /dev/null
+++ b/src/pages/home/report/LinkPreviewer.tsx
@@ -0,0 +1,105 @@
+import React from 'react';
+import {Image, View} from 'react-native';
+import Text from '@components/Text';
+import TextLink from '@components/TextLink';
+import useStyleUtils from '@hooks/useStyleUtils';
+import useTheme from '@hooks/useTheme';
+import useThemeStyles from '@hooks/useThemeStyles';
+import variables from '@styles/variables';
+import type {LinkMetadata} from '@src/types/onyx/ReportAction';
+
+const IMAGE_TYPES = ['jpg', 'jpeg', 'png'];
+const MAX_IMAGE_HEIGHT = 180;
+const MAX_IMAGE_WIDTH = 340;
+
+type LinkPreviewerProps = {
+ /** Data about links provided in message. */
+ linkMetadata?: LinkMetadata[];
+
+ /** Maximum amount of visible link previews. -1 means unlimited amount of previews */
+ maxAmountOfPreviews?: number;
+};
+
+function filterNonUniqueLinks(linkMetadata: LinkMetadata[]): LinkMetadata[] {
+ const linksMap = new Map();
+ const result: LinkMetadata[] = [];
+
+ linkMetadata.forEach((item) => {
+ if (!item.url || linksMap.has(item.url)) {
+ return;
+ }
+
+ linksMap.set(item.url, item.url);
+ result.push(item);
+ });
+
+ return result;
+}
+
+function LinkPreviewer({linkMetadata = [], maxAmountOfPreviews = -1}: LinkPreviewerProps) {
+ const theme = useTheme();
+ const styles = useThemeStyles();
+ const StyleUtils = useStyleUtils();
+ const uniqueLinks = filterNonUniqueLinks(linkMetadata);
+ const maxAmmountOfLinks = maxAmountOfPreviews >= 0 ? Math.min(maxAmountOfPreviews, linkMetadata.length) : linkMetadata.length;
+ const linksToShow = uniqueLinks.slice(0, maxAmmountOfLinks);
+ return linksToShow.map((linkData) => {
+ if (!linkData && Array.isArray(linkData)) {
+ return;
+ }
+ const {description, image, title, logo, publisher, url} = linkData;
+ return (
+
+
+ {logo && (
+
+ )}
+ {publisher && (
+
+ {publisher}
+
+ )}
+
+ {title && url && (
+
+ {title}
+
+ )}
+ {description && {description}}
+ {image?.type && IMAGE_TYPES.includes(image.type) && image.width && image.height && (
+
+ )}
+
+ );
+ });
+}
+
+LinkPreviewer.displayName = 'ReportLinkPreview';
+
+export default LinkPreviewer;
diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts
index 8e56aaa67345..a881b63fbb95 100644
--- a/src/types/onyx/ReportAction.ts
+++ b/src/types/onyx/ReportAction.ts
@@ -63,6 +63,9 @@ type ImageMetadata = {
/** The URL of the image. */
url?: string;
+
+ /** The type of the image. */
+ type?: string;
};
type LinkMetadata = {
@@ -197,4 +200,4 @@ type ReportAction = ReportActionBase & OriginalMessage;
type ReportActions = Record;
export default ReportAction;
-export type {ReportActions, ReportActionBase, Message};
+export type {ReportActions, ReportActionBase, Message, LinkMetadata};