From f78a34befb585f2d07f23384c250e650c7660843 Mon Sep 17 00:00:00 2001 From: idoshamun Date: Sun, 24 May 2026 10:49:52 +0000 Subject: [PATCH 1/2] chore: remove Happening Now notifications support Drop the MajorHeadlineAdded subscription hook, alerts banner, "Get real-time alerts" highlight card menu entry, in-app notification toggle, related feature flag, log events, and the dismiss-banner action. The HighlightCardOptions menu keeps the Pin/Disable placement controls. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../highlight/HighlightCardOptions.spec.tsx | 102 +--------- .../cards/highlight/HighlightCardOptions.tsx | 65 +----- .../EnableHighlightsAlerts.spec.tsx | 189 ------------------ .../highlights/EnableHighlightsAlerts.tsx | 176 ---------------- .../components/highlights/HighlightsPage.tsx | 2 - .../notifications/InAppNotificationsTab.tsx | 32 --- .../src/components/notifications/utils.ts | 2 - packages/shared/src/graphql/actions.ts | 1 - .../useMajorHeadlinesSubscription.ts | 100 --------- packages/shared/src/lib/featureManagement.ts | 4 - packages/shared/src/lib/log.ts | 7 - 11 files changed, 9 insertions(+), 671 deletions(-) delete mode 100644 packages/shared/src/components/highlights/EnableHighlightsAlerts.spec.tsx delete mode 100644 packages/shared/src/components/highlights/EnableHighlightsAlerts.tsx delete mode 100644 packages/shared/src/hooks/notifications/useMajorHeadlinesSubscription.ts diff --git a/packages/shared/src/components/cards/highlight/HighlightCardOptions.spec.tsx b/packages/shared/src/components/cards/highlight/HighlightCardOptions.spec.tsx index 921d2e108d0..a08c36c1eab 100644 --- a/packages/shared/src/components/cards/highlight/HighlightCardOptions.spec.tsx +++ b/packages/shared/src/components/cards/highlight/HighlightCardOptions.spec.tsx @@ -7,23 +7,14 @@ import { SidebarSettingsFlags, } from '../../../graphql/settings'; -const mockSubscribe = jest.fn().mockResolvedValue(undefined); -const mockUnsubscribe = jest.fn().mockResolvedValue(undefined); const mockDisplayToast = jest.fn(); const mockUseAuth = jest.fn(); -const mockUseConditionalFeature = jest.fn(); -const mockUseMajorHeadlinesSubscription = jest.fn(); -const mockRouterPush = jest.fn(); const mockUpdateFlag = jest.fn().mockResolvedValue(undefined); const mockUseSettingsContext = jest.fn(); const mockLogEvent = jest.fn(); const mockInvalidateQueries = jest.fn().mockResolvedValue(undefined); const mockUseActiveFeedContext = jest.fn(); -jest.mock('next/router', () => ({ - useRouter: () => ({ push: mockRouterPush }), -})); - jest.mock('@tanstack/react-query', () => ({ ...(jest.requireActual('@tanstack/react-query') as Iterable), useQueryClient: () => ({ invalidateQueries: mockInvalidateQueries }), @@ -45,14 +36,6 @@ jest.mock('../../../contexts/LogContext', () => ({ useLogContext: () => ({ logEvent: mockLogEvent }), })); -jest.mock('../../../hooks/useConditionalFeature', () => ({ - useConditionalFeature: () => mockUseConditionalFeature(), -})); - -jest.mock('../../../hooks/notifications/useMajorHeadlinesSubscription', () => ({ - useMajorHeadlinesSubscription: () => mockUseMajorHeadlinesSubscription(), -})); - jest.mock('../../../hooks/useToastNotification', () => ({ useToastNotification: () => ({ displayToast: mockDisplayToast }), })); @@ -83,13 +66,6 @@ describe('HighlightCardOptions', () => { beforeEach(() => { jest.clearAllMocks(); mockUseAuth.mockReturnValue({ user: { id: '1' } }); - mockUseConditionalFeature.mockReturnValue({ value: true }); - mockUseMajorHeadlinesSubscription.mockReturnValue({ - isSubscribed: false, - isLoading: false, - subscribe: mockSubscribe, - unsubscribe: mockUnsubscribe, - }); mockUseSettingsContext.mockReturnValue({ flags: { highlightsPlacement: HighlightsPlacement.Default }, updateFlag: mockUpdateFlag, @@ -100,16 +76,13 @@ describe('HighlightCardOptions', () => { }); }); - it('should render the options menu with all items when feature is on and user is logged in', () => { + it('should render the placement options when the user is logged in', () => { renderComponent(); expect( screen.getByRole('button', { name: 'Pin to top' }), ).toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Disable' })).toBeInTheDocument(); - expect( - screen.getByRole('button', { name: 'Get real-time alerts' }), - ).toBeInTheDocument(); }); it('should not render for guests', () => { @@ -118,17 +91,7 @@ describe('HighlightCardOptions', () => { renderComponent(); expect( - screen.queryByRole('button', { name: 'Get real-time alerts' }), - ).not.toBeInTheDocument(); - }); - - it('should not render when feature is off', () => { - mockUseConditionalFeature.mockReturnValue({ value: false }); - - renderComponent(); - - expect( - screen.queryByRole('button', { name: 'Get real-time alerts' }), + screen.queryByRole('button', { name: 'Pin to top' }), ).not.toBeInTheDocument(); }); @@ -196,65 +159,4 @@ describe('HighlightCardOptions', () => { ); }); }); - - it('should subscribe and show toast with settings action when not subscribed', async () => { - renderComponent(); - - fireEvent.click( - screen.getByRole('button', { name: 'Get real-time alerts' }), - ); - - await waitFor(() => { - expect(mockSubscribe).toHaveBeenCalledWith('feed_card'); - }); - await waitFor(() => { - expect(mockDisplayToast).toHaveBeenCalledWith( - "You'll be the first to know when news breaks.", - expect.objectContaining({ - action: expect.objectContaining({ copy: 'Settings' }), - }), - ); - }); - }); - - it('should navigate to notification settings when toast action is clicked', async () => { - renderComponent(); - - fireEvent.click( - screen.getByRole('button', { name: 'Get real-time alerts' }), - ); - - await waitFor(() => { - expect(mockDisplayToast).toHaveBeenCalled(); - }); - - const toastArgs = mockDisplayToast.mock.calls[0][1]; - toastArgs.action.onClick(); - - expect(mockRouterPush).toHaveBeenCalledWith('/settings/notifications'); - }); - - it('should unsubscribe when subscribed', async () => { - mockUseMajorHeadlinesSubscription.mockReturnValue({ - isSubscribed: true, - isLoading: false, - subscribe: mockSubscribe, - unsubscribe: mockUnsubscribe, - }); - - renderComponent(); - - fireEvent.click( - screen.getByRole('button', { name: 'Turn off real-time alerts' }), - ); - - await waitFor(() => { - expect(mockUnsubscribe).toHaveBeenCalledWith('feed_card'); - }); - await waitFor(() => { - expect(mockDisplayToast).toHaveBeenCalledWith( - 'Real-time alerts turned off.', - ); - }); - }); }); diff --git a/packages/shared/src/components/cards/highlight/HighlightCardOptions.tsx b/packages/shared/src/components/cards/highlight/HighlightCardOptions.tsx index c5a2b3fa942..2d7bd89bc7f 100644 --- a/packages/shared/src/components/cards/highlight/HighlightCardOptions.tsx +++ b/packages/shared/src/components/cards/highlight/HighlightCardOptions.tsx @@ -1,16 +1,9 @@ import type { ReactElement } from 'react'; import React, { useMemo, useState } from 'react'; import classNames from 'classnames'; -import { useRouter } from 'next/router'; import { useQueryClient } from '@tanstack/react-query'; import { Button, ButtonSize, ButtonVariant } from '../../buttons/Button'; -import { - BellAddIcon, - BellSubscribedIcon, - EyeCancelIcon, - MenuIcon as KebabIcon, - PinIcon, -} from '../../icons'; +import { EyeCancelIcon, MenuIcon as KebabIcon, PinIcon } from '../../icons'; import { MenuIcon } from '../../MenuIcon'; import { DropdownMenu, @@ -22,9 +15,6 @@ import type { MenuItemProps } from '../../dropdown/common'; import { useActiveFeedContext } from '../../../contexts/ActiveFeedContext'; import { useAuthContext } from '../../../contexts/AuthContext'; import { useSettingsContext } from '../../../contexts/SettingsContext'; -import { useMajorHeadlinesSubscription } from '../../../hooks/notifications/useMajorHeadlinesSubscription'; -import { useConditionalFeature } from '../../../hooks/useConditionalFeature'; -import { featureMajorHeadlinesPush } from '../../../lib/featureManagement'; import { useToastNotification } from '../../../hooks/useToastNotification'; import { useLogContext } from '../../../contexts/LogContext'; import { @@ -34,8 +24,6 @@ import { import { LogEvent, Origin } from '../../../lib/log'; import { labels } from '../../../lib'; -const NOTIFICATION_SETTINGS_PATH = '/settings/notifications'; - interface HighlightCardOptionsProps { className?: string; } @@ -43,15 +31,12 @@ interface HighlightCardOptionsProps { const HighlightCardOptionsContent = ({ className, }: HighlightCardOptionsProps): ReactElement => { - const router = useRouter(); const [isPending, setIsPending] = useState(false); const { displayToast } = useToastNotification(); const { logEvent } = useLogContext(); const { flags, updateFlag } = useSettingsContext(); const queryClient = useQueryClient(); const { queryKey: feedQueryKey } = useActiveFeedContext(); - const { isSubscribed, isLoading, subscribe, unsubscribe } = - useMajorHeadlinesSubscription(); const placement = flags?.highlightsPlacement ?? HighlightsPlacement.Default; const isPinned = placement === HighlightsPlacement.Pinned; @@ -79,32 +64,8 @@ const HighlightCardOptionsContent = ({ } }; - const toggleSubscription = async () => { - if (isPending || isLoading) { - return; - } - setIsPending(true); - try { - if (isSubscribed) { - await unsubscribe('feed_card'); - displayToast('Real-time alerts turned off.'); - return; - } - await subscribe('feed_card'); - displayToast("You'll be the first to know when news breaks.", { - action: { - copy: 'Settings', - onClick: () => router.push(NOTIFICATION_SETTINGS_PATH), - }, - }); - } finally { - setIsPending(false); - } - }; - - const options = useMemo(() => { - const SubscribeIcon = isSubscribed ? BellSubscribedIcon : BellAddIcon; - return [ + const options = useMemo( + () => [ { label: isPinned ? 'Unpin from top' : 'Pin to top', icon: , @@ -120,17 +81,10 @@ const HighlightCardOptionsContent = ({ action: () => updatePlacement(HighlightsPlacement.Disabled), disabled: isPending, }, - { - label: isSubscribed - ? 'Turn off real-time alerts' - : 'Get real-time alerts', - icon: , - action: toggleSubscription, - disabled: isPending || isLoading, - }, - ]; + ], // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isPinned, isSubscribed, isPending, isLoading]); + [isPinned, isPending], + ); return ( @@ -157,13 +111,8 @@ export const HighlightCardOptions = ({ className, }: HighlightCardOptionsProps): ReactElement | null => { const auth = useAuthContext(); - const user = auth?.user; - const { value: isFeatureEnabled } = useConditionalFeature({ - feature: featureMajorHeadlinesPush, - shouldEvaluate: !!user, - }); - if (!isFeatureEnabled || !user) { + if (!auth?.user) { return null; } diff --git a/packages/shared/src/components/highlights/EnableHighlightsAlerts.spec.tsx b/packages/shared/src/components/highlights/EnableHighlightsAlerts.spec.tsx deleted file mode 100644 index 76a28fa3f45..00000000000 --- a/packages/shared/src/components/highlights/EnableHighlightsAlerts.spec.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; -import { EnableHighlightsAlerts } from './EnableHighlightsAlerts'; -import { LogEvent } from '../../lib/log'; -import { ActionType } from '../../graphql/actions'; - -const mockLogEvent = jest.fn(); -const mockSubscribe = jest.fn().mockResolvedValue(undefined); -const mockCompleteAction = jest.fn().mockResolvedValue(undefined); -const mockCheckHasCompleted = jest.fn(); -const mockDisplayToast = jest.fn(); -const mockUseAuth = jest.fn(); -const mockUseConditionalFeature = jest.fn(); -const mockUseMajorHeadlinesSubscription = jest.fn(); -const mockRouterPush = jest.fn(); -const mockUsePushNotificationContext = jest.fn(); - -jest.mock('next/router', () => ({ - useRouter: () => ({ push: mockRouterPush }), -})); - -jest.mock('../../contexts/PushNotificationContext', () => ({ - usePushNotificationContext: () => mockUsePushNotificationContext(), -})); - -jest.mock('../../contexts/LogContext', () => ({ - useLogContext: () => ({ logEvent: mockLogEvent }), -})); - -jest.mock('../../contexts/AuthContext', () => ({ - useAuthContext: () => mockUseAuth(), -})); - -jest.mock('../../hooks/useActions', () => ({ - useActions: () => ({ - checkHasCompleted: mockCheckHasCompleted, - completeAction: mockCompleteAction, - isActionsFetched: true, - }), -})); - -jest.mock('../../hooks/useConditionalFeature', () => ({ - useConditionalFeature: () => mockUseConditionalFeature(), -})); - -jest.mock('../../hooks/notifications/useMajorHeadlinesSubscription', () => ({ - useMajorHeadlinesSubscription: () => mockUseMajorHeadlinesSubscription(), -})); - -jest.mock('../../hooks/useToastNotification', () => ({ - useToastNotification: () => ({ displayToast: mockDisplayToast }), -})); - -const renderComponent = () => render(); - -describe('EnableHighlightsAlerts', () => { - beforeEach(() => { - jest.clearAllMocks(); - mockUseAuth.mockReturnValue({ user: { id: '1' } }); - mockUseConditionalFeature.mockReturnValue({ value: true }); - mockUseMajorHeadlinesSubscription.mockReturnValue({ - isSubscribed: false, - isLoading: false, - subscribe: mockSubscribe, - unsubscribe: jest.fn(), - }); - mockCheckHasCompleted.mockReturnValue(false); - mockUsePushNotificationContext.mockReturnValue({ isSubscribed: false }); - }); - - it('should render banner when feature is on, user is logged in, not subscribed and not dismissed', () => { - renderComponent(); - - expect(screen.getByText('Never miss a major headline')).toBeInTheDocument(); - expect(screen.getByText('Notify me')).toBeInTheDocument(); - }); - - it('should not render when feature is off', () => { - mockUseConditionalFeature.mockReturnValue({ value: false }); - - renderComponent(); - - expect( - screen.queryByText('Never miss a major headline'), - ).not.toBeInTheDocument(); - }); - - it('should not render for guests', () => { - mockUseAuth.mockReturnValue({ user: undefined }); - - renderComponent(); - - expect( - screen.queryByText('Never miss a major headline'), - ).not.toBeInTheDocument(); - }); - - it('should not render when already subscribed', () => { - mockUseMajorHeadlinesSubscription.mockReturnValue({ - isSubscribed: true, - isLoading: false, - subscribe: mockSubscribe, - unsubscribe: jest.fn(), - }); - - renderComponent(); - - expect( - screen.queryByText('Never miss a major headline'), - ).not.toBeInTheDocument(); - }); - - it('should not render when dismissed', () => { - mockCheckHasCompleted.mockReturnValue(true); - - renderComponent(); - - expect( - screen.queryByText('Never miss a major headline'), - ).not.toBeInTheDocument(); - }); - - it('should log impression on render', () => { - renderComponent(); - - expect(mockLogEvent).toHaveBeenCalledWith({ - event_name: LogEvent.ImpressionMajorHeadlinesAlertsBanner, - extra: JSON.stringify({ origin: 'highlights_page' }), - }); - }); - - it('should subscribe and show toast with settings action on CTA click when push is not yet enabled', async () => { - renderComponent(); - - fireEvent.click(screen.getByText('Notify me')); - - await waitFor(() => { - expect(mockSubscribe).toHaveBeenCalledWith('highlights_page'); - }); - - await waitFor(() => { - expect(mockDisplayToast).toHaveBeenCalledWith( - "You'll be the first to know when news breaks.", - expect.objectContaining({ - action: expect.objectContaining({ copy: 'Settings' }), - }), - ); - }); - - const toastArgs = mockDisplayToast.mock.calls[0][1]; - toastArgs.action.onClick(); - expect(mockRouterPush).toHaveBeenCalledWith('/settings/notifications'); - }); - - it('should show enabled state inline when push was already granted', async () => { - mockUsePushNotificationContext.mockReturnValue({ isSubscribed: true }); - - renderComponent(); - - fireEvent.click(screen.getByText('Notify me')); - - await waitFor(() => { - expect(mockSubscribe).toHaveBeenCalledWith('highlights_page'); - }); - - await waitFor(() => { - expect(screen.getByText("You're in the loop")).toBeInTheDocument(); - }); - - expect(mockDisplayToast).not.toHaveBeenCalled(); - }); - - it('should complete dismiss action and log dismiss on close', async () => { - renderComponent(); - - fireEvent.click(screen.getByRole('button', { name: 'Dismiss' })); - - expect(mockLogEvent).toHaveBeenCalledWith({ - event_name: LogEvent.DismissMajorHeadlinesAlertsBanner, - extra: JSON.stringify({ origin: 'highlights_page' }), - }); - - await waitFor(() => { - expect(mockCompleteAction).toHaveBeenCalledWith( - ActionType.DismissedMajorHeadlinesAlertsBanner, - ); - }); - }); -}); diff --git a/packages/shared/src/components/highlights/EnableHighlightsAlerts.tsx b/packages/shared/src/components/highlights/EnableHighlightsAlerts.tsx deleted file mode 100644 index 4055f226933..00000000000 --- a/packages/shared/src/components/highlights/EnableHighlightsAlerts.tsx +++ /dev/null @@ -1,176 +0,0 @@ -import type { ReactElement } from 'react'; -import React, { useCallback, useState } from 'react'; -import classNames from 'classnames'; -import { useRouter } from 'next/router'; -import { - Button, - ButtonColor, - ButtonSize, - ButtonVariant, -} from '../buttons/Button'; -import CloseButton from '../CloseButton'; -import { - cloudinaryNotificationsBrowser, - cloudinaryNotificationsBrowserEnabled, -} from '../../lib/image'; -import { VIcon } from '../icons'; -import { useAuthContext } from '../../contexts/AuthContext'; -import { useActions } from '../../hooks/useActions'; -import { ActionType } from '../../graphql/actions'; -import { useMajorHeadlinesSubscription } from '../../hooks/notifications/useMajorHeadlinesSubscription'; -import { useConditionalFeature } from '../../hooks/useConditionalFeature'; -import { featureMajorHeadlinesPush } from '../../lib/featureManagement'; -import { useLogContext } from '../../contexts/LogContext'; -import useLogEventOnce from '../../hooks/log/useLogEventOnce'; -import { LogEvent } from '../../lib/log'; -import { useToastNotification } from '../../hooks/useToastNotification'; -import { usePushNotificationContext } from '../../contexts/PushNotificationContext'; - -interface EnableHighlightsAlertsProps { - className?: string; -} - -const ORIGIN = 'highlights_page'; -const NOTIFICATION_SETTINGS_PATH = '/settings/notifications'; - -export const EnableHighlightsAlerts = ({ - className, -}: EnableHighlightsAlertsProps): ReactElement | null => { - const router = useRouter(); - const { user } = useAuthContext(); - const { isSubscribed: isPushEnabled } = usePushNotificationContext(); - const { checkHasCompleted, completeAction, isActionsFetched } = useActions(); - const { logEvent } = useLogContext(); - const { displayToast } = useToastNotification(); - const { isSubscribed, isLoading, subscribe } = - useMajorHeadlinesSubscription(); - const [isPending, setIsPending] = useState(false); - const [acceptedJustNow, setAcceptedJustNow] = useState(false); - - const { value: isFeatureEnabled } = useConditionalFeature({ - feature: featureMajorHeadlinesPush, - shouldEvaluate: !!user, - }); - - const isDismissed = checkHasCompleted( - ActionType.DismissedMajorHeadlinesAlertsBanner, - ); - - const shouldRender = - isFeatureEnabled && - !!user && - isActionsFetched && - !isSubscribed && - !isDismissed; - - useLogEventOnce( - () => ({ - event_name: LogEvent.ImpressionMajorHeadlinesAlertsBanner, - extra: JSON.stringify({ origin: ORIGIN }), - }), - { condition: shouldRender }, - ); - - const handleEnable = useCallback(async () => { - if (isPending || isLoading) { - return; - } - setIsPending(true); - try { - await subscribe(ORIGIN); - if (isPushEnabled) { - setAcceptedJustNow(true); - return; - } - displayToast("You'll be the first to know when news breaks.", { - action: { - copy: 'Settings', - onClick: () => router.push(NOTIFICATION_SETTINGS_PATH), - }, - }); - } finally { - setIsPending(false); - } - }, [isPending, isLoading, subscribe, isPushEnabled, displayToast, router]); - - const handleDismiss = useCallback(() => { - completeAction(ActionType.DismissedMajorHeadlinesAlertsBanner); - logEvent({ - event_name: LogEvent.DismissMajorHeadlinesAlertsBanner, - extra: JSON.stringify({ origin: ORIGIN }), - }); - }, [completeAction, logEvent]); - - if (!shouldRender) { - return null; - } - - return ( -
- - {acceptedJustNow && } - {acceptedJustNow ? "You're in the loop" : 'Never miss a major headline'} - -
-

- {acceptedJustNow ? ( - <> - You can change your{' '} - {' '} - anytime. - - ) : ( - 'Be the first to know when something major happens in the developer world.' - )} -

- -
-
- {!acceptedJustNow && ( - - )} -
- -
- ); -}; - -export default EnableHighlightsAlerts; diff --git a/packages/shared/src/components/highlights/HighlightsPage.tsx b/packages/shared/src/components/highlights/HighlightsPage.tsx index f3616cfaa09..784d9d1cd61 100644 --- a/packages/shared/src/components/highlights/HighlightsPage.tsx +++ b/packages/shared/src/components/highlights/HighlightsPage.tsx @@ -13,7 +13,6 @@ import { import { Tab, TabContainer } from '../tabs/TabContainer'; import { DigestCTA } from './DigestCTA'; import { HighlightItem } from './HighlightItem'; -import { EnableHighlightsAlerts } from './EnableHighlightsAlerts'; const MAJOR_HEADLINES_LABEL = 'Headlines'; const SKELETON_COUNT = 5; @@ -149,7 +148,6 @@ export const HighlightsPage = (): ReactElement => { Happening Now - { const { logEvent } = useLogContext(); @@ -50,17 +47,12 @@ const InAppNotificationsTab = (): ReactElement => { const { isSubscribed, isInitialized, isPushSupported } = usePushNotificationContext(); const { openModal } = useLazyModal(); - const { user } = useAuthContext(); const { notificationSettings: ns, toggleSetting, toggleGroup, getGroupStatus, } = useNotificationSettings(); - const { value: isMajorHeadlinesEnabled } = useConditionalFeature({ - feature: featureMajorHeadlinesPush, - shouldEvaluate: !!user, - }); const onTogglePush = async () => { logEvent({ @@ -125,30 +117,6 @@ const InAppNotificationsTab = (): ReactElement => { } /> - {isMajorHeadlinesEnabled && ( - <> - - - Happening Now - - - - toggleSetting(NotificationType.MajorHeadlineAdded, 'inApp') - } - /> - - - - - )} Activity diff --git a/packages/shared/src/components/notifications/utils.ts b/packages/shared/src/components/notifications/utils.ts index e0ff224a3c5..174d95b45b7 100644 --- a/packages/shared/src/components/notifications/utils.ts +++ b/packages/shared/src/components/notifications/utils.ts @@ -95,7 +95,6 @@ export enum NotificationType { NewOpportunityMatch = 'new_opportunity_match', WarmIntro = 'warm_intro', ExperienceCompanyEnriched = 'experience_company_enriched', - MajorHeadlineAdded = 'major_headline_added', LiveRoomStarted = 'live_room_started', } @@ -213,7 +212,6 @@ export const notificationTypeTheme: Partial> = [NotificationType.BriefingReady]: 'text-brand-default', [NotificationType.DigestReady]: 'text-brand-default', [NotificationType.UserFollow]: 'text-brand-default', - [NotificationType.MajorHeadlineAdded]: 'text-brand-default', }; export const notificationTypeNotClickable: Partial< diff --git a/packages/shared/src/graphql/actions.ts b/packages/shared/src/graphql/actions.ts index 8415f0da445..999d0ec1513 100644 --- a/packages/shared/src/graphql/actions.ts +++ b/packages/shared/src/graphql/actions.ts @@ -65,7 +65,6 @@ export enum ActionType { DismissBriefCard = 'dismiss_brief_card', DigestUpsell = 'digest_upsell', AskUpsellSearch = 'ask_upsell_search', - DismissedMajorHeadlinesAlertsBanner = 'dismissed_major_headlines_alerts_banner', DismissedNewTabCustomizer = 'dismissed_new_tab_customizer', SeenKeepItOverlay = 'seen_keep_it_overlay', DismissCompanionDemoWidget = 'dismiss_companion_demo_widget', diff --git a/packages/shared/src/hooks/notifications/useMajorHeadlinesSubscription.ts b/packages/shared/src/hooks/notifications/useMajorHeadlinesSubscription.ts deleted file mode 100644 index 611a0d20346..00000000000 --- a/packages/shared/src/hooks/notifications/useMajorHeadlinesSubscription.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { useCallback } from 'react'; -import { NotificationType } from '../../components/notifications/utils'; -import { NotificationPreferenceStatus } from '../../graphql/notifications'; -import useNotificationSettings from './useNotificationSettings'; -import { usePushNotificationMutation } from './usePushNotificationMutation'; -import { usePushNotificationContext } from '../../contexts/PushNotificationContext'; -import { useAuthContext } from '../../contexts/AuthContext'; -import { useLogContext } from '../../contexts/LogContext'; -import { LogEvent, NotificationPromptSource } from '../../lib/log'; - -type MajorHeadlinesOrigin = 'settings' | 'highlights_page' | 'feed_card'; - -type UseMajorHeadlinesSubscriptionResult = { - isSubscribed: boolean; - isPushEnabled: boolean; - isLoading: boolean; - subscribe: (origin: MajorHeadlinesOrigin) => Promise; - unsubscribe: (origin: MajorHeadlinesOrigin) => Promise; -}; - -const ORIGIN_TO_PROMPT_SOURCE: Record< - MajorHeadlinesOrigin, - NotificationPromptSource -> = { - settings: NotificationPromptSource.MajorHeadlinesSettings, - highlights_page: NotificationPromptSource.MajorHeadlinesPage, - feed_card: NotificationPromptSource.MajorHeadlinesCard, -}; - -export const useMajorHeadlinesSubscription = - (): UseMajorHeadlinesSubscriptionResult => { - const { user } = useAuthContext(); - const { logEvent } = useLogContext(); - const { isSubscribed: isPushEnabled } = usePushNotificationContext(); - const { onEnablePush } = usePushNotificationMutation(); - const { - notificationSettings, - isLoadingPreferences, - setNotificationStatusBulk, - } = useNotificationSettings(); - - const settings = - notificationSettings?.[NotificationType.MajorHeadlineAdded]; - const isSubscribed = - settings?.inApp === NotificationPreferenceStatus.Subscribed; - - const subscribe = useCallback( - async (origin: MajorHeadlinesOrigin) => { - if (!user) { - return; - } - - await onEnablePush(ORIGIN_TO_PROMPT_SOURCE[origin]); - - setNotificationStatusBulk([ - { - type: NotificationType.MajorHeadlineAdded, - channel: 'inApp', - status: NotificationPreferenceStatus.Subscribed, - }, - ]); - - logEvent({ - event_name: LogEvent.EnableMajorHeadlinesAlerts, - extra: JSON.stringify({ origin }), - }); - }, - [user, onEnablePush, setNotificationStatusBulk, logEvent], - ); - - const unsubscribe = useCallback( - async (origin: MajorHeadlinesOrigin) => { - if (!user) { - return; - } - - setNotificationStatusBulk([ - { - type: NotificationType.MajorHeadlineAdded, - channel: 'inApp', - status: NotificationPreferenceStatus.Muted, - }, - ]); - - logEvent({ - event_name: LogEvent.DisableMajorHeadlinesAlerts, - extra: JSON.stringify({ origin }), - }); - }, - [user, setNotificationStatusBulk, logEvent], - ); - - return { - isSubscribed, - isPushEnabled, - isLoading: isLoadingPreferences, - subscribe, - unsubscribe, - }; - }; diff --git a/packages/shared/src/lib/featureManagement.ts b/packages/shared/src/lib/featureManagement.ts index 11a2946d5a6..7e401a653bf 100644 --- a/packages/shared/src/lib/featureManagement.ts +++ b/packages/shared/src/lib/featureManagement.ts @@ -33,10 +33,6 @@ export const discussedFeedVersion = new Feature('discussed_feed_version', 2); export const latestFeedVersion = new Feature('latest_feed_version', 2); export const customFeedVersion = new Feature('custom_feed_version', 2); export const featureFeedV2Highlights = new Feature('feed_v2_highlights', false); -export const featureMajorHeadlinesPush = new Feature( - 'major_headlines_push', - false, -); export const featurePostPageHighlights = new Feature( 'post_page_highlights', false, diff --git a/packages/shared/src/lib/log.ts b/packages/shared/src/lib/log.ts index 4ed96ae7058..f9568acd49d 100644 --- a/packages/shared/src/lib/log.ts +++ b/packages/shared/src/lib/log.ts @@ -143,10 +143,6 @@ export enum LogEvent { EnableNotification = 'enable notification', DisableNotification = 'disable notification', ScheduleDigest = 'schedule digest', - EnableMajorHeadlinesAlerts = 'enable major headlines alerts', - DisableMajorHeadlinesAlerts = 'disable major headlines alerts', - ImpressionMajorHeadlinesAlertsBanner = 'impression major headlines alerts banner', - DismissMajorHeadlinesAlertsBanner = 'dismiss major headlines alerts banner', // notifications - end // squads - start ViewSquadInvitation = 'view squad invitation', @@ -678,9 +674,6 @@ export enum NotificationPromptSource { SquadChecklist = 'squad checklist', SourceSubscribe = 'source subscribe', ReadingReminder = 'reading reminder', - MajorHeadlinesSettings = 'major headlines settings', - MajorHeadlinesPage = 'major headlines page', - MajorHeadlinesCard = 'major headlines card', StandupLobby = 'standup lobby', } From c20b3242a6c424b9a14f4e06ada60e91256a3156 Mon Sep 17 00:00:00 2001 From: idoshamun Date: Sun, 24 May 2026 10:58:49 +0000 Subject: [PATCH 2/2] chore: drop dead Megaphone icon mapping and simplify HighlightCardOptions The Megaphone icon was only mapped from the removed MajorHeadlineAdded notification. With only two static menu items left, inline the options array and drop the useMemo so the eslint-disable is no longer needed. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../cards/highlight/HighlightCardOptions.tsx | 50 +++++++++---------- .../src/components/notifications/utils.ts | 5 -- 2 files changed, 23 insertions(+), 32 deletions(-) diff --git a/packages/shared/src/components/cards/highlight/HighlightCardOptions.tsx b/packages/shared/src/components/cards/highlight/HighlightCardOptions.tsx index 2d7bd89bc7f..0f8e49352cb 100644 --- a/packages/shared/src/components/cards/highlight/HighlightCardOptions.tsx +++ b/packages/shared/src/components/cards/highlight/HighlightCardOptions.tsx @@ -1,5 +1,5 @@ import type { ReactElement } from 'react'; -import React, { useMemo, useState } from 'react'; +import React, { useState } from 'react'; import classNames from 'classnames'; import { useQueryClient } from '@tanstack/react-query'; import { Button, ButtonSize, ButtonVariant } from '../../buttons/Button'; @@ -11,7 +11,6 @@ import { DropdownMenuOptions, DropdownMenuTrigger, } from '../../dropdown/DropdownMenu'; -import type { MenuItemProps } from '../../dropdown/common'; import { useActiveFeedContext } from '../../../contexts/ActiveFeedContext'; import { useAuthContext } from '../../../contexts/AuthContext'; import { useSettingsContext } from '../../../contexts/SettingsContext'; @@ -38,8 +37,7 @@ const HighlightCardOptionsContent = ({ const queryClient = useQueryClient(); const { queryKey: feedQueryKey } = useActiveFeedContext(); - const placement = flags?.highlightsPlacement ?? HighlightsPlacement.Default; - const isPinned = placement === HighlightsPlacement.Pinned; + const isPinned = flags?.highlightsPlacement === HighlightsPlacement.Pinned; const updatePlacement = async (next: HighlightsPlacement) => { if (isPending) { @@ -64,28 +62,6 @@ const HighlightCardOptionsContent = ({ } }; - const options = useMemo( - () => [ - { - label: isPinned ? 'Unpin from top' : 'Pin to top', - icon: , - action: () => - updatePlacement( - isPinned ? HighlightsPlacement.Default : HighlightsPlacement.Pinned, - ), - disabled: isPending, - }, - { - label: 'Disable', - icon: , - action: () => updatePlacement(HighlightsPlacement.Disabled), - disabled: isPending, - }, - ], - // eslint-disable-next-line react-hooks/exhaustive-deps - [isPinned, isPending], - ); - return ( @@ -101,7 +77,27 @@ const HighlightCardOptionsContent = ({ /> - + , + action: () => + updatePlacement( + isPinned + ? HighlightsPlacement.Default + : HighlightsPlacement.Pinned, + ), + disabled: isPending, + }, + { + label: 'Disable', + icon: , + action: () => updatePlacement(HighlightsPlacement.Disabled), + disabled: isPending, + }, + ]} + /> ); diff --git a/packages/shared/src/components/notifications/utils.ts b/packages/shared/src/components/notifications/utils.ts index 174d95b45b7..586d781f5e0 100644 --- a/packages/shared/src/components/notifications/utils.ts +++ b/packages/shared/src/components/notifications/utils.ts @@ -18,7 +18,6 @@ import { AnalyticsIcon, JobIcon, MagicIcon, - MegaphoneIcon, } from '../icons'; import type { NotificationPromptSource } from '../../lib/log'; import { BookmarkReminderIcon } from '../icons/Bookmark/Reminder'; @@ -116,7 +115,6 @@ export enum NotificationIconType { Core = 'Core', Analytics = 'Analytics', Opportunity = 'Opportunity', - Megaphone = 'Megaphone', } export const notificationIcon: Record< @@ -140,7 +138,6 @@ export const notificationIcon: Record< [NotificationIconType.Core]: CoreIcon, [NotificationIconType.Analytics]: AnalyticsIcon, [NotificationIconType.Opportunity]: JobIcon, - [NotificationIconType.Megaphone]: MegaphoneIcon, }; export const notificationIconAsPrimary: NotificationIconType[] = [ @@ -166,7 +163,6 @@ export const notificationIconTypeTheme: Record = { [NotificationIconType.Core]: '', [NotificationIconType.Analytics]: 'text-brand-default', [NotificationIconType.Opportunity]: 'text-black', - [NotificationIconType.Megaphone]: 'text-brand-default', }; export const notificationIconStyle: Record< @@ -190,7 +186,6 @@ export const notificationIconStyle: Record< [NotificationIconType.Core]: null, [NotificationIconType.Analytics]: null, [NotificationIconType.Opportunity]: { background: briefButtonBg }, - [NotificationIconType.Megaphone]: null, }; export const notificationTypeTheme: Partial> =