diff --git a/packages/web/src/components/artist-pick-modal/ArtistPickModal.tsx b/packages/web/src/components/artist-pick-modal/ArtistPickModal.tsx index a023bd1e58c..01e3fd4b2aa 100644 --- a/packages/web/src/components/artist-pick-modal/ArtistPickModal.tsx +++ b/packages/web/src/components/artist-pick-modal/ArtistPickModal.tsx @@ -14,6 +14,7 @@ import { } from '@audius/harmony' import { useDispatch } from 'react-redux' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { useSelector } from 'utils/reducer' const { setArtistPick, unsetArtistPick } = tracksSocialActions @@ -40,7 +41,7 @@ const messagesMap = { } } -export const ArtistPickModal = () => { +const ArtistPickModalContent = () => { const { isOpen, onClose, @@ -86,3 +87,10 @@ export const ArtistPickModal = () => { ) } + +export const ArtistPickModal = componentWithErrorBoundary( + ArtistPickModalContent, + { + name: 'ArtistPickModal' + } +) diff --git a/packages/web/src/components/artist-recommendations/ArtistRecommendations.tsx b/packages/web/src/components/artist-recommendations/ArtistRecommendations.tsx index 7d8ded61e86..00dc26ab6e3 100644 --- a/packages/web/src/components/artist-recommendations/ArtistRecommendations.tsx +++ b/packages/web/src/components/artist-recommendations/ArtistRecommendations.tsx @@ -22,6 +22,7 @@ import { useDispatch, useSelector } from 'react-redux' import { make, useRecord } from 'common/store/analytics/actions' import { ArtistPopover } from 'components/artist/ArtistPopover' import DynamicImage from 'components/dynamic-image/DynamicImage' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import LoadingSpinner from 'components/loading-spinner/LoadingSpinner' import { MountPlacement } from 'components/types' import UserBadges from 'components/user-badges/UserBadges' @@ -119,7 +120,7 @@ const ArtistPopoverWrapper = ({ ) } -export const ArtistRecommendations = forwardRef< +const ArtistRecommendationsContent = forwardRef< HTMLDivElement, ArtistRecommendationsProps >((props, ref) => { @@ -266,3 +267,10 @@ export const ArtistRecommendations = forwardRef< ) }) + +export const ArtistRecommendations = componentWithErrorBoundary( + ArtistRecommendationsContent, + { + name: 'ArtistRecommendations' + } +) diff --git a/packages/web/src/components/artist-recommendations/ArtistRecommendationsDropdown.tsx b/packages/web/src/components/artist-recommendations/ArtistRecommendationsDropdown.tsx index 67c62597258..9028754f04f 100644 --- a/packages/web/src/components/artist-recommendations/ArtistRecommendationsDropdown.tsx +++ b/packages/web/src/components/artist-recommendations/ArtistRecommendationsDropdown.tsx @@ -3,6 +3,8 @@ import { useRef } from 'react' // eslint-disable-next-line no-restricted-imports -- TODO: migrate to @react-spring/web import { useSpring, animated } from 'react-spring' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' + import { ArtistRecommendations, ArtistRecommendationsProps @@ -21,7 +23,7 @@ const fast = { friction: 40 } -export const ArtistRecommendationsDropdown = ( +const ArtistRecommendationsDropdownContent = ( props: ArtistRecommendationsDropdownProps ) => { const { isVisible } = props @@ -48,3 +50,10 @@ export const ArtistRecommendationsDropdown = ( ) } + +export const ArtistRecommendationsDropdown = componentWithErrorBoundary( + ArtistRecommendationsDropdownContent, + { + name: 'ArtistRecommendationsDropdown' + } +) diff --git a/packages/web/src/components/artist-recommendations/ArtistRecommendationsPopup.tsx b/packages/web/src/components/artist-recommendations/ArtistRecommendationsPopup.tsx index 73e6a88222e..21fa37c2436 100644 --- a/packages/web/src/components/artist-recommendations/ArtistRecommendationsPopup.tsx +++ b/packages/web/src/components/artist-recommendations/ArtistRecommendationsPopup.tsx @@ -5,6 +5,7 @@ import { cacheUsersSelectors } from '@audius/common/store' import { Popup } from '@audius/harmony' import { useSelector } from 'react-redux' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { useMainContentRef } from 'pages/MainContentContext' import { AppState } from 'store/types' import zIndex from 'utils/zIndex' @@ -20,7 +21,7 @@ type Props = { onClose: () => void } -export const ArtistRecommendationsPopup = (props: Props) => { +const ArtistRecommendationsPopupContent = (props: Props) => { const { anchorRef, artistId, isVisible, onClose } = props const mainContentRef = useMainContentRef() @@ -58,3 +59,10 @@ export const ArtistRecommendationsPopup = (props: Props) => { ) } + +export const ArtistRecommendationsPopup = componentWithErrorBoundary( + ArtistRecommendationsPopupContent, + { + name: 'ArtistRecommendationsPopup' + } +) diff --git a/packages/web/src/components/artist/ArtistCard.tsx b/packages/web/src/components/artist/ArtistCard.tsx index 15f8a9977c7..4d251294309 100644 --- a/packages/web/src/components/artist/ArtistCard.tsx +++ b/packages/web/src/components/artist/ArtistCard.tsx @@ -5,6 +5,7 @@ import { profilePageActions, usersSocialActions } from '@audius/common/store' import { FollowButton } from '@audius/harmony' import { useDispatch } from 'react-redux' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import Stats, { StatProps } from 'components/stats/Stats' import styles from './ArtistCard.module.css' @@ -18,7 +19,7 @@ type ArtistCardProps = { onNavigateAway: () => void } -export const ArtistCard = (props: ArtistCardProps) => { +export const ArtistCardContent = (props: ArtistCardProps) => { const { artist, onNavigateAway } = props const { user_id, @@ -108,3 +109,7 @@ export const ArtistCard = (props: ArtistCardProps) => { ) } + +export const ArtistCard = componentWithErrorBoundary(ArtistCardContent, { + name: 'ArtistCard' +}) diff --git a/packages/web/src/components/artist/ArtistPopover.tsx b/packages/web/src/components/artist/ArtistPopover.tsx index 4d0b2300bff..e60cadbf2df 100644 --- a/packages/web/src/components/artist/ArtistPopover.tsx +++ b/packages/web/src/components/artist/ArtistPopover.tsx @@ -9,6 +9,7 @@ import Popover from 'antd/lib/popover' import cn from 'classnames' import { useSelector } from 'common/hooks/useSelector' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { MountPlacement } from 'components/types' import { ArtistCard } from './ArtistCard' @@ -43,7 +44,7 @@ type ArtistPopoverProps = { className?: string } -export const ArtistPopover = ({ +const ArtistPopoverContent = ({ handle, children, placement = Placement.RightBottom, @@ -110,3 +111,7 @@ export const ArtistPopover = ({ ) } + +export const ArtistPopover = componentWithErrorBoundary(ArtistPopoverContent, { + name: 'ArtistPopover' +}) diff --git a/packages/web/src/components/artist/ArtistSupporting.tsx b/packages/web/src/components/artist/ArtistSupporting.tsx index ce5c98dcf7e..023274fbf7e 100644 --- a/packages/web/src/components/artist/ArtistSupporting.tsx +++ b/packages/web/src/components/artist/ArtistSupporting.tsx @@ -10,6 +10,7 @@ import { MAX_ARTIST_HOVER_TOP_SUPPORTING } from '@audius/common/utils' import { IconTipping as IconTip } from '@audius/harmony' import { useDispatch } from 'react-redux' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { UserProfilePictureList } from 'components/notification/Notification/components/UserProfilePictureList' import { setUsers, @@ -31,7 +32,8 @@ type ArtistSupportingProps = { artist: User onNavigateAway?: () => void } -export const ArtistSupporting = (props: ArtistSupportingProps) => { + +const ArtistSupportingContent = (props: ArtistSupportingProps) => { const { artist, onNavigateAway } = props const { user_id, supporting_count } = artist const dispatch = useDispatch() @@ -92,3 +94,10 @@ export const ArtistSupporting = (props: ArtistSupportingProps) => {
) : null } + +export const ArtistSupporting = componentWithErrorBoundary( + ArtistSupportingContent, + { + name: 'ArtistSupporting' + } +) diff --git a/packages/web/src/components/comments/ArtistPick.tsx b/packages/web/src/components/comments/ArtistPick.tsx index ea4df6eca38..eb62d8cde20 100644 --- a/packages/web/src/components/comments/ArtistPick.tsx +++ b/packages/web/src/components/comments/ArtistPick.tsx @@ -1,5 +1,7 @@ import { IconHeart, IconPin, IconText } from '@audius/harmony' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' + const pinIcon = { icon: IconPin } const heartIcon = { icon: IconHeart, color: 'active' } @@ -8,7 +10,7 @@ type ArtistPickProps = { isLiked?: boolean } -export const ArtistPick = ({ +const ArtistPickContent = ({ isPinned = false, isLiked = false }: ArtistPickProps) => { @@ -24,3 +26,7 @@ export const ArtistPick = ({ ) } + +export const ArtistPick = componentWithErrorBoundary(ArtistPickContent, { + name: 'ArtistPick' +}) diff --git a/packages/web/src/components/dynamic-image/DynamicImage.tsx b/packages/web/src/components/dynamic-image/DynamicImage.tsx index 8fa3a448d30..248e51d9023 100644 --- a/packages/web/src/components/dynamic-image/DynamicImage.tsx +++ b/packages/web/src/components/dynamic-image/DynamicImage.tsx @@ -11,6 +11,7 @@ import { Box } from '@audius/harmony' import cn from 'classnames' import transparentPlaceholderImg from 'assets/img/1x1-transparent.png' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import Skeleton from 'components/skeleton/Skeleton' import styles from './DynamicImage.module.css' @@ -95,7 +96,7 @@ const fadeIn = ( /** * A dynamic image that transitions between changes to the `image` prop. */ -const DynamicImage = ({ +const DynamicImageContent = ({ image, isUrl = true, wrapperClassName, @@ -202,4 +203,11 @@ const DynamicImage = ({ ) } -export default memo(DynamicImage) +const DynamicImageWithErrorBoundary = componentWithErrorBoundary( + memo(DynamicImageContent), + { + name: 'DynamicImage' + } +) + +export default DynamicImageWithErrorBoundary diff --git a/packages/web/src/components/error-wrapper/componentWithErrorBoundary.tsx b/packages/web/src/components/error-wrapper/componentWithErrorBoundary.tsx new file mode 100644 index 00000000000..938195dfe77 --- /dev/null +++ b/packages/web/src/components/error-wrapper/componentWithErrorBoundary.tsx @@ -0,0 +1,68 @@ +import React, { ReactNode, useCallback } from 'react' + +import { ErrorLevel } from '@audius/common/models' +import { + ErrorBoundary, + ErrorBoundaryProps, + FallbackProps +} from 'react-error-boundary' +import { useDispatch } from 'react-redux' + +import { handleError as handleErrorAction } from 'store/errors/actions' + +interface ComponentWithErrorBoundaryOptions { + fallback?: ReactNode | null + /** + * Debugging name (for logs, error reporting) + */ + name?: string +} + +type HandleError = NonNullable + +export function componentWithErrorBoundary

( + WrappedComponent: React.ComponentType

, + options: ComponentWithErrorBoundaryOptions = {} +) { + const { + fallback = null, + name = WrappedComponent.displayName || WrappedComponent.name || 'Unnamed' + } = options + + const ComponentWithErrorBoundaryWrapper = (props: P) => { + const dispatch = useDispatch() + + const handleError: HandleError = useCallback( + (error, errorInfo) => { + console.error(`ComponentErrorBoundary (${name}):`, error, errorInfo) + dispatch( + handleErrorAction({ + name: `ComponentErrorBoundary: ${name}`, + message: error.message, + shouldRedirect: false, + additionalInfo: errorInfo, + level: ErrorLevel.Error + }) + ) + }, + [dispatch] + ) + + const fallbackRender = useCallback((_: FallbackProps) => { + return React.isValidElement(fallback) ? fallback : <>{fallback} + }, []) + + return ( + + + + ) + } + + ComponentWithErrorBoundaryWrapper.displayName = `WithErrorBoundary(${name})` + if (WrappedComponent.displayName) { + ComponentWithErrorBoundaryWrapper.displayName += `|${WrappedComponent.displayName}` + } + + return ComponentWithErrorBoundaryWrapper +} diff --git a/packages/web/src/components/follow-artist-card/FollowArtistCard.tsx b/packages/web/src/components/follow-artist-card/FollowArtistCard.tsx index 811e7bda7ce..bdddddcf4b3 100644 --- a/packages/web/src/components/follow-artist-card/FollowArtistCard.tsx +++ b/packages/web/src/components/follow-artist-card/FollowArtistCard.tsx @@ -24,6 +24,7 @@ import { useHover } from 'react-use' import { make } from 'common/store/analytics/actions' import { Avatar } from 'components/avatar/Avatar' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import Skeleton from 'components/skeleton/Skeleton' import { useCoverPhoto } from 'hooks/useCoverPhoto' import { useMedia } from 'hooks/useMedia' @@ -34,7 +35,7 @@ type FollowArtistTileProps = { user: UserMetadata } & HTMLProps -export const FollowArtistCard = (props: FollowArtistTileProps) => { +const FollowArtistCardContent = (props: FollowArtistTileProps) => { const { user: { name, user_id, is_verified, track_count, follower_count } } = props @@ -216,6 +217,13 @@ export const FollowArtistCard = (props: FollowArtistTileProps) => { ) } +export const FollowArtistCard = componentWithErrorBoundary( + FollowArtistCardContent, + { + name: 'FollowArtistCard' + } +) + export const FollowArtistTileSkeleton = () => { const { isMobile } = useMedia() diff --git a/packages/web/src/components/nav/desktop/LeftNavLink.tsx b/packages/web/src/components/nav/desktop/LeftNavLink.tsx index 5783534c462..72139fc5089 100644 --- a/packages/web/src/components/nav/desktop/LeftNavLink.tsx +++ b/packages/web/src/components/nav/desktop/LeftNavLink.tsx @@ -6,6 +6,7 @@ import { useDispatch } from 'react-redux' import { NavLink, useLocation } from 'react-router-dom' import { make } from 'common/store/analytics/actions' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { RestrictionType, useRequiresAccountOnClick @@ -18,7 +19,7 @@ export type LeftNavLinkProps = Omit & { exact?: boolean } -export const LeftNavLink = (props: LeftNavLinkProps) => { +const LeftNavLinkContent = (props: LeftNavLinkProps) => { const { to, disabled, @@ -71,3 +72,7 @@ export const LeftNavLink = (props: LeftNavLinkProps) => { ) } + +export const LeftNavLink = componentWithErrorBoundary(LeftNavLinkContent, { + name: 'LeftNavLink' +}) diff --git a/packages/web/src/components/nav/desktop/RestrictedExpandableNavItem.tsx b/packages/web/src/components/nav/desktop/RestrictedExpandableNavItem.tsx index 01a45071682..bda3e30d9a5 100644 --- a/packages/web/src/components/nav/desktop/RestrictedExpandableNavItem.tsx +++ b/packages/web/src/components/nav/desktop/RestrictedExpandableNavItem.tsx @@ -2,13 +2,14 @@ import { useCallback } from 'react' import { ExpandableNavItem, ExpandableNavItemProps } from '@audius/harmony' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { RestrictionType, useRequiresAccountFn } from 'hooks/useRequiresAccount' type Props = Omit & { restriction?: RestrictionType } -export const RestrictedExpandableNavItem = ({ +const RestrictedExpandableNavItemContent = ({ restriction = 'none', disabled, ...props @@ -25,3 +26,10 @@ export const RestrictedExpandableNavItem = ({ ) } + +export const RestrictedExpandableNavItem = componentWithErrorBoundary( + RestrictedExpandableNavItemContent, + { + name: 'RestrictedExpandableNavItem' + } +) diff --git a/packages/web/src/components/notification/Notification/components/NotificationTile.tsx b/packages/web/src/components/notification/Notification/components/NotificationTile.tsx index b966b5b1805..fa40b3f8467 100644 --- a/packages/web/src/components/notification/Notification/components/NotificationTile.tsx +++ b/packages/web/src/components/notification/Notification/components/NotificationTile.tsx @@ -9,6 +9,7 @@ import { Notification } from '@audius/common/store' import cn from 'classnames' import { useDispatch } from 'react-redux' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { closeNotificationPanel } from 'store/application/ui/notifications/notificationsUISlice' import styles from './NotificationTile.module.css' @@ -23,7 +24,7 @@ type NotificationTileProps = { disableClosePanel?: boolean } -export const NotificationTile = (props: NotificationTileProps) => { +const NotificationTileContent = (props: NotificationTileProps) => { const { notification, onClick, children, disabled, disableClosePanel } = props const { isViewed } = notification const dispatch = useDispatch() @@ -52,3 +53,10 @@ export const NotificationTile = (props: NotificationTileProps) => {

) } + +export const NotificationTile = componentWithErrorBoundary( + NotificationTileContent, + { + name: 'NotificationTile' + } +) diff --git a/packages/web/src/components/notification/Notification/components/UserProfilePictureList.tsx b/packages/web/src/components/notification/Notification/components/UserProfilePictureList.tsx index 22d40fd3162..1d0cb27d2b4 100644 --- a/packages/web/src/components/notification/Notification/components/UserProfilePictureList.tsx +++ b/packages/web/src/components/notification/Notification/components/UserProfilePictureList.tsx @@ -6,6 +6,7 @@ import { formatCount } from '@audius/common/utils' import cn from 'classnames' import { useDispatch } from 'react-redux' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import Tooltip from 'components/tooltip/Tooltip' import { setUsers as setUserListUsers, @@ -47,7 +48,7 @@ export type UserProfileListProps = { profilePictureClassname?: string } -export const UserProfilePictureList = ({ +const UserProfilePictureListContent = ({ users, totalUserCount, limit = USER_LENGTH_LIMIT, @@ -148,3 +149,10 @@ export const UserProfilePictureList = ({ ) } + +export const UserProfilePictureList = componentWithErrorBoundary( + UserProfilePictureListContent, + { + name: 'UserProfilePictureList' + } +) diff --git a/packages/web/src/components/notification/NotificationPanel.tsx b/packages/web/src/components/notification/NotificationPanel.tsx index 1bb95aecf0c..55a8c7b6c19 100644 --- a/packages/web/src/components/notification/NotificationPanel.tsx +++ b/packages/web/src/components/notification/NotificationPanel.tsx @@ -18,6 +18,7 @@ import InfiniteScroll from 'react-infinite-scroller' import { useDispatch, useSelector } from 'react-redux' import { useSearchParam } from 'react-use' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import LoadingSpinner from 'components/loading-spinner/LoadingSpinner' import { getModalNotification, @@ -68,7 +69,7 @@ const SCROLL_THRESHOLD = 1000 /** The notification panel displays the list of notifications w/ a * summary of each notification and a link to open the full * notification in a modal */ -export const NotificationPanel = ({ anchorRef }: NotificationPanelProps) => { +const NotificationPanelContent = ({ anchorRef }: NotificationPanelProps) => { const panelIsOpen = useSelector(getNotificationPanelIsOpen) const notifications = useSelector(selectAllNotifications) const hasMore = useSelector(getNotificationHasMore) @@ -206,3 +207,10 @@ export const NotificationPanel = ({ anchorRef }: NotificationPanelProps) => { ) } + +export const NotificationPanel = componentWithErrorBoundary( + NotificationPanelContent, + { + name: 'NotificationPanel' + } +) diff --git a/packages/web/src/components/play-bar/desktop/components/SocialActions.tsx b/packages/web/src/components/play-bar/desktop/components/SocialActions.tsx index c610f49f32f..17cdd28473c 100644 --- a/packages/web/src/components/play-bar/desktop/components/SocialActions.tsx +++ b/packages/web/src/components/play-bar/desktop/components/SocialActions.tsx @@ -13,6 +13,7 @@ import { useSelector } from 'react-redux' import FavoriteButton from 'components/alt-button/FavoriteButton' import RepostButton from 'components/alt-button/RepostButton' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import Tooltip from 'components/tooltip/Tooltip' import { GatedConditionsPill } from 'components/track/GatedConditionsPill' import { useRequiresAccountOnClick } from 'hooks/useRequiresAccount' @@ -39,7 +40,7 @@ const messages = { repost: 'Repost' } -export const SocialActions = ({ +const SocialActionsContent = ({ trackId, uid, isOwner, @@ -138,3 +139,7 @@ export const SocialActions = ({ ) } + +export const SocialActions = componentWithErrorBoundary(SocialActionsContent, { + name: 'SocialActions' +}) diff --git a/packages/web/src/components/search-bar/SearchTag.tsx b/packages/web/src/components/search-bar/SearchTag.tsx index 2d6d9e2af6f..fad26bad7c3 100644 --- a/packages/web/src/components/search-bar/SearchTag.tsx +++ b/packages/web/src/components/search-bar/SearchTag.tsx @@ -6,6 +6,7 @@ import { Tag, TagProps } from '@audius/harmony' import { Link } from 'react-router-dom' import { make, useRecord } from 'common/store/analytics/actions' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' type TagClickingEvent = Extract< AllTrackingEvents, @@ -17,7 +18,7 @@ type SearchTagProps = Extract & { source: TagClickingEvent['source'] } -export const SearchTag = (props: SearchTagProps) => { +const SearchTagContent = (props: SearchTagProps) => { const { onClick, source, children, ...other } = props const record = useRecord() @@ -37,3 +38,7 @@ export const SearchTag = (props: SearchTagProps) => { ) } + +export const SearchTag = componentWithErrorBoundary(SearchTagContent, { + name: 'SearchTag' +}) diff --git a/packages/web/src/components/stats/FavoriteStats.tsx b/packages/web/src/components/stats/FavoriteStats.tsx index 672d61c9df3..6969592c426 100644 --- a/packages/web/src/components/stats/FavoriteStats.tsx +++ b/packages/web/src/components/stats/FavoriteStats.tsx @@ -9,6 +9,7 @@ import { formatCount, pluralize } from '@audius/common/utils' import { IconHeart, PlainButton, PlainButtonProps } from '@audius/harmony' import { useDispatch, useSelector } from 'react-redux' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { setUsers, setVisibility @@ -32,7 +33,7 @@ type FavoriteStatsProps = { noText?: boolean } & Partial> -export const FavoriteStats = ({ +const FavoriteStatsContent = ({ id, entityType, noText, @@ -79,3 +80,7 @@ export const FavoriteStats = ({ ) } + +export const FavoriteStats = componentWithErrorBoundary(FavoriteStatsContent, { + name: 'FavoriteStats' +}) diff --git a/packages/web/src/components/suggested-tracks/SuggestedTracks.tsx b/packages/web/src/components/suggested-tracks/SuggestedTracks.tsx index d28627bd2c1..9ccff1978ff 100644 --- a/packages/web/src/components/suggested-tracks/SuggestedTracks.tsx +++ b/packages/web/src/components/suggested-tracks/SuggestedTracks.tsx @@ -15,6 +15,7 @@ import { animated, useSpring } from '@react-spring/web' import { useToggle } from 'react-use' import DynamicImage from 'components/dynamic-image/DynamicImage' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import LoadingSpinner from 'components/loading-spinner/LoadingSpinner' import Skeleton from 'components/skeleton/Skeleton' import { UserNameAndBadges } from 'components/user-name-and-badges/UserNameAndBadges' @@ -92,7 +93,7 @@ type SuggestedTracksProps = { collectionId: ID } -export const SuggestedTracks = (props: SuggestedTracksProps) => { +const SuggestedTracksContent = (props: SuggestedTracksProps) => { const { collectionId } = props const { suggestedTracks, onRefresh, onAddTrack, isRefreshing } = useGetSuggestedPlaylistTracks(collectionId) @@ -155,3 +156,10 @@ export const SuggestedTracks = (props: SuggestedTracksProps) => { ) } + +export const SuggestedTracks = componentWithErrorBoundary( + SuggestedTracksContent, + { + name: 'SuggestedTracks' + } +) diff --git a/packages/web/src/components/track/AiTrackSection.tsx b/packages/web/src/components/track/AiTrackSection.tsx index 4479437a057..30c2199f0d3 100644 --- a/packages/web/src/components/track/AiTrackSection.tsx +++ b/packages/web/src/components/track/AiTrackSection.tsx @@ -13,6 +13,7 @@ import { useDispatch } from 'react-redux' import { useSelector } from 'common/hooks/useSelector' import { ArtistPopover } from 'components/artist/ArtistPopover' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import UserBadges from 'components/user-badges/UserBadges' import { emptyStringGuard } from 'pages/track-page/utils' import { push } from 'utils/navigation' @@ -35,7 +36,7 @@ type AiTrackSectionProps = { descriptionClassName?: string } -export const AiTrackSection = ({ +const AiTrackSectionContent = ({ attributedUserId, className, descriptionClassName @@ -91,3 +92,10 @@ export const AiTrackSection = ({ ) } + +export const AiTrackSection = componentWithErrorBoundary( + AiTrackSectionContent, + { + name: 'AiTrackSection' + } +) diff --git a/packages/web/src/components/track/TrackMetadataList.tsx b/packages/web/src/components/track/TrackMetadataList.tsx index 0eb5412a662..927b90bf9fe 100644 --- a/packages/web/src/components/track/TrackMetadataList.tsx +++ b/packages/web/src/components/track/TrackMetadataList.tsx @@ -4,6 +4,7 @@ import { Flex } from '@audius/harmony' import { Mood } from '@audius/sdk' import { MetadataItem } from 'components/entity/MetadataItem' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { moodMap } from 'utils/Moods' type TrackMetadataListProps = { @@ -13,7 +14,7 @@ type TrackMetadataListProps = { /** * The additional metadata shown at the bottom of the Track Page Header */ -export const TrackMetadataList = (props: TrackMetadataListProps) => { +const TrackMetadataListContent = (props: TrackMetadataListProps) => { const { trackId } = props const metadataItems = useTrackMetadata({ trackId @@ -35,3 +36,10 @@ export const TrackMetadataList = (props: TrackMetadataListProps) => { ) } + +export const TrackMetadataList = componentWithErrorBoundary( + TrackMetadataListContent, + { + name: 'TrackMetadataList' + } +) diff --git a/packages/web/src/components/track/TrackStats.tsx b/packages/web/src/components/track/TrackStats.tsx index 37a3309778d..ec7cdd4e53a 100644 --- a/packages/web/src/components/track/TrackStats.tsx +++ b/packages/web/src/components/track/TrackStats.tsx @@ -13,6 +13,7 @@ import { } from '@audius/harmony' import { useDispatch } from 'react-redux' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { make, track as trackEvent } from 'services/analytics' import * as userListActions from 'store/application/ui/userListModal/slice' import { @@ -32,7 +33,7 @@ type TrackStatsProps = { scrollToCommentSection: () => void } -export const TrackStats = (props: TrackStatsProps) => { +const TrackStatsContent = (props: TrackStatsProps) => { const { trackId, scrollToCommentSection } = props const { data: track } = useGetTrackById({ id: trackId }) const { data: currentUserId } = useGetCurrentUserId({}) @@ -135,3 +136,7 @@ export const TrackStats = (props: TrackStatsProps) => { ) } + +export const TrackStats = componentWithErrorBoundary(TrackStatsContent, { + name: 'TrackStats' +}) diff --git a/packages/web/src/components/track/TrackTileMetrics.tsx b/packages/web/src/components/track/TrackTileMetrics.tsx index 4a6eccb860f..bee11bd5999 100644 --- a/packages/web/src/components/track/TrackTileMetrics.tsx +++ b/packages/web/src/components/track/TrackTileMetrics.tsx @@ -13,6 +13,7 @@ import { useDispatch } from 'react-redux' import { AvatarList } from 'components/avatar' import { UserName, VanityMetric } from 'components/entity/VanityMetrics' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { TrackTileSize } from 'components/track/types' import { useIsMobile } from 'hooks/useIsMobile' import { make, track as trackEvent } from 'services/analytics' @@ -35,10 +36,10 @@ const { getTrack } = cacheTracksSelectors type RepostsMetricProps = { trackId: ID - size?: TrackTileSize + size: TrackTileSize } -export const RepostsMetric = (props: RepostsMetricProps) => { +const RepostsMetricContent = (props: RepostsMetricProps) => { const { trackId, size } = props const repostCount = useSelector((state) => { @@ -106,11 +107,15 @@ export const RepostsMetric = (props: RepostsMetricProps) => { ) } +export const RepostsMetric = componentWithErrorBoundary(RepostsMetricContent, { + name: 'RepostsMetric' +}) + type SavesMetricProps = { trackId: ID } -export const SavesMetric = (props: SavesMetricProps) => { +const SavesMetricContent = (props: SavesMetricProps) => { const { trackId } = props const saveCount = useSelector((state) => { return getTrack(state, { id: trackId })?.save_count @@ -144,12 +149,16 @@ export const SavesMetric = (props: SavesMetricProps) => { ) } +export const SavesMetric = componentWithErrorBoundary(SavesMetricContent, { + name: 'SavesMetric' +}) + type CommentMetricProps = { trackId: ID size: TrackTileSize } -export const CommentMetric = (props: CommentMetricProps) => { +const CommentMetricContent = (props: CommentMetricProps) => { const { trackId, size } = props const isMobile = useIsMobile() const commentCount = useSelector((state) => { @@ -191,11 +200,15 @@ export const CommentMetric = (props: CommentMetricProps) => { ) } +export const CommentMetric = componentWithErrorBoundary(CommentMetricContent, { + name: 'CommentMetric' +}) + type PlayMetricProps = { trackId: ID } -export const PlayMetric = (props: PlayMetricProps) => { +const PlayMetricContent = (props: PlayMetricProps) => { const { trackId } = props const playCount = useSelector((state) => { return getTrack(state, { id: trackId })?.play_count ?? 0 @@ -205,3 +218,7 @@ export const PlayMetric = (props: PlayMetricProps) => { return {formatCount(playCount)} Plays } + +export const PlayMetric = componentWithErrorBoundary(PlayMetricContent, { + name: 'PlayMetric' +}) diff --git a/packages/web/src/components/track/TrackTileStats.tsx b/packages/web/src/components/track/TrackTileStats.tsx index d42f904e378..132baba99a0 100644 --- a/packages/web/src/components/track/TrackTileStats.tsx +++ b/packages/web/src/components/track/TrackTileStats.tsx @@ -3,6 +3,7 @@ import { ID } from '@audius/common/models' import { cacheTracksSelectors } from '@audius/common/store' import { Flex, Skeleton } from '@audius/harmony' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { EntityRank } from 'components/lineup/EntityRank' import { useIsMobile } from 'hooks/useIsMobile' import { useSelector } from 'utils/reducer' @@ -27,7 +28,7 @@ type TrackTileStatsProps = { isLoading?: boolean } -export const TrackTileStats = (props: TrackTileStatsProps) => { +const TrackTileStatsContent = (props: TrackTileStatsProps) => { const { trackId, isTrending, rankIndex, size, isLoading } = props const isUnlockable = useIsTrackUnlockable(trackId) @@ -70,3 +71,10 @@ export const TrackTileStats = (props: TrackTileStatsProps) => { ) } + +export const TrackTileStats = componentWithErrorBoundary( + TrackTileStatsContent, + { + name: 'TrackTileStats' + } +) diff --git a/packages/web/src/components/track/desktop/ConnectedPlaylistTile.tsx b/packages/web/src/components/track/desktop/ConnectedPlaylistTile.tsx index 74f1731d527..60ab9183a87 100644 --- a/packages/web/src/components/track/desktop/ConnectedPlaylistTile.tsx +++ b/packages/web/src/components/track/desktop/ConnectedPlaylistTile.tsx @@ -40,6 +40,7 @@ import { Dispatch } from 'redux' import { TrackEvent, make } from 'common/store/analytics/actions' import { Draggable } from 'components/dragndrop' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { UserLink } from 'components/link' import { OwnProps as CollectionkMenuProps } from 'components/menu/CollectionMenu' import Menu from 'components/menu/Menu' @@ -93,7 +94,7 @@ type ConnectedPlaylistTileProps = OwnProps & ReturnType & ReturnType -const ConnectedPlaylistTile = ({ +const ConnectedPlaylistTileContent = ({ ordered, index, size, @@ -521,7 +522,11 @@ function mapDispatchToProps(dispatch: Dispatch) { } } -export default connect( +const ConnectedComponent = connect( mapStateToProps, mapDispatchToProps -)(memo(ConnectedPlaylistTile)) +)(memo(ConnectedPlaylistTileContent)) + +export default componentWithErrorBoundary(ConnectedComponent, { + name: 'ConnectedPlaylistTile' +}) diff --git a/packages/web/src/components/track/desktop/ConnectedTrackTile.tsx b/packages/web/src/components/track/desktop/ConnectedTrackTile.tsx index 157e9b19740..330407738e4 100644 --- a/packages/web/src/components/track/desktop/ConnectedTrackTile.tsx +++ b/packages/web/src/components/track/desktop/ConnectedTrackTile.tsx @@ -25,6 +25,7 @@ import { Dispatch } from 'redux' import { useModalState } from 'common/hooks/useModalState' import { Draggable } from 'components/dragndrop' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { UserLink } from 'components/link' import Menu from 'components/menu/Menu' import { OwnProps as TrackMenuProps } from 'components/menu/TrackMenu' @@ -39,6 +40,7 @@ import { TrackTileSize } from '../types' import styles from './ConnectedTrackTile.module.css' import TrackTile from './TrackTile' + const { getUid, getPlaying, getBuffering } = playerSelectors const { requestOpen: requestOpenShareModal } = shareModalUIActions const { getTrack } = cacheTracksSelectors @@ -68,7 +70,7 @@ type ConnectedTrackTileProps = OwnProps & ReturnType & ReturnType -const ConnectedTrackTile = ({ +const ConnectedTrackTileContent = ({ uid, index, size, @@ -372,7 +374,11 @@ function mapDispatchToProps(dispatch: Dispatch) { } } -export default connect( +const ConnectedComponent = connect( mapStateToProps, mapDispatchToProps -)(memo(ConnectedTrackTile)) +)(memo(ConnectedTrackTileContent)) + +export default componentWithErrorBoundary(ConnectedComponent, { + name: 'ConnectedTrackTile' +}) diff --git a/packages/web/src/components/track/desktop/PlaylistTile.tsx b/packages/web/src/components/track/desktop/PlaylistTile.tsx index f98a1b63945..fe766276a62 100644 --- a/packages/web/src/components/track/desktop/PlaylistTile.tsx +++ b/packages/web/src/components/track/desktop/PlaylistTile.tsx @@ -8,6 +8,7 @@ import { } from '@audius/harmony' import cn from 'classnames' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { TrackTileSize, DesktopPlaylistTileProps as PlaylistTileProps @@ -27,7 +28,7 @@ const DefaultTileContainer = ({ children }: { children: ReactNode }) => ( // When we separate track tile from playlist tile, this will be removed. const onClick = () => {} -const PlaylistTile = ({ +const PlaylistTileContent = ({ size, order, isFavorited, @@ -191,4 +192,6 @@ const PlaylistTile = ({ ) } -export default memo(PlaylistTile) +export default componentWithErrorBoundary(memo(PlaylistTileContent), { + name: 'PlaylistTile' +}) diff --git a/packages/web/src/components/track/desktop/TrackListItem.tsx b/packages/web/src/components/track/desktop/TrackListItem.tsx index d7e6c82ad3d..325a728ae30 100644 --- a/packages/web/src/components/track/desktop/TrackListItem.tsx +++ b/packages/web/src/components/track/desktop/TrackListItem.tsx @@ -14,6 +14,7 @@ import { IconKebabHorizontal } from '@audius/harmony' import cn from 'classnames' import { ArtistPopover } from 'components/artist/ArtistPopover' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import Menu from 'components/menu/Menu' import { OwnProps as TrackMenuProps } from 'components/menu/TrackMenu' import Skeleton from 'components/skeleton/Skeleton' @@ -46,7 +47,7 @@ type TrackListItemProps = { isLastTrack?: boolean } -const TrackListItem = ({ +const TrackListItemContent = ({ track, active, disableActions, @@ -219,4 +220,6 @@ const TrackListItem = ({ ) } -export default memo(TrackListItem) +export default componentWithErrorBoundary(memo(TrackListItemContent), { + name: 'TrackListItem' +}) diff --git a/packages/web/src/components/track/desktop/TrackTile.tsx b/packages/web/src/components/track/desktop/TrackTile.tsx index 19e6e2454c8..26ef0557837 100644 --- a/packages/web/src/components/track/desktop/TrackTile.tsx +++ b/packages/web/src/components/track/desktop/TrackTile.tsx @@ -25,6 +25,7 @@ import { useSelector } from 'react-redux' import { CollectionDogEar } from 'components/collection' import { CollectionTileStats } from 'components/collection/CollectionTileStats' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { TextLink } from 'components/link' import Skeleton from 'components/skeleton/Skeleton' import { useRequiresAccountOnClick } from 'hooks/useRequiresAccount' @@ -69,7 +70,7 @@ const RankAndIndexIndicator = ({ ) } -const TrackTile = ({ +const TrackTileContent = ({ size, order, standalone, @@ -325,4 +326,6 @@ const TrackTile = ({ ) } -export default memo(TrackTile) +export default componentWithErrorBoundary(memo(TrackTileContent), { + name: 'TrackTile' +}) diff --git a/packages/web/src/components/trending-genre-selection/components/TrendingGenreSelectionPage.tsx b/packages/web/src/components/trending-genre-selection/components/TrendingGenreSelectionPage.tsx index 638c643f32d..8a098b797a0 100644 --- a/packages/web/src/components/trending-genre-selection/components/TrendingGenreSelectionPage.tsx +++ b/packages/web/src/components/trending-genre-selection/components/TrendingGenreSelectionPage.tsx @@ -1,5 +1,6 @@ import { useEffect, useContext } from 'react' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import MobilePageContainer from 'components/mobile-page-container/MobilePageContainer' import NavContext, { LeftPreset } from 'components/nav/mobile/NavContext' import GenreSelectionList from 'pages/trending-page/components/GenreSelectionList' @@ -16,7 +17,7 @@ const messages = { title: 'PICK A GENRE' } -const TrendingGenreSelectionPage = ({ +const TrendingGenreSelectionPageContent = ({ selectedGenre, didSelectGenre, genres @@ -42,4 +43,6 @@ const TrendingGenreSelectionPage = ({ ) } -export default TrendingGenreSelectionPage +export default componentWithErrorBoundary(TrendingGenreSelectionPageContent, { + name: 'TrendingGenreSelectionPage' +}) diff --git a/packages/web/src/components/user-card/UserCard.tsx b/packages/web/src/components/user-card/UserCard.tsx index 36d0db21a5c..f867f501294 100644 --- a/packages/web/src/components/user-card/UserCard.tsx +++ b/packages/web/src/components/user-card/UserCard.tsx @@ -8,6 +8,7 @@ import { useLinkClickHandler } from 'react-router-dom-v5-compat' import { Avatar } from 'components/avatar' import { Card, CardProps, CardFooter, CardContent } from 'components/card' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { UserLink } from 'components/link' import { useSelector } from 'utils/reducer' @@ -31,7 +32,7 @@ export type UserCardProps = Omit & { onUserLinkClick?: (e: MouseEvent) => void } -export const UserCard = (props: UserCardProps) => { +const UserCardContent = (props: UserCardProps) => { const { id, loading, size, onClick, onUserLinkClick, ...other } = props const user = useSelector((state) => getUser(state, { id })) @@ -100,3 +101,7 @@ export const UserCard = (props: UserCardProps) => { ) } + +export const UserCard = componentWithErrorBoundary(UserCardContent, { + name: 'UserCard' +}) diff --git a/packages/web/src/components/user-list-modal/components/UserListModal.tsx b/packages/web/src/components/user-list-modal/components/UserListModal.tsx index 4ca99a0ea61..431eca0da3e 100644 --- a/packages/web/src/components/user-list-modal/components/UserListModal.tsx +++ b/packages/web/src/components/user-list-modal/components/UserListModal.tsx @@ -45,6 +45,7 @@ import { ChatBlastAudience } from '@audius/sdk' import { useRouteMatch } from 'react-router-dom' import { useSelector } from 'common/hooks/useSelector' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { UserList } from 'components/user-list/UserList' import { ChatBlastWithAudienceCTA } from 'pages/chat-page/components/ChatBlastWithAudienceCTA' import { UserListType } from 'store/application/ui/userListModal/types' @@ -91,7 +92,7 @@ const messages = { remixers: 'Remixers' } -const UserListModal = ({ +const UserListModalContent = ({ userListType, isOpen, onClose @@ -287,4 +288,6 @@ const UserListModal = ({ ) } -export default UserListModal +export default componentWithErrorBoundary(UserListModalContent, { + name: 'UserListModal' +}) diff --git a/packages/web/src/components/user-list/UserList.tsx b/packages/web/src/components/user-list/UserList.tsx index 20e33e1f032..85ebe2048b5 100644 --- a/packages/web/src/components/user-list/UserList.tsx +++ b/packages/web/src/components/user-list/UserList.tsx @@ -22,6 +22,7 @@ import { useDispatch, useSelector } from 'react-redux' import loadingSpinner from 'assets/animations/loadingSpinner.json' import ArtistChip from 'components/artist/ArtistChip' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { MountPlacement } from 'components/types' import * as unfollowConfirmationActions from 'components/unfollow-confirmation-modal/store/actions' import { useIsMobile } from 'hooks/useIsMobile' @@ -56,7 +57,7 @@ type UserListProps = { onNavigateAway?: () => void } -export const UserList = ({ +const UserListContent = ({ tag, stateSelector, userIdSelector, @@ -194,3 +195,7 @@ export const UserList = ({ ) } + +export const UserList = componentWithErrorBoundary(UserListContent, { + name: 'UserList' +}) diff --git a/packages/web/src/components/user-name-and-badges/UserNameAndBadges.tsx b/packages/web/src/components/user-name-and-badges/UserNameAndBadges.tsx index 90bdac35506..0a90c67c1ac 100644 --- a/packages/web/src/components/user-name-and-badges/UserNameAndBadges.tsx +++ b/packages/web/src/components/user-name-and-badges/UserNameAndBadges.tsx @@ -8,6 +8,7 @@ import cn from 'classnames' import { useSelector } from 'react-redux' import { ArtistPopover } from 'components/artist/ArtistPopover' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import UserBadges from 'components/user-badges/UserBadges' import { useNavigateToPage } from 'hooks/useNavigateToPage' @@ -74,10 +75,17 @@ function isIdProps( return (props as UserNameAndBadgesWithIdProps).userId != null } -export const UserNameAndBadges = (props: UserNameAndBadgesProps) => { +const UserNameAndBadgesContent = (props: UserNameAndBadgesProps) => { return isIdProps(props) ? ( ) : ( ) } + +export const UserNameAndBadges = componentWithErrorBoundary( + UserNameAndBadgesContent, + { + name: 'UserNameAndBadges' + } +) diff --git a/packages/web/src/pages/dashboard-page/components/ArtistCard.tsx b/packages/web/src/pages/dashboard-page/components/ArtistCard.tsx index bf76661d18c..d97a4ed50bf 100644 --- a/packages/web/src/pages/dashboard-page/components/ArtistCard.tsx +++ b/packages/web/src/pages/dashboard-page/components/ArtistCard.tsx @@ -3,6 +3,7 @@ import { route } from '@audius/common/utils' import { Text } from '@audius/harmony' import DynamicImage from 'components/dynamic-image/DynamicImage' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import UserBadges from 'components/user-badges/UserBadges' import { useCoverPhoto } from 'hooks/useCoverPhoto' import { useNavigateToPage } from 'hooks/useNavigateToPage' @@ -18,7 +19,7 @@ type ArtistCardProps = { name: string } -export const ArtistCard = ({ userId, handle, name }: ArtistCardProps) => { +const ArtistCardContent = ({ userId, handle, name }: ArtistCardProps) => { const profilePicture = useProfilePicture({ userId, size: SquareSizes.SIZE_150_BY_150 @@ -58,3 +59,7 @@ export const ArtistCard = ({ userId, handle, name }: ArtistCardProps) => { ) } + +export const ArtistCard = componentWithErrorBoundary(ArtistCardContent, { + name: 'ArtistCard' +}) diff --git a/packages/web/src/pages/profile-page/components/desktop/ProfileTopTags.tsx b/packages/web/src/pages/profile-page/components/desktop/ProfileTopTags.tsx index 9b734c72cb9..52c74cbd493 100644 --- a/packages/web/src/pages/profile-page/components/desktop/ProfileTopTags.tsx +++ b/packages/web/src/pages/profile-page/components/desktop/ProfileTopTags.tsx @@ -1,6 +1,7 @@ import { useTopTags } from '@audius/common/api' import { Paper, IconTrending } from '@audius/harmony' +import { componentWithErrorBoundary } from 'components/error-wrapper/componentWithErrorBoundary' import { SearchTag } from 'components/search-bar/SearchTag' import { useProfileParams } from 'pages/profile-page/useProfileParams' @@ -13,7 +14,7 @@ const messages = { const MOST_USED_TAGS_COUNT = 5 -export const ProfileTopTags = () => { +const ProfileTopTagsContent = () => { const user = useProfileParams() const { data: topTags, isPending } = useTopTags({ userId: user?.user_id, @@ -38,3 +39,10 @@ export const ProfileTopTags = () => { ) } + +export const ProfileTopTags = componentWithErrorBoundary( + ProfileTopTagsContent, + { + name: 'ProfileTopTags' + } +)