Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 3 additions & 1 deletion src/components/Card/PhysicalCardScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ const PhysicalCardScreen: FC<Props> = ({ cardId, last4, onPrev }) => {
<Image src={PeanutWalking} unoptimized alt="" aria-hidden className="h-32 w-auto" />
<h1 className="text-xl font-extrabold">You are on the list!</h1>
<p className="text-sm text-grey-1">
{`You are #${data.position} on the list. We'll let you know when cards are ready to be shipped.`}
{data.position === null
? `You are on the list. We'll let you know when cards are ready to be shipped.`
: `You are #${data.position.toLocaleString()} on the list. We'll let you know when cards are ready to be shipped.`}
Comment on lines +79 to +81

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Use a loose equality check to prevent runtime crashes if position is undefined.

The strict equality check (=== null) will evaluate to false if the API omits the position field, making data.position undefined. This would cause the app to crash with a TypeError: Cannot read properties of undefined (reading 'toLocaleString').

Using a loose equality check (== null) handles both null and undefined safely.

🛡️ Proposed fix
-                        {data.position === null
+                        {data.position == null
                             ? `You are on the list. We'll let you know when cards are ready to be shipped.`
                             : `You are #${data.position.toLocaleString()} on the list. We'll let you know when cards are ready to be shipped.`}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{data.position === null
? `You are on the list. We'll let you know when cards are ready to be shipped.`
: `You are #${data.position.toLocaleString()} on the list. We'll let you know when cards are ready to be shipped.`}
{data.position == null
? `You are on the list. We'll let you know when cards are ready to be shipped.`
: `You are #${data.position.toLocaleString()} on the list. We'll let you know when cards are ready to be shipped.`}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/Card/PhysicalCardScreen.tsx` around lines 79 - 81, Update the
position check in the PhysicalCardScreen display expression to treat both null
and undefined as the “on the list” state by using a loose null comparison. Keep
the toLocaleString formatting path for defined position values.

</p>
</div>
) : (
Expand Down
63 changes: 63 additions & 0 deletions src/components/Card/__tests__/PhysicalCardScreen.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* PhysicalCardScreen — waitlist "on the list" copy contract.
*
* The joined branch is gated on joinedAt, but the API types position as
* nullable, so a joined user can have no queue position. Interpolating it
* blindly renders "You are #null on the list."
*/
import React from 'react'
import { render, screen } from '@testing-library/react'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import PhysicalCardScreen from '@/components/Card/PhysicalCardScreen'
import { rainApi } from '@/services/rain'

jest.mock('@/context/authContext', () => ({
useAuth: () => ({ user: { accounts: [] }, fetchUser: jest.fn() }),
}))
jest.mock('next/image', () => ({
__esModule: true,
// eslint-disable-next-line @next/next/no-img-element -- test stub, not real markup
default: (props: Record<string, unknown>) => <img alt={String(props.alt ?? '')} />,
}))
jest.mock('posthog-js', () => ({ __esModule: true, default: { capture: jest.fn() } }))
jest.mock('@/services/rain', () => ({
rainApi: { getPhysicalWaitlist: jest.fn(), joinPhysicalWaitlist: jest.fn() },
}))

const mockGet = rainApi.getPhysicalWaitlist as jest.Mock

const renderScreen = () => {
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
return render(
<QueryClientProvider client={queryClient}>
<PhysicalCardScreen cardId="card-1" last4="4242" />
</QueryClientProvider>
)
}

describe('PhysicalCardScreen — joined waitlist copy', () => {
beforeEach(() => jest.clearAllMocks())

it('shows the queue position when the API returns one', async () => {
mockGet.mockResolvedValue({ joinedAt: '2026-01-01T00:00:00Z', position: 42 })
renderScreen()
expect(await screen.findByText(/You are #42 on the list\./)).toBeInTheDocument()
})

it('omits the position rather than rendering "#null" when there is none', async () => {
mockGet.mockResolvedValue({ joinedAt: '2026-01-01T00:00:00Z', position: null })
renderScreen()
const body = await screen.findByText(/You are on the list\./)
expect(body).toBeInTheDocument()
expect(body.textContent).not.toMatch(/#/)
})

// Derived rather than hardcoded: the separator is locale-dependent, and the
// contract is that the copy delegates to toLocaleString, not that it says "1,234".
it('formats large positions with thousands separators', async () => {
mockGet.mockResolvedValue({ joinedAt: '2026-01-01T00:00:00Z', position: 1234 })
renderScreen()
const body = await screen.findByText(/on the list\./)
expect(body.textContent).toContain(`You are #${(1234).toLocaleString()} on the list.`)
})
Comment thread
coderabbitai[bot] marked this conversation as resolved.
})
Loading