diff --git a/packages/common/src/adapters/notification.ts b/packages/common/src/adapters/notification.ts index a3382baaf99..101ea49d2ea 100644 --- a/packages/common/src/adapters/notification.ts +++ b/packages/common/src/adapters/notification.ts @@ -688,6 +688,15 @@ export const notificationFromSDK = ( type: string actions: typeof notification.actions } + if (n.type === 'weekly_rotation') { + const data = n.actions[0].data as unknown as Record + return { + type: NotificationType.WeeklyRotation, + year: data.year, + week: data.week, + ...formatBaseNotification(notification) + } + } if (n.type === 'remix_contest_update') { const data = n.actions[0].data as unknown as Record return { diff --git a/packages/common/src/messages/notifications.ts b/packages/common/src/messages/notifications.ts index 65af126dd3b..38cc870e934 100644 --- a/packages/common/src/messages/notifications.ts +++ b/packages/common/src/messages/notifications.ts @@ -3,3 +3,8 @@ export const listenStreakReminderMessages = { body: (streak: number) => `Your ${streak} day listening streak will end in 6 hours! Keep listening to earn daily rewards!` } + +export const weeklyRotationNotificationMessages = { + title: 'Your Weekly Rotation Is Ready', + body: 'A fresh mix of tracks picked just for you. Give it a spin before it rotates next Wednesday.' +} diff --git a/packages/common/src/store/notifications/types.ts b/packages/common/src/store/notifications/types.ts index bb254753f5f..d2c4e5b05d3 100644 --- a/packages/common/src/store/notifications/types.ts +++ b/packages/common/src/store/notifications/types.ts @@ -46,6 +46,7 @@ export enum NotificationType { CommentMention = 'CommentMention', CommentReaction = 'CommentReaction', ListenStreakReminder = 'ListenStreakReminder', + WeeklyRotation = 'WeeklyRotation', ArtistRemixContestEnded = 'ArtistRemixContestEnded', ArtistRemixContestEndingSoon = 'ArtistRemixContestEndingSoon', ArtistRemixContestSubmissions = 'ArtistRemixContestSubmissions', @@ -94,6 +95,7 @@ export enum PushNotificationType { CommentMention = 'CommentMention', CommentReaction = 'CommentReaction', ListenStreakReminder = 'ListenStreakReminder', + WeeklyRotation = 'WeeklyRotation', FanClubTextPost = 'FanClubTextPost' } @@ -604,6 +606,13 @@ export type ListenStreakReminderNotification = BaseNotification & { streak: number } +export type WeeklyRotationNotification = BaseNotification & { + type: NotificationType.WeeklyRotation + /** The rotation period announced, as an ISO (year, week) pair. */ + year: number + week: number +} + export type FanRemixContestStartedNotification = BaseNotification & { type: NotificationType.FanRemixContestStarted entityId: ID @@ -709,6 +718,7 @@ export type Notification = | CommentMentionNotification | CommentReactionNotification | ListenStreakReminderNotification + | WeeklyRotationNotification | ArtistRemixContestEndedNotification | FanRemixContestEndedNotification | FanRemixContestWinnersSelectedNotification diff --git a/packages/mobile/src/hooks/useNotificationNavigation.ts b/packages/mobile/src/hooks/useNotificationNavigation.ts index 1ba3930373d..32ae1ecd96a 100644 --- a/packages/mobile/src/hooks/useNotificationNavigation.ts +++ b/packages/mobile/src/hooks/useNotificationNavigation.ts @@ -272,6 +272,12 @@ export const useNotificationNavigation = () => { ) => { navigation.navigate('RewardsScreen') }, + // Handles both the in-app tile and the push payload (`type: + // 'WeeklyRotation'`); neither carries an entity, the mix is fetched on + // demand for the signed-in user. + [NotificationType.WeeklyRotation]: () => { + navigation.navigate('WeeklyRotationScreen') + }, [PushNotificationType.FavoriteAlbum]: socialActionHandler, [PushNotificationType.FavoritePlaylist]: socialActionHandler, [PushNotificationType.FavoriteTrack]: socialActionHandler, diff --git a/packages/mobile/src/screens/notifications-screen/NotificationListItem.tsx b/packages/mobile/src/screens/notifications-screen/NotificationListItem.tsx index 311076349c1..8265ae1ead5 100644 --- a/packages/mobile/src/screens/notifications-screen/NotificationListItem.tsx +++ b/packages/mobile/src/screens/notifications-screen/NotificationListItem.tsx @@ -43,6 +43,7 @@ import { FanRemixContestSubmissionNotification } from './Notifications/FanRemixC import { FanRemixContestWinnersSelectedNotification } from './Notifications/FanRemixContestWinnersSelectedNotification' import { ListenStreakReminderNotification } from './Notifications/ListenStreakReminderNotification' import { RemixContestUpdateNotification } from './Notifications/RemixContestUpdateNotification' +import { WeeklyRotationNotification } from './Notifications/WeeklyRotationNotification' type NotificationListItemProps = { notification: Notification @@ -117,6 +118,8 @@ export const NotificationListItem = (props: NotificationListItemProps) => { return case NotificationType.ListenStreakReminder: return + case NotificationType.WeeklyRotation: + return case NotificationType.FanRemixContestEnded: return case NotificationType.FanRemixContestEndingSoon: diff --git a/packages/mobile/src/screens/notifications-screen/Notifications/WeeklyRotationNotification.tsx b/packages/mobile/src/screens/notifications-screen/Notifications/WeeklyRotationNotification.tsx new file mode 100644 index 00000000000..a28e6a5bf2a --- /dev/null +++ b/packages/mobile/src/screens/notifications-screen/Notifications/WeeklyRotationNotification.tsx @@ -0,0 +1,50 @@ +import { useCallback } from 'react' + +import { useFeatureFlag } from '@audius/common/hooks' +import { weeklyRotationNotificationMessages as messages } from '@audius/common/messages' +import { FeatureFlags } from '@audius/common/services' +import type { WeeklyRotationNotification as WeeklyRotationNotificationType } from '@audius/common/store' + +import { IconArrowRotate } from '@audius/harmony-native' +import { useNotificationNavigation } from 'app/hooks/useNotificationNavigation' + +import { + NotificationHeader, + NotificationText, + NotificationTile, + NotificationTitle +} from '../Notification' + +type WeeklyRotationNotificationProps = { + notification: WeeklyRotationNotificationType +} + +/** + * "Your Weekly Rotation is ready", sent every Wednesday. Opens the viewer's + * own mix; there is no entity to resolve. Hidden while the feature flag is + * off, since the screen it opens is gated too. + */ +export const WeeklyRotationNotification = ( + props: WeeklyRotationNotificationProps +) => { + const { notification } = props + const navigation = useNotificationNavigation() + const { isEnabled: isWeeklyRotationEnabled } = useFeatureFlag( + FeatureFlags.WEEKLY_ROTATION + ) + + const handlePress = useCallback(() => { + navigation.navigate(notification) + }, [navigation, notification]) + + if (!isWeeklyRotationEnabled) return null + + return ( + + + {messages.title} + + {messages.body} + + ) +} diff --git a/packages/web/src/components/notification/Notification/Notification.tsx b/packages/web/src/components/notification/Notification/Notification.tsx index a1e5f35faf1..7ed9f404440 100644 --- a/packages/web/src/components/notification/Notification/Notification.tsx +++ b/packages/web/src/components/notification/Notification/Notification.tsx @@ -44,6 +44,7 @@ import { TrendingUndergroundNotification } from './TrendingUndergroundNotificati import { USDCPurchaseBuyerNotification } from './USDCPurchaseBuyerNotification' import { USDCPurchaseSellerNotification } from './USDCPurchaseSellerNotification' import { UserSubscriptionNotification } from './UserSubscriptionNotification' +import { WeeklyRotationNotification } from './WeeklyRotationNotification' type NotificationProps = { notification: Notifications @@ -147,6 +148,9 @@ export const Notification = (props: NotificationProps) => { case NotificationType.ListenStreakReminder: { return } + case NotificationType.WeeklyRotation: { + return + } case NotificationType.FanRemixContestEndingSoon: { return ( diff --git a/packages/web/src/components/notification/Notification/WeeklyRotationNotification.tsx b/packages/web/src/components/notification/Notification/WeeklyRotationNotification.tsx new file mode 100644 index 00000000000..d0d8551b4c8 --- /dev/null +++ b/packages/web/src/components/notification/Notification/WeeklyRotationNotification.tsx @@ -0,0 +1,67 @@ +import { useCallback } from 'react' + +import { useFeatureFlag } from '@audius/common/hooks' +import { weeklyRotationNotificationMessages as messages } from '@audius/common/messages' +import { Name } from '@audius/common/models' +import { FeatureFlags } from '@audius/common/services' +import { WeeklyRotationNotification as WeeklyRotationNotificationType } from '@audius/common/store' +import { route } from '@audius/common/utils' +import { useDispatch } from 'react-redux' + +import { make, useRecord } from 'common/store/analytics/actions' +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 { IconWeeklyRotation } from './components/icons' + +const { WEEKLY_ROTATION_PAGE } = route + +type WeeklyRotationNotificationProps = { + notification: WeeklyRotationNotificationType +} + +/** + * "Your Weekly Rotation is ready", sent every Wednesday. Opens the viewer's + * own mix; there is no entity to resolve. + * + * Hidden while the feature flag is off: the server fans the row out to every + * active listener regardless, and a tile that links to a page which bounces + * back to Explore would be worse than no tile. + */ +export const WeeklyRotationNotification = ( + props: WeeklyRotationNotificationProps +) => { + const { notification } = props + const record = useRecord() + const dispatch = useDispatch() + const { timeLabel, isViewed } = notification + const { isEnabled: isWeeklyRotationEnabled } = useFeatureFlag( + FeatureFlags.WEEKLY_ROTATION + ) + + const handleClick = useCallback(() => { + dispatch(push(WEEKLY_ROTATION_PAGE)) + record( + make(Name.NOTIFICATIONS_CLICK_TILE, { + kind: notification.type, + link_to: WEEKLY_ROTATION_PAGE + }) + ) + }, [dispatch, notification, record]) + + if (!isWeeklyRotationEnabled) return null + + return ( + + }> + {messages.title} + + {messages.body} + + + ) +} diff --git a/packages/web/src/components/notification/Notification/components/icons.tsx b/packages/web/src/components/notification/Notification/components/icons.tsx index 5bf6621df46..bdaf3a4950c 100644 --- a/packages/web/src/components/notification/Notification/components/icons.tsx +++ b/packages/web/src/components/notification/Notification/components/icons.tsx @@ -12,7 +12,8 @@ import { IconTrending as IconTrendingBase, IconTrophy, IconUser, - IconCart as IconCartBase + IconCart as IconCartBase, + IconArrowRotate } from '@audius/harmony' import styles from './icons.module.css' @@ -81,3 +82,7 @@ export const IconStreakFire = () => { ) } + +export const IconWeeklyRotation = () => { + return +}