diff --git a/Dockerfile b/Dockerfile index 29bbc3c..eb661af 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,9 @@ -FROM node:23-alpine AS base +FROM node:26-alpine AS base WORKDIR /app RUN apk add --no-cache libc6-compat -RUN corepack enable && corepack prepare pnpm@11.1.1 --activate +RUN npm install -g pnpm FROM base AS deps WORKDIR /app @@ -28,7 +28,7 @@ ENV HOSTNAME=0.0.0.0 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs -# I know all the paths are messed up but I don't have time to fix it +# I know all the paths are messed up COPY --from=builder /app/backend/public ./public COPY --from=builder /app/backend/.next ./.next COPY --from=builder /app/backend/.next/static ./.next/standalone/backend/.next/static diff --git a/backend/next.config.ts b/backend/next.config.ts index ff266bf..978a1da 100644 --- a/backend/next.config.ts +++ b/backend/next.config.ts @@ -2,6 +2,9 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { output: "standalone", + experimental: { + useTypeScriptCli: true, + }, async headers() { return [ { diff --git a/backend/package.json b/backend/package.json index 9a4338e..725ab49 100644 --- a/backend/package.json +++ b/backend/package.json @@ -2,7 +2,7 @@ "name": "@tomodachi-share/backend", "version": "0.1.0", "private": true, - "packageManager": "pnpm@11.8.0", + "packageManager": "pnpm@11.18.0", "scripts": { "dev": "next dev", "build": "next build", @@ -12,39 +12,39 @@ }, "dependencies": { "@2toad/profanity": "^3.3.0", - "@auth/prisma-adapter": "2.11.2", + "@auth/prisma-adapter": "2.11.3", "@prisma/client": "6.19.3", "@tomodachi-share/shared": "workspace:*", "bit-buffer": "^0.3.0", "charinfo-ex": "^0.0.5", "dayjs": "^1.11.21", - "downshift": "^9.3.6", + "downshift": "^9.4.0", "file-type": "^22.0.1", - "next": "16.2.9", + "next": "16.2.12", "next-auth": "5.0.0-beta.30", "qrcode-generator": "^2.0.4", - "react": "^19.2.7", - "react-dom": "^19.2.7", - "redis": "^6.0.0", - "satori": "^0.26.0", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "redis": "^6.2.0", + "satori": "^0.29.0", "sharp": "^0.34.5", "sjcl-with-all": "1.0.8", "zod": "^4.4.3" }, "devDependencies": { - "@eslint/eslintrc": "^3.3.5", + "@eslint/eslintrc": "^3.3.6", "@iconify/react": "^6.0.2", - "@tailwindcss/postcss": "^4.3.1", - "@types/node": "^26.0.0", - "@types/react": "^19.2.17", - "@types/react-dom": "^19.2.3", + "@tailwindcss/postcss": "^4.3.3", + "@types/node": "^26.1.2", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", "@types/sjcl": "^1.0.34", - "eslint": "^10.5.0", - "eslint-config-next": "16.2.9", + "eslint": "^10.8.0", + "eslint-config-next": "16.2.12", "prisma": "^6.19.3", "schema-dts": "^2.0.0", - "tailwindcss": "^4.3.1", - "typescript": "^6.0.3" + "tailwindcss": "^4.3.3", + "typescript": "^7.0.2" }, "exports": { ".": "./src/types.d.ts" diff --git a/frontend/package.json b/frontend/package.json index f46e281..c7dcae5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -11,43 +11,43 @@ }, "dependencies": { "@bprogress/react": "^1.2.7", - "@fontsource-variable/lexend": "^5.2.11", + "@fontsource-variable/lexend": "^5.3.0", "@hello-pangea/dnd": "^18.0.1", "@nanostores/react": "^1.1.0", - "@tailwindcss/vite": "^4.3.0", + "@tailwindcss/vite": "^4.3.3", "@tomodachi-share/shared": "workspace:*", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", + "@types/react": "^19.2.18", + "@types/react-dom": "^19.2.4", "canvas-confetti": "^1.9.4", - "dayjs": "^1.11.20", - "downshift": "^9.3.2", + "dayjs": "^1.11.21", + "downshift": "^9.4.0", "embla-carousel-react": "^8.6.0", "jsqr": "^1.4.0", - "nanostores": "^1.3.0", + "nanostores": "^1.4.2", "qrcode-generator": "^2.0.4", - "react": "^19.2.6", - "react-dom": "^19.2.6", - "react-dropzone": "^15.0.0", - "react-image-crop": "^11.0.10", - "react-router": "^7.15.0", - "tailwindcss": "^4.3.0", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "react-dropzone": "^19.1.1", + "react-image-crop": "^11.1.2", + "react-router": "^8.3.0", + "tailwindcss": "^4.3.3", "vite-plugin-node-polyfills": "^0.26.0", "zod": "^4.4.3" }, "devDependencies": { - "@eslint/js": "^9.39.4", + "@eslint/js": "^10.0.1", "@iconify/react": "^6.0.2", "@types/canvas-confetti": "^1.9.0", - "@types/node": "^24.12.4", + "@types/node": "^26.1.2", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^6.0.1", - "eslint": "^9.39.4", + "@vitejs/plugin-react": "^6.0.5", + "eslint": "^10.8.0", "eslint-plugin-react-hooks": "^7.1.1", - "eslint-plugin-react-refresh": "^0.5.2", - "globals": "^17.6.0", - "typescript": "~6.0.3", - "typescript-eslint": "^8.59.3", - "vite": "^8.0.12" + "eslint-plugin-react-refresh": "^0.5.3", + "globals": "^17.8.0", + "typescript": "~7.0.2", + "typescript-eslint": "^8.65.0", + "vite": "^8.2.0" } } diff --git a/frontend/src/components/dropzone.tsx b/frontend/src/components/dropzone.tsx index 4d67715..4e195dc 100644 --- a/frontend/src/components/dropzone.tsx +++ b/frontend/src/components/dropzone.tsx @@ -1,10 +1,10 @@ import { type ReactNode, useState } from "react"; -import { type DropzoneOptions, type FileWithPath, useDropzone } from "react-dropzone"; +import { type DropzoneOptions, useDropzone } from "react-dropzone"; import { Icon } from "@iconify/react"; interface Props { type?: "file" | "image"; - onDrop: (acceptedFiles: FileWithPath[]) => void; + onDrop: (acceptedFiles: File[]) => void; options?: DropzoneOptions; children?: ReactNode; } @@ -12,7 +12,7 @@ interface Props { export default function Dropzone({ type = "image", onDrop, options, children }: Props) { const [isDraggingOver, setIsDraggingOver] = useState(false); - const handleDrop = (acceptedFiles: FileWithPath[]) => { + const handleDrop = (acceptedFiles: File[]) => { setIsDraggingOver(false); onDrop(acceptedFiles); }; diff --git a/frontend/src/components/footer.tsx b/frontend/src/components/footer.tsx index 393002b..433263c 100644 --- a/frontend/src/components/footer.tsx +++ b/frontend/src/components/footer.tsx @@ -1,4 +1,3 @@ -import { Icon } from "@iconify/react"; import { Link } from "react-router"; export default function Footer() { @@ -15,32 +14,15 @@ export default function Footer() { Terms of Service - - Privacy Policy - - - - - Discord - - - - + {/* Contact / feedback */} +
+

+ Questions, bugs, or feature ideas? Contact me at{" "} + + hello@trafficlunar.net + +

+
+ {/* Copyright */}

© {new Date().getFullYear()} TomodachiShare. All rights reserved.

diff --git a/frontend/src/components/profile-settings/profile-picture.tsx b/frontend/src/components/profile-settings/profile-picture.tsx index 765afe4..2c944c8 100644 --- a/frontend/src/components/profile-settings/profile-picture.tsx +++ b/frontend/src/components/profile-settings/profile-picture.tsx @@ -1,5 +1,4 @@ import { useCallback, useState } from "react"; -import { type FileWithPath } from "react-dropzone"; import { Icon } from "@iconify/react"; import dayjs from "dayjs"; @@ -11,7 +10,7 @@ import { useNavigate } from "react-router"; export default function ProfilePictureSettings() { const navigate = useNavigate(); const [error, setError] = useState(undefined); - const [newPicture, setNewPicture] = useState(); + const [newPicture, setNewPicture] = useState(); const changeDate = dayjs().add(7, "days"); @@ -35,7 +34,7 @@ export default function ProfilePictureSettings() { navigate(0); }; - const handleDrop = useCallback((acceptedFiles: FileWithPath[]) => { + const handleDrop = useCallback((acceptedFiles: File[]) => { if (!acceptedFiles[0]) return; setNewPicture(acceptedFiles[0]); }, []); diff --git a/frontend/src/components/submit-form/image-list.tsx b/frontend/src/components/submit-form/image-list.tsx index 7aaf2b0..20344e8 100644 --- a/frontend/src/components/submit-form/image-list.tsx +++ b/frontend/src/components/submit-form/image-list.tsx @@ -1,73 +1,72 @@ -import { type FileWithPath } from "react-dropzone"; -import { DragDropContext, Draggable, Droppable, type DropResult } from "@hello-pangea/dnd"; -import { Icon } from "@iconify/react"; - -interface Props { - files: readonly FileWithPath[]; - setFiles: React.Dispatch>; -} - -export default function ImageList({ files, setFiles }: Props) { - const handleDelete = (index: number) => { - const newFiles = [...files]; - newFiles.splice(index, 1); - setFiles(newFiles); - }; - - const handleDragEnd = (result: DropResult) => { - if (!result.destination) return; - - const items = Array.from(files); - const [reorderedItem] = items.splice(result.source.index, 1); - items.splice(result.destination.index, 0, reorderedItem); - - setFiles(items); - }; - - return ( - - - {(provided) => ( -
- {files.map((file, index) => ( - - {(provided) => ( -
- {file.name} -
- {file.name} - -
- -
- -
-
- )} -
- ))} - {provided.placeholder} -
- )} -
-
- ); -} +import { DragDropContext, Draggable, Droppable, type DropResult } from "@hello-pangea/dnd"; +import { Icon } from "@iconify/react"; + +interface Props { + files: readonly File[]; + setFiles: React.Dispatch>; +} + +export default function ImageList({ files, setFiles }: Props) { + const handleDelete = (index: number) => { + const newFiles = [...files]; + newFiles.splice(index, 1); + setFiles(newFiles); + }; + + const handleDragEnd = (result: DropResult) => { + if (!result.destination) return; + + const items = Array.from(files); + const [reorderedItem] = items.splice(result.source.index, 1); + items.splice(result.destination.index, 0, reorderedItem); + + setFiles(items); + }; + + return ( + + + {(provided) => ( +
+ {files.map((file, index) => ( + + {(provided) => ( +
+ {file.name} +
+ {file.name} + +
+ +
+ +
+
+ )} +
+ ))} + {provided.placeholder} +
+ )} +
+
+ ); +} diff --git a/frontend/src/components/submit-form/qr-upload.tsx b/frontend/src/components/submit-form/qr-upload.tsx index 83293c9..05f5b17 100644 --- a/frontend/src/components/submit-form/qr-upload.tsx +++ b/frontend/src/components/submit-form/qr-upload.tsx @@ -1,67 +1,66 @@ -import { useCallback, useRef, useState } from "react"; -import { type FileWithPath } from "react-dropzone"; -import jsQR from "jsqr"; -import Dropzone from "../dropzone"; - -interface Props { - setQrBytesRaw: React.Dispatch>; -} - -export default function QrUpload({ setQrBytesRaw }: Props) { - const [hasImage, setHasImage] = useState(false); - const canvasRef = useRef(null); - - const handleDrop = useCallback( - (acceptedFiles: FileWithPath[]) => { - const file = acceptedFiles[0]; - - // Scan QR code - const reader = new FileReader(); - reader.onload = async (event) => { - const image = new Image(); - image.onload = () => { - const canvas = canvasRef.current; - if (!canvas) return; - - const ctx = canvas.getContext("2d"); - if (!ctx) return; - - canvas.width = image.width; - canvas.height = image.height; - ctx.drawImage(image, 0, 0, image.width, image.height); - - const imageData = ctx.getImageData(0, 0, image.width, image.height); - const code = jsQR(imageData.data, image.width, image.height); - if (!code) return; - - setQrBytesRaw(code.binaryData!); - setHasImage(true); - }; - image.src = event.target!.result as string; - }; - reader.readAsDataURL(file); - }, - [setQrBytesRaw], - ); - - return ( -
- -

- {!hasImage ? ( - <> - Drag and drop your QR code image here -
- or click to open - - ) : ( - "Uploaded!" - )} -

-
- - {/* Canvas is used to scan the QR code */} - -
- ); -} +import { useCallback, useRef, useState } from "react"; +import jsQR from "jsqr"; +import Dropzone from "../dropzone"; + +interface Props { + setQrBytesRaw: React.Dispatch>; +} + +export default function QrUpload({ setQrBytesRaw }: Props) { + const [hasImage, setHasImage] = useState(false); + const canvasRef = useRef(null); + + const handleDrop = useCallback( + (acceptedFiles: File[]) => { + const file = acceptedFiles[0]; + + // Scan QR code + const reader = new FileReader(); + reader.onload = async (event) => { + const image = new Image(); + image.onload = () => { + const canvas = canvasRef.current; + if (!canvas) return; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + canvas.width = image.width; + canvas.height = image.height; + ctx.drawImage(image, 0, 0, image.width, image.height); + + const imageData = ctx.getImageData(0, 0, image.width, image.height); + const code = jsQR(imageData.data, image.width, image.height); + if (!code) return; + + setQrBytesRaw(code.binaryData!); + setHasImage(true); + }; + image.src = event.target!.result as string; + }; + reader.readAsDataURL(file); + }, + [setQrBytesRaw], + ); + + return ( +
+ +

+ {!hasImage ? ( + <> + Drag and drop your QR code image here +
+ or click to open + + ) : ( + "Uploaded!" + )} +

+
+ + {/* Canvas is used to scan the QR code */} + +
+ ); +} diff --git a/frontend/src/components/submit-form/switch-file-upload.tsx b/frontend/src/components/submit-form/switch-file-upload.tsx index 986dc2e..b781b86 100644 --- a/frontend/src/components/submit-form/switch-file-upload.tsx +++ b/frontend/src/components/submit-form/switch-file-upload.tsx @@ -1,5 +1,4 @@ import { useCallback, useState } from "react"; -import { type FileWithPath } from "react-dropzone"; import { Icon } from "@iconify/react"; import Dropzone from "../dropzone"; import Camera from "./camera"; @@ -20,7 +19,7 @@ export default function SwitchFileUpload({ text, type = "image", forceCrop, file const [isCropOpen, setIsCropOpen] = useState(false); const handleDrop = useCallback( - (acceptedFiles: FileWithPath[]) => { + (acceptedFiles: File[]) => { const file = acceptedFiles[0]; if (type === "file") { setFile!(file); diff --git a/frontend/src/layout.tsx b/frontend/src/layout.tsx index ae1b068..1a4edb0 100644 --- a/frontend/src/layout.tsx +++ b/frontend/src/layout.tsx @@ -66,7 +66,7 @@ export default function Layout({ children }: { children: React.ReactNode }) { >
Support me (trafficlunar) on Ko-fi
- Support me! + Support TomodachiShare's costs!
{children}