diff --git a/.changeset/giant-files-explain.md b/.changeset/giant-files-explain.md new file mode 100644 index 00000000000..10a406c8399 --- /dev/null +++ b/.changeset/giant-files-explain.md @@ -0,0 +1,5 @@ +--- +"@audius/sdk": patch +--- + +Artist remix contest ended notification diff --git a/packages/common/src/adapters/notification.ts b/packages/common/src/adapters/notification.ts index 2ead45d9aa0..baaee3dd48f 100644 --- a/packages/common/src/adapters/notification.ts +++ b/packages/common/src/adapters/notification.ts @@ -661,6 +661,14 @@ export const notificationFromSDK = ( ...formatBaseNotification(notification) } } + case 'artist_remix_contest_ended': { + const data = notification.actions[0].data + return { + type: NotificationType.ArtistRemixContestEnded, + entityId: HashId.parse(data.entityId), + ...formatBaseNotification(notification) + } + } case 'remix_contest_ending_soon': { const data = notification.actions[0].data return { diff --git a/packages/common/src/api/tan-query/notifications/useNotificationValidTypes.ts b/packages/common/src/api/tan-query/notifications/useNotificationValidTypes.ts index 33ea6f33b2a..8162b46ce26 100644 --- a/packages/common/src/api/tan-query/notifications/useNotificationValidTypes.ts +++ b/packages/common/src/api/tan-query/notifications/useNotificationValidTypes.ts @@ -18,6 +18,7 @@ export const useNotificationValidTypes = () => { ValidTypes.CommentReaction, ValidTypes.ClaimableReward, ValidTypes.ListenStreakReminder, + ValidTypes.ArtistRemixContestEnded, ValidTypes.RemixContestStarted, ValidTypes.RemixContestEnded, ValidTypes.RemixContestEndingSoon diff --git a/packages/common/src/store/notifications/types.ts b/packages/common/src/store/notifications/types.ts index c7993a2ec0f..d87e94e03d0 100644 --- a/packages/common/src/store/notifications/types.ts +++ b/packages/common/src/store/notifications/types.ts @@ -47,7 +47,8 @@ export enum NotificationType { CommentThread = 'CommentThread', CommentMention = 'CommentMention', CommentReaction = 'CommentReaction', - ListenStreakReminder = 'ListenStreakReminder' + ListenStreakReminder = 'ListenStreakReminder', + ArtistRemixContestEnded = 'ArtistRemixContestEnded' } export enum PushNotificationType { @@ -716,6 +717,11 @@ export type RemixContestEndingSoonNotification = BaseNotification & { entityUserId: ID } +export type ArtistRemixContestEndedNotification = BaseNotification & { + type: NotificationType.ArtistRemixContestEnded + entityId: ID +} + export type Notification = | AnnouncementNotification | UserSubscriptionNotification @@ -754,6 +760,7 @@ export type Notification = | CommentMentionNotification | CommentReactionNotification | ListenStreakReminderNotification + | ArtistRemixContestEndedNotification export type IdentityNotification = Omit & { timestamp: string diff --git a/packages/discovery-provider/integration_tests/tasks/test_artist_remix_contest_ended_notification.py b/packages/discovery-provider/integration_tests/tasks/test_artist_remix_contest_ended_notification.py new file mode 100644 index 00000000000..3180f0e5106 --- /dev/null +++ b/packages/discovery-provider/integration_tests/tasks/test_artist_remix_contest_ended_notification.py @@ -0,0 +1,70 @@ +import logging +from datetime import datetime, timedelta + +from integration_tests.utils import populate_mock_db +from src.models.notifications.notification import Notification +from src.tasks.remix_contest_notifications.artist_remix_contest_ended import ( + create_artist_remix_contest_ended_notifications, +) +from src.utils.db_session import get_db + +logger = logging.getLogger(__name__) + +TEST_EVENT_CREATOR_ID = 1 +TEST_TRACK_ID = 100 + + +def test_artist_remix_contest_ended_notification(app): + """Test that artist remix contest ended notification is created for the contest creator when the contest ends""" + with app.app_context(): + db = get_db() + + now = datetime.now() + end_date = now - timedelta(hours=1) + + entities = { + "users": [ + { + "user_id": TEST_EVENT_CREATOR_ID, + "is_current": True, + "created_at": now, + "updated_at": now, + } + ], + "tracks": [ + { + "track_id": TEST_TRACK_ID, + "owner_id": TEST_EVENT_CREATOR_ID, + "is_current": True, + "is_delete": False, + "created_at": now, + "updated_at": now, + } + ], + "events": [ + { + "event_id": 1, + "event_type": "remix_contest", + "user_id": TEST_EVENT_CREATOR_ID, + "entity_id": TEST_TRACK_ID, + "entity_type": "track", + "is_deleted": False, + "created_at": now, + "updated_at": now, + "end_date": end_date, + } + ], + } + populate_mock_db(db, entities) + + with db.scoped_session() as session: + create_artist_remix_contest_ended_notifications(session, now=now) + notifications = ( + session.query(Notification) + .filter(Notification.type == "artist_remix_contest_ended") + .all() + ) + assert len(notifications) == 1 + notification = notifications[0] + assert notification.user_ids == [TEST_EVENT_CREATOR_ID] + assert notification.data["entity_id"] == TEST_TRACK_ID diff --git a/packages/discovery-provider/src/api/v1/models/notifications.py b/packages/discovery-provider/src/api/v1/models/notifications.py index ee7395b2864..ce46ad3e4ba 100644 --- a/packages/discovery-provider/src/api/v1/models/notifications.py +++ b/packages/discovery-provider/src/api/v1/models/notifications.py @@ -280,6 +280,34 @@ def format(self, value): }, ) +artist_remix_contest_ended_notification_action_data = ns.model( + "artist_remix_contest_ended_notification_action_data", + { + "entity_id": fields.String(required=True), + }, +) +artist_remix_contest_ended_notification_action = ns.clone( + "artist_remix_contest_ended_notification_action", + notification_action_base, + { + "data": fields.Nested( + artist_remix_contest_ended_notification_action_data, required=True + ) + }, +) +artist_remix_contest_ended_notification = ns.clone( + "artist_remix_contest_ended_notification", + notification_base, + { + "actions": fields.List( + fields.Nested( + artist_remix_contest_ended_notification_action, required=True + ), + required=True, + ) + }, +) + cosign_notification_action_data = ns.model( "cosign_notification_action_data", { @@ -1056,6 +1084,7 @@ def format(self, value): "remix_contest_started": remix_contest_started_notification, "remix_contest_ended": remix_contest_ended_notification, "remix_contest_ending_soon": remix_contest_ending_soon_notification, + "artist_remix_contest_ended": artist_remix_contest_ended_notification, }, discriminator="type", ), diff --git a/packages/discovery-provider/src/api/v1/utils/extend_notification.py b/packages/discovery-provider/src/api/v1/utils/extend_notification.py index 087af503a71..dd89a072eb5 100644 --- a/packages/discovery-provider/src/api/v1/utils/extend_notification.py +++ b/packages/discovery-provider/src/api/v1/utils/extend_notification.py @@ -813,6 +813,22 @@ def extend_remix_contest_ending_soon(action: NotificationAction): } +def extend_artist_remix_contest_ended(action: NotificationAction): + data = action["data"] # type: ignore + return { + "specifier": encode_int_id(int(action["specifier"])), + "type": action["type"], + "timestamp": ( + datetime.timestamp(action["timestamp"]) + if action["timestamp"] + else action["timestamp"] + ), + "data": { + "entity_id": encode_int_id(data["entity_id"]), + }, + } + + notification_action_handler = { "follow": extend_follow, "repost": extend_repost, @@ -851,4 +867,5 @@ def extend_remix_contest_ending_soon(action: NotificationAction): "remix_contest_started": extend_remix_contest_started, "remix_contest_ended": extend_remix_contest_ended, "remix_contest_ending_soon": extend_remix_contest_ending_soon, + "artist_remix_contest_ended": extend_artist_remix_contest_ended, } diff --git a/packages/discovery-provider/src/queries/get_notifications.py b/packages/discovery-provider/src/queries/get_notifications.py index d0b7a355406..7285f4b2cb0 100644 --- a/packages/discovery-provider/src/queries/get_notifications.py +++ b/packages/discovery-provider/src/queries/get_notifications.py @@ -177,6 +177,7 @@ class NotificationType(str, Enum): REMIX_CONTEST_STARTED = "remix_contest_started" REMIX_CONTEST_ENDED = "remix_contest_ended" REMIX_CONTEST_ENDING_SOON = "remix_contest_ending_soon" + ARTIST_REMIX_CONTEST_ENDED = "artist_remix_contest_ended" def __str__(self) -> str: return str.__str__(self) @@ -500,6 +501,10 @@ class RemixContestEndingSoonNotification(TypedDict): entity_id: int +class ArtistRemixContestEndedNotification(TypedDict): + entity_id: int + + NotificationData = Union[ AnnouncementNotification, FollowNotification, @@ -535,6 +540,7 @@ class RemixContestEndingSoonNotification(TypedDict): RemixContestStartedNotification, RemixContestEndedNotification, RemixContestEndingSoonNotification, + ArtistRemixContestEndedNotification, ] diff --git a/packages/discovery-provider/src/tasks/remix_contest_notifications/artist_remix_contest_ended.py b/packages/discovery-provider/src/tasks/remix_contest_notifications/artist_remix_contest_ended.py new file mode 100644 index 00000000000..bfee9014350 --- /dev/null +++ b/packages/discovery-provider/src/tasks/remix_contest_notifications/artist_remix_contest_ended.py @@ -0,0 +1,62 @@ +from datetime import datetime, timedelta + +from src.models.events.event import Event, EventType +from src.models.notifications.notification import Notification +from src.utils.structured_logger import StructuredLogger + +ARTIST_REMIX_CONTEST_ENDED = "artist_remix_contest_ended" +REMIX_CONTEST_ENDED_WINDOW_HOURS = 24 + +logger = StructuredLogger(__name__) + + +def get_artist_remix_contest_ended_group_id(event_id): + return f"{ARTIST_REMIX_CONTEST_ENDED}:{event_id}" + + +def create_artist_remix_contest_ended_notifications(session, now=None): + now = now or datetime.now() + window_start = now - timedelta(hours=REMIX_CONTEST_ENDED_WINDOW_HOURS) + window_end = now + + ended_contests = ( + session.query(Event) + .filter( + Event.event_type == EventType.remix_contest, + Event.is_deleted == False, + Event.end_date != None, + Event.end_date.between(window_start, window_end), + ) + .all() + ) + + new_notifications = [] + for event in ended_contests: + group_id = get_artist_remix_contest_ended_group_id(event.event_id) + exists = ( + session.query(Notification) + .filter( + Notification.group_id == group_id, + Notification.type == ARTIST_REMIX_CONTEST_ENDED, + Notification.user_ids.any(event.user_id), + ) + .first() + ) + if not exists: + new_notification = Notification( + specifier=str(event.user_id), + group_id=group_id, + blocknumber=None, + user_ids=[event.user_id], + type=ARTIST_REMIX_CONTEST_ENDED, + data={ + "entity_id": event.entity_id, + }, + timestamp=now, + ) + new_notifications.append(new_notification) + logger.info( + f"Inserting {len(new_notifications)} artist remix contest ended notifications" + ) + session.add_all(new_notifications) + session.commit() diff --git a/packages/mobile/src/screens/notifications-screen/NotificationListItem.tsx b/packages/mobile/src/screens/notifications-screen/NotificationListItem.tsx index 12d9ea2f478..7f3c8e1939d 100644 --- a/packages/mobile/src/screens/notifications-screen/NotificationListItem.tsx +++ b/packages/mobile/src/screens/notifications-screen/NotificationListItem.tsx @@ -37,6 +37,7 @@ import { CommentMentionNotification, CommentReactionNotification } from './Notifications' +import { ArtistRemixContestEndedNotification } from './Notifications/ArtistRemixContestEndedNotification' import { ListenStreakReminderNotification } from './Notifications/ListenStreakReminderNotification' import { RemixContestEndedNotification } from './Notifications/RemixContestEndedNotification' import { RemixContestEndingSoonNotification } from './Notifications/RemixContestEndingSoonNotification' @@ -135,6 +136,10 @@ export const NotificationListItem = (props: NotificationListItemProps) => { return case NotificationType.RemixContestEnded: return + case NotificationType.ArtistRemixContestEnded: + return ( + + ) case NotificationType.RemixContestEndingSoon: return ( diff --git a/packages/mobile/src/screens/notifications-screen/Notifications/ArtistRemixContestEndedNotification.tsx b/packages/mobile/src/screens/notifications-screen/Notifications/ArtistRemixContestEndedNotification.tsx new file mode 100644 index 00000000000..315ce9adef6 --- /dev/null +++ b/packages/mobile/src/screens/notifications-screen/Notifications/ArtistRemixContestEndedNotification.tsx @@ -0,0 +1,51 @@ +import { useCallback } from 'react' + +import { useTrack } from '@audius/common/api' +import type { ArtistRemixContestEndedNotification as ArtistRemixContestEndedNotificationType } from '@audius/common/store' + +import { IconTrophy } from '@audius/harmony-native' +import { useNotificationNavigation } from 'app/hooks/useNotificationNavigation' + +import { + NotificationHeader, + NotificationText, + NotificationTile, + NotificationTitle +} from '../Notification' + +const messages = { + title: 'Your Remix Contest Ended', + description: + "Your remix contest has ended. Don't forget to contact your winners!" +} + +type ArtistRemixContestEndedNotificationProps = { + notification: ArtistRemixContestEndedNotificationType +} + +export const ArtistRemixContestEndedNotification = ( + props: ArtistRemixContestEndedNotificationProps +) => { + const { notification } = props + const { entityId } = notification + + const navigation = useNotificationNavigation() + const { data: track } = useTrack(entityId) + + const handlePress = useCallback(() => { + if (track) { + navigation.navigate(notification) + } + }, [track, navigation, notification]) + + if (!track) return null + + return ( + + + {messages.title} + + {messages.description} + + ) +} diff --git a/packages/mobile/src/screens/notifications-screen/Notifications/index.ts b/packages/mobile/src/screens/notifications-screen/Notifications/index.ts index 68346b6bd7f..14aeb12ef71 100644 --- a/packages/mobile/src/screens/notifications-screen/Notifications/index.ts +++ b/packages/mobile/src/screens/notifications-screen/Notifications/index.ts @@ -31,3 +31,6 @@ export * from './CommentNotification' export * from './CommentThreadNotification' export * from './CommentMentionNotification' export * from './CommentReactionNotification' +export * from './RemixContestEndedNotification' +export * from './RemixContestEndingSoonNotification' +export * from './ArtistRemixContestEndedNotification' diff --git a/packages/sdk/src/sdk/api/generated/full/.openapi-generator/FILES b/packages/sdk/src/sdk/api/generated/full/.openapi-generator/FILES index 7b4cadd0de9..225a54001fe 100644 --- a/packages/sdk/src/sdk/api/generated/full/.openapi-generator/FILES +++ b/packages/sdk/src/sdk/api/generated/full/.openapi-generator/FILES @@ -26,6 +26,9 @@ models/AnnouncementNotificationActionData.ts models/ApproveManagerRequestNotification.ts models/ApproveManagerRequestNotificationAction.ts models/ApproveManagerRequestNotificationActionData.ts +models/ArtistRemixContestEndedNotification.ts +models/ArtistRemixContestEndedNotificationAction.ts +models/ArtistRemixContestEndedNotificationActionData.ts models/Attestation.ts models/AttestationReponse.ts models/ChallengeRewardNotification.ts diff --git a/packages/sdk/src/sdk/api/generated/full/apis/NotificationsApi.ts b/packages/sdk/src/sdk/api/generated/full/apis/NotificationsApi.ts index 9690147be8a..64fb0f9a9b3 100644 --- a/packages/sdk/src/sdk/api/generated/full/apis/NotificationsApi.ts +++ b/packages/sdk/src/sdk/api/generated/full/apis/NotificationsApi.ts @@ -165,6 +165,7 @@ export const GetNotificationsValidTypesEnum = { ListenStreakReminder: 'listen_streak_reminder', RemixContestStarted: 'remix_contest_started', RemixContestEnded: 'remix_contest_ended', - RemixContestEndingSoon: 'remix_contest_ending_soon' + RemixContestEndingSoon: 'remix_contest_ending_soon', + ArtistRemixContestEnded: 'artist_remix_contest_ended' } as const; export type GetNotificationsValidTypesEnum = typeof GetNotificationsValidTypesEnum[keyof typeof GetNotificationsValidTypesEnum]; diff --git a/packages/sdk/src/sdk/api/generated/full/models/ArtistRemixContestEndedNotification.ts b/packages/sdk/src/sdk/api/generated/full/models/ArtistRemixContestEndedNotification.ts new file mode 100644 index 00000000000..3f783101b31 --- /dev/null +++ b/packages/sdk/src/sdk/api/generated/full/models/ArtistRemixContestEndedNotification.ts @@ -0,0 +1,109 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck +/** + * API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { ArtistRemixContestEndedNotificationAction } from './ArtistRemixContestEndedNotificationAction'; +import { + ArtistRemixContestEndedNotificationActionFromJSON, + ArtistRemixContestEndedNotificationActionFromJSONTyped, + ArtistRemixContestEndedNotificationActionToJSON, +} from './ArtistRemixContestEndedNotificationAction'; + +/** + * + * @export + * @interface ArtistRemixContestEndedNotification + */ +export interface ArtistRemixContestEndedNotification { + /** + * + * @type {string} + * @memberof ArtistRemixContestEndedNotification + */ + type: string; + /** + * + * @type {string} + * @memberof ArtistRemixContestEndedNotification + */ + groupId: string; + /** + * + * @type {boolean} + * @memberof ArtistRemixContestEndedNotification + */ + isSeen: boolean; + /** + * + * @type {number} + * @memberof ArtistRemixContestEndedNotification + */ + seenAt?: number; + /** + * + * @type {Array} + * @memberof ArtistRemixContestEndedNotification + */ + actions: Array; +} + +/** + * Check if a given object implements the ArtistRemixContestEndedNotification interface. + */ +export function instanceOfArtistRemixContestEndedNotification(value: object): value is ArtistRemixContestEndedNotification { + let isInstance = true; + isInstance = isInstance && "type" in value && value["type"] !== undefined; + isInstance = isInstance && "groupId" in value && value["groupId"] !== undefined; + isInstance = isInstance && "isSeen" in value && value["isSeen"] !== undefined; + isInstance = isInstance && "actions" in value && value["actions"] !== undefined; + + return isInstance; +} + +export function ArtistRemixContestEndedNotificationFromJSON(json: any): ArtistRemixContestEndedNotification { + return ArtistRemixContestEndedNotificationFromJSONTyped(json, false); +} + +export function ArtistRemixContestEndedNotificationFromJSONTyped(json: any, ignoreDiscriminator: boolean): ArtistRemixContestEndedNotification { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'type': json['type'], + 'groupId': json['group_id'], + 'isSeen': json['is_seen'], + 'seenAt': !exists(json, 'seen_at') ? undefined : json['seen_at'], + 'actions': ((json['actions'] as Array).map(ArtistRemixContestEndedNotificationActionFromJSON)), + }; +} + +export function ArtistRemixContestEndedNotificationToJSON(value?: ArtistRemixContestEndedNotification | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'type': value.type, + 'group_id': value.groupId, + 'is_seen': value.isSeen, + 'seen_at': value.seenAt, + 'actions': ((value.actions as Array).map(ArtistRemixContestEndedNotificationActionToJSON)), + }; +} + diff --git a/packages/sdk/src/sdk/api/generated/full/models/ArtistRemixContestEndedNotificationAction.ts b/packages/sdk/src/sdk/api/generated/full/models/ArtistRemixContestEndedNotificationAction.ts new file mode 100644 index 00000000000..598b5543686 --- /dev/null +++ b/packages/sdk/src/sdk/api/generated/full/models/ArtistRemixContestEndedNotificationAction.ts @@ -0,0 +1,101 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck +/** + * API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +import type { ArtistRemixContestEndedNotificationActionData } from './ArtistRemixContestEndedNotificationActionData'; +import { + ArtistRemixContestEndedNotificationActionDataFromJSON, + ArtistRemixContestEndedNotificationActionDataFromJSONTyped, + ArtistRemixContestEndedNotificationActionDataToJSON, +} from './ArtistRemixContestEndedNotificationActionData'; + +/** + * + * @export + * @interface ArtistRemixContestEndedNotificationAction + */ +export interface ArtistRemixContestEndedNotificationAction { + /** + * + * @type {string} + * @memberof ArtistRemixContestEndedNotificationAction + */ + specifier: string; + /** + * + * @type {string} + * @memberof ArtistRemixContestEndedNotificationAction + */ + type: string; + /** + * + * @type {number} + * @memberof ArtistRemixContestEndedNotificationAction + */ + timestamp: number; + /** + * + * @type {ArtistRemixContestEndedNotificationActionData} + * @memberof ArtistRemixContestEndedNotificationAction + */ + data: ArtistRemixContestEndedNotificationActionData; +} + +/** + * Check if a given object implements the ArtistRemixContestEndedNotificationAction interface. + */ +export function instanceOfArtistRemixContestEndedNotificationAction(value: object): value is ArtistRemixContestEndedNotificationAction { + let isInstance = true; + isInstance = isInstance && "specifier" in value && value["specifier"] !== undefined; + isInstance = isInstance && "type" in value && value["type"] !== undefined; + isInstance = isInstance && "timestamp" in value && value["timestamp"] !== undefined; + isInstance = isInstance && "data" in value && value["data"] !== undefined; + + return isInstance; +} + +export function ArtistRemixContestEndedNotificationActionFromJSON(json: any): ArtistRemixContestEndedNotificationAction { + return ArtistRemixContestEndedNotificationActionFromJSONTyped(json, false); +} + +export function ArtistRemixContestEndedNotificationActionFromJSONTyped(json: any, ignoreDiscriminator: boolean): ArtistRemixContestEndedNotificationAction { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'specifier': json['specifier'], + 'type': json['type'], + 'timestamp': json['timestamp'], + 'data': ArtistRemixContestEndedNotificationActionDataFromJSON(json['data']), + }; +} + +export function ArtistRemixContestEndedNotificationActionToJSON(value?: ArtistRemixContestEndedNotificationAction | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'specifier': value.specifier, + 'type': value.type, + 'timestamp': value.timestamp, + 'data': ArtistRemixContestEndedNotificationActionDataToJSON(value.data), + }; +} + diff --git a/packages/sdk/src/sdk/api/generated/full/models/ArtistRemixContestEndedNotificationActionData.ts b/packages/sdk/src/sdk/api/generated/full/models/ArtistRemixContestEndedNotificationActionData.ts new file mode 100644 index 00000000000..7958d45895c --- /dev/null +++ b/packages/sdk/src/sdk/api/generated/full/models/ArtistRemixContestEndedNotificationActionData.ts @@ -0,0 +1,67 @@ +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck +/** + * API + * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator) + * + * The version of the OpenAPI document: 1.0 + * + * + * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +import { exists, mapValues } from '../runtime'; +/** + * + * @export + * @interface ArtistRemixContestEndedNotificationActionData + */ +export interface ArtistRemixContestEndedNotificationActionData { + /** + * + * @type {string} + * @memberof ArtistRemixContestEndedNotificationActionData + */ + entityId: string; +} + +/** + * Check if a given object implements the ArtistRemixContestEndedNotificationActionData interface. + */ +export function instanceOfArtistRemixContestEndedNotificationActionData(value: object): value is ArtistRemixContestEndedNotificationActionData { + let isInstance = true; + isInstance = isInstance && "entityId" in value && value["entityId"] !== undefined; + + return isInstance; +} + +export function ArtistRemixContestEndedNotificationActionDataFromJSON(json: any): ArtistRemixContestEndedNotificationActionData { + return ArtistRemixContestEndedNotificationActionDataFromJSONTyped(json, false); +} + +export function ArtistRemixContestEndedNotificationActionDataFromJSONTyped(json: any, ignoreDiscriminator: boolean): ArtistRemixContestEndedNotificationActionData { + if ((json === undefined) || (json === null)) { + return json; + } + return { + + 'entityId': json['entity_id'], + }; +} + +export function ArtistRemixContestEndedNotificationActionDataToJSON(value?: ArtistRemixContestEndedNotificationActionData | null): any { + if (value === undefined) { + return undefined; + } + if (value === null) { + return null; + } + return { + + 'entity_id': value.entityId, + }; +} + diff --git a/packages/sdk/src/sdk/api/generated/full/models/Notification.ts b/packages/sdk/src/sdk/api/generated/full/models/Notification.ts index 1ad9545a9f2..52cd052551f 100644 --- a/packages/sdk/src/sdk/api/generated/full/models/Notification.ts +++ b/packages/sdk/src/sdk/api/generated/full/models/Notification.ts @@ -27,6 +27,13 @@ import { ApproveManagerRequestNotificationFromJSONTyped, ApproveManagerRequestNotificationToJSON, } from './ApproveManagerRequestNotification'; +import { + ArtistRemixContestEndedNotification, + instanceOfArtistRemixContestEndedNotification, + ArtistRemixContestEndedNotificationFromJSON, + ArtistRemixContestEndedNotificationFromJSONTyped, + ArtistRemixContestEndedNotificationToJSON, +} from './ArtistRemixContestEndedNotification'; import { ChallengeRewardNotification, instanceOfChallengeRewardNotification, @@ -271,7 +278,7 @@ import { * * @export */ -export type Notification = { type: 'announcement' } & AnnouncementNotification | { type: 'approve_manager_request' } & ApproveManagerRequestNotification | { type: 'challenge_reward' } & ChallengeRewardNotification | { type: 'claimable_reward' } & ClaimableRewardNotification | { type: 'comment' } & CommentNotification | { type: 'comment_mention' } & CommentMentionNotification | { type: 'comment_reaction' } & CommentReactionNotification | { type: 'comment_thread' } & CommentThreadNotification | { type: 'cosign' } & CosignNotification | { type: 'create' } & CreateNotification | { type: 'follow' } & FollowNotification | { type: 'listen_streak_reminder' } & ListenStreakReminderNotification | { type: 'milestone' } & MilestoneNotification | { type: 'reaction' } & ReactionNotification | { type: 'remix' } & RemixNotification | { type: 'remix_contest_ended' } & RemixContestEndedNotification | { type: 'remix_contest_ending_soon' } & RemixContestEndingSoonNotification | { type: 'remix_contest_started' } & RemixContestStartedNotification | { type: 'repost' } & RepostNotification | { type: 'repost_of_repost' } & RepostOfRepostNotification | { type: 'request_manager' } & RequestManagerNotification | { type: 'save' } & SaveNotification | { type: 'save_of_repost' } & SaveOfRepostNotification | { type: 'supporter_dethroned' } & SupporterDethronedNotification | { type: 'supporter_rank_up' } & SupporterRankUpNotification | { type: 'supporting_rank_up' } & SupporterRankUpNotification | { type: 'tastemaker' } & TastemakerNotification | { type: 'tier_change' } & TierChangeNotification | { type: 'tip_receive' } & ReceiveTipNotification | { type: 'tip_send' } & SendTipNotification | { type: 'track_added_to_playlist' } & TrackAddedToPlaylistNotification | { type: 'track_added_to_purchased_album' } & TrackAddedToPurchasedAlbumNotification | { type: 'trending' } & TrendingNotification | { type: 'trending_playlist' } & TrendingPlaylistNotification | { type: 'trending_underground' } & TrendingUndergroundNotification | { type: 'usdc_purchase_buyer' } & UsdcPurchaseBuyerNotification | { type: 'usdc_purchase_seller' } & UsdcPurchaseSellerNotification; +export type Notification = { type: 'announcement' } & AnnouncementNotification | { type: 'approve_manager_request' } & ApproveManagerRequestNotification | { type: 'artist_remix_contest_ended' } & ArtistRemixContestEndedNotification | { type: 'challenge_reward' } & ChallengeRewardNotification | { type: 'claimable_reward' } & ClaimableRewardNotification | { type: 'comment' } & CommentNotification | { type: 'comment_mention' } & CommentMentionNotification | { type: 'comment_reaction' } & CommentReactionNotification | { type: 'comment_thread' } & CommentThreadNotification | { type: 'cosign' } & CosignNotification | { type: 'create' } & CreateNotification | { type: 'follow' } & FollowNotification | { type: 'listen_streak_reminder' } & ListenStreakReminderNotification | { type: 'milestone' } & MilestoneNotification | { type: 'reaction' } & ReactionNotification | { type: 'remix' } & RemixNotification | { type: 'remix_contest_ended' } & RemixContestEndedNotification | { type: 'remix_contest_ending_soon' } & RemixContestEndingSoonNotification | { type: 'remix_contest_started' } & RemixContestStartedNotification | { type: 'repost' } & RepostNotification | { type: 'repost_of_repost' } & RepostOfRepostNotification | { type: 'request_manager' } & RequestManagerNotification | { type: 'save' } & SaveNotification | { type: 'save_of_repost' } & SaveOfRepostNotification | { type: 'supporter_dethroned' } & SupporterDethronedNotification | { type: 'supporter_rank_up' } & SupporterRankUpNotification | { type: 'supporting_rank_up' } & SupporterRankUpNotification | { type: 'tastemaker' } & TastemakerNotification | { type: 'tier_change' } & TierChangeNotification | { type: 'tip_receive' } & ReceiveTipNotification | { type: 'tip_send' } & SendTipNotification | { type: 'track_added_to_playlist' } & TrackAddedToPlaylistNotification | { type: 'track_added_to_purchased_album' } & TrackAddedToPurchasedAlbumNotification | { type: 'trending' } & TrendingNotification | { type: 'trending_playlist' } & TrendingPlaylistNotification | { type: 'trending_underground' } & TrendingUndergroundNotification | { type: 'usdc_purchase_buyer' } & UsdcPurchaseBuyerNotification | { type: 'usdc_purchase_seller' } & UsdcPurchaseSellerNotification; export function NotificationFromJSON(json: any): Notification { return NotificationFromJSONTyped(json, false); @@ -286,6 +293,8 @@ export function NotificationFromJSONTyped(json: any, ignoreDiscriminator: boolea return {...AnnouncementNotificationFromJSONTyped(json, true), type: 'announcement'}; case 'approve_manager_request': return {...ApproveManagerRequestNotificationFromJSONTyped(json, true), type: 'approve_manager_request'}; + case 'artist_remix_contest_ended': + return {...ArtistRemixContestEndedNotificationFromJSONTyped(json, true), type: 'artist_remix_contest_ended'}; case 'challenge_reward': return {...ChallengeRewardNotificationFromJSONTyped(json, true), type: 'challenge_reward'}; case 'claimable_reward': @@ -373,6 +382,8 @@ export function NotificationToJSON(value?: Notification | null): any { return AnnouncementNotificationToJSON(value); case 'approve_manager_request': return ApproveManagerRequestNotificationToJSON(value); + case 'artist_remix_contest_ended': + return ArtistRemixContestEndedNotificationToJSON(value); case 'challenge_reward': return ChallengeRewardNotificationToJSON(value); case 'claimable_reward': diff --git a/packages/sdk/src/sdk/api/generated/full/models/index.ts b/packages/sdk/src/sdk/api/generated/full/models/index.ts index 1a86a16faf5..de2ea93ece6 100644 --- a/packages/sdk/src/sdk/api/generated/full/models/index.ts +++ b/packages/sdk/src/sdk/api/generated/full/models/index.ts @@ -14,6 +14,9 @@ export * from './AnnouncementNotificationActionData'; export * from './ApproveManagerRequestNotification'; export * from './ApproveManagerRequestNotificationAction'; export * from './ApproveManagerRequestNotificationActionData'; +export * from './ArtistRemixContestEndedNotification'; +export * from './ArtistRemixContestEndedNotificationAction'; +export * from './ArtistRemixContestEndedNotificationActionData'; export * from './Attestation'; export * from './AttestationReponse'; export * from './ChallengeRewardNotification'; diff --git a/packages/web/src/components/notification/Notification/ArtistRemixContestEndedNotification.tsx b/packages/web/src/components/notification/Notification/ArtistRemixContestEndedNotification.tsx new file mode 100644 index 00000000000..336ee9e8909 --- /dev/null +++ b/packages/web/src/components/notification/Notification/ArtistRemixContestEndedNotification.tsx @@ -0,0 +1,55 @@ +import { useCallback } from 'react' + +import { useTrack } from '@audius/common/api' +import { ArtistRemixContestEndedNotification as ArtistRemixContestEndedNotificationType } from '@audius/common/store' +import { Flex, IconTrophy } from '@audius/harmony' +import { useDispatch } from 'react-redux' + +import { push } from 'utils/navigation' +import { fullTrackPage } from 'utils/route' + +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' + +const messages = { + title: 'Your Remix Contest Ended', + description: + "Your remix contest has ended. Don't forget to contact your winners!" +} + +type ArtistRemixContestEndedNotificationProps = { + notification: ArtistRemixContestEndedNotificationType +} + +export const ArtistRemixContestEndedNotification = ( + props: ArtistRemixContestEndedNotificationProps +) => { + const { notification } = props + const { timeLabel, isViewed, entityId } = notification + const dispatch = useDispatch() + + const { data: track } = useTrack(entityId) + + const handleClick = useCallback(() => { + if (track) { + dispatch(push(fullTrackPage(track.permalink))) + } + }, [track, dispatch]) + + if (!track) return null + + return ( + + }> + {messages.title} + + + {messages.description} + + + + ) +} diff --git a/packages/web/src/components/notification/Notification/Notification.tsx b/packages/web/src/components/notification/Notification/Notification.tsx index 2236a29c1c1..e612fe6d045 100644 --- a/packages/web/src/components/notification/Notification/Notification.tsx +++ b/packages/web/src/components/notification/Notification/Notification.tsx @@ -8,6 +8,7 @@ import ErrorWrapper from 'components/error-wrapper/ErrorWrapper' import { AddTrackToPlaylistNotification } from './AddTrackToPlaylistNotification' import { AnnouncementNotification } from './AnnouncementNotification' import { ApproveManagerNotification } from './ApproveManagerRequestNotification' +import { ArtistRemixContestEndedNotification } from './ArtistRemixContestEndedNotification' import { ChallengeRewardNotification } from './ChallengeRewardNotification' import { ClaimableRewardNotification } from './ClaimableRewardNotification' import { CommentMentionNotification } from './CommentMentionNotification' @@ -167,6 +168,11 @@ export const Notification = (props: NotificationProps) => { ) } + case NotificationType.ArtistRemixContestEnded: { + return ( + + ) + } default: { return null }