diff --git a/packages/shared/src/components/auth/AuthOptionsInner.tsx b/packages/shared/src/components/auth/AuthOptionsInner.tsx index 579e033600..255ddd2050 100644 --- a/packages/shared/src/components/auth/AuthOptionsInner.tsx +++ b/packages/shared/src/components/auth/AuthOptionsInner.tsx @@ -146,6 +146,7 @@ function AuthOptionsInner({ isOnboardingFunnel, compact, signupStyle, + inlineProviders, preferGithub, autoTriggerProvider, socialProviderScopes, @@ -871,6 +872,7 @@ function AuthOptionsInner({ hideSignupDisclaimer={hideSignupDisclaimer} compact={compact} signupStyle={signupStyle} + inlineProviders={inlineProviders} preferGithub={preferGithub} onAuthOpenLogged={() => setHasLoggedAuthOpen(true)} /> diff --git a/packages/shared/src/components/auth/ExploreSignupStrip.spec.tsx b/packages/shared/src/components/auth/ExploreSignupStrip.spec.tsx index afbc7f3ba8..b9988d41d1 100644 --- a/packages/shared/src/components/auth/ExploreSignupStrip.spec.tsx +++ b/packages/shared/src/components/auth/ExploreSignupStrip.spec.tsx @@ -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'; @@ -14,6 +17,45 @@ jest.mock('../../hooks/useViewSize', () => ({ useViewSize: jest.fn(), })); +jest.mock('./AuthOptions', () => ({ + __esModule: true, + default: ({ + onAuthStateUpdate, + }: { + onAuthStateUpdate?: (props: Record) => void; + }) => { + const { AuthDisplay: Display } = jest.requireActual('./common'); + + return ( +
+ + +
+ ); + }, +})); + const mockUseViewSize = useViewSize as jest.Mock; const logEvent = jest.fn(); const showLogin = jest.fn(); @@ -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(); }); @@ -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 () => { @@ -136,7 +182,11 @@ describe('ExploreSignupStrip', () => { }); expect(showLogin).toHaveBeenCalledWith({ trigger: AuthTriggers.Onboarding, - options: { isLogin: true }, + options: { + isLogin: true, + defaultDisplay: undefined, + formValues: undefined, + }, }); }); }); diff --git a/packages/shared/src/components/auth/ExploreSignupStrip.tsx b/packages/shared/src/components/auth/ExploreSignupStrip.tsx index 6e1c442036..0d5509e480 100644 --- a/packages/shared/src/components/auth/ExploreSignupStrip.tsx +++ b/packages/shared/src/components/auth/ExploreSignupStrip.tsx @@ -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'; @@ -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, @@ -47,9 +53,14 @@ export function ExploreSignupStrip({ // Holds the strip's slot in the server HTML until boot answers. if (!isAuthReady) { return ( - +
+
+
+
+
); } @@ -57,28 +68,64 @@ export function ExploreSignupStrip({ 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 ( - - } - /> + +
+
+
+
+

+ {copy.heading} +

+

{copy.body}

+ +
+
+ ); } diff --git a/packages/shared/src/components/auth/HijackingCoverStrip.tsx b/packages/shared/src/components/auth/HijackingCoverStrip.tsx index 395ca2a4a8..5fe887b448 100644 --- a/packages/shared/src/components/auth/HijackingCoverStrip.tsx +++ b/packages/shared/src/components/auth/HijackingCoverStrip.tsx @@ -1,7 +1,6 @@ import type { ReactElement, ReactNode } from 'react'; import React from 'react'; import classNames from 'classnames'; -import { Button, ButtonSize, ButtonVariant } from '../buttons/Button'; import { cloudinaryHijackingCoverArt } from '../../lib/image'; export const hijackingPrimaryCta = @@ -15,54 +14,47 @@ export interface HijackingCoverCopy { body: string; } -interface HijackingCoverAuthActionsProps { - signup: string; - login: string; - onSignupClick: () => void; - onLoginClick: () => void; +const coverArtPosition = { objectPosition: '50% 62%' }; + +// The card's height without a sizer. +const hijackingCoverStripMinHeight = 'min-h-[14rem]'; + +interface HijackingCoverCardProps { + children: ReactNode; + className?: string; } -// The sign up / log in pair for surfaces that offer both. -export function HijackingCoverAuthActions({ - signup, - login, - onSignupClick, - onLoginClick, -}: HijackingCoverAuthActionsProps): ReactElement { +// The cover art card; children lay out the content over it. +export function HijackingCoverCard({ + children, + className, +}: HijackingCoverCardProps): ReactElement { return ( - <> - - - +
+
+ +
+
+ {children} +
+
); } -const coverArtPosition = { objectPosition: '50% 62%' }; +export const hijackingCoverHeadingClassName = + 'font-bold text-white typo-title2 [text-shadow:0_2px_18px_rgba(0,0,0,0.6)]'; -// The card's height without a sizer. -export const hijackingCoverStripMinHeight = 'min-h-[14rem]'; +export const hijackingCoverBodyClassName = + 'text-white/80 text-balance text-sm [text-shadow:0_1px_12px_rgba(0,0,0,0.6)]'; interface HijackingCoverStripProps { copy: HijackingCoverCopy; @@ -82,60 +74,36 @@ export function HijackingCoverStrip({ className, }: HijackingCoverStripProps): ReactElement { return ( -
-
- + {!!sizer && ( +
-
-
- {!!sizer && ( -
- {sizer} -
+ className="invisible hidden tablet:flex tablet:flex-row tablet:items-stretch" + > + {sizer} +
+ )} +
+

{copy.heading}

+

-

- {copy.heading} -

-

- {copy.body} -

-
- {actions} -
+ {copy.body} +

+
+ {actions}
-
- ); -} - -// The same box as the strip without a sizer, empty: holds its slot. -export function HijackingCoverStripPlaceholder({ - className, -}: { - className?: string; -}): ReactElement { - return ( -
-
-
+ ); } diff --git a/packages/shared/src/components/auth/OnboardingRegistrationForm.tsx b/packages/shared/src/components/auth/OnboardingRegistrationForm.tsx index 52922b20a4..d9ff8a9e01 100644 --- a/packages/shared/src/components/auth/OnboardingRegistrationForm.tsx +++ b/packages/shared/src/components/auth/OnboardingRegistrationForm.tsx @@ -40,6 +40,7 @@ interface OnboardingRegistrationFormProps extends AuthFormProps { hideSignupDisclaimer?: boolean; compact?: boolean; signupStyle?: SignupStyle; + inlineProviders?: boolean; preferGithub?: boolean; onAuthOpenLogged?: () => void; } @@ -119,6 +120,7 @@ export const OnboardingRegistrationForm = ({ hideSignupDisclaimer, compact, signupStyle, + inlineProviders, preferGithub, onAuthOpenLogged, }: OnboardingRegistrationFormProps): ReactElement => { @@ -211,7 +213,8 @@ export const OnboardingRegistrationForm = ({