diff --git a/apps/web/src/components/auth/SignInForm.signInOptions.test.ts b/apps/web/src/components/auth/SignInForm.signInOptions.test.ts index be8b2ebd34..2ab0fc85aa 100644 --- a/apps/web/src/components/auth/SignInForm.signInOptions.test.ts +++ b/apps/web/src/components/auth/SignInForm.signInOptions.test.ts @@ -1,9 +1,8 @@ /* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires -- Jest node-environment mocks must be registered before loading the component. */ -// The sign-in landing keeps the email prompt but must also offer the OAuth -// providers (including 'Continue with ChatGPT'), the same group the sign-up page -// renders. The ChatGPT option is behind the PostHog flag, which for a -// signed-out visitor is evaluated against the email the visitor typed; the hook -// is stubbed here and the filter is asserted. The provider buttons and the +// The sign-in landing keeps the email prompt but must offer the same OAuth +// provider group as sign-up, 'Continue with ChatGPT' included. The ChatGPT +// button is not decided from the visitor's address: it renders on the first +// screen, before the visitor takes the email step. The provider buttons and the // email form are stubbed the way `SignInForm.test.ts` stubs them (their CSS // module cannot load in jest), but the stubs render the real provider labels // and the form's submit label. @@ -14,20 +13,11 @@ import { renderToStaticMarkup } from 'react-dom/server'; let mockFlowEmail = ''; let mockHintEmail = ''; -let mockChatGptAllowed = false; -let mockHookEmail: string | null = null; jest.mock('@/components/AnimatedLogoMark', () => ({ AnimatedLogoMark: () => null, })); -jest.mock('@/hooks/useChatGptSignInAccess', () => ({ - useChatGptSignInAccess: (email: string | null) => { - mockHookEmail = email; - return mockChatGptAllowed; - }, -})); - jest.mock('@/hooks/useSignInFlow', () => ({ useSignInFlow: ({ isSignUp }: { searchParams: Record; isSignUp?: boolean }) => ({ isHintLoaded: true, @@ -99,89 +89,37 @@ const { SignInForm } = require('./SignInForm') as { beforeEach(() => { mockFlowEmail = ''; mockHintEmail = ''; - mockChatGptAllowed = false; - mockHookEmail = null; }); describe('SignInForm sign-in options', () => { - it('hides ChatGPT on sign-in when the flag is off for the submitted email', () => { - const html = renderToStaticMarkup( - createElement(SignInForm, { searchParams: {}, title: 'Welcome.' }) - ); - - expect(html).not.toContain('Continue with ChatGPT'); - expect(html).toContain('Continue with Google'); - expect(html).toContain('Continue with Email'); - // Nothing is known before a submit, so the hook gets no address. - expect(mockHookEmail).toBe(null); - }); - - it('offers ChatGPT on sign-in when the flag is on for the submitted email', () => { - mockFlowEmail = 'person@kilo.ai'; - mockChatGptAllowed = true; + it('offers ChatGPT with the other OAuth providers before the email step', () => { const html = renderToStaticMarkup( createElement(SignInForm, { searchParams: {}, title: 'Welcome.' }) ); + // No address is known and the visitor has not submitted one, so the button + // cannot come from the ChatGPT access flag: it is a plain sign-in option. expect(html.match(/Continue with ChatGPT/g)).toHaveLength(1); expect(html).toContain('Continue with Google'); expect(html).toContain('Continue with Email'); - // Typing alone is not evaluated; the hook waits for the submit. - expect(mockHookEmail).toBe(null); - }); - - it('evaluates a prefilled ?email= address before the providers render', () => { - mockChatGptAllowed = true; - renderToStaticMarkup( - createElement(SignInForm, { - searchParams: { email: 'prefill@kilo.ai' }, - title: 'Welcome.', - }) + // It renders inside the provider group, in the shared provider order, so it + // reads as one option beside the others rather than a lone button. + expect(html.indexOf('Continue with Google')).toBeLessThan( + html.indexOf('Continue with ChatGPT') ); - - expect(mockHookEmail).toBe('prefill@kilo.ai'); }); - it('evaluates a stored returning-user address', () => { - mockFlowEmail = 'returning@kilo.ai'; - mockHintEmail = 'returning@kilo.ai'; - mockChatGptAllowed = true; - renderToStaticMarkup(createElement(SignInForm, { searchParams: {}, title: 'Welcome.' })); - - expect(mockHookEmail).toBe('returning@kilo.ai'); - }); - - it('evaluates the query prefill over a stored returning-user address', () => { - mockFlowEmail = 'prefill@kilo.ai'; - mockHintEmail = 'hint@kilo.ai'; - mockChatGptAllowed = true; - renderToStaticMarkup( + it('offers ChatGPT on the first sign-up screen', () => { + const html = renderToStaticMarkup( createElement(SignInForm, { - searchParams: { email: 'prefill@kilo.ai' }, - title: 'Welcome.', + searchParams: {}, + isSignUp: true, + title: 'Create your account', }) ); - expect(mockHookEmail).toBe('prefill@kilo.ai'); - }); - - it('hides ChatGPT on sign-up when the flag is off for the typed email', () => { - const html = renderToStaticMarkup( - createElement(SignInForm, { searchParams: {}, isSignUp: true, title: 'Create your account' }) - ); - - expect(html).not.toContain('Continue with ChatGPT'); + expect(html.match(/Continue with ChatGPT/g)).toHaveLength(1); expect(html).toContain('Continue with Google'); expect(html).toContain('Continue with Email'); }); - - it('offers ChatGPT on sign-up when the flag is on for the typed email', () => { - mockFlowEmail = 'person@openai.com'; - mockChatGptAllowed = true; - const html = renderToStaticMarkup( - createElement(SignInForm, { searchParams: {}, isSignUp: true, title: 'Create your account' }) - ); - - expect(html.match(/Continue with ChatGPT/g)).toHaveLength(1); - }); }); diff --git a/apps/web/src/components/auth/SignInForm.tsx b/apps/web/src/components/auth/SignInForm.tsx index 9691f9461c..5a3ddd6a43 100644 --- a/apps/web/src/components/auth/SignInForm.tsx +++ b/apps/web/src/components/auth/SignInForm.tsx @@ -18,26 +18,11 @@ import Link from 'next/link'; import { SquareUserRound } from 'lucide-react'; import React from 'react'; import type { SignInFormInitialState } from '@/hooks/useSignInFlow'; -import { useChatGptSignInAccess } from '@/hooks/useChatGptSignInAccess'; -import type { AuthProviderId } from '@kilocode/db/schema-types'; import { OAuthProviderIds } from '@/lib/auth/provider-metadata'; import { buildEnterpriseSsoHref, buildNormalSignInHref } from '@/lib/auth/sign-in-navigation'; import type { SsoAccountMismatch } from '@/lib/auth/sso-account-mismatch'; import getSignInCallbackUrl from '@/lib/getSignInCallbackUrl'; -/** - * 'Sign in with ChatGPT' is restricted by the PostHog flag's email allow-list. - * A signed-out visitor is not known to PostHog, so the sign-in page evaluates - * the flag against the email the visitor typed and hides the ChatGPT button - * when the flag is off for that email. - */ -function withoutChatGptWhenUnavailable( - providers: readonly AuthProviderId[], - chatGptAllowed: boolean -): AuthProviderId[] { - return chatGptAllowed ? [...providers] : providers.filter(id => id !== 'openai'); -} - type SignInFormProps = { searchParams: Record; error?: string; @@ -70,18 +55,6 @@ export function SignInForm({ isSignUp, storybookInitialState, }); - // The ChatGPT option is decided from the address the visitor submits, or from - // an address already known without typing. A `?email=` prefill wins over a - // stored returning-user hint: the flow auto-triggers Turnstile for the - // prefill and shows it on the provider screen, so the prefill is the address - // in use. Typing alone never evaluates, so one address costs one reload. - const [submittedEmail, setSubmittedEmail] = React.useState(null); - const knownEmail = (searchParams.email || flow.hint?.lastEmail || '').trim(); - const chatGptAllowed = useChatGptSignInAccess(submittedEmail ?? (knownEmail || null)); - const handleEmailSubmit = (event: React.FormEvent) => { - setSubmittedEmail(flow.email); - flow.handleEmailSubmit(event); - }; // An Enterprise SSO request for a different address than the signed-in // session cannot proceed; offer the one-tap switch before any normal @@ -161,7 +134,7 @@ export function SignInForm({ {errorNotification}