mirror of
https://github.com/aljazceru/mutiny-web.git
synced 2026-01-31 12:04:23 +01:00
working qr scanner nice
This commit is contained in:
14
package-lock.json
generated
14
package-lock.json
generated
@@ -12,6 +12,7 @@
|
||||
"framer-motion": "^8.5.4",
|
||||
"nostr-react": "^0.6.4",
|
||||
"nostr-tools": "^1.3.2",
|
||||
"qr-scanner": "^1.4.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
@@ -2433,6 +2434,11 @@
|
||||
"integrity": "sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/offscreencanvas": {
|
||||
"version": "2019.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/offscreencanvas/-/offscreencanvas-2019.7.0.tgz",
|
||||
"integrity": "sha512-PGcyveRIpL1XIqK8eBsmRBt76eFgtzuPiSTyKHZxnGemp2yzGzWpjYKAfK3wIMiU7eH+851yEpiuP8JZerTmWg=="
|
||||
},
|
||||
"node_modules/@types/prop-types": {
|
||||
"version": "15.7.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz",
|
||||
@@ -4609,6 +4615,14 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/qr-scanner": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/qr-scanner/-/qr-scanner-1.4.2.tgz",
|
||||
"integrity": "sha512-kV1yQUe2FENvn59tMZW6mOVfpq9mGxGf8l6+EGaXUOd4RBOLg7tRC83OrirM5AtDvZRpdjdlXURsHreAOSPOUw==",
|
||||
"dependencies": {
|
||||
"@types/offscreencanvas": "^2019.6.4"
|
||||
}
|
||||
},
|
||||
"node_modules/queue-microtask": {
|
||||
"version": "1.2.3",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
"framer-motion": "^8.5.4",
|
||||
"nostr-react": "^0.6.4",
|
||||
"nostr-tools": "^1.3.2",
|
||||
"qr-scanner": "^1.4.2",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-linkify": "1.0.0-alpha",
|
||||
|
||||
@@ -4,6 +4,7 @@ import Join from "@/routes/Join";
|
||||
import Layout from "@/components/Layout";
|
||||
import SecretWaitlistSkipper from "@/routes/SecretWaitlistSkipper";
|
||||
import Home from "@/routes/Home";
|
||||
import Scanner from "@/routes/Scanner";
|
||||
|
||||
function App() {
|
||||
let active = localStorage.getItem('active') || "";
|
||||
@@ -13,10 +14,10 @@ function App() {
|
||||
{/* globals such as header will go here */}
|
||||
|
||||
<Routes>
|
||||
{/* <Route path="/" element={<Home />} /> */}
|
||||
<Route path="/" element={<Layout />}>
|
||||
<Route index element={active === "true" ? <Home /> : <Join />} />
|
||||
<Route path="secretwaitlistskipper" element={<SecretWaitlistSkipper />} />
|
||||
<Route path="scanner" element={<Scanner />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</div>
|
||||
|
||||
37
src/components/Reader.tsx
Normal file
37
src/components/Reader.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import QrScanner from 'qr-scanner';
|
||||
import { useEffect, useRef } from "react";
|
||||
|
||||
export default function Scanner({ onResult }: { onResult: (result: string) => void }) {
|
||||
const container = useRef<HTMLVideoElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let scanner: QrScanner | null;
|
||||
if (container.current) {
|
||||
scanner = new QrScanner(
|
||||
container.current,
|
||||
(result) => {
|
||||
onResult(result.data);
|
||||
},
|
||||
{
|
||||
returnDetailedScanResult: true,
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
scanner.start();
|
||||
}
|
||||
|
||||
return () => {
|
||||
scanner?.destroy();
|
||||
scanner = null;
|
||||
}
|
||||
}, [onResult]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div id="video-container">
|
||||
<video ref={container} className="w-full h-full fixed object-cover bg-gray"></video>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -3,12 +3,20 @@
|
||||
@tailwind utilities;
|
||||
|
||||
body {
|
||||
@apply text-white;
|
||||
@apply bg-fixed bg-no-repeat bg-gradient-to-b from-black to-[#0b215b] bg-black;
|
||||
@apply text-white bg-black;
|
||||
@apply bg-fixed bg-no-repeat bg-gradient-to-b from-black to-[#0b215b];
|
||||
overscroll-behavior-y: none;
|
||||
min-height: 100.3%;
|
||||
}
|
||||
|
||||
.bg-gradient {
|
||||
@apply bg-fixed bg-no-repeat bg-gradient-to-b from-black to-[#0b215b];
|
||||
}
|
||||
|
||||
.bg-gray {
|
||||
@apply bg-fixed bg-no-repeat bg-gradient-to-b from-[hsl(224,5%,5%)] to-[hsl(224,5%,20%)];
|
||||
}
|
||||
|
||||
.react-modal-sheet-container {
|
||||
@apply !bg-[#262626];
|
||||
}
|
||||
@@ -16,3 +24,19 @@ body {
|
||||
a {
|
||||
@apply underline decoration-light-text hover:decoration-white;
|
||||
}
|
||||
|
||||
#video-container {
|
||||
position: relative;
|
||||
width: max-content;
|
||||
height: max-content;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#video-container .scan-region-highlight {
|
||||
border-radius: 30px;
|
||||
outline: rgba(0, 0, 0, 0.25) solid 50vmax;
|
||||
}
|
||||
|
||||
#video-container .scan-region-highlight-svg {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import mutiny_m from '@/assets/m.svg';
|
||||
import scan from '@/assets/scan.svg';
|
||||
import settings from '@/assets/settings.svg';
|
||||
import send from '@/assets/send.svg';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
function ActivityItem() {
|
||||
return (
|
||||
@@ -89,17 +90,16 @@ function App() {
|
||||
<nav className='bg-black fixed bottom-0 shadow-lg z-40 w-full safe-bottom'>
|
||||
<ul className='h-16 flex justify-between px-16 items-center'>
|
||||
<li className='h-full border-t-2 border-b-2 border-b-black flex flex-col justify-center'>
|
||||
<img src={mutiny_m} className="App-logo" alt="logo" />
|
||||
<img src={mutiny_m} alt="home" />
|
||||
</li>
|
||||
<li>
|
||||
<img src={scan} className="App-logo" alt="logo" />
|
||||
<Link to="/scanner">
|
||||
<img src={scan} alt="scan" />
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<img src={settings} className="App-logo" alt="logo" />
|
||||
<img src={settings} alt="settings" />
|
||||
</li>
|
||||
{/* <li>home</li> */}
|
||||
{/* <li>scan</li> */}
|
||||
{/* <li>settings</li> */}
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
|
||||
48
src/routes/Scanner.tsx
Normal file
48
src/routes/Scanner.tsx
Normal file
@@ -0,0 +1,48 @@
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import button from "@/styles/button";
|
||||
|
||||
import { useState } from "react";
|
||||
import Reader from "@/components/Reader";
|
||||
|
||||
export default function Scanner() {
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [scanResult, setScanResult] = useState<string | null>(null);
|
||||
|
||||
function onResult(result: string) {
|
||||
setScanResult(result);
|
||||
}
|
||||
|
||||
function exit() {
|
||||
navigate("/")
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{scanResult ?
|
||||
<div className="w-full p-8">
|
||||
<div className="mt-[20vw] rounded-xl p-4 flex flex-col gap-2 bg-[rgba(0,0,0,0.5)]">
|
||||
<header className='text-sm font-semibold uppercase'>
|
||||
Scan Result
|
||||
</header>
|
||||
<code className="break-all">{scanResult}</code>
|
||||
</div>
|
||||
</div> : <Reader onResult={onResult} />
|
||||
}
|
||||
<div className="w-full flex flex-col fixed bottom-[2rem] gap-8 px-8">
|
||||
{!scanResult &&
|
||||
<>
|
||||
<button className={button({ intent: "blue" })} onClick={exit}>Paste Something</button>
|
||||
<button className={button()} onClick={exit}>Cancel</button>
|
||||
</>
|
||||
}
|
||||
{scanResult &&
|
||||
<>
|
||||
<button className={button({ intent: "red" })} onClick={() => setScanResult(null)}>Try Again</button>
|
||||
<button className={button()} onClick={exit}>Cancel</button>
|
||||
</>
|
||||
}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user