Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2761223
feat(auth): give the public pages' signup strip the sticky banner's a…
tsahimatsliah Sep 16, 2026
96cbca2
fix(auth): scrim the strip's auth column and match the banner's colum…
tsahimatsliah Sep 16, 2026
a80b308
fix(auth): use arbitrary opacities so the strip's scrim actually renders
tsahimatsliah Sep 16, 2026
7a1131c
chore(auth): order the strip scrim's classnames
tsahimatsliah Sep 16, 2026
c4b4eda
feat(auth): put the strip's copy and auth stack on the left over a pr…
tsahimatsliah Sep 16, 2026
d9c73f3
feat(auth): centre the strip's column and frame the art around the ma…
tsahimatsliah Sep 16, 2026
251b4de
feat(auth): align the strip's column left and give its rows room
tsahimatsliah Sep 17, 2026
4bf2d44
feat(auth): align the strip's copy left and push the mascot further r…
tsahimatsliah Sep 17, 2026
33f7308
feat(auth): scale the strip's mascot up by half
tsahimatsliah Sep 17, 2026
3b83443
feat(auth): centre the strip's copy, buttons and art
tsahimatsliah Sep 17, 2026
e0f2d95
fix(auth): keep the strip's sides crisp with a narrower, lighter blur…
tsahimatsliah Sep 17, 2026
831e40a
fix(auth): shrink the strip's blur to a pocket behind the bottom links
tsahimatsliah Sep 17, 2026
42fefd5
chore(auth): drop the cover card's orphaned art prop and placeholder
tsahimatsliah Sep 17, 2026
c7bc0d2
fix(auth): even out the strip's vertical rhythm and make the provider…
tsahimatsliah Sep 17, 2026
bed2658
fix(auth): tighten the strip's title to its subtitle and open up the …
tsahimatsliah Sep 17, 2026
e8f8642
chore(auth): strip the signup strip's change narration and mock-prop …
tsahimatsliah Sep 17, 2026
e210cf3
fix(auth): match the gap under the strip's provider row to the one ab…
tsahimatsliah Sep 17, 2026
f7c1b73
Merge branch 'main' into claude/public-pages-strip-redesign-ecfe9b
tsahimatsliah Sep 17, 2026
8ebc3bc
fix(auth): trim the strip's vertical padding
tsahimatsliah Sep 17, 2026
6763608
fix(auth): settle the strip's padding at 24px
tsahimatsliah Sep 17, 2026
0e93405
fix(auth): hold the strip's form slot while its chunk loads
tsahimatsliah Sep 17, 2026
4f12715
fix(auth): size the strip's placeholder like the bordered card
tsahimatsliah Sep 17, 2026
4c0ee59
Merge branch 'main' into claude/public-pages-strip-redesign-ecfe9b
tsahimatsliah Sep 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/shared/src/components/auth/AuthOptionsInner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ function AuthOptionsInner({
isOnboardingFunnel,
compact,
signupStyle,
inlineProviders,
preferGithub,
autoTriggerProvider,
socialProviderScopes,
Expand Down Expand Up @@ -871,6 +872,7 @@ function AuthOptionsInner({
hideSignupDisclaimer={hideSignupDisclaimer}
compact={compact}
signupStyle={signupStyle}
inlineProviders={inlineProviders}
preferGithub={preferGithub}
onAuthOpenLogged={() => setHasLoggedAuthOpen(true)}
/>
Expand Down
78 changes: 64 additions & 14 deletions packages/shared/src/components/auth/ExploreSignupStrip.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ import { QueryClient } from '@tanstack/react-query';
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { TestBootProvider } from '../../../__tests__/helpers/boot';
import { ExploreSignupStrip } from './ExploreSignupStrip';
import { hijackingCoverStripMinHeight } from './HijackingCoverStrip';
import {
ExploreSignupStrip,
exploreSignupStripMinHeight,
} from './ExploreSignupStrip';
import { AuthDisplay } from './common';
import { useViewSize } from '../../hooks/useViewSize';
import { AuthTriggers } from '../../lib/auth';
import { LogEvent, TargetId, TargetType } from '../../lib/log';
Expand All @@ -14,6 +17,45 @@ jest.mock('../../hooks/useViewSize', () => ({
useViewSize: jest.fn(),
}));

jest.mock('./AuthOptions', () => ({
__esModule: true,
default: ({
onAuthStateUpdate,
}: {
onAuthStateUpdate?: (props: Record<string, unknown>) => void;
}) => {
const { AuthDisplay: Display } = jest.requireActual('./common');

return (
<div>
<button
type="button"
onClick={() =>
onAuthStateUpdate?.({
isAuthenticating: true,
defaultDisplay: Display.Registration,
})
}
>
Continue with email
</button>
<button
type="button"
onClick={() =>
onAuthStateUpdate?.({
isAuthenticating: true,
isLoginFlow: true,
email: '',
})
}
>
Log in
</button>
</div>
);
},
}));

const mockUseViewSize = useViewSize as jest.Mock;
const logEvent = jest.fn();
const showLogin = jest.fn();
Expand Down Expand Up @@ -73,9 +115,9 @@ describe('ExploreSignupStrip', () => {
const { container } = renderComponent({ isAuthReady: false });

expect(screen.queryByRole('heading')).not.toBeInTheDocument();
expect(container.firstElementChild?.firstElementChild).toHaveClass(
hijackingCoverStripMinHeight,
);
expect(
container.firstElementChild?.firstElementChild?.firstElementChild,
).toHaveClass(exploreSignupStripMinHeight);
expect(logEvent).not.toHaveBeenCalled();
});

Expand Down Expand Up @@ -108,20 +150,24 @@ describe('ExploreSignupStrip', () => {
expect(logEvent).toHaveBeenCalledWith(impression);
});

it('should open signup inline and log the click', async () => {
it('should hand the email signup off to the modal', async () => {
renderComponent();

await userEvent.click(screen.getByRole('button', { name: /Sign up/ }));
await userEvent.click(
screen.getByRole('button', { name: 'Continue with email' }),
);

expect(logEvent).toHaveBeenCalledWith({
event_name: LogEvent.Click,
target_type: TargetType.SignupButton,
target_id: TargetId.ExploreStrip,
});
expect(showLogin).toHaveBeenCalledWith({
trigger: AuthTriggers.Onboarding,
options: { isLogin: false },
options: {
isLogin: false,
defaultDisplay: AuthDisplay.Registration,
formValues: undefined,
},
});
expect(logEvent).not.toHaveBeenCalledWith(
expect.objectContaining({ event_name: LogEvent.Click }),
);
});

it('should open login inline and log the click', async () => {
Expand All @@ -136,7 +182,11 @@ describe('ExploreSignupStrip', () => {
});
expect(showLogin).toHaveBeenCalledWith({
trigger: AuthTriggers.Onboarding,
options: { isLogin: true },
options: {
isLogin: true,
defaultDisplay: undefined,
formValues: undefined,
},
});
});
});
97 changes: 72 additions & 25 deletions packages/shared/src/components/auth/ExploreSignupStrip.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import type { ReactElement } from 'react';
import React from 'react';
import classNames from 'classnames';
import AuthOptions from './AuthOptions';
import { ButtonSize } from '../buttons/Button';
import type { AuthOptionsProps } from './common';
import { AuthDisplay } from './common';
import type { HijackingCoverCopy } from './HijackingCoverStrip';
import {
HijackingCoverAuthActions,
HijackingCoverStrip,
HijackingCoverStripPlaceholder,
HijackingCoverCard,
hijackingCoverBodyClassName,
hijackingCoverHeadingClassName,
} from './HijackingCoverStrip';
import { useAuthContext } from '../../contexts/AuthContext';
import { useLogContext } from '../../contexts/LogContext';
Expand All @@ -20,6 +24,8 @@ const copy: HijackingCoverCopy = {
body: 'Log in to pick up where you left off.',
};

export const exploreSignupStripMinHeight = 'min-h-[14.5rem]';

// The new tab's cover strip for anonymous visitors, tablet and up.
export function ExploreSignupStrip({
className,
Expand Down Expand Up @@ -47,38 +53,79 @@ export function ExploreSignupStrip({
// Holds the strip's slot in the server HTML until boot answers.
if (!isAuthReady) {
return (
<HijackingCoverStripPlaceholder
className={classNames('hidden tablet:block', className)}
/>
<section
aria-hidden
className={classNames('hidden w-full tablet:block', className)}
>
<div className="rounded-16 border border-transparent">
<div className={exploreSignupStripMinHeight} />
</div>
</section>
);
}

if (!isTablet) {
return null;
}

const onAuthClick = (isLogin: boolean) => (): void => {
logEvent({
event_name: LogEvent.Click,
target_type: isLogin ? TargetType.LoginButton : TargetType.SignupButton,
target_id: TargetId.ExploreStrip,
});
const onAuthStateUpdate: AuthOptionsProps['onAuthStateUpdate'] = (props) => {
if (props.isLoginFlow) {
logEvent({
event_name: LogEvent.Click,
target_type: TargetType.LoginButton,
target_id: TargetId.ExploreStrip,
});
}

showLogin({ trigger: AuthTriggers.Onboarding, options: { isLogin } });
showLogin({
trigger: AuthTriggers.Onboarding,
options: {
isLogin: !!props.isLoginFlow,
defaultDisplay: props.defaultDisplay,
formValues: props.email ? { email: props.email } : undefined,
},
});
};

return (
<HijackingCoverStrip
copy={copy}
className={className}
actions={
<HijackingCoverAuthActions
signup="Sign up"
login="Log in"
onSignupClick={onAuthClick(false)}
onLoginClick={onAuthClick(true)}
/>
}
/>
<HijackingCoverCard className={className}>
<div className="cover-strip-blur pointer-events-none absolute bottom-0 left-1/2 h-3/5 w-[24rem] -translate-x-1/2" />
<div className="pointer-events-none absolute inset-x-0 bottom-0 h-3/4 bg-gradient-to-t from-raw-pepper-90/[0.9] via-raw-pepper-90/[0.55] to-transparent" />
<div
className={classNames(
'dark relative z-1 flex flex-col items-center justify-center px-6 py-6 text-center',
exploreSignupStripMinHeight,
)}
>
<div className="flex w-full max-w-[26.25rem] flex-col items-center gap-1">
<h3
className={classNames(
'text-balance',
hijackingCoverHeadingClassName,
)}
>
{copy.heading}
</h3>
<p className={hijackingCoverBodyClassName}>{copy.body}</p>
<AuthOptions
ignoreMessages
formRef={null as unknown as AuthOptionsProps['formRef']}
trigger={AuthTriggers.Onboarding}
targetId={TargetId.ExploreStrip}
simplified
defaultDisplay={AuthDisplay.OnboardingSignup}
forceDefaultDisplay
signupStyle="singlePrimary"
inlineProviders
preferGithub={false}
onboardingSignupButton={{ size: ButtonSize.Medium }}
className={{
container: 'mt-4 !min-h-[6.75rem] !overflow-visible',
}}
onAuthStateUpdate={onAuthStateUpdate}
/>
</div>
</div>
</HijackingCoverCard>
);
}
Loading
Loading