From 6ebb683e9d337fa683605f2125068a4e1ab4dda9 Mon Sep 17 00:00:00 2001 From: Shaurya K Sharma Date: Fri, 9 Oct 2026 00:18:13 +0100 Subject: [PATCH] refactor(frontend): remove disconnected waitlist client surfaces (#544) --- .../components/WaitlistModal.test.tsx | 62 -------- .../waitlist/components/WaitlistModal.tsx | 148 ------------------ .../waitlist/hooks/useWaitlistForm.ts | 32 ---- .../frontend/src/shared/services/api/index.ts | 1 - .../src/shared/services/api/waitlist.test.ts | 27 ---- .../src/shared/services/api/waitlist.ts | 9 -- 6 files changed, 279 deletions(-) delete mode 100644 apps/frontend/src/features/waitlist/components/WaitlistModal.test.tsx delete mode 100644 apps/frontend/src/features/waitlist/components/WaitlistModal.tsx delete mode 100644 apps/frontend/src/features/waitlist/hooks/useWaitlistForm.ts delete mode 100644 apps/frontend/src/shared/services/api/waitlist.test.ts delete mode 100644 apps/frontend/src/shared/services/api/waitlist.ts diff --git a/apps/frontend/src/features/waitlist/components/WaitlistModal.test.tsx b/apps/frontend/src/features/waitlist/components/WaitlistModal.test.tsx deleted file mode 100644 index ee85ef18..00000000 --- a/apps/frontend/src/features/waitlist/components/WaitlistModal.test.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import { afterEach, describe, expect, it, vi } from 'vitest'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; -import { waitlistService } from '@/shared/services/api'; -import { ApiError } from '@/shared/services/api'; -import { WaitlistModal } from './WaitlistModal'; - -describe('WaitlistModal', () => { - afterEach(() => { - vi.restoreAllMocks(); - }); - - it('submits the entered email and optional name, then shows a confirmation', async () => { - const join = vi.spyOn(waitlistService, 'join').mockResolvedValue({ status: 'ok' }); - const onClose = vi.fn(); - - render(); - - fireEvent.change(screen.getByLabelText('Email'), { target: { value: 'visitor@example.com' } }); - fireEvent.change(screen.getByLabelText(/Name/), { target: { value: 'Visitor Name' } }); - fireEvent.click(screen.getByRole('button', { name: 'Join the waitlist' })); - - await waitFor(() => - expect(join).toHaveBeenCalledWith({ email: 'visitor@example.com', name: 'Visitor Name' }), - ); - expect(await screen.findByText("You're on the list")).toBeVisible(); - expect(onClose).not.toHaveBeenCalled(); - }); - - it('submits without a name when the optional field is left blank', async () => { - const join = vi.spyOn(waitlistService, 'join').mockResolvedValue({ status: 'ok' }); - - render(); - - fireEvent.change(screen.getByLabelText('Email'), { target: { value: 'no-name@example.com' } }); - fireEvent.click(screen.getByRole('button', { name: 'Join the waitlist' })); - - await waitFor(() => expect(join).toHaveBeenCalledWith({ email: 'no-name@example.com', name: undefined })); - }); - - it('shows an error and does not confirm when the request fails', async () => { - vi.spyOn(waitlistService, 'join').mockRejectedValue( - new ApiError(422, 'Request validation failed.', null, '/waitlist'), - ); - - render(); - - fireEvent.change(screen.getByLabelText('Email'), { target: { value: 'rejected@example.com' } }); - fireEvent.click(screen.getByRole('button', { name: 'Join the waitlist' })); - - expect(await screen.findByRole('alert')).toBeVisible(); - expect(screen.queryByText("You're on the list")).not.toBeInTheDocument(); - }); - - it('calls onClose when Close is clicked before submitting', () => { - const onClose = vi.fn(); - render(); - - fireEvent.click(screen.getByRole('button', { name: 'Close' })); - - expect(onClose).toHaveBeenCalledTimes(1); - }); -}); diff --git a/apps/frontend/src/features/waitlist/components/WaitlistModal.tsx b/apps/frontend/src/features/waitlist/components/WaitlistModal.tsx deleted file mode 100644 index ef76ac84..00000000 --- a/apps/frontend/src/features/waitlist/components/WaitlistModal.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import { useState } from 'react'; -import { Loader2 } from 'lucide-react'; -import { useWaitlistForm } from '../hooks/useWaitlistForm'; -import { cn } from '@/shared/utils/cn'; - -interface WaitlistModalProps { - onClose: () => void; - /** Landing page's own resolved theme (see LandingPage.tsx) -- this modal - * has no theme awareness of its own, and the light-mode classes below - * are left as literal hex rather than switched to tokens, so light-mode - * output stays pixel-identical to before this prop existed. */ - dark?: boolean; -} - -export function WaitlistModal({ onClose, dark = false }: WaitlistModalProps) { - const [submitted, setSubmitted] = useState(false); - const { email, setEmail, name, setName, submitting, error, submit } = useWaitlistForm(() => setSubmitted(true)); - - return ( -
-
- {submitted ? ( -
-

- You're on the list -

-

- PARTHA is currently in a small, invite-only beta. We'll reach out by email when there's room for you. -

- -
- ) : ( - <> -
-
-

- Beta waitlist -

-

- Join the waitlist -

-
- -
-

- PARTHA is invite-only while we work directly with a small group of engineering teams. Leave your email - and we'll follow up when there's room. -

-
-
- - setEmail(event.target.value)} - className={cn( - 'w-full rounded-xl border px-3 py-2.5 text-sm', - dark ? 'border-input bg-background text-foreground' : 'border-[#fa4d01]/20 bg-white text-[#30152f]' - )} - /> -
-
- - setName(event.target.value)} - className={cn( - 'w-full rounded-xl border px-3 py-2.5 text-sm', - dark ? 'border-input bg-background text-foreground' : 'border-[#fa4d01]/20 bg-white text-[#30152f]' - )} - /> -
- - {error && ( -

- {error} -

- )} - - -
- - )} -
-
- ); -} diff --git a/apps/frontend/src/features/waitlist/hooks/useWaitlistForm.ts b/apps/frontend/src/features/waitlist/hooks/useWaitlistForm.ts deleted file mode 100644 index ea962650..00000000 --- a/apps/frontend/src/features/waitlist/hooks/useWaitlistForm.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { useState, type FormEvent } from 'react'; -import { getErrorMessage, waitlistService } from '@/shared/services/api'; - -export function useWaitlistForm(onSuccess: () => void) { - const [email, setEmail] = useState(''); - const [name, setName] = useState(''); - const [submitting, setSubmitting] = useState(false); - const [error, setError] = useState(null); - - const submit = async (event: FormEvent) => { - event.preventDefault(); - if (submitting) return; - setSubmitting(true); - setError(null); - try { - await waitlistService.join({ email: email.trim(), name: name.trim() || undefined }); - onSuccess(); - } catch (caught) { - setError(getErrorMessage(caught)); - } finally { - setSubmitting(false); - } - }; - - const reset = () => { - setEmail(''); - setName(''); - setError(null); - }; - - return { email, setEmail, name, setName, submitting, error, submit, reset }; -} diff --git a/apps/frontend/src/shared/services/api/index.ts b/apps/frontend/src/shared/services/api/index.ts index 572f2e8c..df0b2661 100644 --- a/apps/frontend/src/shared/services/api/index.ts +++ b/apps/frontend/src/shared/services/api/index.ts @@ -12,7 +12,6 @@ export { reviewService } from './review'; export { dependencyService } from './dependencies'; export { aiService } from './ai'; export { documentationService, exportService } from './documentation'; -export { waitlistService } from './waitlist'; export type * from './types'; export type * from './generated'; diff --git a/apps/frontend/src/shared/services/api/waitlist.test.ts b/apps/frontend/src/shared/services/api/waitlist.test.ts deleted file mode 100644 index 176dc341..00000000 --- a/apps/frontend/src/shared/services/api/waitlist.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { describe, expect, it, vi } from 'vitest'; -import { api } from './client'; -import { waitlistService } from './waitlist'; - -vi.mock('./client', () => ({ - api: { get: vi.fn(), post: vi.fn(), put: vi.fn(), patch: vi.fn(), delete: vi.fn() }, -})); - -describe('waitlistService', () => { - it('join posts the request body to /waitlist', async () => { - const request = { email: 'a@b.com', name: 'A' }; - const response = { status: 'confirmed' }; - vi.mocked(api.post).mockResolvedValue(response); - - const result = await waitlistService.join(request as never); - - expect(api.post).toHaveBeenCalledWith('/waitlist', request, undefined); - expect(result).toBe(response); - }); - - it('propagates a rejected request (e.g. a duplicate signup) rather than swallowing it', async () => { - const error = new Error('already on the waitlist'); - vi.mocked(api.post).mockRejectedValue(error); - - await expect(waitlistService.join({} as never)).rejects.toBe(error); - }); -}); diff --git a/apps/frontend/src/shared/services/api/waitlist.ts b/apps/frontend/src/shared/services/api/waitlist.ts deleted file mode 100644 index aaadaef0..00000000 --- a/apps/frontend/src/shared/services/api/waitlist.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { api } from './client'; -import type { RequestConfig } from './client'; -import type { WaitlistSignupRequest, WaitlistSignupResponse } from './types'; - -export const waitlistService = { - join(request: WaitlistSignupRequest, config?: RequestConfig): Promise { - return api.post('/waitlist', request, config); - }, -};