diff --git a/packages/common/src/messages/comments.ts b/packages/common/src/messages/comments.ts new file mode 100644 index 00000000000..9705e1d9805 --- /dev/null +++ b/packages/common/src/messages/comments.ts @@ -0,0 +1,12 @@ +export const commentsMessages = { + postComment: 'Post Comment', + addComment: 'Add a comment', + firstComment: 'Be the first to comment!', + noComments: 'Nothing here yet', + noCommentsDescription: 'Be the first to comment on this track', + viewAll: 'View All', + showMoreReplies: 'Show More Replies', + replies: 'Replies', + showReplies: 'Show Replies', + hideReplies: 'Hide Replies' +} diff --git a/packages/common/src/messages/index.ts b/packages/common/src/messages/index.ts index c16e351f5a5..a42952ed55d 100644 --- a/packages/common/src/messages/index.ts +++ b/packages/common/src/messages/index.ts @@ -3,3 +3,4 @@ export * from './sign-on/signUpPolicyMessages' export * from './sign-on/pages' export * from './search' export * from './edit' +export * from './comments' diff --git a/packages/common/src/utils/route.ts b/packages/common/src/utils/route.ts index 037ab0f6c83..1e282cbcf78 100644 --- a/packages/common/src/utils/route.ts +++ b/packages/common/src/utils/route.ts @@ -125,6 +125,7 @@ export const EDIT_ALBUM_PAGE = '/:handle/album/:slug/edit' export const TRACK_PAGE = '/:handle/:slug' export const TRACK_EDIT_PAGE = '/:handle/:slug/edit' export const TRACK_REMIXES_PAGE = '/:handle/:slug/remixes' +export const TRACK_COMMENTS_PAGE = '/:handle/:slug/comments' export const PROFILE_PAGE = '/:handle' export const PROFILE_PAGE_TRACKS = '/:handle/tracks' export const PROFILE_PAGE_ALBUMS = '/:handle/albums' diff --git a/packages/mobile/src/components/comments/CommentForm.tsx b/packages/mobile/src/components/comments/CommentForm.tsx index 4711d2e87a3..57482dfc8d9 100644 --- a/packages/mobile/src/components/comments/CommentForm.tsx +++ b/packages/mobile/src/components/comments/CommentForm.tsx @@ -1,4 +1,5 @@ import { useCurrentCommentSection } from '@audius/common/context' +import { commentsMessages as messages } from '@audius/common/messages' import type { FormikHelpers } from 'formik' import { Formik, useFormikContext } from 'formik' import type { TextInput as RNTextInput } from 'react-native' @@ -20,11 +21,6 @@ type CommentFormProps = { TextInputComponent?: typeof RNTextInput } -const messages = { - beFirstComment: 'Be the first to comment', - addComment: 'Add a comment' -} - type CommentFormContentProps = Omit< CommentFormProps, 'onSubmit' | 'initialValue' @@ -35,9 +31,7 @@ const CommentFormContent = (props: CommentFormContentProps) => { const { currentUserId, comments } = useCurrentCommentSection() const { submitForm } = useFormikContext() - const message = comments?.length - ? messages.addComment - : messages.beFirstComment + const message = comments?.length ? messages.addComment : messages.firstComment return ( diff --git a/packages/mobile/src/components/comments/CommentSection.tsx b/packages/mobile/src/components/comments/CommentSection.tsx index 67d3edf756c..218537bb8c4 100644 --- a/packages/mobile/src/components/comments/CommentSection.tsx +++ b/packages/mobile/src/components/comments/CommentSection.tsx @@ -3,6 +3,7 @@ import { useCurrentCommentSection, usePostComment } from '@audius/common/context' +import { commentsMessages as messages } from '@audius/common/messages' import type { ID } from '@audius/common/models' import { Status } from '@audius/common/models' import { TouchableOpacity } from 'react-native' @@ -21,24 +22,16 @@ import Skeleton from '../skeleton' import { CommentBlock } from './CommentBlock' import { CommentForm } from './CommentForm' -const messages = { - noComments: 'Nothing here yet', - viewAll: 'View all' -} - const CommentSectionHeader = () => { const { - artistId, - currentUserId, entityId, commentSectionLoading: isLoading, - comments, - isEntityOwner + comments } = useCurrentCommentSection() const { onOpen: openDrawer } = useDrawer('Comment') const handlePressViewAll = () => { - openDrawer({ userId: currentUserId, entityId, isEntityOwner, artistId }) + openDrawer({ entityId }) } const isShowingComments = !isLoading && comments?.length diff --git a/packages/mobile/src/components/comments/CommentThread.tsx b/packages/mobile/src/components/comments/CommentThread.tsx index 7fd66e0b3b9..f07a3fd241f 100644 --- a/packages/mobile/src/components/comments/CommentThread.tsx +++ b/packages/mobile/src/components/comments/CommentThread.tsx @@ -2,6 +2,7 @@ import { useState } from 'react' import { useGetCommentById } from '@audius/common/api' import { useCurrentCommentSection } from '@audius/common/context' +import { commentsMessages as messages } from '@audius/common/messages' import type { ReplyComment } from '@audius/sdk' import { @@ -14,10 +15,6 @@ import { import { CommentBlock } from './CommentBlock' -const messages = { - showMoreReplies: 'Show More Replies' -} - export const CommentThread = ({ commentId }: { commentId: string }) => { const { data: rootComment } = useGetCommentById({ id: commentId diff --git a/packages/mobile/src/components/comments/NoComments.tsx b/packages/mobile/src/components/comments/NoComments.tsx index c0d65741ca2..70282fac0e9 100644 --- a/packages/mobile/src/components/comments/NoComments.tsx +++ b/packages/mobile/src/components/comments/NoComments.tsx @@ -1,9 +1,6 @@ -import { Flex, Text } from '@audius/harmony-native' +import { commentsMessages as messages } from '@audius/common/messages' -const messages = { - title: 'Nothing here yet', - subtitle: 'Be the first to comment on this track' // TODO: make this derive from entity type -} +import { Flex, Text } from '@audius/harmony-native' export const NoComments = () => ( ( direction='column' style={{ paddingTop: 80, paddingBottom: 80 }} > - {messages.title} - {messages.subtitle} + {messages.noComments} + {messages.noCommentsDescription} ) diff --git a/packages/web/src/app/web-player/WebPlayer.jsx b/packages/web/src/app/web-player/WebPlayer.jsx index af6f86aad0c..13eb0aef2b3 100644 --- a/packages/web/src/app/web-player/WebPlayer.jsx +++ b/packages/web/src/app/web-player/WebPlayer.jsx @@ -84,6 +84,7 @@ import { SubPage } from 'pages/settings-page/components/mobile/SettingsPage' import SmartCollectionPage from 'pages/smart-collection/SmartCollectionPage' import SupportingPage from 'pages/supporting-page/SupportingPage' import TopSupportersPage from 'pages/top-supporters-page/TopSupportersPage' +import { TrackCommentsPage } from 'pages/track-page/TrackCommentsPage' import TrackPage from 'pages/track-page/TrackPage' import TrendingPage from 'pages/trending-page/TrendingPage' import TrendingPlaylistsPage from 'pages/trending-playlists/TrendingPlaylistPage' @@ -135,6 +136,7 @@ const { PLAYLIST_PAGE, ALBUM_PAGE, TRACK_PAGE, + TRACK_COMMENTS_PAGE, TRACK_REMIXES_PAGE, PROFILE_PAGE, authenticatedRoutes, @@ -911,6 +913,13 @@ class WebPlayer extends Component { component={AiAttributedTracksPage} /> + + (isPinned ? 'Unpin Comment' : 'Pin Comment'), @@ -55,6 +59,8 @@ export const CommentActionBar = ({ const [reactToComment] = useReactToComment() const [pinComment] = usePinComment() + const [isMobileAppDrawerOpen, toggleIsMobileAppDrawer] = useToggle(false) + const isMobile = useIsMobile() // component state const [reactionState, setReactionState] = useState(false) // TODO: temporary - eventually this will live in metadata @@ -76,6 +82,14 @@ export const CommentActionBar = ({ pinComment(commentId, !isPinned) }, [commentId, isPinned, pinComment]) + const handleClickReply = useCallback(() => { + if (isMobile) { + toggleIsMobileAppDrawer() + } else { + onClickReply() + } + }, [isMobile, onClickReply, toggleIsMobileAppDrawer]) + const popupMenuItems = useMemo(() => { let items: PopupMenuItem[] = [] const entityOwnerMenuItems: PopupMenuItem[] = [ @@ -148,7 +162,7 @@ export const CommentActionBar = ({ @@ -165,11 +179,19 @@ export const CommentActionBar = ({ ref={anchorRef} disabled={isDisabled} onClick={() => { - triggerPopup() + if (isMobile) { + toggleIsMobileAppDrawer() + } else { + triggerPopup() + } }} /> )} /> + ) } diff --git a/packages/web/src/components/comments/CommentBlock.tsx b/packages/web/src/components/comments/CommentBlock.tsx index 0f228c7754f..1e094e6a263 100644 --- a/packages/web/src/components/comments/CommentBlock.tsx +++ b/packages/web/src/components/comments/CommentBlock.tsx @@ -26,10 +26,11 @@ const { getUser } = cacheUsersSelectors export type CommentBlockProps = { comment: Comment parentCommentId?: string + hideActions?: boolean } export const CommentBlock = (props: CommentBlockProps) => { - const { comment, parentCommentId } = props + const { comment, parentCommentId, hideActions } = props const { isPinned, message, @@ -120,13 +121,15 @@ export const CommentBlock = (props: CommentBlockProps) => { ) : ( {message} )} - setShowReplyInput((prev) => !prev)} - onClickEdit={() => setShowEditInput((prev) => !prev)} - onClickDelete={() => deleteComment(commentId)} - isDisabled={isDeleting} - /> + {hideActions ? null : ( + setShowReplyInput((prev) => !prev)} + onClickEdit={() => setShowEditInput((prev) => !prev)} + onClickDelete={() => deleteComment(commentId)} + isDisabled={isDeleting} + /> + )} {showReplyInput ? ( { - const { currentUserId, entityId } = useCurrentCommentSection() + const { currentUserId, entityId, comments } = useCurrentCommentSection() + const isMobile = useIsMobile() + const isFirstComment = comments.length === 0 + const [isMobileAppDrawerOpen, toggleIsMobileAppDrawer] = useToggle(false) const [editComment] = useEditComment() const currentlyPlayingTrackId = useSelector(getTrackId) @@ -81,6 +88,12 @@ export const CommentForm = ({ } } + const handleClickInput = useAuthenticatedCallback(() => { + if (isMobile) { + toggleIsMobileAppDrawer() + } + }, [isMobile, toggleIsMobileAppDrawer]) + const profileImage = useProfilePicture( currentUserId ?? null, SquareSizes.SIZE_150_BY_150 @@ -112,20 +125,30 @@ export const CommentForm = ({ ) : null} {isLoading ? ( ) : ( )} + ) diff --git a/packages/web/src/components/comments/CommentList.tsx b/packages/web/src/components/comments/CommentList.tsx new file mode 100644 index 00000000000..e0eb5275459 --- /dev/null +++ b/packages/web/src/components/comments/CommentList.tsx @@ -0,0 +1,24 @@ +import { useCurrentCommentSection } from '@audius/common/context' +import { Flex } from '@audius/harmony' + +import { CommentSkeletons } from './CommentSkeletons' +import { CommentThread } from './CommentThread' +import { NoComments } from './NoComments' + +export const CommentList = () => { + const { comments, commentSectionLoading } = useCurrentCommentSection() + + // TODO: break out list skeleton from reply skeleton + if (commentSectionLoading) { + return + } + + return ( + + {comments.length === 0 ? : null} + {comments.map(({ id }) => ( + + ))} + + ) +} diff --git a/packages/web/src/components/comments/CommentSection.tsx b/packages/web/src/components/comments/CommentSection.tsx index 604bc7b0069..683d90a38c8 100644 --- a/packages/web/src/components/comments/CommentSection.tsx +++ b/packages/web/src/components/comments/CommentSection.tsx @@ -5,6 +5,7 @@ import { EntityType } from '@audius/sdk' import { useIsMobile } from 'hooks/useIsMobile' import { CommentSectionDesktop } from './CommentSectionDesktop' +import { CommentSectionMobile } from './CommentSectionMobile' type CommentSectionProps = { entityId: ID @@ -17,7 +18,7 @@ export const CommentSection = (props: CommentSectionProps) => { return ( - {isMobile ? null : } + {isMobile ? : } ) } diff --git a/packages/web/src/components/comments/CommentSectionMobile.tsx b/packages/web/src/components/comments/CommentSectionMobile.tsx new file mode 100644 index 00000000000..8e369d5cbb5 --- /dev/null +++ b/packages/web/src/components/comments/CommentSectionMobile.tsx @@ -0,0 +1,92 @@ +import { useGetTrackById } from '@audius/common/api' +import { useCurrentCommentSection } from '@audius/common/context' +import { commentsMessages as messages } from '@audius/common/messages' +import { + Flex, + IconCaretRight, + Paper, + PlainButton, + Skeleton, + Text +} from '@audius/harmony' +import { Link } from 'react-router-dom' + +import { CommentBlock } from './CommentBlock' +import { CommentForm } from './CommentForm' + +const CommentSectionHeader = () => { + const { + entityId, + commentSectionLoading: isLoading, + comments + } = useCurrentCommentSection() + + const { data: track } = useGetTrackById({ id: entityId }) + + const isShowingComments = !isLoading && comments?.length + + return ( + + + Comments + {isShowingComments ? ( +  ({comments.length}) + ) : null} + + {isShowingComments ? ( + + {messages.viewAll} + + ) : null} + + ) +} + +const CommentSectionContent = () => { + const { + currentUserId, + commentSectionLoading: isLoading, + comments + } = useCurrentCommentSection() + + // Loading state + if (isLoading) { + return ( + + + + + + + + ) + } + + // Empty state + if (!comments || !comments.length) { + return ( + + {messages.noComments} + + + ) + } + + return +} + +export const CommentSectionMobile = () => { + return ( + + + + + + + ) +} diff --git a/packages/web/src/components/comments/CommentThread.tsx b/packages/web/src/components/comments/CommentThread.tsx index 250364684b2..e0d990617e5 100644 --- a/packages/web/src/components/comments/CommentThread.tsx +++ b/packages/web/src/components/comments/CommentThread.tsx @@ -2,22 +2,26 @@ import { useState } from 'react' import { useGetCommentById } from '@audius/common/api' import { useCurrentCommentSection } from '@audius/common/context' -import { Flex, IconCaretDown, IconCaretUp, TextLink } from '@audius/harmony' +import { commentsMessages as messages } from '@audius/common/messages' +import { + Box, + Flex, + IconCaretDown, + IconCaretUp, + PlainButton, + TextLink +} from '@audius/harmony' import { ReplyComment } from '@audius/sdk' import { CommentBlock } from './CommentBlock' -const messages = { - showMoreReplies: 'Show More Replies' -} - export const CommentThread = ({ commentId }: { commentId: string }) => { const { data: rootComment } = useGetCommentById({ id: commentId }) const { handleLoadMoreReplies } = useCurrentCommentSection() const [hiddenReplies, setHiddenReplies] = useState<{ - [parentCommentId: number]: boolean + [parentCommentId: string]: boolean }>({}) const toggleReplies = (commentId: string) => { @@ -29,23 +33,33 @@ export const CommentThread = ({ commentId }: { commentId: string }) => { if (!rootComment) return null return ( - + {(rootComment?.replies?.length ?? 0) > 0 ? ( - toggleReplies(rootComment.id)}> - {hiddenReplies[rootComment.id] ? ( - - ) : ( - - )} - {hiddenReplies[rootComment.id] ? 'Show' : 'Hide'} Replies - + + toggleReplies(rootComment.id)} + variant='subdued' + iconLeft={ + hiddenReplies[rootComment.id] ? IconCaretDown : IconCaretUp + } + > + {hiddenReplies[rootComment.id] + ? messages.showReplies + : messages.hideReplies} + + ) : null} {hiddenReplies[rootComment.id] ? null : ( - + {rootComment?.replies?.map((reply: ReplyComment) => ( - + ( ( direction='column' css={{ paddingTop: 80, paddingBottom: 80 }} > - {messages.title} - {messages.subtitle} + {messages.noComments} + {messages.noCommentsDescription} ) diff --git a/packages/web/src/pages/track-page/TrackCommentsPage.tsx b/packages/web/src/pages/track-page/TrackCommentsPage.tsx new file mode 100644 index 00000000000..722a365d3da --- /dev/null +++ b/packages/web/src/pages/track-page/TrackCommentsPage.tsx @@ -0,0 +1,47 @@ +import { useContext, useEffect } from 'react' + +import { useGetCurrentUserId, useGetTrackByPermalink } from '@audius/common/api' +import { CommentSectionProvider } from '@audius/common/context' +import { useParams } from 'react-router-dom' + +import { CommentList } from 'components/comments/CommentList' +import MobilePageContainer from 'components/mobile-page-container/MobilePageContainer' +import NavContext from 'components/nav/mobile/NavContext' + +const messages = { + title: 'Comments' +} + +type TrackCommentsParams = { + slug: string + handle: string +} + +export const TrackCommentsPage = () => { + const { slug, handle } = useParams() + const { data: currentUserId } = useGetCurrentUserId({}) + const { data: track } = useGetTrackByPermalink({ + permalink: `/${handle}/${slug}`, + currentUserId + }) + + const { setLeft, setCenter, setRight } = useContext(NavContext) + + useEffect(() => { + setCenter(messages.title) + setRight(null) + }, [setCenter, setLeft, setRight]) + + if (!track) return null + + return ( + ({ backgroundColor: theme.color.background.white })} + > + + + + + ) +} diff --git a/packages/web/src/pages/track-page/components/desktop/TrackPage.tsx b/packages/web/src/pages/track-page/components/desktop/TrackPage.tsx index e32cc842735..9bd2e007224 100644 --- a/packages/web/src/pages/track-page/components/desktop/TrackPage.tsx +++ b/packages/web/src/pages/track-page/components/desktop/TrackPage.tsx @@ -249,7 +249,7 @@ const TrackPage = ({ css={{ maxWidth: 1080 }} justifyContent='center' > - {isCommentingEnabled && heroTrack?.owner_id ? ( + {isCommentingEnabled ? ( diff --git a/packages/web/src/pages/track-page/components/mobile/TrackPage.module.css b/packages/web/src/pages/track-page/components/mobile/TrackPage.module.css deleted file mode 100644 index dfa95e37571..00000000000 --- a/packages/web/src/pages/track-page/components/mobile/TrackPage.module.css +++ /dev/null @@ -1,27 +0,0 @@ -.trackContent { - padding: 14px 12px 0px; - width: 100%; -} - -.tracksContainer { - padding-top: 41px; -} - -.remixes { - width: 100%; - margin-top: 32px; -} - -.lineupHeader { - color: var(--harmony-n-500); - font-size: var(--harmony-font-s); - font-weight: var(--harmony-font-bold); - letter-spacing: 1px; - margin-bottom: 12px; - margin-top: 24px; - text-transform: uppercase; -} - -.originalTrack { - margin-bottom: 16px !important; -} diff --git a/packages/web/src/pages/track-page/components/mobile/TrackPage.tsx b/packages/web/src/pages/track-page/components/mobile/TrackPage.tsx index dbb371075c4..3ce734c7ccc 100644 --- a/packages/web/src/pages/track-page/components/mobile/TrackPage.tsx +++ b/packages/web/src/pages/track-page/components/mobile/TrackPage.tsx @@ -1,13 +1,16 @@ import { useEffect, useContext } from 'react' -import { useGatedContentAccess } from '@audius/common/hooks' +import { useFeatureFlag, useGatedContentAccess } from '@audius/common/hooks' import { ID, LineupState, Track, User } from '@audius/common/models' +import { FeatureFlags } from '@audius/common/services' import { trackPageLineupActions, OverflowAction, QueueItem } from '@audius/common/store' +import { Flex, Text } from '@audius/harmony' +import { CommentSection } from 'components/comments/CommentSection' import { HeaderContext } from 'components/header/mobile/HeaderContextProvider' import Lineup from 'components/lineup/Lineup' import { LineupVariant } from 'components/lineup/types' @@ -22,7 +25,6 @@ import { getTrackDefaults } from 'pages/track-page/utils' import Remixes from './Remixes' import TrackPageHeader from './TrackHeader' -import styles from './TrackPage.module.css' const { tracksActions } = trackPageLineupActions const messages = { @@ -121,6 +123,11 @@ const TrackPage = ({ const { isFetchingNFTAccess, hasStreamAccess, hasDownloadAccess } = useGatedContentAccess(heroTrack) + + const { isEnabled: isCommentingEnabled } = useFeatureFlag( + FeatureFlags.COMMENTS_ENABLED + ) + const loading = !heroTrack || isFetchingNFTAccess const onPlay = () => onHeroPlay({ isPlaying: heroPlaying }) @@ -139,15 +146,17 @@ const TrackPage = ({ const defaults = getTrackDefaults(heroTrack) const renderOriginalTrackTitle = () => ( -
{messages.originalTrack}
+ + {messages.originalTrack} + ) const renderMoreByTitle = () => (defaults.remixParentTrackId && entries.length > 2) || (!defaults.remixParentTrackId && entries.length > 1) ? ( -
{`${messages.moreBy} ${user?.name}`}
+ + {messages.moreBy} {user?.name} + ) : null return ( @@ -159,7 +168,7 @@ const TrackPage = ({ structuredData={structuredData} noIndex={defaults.isUnlisted} > -
+ 0 && ( -
- -
+ )} -
- {!hasValidRemixParent && renderMoreByTitle()} - {hasValidRemixParent && renderOriginalTrackTitle()} + {isCommentingEnabled ? ( + + ) : null} + + {hasValidRemixParent + ? renderOriginalTrackTitle() + : renderMoreByTitle()} +
} - leadingElementClassName={styles.originalTrack} showLeadingElementArtistPick={false} // Don't render the first tile in the lineup. start={1} @@ -249,8 +259,8 @@ const TrackPage = ({ pauseTrack={pause} actions={tracksActions} /> -
-
+
+ ) }