diff --git a/next-env.d.ts b/next-env.d.ts index 4f11a03..725dd6f 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +/// // NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. +// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information. diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..a5788fb --- /dev/null +++ b/next.config.js @@ -0,0 +1,5 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { +} + +module.exports = nextConfig \ No newline at end of file diff --git a/package.json b/package.json index 10a9074..1e07b3c 100644 --- a/package.json +++ b/package.json @@ -28,14 +28,14 @@ "immer": "^8.0.4", "io-ts": "^2.2.21", "ioredis": "^4.28.5", - "next": "^14.2.7", + "next": "^14.2.8", "nodemon": "^1.19.4", "peer": "^0.5.3", "peerjs": "^1.5.4", "postcss": "^8.4.44", - "react": "^18.3.1", + "react": "^18.2.0", "react-device-detect": "^1.17.0", - "react-dom": "^18.3.1", + "react-dom": "^18.2.0", "react-qr": "0.0.2", "react-qr-code": "^1.1.1", "streamsaver": "^2.0.6", @@ -43,8 +43,7 @@ "twilio": "^2.11.1", "use-http": "^1.0.28", "web-streams-polyfill": "^3.3.3", - "webrtcsupport": "^2.2.0", - "xkcd-password": "^1.2.0" + "webrtcsupport": "^2.2.0" }, "devDependencies": { "@types/debug": "^4.1.12", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef04622..0f443df 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,8 +30,8 @@ dependencies: specifier: ^4.28.5 version: 4.28.5 next: - specifier: ^14.2.7 - version: 14.2.7(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1) + specifier: ^14.2.8 + version: 14.2.8(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1) nodemon: specifier: ^1.19.4 version: 1.19.4 @@ -45,13 +45,13 @@ dependencies: specifier: ^8.4.44 version: 8.4.44 react: - specifier: ^18.3.1 + specifier: ^18.2.0 version: 18.3.1 react-device-detect: specifier: ^1.17.0 version: 1.17.0(react-dom@18.3.1)(react@18.3.1) react-dom: - specifier: ^18.3.1 + specifier: ^18.2.0 version: 18.3.1(react@18.3.1) react-qr: specifier: 0.0.2 @@ -77,9 +77,6 @@ dependencies: webrtcsupport: specifier: ^2.2.0 version: 2.2.0 - xkcd-password: - specifier: ^1.2.0 - version: 1.2.0 devDependencies: '@types/debug': @@ -1773,12 +1770,12 @@ packages: engines: {node: '>= 10'} dev: false - /@next/env@14.2.7: - resolution: {integrity: sha512-OTx9y6I3xE/eih+qtthppwLytmpJVPM5PPoJxChFsbjIEFXIayG0h/xLzefHGJviAa3Q5+Fd+9uYojKkHDKxoQ==} + /@next/env@14.2.8: + resolution: {integrity: sha512-L44a+ynqkolyNBnYfF8VoCiSrjSZWgEHYKkKLGcs/a80qh7AkfVUD/MduVPgdsWZ31tgROR+yJRA0PZjSVBXWQ==} dev: false - /@next/swc-darwin-arm64@14.2.7: - resolution: {integrity: sha512-UhZGcOyI9LE/tZL3h9rs/2wMZaaJKwnpAyegUVDGZqwsla6hMfeSj9ssBWQS9yA4UXun3pPhrFLVnw5KXZs3vw==} + /@next/swc-darwin-arm64@14.2.8: + resolution: {integrity: sha512-1VrQlG8OzdyvvGZhGJFnaNE2P10Jjy/2FopnqbY0nSa/gr8If3iINxvOEW3cmVeoAYkmW0RsBazQecA2dBFOSw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] @@ -1786,8 +1783,8 @@ packages: dev: false optional: true - /@next/swc-darwin-x64@14.2.7: - resolution: {integrity: sha512-ys2cUgZYRc+CbyDeLAaAdZgS7N1Kpyy+wo0b/gAj+SeOeaj0Lw/q+G1hp+DuDiDAVyxLBCJXEY/AkhDmtihUTA==} + /@next/swc-darwin-x64@14.2.8: + resolution: {integrity: sha512-87t3I86rNRSOJB1gXIUzaQWWSWrkWPDyZGsR0Z7JAPtLeX3uUOW2fHxl7dNWD2BZvbvftctTQjgtfpp7nMtmWg==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] @@ -1795,8 +1792,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-gnu@14.2.7: - resolution: {integrity: sha512-2xoWtE13sUJ3qrC1lwE/HjbDPm+kBQYFkkiVECJWctRASAHQ+NwjMzgrfqqMYHfMxFb5Wws3w9PqzZJqKFdWcQ==} + /@next/swc-linux-arm64-gnu@14.2.8: + resolution: {integrity: sha512-ta2sfVzbOpTbgBrF9HM5m+U58dv6QPuwU4n5EX4LLyCJGKc433Z0D9h9gay/HSOjLEXJ2fJYrMP5JYYbHdxhtw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1804,8 +1801,8 @@ packages: dev: false optional: true - /@next/swc-linux-arm64-musl@14.2.7: - resolution: {integrity: sha512-+zJ1gJdl35BSAGpkCbfyiY6iRTaPrt3KTl4SF/B1NyELkqqnrNX6cp4IjjjxKpd64/7enI0kf6b9O1Uf3cL0pw==} + /@next/swc-linux-arm64-musl@14.2.8: + resolution: {integrity: sha512-+IoLTPK6Z5uIgDhgeWnQF5/o5GBN7+zyUNrs4Bes1W3g9++YELb8y0unFybS8s87ntAKMDl6jeQ+mD7oNwp/Ng==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] @@ -1813,8 +1810,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-gnu@14.2.7: - resolution: {integrity: sha512-m6EBqrskeMUzykBrv0fDX/28lWIBGhMzOYaStp0ihkjzIYJiKUOzVYD1gULHc8XDf5EMSqoH/0/TRAgXqpQwmw==} + /@next/swc-linux-x64-gnu@14.2.8: + resolution: {integrity: sha512-pO+hVXC+mvzUOQJJRG4RX4wJsRJ5BkURSf6dD6EjUXAX4Ml9es1WsEfkaZ4lcpmFzFvY47IkDaffks/GdCn9ag==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1822,8 +1819,8 @@ packages: dev: false optional: true - /@next/swc-linux-x64-musl@14.2.7: - resolution: {integrity: sha512-gUu0viOMvMlzFRz1r1eQ7Ql4OE+hPOmA7smfZAhn8vC4+0swMZaZxa9CSIozTYavi+bJNDZ3tgiSdMjmMzRJlQ==} + /@next/swc-linux-x64-musl@14.2.8: + resolution: {integrity: sha512-bCat9izctychCtf3uL1nqHq31N5e1VxvdyNcBQflkudPMLbxVnlrw45Vi87K+lt1CwrtVayHqzo4ie0Szcpwzg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] @@ -1831,8 +1828,8 @@ packages: dev: false optional: true - /@next/swc-win32-arm64-msvc@14.2.7: - resolution: {integrity: sha512-PGbONHIVIuzWlYmLvuFKcj+8jXnLbx4WrlESYlVnEzDsa3+Q2hI1YHoXaSmbq0k4ZwZ7J6sWNV4UZfx1OeOlbQ==} + /@next/swc-win32-arm64-msvc@14.2.8: + resolution: {integrity: sha512-gbxfUaSPV7EyUobpavida2Hwi62GhSJaSg7iBjmBWoxkxlmETOD7U4tWt763cGIsyE6jM7IoNavq0BXqwdW2QA==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] @@ -1840,8 +1837,8 @@ packages: dev: false optional: true - /@next/swc-win32-ia32-msvc@14.2.7: - resolution: {integrity: sha512-BiSY5umlx9ed5RQDoHcdbuKTUkuFORDqzYKPHlLeS+STUWQKWziVOn3Ic41LuTBvqE0TRJPKpio9GSIblNR+0w==} + /@next/swc-win32-ia32-msvc@14.2.8: + resolution: {integrity: sha512-PUXzEzjTTlUh3b5VAn1nlpwvujTnuCMMwbiCnaTazoVlN1nA3kWjlmp42IfURA2N/nyrlVEw7pURa/o4Qxj1cw==} engines: {node: '>= 10'} cpu: [ia32] os: [win32] @@ -1849,8 +1846,8 @@ packages: dev: false optional: true - /@next/swc-win32-x64-msvc@14.2.7: - resolution: {integrity: sha512-pxsI23gKWRt/SPHFkDEsP+w+Nd7gK37Hpv0ngc5HpWy2e7cKx9zR/+Q2ptAUqICNTecAaGWvmhway7pj/JLEWA==} + /@next/swc-win32-x64-msvc@14.2.8: + resolution: {integrity: sha512-EnPKv0ttq02E9/1KZ/8Dn7kuutv6hy1CKc0HlNcvzOQcm4/SQtvfws5gY0zrG9tuupd3HfC2L/zcTrnBhpjTuQ==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -2686,11 +2683,6 @@ packages: strip-ansi: 5.2.0 dev: false - /ansi-regex@0.2.1: - resolution: {integrity: sha512-sGwIGMjhYdW26/IhwK2gkWWI8DRCVO6uj3hYgHT+zD+QL1pa37tM3ujhyfcJIYSbsxp7Gxhy7zrRW/1AHm4BmA==} - engines: {node: '>=0.10.0'} - dev: false - /ansi-regex@2.1.1: resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} engines: {node: '>=0.10.0'} @@ -2715,11 +2707,6 @@ packages: engines: {node: '>=12'} dev: false - /ansi-styles@1.1.0: - resolution: {integrity: sha512-f2PKUkN5QngiSemowa6Mrk9MPCdtFiOSmibjZ+j1qhLGHHYsqZwmBMRF3IRMVXo8sybDqx2fJl2d/8OphBoWkA==} - engines: {node: '>=0.10.0'} - dev: false - /ansi-styles@2.2.1: resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} engines: {node: '>=0.10.0'} @@ -2957,10 +2944,6 @@ packages: resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} dev: false - /async@0.9.2: - resolution: {integrity: sha512-l6ToIJIotphWahxxHyzK9bnLR6kM4jJIIgLShZeqLY7iboHoGkdgFl7W2/Ivi4SkMJYGKqW8vSuk0uKUj6qsSw==} - dev: false - /async@2.6.4: resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==} dependencies: @@ -3321,17 +3304,6 @@ packages: resolution: {integrity: sha512-ODLXH644w9C2fMPAm7bMDQ3GRvipZWZfKc+8As6hIadRIelE0n0xZuN38NS6kiK3KPEVrpymmQD8bvncAHWQkQ==} dev: false - /chalk@0.5.1: - resolution: {integrity: sha512-bIKA54hP8iZhyDT81TOsJiQvR1gW+ZYSXFaZUAvoD4wCHdbHY2actmpTE4x344ZlFqHbvoxKOaESULTZN2gstg==} - engines: {node: '>=0.10.0'} - dependencies: - ansi-styles: 1.1.0 - escape-string-regexp: 1.0.5 - has-ansi: 0.1.0 - strip-ansi: 0.3.0 - supports-color: 0.2.0 - dev: false - /chalk@1.1.3: resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} engines: {node: '>=0.10.0'} @@ -5105,14 +5077,6 @@ packages: pinkie-promise: 2.0.1 dev: false - /has-ansi@0.1.0: - resolution: {integrity: sha512-1YsTg1fk2/6JToQhtZkArMkurq8UoWU1Qe0aR3VUHjgij4nOylSWLWAtBXoZ4/dXOmugfLGm1c+QhuD0JyedFA==} - engines: {node: '>=0.10.0'} - hasBin: true - dependencies: - ansi-regex: 0.2.1 - dev: false - /has-ansi@2.0.0: resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} engines: {node: '>=0.10.0'} @@ -6751,8 +6715,8 @@ packages: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: false - /next@14.2.7(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1): - resolution: {integrity: sha512-4Qy2aK0LwH4eQiSvQWyKuC7JXE13bIopEQesWE0c/P3uuNRnZCQanI0vsrMLmUQJLAto+A+/8+sve2hd+BQuOQ==} + /next@14.2.8(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1): + resolution: {integrity: sha512-EyEyJZ89r8C5FPlS/401AiF3O8jeMtHIE+bLom9MwcdWJJFBgRl+MR/2VgO0v5bI6tQORNY0a0DR5sjpFNrjbg==} engines: {node: '>=18.17.0'} hasBin: true peerDependencies: @@ -6769,7 +6733,7 @@ packages: sass: optional: true dependencies: - '@next/env': 14.2.7 + '@next/env': 14.2.8 '@swc/helpers': 0.5.5 busboy: 1.6.0 caniuse-lite: 1.0.30001655 @@ -6779,15 +6743,15 @@ packages: react-dom: 18.3.1(react@18.3.1) styled-jsx: 5.1.1(@babel/core@7.25.2)(react@18.3.1) optionalDependencies: - '@next/swc-darwin-arm64': 14.2.7 - '@next/swc-darwin-x64': 14.2.7 - '@next/swc-linux-arm64-gnu': 14.2.7 - '@next/swc-linux-arm64-musl': 14.2.7 - '@next/swc-linux-x64-gnu': 14.2.7 - '@next/swc-linux-x64-musl': 14.2.7 - '@next/swc-win32-arm64-msvc': 14.2.7 - '@next/swc-win32-ia32-msvc': 14.2.7 - '@next/swc-win32-x64-msvc': 14.2.7 + '@next/swc-darwin-arm64': 14.2.8 + '@next/swc-darwin-x64': 14.2.8 + '@next/swc-linux-arm64-gnu': 14.2.8 + '@next/swc-linux-arm64-musl': 14.2.8 + '@next/swc-linux-x64-gnu': 14.2.8 + '@next/swc-linux-x64-musl': 14.2.8 + '@next/swc-win32-arm64-msvc': 14.2.8 + '@next/swc-win32-ia32-msvc': 14.2.8 + '@next/swc-win32-x64-msvc': 14.2.8 transitivePeerDependencies: - '@babel/core' - babel-plugin-macros @@ -7564,17 +7528,6 @@ packages: inherits: 2.0.4 dev: false - /random-lib@1.1.3: - resolution: {integrity: sha512-W/Mkgwrbm4vFw+Fnye1+Yq9vJ0Y85RjbDYDJFfJeIyjOyVVE5Ax310aYXgcCyyGvZgZYkn0xBB5TBQK9JkfjFQ==} - engines: {node: '>=0.10.22'} - dependencies: - async: 0.9.2 - debug: 2.6.9(supports-color@5.5.0) - when: 3.7.8 - transitivePeerDependencies: - - supports-color - dev: false - /range-parser@1.2.1: resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} engines: {node: '>= 0.6'} @@ -8576,14 +8529,6 @@ packages: resolution: {integrity: sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==} dev: false - /strip-ansi@0.3.0: - resolution: {integrity: sha512-DerhZL7j6i6/nEnVG0qViKXI0OKouvvpsAiaj7c+LfqZZZxdwZtv8+UiA/w4VUJpT8UzX0pR1dcHOii1GbmruQ==} - engines: {node: '>=0.10.0'} - hasBin: true - dependencies: - ansi-regex: 0.2.1 - dev: false - /strip-ansi@3.0.1: resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} engines: {node: '>=0.10.0'} @@ -8689,12 +8634,6 @@ packages: resolution: {integrity: sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==} dev: false - /supports-color@0.2.0: - resolution: {integrity: sha512-tdCZ28MnM7k7cJDJc7Eq80A9CsRFAAOZUy41npOZCs++qSjfIy7o5Rh46CBk+Dk5FbKJ33X3Tqg4YrV07N5RaA==} - engines: {node: '>=0.10.0'} - hasBin: true - dev: false - /supports-color@2.0.0: resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} engines: {node: '>=0.8.0'} @@ -9279,10 +9218,6 @@ packages: webidl-conversions: 3.0.1 dev: false - /when@3.7.8: - resolution: {integrity: sha512-5cZ7mecD3eYcMiCH4wtRPA5iFJZ50BJYDfckI5RRpQiktMiYTcn0ccLTZOvcbBume+1304fQztxeNzNS9Gvrnw==} - dev: false - /which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} dependencies: @@ -9435,21 +9370,6 @@ packages: engines: {node: '>=4'} dev: false - /xkcd-password@1.2.0: - resolution: {integrity: sha512-JWQoDCiysReS+vUnLJJ16bJakRPQY/eV1DA0+21xoZplDadlGFGQxCPP3FuWEuZ9Dq3v8cvcHKajXVdf1Cj1cQ==} - engines: {node: '>=0.10.22'} - hasBin: true - dependencies: - async: 0.9.2 - chalk: 0.5.1 - debug: 2.6.9(supports-color@5.5.0) - minimist: 1.2.8 - random-lib: 1.1.3 - when: 3.7.8 - transitivePeerDependencies: - - supports-color - dev: false - /xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} diff --git a/src/app/download/[...slug]/page.tsx b/src/app/download/[...slug]/page.tsx new file mode 100644 index 0000000..40c2cc2 --- /dev/null +++ b/src/app/download/[...slug]/page.tsx @@ -0,0 +1,42 @@ +import { channelRepo } from '../../../channel' +import Spinner from '../../../components/Spinner' +import Wordmark from '../../../components/Wordmark' +import Downloader from '../../../components/Downloader' +import WebRTCProvider from '../../../components/WebRTCProvider' + +const normalizeSlug = (rawSlug: string | string[]): string => { + if (typeof rawSlug === 'string') { + return rawSlug + } else { + return rawSlug.join('/') + } +} + +export default async function DownloadPage({ + params, +}: { + params: { slug: string[] } +}): Promise { + const slug = normalizeSlug(params.slug) + const channel = await channelRepo.fetch(slug) + + if (!channel) { + return ( +
+ + +

Not found

+
+ ) + } + + return ( +
+ + + + + +
+ ) +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..cb0e3da --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,34 @@ +import React from 'react' +import Footer from '../components/Footer' +import '../styles.css' + +export const metadata = { + title: 'FilePizza • Your files, delivered.', + description: 'Peer-to-peer file transfers in your web browser.', + charSet: 'utf-8', + viewport: 'width=device-width, initial-scale=1', + openGraph: { + url: 'https://file.pizza', + title: 'FilePizza • Your files, delivered.', + description: 'Peer-to-peer file transfers in your web browser.', + images: [{ url: 'https://file.pizza/images/fb.png' }], + }, +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}): React.ReactElement { + return ( + + + + + +
{children}
+
+ + + ) +} diff --git a/src/pages/index.tsx b/src/app/page.tsx similarity index 82% rename from src/pages/index.tsx rename to src/app/page.tsx index 5634531..2888f3e 100644 --- a/src/pages/index.tsx +++ b/src/app/page.tsx @@ -1,3 +1,5 @@ +'use client' + import React, { useCallback, useState } from 'react' import WebRTCProvider from '../components/WebRTCProvider' import DropZone from '../components/DropZone' @@ -7,12 +9,11 @@ import PasswordField from '../components/PasswordField' import StartButton from '../components/StartButton' import StopButton from '../components/StopButton' import { UploadedFile } from '../types' -import { NextPage } from 'next' import Spinner from '../components/Spinner' import Wordmark from '../components/Wordmark' import CancelButton from '../components/CancelButton' -export const IndexPage: NextPage = () => { +export default function IndexPage(): JSX.Element { const [uploadedFiles, setUploadedFiles] = useState([]) const [password, setPassword] = useState('') const [uploading, setUploading] = useState(false) @@ -44,10 +45,10 @@ export const IndexPage: NextPage = () => { if (!uploadedFiles.length) { return ( -
+
-
+

Peer-to-peer file transfers in your browser.

@@ -62,11 +63,12 @@ export const IndexPage: NextPage = () => { if (!uploading) { return ( -
+
-

- You are about to start uploading {uploadedFiles.length} files. +

+ You are about to start uploading {uploadedFiles.length}{' '} + {uploadedFiles.length === 1 ? 'file' : 'files'}.

@@ -79,11 +81,12 @@ export const IndexPage: NextPage = () => { } return ( -
+
-

- You are uploading {uploadedFiles.length} files. +

+ You are uploading {uploadedFiles.length}{' '} + {uploadedFiles.length === 1 ? 'file' : 'files'}.

@@ -93,9 +96,3 @@ export const IndexPage: NextPage = () => {
) } - -IndexPage.getInitialProps = () => { - return {} -} - -export default IndexPage diff --git a/src/channel.ts b/src/channel.ts index 6970c2e..8066b34 100644 --- a/src/channel.ts +++ b/src/channel.ts @@ -1,3 +1,4 @@ +import 'server-only' import config from './config' import Redis from 'ioredis' import { generateShortSlug, generateLongSlug } from './slugs' diff --git a/src/components/CancelButton.tsx b/src/components/CancelButton.tsx index 0303200..868676b 100644 --- a/src/components/CancelButton.tsx +++ b/src/components/CancelButton.tsx @@ -1,10 +1,10 @@ import React from 'react' -type Props = { +export default function CancelButton({ + onClick, +}: { onClick: React.MouseEventHandler -} - -const CancelButton: React.FC = ({ onClick }: Props) => { +}): JSX.Element { return ( ) } - -export default CancelButton diff --git a/src/components/DownloadButton.tsx b/src/components/DownloadButton.tsx index d3542a7..c031820 100644 --- a/src/components/DownloadButton.tsx +++ b/src/components/DownloadButton.tsx @@ -1,10 +1,10 @@ import React from 'react' -type Props = { +export default function DownloadButton({ + onClick, +}: { onClick?: React.MouseEventHandler -} - -const DownloadButton: React.FC = ({ onClick }: Props) => { +}): JSX.Element { return ( ) } - -export default DownloadButton diff --git a/src/components/Downloader.tsx b/src/components/Downloader.tsx index 25c3505..ff3190d 100644 --- a/src/components/Downloader.tsx +++ b/src/components/Downloader.tsx @@ -1,3 +1,5 @@ +'use client' + import React, { useCallback, useEffect, useRef, useState } from 'react' import { useWebRTC } from './WebRTCProvider' import { @@ -23,11 +25,12 @@ import ProgressBar from './ProgressBar' const baseURL = process.env.NEXT_PUBLIC_BASE_URL ?? 'http://localhost:3000' // eslint-disable-next-line @typescript-eslint/no-var-requires -if (process.browser) require('web-streams-polyfill/ponyfill') +if (typeof window !== 'undefined') require('web-streams-polyfill/ponyfill') // eslint-disable-next-line @typescript-eslint/no-var-requires -const streamSaver = process.browser ? require('streamsaver') : null -if (process.browser) { +const streamSaver = + typeof window !== 'undefined' ? require('streamsaver') : null +if (typeof window !== 'undefined') { streamSaver.mitm = baseURL + '/stream.html' } diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index 82ee94a..cb174cc 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -1,21 +1,28 @@ +'use client' + import React, { useCallback } from 'react' const DONATE_HREF = 'https://commerce.coinbase.com/checkout/247b6ffe-fb4e-47a8-9a76-e6b7ef83ea22' -const FooterLink: React.FC<{ href: string; children: React.ReactNode }> = ({ +function FooterLink({ href, children, -}) => ( - - {children} - -) +}: { + href: string + children: React.ReactNode +}): JSX.Element { + return ( + + {children} + + ) +} export function Footer(): JSX.Element { const handleDonate = useCallback(() => { @@ -23,34 +30,38 @@ export function Footer(): JSX.Element { }, []) return ( -
-
-
+ <> +
{/* Spacer to account for footer height */} +
+
+
+

+ Like FilePizza? Support its development!{' '} +

+ +
+

- Like FilePizza? Support its development!{' '} + Cooked up by{' '} + Alex Kern &{' '} + Neeraj Baid while + eating Sliver @ UC Berkeley ·{' '} + + FAQ + {' '} + ·{' '} + + Fork us +

-
- -

- Cooked up by Alex Kern{' '} - & Neeraj Baid{' '} - while eating Sliver @ UC Berkeley ·{' '} - - FAQ - {' '} - ·{' '} - - Fork us - -

-
-
+
+ ) } diff --git a/src/components/PasswordField.tsx b/src/components/PasswordField.tsx index b0d4d17..11da8a0 100644 --- a/src/components/PasswordField.tsx +++ b/src/components/PasswordField.tsx @@ -1,21 +1,16 @@ import React, { useCallback } from 'react' -export function PasswordField({ - value, - onChange, - isRequired, - isInvalid, -}: { +export default function PasswordField(props: { value: string onChange: (v: string) => void isRequired?: boolean isInvalid?: boolean }): JSX.Element { const handleChange = useCallback( - (e: React.ChangeEvent) => { - onChange(e.target.value) + function (e: React.ChangeEvent): void { + props.onChange(e.target.value) }, - [onChange], + [props.onChange], ) return ( @@ -23,15 +18,13 @@ export function PasswordField({ autoFocus type="password" className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${ - isInvalid ? 'border-red-500' : 'border-gray-300' + props.isInvalid ? 'border-red-500' : 'border-gray-300' }`} placeholder={ - isRequired ? 'Enter password...' : 'Add password (optional)...' + props.isRequired ? 'Enter password...' : 'Add password (optional)...' } - value={value} + value={props.value} onChange={handleChange} /> ) } - -export default PasswordField diff --git a/src/components/ProgressBar.tsx b/src/components/ProgressBar.tsx index 379ec28..0d27903 100644 --- a/src/components/ProgressBar.tsx +++ b/src/components/ProgressBar.tsx @@ -17,7 +17,7 @@ export default function ProgressBar({ isComplete ? 'bg-green-500' : 'bg-blue-500' } transition-all duration-300 ease-in-out`} style={{ width: `${percentage}%` }} - > + /> ) } diff --git a/src/components/StartButton.tsx b/src/components/StartButton.tsx index 6e4f99d..7beb376 100644 --- a/src/components/StartButton.tsx +++ b/src/components/StartButton.tsx @@ -1,10 +1,10 @@ import React from 'react' -export function StartButton({ +export default function StartButton({ onClick, }: { - onClick: React.MouseEventHandler -}): JSX.Element { + onClick: React.MouseEventHandler +}): React.ReactElement { return ( ) } - -export default StopButton diff --git a/src/components/TypeBadge.tsx b/src/components/TypeBadge.tsx new file mode 100644 index 0000000..f827273 --- /dev/null +++ b/src/components/TypeBadge.tsx @@ -0,0 +1,21 @@ +import React from 'react' + +function getTypeColor(fileType: string): string { + if (fileType.startsWith('image/')) return 'bg-blue-100 text-blue-800' + if (fileType.startsWith('text/')) return 'bg-green-100 text-green-800' + if (fileType.startsWith('audio/')) return 'bg-purple-100 text-purple-800' + if (fileType.startsWith('video/')) return 'bg-red-100 text-red-800' + return 'bg-gray-100 text-gray-800' +} + +export default function TypeBadge({ type }: { type: string }): JSX.Element { + return ( + + {type} + + ) +} diff --git a/src/components/UnlockButton.tsx b/src/components/UnlockButton.tsx index 8615185..39c54ba 100644 --- a/src/components/UnlockButton.tsx +++ b/src/components/UnlockButton.tsx @@ -1,10 +1,10 @@ import React from 'react' -type Props = { - onClick?: React.MouseEventHandler -} - -const UnlockButton: React.FC = ({ onClick }: Props) => { +export default function UnlockButton({ + onClick, +}: { + onClick?: React.MouseEventHandler +}): React.ReactElement { return ( ) } - -export default UnlockButton diff --git a/src/components/UploadFileList.tsx b/src/components/UploadFileList.tsx index b692df8..7fe42d7 100644 --- a/src/components/UploadFileList.tsx +++ b/src/components/UploadFileList.tsx @@ -1,4 +1,5 @@ import React from 'react' +import TypeBadge from './TypeBadge' type UploadedFileLike = { fullPath?: string @@ -6,40 +7,21 @@ type UploadedFileLike = { type: string } -interface Props { - files: UploadedFileLike[] - onChange?: (updatedFiles: UploadedFileLike[]) => void -} - -const getFileName = (file: UploadedFileLike): string => { +function getFileName(file: UploadedFileLike): string { if (file.fullPath) { return file.fullPath.slice(1) } return file.name || 'Unknown' } -export function TypeBadge({ type }: { type: string }): JSX.Element { - const getTypeColor = (fileType: string): string => { - if (fileType.startsWith('image/')) return 'bg-blue-100 text-blue-800' - if (fileType.startsWith('text/')) return 'bg-green-100 text-green-800' - if (fileType.startsWith('audio/')) return 'bg-purple-100 text-purple-800' - if (fileType.startsWith('video/')) return 'bg-red-100 text-red-800' - return 'bg-gray-100 text-gray-800' - } - - return ( - - {type} - - ) -} - -export function UploadFileList({ files, onChange }: Props): JSX.Element { - const handleRemove = (index: number) => { +export default function UploadFileList({ + files, + onChange, +}: { + files: UploadedFileLike[] + onChange?: (updatedFiles: UploadedFileLike[]) => void +}): JSX.Element { + function handleRemove(index: number): void { if (onChange) { const updatedFiles = files.filter((_, i) => i !== index) onChange(updatedFiles) @@ -70,5 +52,3 @@ export function UploadFileList({ files, onChange }: Props): JSX.Element { return
{items}
} - -export default UploadFileList diff --git a/src/components/Uploader.tsx b/src/components/Uploader.tsx index 3c3cede..2d94f25 100644 --- a/src/components/Uploader.tsx +++ b/src/components/Uploader.tsx @@ -36,6 +36,7 @@ type UploaderConnection = { // TODO(@kern): Use better values const RENEW_INTERVAL = 5000 // 20 minutes const MAX_CHUNK_SIZE = 10 * 1024 * 1024 // 10 Mi +const QR_CODE_SIZE = 128 function useUploaderChannel(uploaderPeerID: string): { loading: boolean @@ -330,36 +331,46 @@ export default function Uploader({ return ( <> -
-
- +
+
+
-
-
- - +
+
+ +
+ + +
-
- - +
+ +
+ + +
diff --git a/src/components/WebRTCProvider.tsx b/src/components/WebRTCProvider.tsx index 9b2a7ad..c89f0f1 100644 --- a/src/components/WebRTCProvider.tsx +++ b/src/components/WebRTCProvider.tsx @@ -1,3 +1,5 @@ +'use client' + import React, { useState, useEffect, useRef, useContext } from 'react' import type { default as PeerType } from 'peerjs' import Loading from './Loading' diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx deleted file mode 100644 index 740a629..0000000 --- a/src/pages/_app.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import React from 'react' -import { AppProps } from 'next/app' -import Head from 'next/head' -import Footer from '../components/Footer' -import '../styles.css' - -const App: React.FC = ({ Component, pageProps }: AppProps) => ( - <> - - - - - - - - - FilePizza • Your files, delivered. - - - -
- -
- -