diff --git a/app/frontend/app/components/NavBar.tsx b/app/frontend/app/components/NavBar.tsx index fd3a32e..d7c9d7f 100644 --- a/app/frontend/app/components/NavBar.tsx +++ b/app/frontend/app/components/NavBar.tsx @@ -13,6 +13,7 @@ const NavBar: React.FC = ({ activeTab = 'Home', onTabChange }) => { { label: "Home", path: "/mainPage"}, { label: "New Session", path: "/new-session"}, { label: "Past Sessions", path: "/past-sessions"}, + {label: "Current Session", path:"new-session/current-session"}, {label: "User Profile", path: "/user-profile"}, ]; diff --git a/app/frontend/app/components/RecordView.tsx b/app/frontend/app/components/RecordView.tsx new file mode 100644 index 0000000..b7ed3a8 --- /dev/null +++ b/app/frontend/app/components/RecordView.tsx @@ -0,0 +1,17 @@ +import { ReactMediaRecorder } from "react-media-recorder"; + +const RecordView = () => ( +
+ ( +
+

{status}

+ + +
+ )} + /> +
+); \ No newline at end of file diff --git a/app/frontend/app/components/RecordingComponent.jsx b/app/frontend/app/components/RecordingComponent.jsx new file mode 100644 index 0000000..1681721 --- /dev/null +++ b/app/frontend/app/components/RecordingComponent.jsx @@ -0,0 +1,42 @@ + +import { useState } from 'react' +import { RecordPanelHost, useRecordPanel } from 'recordpanel' +import 'recordpanel/styles' + +function RecordingComponent() { + const [recording, setRecording] = useState(null) + const recorder = useRecordPanel() + + const handleCapture = async () => { + try { + const result = await recorder.capture({ + cameraEnabled: true, + audioEnabled: true + }) + + setRecording(result) + + // Download the recording + const a = document.createElement('a') + a.href = result.url + a.download = `recording-${Date.now()}.webm` + a.click() + + // Clean up + URL.revokeObjectURL(result.url) + } catch (error) { + console.error('Recording failed:', error) + } + } + + return ( +
+ + {recording && ( +
+ ) +} + +export default RecordingComponent; diff --git a/app/frontend/app/components/ScreenshotTool.jsx b/app/frontend/app/components/ScreenshotTool.jsx new file mode 100644 index 0000000..330f6b4 --- /dev/null +++ b/app/frontend/app/components/ScreenshotTool.jsx @@ -0,0 +1,25 @@ +import React, { useRef, useState } from 'react'; +import { useScreenshot } from 'use-react-screenshot'; + +const ScreenshotTool = () => { + const ref = useRef(null); + const [image, takeScreenshot] = useScreenshot(); + const getImage = () => takeScreenshot(ref.current); + + const width = 400; + return ( +
+
+ +
+ {'Screenshot'} +
+

+

+
+
+ ) +}; +export default ScreenshotTool; \ No newline at end of file diff --git a/app/frontend/app/components/VideoRecorder.jsx b/app/frontend/app/components/VideoRecorder.jsx new file mode 100644 index 0000000..98a94a7 --- /dev/null +++ b/app/frontend/app/components/VideoRecorder.jsx @@ -0,0 +1,96 @@ +import React, { useState, useRef } from 'react'; + +const VideoRecorder = () => { + const [recording, setRecording] = useState(false); + const [videoURL, setVideoURL] = useState(null); + + const mediaRecorderRef = useRef(null); + const liveVideoRef = useRef(null); + const chunksRef = useRef([]); + + const startRecording = async () => { + try { + // 1. Request audio and video access + const stream = await navigator.mediaDevices.getUserMedia({ + video: true, + audio: true + }); + + // 2. Show live preview to the user + if (liveVideoRef.current) { + liveVideoRef.current.srcObject = stream; + } + + // 3. Initialize MediaRecorder + const recorder = new MediaRecorder(stream, { mimeType: 'video/webm' }); + mediaRecorderRef.current = recorder; + chunksRef.current = []; + + // 4. Handle data chunks + recorder.ondataavailable = (e) => { + if (e.data.size > 0) { + chunksRef.current.push(e.data); + } + }; + + // 5. Handle recording stop + recorder.onstop = () => { + const completeBlob = new Blob(chunksRef.current, { type: 'video/webm' }); + const url = URL.createObjectURL(completeBlob); + setVideoURL(url); + + // Stop all tracks to turn off camera/mic hardware lights + stream.getTracks().forEach(track => track.stop()); + }; + + recorder.start(); + setRecording(true); + } catch (err) { + console.error("Error accessing media devices:", err); + alert("Please allow camera and microphone access."); + } + }; + + const stopRecording = () => { + mediaRecorderRef.current.stop(); + setRecording(false); + }; + + return ( +
+

Wellness Begins!

+ +
+ {/* Live Preview / Recorded Result */} +
+ +
+ {!recording ? ( + + ) : ( + + )} + + {videoURL && !recording && ( + + Download Recording + + )} +
+
+ ); +}; + +// Simple styles +const btnStyle = { padding: '10px 20px', cursor: 'pointer', backgroundColor: '#007bff', color: '#fff', border: 'none', borderRadius: '5px' }; +const linkStyle = { padding: '10px 20px', textDecoration: 'none', backgroundColor: '#28a745', color: '#fff', borderRadius: '5px' }; + +export default VideoRecorder; \ No newline at end of file diff --git a/app/frontend/app/routes.ts b/app/frontend/app/routes.ts index 7fccdaa..4d97ce4 100644 --- a/app/frontend/app/routes.ts +++ b/app/frontend/app/routes.ts @@ -7,6 +7,6 @@ export default [ route("login", "routes/login.tsx"), route("new-session", "routes/NewSessionContainer.tsx"), route("past-sessions", "routes/PastSessionsContainer.tsx"), - route("new-session/current-session", "routes/CurrentSessionContainer.tsx"), + route("mainPage/new-session/current-session", "routes/CurrentSessionContainer.tsx"), route("user-profile", "routes/UserProfileContainer.tsx") ] satisfies RouteConfig; diff --git a/app/frontend/app/routes/CurrentSessionContainer.tsx b/app/frontend/app/routes/CurrentSessionContainer.tsx index 782c992..63816c4 100644 --- a/app/frontend/app/routes/CurrentSessionContainer.tsx +++ b/app/frontend/app/routes/CurrentSessionContainer.tsx @@ -1,10 +1,18 @@ import React from 'react'; +import { RecordPanelHost, useRecordPanel } from 'recordpanel'; +import RecordingComponent from '../components/RecordingComponent'; +import VideoRecorder from '../components/VideoRecorder'; +import ScreenshotTool from '../components/ScreenshotTool.jsx'; const CurrentSessionContainer: React.FC = () => { + return (
-

Placeholder: Session in progress

+ +
+ ); } + export default CurrentSessionContainer; \ No newline at end of file diff --git a/app/frontend/package-lock.json b/app/frontend/package-lock.json index 8cb2850..e23722a 100644 --- a/app/frontend/package-lock.json +++ b/app/frontend/package-lock.json @@ -9,11 +9,15 @@ "@react-router/node": "7.12.0", "@react-router/serve": "7.12.0", "@supabase/supabase-js": "^2.90.1", + "html2canvas": "^1.4.1", "isbot": "^5.1.31", "react": "^19.2.3", "react-dom": "^19.2.3", + "react-media-recorder": "^1.7.2", "react-router": "7.12.0", - "react-router-dom": "^7.12.0" + "react-router-dom": "^7.12.0", + "recordpanel": "^1.0.0", + "use-react-screenshot": "^4.0.0" }, "devDependencies": { "@react-router/dev": "7.12.0", @@ -475,6 +479,15 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/runtime": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", + "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/template": { "version": "7.28.6", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", @@ -1021,6 +1034,39 @@ "integrity": "sha512-EMlH1e30yzmTpGLQjlFmaDAjyOeZhng1/XCd7DExR8PNAnG/G1tyruZxEoUe11ClnwGhGrtsdnyyUx1frSzjng==", "license": "MIT" }, + "node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", + "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@react-router/dev": { "version": "7.12.0", "resolved": "https://registry.npmjs.org/@react-router/dev/-/dev-7.12.0.tgz", @@ -2036,6 +2082,19 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/automation-events": { + "version": "7.1.15", + "resolved": "https://registry.npmjs.org/automation-events/-/automation-events-7.1.15.tgz", + "integrity": "sha512-NsHJlve3twcgs8IyP4iEYph7Fzpnh6klN7G5LahwvypakBjFbsiGHJxrqTmeHKREdu/Tx6oZboqNI0tD4MnFlA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.2.0" + } + }, "node_modules/babel-dead-code-elimination": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/babel-dead-code-elimination/-/babel-dead-code-elimination-1.0.12.tgz", @@ -2049,6 +2108,15 @@ "@babel/types": "^7.23.6" } }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/baseline-browser-mapping": { "version": "2.9.15", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.15.tgz", @@ -2116,6 +2184,18 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/broker-factory": { + "version": "3.1.13", + "resolved": "https://registry.npmjs.org/broker-factory/-/broker-factory-3.1.13.tgz", + "integrity": "sha512-H2VALe31mEtO/SRcNp4cUU5BAm1biwhc/JaF77AigUuni/1YT0FLCJfbUxwIEs9y6Kssjk2fmXgf+Y9ALvmKlw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "fast-unique-numbers": "^9.0.26", + "tslib": "^2.8.1", + "worker-factory": "^7.0.48" + } + }, "node_modules/browserslist": { "version": "4.28.1", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", @@ -2241,6 +2321,42 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/compilerr": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/compilerr/-/compilerr-10.0.2.tgz", + "integrity": "sha512-CFwUXxJ9OuWsSvnLSbefxi+GLsZ0YnuJh40ry5QdmZ1FWK59OG+QB8XSj6t7Kq+/c5DSS7en+cML6GlzHKH58A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "dashify": "^2.0.0", + "indefinite-article": "0.0.2", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.15.4" + } + }, "node_modules/compressible": { "version": "2.0.18", "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", @@ -2336,6 +2452,15 @@ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT" }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", @@ -2343,6 +2468,15 @@ "dev": true, "license": "MIT" }, + "node_modules/dashify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dashify/-/dashify-2.0.0.tgz", + "integrity": "sha512-hpA5C/YrPjucXypHPPc0oJ1l9Hf6wWbiOL7Ik42cxnsUOhWiCB/fylKbKqqJalW9FgkNQCw16YO8uW9Hs0Iy1A==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/debug": { "version": "4.4.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", @@ -2640,6 +2774,69 @@ "dev": true, "license": "MIT" }, + "node_modules/extendable-media-recorder": { + "version": "6.6.10", + "resolved": "https://registry.npmjs.org/extendable-media-recorder/-/extendable-media-recorder-6.6.10.tgz", + "integrity": "sha512-gnSmLqDFq40ZdbGfuarnMLNqYPLCPpPr0p21V+g67wG4Pv2oCc/ga8sfsZrEM5GywEi7FcpyRm3z99JWZ/0aPw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.18.9", + "media-encoder-host": "^8.0.76", + "multi-buffer-data-view": "^3.0.20", + "recorder-audio-worklet": "^5.1.26", + "standardized-audio-context": "^25.3.29", + "subscribable-things": "^2.1.6", + "tslib": "^2.4.0" + } + }, + "node_modules/extendable-media-recorder-wav-encoder": { + "version": "7.0.135", + "resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder/-/extendable-media-recorder-wav-encoder-7.0.135.tgz", + "integrity": "sha512-Nlox1+siSjhpf9zreNuGvWW7Nwy9/2OHFtYL8cTETFuae9UDnY6s19xE/jIZi5CleS+eA1EBfYUPy77RQSTGEA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "extendable-media-recorder-wav-encoder-broker": "^7.0.125", + "extendable-media-recorder-wav-encoder-worker": "^8.0.121", + "tslib": "^2.8.1" + } + }, + "node_modules/extendable-media-recorder-wav-encoder-broker": { + "version": "7.0.125", + "resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder-broker/-/extendable-media-recorder-wav-encoder-broker-7.0.125.tgz", + "integrity": "sha512-HVmznJvyG+eFZJRYLd9h3OF0oNNIGEmEHAP4IQ0Y5gwxJcmrFhVmGB4hLi1GT/jNM8aSoCxIePVkCX+5tuGvpA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "broker-factory": "^3.1.13", + "extendable-media-recorder-wav-encoder-worker": "^8.0.121", + "tslib": "^2.8.1" + } + }, + "node_modules/extendable-media-recorder-wav-encoder-worker": { + "version": "8.0.121", + "resolved": "https://registry.npmjs.org/extendable-media-recorder-wav-encoder-worker/-/extendable-media-recorder-wav-encoder-worker-8.0.121.tgz", + "integrity": "sha512-UBBgWkyE9fpCLDdrWdTZM56FkImAljpUuxr6+y9W6LHvY7XWhkZP+yO5uZUUquS5IpsBlY2uKOWpKwiLdo3FOg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "tslib": "^2.8.1", + "worker-factory": "^7.0.48" + } + }, + "node_modules/fast-unique-numbers": { + "version": "9.0.26", + "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-9.0.26.tgz", + "integrity": "sha512-3Mtq8p1zQinjGyWfKeuBunbuFoixG72AUkk4VvzbX4ykCW9Q4FzRaNyIlfQhUjnKw2ARVP+/CKnoyr6wfHftig==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "tslib": "^2.8.1" + }, + "engines": { + "node": ">=18.2.0" + } + }, "node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -2842,6 +3039,19 @@ "node": ">= 0.4" } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -2883,6 +3093,12 @@ "node": ">=0.10.0" } }, + "node_modules/indefinite-article": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/indefinite-article/-/indefinite-article-0.0.2.tgz", + "integrity": "sha512-Au/2XzRkvxq2J6w5uvSSbBKPZ5kzINx5F2wb0SF8xpRL8BP9Lav81TnRbfPp6p+SYjYxwaaLn4EUwI3/MmYKSw==", + "license": "MIT" + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -3228,6 +3444,15 @@ "yallist": "^3.0.2" } }, + "node_modules/lucide-react": { + "version": "0.554.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.554.0.tgz", + "integrity": "sha512-St+z29uthEJVx0Is7ellNkgTEhaeSoA42I7JjOCBCrc5X6LYMGSv0P/2uS5HDLTExP5tpiqRD2PyUEOS6s9UXA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -3247,6 +3472,43 @@ "node": ">= 0.4" } }, + "node_modules/media-encoder-host": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/media-encoder-host/-/media-encoder-host-8.1.0.tgz", + "integrity": "sha512-VwX3ex48ltl+K1ObGEq3IcZp/XqpNTWemd9brC9ovo89rYmCRKTZAp1FCyfAY86RdvSMrUs26lbo45DIDVyERg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4", + "media-encoder-host-broker": "^7.1.0", + "media-encoder-host-worker": "^9.2.0", + "tslib": "^2.6.2" + } + }, + "node_modules/media-encoder-host-broker": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/media-encoder-host-broker/-/media-encoder-host-broker-7.1.0.tgz", + "integrity": "sha512-Emu3f45Wbf6AoRJxfvZ8e5nh8fRVviBfkABgYNvVUsVBgJ7+l137gn324g/JmNVQhhVQ89fjmGT1kHIJ9JG5Nw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4", + "broker-factory": "^3.0.97", + "fast-unique-numbers": "^9.0.4", + "media-encoder-host-worker": "^9.2.0", + "tslib": "^2.6.2" + } + }, + "node_modules/media-encoder-host-worker": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/media-encoder-host-worker/-/media-encoder-host-worker-9.2.0.tgz", + "integrity": "sha512-LrJJgNBDZH2y1PYBLaiYQw9uFU5i3yPvDkDxdko+L3Z4qzhKq9+4eYxKDqlwO4EdOlaiggvMpkgZl3roOniz2A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.24.4", + "extendable-media-recorder-wav-encoder-broker": "^7.0.100", + "tslib": "^2.6.2", + "worker-factory": "^7.0.24" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -3365,6 +3627,19 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, + "node_modules/multi-buffer-data-view": { + "version": "3.0.24", + "resolved": "https://registry.npmjs.org/multi-buffer-data-view/-/multi-buffer-data-view-3.0.24.tgz", + "integrity": "sha512-jm7Ycplx37ExXyQmqhwl7zfQmAj81y5LLzVx0XyWea4omP9W/xJhLEHs/5b+WojGyYSRt8BHiXZVcYzu68Ma0Q==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.20.6", + "tslib": "^2.4.1" + }, + "engines": { + "node": ">=12.20.1" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -3625,6 +3900,16 @@ "react": "^19.2.3" } }, + "node_modules/react-media-recorder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/react-media-recorder/-/react-media-recorder-1.7.2.tgz", + "integrity": "sha512-qNn9VUe/bScN3IXWGbSgULbCnYvfamj8RDzjWa5jAtA8M8l9tKg3GujcCgwx2rLCq4rLw+k048LK8i3oPDx4hQ==", + "license": "MIT", + "dependencies": { + "extendable-media-recorder": "^6.6.5", + "extendable-media-recorder-wav-encoder": "^7.0.68" + } + }, "node_modules/react-refresh": { "version": "0.14.2", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", @@ -3700,6 +3985,74 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/recorder-audio-worklet": { + "version": "5.1.39", + "resolved": "https://registry.npmjs.org/recorder-audio-worklet/-/recorder-audio-worklet-5.1.39.tgz", + "integrity": "sha512-w/RazoBwZnkFnEPRsJYNThOHznLQC98/IzWRrutpJQVvCcL0nbLsVSLDaRrnrqVpRUI11VgiXRh30HaHiSdVhQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "broker-factory": "^3.0.75", + "fast-unique-numbers": "^7.0.2", + "recorder-audio-worklet-processor": "^4.2.21", + "standardized-audio-context": "^25.3.41", + "subscribable-things": "^2.1.14", + "tslib": "^2.5.0", + "worker-factory": "^6.0.76" + } + }, + "node_modules/recorder-audio-worklet-processor": { + "version": "4.2.21", + "resolved": "https://registry.npmjs.org/recorder-audio-worklet-processor/-/recorder-audio-worklet-processor-4.2.21.tgz", + "integrity": "sha512-oiiS2sp6eMxkvjt13yetSYUJvnAxBZk60mIxz0Vf/2lDWa/4svCyMLHIDzYKbHahkISd0UYyqLS9dI7xDlUOCA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "tslib": "^2.5.0" + } + }, + "node_modules/recorder-audio-worklet/node_modules/fast-unique-numbers": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/fast-unique-numbers/-/fast-unique-numbers-7.0.2.tgz", + "integrity": "sha512-xnqpsnu889bHbq5cbDMwCJ2BPf6kjFPMu+RHfqKvisRxeEbTOVxY5aW/ZNsZ/r8OlwatxmjdFEVQog2xAhLkvg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "tslib": "^2.5.0" + }, + "engines": { + "node": ">=14.15.4" + } + }, + "node_modules/recorder-audio-worklet/node_modules/worker-factory": { + "version": "6.0.76", + "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-6.0.76.tgz", + "integrity": "sha512-W1iBNPmE9p0asU4aFmYJYCnMxhkvk4qlKc660GlHxWgmflY64NxxTbmKqipu4K5p9LiKKPjqXfcQme6153BZEQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.21.0", + "compilerr": "^10.0.2", + "fast-unique-numbers": "^7.0.2", + "tslib": "^2.5.0" + } + }, + "node_modules/recordpanel": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/recordpanel/-/recordpanel-1.0.0.tgz", + "integrity": "sha512-Q0fQvh085pIcvYWQGMc/5Ch4vyaFGcKoAwKFIVLEJ8lGe9F9Ocn64k60o8EFGQIOlVV+b+dM/od1EvVQaLRlcw==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "^1.2.4", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "lucide-react": "^0.554.0", + "tailwind-merge": "^3.4.0" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, "node_modules/rollup": { "version": "4.55.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.1.tgz", @@ -3745,6 +4098,12 @@ "fsevents": "~2.3.2" } }, + "node_modules/rxjs-interop": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/rxjs-interop/-/rxjs-interop-2.0.0.tgz", + "integrity": "sha512-ASEq9atUw7lualXB+knvgtvwkCEvGWV2gDD/8qnASzBkzEARZck9JAyxmY8OS6Nc1pCPEgDTKNcx+YqqYfzArw==", + "license": "MIT" + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -3957,6 +4316,17 @@ "source-map": "^0.6.0" } }, + "node_modules/standardized-audio-context": { + "version": "25.3.77", + "resolved": "https://registry.npmjs.org/standardized-audio-context/-/standardized-audio-context-25.3.77.tgz", + "integrity": "sha512-Ki9zNz6pKcC5Pi+QPjPyVsD9GwJIJWgryji0XL9cAJXMGyn+dPOf6Qik1AHei0+UNVcc4BOCa0hWLBzlwqsW/A==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.6", + "automation-events": "^7.0.9", + "tslib": "^2.7.0" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -3966,6 +4336,27 @@ "node": ">= 0.8" } }, + "node_modules/subscribable-things": { + "version": "2.1.57", + "resolved": "https://registry.npmjs.org/subscribable-things/-/subscribable-things-2.1.57.tgz", + "integrity": "sha512-Ebcu2SJUntGnfJlTKc5jIGcDbuev4Ys2bRstzl5DUyzjWTZV9ymONZ0x8kEiN8NtnDlVuFe40EB8t3XvH8SWkw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "rxjs-interop": "^2.0.0", + "tslib": "^2.8.1" + } + }, + "node_modules/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", @@ -3987,6 +4378,15 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -4057,7 +4457,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, + "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -4113,6 +4513,20 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/use-react-screenshot": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/use-react-screenshot/-/use-react-screenshot-4.0.0.tgz", + "integrity": "sha512-4UZIORp7iCklfNOS/dPJab9SPeGdS0nFyIi3qA1rfMyYf/em/KfodYhrOlSHAHWvfdeCrS67Jjk6H4M4oLYSWg==", + "license": "MIT", + "engines": { + "node": ">=8", + "npm": ">=5" + }, + "peerDependencies": { + "html2canvas": "^1.3.3", + "react": "^18.2.0" + } + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -4122,6 +4536,15 @@ "node": ">= 0.4.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/valibot": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz", @@ -4271,6 +4694,17 @@ } } }, + "node_modules/worker-factory": { + "version": "7.0.48", + "resolved": "https://registry.npmjs.org/worker-factory/-/worker-factory-7.0.48.tgz", + "integrity": "sha512-CGmBy3tJvpBPjUvb0t4PrpKubUsfkI1Ohg0/GGFU2RvA9j/tiVYwKU8O7yu7gH06YtzbeJLzdUR29lmZKn5pag==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "fast-unique-numbers": "^9.0.26", + "tslib": "^2.8.1" + } + }, "node_modules/ws": { "version": "8.19.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz", diff --git a/app/frontend/package.json b/app/frontend/package.json index b9f0fdd..2d9960c 100644 --- a/app/frontend/package.json +++ b/app/frontend/package.json @@ -12,11 +12,15 @@ "@react-router/node": "7.12.0", "@react-router/serve": "7.12.0", "@supabase/supabase-js": "^2.90.1", + "html2canvas": "^1.4.1", "isbot": "^5.1.31", "react": "^19.2.3", "react-dom": "^19.2.3", + "react-media-recorder": "^1.7.2", "react-router": "7.12.0", - "react-router-dom": "^7.12.0" + "react-router-dom": "^7.12.0", + "recordpanel": "^1.0.0", + "use-react-screenshot": "^4.0.0" }, "devDependencies": { "@react-router/dev": "7.12.0", diff --git a/app/frontend/react-webcam b/app/frontend/react-webcam new file mode 160000 index 0000000..8cb6d71 --- /dev/null +++ b/app/frontend/react-webcam @@ -0,0 +1 @@ +Subproject commit 8cb6d71f970da8297896e1503aae58a2533c57a3