diff --git a/packages/common/src/adapters/notification.ts b/packages/common/src/adapters/notification.ts index b7df80d9cd2..879d4cd2ecc 100644 --- a/packages/common/src/adapters/notification.ts +++ b/packages/common/src/adapters/notification.ts @@ -662,6 +662,47 @@ export const notificationFromSDK = ( } } default: + // Types below may arrive before the SDK is regenerated + { + const n = notification as unknown as { + type: string + actions: typeof notification.actions + } + if (n.type === 'remix_contest_update') { + const data = n.actions[0].data as unknown as Record + return { + type: NotificationType.RemixContestUpdate, + eventId: HashId.parse(data.eventId ?? data.event_id), + entityId: HashId.parse(data.entityId ?? data.entity_id), + entityUserId: HashId.parse( + data.entityUserId ?? data.entity_user_id + ), + commentId: HashId.parse(data.commentId ?? data.comment_id), + userIds: [], + entityType: Entity.Track, + ...formatBaseNotification(notification) + } + } + if (n.type === 'fan_remix_contest_submission') { + const data = n.actions[0].data as unknown as Record + return { + type: NotificationType.FanRemixContestSubmission, + eventId: HashId.parse(data.eventId ?? data.event_id), + entityId: HashId.parse(data.entityId ?? data.entity_id), + entityUserId: HashId.parse( + data.entityUserId ?? data.entity_user_id + ), + submissionTrackId: HashId.parse( + data.submissionTrackId ?? data.submission_track_id + ), + userIds: [ + HashId.parse(data.submitterUserId ?? data.submitter_user_id) + ].filter(removeNullable), + entityType: Entity.Track, + ...formatBaseNotification(notification) + } + } + } return undefined } } diff --git a/packages/common/src/store/notifications/types.ts b/packages/common/src/store/notifications/types.ts index 58494f86121..7090f6db066 100644 --- a/packages/common/src/store/notifications/types.ts +++ b/packages/common/src/store/notifications/types.ts @@ -25,6 +25,8 @@ export enum NotificationType { FanRemixContestEnded = 'FanRemixContestEnded', FanRemixContestEndingSoon = 'FanRemixContestEndingSoon', FanRemixContestWinnersSelected = 'FanRemixContestWinnersSelected', + RemixContestUpdate = 'RemixContestUpdate', + FanRemixContestSubmission = 'FanRemixContestSubmission', Tastemaker = 'Tastemaker', TrendingTrack = 'TrendingTrack', TrendingUnderground = 'TrendingUnderground', @@ -612,6 +614,26 @@ export type FanRemixContestWinnersSelectedNotification = BaseNotification & { entityType: Entity.Track } +export type RemixContestUpdateNotification = BaseNotification & { + type: NotificationType.RemixContestUpdate + eventId: ID + entityId: ID + entityUserId: ID + commentId: ID + userIds: ID[] + entityType: Entity.Track +} + +export type FanRemixContestSubmissionNotification = BaseNotification & { + type: NotificationType.FanRemixContestSubmission + eventId: ID + entityId: ID + entityUserId: ID + submissionTrackId: ID + userIds: ID[] + entityType: Entity.Track +} + export type ArtistRemixContestEndingSoonNotification = BaseNotification & { type: NotificationType.ArtistRemixContestEndingSoon entityId: ID @@ -665,6 +687,8 @@ export type Notification = | ArtistRemixContestEndedNotification | FanRemixContestEndedNotification | FanRemixContestWinnersSelectedNotification + | RemixContestUpdateNotification + | FanRemixContestSubmissionNotification | ArtistRemixContestEndingSoonNotification | ArtistRemixContestSubmissionsNotification | FanClubTextPostNotification diff --git a/packages/discovery-provider/integration_tests/tasks/entity_manager/test_event_comment.py b/packages/discovery-provider/integration_tests/tasks/entity_manager/test_event_comment.py index 076bf6fdff2..daa42ce8be1 100644 --- a/packages/discovery-provider/integration_tests/tasks/entity_manager/test_event_comment.py +++ b/packages/discovery-provider/integration_tests/tasks/entity_manager/test_event_comment.py @@ -219,7 +219,8 @@ def test_create_event_comment_by_owner_is_post_update(app, mocker): assert recipient_ids == [2, 3] for notif in update_notifs: assert notif.data["event_id"] == 100 - assert notif.data["event_user_id"] == 1 + assert notif.data["entity_id"] == 1 + assert notif.data["entity_user_id"] == 1 assert notif.data["comment_id"] == 500 diff --git a/packages/discovery-provider/src/tasks/entity_manager/entities/comment.py b/packages/discovery-provider/src/tasks/entity_manager/entities/comment.py index f3a121f468f..8b66109e1e5 100644 --- a/packages/discovery-provider/src/tasks/entity_manager/entities/comment.py +++ b/packages/discovery-provider/src/tasks/entity_manager/entities/comment.py @@ -386,6 +386,12 @@ def create_comment(params: ManageEntityParameters): event_follower_user_ids = { row[0] for row in event_follower_rows if row[0] != entity_user_id } + event_row = ( + params.session.query(Event) + .filter(Event.event_id == stored_entity_id) + .first() + ) + contest_track_id = event_row.entity_id if event_row else None for recipient_id in event_follower_user_ids: event_update_notification = Notification( blocknumber=params.block_number, @@ -399,7 +405,8 @@ def create_comment(params: ManageEntityParameters): ), data={ "event_id": stored_entity_id, - "event_user_id": entity_user_id, + "entity_id": contest_track_id, + "entity_user_id": entity_user_id, "comment_id": comment_id, }, ) diff --git a/packages/discovery-provider/src/tasks/entity_manager/entities/track.py b/packages/discovery-provider/src/tasks/entity_manager/entities/track.py index d9fed3ecba2..4f085354916 100644 --- a/packages/discovery-provider/src/tasks/entity_manager/entities/track.py +++ b/packages/discovery-provider/src/tasks/entity_manager/entities/track.py @@ -1,7 +1,7 @@ from datetime import datetime, timezone from typing import Dict, List, Union -from sqlalchemy import desc +from sqlalchemy import desc, or_ from sqlalchemy.orm.session import Session from sqlalchemy.sql import null @@ -15,11 +15,16 @@ ) from src.models.tracks.remix import Remix from src.models.tracks.stem import Stem +from src.models.events.event import Event, EventType from src.models.tracks.track import Track from src.models.tracks.track_download import TrackDownload from src.models.tracks.track_price_history import TrackPriceHistory from src.models.tracks.track_route import TrackRoute from src.models.users.usdc_purchase import PurchaseAccessType +from src.models.social.subscription import ( + SUBSCRIPTION_EVENT_ENTITY_TYPE, + Subscription, +) from src.models.users.user import User from src.tasks.entity_manager.utils import ( CHARACTER_LIMIT_DESCRIPTION, @@ -595,6 +600,62 @@ def create_remix_contest_notification_helper( ) +def auto_subscribe_to_contest_on_submission( + params: ManageEntityParameters, track_record: Track +) -> None: + if track_record.stem_of is not None or not track_record.remix_of: + return + parent_ids = get_remix_parent_track_ids(params.metadata) + if not parent_ids: + return + parent_track_id = parent_ids[0] + now = params.block_datetime + event = ( + params.session.query(Event) + .filter( + Event.event_type == EventType.remix_contest, + Event.entity_id == parent_track_id, + Event.is_deleted == False, + ) + .filter(or_(Event.end_date == None, Event.end_date > now)) + .first() + ) + if not event: + return + uploader_id = track_record.owner_id + if uploader_id == event.user_id: + return + existing = ( + params.session.query(Subscription) + .filter( + Subscription.subscriber_id == uploader_id, + Subscription.user_id == event.event_id, + Subscription.entity_type == SUBSCRIPTION_EVENT_ENTITY_TYPE, + Subscription.is_current == True, + Subscription.is_delete == False, + ) + .first() + ) + if existing: + return + # Synthetic tx: unique per (contest, upload tx) so reindexing stays idempotent + sub_tx = f"auto_e{event.event_id}_{params.txhash}"[:200] + params.session.add( + Subscription( + blockhash=params.event_blockhash, + blocknumber=params.block_number, + created_at=params.block_datetime, + txhash=sub_tx, + user_id=event.event_id, + subscriber_id=uploader_id, + is_current=True, + is_delete=False, + entity_type=SUBSCRIPTION_EVENT_ENTITY_TYPE, + entity_id=event.event_id, + ) + ) + + def create_track(params: ManageEntityParameters): handle = get_handle(params) validate_track_tx(params) @@ -634,6 +695,8 @@ def create_track(params: ManageEntityParameters): params.challenge_bus, params.block_number, params.block_datetime, track_record ) + auto_subscribe_to_contest_on_submission(params, track_record) + params.add_record(track_id, track_record) diff --git a/packages/discovery-provider/src/tasks/entity_manager/utils.py b/packages/discovery-provider/src/tasks/entity_manager/utils.py index fb1d12be219..5e34039912a 100644 --- a/packages/discovery-provider/src/tasks/entity_manager/utils.py +++ b/packages/discovery-provider/src/tasks/entity_manager/utils.py @@ -33,7 +33,10 @@ from src.models.social.repost import Repost from src.models.social.save import Save from src.models.social.share import Share -from src.models.social.subscription import Subscription +from src.models.social.subscription import ( + SUBSCRIPTION_EVENT_ENTITY_TYPE, + Subscription, +) from src.models.tracks.track import Track from src.models.tracks.track_route import TrackRoute from src.models.users.associated_wallet import AssociatedWallet @@ -670,6 +673,24 @@ def safe_add_notification(session: Session, notification: Notification): session.add(notification) +def get_remix_contest_event_subscriber_user_ids(session: Session, event: Event) -> List[int]: + """ + user_id on `subscriptions` is the event_id for Event subscriptions + (see subscription row shape for entity_type = 'Event'). + """ + rows = ( + session.query(Subscription.subscriber_id) + .filter( + Subscription.user_id == event.event_id, + Subscription.entity_type == SUBSCRIPTION_EVENT_ENTITY_TYPE, + Subscription.is_current == True, + Subscription.is_delete == False, + ) + .all() + ) + return [r[0] for r in rows] + + def create_remix_contest_notification( session: Session, track: Track, @@ -722,11 +743,16 @@ def create_remix_contest_notification( .all() ) + contest_follower_user_ids = get_remix_contest_event_subscriber_user_ids( + session, remix_contest_event + ) + # Combine and deduplicate user IDs user_ids = list( set( [user_id for (user_id,) in follower_user_ids] + [user_id for (user_id,) in save_user_ids] + + contest_follower_user_ids ) ) diff --git a/packages/discovery-provider/src/tasks/remix_contest_notifications/fan_remix_contest_ended.py b/packages/discovery-provider/src/tasks/remix_contest_notifications/fan_remix_contest_ended.py index d819c30a032..d181dff721e 100644 --- a/packages/discovery-provider/src/tasks/remix_contest_notifications/fan_remix_contest_ended.py +++ b/packages/discovery-provider/src/tasks/remix_contest_notifications/fan_remix_contest_ended.py @@ -2,6 +2,12 @@ from src.models.events.event import Event, EventType from src.models.notifications.notification import Notification +from src.models.social.follow import Follow +from src.models.social.save import Save, SaveType +from src.models.social.subscription import ( + SUBSCRIPTION_EVENT_ENTITY_TYPE, + Subscription, +) from src.models.tracks.track import Track from src.utils.structured_logger import StructuredLogger @@ -47,7 +53,55 @@ def create_fan_remix_contest_ended_notifications(session, now=None): .all() ) remixer_user_ids = {row[0] for row in remixers} - for user_id in remixer_user_ids: + event_follower_user_ids = { + row[0] + for row in ( + session.query(Subscription.subscriber_id) + .filter( + Subscription.user_id == event.event_id, + Subscription.entity_type == SUBSCRIPTION_EVENT_ENTITY_TYPE, + Subscription.is_current == True, + Subscription.is_delete == False, + ) + .all() + ) + } + # Followers of the contest host artist + host_follower_user_ids = { + row[0] + for row in ( + session.query(Follow.follower_user_id) + .filter( + Follow.followee_user_id == event.user_id, + Follow.is_current == True, + Follow.is_delete == False, + ) + .all() + ) + } + # Users who favorited the parent contest track + favoriter_user_ids = { + row[0] + for row in ( + session.query(Save.user_id) + .filter( + Save.save_item_id == contest_track_id, + Save.save_type == SaveType.track, + Save.is_current == True, + Save.is_delete == False, + ) + .all() + ) + } + notified_user_ids = ( + remixer_user_ids + | event_follower_user_ids + | host_follower_user_ids + | favoriter_user_ids + ) + # Exclude the contest host — they have artist_remix_contest_ended. + notified_user_ids.discard(event.user_id) + for user_id in notified_user_ids: group_id = get_fan_remix_contest_ended_group_id(event.event_id) parent_track = ( session.query(Track) diff --git a/packages/discovery-provider/src/tasks/remix_contest_notifications/fan_remix_contest_ending_soon.py b/packages/discovery-provider/src/tasks/remix_contest_notifications/fan_remix_contest_ending_soon.py index b10a61482bd..a380e98d032 100644 --- a/packages/discovery-provider/src/tasks/remix_contest_notifications/fan_remix_contest_ending_soon.py +++ b/packages/discovery-provider/src/tasks/remix_contest_notifications/fan_remix_contest_ending_soon.py @@ -4,6 +4,10 @@ from src.models.notifications.notification import Notification from src.models.social.follow import Follow from src.models.social.save import Save, SaveType +from src.models.social.subscription import ( + SUBSCRIPTION_EVENT_ENTITY_TYPE, + Subscription, +) from src.models.tracks.track import Track from src.utils.structured_logger import StructuredLogger @@ -71,7 +75,22 @@ def create_fan_remix_contest_ending_soon_notifications(session, now=None): ) .all() ) - notified_user_ids = follower_user_ids | favoriter_user_ids + event_follower_user_ids = set( + row[0] + for row in session.query(Subscription.subscriber_id) + .filter( + Subscription.user_id == event.event_id, + Subscription.entity_type == SUBSCRIPTION_EVENT_ENTITY_TYPE, + Subscription.is_current == True, + Subscription.is_delete == False, + ) + .all() + ) + notified_user_ids = ( + follower_user_ids | favoriter_user_ids | event_follower_user_ids + ) + # Exclude the contest host — they have artist_remix_contest_ending_soon. + notified_user_ids.discard(event.user_id) parent_track_owner_id = parent_track.owner_id if parent_track else None group_id = get_fan_remix_contest_ending_soon_group_id(event.event_id) for user_id in notified_user_ids: diff --git a/packages/discovery-provider/src/tasks/remix_contest_notifications/fan_remix_contest_winners_selected.py b/packages/discovery-provider/src/tasks/remix_contest_notifications/fan_remix_contest_winners_selected.py index 083ce233dbb..9ea35acf768 100644 --- a/packages/discovery-provider/src/tasks/remix_contest_notifications/fan_remix_contest_winners_selected.py +++ b/packages/discovery-provider/src/tasks/remix_contest_notifications/fan_remix_contest_winners_selected.py @@ -2,6 +2,12 @@ from src.models.events.event import Event, EventType from src.models.notifications.notification import Notification +from src.models.social.follow import Follow +from src.models.social.save import Save, SaveType +from src.models.social.subscription import ( + SUBSCRIPTION_EVENT_ENTITY_TYPE, + Subscription, +) from src.models.tracks.track import Track from src.models.users.user import User from src.tasks.entity_manager.utils import safe_add_notification @@ -90,11 +96,67 @@ def create_fan_remix_contest_winners_selected_notification(session, event_id, no remixer_user_ids = [user_id[0] for user_id in remixer_user_ids] - if not remixer_user_ids: - logger.info(f"No remixers found for contest {event_id}") + event_follower_user_ids = [ + row[0] + for row in ( + session.query(Subscription.subscriber_id) + .filter( + Subscription.user_id == event_id, + Subscription.entity_type == SUBSCRIPTION_EVENT_ENTITY_TYPE, + Subscription.is_current == True, + Subscription.is_delete == False, + ) + .all() + ) + ] + + # Followers of the contest host artist + host_follower_user_ids = [ + row[0] + for row in ( + session.query(Follow.follower_user_id) + .filter( + Follow.followee_user_id == event.user_id, + Follow.is_current == True, + Follow.is_delete == False, + ) + .all() + ) + ] + + # Users who favorited the parent contest track + favoriter_user_ids = [ + row[0] + for row in ( + session.query(Save.user_id) + .filter( + Save.save_item_id == contest_track_id, + Save.save_type == SaveType.track, + Save.is_current == True, + Save.is_delete == False, + ) + .all() + ) + ] + + recipient_user_ids = list( + set( + remixer_user_ids + + event_follower_user_ids + + host_follower_user_ids + + favoriter_user_ids + ) + ) + # Exclude the contest host themselves — they have artist_remix_contest_* + # notifications and shouldn't get the "fan" version. + recipient_user_ids = [u for u in recipient_user_ids if u != event.user_id] + if not recipient_user_ids: + logger.info( + f"No recipients to notify for contest {event_id}" + ) return - for user_id in remixer_user_ids: + for user_id in recipient_user_ids: # Create unique group_id per user to prevent conflicts user_group_id = f"{group_id}:user:{user_id}" safe_add_notification( @@ -114,5 +176,5 @@ def create_fan_remix_contest_winners_selected_notification(session, event_id, no ) logger.info( - f"Created {len(remixer_user_ids)} winners selected notifications for event {event_id}" + f"Created {len(recipient_user_ids)} winners selected notifications for event {event_id}" ) diff --git a/packages/mobile/src/hooks/useNotificationNavigation.ts b/packages/mobile/src/hooks/useNotificationNavigation.ts index a6030ebdbde..e82e1543bc1 100644 --- a/packages/mobile/src/hooks/useNotificationNavigation.ts +++ b/packages/mobile/src/hooks/useNotificationNavigation.ts @@ -37,7 +37,8 @@ import type { CommentThreadNotification, CommentReactionNotification, AnnouncementPushNotification, - FanClubTextPostNotification + FanClubTextPostNotification, + FanRemixContestSubmissionNotification } from '@audius/common/store' import { NotificationType, @@ -319,6 +320,15 @@ export const useNotificationNavigation = () => { [NotificationType.FanRemixContestEnded]: entityHandler, [NotificationType.FanRemixContestEndingSoon]: entityHandler, [NotificationType.FanRemixContestWinnersSelected]: entityHandler, + [NotificationType.RemixContestUpdate]: entityHandler, + [NotificationType.FanRemixContestSubmission]: ( + notification: FanRemixContestSubmissionNotification + ) => { + navigation.navigate('Track', { + trackId: notification.submissionTrackId, + canBeUnlisted: false + }) + }, [NotificationType.ArtistRemixContestEnded]: entityHandler, [NotificationType.FanClubTextPost]: ( notification: FanClubTextPostNotification & { ticker?: string } diff --git a/packages/mobile/src/screens/notifications-screen/NotificationListItem.tsx b/packages/mobile/src/screens/notifications-screen/NotificationListItem.tsx index c3f47de65e5..2e842c0f417 100644 --- a/packages/mobile/src/screens/notifications-screen/NotificationListItem.tsx +++ b/packages/mobile/src/screens/notifications-screen/NotificationListItem.tsx @@ -36,8 +36,10 @@ import { FanClubTextPostNotification } from './Notifications/FanClubTextPostNoti import { FanRemixContestEndedNotification } from './Notifications/FanRemixContestEndedNotification' import { FanRemixContestEndingSoonNotification } from './Notifications/FanRemixContestEndingSoonNotification' import { FanRemixContestStartedNotification } from './Notifications/FanRemixContestStartedNotification' +import { FanRemixContestSubmissionNotification } from './Notifications/FanRemixContestSubmissionNotification' import { FanRemixContestWinnersSelectedNotification } from './Notifications/FanRemixContestWinnersSelectedNotification' import { ListenStreakReminderNotification } from './Notifications/ListenStreakReminderNotification' +import { RemixContestUpdateNotification } from './Notifications/RemixContestUpdateNotification' type NotificationListItemProps = { notification: Notification @@ -120,6 +122,12 @@ export const NotificationListItem = (props: NotificationListItemProps) => { notification={notification} /> ) + case NotificationType.RemixContestUpdate: + return + case NotificationType.FanRemixContestSubmission: + return ( + + ) case NotificationType.ArtistRemixContestEndingSoon: return ( { + const { notification } = props + const { userIds, entityId, submissionTrackId } = notification + const submitterId = userIds[0] + + const navigation = useNavigation() + const { data: submitter } = useUser(submitterId) + const { data: contestTrack } = useTrack(entityId) + const { data: submissionTrack } = useTrack(submissionTrackId) + + const handlePress = useCallback(() => { + if (submissionTrack) { + navigation.push('Track', { + trackId: submissionTrack.track_id + }) + } + }, [submissionTrack, navigation]) + + if (!submitter || !contestTrack || !submissionTrack) return null + + return ( + + + {messages.title} + + + submitted a track + {messages.description2} + + + + ) +} diff --git a/packages/mobile/src/screens/notifications-screen/Notifications/RemixContestUpdateNotification.tsx b/packages/mobile/src/screens/notifications-screen/Notifications/RemixContestUpdateNotification.tsx new file mode 100644 index 00000000000..e706628b607 --- /dev/null +++ b/packages/mobile/src/screens/notifications-screen/Notifications/RemixContestUpdateNotification.tsx @@ -0,0 +1,58 @@ +import { useCallback } from 'react' + +import { useTrack, useUser } from '@audius/common/api' +import type { RemixContestUpdateNotification as RemixContestUpdateNotificationType } from '@audius/common/store' + +import { IconTrophy } from '@audius/harmony-native' +import { useNavigation } from 'app/hooks/useNavigation' + +import { + NotificationHeader, + NotificationText, + NotificationTile, + NotificationTitle, + EntityLink, + UserNameLink +} from '../Notification' + +const messages = { + title: 'Contest update', + description: 'posted an update in' +} + +type RemixContestUpdateNotificationProps = { + notification: RemixContestUpdateNotificationType +} + +export const RemixContestUpdateNotification = ( + props: RemixContestUpdateNotificationProps +) => { + const { notification } = props + const { entityId, entityUserId } = notification + + const navigation = useNavigation() + const { data: user } = useUser(entityUserId) + const { data: track } = useTrack(entityId) + + const handlePress = useCallback(() => { + if (track) { + navigation.push('Track', { + trackId: track.track_id + }) + } + }, [track, navigation]) + + if (!user || !track) return null + + return ( + + + {messages.title} + + + {messages.description}{' '} + + + + ) +} diff --git a/packages/web/src/components/notification/Notification/FanRemixContestSubmissionNotification.tsx b/packages/web/src/components/notification/Notification/FanRemixContestSubmissionNotification.tsx new file mode 100644 index 00000000000..f8b84c43e84 --- /dev/null +++ b/packages/web/src/components/notification/Notification/FanRemixContestSubmissionNotification.tsx @@ -0,0 +1,71 @@ +import { useCallback } from 'react' + +import { useTrack, useUser } from '@audius/common/api' +import { FanRemixContestSubmissionNotification as FanRemixContestSubmissionNotificationType } from '@audius/common/store' +import { Flex, IconTrophy } from '@audius/harmony' +import { useDispatch } from 'react-redux' + +import { TextLink } from 'components/link/TextLink' +import { push } from 'utils/navigation' + +import { NotificationBody } from './components/NotificationBody' +import { NotificationFooter } from './components/NotificationFooter' +import { NotificationHeader } from './components/NotificationHeader' +import { NotificationTile } from './components/NotificationTile' +import { NotificationTitle } from './components/NotificationTitle' +import { UserNameLink } from './components/UserNameLink' + +const messages = { + title: 'New submission', + forContest: ' for ' +} + +type FanRemixContestSubmissionNotificationProps = { + notification: FanRemixContestSubmissionNotificationType +} + +export const FanRemixContestSubmissionNotification = ( + props: FanRemixContestSubmissionNotificationProps +) => { + const { notification } = props + const { timeLabel, isViewed, userIds, entityId, submissionTrackId } = + notification + const dispatch = useDispatch() + + const submitterId = userIds[0] + const { data: submitter } = useUser(submitterId) + const { data: contestTrack } = useTrack(entityId) + const { data: submissionTrack } = useTrack(submissionTrackId) + + const handleClick = useCallback(() => { + if (submissionTrack) { + dispatch(push(submissionTrack.permalink)) + } + }, [submissionTrack, dispatch]) + + if (!contestTrack || !submissionTrack || !submitter) return null + + return ( + + }> + {messages.title} + + + + + {' submitted a track'} + {messages.forContest} + + {contestTrack.title} + + + + + + ) +} diff --git a/packages/web/src/components/notification/Notification/Notification.tsx b/packages/web/src/components/notification/Notification/Notification.tsx index 0e64a21e784..2d7063492ff 100644 --- a/packages/web/src/components/notification/Notification/Notification.tsx +++ b/packages/web/src/components/notification/Notification/Notification.tsx @@ -21,12 +21,14 @@ import { FanClubTextPostNotification } from './FanClubTextPostNotification' import { FanRemixContestEndedNotification } from './FanRemixContestEndedNotification' import { FanRemixContestEndingSoonNotification } from './FanRemixContestEndingSoonNotification' import { FanRemixContestStartedNotification } from './FanRemixContestStartedNotification' +import { FanRemixContestSubmissionNotification } from './FanRemixContestSubmissionNotification' import { FanRemixContestWinnersSelectedNotification } from './FanRemixContestWinnersSelectedNotification' import { FavoriteNotification } from './FavoriteNotification' import { FavoriteOfRepostNotification } from './FavoriteOfRepostNotification' import { FollowNotification } from './FollowNotification' import { ListenStreakReminderNotification } from './ListenStreakReminderNotification' import { MilestoneNotification } from './MilestoneNotification' +import { RemixContestUpdateNotification } from './RemixContestUpdateNotification' import { RemixCosignNotification } from './RemixCosignNotification' import { RemixCreateNotification } from './RemixCreateNotification' import { RepostNotification } from './RepostNotification' @@ -172,6 +174,14 @@ export const Notification = (props: NotificationProps) => { /> ) } + case NotificationType.RemixContestUpdate: { + return + } + case NotificationType.FanRemixContestSubmission: { + return ( + + ) + } case NotificationType.FanClubTextPost: { return } diff --git a/packages/web/src/components/notification/Notification/RemixContestUpdateNotification.tsx b/packages/web/src/components/notification/Notification/RemixContestUpdateNotification.tsx new file mode 100644 index 00000000000..d00b4b55137 --- /dev/null +++ b/packages/web/src/components/notification/Notification/RemixContestUpdateNotification.tsx @@ -0,0 +1,72 @@ +import { useCallback } from 'react' + +import { useNotificationEntity } from '@audius/common/api' +import { + RemixContestUpdateNotification as RemixContestUpdateNotificationType, + TrackEntity +} from '@audius/common/store' +import { Flex, IconTrophy } from '@audius/harmony' +import { useDispatch } from 'react-redux' + +import { TextLink } from 'components/link/TextLink' +import { push } from 'utils/navigation' + +import { NotificationBody } from './components/NotificationBody' +import { NotificationFooter } from './components/NotificationFooter' +import { NotificationHeader } from './components/NotificationHeader' +import { NotificationTile } from './components/NotificationTile' +import { NotificationTitle } from './components/NotificationTitle' +import { TrackContent } from './components/TrackContent' +import { UserNameLink } from './components/UserNameLink' +import { getEntityLink } from './utils' + +const messages = { + title: 'Contest update', + description: 'posted an update in ' +} + +type RemixContestUpdateNotificationProps = { + notification: RemixContestUpdateNotificationType +} + +export const RemixContestUpdateNotification = ( + props: RemixContestUpdateNotificationProps +) => { + const { notification } = props + const { timeLabel, isViewed } = notification + const dispatch = useDispatch() + + const entity = useNotificationEntity(notification) as TrackEntity | null + + const handleClick = useCallback(() => { + if (entity) { + dispatch(push(`${getEntityLink(entity)}/contest`)) + } + }, [entity, dispatch]) + + if (!entity || !entity.user) return null + + return ( + + }> + {messages.title} + + + + + {' '} + {messages.description} + + {entity.title} + + + + + + ) +}