diff --git a/.gitignore b/.gitignore
index c058940..e9e17a1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@
# next.js
/.next/
/out/
+certificates/
# production
/build
diff --git a/package.json b/package.json
index 3840a29..5299718 100644
--- a/package.json
+++ b/package.json
@@ -30,7 +30,6 @@
"react": "^19.2.4",
"react-dom": "^19.2.4",
"react-dropzone": "^14.3.8",
- "react-webcam": "^7.2.0",
"redis": "^5.10.0",
"satori": "^0.19.1",
"seedrandom": "^3.0.5",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4c6194a..c8dd795 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -62,9 +62,6 @@ importers:
react-dropzone:
specifier: ^14.3.8
version: 14.3.8(react@19.2.4)
- react-webcam:
- specifier: ^7.2.0
- version: 7.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
redis:
specifier: ^5.10.0
version: 5.10.0
@@ -2533,12 +2530,6 @@ packages:
redux:
optional: true
- react-webcam@7.2.0:
- resolution: {integrity: sha512-xkrzYPqa1ag2DP+2Q/kLKBmCIfEx49bVdgCCCcZf88oF+0NPEbkwYk3/s/C7Zy0mhM8k+hpdNkBLzxg8H0aWcg==}
- peerDependencies:
- react: '>=16.2.0'
- react-dom: '>=16.2.0'
-
react@19.2.4:
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
engines: {node: '>=0.10.0'}
@@ -5299,11 +5290,6 @@ snapshots:
'@types/react': 19.2.10
redux: 5.0.1
- react-webcam@7.2.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
- dependencies:
- react: 19.2.4
- react-dom: 19.2.4(react@19.2.4)
-
react@19.2.4: {}
readdirp@4.1.2: {}
diff --git a/src/app/globals.css b/src/app/globals.css
index ac3d5b8..6131603 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -43,15 +43,15 @@ body {
}
.button:disabled {
- @apply text-zinc-600 !bg-zinc-100 !border-zinc-300 cursor-auto;
+ @apply text-zinc-600 bg-zinc-100! border-zinc-300! cursor-auto;
}
.input {
- @apply !bg-orange-200 outline-0 focus:ring-[3px] ring-orange-400/50 transition placeholder:text-black/40;
+ @apply bg-orange-200! outline-0 focus:ring-[3px] ring-orange-400/50 transition placeholder:text-black/40;
}
.input:disabled {
- @apply text-zinc-600 !bg-zinc-100 !border-zinc-300;
+ @apply text-zinc-600 bg-zinc-100! border-zinc-300!;
}
.checkbox {
diff --git a/src/app/submit/page.tsx b/src/app/submit/page.tsx
index 0cd7a47..f2c63dd 100644
--- a/src/app/submit/page.tsx
+++ b/src/app/submit/page.tsx
@@ -32,8 +32,13 @@ export default async function SubmitPage() {
if (activePunishment) redirect("/off-the-island");
// Check if submissions are disabled
- const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/admin/can-submit`);
- const { value } = await response.json();
+ let value: boolean | null = true;
+ try {
+ const response = await fetch(`${process.env.NEXT_PUBLIC_BASE_URL}/api/admin/can-submit`);
+ value = await response.json();
+ } catch (error) {
+ return
An error occurred!
;
+ }
if (!value)
return (
diff --git a/src/components/mii-list/index.tsx b/src/components/mii-list/index.tsx
index dd8d185..f45bb7d 100644
--- a/src/components/mii-list/index.tsx
+++ b/src/components/mii-list/index.tsx
@@ -10,8 +10,6 @@ import { searchSchema } from "@/lib/schemas";
import { auth } from "@/lib/auth";
import { prisma } from "@/lib/prisma";
-import GenderSelect from "./gender-select";
-import TagFilter from "./tag-filter";
import SortSelect from "./sort-select";
import Carousel from "../carousel";
import LikeButton from "../like-button";
diff --git a/src/components/submit-form/qr-scanner.tsx b/src/components/submit-form/qr-scanner.tsx
index 103dc95..91f0d26 100644
--- a/src/components/submit-form/qr-scanner.tsx
+++ b/src/components/submit-form/qr-scanner.tsx
@@ -1,7 +1,6 @@
"use client";
import { useCallback, useEffect, useRef, useState } from "react";
-import Webcam from "react-webcam";
import jsQR from "jsqr";
import { Icon } from "@iconify/react";
@@ -22,7 +21,7 @@ export default function QrScanner({ isOpen, setIsOpen, setQrBytesRaw }: Props) {
const [devices, setDevices] = useState([]);
const [selectedDeviceId, setSelectedDeviceId] = useState(null);
- const webcamRef = useRef(null);
+ const videoRef = useRef(null);
const requestRef = useRef(null);
const canvasRef = useRef(null);
@@ -52,12 +51,9 @@ export default function QrScanner({ isOpen, setIsOpen, setQrBytesRaw }: Props) {
// Continue scanning in a loop
requestRef.current = requestAnimationFrame(scanQRCode);
- const webcam = webcamRef.current;
+ const video = videoRef.current;
const canvas = canvasRef.current;
- if (!webcam || !canvas) return;
-
- const video = webcam.video;
- if (!video || video.videoWidth === 0 || video.videoHeight === 0) return;
+ if (!video || video.videoWidth === 0 || video.videoHeight === 0 || !canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
@@ -68,7 +64,7 @@ export default function QrScanner({ isOpen, setIsOpen, setQrBytesRaw }: Props) {
const imageData = ctx.getImageData(0, 0, video.videoWidth, video.videoHeight);
const code = jsQR(imageData.data, imageData.width, imageData.height);
- if (!code) return;
+ if (!code || !code.binaryData) return;
// Cancel animation frame to stop scanning
if (requestRef.current) {
@@ -76,15 +72,20 @@ export default function QrScanner({ isOpen, setIsOpen, setQrBytesRaw }: Props) {
requestRef.current = null;
}
- setQrBytesRaw(code.binaryData!);
+ setQrBytesRaw(code.binaryData);
setIsOpen(false);
}, [isOpen, setIsOpen, setQrBytesRaw]);
- const requestPermission = async () => {
+ const requestPermission = () => {
+ if (!navigator.mediaDevices) return;
+
navigator.mediaDevices
- .getUserMedia({ video: true })
+ .getUserMedia({ video: true, audio: false })
.then(() => setPermissionGranted(true))
- .catch(() => setPermissionGranted(false));
+ .catch((err) => {
+ setPermissionGranted(false);
+ console.error("An error occurred trying to access the camera", err);
+ });
};
const close = () => {
@@ -98,34 +99,50 @@ export default function QrScanner({ isOpen, setIsOpen, setQrBytesRaw }: Props) {
if (isOpen) {
// slight delay to trigger animation
setTimeout(() => setIsVisible(true), 10);
+ requestPermission();
}
}, [isOpen]);
- useEffect(() => {
- if (!isOpen) return;
- requestPermission();
-
- if (!navigator.mediaDevices.enumerateDevices) return;
- navigator.mediaDevices.enumerateDevices().then((devices) => {
- const videoDevices = devices.filter((d) => d.kind === "videoinput");
- setDevices(videoDevices);
- if (!selectedDeviceId && videoDevices.length > 0) {
- setSelectedDeviceId(videoDevices[0].deviceId);
- }
- });
- }, [isOpen, selectedDeviceId]);
-
useEffect(() => {
if (!isOpen || !permissionGranted) return;
+ navigator.mediaDevices
+ .enumerateDevices()
+ .then((devices) => {
+ const videoDevices = devices.filter((d) => d.kind === "videoinput");
+ setDevices(videoDevices);
+
+ const targetDeviceId = selectedDeviceId || videoDevices[0]?.deviceId;
+ if (!targetDeviceId) return;
+ setSelectedDeviceId(targetDeviceId);
+
+ // start camera stream
+ return navigator.mediaDevices.getUserMedia({
+ video: { deviceId: targetDeviceId },
+ audio: false,
+ });
+ })
+ .then((stream) => {
+ if (!stream || !videoRef.current) return;
+ videoRef.current.srcObject = stream;
+ videoRef.current.play();
+ })
+ .catch((err) => console.error("Camera error", err));
+
requestRef.current = requestAnimationFrame(scanQRCode);
+ // cleanup
return () => {
if (requestRef.current) {
cancelAnimationFrame(requestRef.current);
}
+ if (videoRef.current?.srcObject) {
+ const stream = videoRef.current.srcObject as MediaStream;
+ stream.getTracks().forEach((track) => track.stop());
+ videoRef.current.srcObject = null;
+ }
};
- }, [isOpen, permissionGranted, scanQRCode]);
+ }, [isOpen, permissionGranted, selectedDeviceId, scanQRCode]);
if (!isOpen) return null;
@@ -133,9 +150,7 @@ export default function QrScanner({ isOpen, setIsOpen, setQrBytesRaw }: Props) {
- {!permissionGranted ? (
-
+ {!permissionGranted && (
+
Camera access denied
Please allow camera access in your browser settings to scan QR codes
- ) : (
- <>
-
{
- const newDevices = await navigator.mediaDevices.enumerateDevices();
- const videoDevices = newDevices.filter((d) => d.kind === "videoinput");
- setDevices(videoDevices);
- }}
- className="size-full object-cover rounded-2xl border-2 border-amber-500"
- />
-
-
- >
)}
+
+
+
+