improve styling

pull/134/head
Alex Kern 1 year ago
parent dd25e6f7d8
commit b5cc41558b
No known key found for this signature in database
GPG Key ID: EF051FACCACBEE25

3
next-env.d.ts vendored

@ -1,5 +1,6 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
/// <reference types="next/navigation-types/compat/navigation" />
// NOTE: This file should not be edited // 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.

@ -0,0 +1,5 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
}
module.exports = nextConfig

@ -28,14 +28,14 @@
"immer": "^8.0.4", "immer": "^8.0.4",
"io-ts": "^2.2.21", "io-ts": "^2.2.21",
"ioredis": "^4.28.5", "ioredis": "^4.28.5",
"next": "^14.2.7", "next": "^14.2.8",
"nodemon": "^1.19.4", "nodemon": "^1.19.4",
"peer": "^0.5.3", "peer": "^0.5.3",
"peerjs": "^1.5.4", "peerjs": "^1.5.4",
"postcss": "^8.4.44", "postcss": "^8.4.44",
"react": "^18.3.1", "react": "^18.2.0",
"react-device-detect": "^1.17.0", "react-device-detect": "^1.17.0",
"react-dom": "^18.3.1", "react-dom": "^18.2.0",
"react-qr": "0.0.2", "react-qr": "0.0.2",
"react-qr-code": "^1.1.1", "react-qr-code": "^1.1.1",
"streamsaver": "^2.0.6", "streamsaver": "^2.0.6",
@ -43,8 +43,7 @@
"twilio": "^2.11.1", "twilio": "^2.11.1",
"use-http": "^1.0.28", "use-http": "^1.0.28",
"web-streams-polyfill": "^3.3.3", "web-streams-polyfill": "^3.3.3",
"webrtcsupport": "^2.2.0", "webrtcsupport": "^2.2.0"
"xkcd-password": "^1.2.0"
}, },
"devDependencies": { "devDependencies": {
"@types/debug": "^4.1.12", "@types/debug": "^4.1.12",

@ -30,8 +30,8 @@ dependencies:
specifier: ^4.28.5 specifier: ^4.28.5
version: 4.28.5 version: 4.28.5
next: next:
specifier: ^14.2.7 specifier: ^14.2.8
version: 14.2.7(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1) version: 14.2.8(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1)
nodemon: nodemon:
specifier: ^1.19.4 specifier: ^1.19.4
version: 1.19.4 version: 1.19.4
@ -45,13 +45,13 @@ dependencies:
specifier: ^8.4.44 specifier: ^8.4.44
version: 8.4.44 version: 8.4.44
react: react:
specifier: ^18.3.1 specifier: ^18.2.0
version: 18.3.1 version: 18.3.1
react-device-detect: react-device-detect:
specifier: ^1.17.0 specifier: ^1.17.0
version: 1.17.0(react-dom@18.3.1)(react@18.3.1) version: 1.17.0(react-dom@18.3.1)(react@18.3.1)
react-dom: react-dom:
specifier: ^18.3.1 specifier: ^18.2.0
version: 18.3.1(react@18.3.1) version: 18.3.1(react@18.3.1)
react-qr: react-qr:
specifier: 0.0.2 specifier: 0.0.2
@ -77,9 +77,6 @@ dependencies:
webrtcsupport: webrtcsupport:
specifier: ^2.2.0 specifier: ^2.2.0
version: 2.2.0 version: 2.2.0
xkcd-password:
specifier: ^1.2.0
version: 1.2.0
devDependencies: devDependencies:
'@types/debug': '@types/debug':
@ -1773,12 +1770,12 @@ packages:
engines: {node: '>= 10'} engines: {node: '>= 10'}
dev: false dev: false
/@next/env@14.2.7: /@next/env@14.2.8:
resolution: {integrity: sha512-OTx9y6I3xE/eih+qtthppwLytmpJVPM5PPoJxChFsbjIEFXIayG0h/xLzefHGJviAa3Q5+Fd+9uYojKkHDKxoQ==} resolution: {integrity: sha512-L44a+ynqkolyNBnYfF8VoCiSrjSZWgEHYKkKLGcs/a80qh7AkfVUD/MduVPgdsWZ31tgROR+yJRA0PZjSVBXWQ==}
dev: false dev: false
/@next/swc-darwin-arm64@14.2.7: /@next/swc-darwin-arm64@14.2.8:
resolution: {integrity: sha512-UhZGcOyI9LE/tZL3h9rs/2wMZaaJKwnpAyegUVDGZqwsla6hMfeSj9ssBWQS9yA4UXun3pPhrFLVnw5KXZs3vw==} resolution: {integrity: sha512-1VrQlG8OzdyvvGZhGJFnaNE2P10Jjy/2FopnqbY0nSa/gr8If3iINxvOEW3cmVeoAYkmW0RsBazQecA2dBFOSw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
@ -1786,8 +1783,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-darwin-x64@14.2.7: /@next/swc-darwin-x64@14.2.8:
resolution: {integrity: sha512-ys2cUgZYRc+CbyDeLAaAdZgS7N1Kpyy+wo0b/gAj+SeOeaj0Lw/q+G1hp+DuDiDAVyxLBCJXEY/AkhDmtihUTA==} resolution: {integrity: sha512-87t3I86rNRSOJB1gXIUzaQWWSWrkWPDyZGsR0Z7JAPtLeX3uUOW2fHxl7dNWD2BZvbvftctTQjgtfpp7nMtmWg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
@ -1795,8 +1792,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-arm64-gnu@14.2.7: /@next/swc-linux-arm64-gnu@14.2.8:
resolution: {integrity: sha512-2xoWtE13sUJ3qrC1lwE/HjbDPm+kBQYFkkiVECJWctRASAHQ+NwjMzgrfqqMYHfMxFb5Wws3w9PqzZJqKFdWcQ==} resolution: {integrity: sha512-ta2sfVzbOpTbgBrF9HM5m+U58dv6QPuwU4n5EX4LLyCJGKc433Z0D9h9gay/HSOjLEXJ2fJYrMP5JYYbHdxhtw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@ -1804,8 +1801,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-arm64-musl@14.2.7: /@next/swc-linux-arm64-musl@14.2.8:
resolution: {integrity: sha512-+zJ1gJdl35BSAGpkCbfyiY6iRTaPrt3KTl4SF/B1NyELkqqnrNX6cp4IjjjxKpd64/7enI0kf6b9O1Uf3cL0pw==} resolution: {integrity: sha512-+IoLTPK6Z5uIgDhgeWnQF5/o5GBN7+zyUNrs4Bes1W3g9++YELb8y0unFybS8s87ntAKMDl6jeQ+mD7oNwp/Ng==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
@ -1813,8 +1810,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-x64-gnu@14.2.7: /@next/swc-linux-x64-gnu@14.2.8:
resolution: {integrity: sha512-m6EBqrskeMUzykBrv0fDX/28lWIBGhMzOYaStp0ihkjzIYJiKUOzVYD1gULHc8XDf5EMSqoH/0/TRAgXqpQwmw==} resolution: {integrity: sha512-pO+hVXC+mvzUOQJJRG4RX4wJsRJ5BkURSf6dD6EjUXAX4Ml9es1WsEfkaZ4lcpmFzFvY47IkDaffks/GdCn9ag==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@ -1822,8 +1819,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-linux-x64-musl@14.2.7: /@next/swc-linux-x64-musl@14.2.8:
resolution: {integrity: sha512-gUu0viOMvMlzFRz1r1eQ7Ql4OE+hPOmA7smfZAhn8vC4+0swMZaZxa9CSIozTYavi+bJNDZ3tgiSdMjmMzRJlQ==} resolution: {integrity: sha512-bCat9izctychCtf3uL1nqHq31N5e1VxvdyNcBQflkudPMLbxVnlrw45Vi87K+lt1CwrtVayHqzo4ie0Szcpwzg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
@ -1831,8 +1828,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-arm64-msvc@14.2.7: /@next/swc-win32-arm64-msvc@14.2.8:
resolution: {integrity: sha512-PGbONHIVIuzWlYmLvuFKcj+8jXnLbx4WrlESYlVnEzDsa3+Q2hI1YHoXaSmbq0k4ZwZ7J6sWNV4UZfx1OeOlbQ==} resolution: {integrity: sha512-gbxfUaSPV7EyUobpavida2Hwi62GhSJaSg7iBjmBWoxkxlmETOD7U4tWt763cGIsyE6jM7IoNavq0BXqwdW2QA==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
@ -1840,8 +1837,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-ia32-msvc@14.2.7: /@next/swc-win32-ia32-msvc@14.2.8:
resolution: {integrity: sha512-BiSY5umlx9ed5RQDoHcdbuKTUkuFORDqzYKPHlLeS+STUWQKWziVOn3Ic41LuTBvqE0TRJPKpio9GSIblNR+0w==} resolution: {integrity: sha512-PUXzEzjTTlUh3b5VAn1nlpwvujTnuCMMwbiCnaTazoVlN1nA3kWjlmp42IfURA2N/nyrlVEw7pURa/o4Qxj1cw==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
@ -1849,8 +1846,8 @@ packages:
dev: false dev: false
optional: true optional: true
/@next/swc-win32-x64-msvc@14.2.7: /@next/swc-win32-x64-msvc@14.2.8:
resolution: {integrity: sha512-pxsI23gKWRt/SPHFkDEsP+w+Nd7gK37Hpv0ngc5HpWy2e7cKx9zR/+Q2ptAUqICNTecAaGWvmhway7pj/JLEWA==} resolution: {integrity: sha512-EnPKv0ttq02E9/1KZ/8Dn7kuutv6hy1CKc0HlNcvzOQcm4/SQtvfws5gY0zrG9tuupd3HfC2L/zcTrnBhpjTuQ==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
@ -2686,11 +2683,6 @@ packages:
strip-ansi: 5.2.0 strip-ansi: 5.2.0
dev: false 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: /ansi-regex@2.1.1:
resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==} resolution: {integrity: sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2715,11 +2707,6 @@ packages:
engines: {node: '>=12'} engines: {node: '>=12'}
dev: false 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: /ansi-styles@2.2.1:
resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -2957,10 +2944,6 @@ packages:
resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==}
dev: false dev: false
/async@0.9.2:
resolution: {integrity: sha512-l6ToIJIotphWahxxHyzK9bnLR6kM4jJIIgLShZeqLY7iboHoGkdgFl7W2/Ivi4SkMJYGKqW8vSuk0uKUj6qsSw==}
dev: false
/async@2.6.4: /async@2.6.4:
resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==} resolution: {integrity: sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==}
dependencies: dependencies:
@ -3321,17 +3304,6 @@ packages:
resolution: {integrity: sha512-ODLXH644w9C2fMPAm7bMDQ3GRvipZWZfKc+8As6hIadRIelE0n0xZuN38NS6kiK3KPEVrpymmQD8bvncAHWQkQ==} resolution: {integrity: sha512-ODLXH644w9C2fMPAm7bMDQ3GRvipZWZfKc+8As6hIadRIelE0n0xZuN38NS6kiK3KPEVrpymmQD8bvncAHWQkQ==}
dev: false 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: /chalk@1.1.3:
resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==} resolution: {integrity: sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -5105,14 +5077,6 @@ packages:
pinkie-promise: 2.0.1 pinkie-promise: 2.0.1
dev: false 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: /has-ansi@2.0.0:
resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -6751,8 +6715,8 @@ packages:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
dev: false dev: false
/next@14.2.7(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1): /next@14.2.8(@babel/core@7.25.2)(react-dom@18.3.1)(react@18.3.1):
resolution: {integrity: sha512-4Qy2aK0LwH4eQiSvQWyKuC7JXE13bIopEQesWE0c/P3uuNRnZCQanI0vsrMLmUQJLAto+A+/8+sve2hd+BQuOQ==} resolution: {integrity: sha512-EyEyJZ89r8C5FPlS/401AiF3O8jeMtHIE+bLom9MwcdWJJFBgRl+MR/2VgO0v5bI6tQORNY0a0DR5sjpFNrjbg==}
engines: {node: '>=18.17.0'} engines: {node: '>=18.17.0'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -6769,7 +6733,7 @@ packages:
sass: sass:
optional: true optional: true
dependencies: dependencies:
'@next/env': 14.2.7 '@next/env': 14.2.8
'@swc/helpers': 0.5.5 '@swc/helpers': 0.5.5
busboy: 1.6.0 busboy: 1.6.0
caniuse-lite: 1.0.30001655 caniuse-lite: 1.0.30001655
@ -6779,15 +6743,15 @@ packages:
react-dom: 18.3.1(react@18.3.1) react-dom: 18.3.1(react@18.3.1)
styled-jsx: 5.1.1(@babel/core@7.25.2)(react@18.3.1) styled-jsx: 5.1.1(@babel/core@7.25.2)(react@18.3.1)
optionalDependencies: optionalDependencies:
'@next/swc-darwin-arm64': 14.2.7 '@next/swc-darwin-arm64': 14.2.8
'@next/swc-darwin-x64': 14.2.7 '@next/swc-darwin-x64': 14.2.8
'@next/swc-linux-arm64-gnu': 14.2.7 '@next/swc-linux-arm64-gnu': 14.2.8
'@next/swc-linux-arm64-musl': 14.2.7 '@next/swc-linux-arm64-musl': 14.2.8
'@next/swc-linux-x64-gnu': 14.2.7 '@next/swc-linux-x64-gnu': 14.2.8
'@next/swc-linux-x64-musl': 14.2.7 '@next/swc-linux-x64-musl': 14.2.8
'@next/swc-win32-arm64-msvc': 14.2.7 '@next/swc-win32-arm64-msvc': 14.2.8
'@next/swc-win32-ia32-msvc': 14.2.7 '@next/swc-win32-ia32-msvc': 14.2.8
'@next/swc-win32-x64-msvc': 14.2.7 '@next/swc-win32-x64-msvc': 14.2.8
transitivePeerDependencies: transitivePeerDependencies:
- '@babel/core' - '@babel/core'
- babel-plugin-macros - babel-plugin-macros
@ -7564,17 +7528,6 @@ packages:
inherits: 2.0.4 inherits: 2.0.4
dev: false 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: /range-parser@1.2.1:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@ -8576,14 +8529,6 @@ packages:
resolution: {integrity: sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==} resolution: {integrity: sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==}
dev: false 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: /strip-ansi@3.0.1:
resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==} resolution: {integrity: sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -8689,12 +8634,6 @@ packages:
resolution: {integrity: sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==} resolution: {integrity: sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==}
dev: false 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: /supports-color@2.0.0:
resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==} resolution: {integrity: sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==}
engines: {node: '>=0.8.0'} engines: {node: '>=0.8.0'}
@ -9279,10 +9218,6 @@ packages:
webidl-conversions: 3.0.1 webidl-conversions: 3.0.1
dev: false dev: false
/when@3.7.8:
resolution: {integrity: sha512-5cZ7mecD3eYcMiCH4wtRPA5iFJZ50BJYDfckI5RRpQiktMiYTcn0ccLTZOvcbBume+1304fQztxeNzNS9Gvrnw==}
dev: false
/which-boxed-primitive@1.0.2: /which-boxed-primitive@1.0.2:
resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==}
dependencies: dependencies:
@ -9435,21 +9370,6 @@ packages:
engines: {node: '>=4'} engines: {node: '>=4'}
dev: false 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: /xtend@4.0.2:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'} engines: {node: '>=0.4'}

@ -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<JSX.Element> {
const slug = normalizeSlug(params.slug)
const channel = await channelRepo.fetch(slug)
if (!channel) {
return (
<div className="flex flex-col items-center space-y-5 py-10 max-w-2xl mx-auto">
<Spinner direction="down" />
<Wordmark />
<p>Not found</p>
</div>
)
}
return (
<div className="flex flex-col items-center space-y-5 py-10 max-w-2xl mx-auto">
<Spinner direction="down" />
<Wordmark />
<WebRTCProvider>
<Downloader uploaderPeerID={channel.uploaderPeerID} />
</WebRTCProvider>
</div>
)
}

@ -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 (
<html lang="en">
<head>
<meta name="monetization" content="$twitter.xrptipbot.com/kernio" />
</head>
<body>
<main>{children}</main>
<Footer />
</body>
</html>
)
}

@ -1,3 +1,5 @@
'use client'
import React, { useCallback, useState } from 'react' import React, { useCallback, useState } from 'react'
import WebRTCProvider from '../components/WebRTCProvider' import WebRTCProvider from '../components/WebRTCProvider'
import DropZone from '../components/DropZone' import DropZone from '../components/DropZone'
@ -7,12 +9,11 @@ import PasswordField from '../components/PasswordField'
import StartButton from '../components/StartButton' import StartButton from '../components/StartButton'
import StopButton from '../components/StopButton' import StopButton from '../components/StopButton'
import { UploadedFile } from '../types' import { UploadedFile } from '../types'
import { NextPage } from 'next'
import Spinner from '../components/Spinner' import Spinner from '../components/Spinner'
import Wordmark from '../components/Wordmark' import Wordmark from '../components/Wordmark'
import CancelButton from '../components/CancelButton' import CancelButton from '../components/CancelButton'
export const IndexPage: NextPage = () => { export default function IndexPage(): JSX.Element {
const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([]) const [uploadedFiles, setUploadedFiles] = useState<UploadedFile[]>([])
const [password, setPassword] = useState('') const [password, setPassword] = useState('')
const [uploading, setUploading] = useState(false) const [uploading, setUploading] = useState(false)
@ -44,10 +45,10 @@ export const IndexPage: NextPage = () => {
if (!uploadedFiles.length) { if (!uploadedFiles.length) {
return ( return (
<div className="flex flex-col items-center space-y-5 py-10"> <div className="flex flex-col items-center space-y-5 py-10 max-w-2xl mx-auto">
<Spinner direction="up" /> <Spinner direction="up" />
<Wordmark /> <Wordmark />
<div className="flex flex-col items-center space-y-1"> <div className="flex flex-col items-center space-y-1 max-w-md">
<p className="text-lg text-center text-stone-800"> <p className="text-lg text-center text-stone-800">
Peer-to-peer file transfers in your browser. Peer-to-peer file transfers in your browser.
</p> </p>
@ -62,11 +63,12 @@ export const IndexPage: NextPage = () => {
if (!uploading) { if (!uploading) {
return ( return (
<div className="flex flex-col items-center space-y-5 py-10"> <div className="flex flex-col items-center space-y-5 py-10 max-w-2xl mx-auto">
<Spinner direction="up" /> <Spinner direction="up" />
<Wordmark /> <Wordmark />
<p className="text-lg text-center text-stone-800"> <p className="text-lg text-center text-stone-800 max-w-md">
You are about to start uploading {uploadedFiles.length} files. You are about to start uploading {uploadedFiles.length}{' '}
{uploadedFiles.length === 1 ? 'file' : 'files'}.
</p> </p>
<UploadFileList files={uploadedFiles} onChange={handleFileListChange} /> <UploadFileList files={uploadedFiles} onChange={handleFileListChange} />
<PasswordField value={password} onChange={handleChangePassword} /> <PasswordField value={password} onChange={handleChangePassword} />
@ -79,11 +81,12 @@ export const IndexPage: NextPage = () => {
} }
return ( return (
<div className="flex flex-col items-center space-y-5 py-10"> <div className="flex flex-col items-center space-y-5 py-10 max-w-2xl mx-auto">
<Spinner direction="up" isRotating /> <Spinner direction="up" isRotating />
<Wordmark /> <Wordmark />
<p className="text-lg text-center text-stone-800"> <p className="text-lg text-center text-stone-800 max-w-md">
You are uploading {uploadedFiles.length} files. You are uploading {uploadedFiles.length}{' '}
{uploadedFiles.length === 1 ? 'file' : 'files'}.
</p> </p>
<UploadFileList files={uploadedFiles} /> <UploadFileList files={uploadedFiles} />
<WebRTCProvider> <WebRTCProvider>
@ -93,9 +96,3 @@ export const IndexPage: NextPage = () => {
</div> </div>
) )
} }
IndexPage.getInitialProps = () => {
return {}
}
export default IndexPage

@ -1,3 +1,4 @@
import 'server-only'
import config from './config' import config from './config'
import Redis from 'ioredis' import Redis from 'ioredis'
import { generateShortSlug, generateLongSlug } from './slugs' import { generateShortSlug, generateLongSlug } from './slugs'

@ -1,10 +1,10 @@
import React from 'react' import React from 'react'
type Props = { export default function CancelButton({
onClick,
}: {
onClick: React.MouseEventHandler onClick: React.MouseEventHandler
} }): JSX.Element {
const CancelButton: React.FC<Props> = ({ onClick }: Props) => {
return ( return (
<button <button
onClick={onClick} onClick={onClick}
@ -14,5 +14,3 @@ const CancelButton: React.FC<Props> = ({ onClick }: Props) => {
</button> </button>
) )
} }
export default CancelButton

@ -1,10 +1,10 @@
import React from 'react' import React from 'react'
type Props = { export default function DownloadButton({
onClick,
}: {
onClick?: React.MouseEventHandler onClick?: React.MouseEventHandler
} }): JSX.Element {
const DownloadButton: React.FC<Props> = ({ onClick }: Props) => {
return ( return (
<button <button
onClick={onClick} onClick={onClick}
@ -14,5 +14,3 @@ const DownloadButton: React.FC<Props> = ({ onClick }: Props) => {
</button> </button>
) )
} }
export default DownloadButton

@ -1,3 +1,5 @@
'use client'
import React, { useCallback, useEffect, useRef, useState } from 'react' import React, { useCallback, useEffect, useRef, useState } from 'react'
import { useWebRTC } from './WebRTCProvider' import { useWebRTC } from './WebRTCProvider'
import { import {
@ -23,11 +25,12 @@ import ProgressBar from './ProgressBar'
const baseURL = process.env.NEXT_PUBLIC_BASE_URL ?? 'http://localhost:3000' const baseURL = process.env.NEXT_PUBLIC_BASE_URL ?? 'http://localhost:3000'
// eslint-disable-next-line @typescript-eslint/no-var-requires // 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 // eslint-disable-next-line @typescript-eslint/no-var-requires
const streamSaver = process.browser ? require('streamsaver') : null const streamSaver =
if (process.browser) { typeof window !== 'undefined' ? require('streamsaver') : null
if (typeof window !== 'undefined') {
streamSaver.mitm = baseURL + '/stream.html' streamSaver.mitm = baseURL + '/stream.html'
} }

@ -1,21 +1,28 @@
'use client'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
const DONATE_HREF = const DONATE_HREF =
'https://commerce.coinbase.com/checkout/247b6ffe-fb4e-47a8-9a76-e6b7ef83ea22' 'https://commerce.coinbase.com/checkout/247b6ffe-fb4e-47a8-9a76-e6b7ef83ea22'
const FooterLink: React.FC<{ href: string; children: React.ReactNode }> = ({ function FooterLink({
href, href,
children, children,
}) => ( }: {
<a href: string
className="text-stone-600 underline hover:text-stone-800" children: React.ReactNode
href={href} }): JSX.Element {
target="_blank" return (
rel="noopener noreferrer" <a
> className="text-stone-600 underline hover:text-stone-800"
{children} href={href}
</a> target="_blank"
) rel="noopener noreferrer"
>
{children}
</a>
)
}
export function Footer(): JSX.Element { export function Footer(): JSX.Element {
const handleDonate = useCallback(() => { const handleDonate = useCallback(() => {
@ -23,34 +30,38 @@ export function Footer(): JSX.Element {
}, []) }, [])
return ( return (
<footer className="text-center py-2.5 pb-4 text-xs border-t border-stone-200 shadow-[0_-1px_2px_rgba(0,0,0,0.04)]"> <>
<div className="flex flex-col items-center space-y-1 px-4 sm:px-6 md:px-8"> <div className="h-[100px]" /> {/* Spacer to account for footer height */}
<div className="flex items-center space-x-2"> <footer className="fixed bottom-0 left-0 right-0 text-center py-2.5 pb-4 text-xs border-t border-stone-200 shadow-[0_-1px_2px_rgba(0,0,0,0.04)] bg-white">
<div className="flex flex-col items-center space-y-1 px-4 sm:px-6 md:px-8">
<div className="flex items-center space-x-2">
<p className="text-stone-600">
<strong>Like FilePizza?</strong> Support its development!{' '}
</p>
<button
className="px-1.5 py-0.5 bg-green-500 text-white rounded-md hover:bg-green-600 transition-colors duration-200 font-medium text-[10px]"
onClick={handleDonate}
>
Donate
</button>
</div>
<p className="text-stone-600"> <p className="text-stone-600">
<strong>Like FilePizza?</strong> Support its development!{' '} Cooked up by{' '}
<FooterLink href="http://kern.io">Alex Kern</FooterLink> &amp;{' '}
<FooterLink href="http://neeraj.io">Neeraj Baid</FooterLink> while
eating <strong>Sliver</strong> @ UC Berkeley &middot;{' '}
<FooterLink href="https://github.com/kern/filepizza#faq">
FAQ
</FooterLink>{' '}
&middot;{' '}
<FooterLink href="https://github.com/kern/filepizza">
Fork us
</FooterLink>
</p> </p>
<button
className="px-1.5 py-0.5 bg-green-500 text-white rounded-md hover:bg-green-600 transition-colors duration-200 font-medium text-[10px]"
onClick={handleDonate}
>
Donate
</button>
</div> </div>
</footer>
<p className="text-stone-600"> </>
Cooked up by <FooterLink href="http://kern.io">Alex Kern</FooterLink>{' '}
&amp; <FooterLink href="http://neeraj.io">Neeraj Baid</FooterLink>{' '}
while eating <strong>Sliver</strong> @ UC Berkeley &middot;{' '}
<FooterLink href="https://github.com/kern/filepizza#faq">
FAQ
</FooterLink>{' '}
&middot;{' '}
<FooterLink href="https://github.com/kern/filepizza">
Fork us
</FooterLink>
</p>
</div>
</footer>
) )
} }

@ -1,21 +1,16 @@
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
export function PasswordField({ export default function PasswordField(props: {
value,
onChange,
isRequired,
isInvalid,
}: {
value: string value: string
onChange: (v: string) => void onChange: (v: string) => void
isRequired?: boolean isRequired?: boolean
isInvalid?: boolean isInvalid?: boolean
}): JSX.Element { }): JSX.Element {
const handleChange = useCallback( const handleChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => { function (e: React.ChangeEvent<HTMLInputElement>): void {
onChange(e.target.value) props.onChange(e.target.value)
}, },
[onChange], [props.onChange],
) )
return ( return (
@ -23,15 +18,13 @@ export function PasswordField({
autoFocus autoFocus
type="password" type="password"
className={`w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 ${ 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={ placeholder={
isRequired ? 'Enter password...' : 'Add password (optional)...' props.isRequired ? 'Enter password...' : 'Add password (optional)...'
} }
value={value} value={props.value}
onChange={handleChange} onChange={handleChange}
/> />
) )
} }
export default PasswordField

@ -17,7 +17,7 @@ export default function ProgressBar({
isComplete ? 'bg-green-500' : 'bg-blue-500' isComplete ? 'bg-green-500' : 'bg-blue-500'
} transition-all duration-300 ease-in-out`} } transition-all duration-300 ease-in-out`}
style={{ width: `${percentage}%` }} style={{ width: `${percentage}%` }}
></div> />
</div> </div>
) )
} }

@ -1,10 +1,10 @@
import React from 'react' import React from 'react'
export function StartButton({ export default function StartButton({
onClick, onClick,
}: { }: {
onClick: React.MouseEventHandler onClick: React.MouseEventHandler<HTMLButtonElement>
}): JSX.Element { }): React.ReactElement {
return ( return (
<button <button
onClick={onClick} onClick={onClick}
@ -14,5 +14,3 @@ export function StartButton({
</button> </button>
) )
} }
export default StartButton

@ -1,19 +1,26 @@
import React from 'react' import React from 'react'
type Props = { export default function StopButton({
onClick: React.MouseEventHandler isDownloading,
onClick,
}: {
onClick: React.MouseEventHandler<HTMLButtonElement>
isDownloading?: boolean isDownloading?: boolean
} }): React.ReactElement {
const StopButton: React.FC<Props> = ({ isDownloading, onClick }: Props) => {
return ( return (
<button <button
className="px-2 py-1 text-xs text-orange-500 bg-transparent hover:bg-orange-100 rounded transition-colors duration-200" className="px-2 py-1 text-xs text-orange-500 bg-transparent hover:bg-orange-100 rounded transition-colors duration-200 flex items-center"
onClick={onClick} onClick={onClick}
> >
<svg
className="w-4 h-4 mr-1"
viewBox="0 0 24 24"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
>
<rect x="4" y="4" width="16" height="16" />
</svg>
{isDownloading ? 'Stop Download' : 'Stop Upload'} {isDownloading ? 'Stop Download' : 'Stop Upload'}
</button> </button>
) )
} }
export default StopButton

@ -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 (
<span
className={`px-2 py-1 text-xs font-semibold rounded-full ${getTypeColor(
type,
)} transition-all duration-300 mr-2`}
>
{type}
</span>
)
}

@ -1,10 +1,10 @@
import React from 'react' import React from 'react'
type Props = { export default function UnlockButton({
onClick?: React.MouseEventHandler onClick,
} }: {
onClick?: React.MouseEventHandler<HTMLButtonElement>
const UnlockButton: React.FC<Props> = ({ onClick }: Props) => { }): React.ReactElement {
return ( return (
<button <button
onClick={onClick} onClick={onClick}
@ -14,5 +14,3 @@ const UnlockButton: React.FC<Props> = ({ onClick }: Props) => {
</button> </button>
) )
} }
export default UnlockButton

@ -1,4 +1,5 @@
import React from 'react' import React from 'react'
import TypeBadge from './TypeBadge'
type UploadedFileLike = { type UploadedFileLike = {
fullPath?: string fullPath?: string
@ -6,40 +7,21 @@ type UploadedFileLike = {
type: string type: string
} }
interface Props { function getFileName(file: UploadedFileLike): string {
files: UploadedFileLike[]
onChange?: (updatedFiles: UploadedFileLike[]) => void
}
const getFileName = (file: UploadedFileLike): string => {
if (file.fullPath) { if (file.fullPath) {
return file.fullPath.slice(1) return file.fullPath.slice(1)
} }
return file.name || 'Unknown' return file.name || 'Unknown'
} }
export function TypeBadge({ type }: { type: string }): JSX.Element { export default function UploadFileList({
const getTypeColor = (fileType: string): string => { files,
if (fileType.startsWith('image/')) return 'bg-blue-100 text-blue-800' onChange,
if (fileType.startsWith('text/')) return 'bg-green-100 text-green-800' }: {
if (fileType.startsWith('audio/')) return 'bg-purple-100 text-purple-800' files: UploadedFileLike[]
if (fileType.startsWith('video/')) return 'bg-red-100 text-red-800' onChange?: (updatedFiles: UploadedFileLike[]) => void
return 'bg-gray-100 text-gray-800' }): JSX.Element {
} function handleRemove(index: number): void {
return (
<span
className={`px-2 py-1 text-xs font-semibold rounded-full ${getTypeColor(
type,
)} transition-all duration-300 mr-2`}
>
{type}
</span>
)
}
export function UploadFileList({ files, onChange }: Props): JSX.Element {
const handleRemove = (index: number) => {
if (onChange) { if (onChange) {
const updatedFiles = files.filter((_, i) => i !== index) const updatedFiles = files.filter((_, i) => i !== index)
onChange(updatedFiles) onChange(updatedFiles)
@ -70,5 +52,3 @@ export function UploadFileList({ files, onChange }: Props): JSX.Element {
return <div className="w-full">{items}</div> return <div className="w-full">{items}</div>
} }
export default UploadFileList

@ -36,6 +36,7 @@ type UploaderConnection = {
// TODO(@kern): Use better values // TODO(@kern): Use better values
const RENEW_INTERVAL = 5000 // 20 minutes const RENEW_INTERVAL = 5000 // 20 minutes
const MAX_CHUNK_SIZE = 10 * 1024 * 1024 // 10 Mi const MAX_CHUNK_SIZE = 10 * 1024 * 1024 // 10 Mi
const QR_CODE_SIZE = 128
function useUploaderChannel(uploaderPeerID: string): { function useUploaderChannel(uploaderPeerID: string): {
loading: boolean loading: boolean
@ -330,36 +331,46 @@ export default function Uploader({
return ( return (
<> <>
<div className="flex w-full"> <div className="flex w-full items-center">
<div className="flex-none"> <div className="flex-none mr-4">
<QRCode value={shortURL} size={88} /> <QRCode value={shortURL} size={QR_CODE_SIZE} />
</div> </div>
<div className="flex-auto flex flex-col"> <div className="flex-auto flex flex-col justify-center space-y-2">
<div className="flex w-full"> <div className="flex flex-col w-full">
<input <label className="text-[10px] text-gray-400 mb-0.5 font-bold">
className="flex-grow px-2 py-1 text-xs border rounded-l" Long URL
value={longURL} </label>
readOnly <div className="flex w-full">
/> <input
<button className="flex-grow px-3 py-2 text-xs border border-r-0 rounded-l"
className="px-4 py-1 text-sm text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-r" value={longURL}
onClick={onCopyLongURL} readOnly
> />
{hasCopiedLongURL ? 'Copied' : 'Copy'} <button
</button> className="px-4 py-2 text-sm text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-r border-t border-r border-b"
onClick={onCopyLongURL}
>
{hasCopiedLongURL ? 'Copied' : 'Copy'}
</button>
</div>
</div> </div>
<div className="flex w-full mt-2"> <div className="flex flex-col w-full mt-2">
<input <label className="text-[10px] text-gray-400 mb-0.5 font-bold">
className="flex-grow px-2 py-1 text-xs border rounded-l" Short URL
value={shortURL} </label>
readOnly <div className="flex w-full">
/> <input
<button className="flex-grow px-3 py-2 text-xs border border-r-0 rounded-l"
className="px-4 py-1 text-sm text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-r" value={shortURL}
onClick={onCopyShortURL} readOnly
> />
{hasCopiedShortURL ? 'Copied' : 'Copy'} <button
</button> className="px-4 py-2 text-sm text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-r border-t border-r border-b"
onClick={onCopyShortURL}
>
{hasCopiedShortURL ? 'Copied' : 'Copy'}
</button>
</div>
</div> </div>
</div> </div>
</div> </div>

@ -1,3 +1,5 @@
'use client'
import React, { useState, useEffect, useRef, useContext } from 'react' import React, { useState, useEffect, useRef, useContext } from 'react'
import type { default as PeerType } from 'peerjs' import type { default as PeerType } from 'peerjs'
import Loading from './Loading' import Loading from './Loading'

@ -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<AppProps> = ({ Component, pageProps }: AppProps) => (
<>
<Head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="monetization" content="$twitter.xrptipbot.com/kernio" />
<meta property="og:url" content="https://file.pizza" />
<meta property="og:title" content="FilePizza • Your files, delivered." />
<meta
property="og:description"
content="Peer-to-peer file transfers in your web browser."
/>
<meta property="og:image" content="https://file.pizza/images/fb.png" />
<title>FilePizza Your files, delivered.</title>
<meta property="og:title" content="FilePizza" key="title" />
</Head>
<main>
<Component {...pageProps} />
</main>
<Footer />
</>
)
export default App

@ -1,51 +0,0 @@
import React from 'react'
import WebRTCProvider from '../../components/WebRTCProvider'
import Downloader from '../../components/Downloader'
import { NextPage, GetServerSideProps } from 'next'
import { channelRepo } from '../../channel'
import Spinner from '../../components/Spinner'
import Wordmark from '../../components/Wordmark'
type Props = {
slug: string
uploaderPeerID: string
error?: string
}
const DownloadPage: NextPage<Props> = ({ uploaderPeerID }) => {
return (
<div className="flex flex-col items-center space-y-5 py-10 w-full">
<Spinner direction="down" />
<Wordmark />
<WebRTCProvider>
<Downloader uploaderPeerID={uploaderPeerID} />
</WebRTCProvider>
</div>
)
}
export const getServerSideProps: GetServerSideProps<Props> = async (ctx) => {
const slug = normalizeSlug(ctx.query.slug)
const channel = await channelRepo.fetch(slug)
if (!channel) {
ctx.res.statusCode = 404
return {
props: { slug, uploaderPeerID: '', error: 'not found' },
}
}
return {
props: { slug, uploaderPeerID: channel.uploaderPeerID },
}
}
const normalizeSlug = (rawSlug: string | string[]): string => {
if (typeof rawSlug === 'string') {
return rawSlug
} else {
return rawSlug.join('/')
}
}
export default DownloadPage

@ -1,6 +1,49 @@
import xkcdPassword from 'xkcd-password' import 'server-only'
import crypto from 'crypto'
import config from './config' import config from './config'
/**
* Generates an array of random words from a given word list.
*
* @param wordList - An array of words to choose from.
* @param numWords - The number of words to generate.
* @returns A Promise that resolves to an array of randomly selected words.
*/
function generateRandomWords(
wordList: string[],
numWords: number,
): Promise<string[]> {
return new Promise((resolve, reject) => {
if (!Array.isArray(wordList) || wordList.length === 0) {
reject(new Error('Word list must be a non-empty array'))
return
}
if (numWords <= 0) {
reject(new Error('Number of words must be greater than zero'))
return
}
const getRandomInt = (max: number): number => {
const buffer = new Uint32Array(1)
if (typeof window !== 'undefined' && window.crypto) {
window.crypto.getRandomValues(buffer)
} else {
crypto.randomFillSync(buffer)
}
return buffer[0] % max
}
const result: string[] = []
for (let i = 0; i < numWords; i++) {
const randomIndex = getRandomInt(wordList.length)
result.push(wordList[randomIndex])
}
resolve(result)
})
}
export const generateShortSlug = (): string => { export const generateShortSlug = (): string => {
let result = '' let result = ''
for (let i = 0; i < config.shortSlug.numChars; i++) { for (let i = 0; i < config.shortSlug.numChars; i++) {
@ -12,14 +55,10 @@ export const generateShortSlug = (): string => {
return result return result
} }
const longSlugGenerator = new xkcdPassword()
longSlugGenerator.initWithWordList(config.longSlug.words)
export const generateLongSlug = async (): Promise<string> => { export const generateLongSlug = async (): Promise<string> => {
const parts = await longSlugGenerator.generate({ const parts = await generateRandomWords(
numWords: config.longSlug.numWords, config.longSlug.words,
minLength: 1, config.longSlug.numWords,
maxLength: 256, )
})
return parts.join('/') return parts.join('/')
} }

@ -18,16 +18,23 @@
"isolatedModules": true, "isolatedModules": true,
"downlevelIteration": true, "downlevelIteration": true,
"jsx": "preserve", "jsx": "preserve",
"incremental": true "incremental": true,
"plugins": [
{
"name": "next"
}
],
"strictNullChecks": true
}, },
"include": [ "include": [
"tailwind.config.js", "tailwind.config.js",
"next-env.d.ts", "next-env.d.ts",
"src/**/*.js", "src/**/*.js",
"src/**/*.ts", "src/**/*.ts",
"src/**/*.tsx" "src/**/*.tsx",
".next/types/**/*.ts"
], ],
"exclude": [ "exclude": [
"node_modules" "node_modules"
] ]
} }

Loading…
Cancel
Save