Skip to content
2 changes: 1 addition & 1 deletion packages/extension/src/newtab/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ function HijackingPage({
onPageChanged={onPageChanged}
initialPage="/"
shouldInitializeCurrentPage={false}
shortcuts={<HijackingLoginStrip />}
loginStrip={<HijackingLoginStrip />}
/>
);
}
Expand Down
65 changes: 65 additions & 0 deletions packages/extension/src/newtab/ExtensionSignInContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import type { ReactElement } from 'react';
import React from 'react';
import classNames from 'classnames';
import {
Button,
ButtonSize,
ButtonVariant,
} from '@dailydotdev/shared/src/components/buttons/Button';
import {
Typography,
TypographyType,
} from '@dailydotdev/shared/src/components/typography/Typography';
import { useAuthContext } from '@dailydotdev/shared/src/contexts/AuthContext';
import { loginHref, onboardingHref } from './onboardingHref';

interface ExtensionSignInContentProps {
className?: string;
}

// The sign-in title and Log in / Sign up CTAs for logged-out users. Renders
// nothing until auth resolves, or once the user is logged in.
export const ExtensionSignInContent = ({
className,
}: ExtensionSignInContentProps): ReactElement | null => {
const { isAuthReady, isLoggedIn } = useAuthContext();

if (!isAuthReady || isLoggedIn) {
return null;
}

return (
<div
className={classNames(
'flex w-full items-center justify-between gap-4',
className,
)}
>
<Typography
type={TypographyType.Body}
bold
className="min-w-0 flex-1 text-text-primary"
>
Sign in to personalize your feed
</Typography>
<div className="flex shrink-0 items-center gap-2">
<Button
tag="a"
href={loginHref}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking: once these became plain anchors, header clicks stopped showing up in analytics. Before this commit they went through showLogin({ trigger: AuthTriggers.MainButton }), so the auth flow recorded where the click came from. Now nothing is logged before the browser leaves for the webapp. The cover strip on the same screen logs LogEvent.Click with TargetType.LoginButton / SignupButton and target_id: 'hijacking' (HijackingLoginStrip.tsx, logClick), so a sign-up from the cover strip will be counted but one from the header CTAs on that same screen won't.

Suggest adding the same onClick click log to both anchors, with a separate target_id (e.g. 'hijacking_header') so the two placements can be compared. Keep href as is.

Reviewed by AI.

variant={ButtonVariant.Secondary}
size={ButtonSize.Small}
>
Log in
</Button>
<Button
tag="a"
href={onboardingHref}
variant={ButtonVariant.Primary}
size={ButtonSize.Small}
>
Sign up
</Button>
</div>
</div>
);
};
70 changes: 0 additions & 70 deletions packages/extension/src/newtab/ExtensionSignInStrip.tsx

This file was deleted.

24 changes: 3 additions & 21 deletions packages/extension/src/newtab/HijackingLoginStrip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import {
ProfilePicture,
} from '@dailydotdev/shared/src/components/ProfilePicture';
import {
OnboardingActions,
providerMap,
type SocialProvider,
} from '@dailydotdev/shared/src/components/auth/common';
Expand All @@ -24,25 +23,8 @@ import { onboardingGradientClasses } from '@dailydotdev/shared/src/components/on
import { useAuthContext } from '@dailydotdev/shared/src/contexts/AuthContext';
import { useLogContext } from '@dailydotdev/shared/src/contexts/LogContext';
import { useSignBack } from '@dailydotdev/shared/src/hooks/auth/useSignBack';
import { onboardingUrl } from '@dailydotdev/shared/src/lib/constants';
import { LogEvent, TargetType } from '@dailydotdev/shared/src/lib/log';
import feedStyles from '@dailydotdev/shared/src/components/Feed.module.css';

// The extension can't run social OAuth from its own origin (the API rejects it
// with a 403), so auth is handed off to the webapp onboarding flow, which runs
// on a trusted origin and auto-triggers the relevant auth screen.
const buildOnboardingHref = (action?: OnboardingActions): string => {
const base = new URL(onboardingUrl);
base.searchParams.append('r', 'extension');
if (action) {
base.searchParams.append('action', action);
}

return base.toString();
};

const onboardingHref = buildOnboardingHref();
const loginHref = buildOnboardingHref(OnboardingActions.Login);
import { loginHref, onboardingHref } from './onboardingHref';

const COPY = {
heading: 'Unlock the full daily.dev experience',
Expand Down Expand Up @@ -106,7 +88,7 @@ function CoverStrip({
<HijackingCoverStrip
copy={{ heading: COPY.heading, body: copy.body }}
actions={action}
className={classNames('mb-4', feedStyles.cards)}
className="mb-4"
sizer={<CoverStripSizer isLoggedOut={isLoggedOut} />}
/>
);
Expand Down Expand Up @@ -186,7 +168,7 @@ export default function HijackingLoginStrip(): ReactElement {

if (hasContinueAs && signBack) {
return (
<section className={classNames('mb-4 w-full pb-0', feedStyles.cards)}>
<section className="mb-4 w-full pb-0">
<div className="relative overflow-hidden rounded-16 border border-border-subtlest-tertiary bg-raw-pepper-90 shadow-2">
<div className="top-hero-aurora pointer-events-none absolute inset-0" />
<div className="dark relative z-1">
Expand Down
199 changes: 199 additions & 0 deletions packages/extension/src/newtab/MainFeedPage.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
import React from 'react';
import type { ReactNode } from 'react';
import { render, screen } from '@testing-library/react';
import type { AuthContextData } from '@dailydotdev/shared/src/contexts/AuthContext';
import AuthContext from '@dailydotdev/shared/src/contexts/AuthContext';
import { useLaptopLayout } from '@dailydotdev/shared/src/hooks/layout/useLaptopLayout';
import loggedUser from '@dailydotdev/shared/__tests__/fixture/loggedUser';
import MainFeedPage from './MainFeedPage';
import { loginHref, onboardingHref } from './onboardingHref';

jest.mock('next/dynamic', () => () => () => null);

jest.mock('@dailydotdev/shared/src/components/MainLayout', () => ({
__esModule: true,
default: ({
topBanner,
children,
}: {
topBanner: ReactNode;
children: ReactNode;
}) => (
<div>
<div data-testid="top-banner">{topBanner}</div>
{children}
</div>
),
}));

jest.mock('@dailydotdev/shared/src/components/MainFeedLayout', () => ({
__esModule: true,
default: ({
pageHeaderContent,
shortcuts,
}: {
pageHeaderContent: ReactNode;
shortcuts: ReactNode;
}) => (
<div>
<div data-testid="page-header">{pageHeaderContent}</div>
<div data-testid="shortcuts">{shortcuts}</div>
</div>
),
}));

jest.mock('@dailydotdev/shared/src/components/ScrollToTopButton', () => ({
__esModule: true,
default: () => null,
}));

jest.mock('@dailydotdev/shared/src/hooks/layout/useLaptopLayout', () => ({
useLaptopLayout: jest.fn(),
}));

jest.mock('@dailydotdev/shared/src/hooks', () => ({
...jest.requireActual('@dailydotdev/shared/src/hooks'),
useFeedLayout: () => ({ shouldUseListFeedLayout: false }),
}));

jest.mock('@dailydotdev/shared/src/contexts/LogContext', () => ({
useLogContext: () => ({ logEvent: jest.fn() }),
}));

jest.mock('@dailydotdev/shared/src/components/GrowthBookProvider', () => ({
useFeaturesReadyContext: () => ({ getFeatureValue: jest.fn() }),
}));

jest.mock('@dailydotdev/shared/src/contexts/SettingsContext', () => ({
useSettingsContext: () => ({ optOutCompanion: true, showTopSites: false }),
}));

jest.mock(
'@dailydotdev/shared/src/features/shortcuts/hooks/useShortcutLinks',
() => ({
useShortcutLinks: () => ({ shortcutLinks: [] }),
}),
);

jest.mock('@dailydotdev/shared/src/contexts/DndContext', () => ({
useDndContext: () => ({
isActive: false,
showDnd: false,
setShowDnd: jest.fn(),
}),
}));

jest.mock('@dailydotdev/shared/src/contexts/FeedContext', () => ({
FeedLayoutProvider: ({ children }: { children: ReactNode }) => children,
}));

jest.mock('@dailydotdev/shared/src/hooks/feed/useCustomDefaultFeed', () => ({
__esModule: true,
default: () => ({ isCustomDefaultFeed: false }),
}));

jest.mock('./ShortcutLinks/ShortcutLinks', () => ({
__esModule: true,
default: () => <div data-testid="shortcut-links" />,
}));

jest.mock('./ExtensionTopBanners', () => ({
ExtensionTopBanners: () => null,
}));

jest.mock('../companion/CompanionPopupButton', () => ({
CompanionPopupButton: () => null,
}));

jest.mock('../companion/useCompanionSettings', () => ({
useCompanionSettings: jest.fn(),
}));

const mockUseLaptopLayout = useLaptopLayout as jest.MockedFunction<
typeof useLaptopLayout
>;

const loggedOut = {
user: undefined,
isLoggedIn: false,
isAuthReady: true,
loadingUser: false,
} as unknown as AuthContextData;

const loggedIn = {
user: loggedUser,
isLoggedIn: true,
isAuthReady: true,
loadingUser: false,
} as unknown as AuthContextData;

const renderPage = ({
auth,
isLaptop,
loginStrip,
}: {
auth: AuthContextData;
isLaptop: boolean;
loginStrip?: ReactNode;
}) => {
mockUseLaptopLayout.mockReturnValue(isLaptop);

return render(
<AuthContext.Provider value={auth}>
<MainFeedPage onPageChanged={jest.fn()} loginStrip={loginStrip} />
</AuthContext.Provider>,
);
};

const loginStrip = <div data-testid="login-strip" />;

describe('MainFeedPage login strip', () => {
it('renders the strip above the feed and the sign-in CTAs in the page header on laptop', () => {
renderPage({ auth: loggedOut, isLaptop: true, loginStrip });

expect(screen.getByTestId('top-banner')).toContainElement(
screen.getByTestId('login-strip'),
);
expect(screen.getByTestId('shortcuts')).toBeEmptyDOMElement();

const header = screen.getByTestId('page-header');
expect(header).toHaveTextContent('Sign in to personalize your feed');
expect(screen.getByRole('link', { name: 'Log in' })).toHaveAttribute(
'href',
loginHref,
);
expect(screen.getByRole('link', { name: 'Sign up' })).toHaveAttribute(
'href',
onboardingHref,
);
});

it('renders the strip in the feed shortcuts slot below laptop', () => {
renderPage({ auth: loggedOut, isLaptop: false, loginStrip });

expect(screen.getByTestId('shortcuts')).toContainElement(
screen.getByTestId('login-strip'),
);
expect(screen.getByTestId('top-banner')).toBeEmptyDOMElement();
});

it('keeps the sign-in CTAs out of the header for a logged-in user', () => {
renderPage({ auth: loggedIn, isLaptop: true, loginStrip });

expect(screen.getByTestId('top-banner')).toContainElement(
screen.getByTestId('login-strip'),
);
expect(screen.getByTestId('page-header')).toBeEmptyDOMElement();
});

it('renders the shortcuts row and no sign-in UI on the regular new tab', () => {
renderPage({ auth: loggedIn, isLaptop: false });

expect(screen.queryByTestId('login-strip')).not.toBeInTheDocument();
expect(screen.getByTestId('shortcut-links')).toBeInTheDocument();
expect(screen.getByTestId('page-header')).toBeEmptyDOMElement();
expect(
screen.queryByText('Sign in to personalize your feed'),
).not.toBeInTheDocument();
});
});
Loading
Loading