diff --git a/src/app/page.tsx b/src/app/page.tsx
index f8d06d2..cff189b 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -16,6 +16,7 @@ import { getFileName } from '../fs'
import TitleText from '../components/TitleText'
import { pluralize } from '../utils/pluralize'
import TermsAcceptance from '../components/TermsAcceptance'
+import AddFilesButton from '../components/AddFilesButton'
function PageWrapper({ children }: { children: React.ReactNode }): JSX.Element {
return (
@@ -59,6 +60,7 @@ function ConfirmUploadState({
onCancel,
onStart,
onRemoveFile,
+ onAddFiles,
}: {
uploadedFiles: UploadedFile[]
password: string
@@ -66,6 +68,7 @@ function ConfirmUploadState({
onCancel: () => void
onStart: () => void
onRemoveFile: (index: number) => void
+ onAddFiles: (files: UploadedFile[]) => void
}): JSX.Element {
const fileListData = useUploaderFileListData(uploadedFiles)
return (
@@ -75,6 +78,9 @@ function ConfirmUploadState({
{pluralize(uploadedFiles.length, 'file', 'files')}.
+
@@ -137,6 +143,10 @@ export default function UploadPage(): JSX.Element {
setUploadedFiles((fs) => fs.filter((_, i) => i !== index))
}, [])
+ const handleAddFiles = useCallback((files: UploadedFile[]) => {
+ setUploadedFiles((fs) => [...fs, ...files])
+ }, [])
+
if (!uploadedFiles.length) {
return
}
@@ -150,6 +160,7 @@ export default function UploadPage(): JSX.Element {
onCancel={handleCancel}
onStart={handleStart}
onRemoveFile={handleRemoveFile}
+ onAddFiles={handleAddFiles}
/>
)
}
diff --git a/src/components/AddFilesButton.tsx b/src/components/AddFilesButton.tsx
new file mode 100644
index 0000000..1a11526
--- /dev/null
+++ b/src/components/AddFilesButton.tsx
@@ -0,0 +1,45 @@
+import React, { useRef, useCallback, JSX } from 'react'
+import { UploadedFile } from '../types'
+
+export default function AddFilesButton({
+ onAdd,
+}: {
+ onAdd: (files: UploadedFile[]) => void
+}): JSX.Element {
+ const fileInputRef = useRef(null)
+
+ const handleClick = useCallback(() => {
+ fileInputRef.current?.click()
+ }, [])
+
+ const handleChange = useCallback(
+ (e: React.ChangeEvent) => {
+ if (e.target.files) {
+ onAdd(Array.from(e.target.files) as UploadedFile[])
+ e.target.value = ''
+ }
+ },
+ [onAdd],
+ )
+
+ return (
+ <>
+
+
+ >
+ )
+}
diff --git a/tests/e2e/add-files.test.ts b/tests/e2e/add-files.test.ts
new file mode 100644
index 0000000..17da139
--- /dev/null
+++ b/tests/e2e/add-files.test.ts
@@ -0,0 +1,17 @@
+///
+import { test, expect } from '@playwright/test'
+import { createTestFile, uploadFile, addFile } from './helpers'
+
+test('user can add more files before starting upload', async ({ page }) => {
+ const file1 = createTestFile('first.txt', 'A')
+ const file2 = createTestFile('second.txt', 'B')
+
+ await uploadFile(page, file1)
+
+ // Add another file using the add files button
+ await addFile(page, file2)
+
+ // Both files should be listed
+ await expect(page.getByText(file1.name)).toBeVisible()
+ await expect(page.getByText(file2.name)).toBeVisible()
+})
diff --git a/tests/e2e/helpers.ts b/tests/e2e/helpers.ts
index 48ea67d..a47b7d3 100644
--- a/tests/e2e/helpers.ts
+++ b/tests/e2e/helpers.ts
@@ -66,6 +66,33 @@ export async function uploadFile(
)
}
+export async function addFile(
+ page: Page,
+ testFile: TestFile,
+): Promise {
+ await page.evaluate(
+ ({ testContent, testFileName }) => {
+ const input = document.querySelector(
+ '#add-files-input',
+ ) as HTMLInputElement
+ if (input) {
+ const file = new File([testContent], testFileName, {
+ type: 'text/plain',
+ })
+ const dataTransfer = new DataTransfer()
+ dataTransfer.items.add(file)
+ input.files = dataTransfer.files
+
+ const event = new Event('change', { bubbles: true })
+ input.dispatchEvent(event)
+ }
+ },
+ { testContent: testFile.content, testFileName: testFile.name },
+ )
+
+ await expect(page.getByText(testFile.name)).toBeVisible({ timeout: 5000 })
+}
+
export async function startUpload(page: Page): Promise {
// Start sharing
await page.locator('#start-button').click()