Skip to content

Commit 801e3b5

Browse files
committed
Make Splitwise import a visible V1 path
1 parent 6962aac commit 801e3b5

28 files changed

Lines changed: 162 additions & 164 deletions

File tree

‎apps/web/e2e/import.spec.ts‎

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { expect, test } from '@playwright/test'
22
import { SIMPLE_GROUP } from '../src/lib/__fixtures__/splitwise'
33

4-
test.skip(process.env.NEXT_PUBLIC_SPLIT_V2_ENABLED !== '1', 'Splitwise import belongs to the v2 surface')
5-
64
/**
75
* The switch, end to end: a real Splitwise export goes in through the real file input, and the
86
* room that comes out is asserted against the balances the file itself states.

‎apps/web/e2e/landing.spec.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,12 +1039,12 @@ test('the room handoff shares a localized message, the link, and the room drawin
10391039
)
10401040
})
10411041

1042-
test('v1 does not expose AI or migration tooling in either landing variant', async ({ page }) => {
1042+
test('v1 exposes Splitwise import without exposing AI tooling', async ({ page }) => {
10431043
await openLanding(page)
1044-
await expect(page.getByRole('link', { name: 'Import from Splitwise' })).toHaveCount(0)
1045-
await expect(page.getByText(/quick add|scan a receipt|AI receipt|import your Splitwise/i)).toHaveCount(0)
1044+
await expect(page.getByRole('link', { name: 'Import from Splitwise' })).toBeVisible()
1045+
await expect(page.getByText(/quick add|scan a receipt|AI receipt/i)).toHaveCount(0)
10461046

1047-
expect((await page.goto('/import'))?.status()).toBe(404)
1047+
expect((await page.goto('/import'))?.status()).toBe(200)
10481048
expect((await page.goto('/blog/scan-a-receipt-to-split-a-bill'))?.status()).toBe(404)
10491049
})
10501050

‎apps/web/e2e/pwa.spec.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { expect, test, type Page } from '@playwright/test'
1212
*/
1313
test.use({ extraHTTPHeaders: { 'x-forwarded-for': '192.0.2.20' } })
1414

15-
test('the manifest names the app Split, offers one shortcut, and carries no room', async ({ request }) => {
15+
test('the manifest names the app Split, offers v1 creation shortcuts, and carries no room', async ({ request }) => {
1616
const response = await request.get('/manifest.webmanifest')
1717
expect(response.status()).toBe(200)
1818

@@ -25,7 +25,7 @@ test('the manifest names the app Split, offers one shortcut, and carries no room
2525

2626
expect(manifest.name).toBe('Split')
2727
expect(manifest.short_name).toBe('Split')
28-
expect(manifest.shortcuts?.map((shortcut) => shortcut.url)).toEqual(['/new'])
28+
expect(manifest.shortcuts?.map((shortcut) => shortcut.url)).toEqual(['/new', '/import'])
2929
// The share sheet is v2's, and this build is v1. An entry here would put Split in every
3030
// Android photo share sheet and then decline the photo.
3131
expect(manifest.share_target).toBeUndefined()

‎apps/web/e2e/room.spec.ts‎

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -579,21 +579,34 @@ test('a link holder can export the room without exporting the room credential',
579579
const url = (await roomLink.innerText()).trim()
580580
await page.getByTestId('go-to-room').click()
581581

582-
// Export is a row in Settings now, not a top-bar menu. The row says what you
583-
// would get; the sheet behind it says who is in the file, and hands it over.
582+
// Import/export is one row in Settings. It names both jobs without implying
583+
// that a Splitwise file will be merged into the room currently open.
584584
await page.getByTestId('open-room-settings').click()
585585
await expect(page.getByTestId('settings-sheet')).toBeVisible({ timeout: 15_000 })
586586
const exportRow = page.getByTestId('export-row')
587-
await expect(exportRow).toContainText('CSV · JSON')
587+
await expect(exportRow).toContainText('Import / export')
588+
await expect(exportRow).toContainText('Splitwise · CSV · JSON')
588589
await exportRow.click()
589590

590591
const exportSheet = page.getByTestId('export-sheet')
591592
await expect(exportSheet).toBeVisible()
593+
await expect(exportSheet).toContainText('Bring over a Splitwise group')
594+
await expect(exportSheet).toContainText('This creates a new room. Your current room stays as it is.')
595+
await expect(page.getByTestId('open-splitwise-import')).toBeVisible()
592596
// One disclosure sentence, and it does not claim to be the whole money
593597
// history — deleted records are left out, so that wording would be false.
598+
await expect(exportSheet).toContainText('Export this room')
594599
await expect(exportSheet).toContainText('The file has everyone’s names and every expense and payment.')
595600
await expect(exportSheet).not.toContainText('money history')
596601

602+
const csvDownloadPromise = page.waitForEvent('download')
603+
await exportSheet.getByRole('button', { name: 'Download CSV' }).click()
604+
const csvDownload = await csvDownloadPromise
605+
expect(csvDownload.suggestedFilename()).toBe('export-room.csv')
606+
const csvPath = await csvDownload.path()
607+
expect(csvPath).not.toBeNull()
608+
expect(await readFile(csvPath!, 'utf8')).not.toContain(new URL(url).pathname)
609+
597610
const jsonDownloadPromise = page.waitForEvent('download')
598611
await exportSheet.getByRole('button', { name: 'Download JSON' }).click()
599612
const jsonDownload = await jsonDownloadPromise
@@ -612,4 +625,9 @@ test('a link holder can export the room without exporting the room credential',
612625
await page.getByTestId('close-export-sheet').click()
613626
await expect(exportSheet).toHaveAttribute('data-state', 'closed')
614627
await expect(page.getByTestId('settings-sheet')).toBeVisible()
628+
629+
await exportRow.click()
630+
await page.getByTestId('open-splitwise-import').click()
631+
await page.waitForURL('/import')
632+
await expect(page.getByTestId('import-choose')).toBeVisible()
615633
})

‎apps/web/src/app/(marketing)/import/opengraph-image.tsx‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import { ImageResponse } from 'next/og'
2-
import { notFound } from 'next/navigation'
32
import { BrandCard, OG_CONTENT_TYPE, OG_SIZE } from '@/server/og/card'
43
import { ogFonts } from '@/server/og/fonts'
54
import { marketingCopy } from '@/components/marketing/copy'
6-
import { splitV2Enabled } from '@/lib/flags'
75

86
/**
97
* Unfurl for the importer. It was sharing as a blank card: the `(marketing)` group's landing
@@ -20,7 +18,6 @@ export const contentType = OG_CONTENT_TYPE
2018
export const alt = marketingCopy.importPage.meta.title
2119

2220
export default async function ImportOgImage() {
23-
if (!splitV2Enabled()) notFound()
2421
return new ImageResponse(<BrandCard lines={['SPLIT', 'IT']} tagline={marketingCopy.importPage.hero.title} />, {
2522
...OG_SIZE,
2623
fonts: await ogFonts(),

‎apps/web/src/app/(marketing)/import/page.tsx‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
import type { Metadata } from 'next'
22
import Link from 'next/link'
3-
import { notFound } from 'next/navigation'
43
import { Breadcrumbs } from '@/components/marketing/Breadcrumbs'
54
import { JsonLd } from '@/components/marketing/JsonLd'
65
import { SiteFooter } from '@/components/marketing/SiteFooter'
76
import { marketingCopy } from '@/components/marketing/copy'
87
import { SplitwiseImport } from '@/components/import/SplitwiseImport'
98
import { Button } from '@/components/ui/Button'
10-
import { splitV2Enabled } from '@/lib/flags'
119
import { breadcrumbSchema, pageMetadata } from '@/lib/seo'
1210

1311
const { importPage } = marketingCopy
@@ -48,8 +46,6 @@ const faqJsonLd = {
4846
}
4947

5048
export default function ImportPage() {
51-
if (!splitV2Enabled()) notFound()
52-
5349
return (
5450
<main className="flex min-h-dvh flex-col gap-10 bg-background">
5551
<JsonLd data={faqJsonLd} />

‎apps/web/src/app/api/import/route.ts‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { badRequest, notFound, readJsonCapped, respond } from '@/server/http'
1+
import { badRequest, readJsonCapped, respond } from '@/server/http'
22
import { CREATE_LIMIT, enforceRateLimit } from '@/server/rateLimit'
33
import { toRoomState } from '@/server/roomState'
44
import { importRoom } from '@/server/splitwiseImport'
55
import { importRoomSchema } from '@/server/validation'
66
import type { RoomStateWithMember } from '@/lib/api-types'
7-
import { splitV2Enabled } from '@/lib/flags'
87
import { MAX_EXPENSES, MAX_MEMBERS } from '@/lib/splitwise-csv'
98

109
export const dynamic = 'force-dynamic'
@@ -62,7 +61,6 @@ export function assertImportCardinality(raw: unknown): void {
6261
*/
6362
export const POST = (request: Request) =>
6463
respond(async (): Promise<RoomStateWithMember> => {
65-
if (!splitV2Enabled()) throw notFound('not found')
6664
enforceRateLimit(request, CREATE_LIMIT, 'create')
6765

6866
const raw = await readJsonCapped(

‎apps/web/src/app/manifest.test.ts‎

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,11 @@ describe('the installed app is named Split', () => {
3838
})
3939

4040
describe('shortcuts', () => {
41-
it('offers only the screen that works with no room on the device', async () => {
42-
const manifest = await manifestWith(false)
43-
expect(manifest.shortcuts).toEqual([{ name: 'New split', url: '/new' }])
44-
})
45-
46-
it('adds the importer only where /import is not a 404', async () => {
47-
const manifest = await manifestWith(true)
48-
expect(manifest.shortcuts?.map((shortcut) => shortcut.url)).toEqual(['/new', '/import'])
41+
it('offers new-room and import entry points in both flag states', async () => {
42+
for (const v2 of [false, true]) {
43+
const manifest = await manifestWith(v2)
44+
expect(manifest.shortcuts?.map((shortcut) => shortcut.url)).toEqual(['/new', '/import'])
45+
}
4946
})
5047
})
5148

‎apps/web/src/app/manifest.ts‎

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,7 @@ export default function manifest(): MetadataRoute.Manifest {
3333
// The one thing somebody opens the app cold to do, and the only screen that works with
3434
// no room on this device at all. `start_url` already covers "take me to my rooms".
3535
{ name: 'New split', url: '/new' },
36-
// Only on a deployment that opted into v2 — /import calls notFound() otherwise, and a
37-
// launcher shortcut to a 404 is the definition of a dead end.
38-
...(splitV2Enabled() ? [{ name: 'Import from Splitwise', url: '/import' }] : []),
36+
{ name: 'Import from Splitwise', url: '/import' },
3937
],
4038
/**
4139
* Split in the OS share sheet, for a photo of a bill — v2 only.

‎apps/web/src/components/marketing/ReadMore.tsx‎

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import { motion } from 'motion/react'
77
import { Doodle } from '@/components/ui/Doodle'
88
import type { DoodleName } from '@/components/ui/doodles'
99
import { Icon } from '@/components/ui/Icon'
10-
import { splitV2Enabled } from '@/lib/flags'
1110
import { useMotionAllowed } from '@/lib/use-motion'
1211
import { useFeedback } from '@/lib/use-settings'
1312
import { LANDING_CAST, LandingPersona } from './LandingPersona'
@@ -194,10 +193,7 @@ export function ReadMore() {
194193
<Fold key={question} title={t(`faq.${question}.q`)} onToggle={onToggle}>
195194
<p className="max-w-xl text-sm leading-6 text-grey-1">
196195
{t(`faq.${question}.a`)}
197-
{/* The importer is v2-only surface: /import 404s in a v1 build,
198-
so pointing at it there would send the one reader who raised
199-
this objection to a dead page. */}
200-
{question === 'retype' && splitV2Enabled() && ` ${t('faq.retype.import')}`}
196+
{question === 'retype' && ` ${t('faq.retype.import')}`}
201197
</p>
202198
</Fold>
203199
))}

0 commit comments

Comments
 (0)