Merge branch 'feature/camera-scan' into develop

This commit is contained in:
Willian Mitsuda 2021-09-01 18:14:16 -03:00
commit eeeba456c6
7 changed files with 242 additions and 46 deletions

90
package-lock.json generated
View File

@ -9,6 +9,7 @@
"version": "0.1.0", "version": "0.1.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@blackbox-vision/react-qr-reader": "^5.0.0",
"@chainlink/contracts": "^0.2.1", "@chainlink/contracts": "^0.2.1",
"@craco/craco": "^6.2.0", "@craco/craco": "^6.2.0",
"@fontsource/fira-code": "^4.5.1", "@fontsource/fira-code": "^4.5.1",
@ -1209,6 +1210,19 @@
"version": "0.2.3", "version": "0.2.3",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@blackbox-vision/react-qr-reader": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@blackbox-vision/react-qr-reader/-/react-qr-reader-5.0.0.tgz",
"integrity": "sha512-VLNKwwJTv4UX1inUNgt2aGC2yIhKBYptW9EOhn7Nq//WzjD5KvHG7WR48HTzGUZ2s/EA0XlxZSfKOarHV1Vb/A==",
"dependencies": {
"@zxing/browser": "0.0.7",
"@zxing/library": "^0.18.3"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0",
"react-dom": "^16.8.0 || ^17.0.0"
}
},
"node_modules/@chainlink/contracts": { "node_modules/@chainlink/contracts": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/@chainlink/contracts/-/contracts-0.2.1.tgz", "resolved": "https://registry.npmjs.org/@chainlink/contracts/-/contracts-0.2.1.tgz",
@ -3512,6 +3526,37 @@
"resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz", "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz",
"integrity": "sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg==" "integrity": "sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg=="
}, },
"node_modules/@zxing/browser": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@zxing/browser/-/browser-0.0.7.tgz",
"integrity": "sha512-AepzMgDnD6EjxewqmXpHJsi4S3Gw9ilZJLIbTf6fWuWySEcHBodnGu3p7FWlgq1Sd5QyfPhTum5z3CBkkhMVng==",
"optionalDependencies": {
"@zxing/text-encoding": "^0.9.0"
},
"peerDependencies": {
"@zxing/library": "^0.18.3"
}
},
"node_modules/@zxing/library": {
"version": "0.18.6",
"resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.18.6.tgz",
"integrity": "sha512-bulZ9JHoLFd9W36pi+7e7DnEYNJhljYjZ1UTsKPOoLMU3qtC+REHITeCRNx40zTRJZx18W5TBRXt5pq2Uopjsw==",
"dependencies": {
"ts-custom-error": "^3.0.0"
},
"engines": {
"node": ">= 10.4.0"
},
"optionalDependencies": {
"@zxing/text-encoding": "~0.9.0"
}
},
"node_modules/@zxing/text-encoding": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz",
"integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==",
"optional": true
},
"node_modules/abab": { "node_modules/abab": {
"version": "2.0.5", "version": "2.0.5",
"license": "BSD-3-Clause" "license": "BSD-3-Clause"
@ -17825,6 +17870,14 @@
"version": "1.0.1", "version": "1.0.1",
"license": "MIT" "license": "MIT"
}, },
"node_modules/ts-custom-error": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.2.0.tgz",
"integrity": "sha512-cBvC2QjtvJ9JfWLvstVnI45Y46Y5dMxIaG1TDMGAD/R87hpvqFL+7LhvUDhnRCfOnx/xitollFWWvUKKKhbN0A==",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/ts-pnp": { "node_modules/ts-pnp": {
"version": "1.2.0", "version": "1.2.0",
"license": "MIT", "license": "MIT",
@ -20240,6 +20293,15 @@
"@bcoe/v8-coverage": { "@bcoe/v8-coverage": {
"version": "0.2.3" "version": "0.2.3"
}, },
"@blackbox-vision/react-qr-reader": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/@blackbox-vision/react-qr-reader/-/react-qr-reader-5.0.0.tgz",
"integrity": "sha512-VLNKwwJTv4UX1inUNgt2aGC2yIhKBYptW9EOhn7Nq//WzjD5KvHG7WR48HTzGUZ2s/EA0XlxZSfKOarHV1Vb/A==",
"requires": {
"@zxing/browser": "0.0.7",
"@zxing/library": "^0.18.3"
}
},
"@chainlink/contracts": { "@chainlink/contracts": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/@chainlink/contracts/-/contracts-0.2.1.tgz", "resolved": "https://registry.npmjs.org/@chainlink/contracts/-/contracts-0.2.1.tgz",
@ -21700,6 +21762,29 @@
"resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz", "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.6.0.tgz",
"integrity": "sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg==" "integrity": "sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg=="
}, },
"@zxing/browser": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/@zxing/browser/-/browser-0.0.7.tgz",
"integrity": "sha512-AepzMgDnD6EjxewqmXpHJsi4S3Gw9ilZJLIbTf6fWuWySEcHBodnGu3p7FWlgq1Sd5QyfPhTum5z3CBkkhMVng==",
"requires": {
"@zxing/text-encoding": "^0.9.0"
}
},
"@zxing/library": {
"version": "0.18.6",
"resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.18.6.tgz",
"integrity": "sha512-bulZ9JHoLFd9W36pi+7e7DnEYNJhljYjZ1UTsKPOoLMU3qtC+REHITeCRNx40zTRJZx18W5TBRXt5pq2Uopjsw==",
"requires": {
"@zxing/text-encoding": "~0.9.0",
"ts-custom-error": "^3.0.0"
}
},
"@zxing/text-encoding": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz",
"integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==",
"optional": true
},
"abab": { "abab": {
"version": "2.0.5" "version": "2.0.5"
}, },
@ -31387,6 +31472,11 @@
"tryer": { "tryer": {
"version": "1.0.1" "version": "1.0.1"
}, },
"ts-custom-error": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.2.0.tgz",
"integrity": "sha512-cBvC2QjtvJ9JfWLvstVnI45Y46Y5dMxIaG1TDMGAD/R87hpvqFL+7LhvUDhnRCfOnx/xitollFWWvUKKKhbN0A=="
},
"ts-pnp": { "ts-pnp": {
"version": "1.2.0" "version": "1.2.0"
}, },

View File

@ -4,6 +4,7 @@
"private": true, "private": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@blackbox-vision/react-qr-reader": "^5.0.0",
"@chainlink/contracts": "^0.2.1", "@chainlink/contracts": "^0.2.1",
"@craco/craco": "^6.2.0", "@craco/craco": "^6.2.0",
"@fontsource/fira-code": "^4.5.1", "@fontsource/fira-code": "^4.5.1",

View File

@ -3,7 +3,9 @@ import { NavLink, useHistory } from "react-router-dom";
import { commify } from "@ethersproject/units"; import { commify } from "@ethersproject/units";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faBurn } from "@fortawesome/free-solid-svg-icons/faBurn"; import { faBurn } from "@fortawesome/free-solid-svg-icons/faBurn";
import { faQrcode } from "@fortawesome/free-solid-svg-icons/faQrcode";
import Logo from "./Logo"; import Logo from "./Logo";
import CameraScanner from "./search/CameraScanner";
import Timestamp from "./components/Timestamp"; import Timestamp from "./components/Timestamp";
import { RuntimeContext } from "./useRuntime"; import { RuntimeContext } from "./useRuntime";
import { useLatestBlock } from "./useLatestBlock"; import { useLatestBlock } from "./useLatestBlock";
@ -30,11 +32,13 @@ const Home: React.FC = () => {
}; };
const latestBlock = useLatestBlock(provider); const latestBlock = useLatestBlock(provider);
const [isScanning, setScanning] = useState<boolean>(false);
document.title = "Home | Otterscan"; document.title = "Home | Otterscan";
return ( return (
<div className="m-auto"> <div className="m-auto">
{isScanning && <CameraScanner turnOffScan={() => setScanning(false)} />}
<Logo /> <Logo />
<form <form
className="flex flex-col" className="flex flex-col"
@ -42,16 +46,26 @@ const Home: React.FC = () => {
autoComplete="off" autoComplete="off"
spellCheck={false} spellCheck={false}
> >
<input <div className="flex mb-10">
className="w-full border rounded focus:outline-none px-2 py-1 mb-10" <input
type="text" className="w-full border-l border-t border-b rounded-l focus:outline-none px-2 py-1"
size={50} type="text"
placeholder="Search by address / txn hash / block number / ENS name" size={50}
onChange={handleChange} placeholder="Search by address / txn hash / block number / ENS name"
autoFocus onChange={handleChange}
></input> autoFocus
/>
<button
className="border rounded-r bg-skin-button-fill hover:bg-skin-button-hover-fill focus:outline-none px-2 py-1 text-base text-skin-button flex justify-center items-center"
type="button"
onClick={() => setScanning(true)}
title="Scan an ETH address using your camera"
>
<FontAwesomeIcon icon={faQrcode} />
</button>
</div>
<button <button
className="mx-auto px-3 py-1 mb-10 rounded bg-gray-100 hover:bg-gray-200 focus:outline-none" className="mx-auto px-3 py-1 mb-10 rounded bg-skin-button-fill hover:bg-skin-button-hover-fill focus:outline-none"
type="submit" type="submit"
> >
Search Search

View File

@ -1,7 +1,10 @@
import React, { useState, useRef, useContext } from "react"; import React, { useState, useRef, useContext } from "react";
import { Link, useHistory } from "react-router-dom"; import { Link, useHistory } from "react-router-dom";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faQrcode } from "@fortawesome/free-solid-svg-icons/faQrcode";
import useKeyboardShortcut from "use-keyboard-shortcut"; import useKeyboardShortcut from "use-keyboard-shortcut";
import PriceBox from "./PriceBox"; import PriceBox from "./PriceBox";
import CameraScanner from "./search/CameraScanner";
import { RuntimeContext } from "./useRuntime"; import { RuntimeContext } from "./useRuntime";
const Title: React.FC = () => { const Title: React.FC = () => {
@ -29,46 +32,59 @@ const Title: React.FC = () => {
searchRef.current?.focus(); searchRef.current?.focus();
}); });
const [isScanning, setScanning] = useState<boolean>(false);
return ( return (
<div className="px-9 py-2 flex justify-between items-baseline"> <>
<Link className="self-center" to="/"> {isScanning && <CameraScanner turnOffScan={() => setScanning(false)} />}
<div className="text-2xl text-link-blue font-title font-bold flex items-center space-x-2"> <div className="px-9 py-2 flex justify-between items-baseline">
<img <Link className="self-center" to="/">
className="rounded-full" <div className="text-2xl text-link-blue font-title font-bold flex items-center space-x-2">
src="/otter.jpg" <img
width={32} className="rounded-full"
height={32} src="/otter.jpg"
alt="An otter scanning" width={32}
title="An otter scanning" height={32}
/> alt="An otter scanning"
<span>Otterscan</span> title="An otter scanning"
</div> />
</Link> <span>Otterscan</span>
<div className="flex items-baseline space-x-3"> </div>
{provider?.network.chainId === 1 && <PriceBox />} </Link>
<form <div className="flex items-baseline space-x-3">
className="flex" {provider?.network.chainId === 1 && <PriceBox />}
onSubmit={handleSubmit} <form
autoComplete="off" className="flex"
spellCheck={false} onSubmit={handleSubmit}
> autoComplete="off"
<input spellCheck={false}
className="w-full border-t border-b border-l rounded-l focus:outline-none px-2 py-1 text-sm"
type="text"
size={60}
placeholder='Type "/" to search by address / txn hash / block number / ENS name'
onChange={handleChange}
ref={searchRef}
/>
<button
className="rounded-r border-t border-b border-r bg-gray-100 hover:bg-gray-200 focus:outline-none px-2 py-1 text-sm text-gray-500"
type="submit"
> >
Search <input
</button> className="w-full border-t border-b border-l rounded-l focus:outline-none px-2 py-1 text-sm"
</form> type="text"
size={60}
placeholder='Type "/" to search by address / txn hash / block number / ENS name'
onChange={handleChange}
ref={searchRef}
/>
<button
className="border bg-skin-button-fill hover:bg-skin-button-hover-fill focus:outline-none px-2 py-1 text-sm text-skin-button"
type="button"
onClick={() => setScanning(true)}
title="Scan an ETH address using your camera"
>
<FontAwesomeIcon icon={faQrcode} />
</button>
<button
className="rounded-r border-t border-b border-r bg-skin-button-fill hover:bg-skin-button-hover-fill focus:outline-none px-2 py-1 text-sm text-skin-button"
type="submit"
>
Search
</button>
</form>
</div>
</div> </div>
</div> </>
); );
}; };

View File

@ -4,6 +4,10 @@
@layer base { @layer base {
:root { :root {
--color-button-fill: 244, 244, 245; /* gray-100 */
--color-button-hover-fill: 228, 228, 231; /* gray-200 */
--color-button-text: 113, 113, 122; /* gray-500 */
--color-from-border: 254, 226, 226; --color-from-border: 254, 226, 226;
--color-from-text: 220, 38, 38; --color-from-text: 220, 38, 38;
--color-from-fill: 254, 242, 242; --color-from-fill: 254, 242, 242;
@ -11,4 +15,16 @@
--color-table-row-hover: 243, 244, 246; --color-table-row-hover: 243, 244, 246;
} }
.test-theme {
--color-button-fill: 14, 165, 233; /* sky-500 */
--color-button-hover-fill: 56, 189, 248; /* sky-400 */
--color-button-text: 186, 230, 253; /* sky-200 */
--color-from-border: 251, 146, 60;
--color-from-text: 249, 115, 22;
--color-from-fill: 254, 215, 170;
--color-to-fill: 125, 211, 252;
--color-table-row-hover: 2, 132, 199;
}
} }

View File

@ -0,0 +1,54 @@
import React from "react";
import { useHistory } from "react-router-dom";
import { isAddress } from "@ethersproject/address";
import { QrReader } from "@blackbox-vision/react-qr-reader";
import { OnResultFunction } from "@blackbox-vision/react-qr-reader/dist-types/types";
import { BarcodeFormat } from "@zxing/library";
import { Dialog } from "@headlessui/react";
type CameraScannerProps = {
turnOffScan: () => void;
};
const CameraScanner: React.FC<CameraScannerProps> = ({ turnOffScan }) => {
const history = useHistory();
const evaluateScan: OnResultFunction = (result, error, codeReader) => {
console.log("scan");
if (!error && result?.getBarcodeFormat() === BarcodeFormat.QR_CODE) {
const text = result.getText();
console.log(`Scanned: ${text}`);
if (!isAddress(text)) {
console.warn("Not an ETH address");
return;
}
history.push(`/search?q=${text}`);
turnOffScan();
}
};
return (
<Dialog
className="fixed z-10 inset-0 overflow-y-auto"
open={true}
onClose={turnOffScan}
>
<div className="flex items-center justify-center min-h-screen">
<Dialog.Overlay className="fixed inset-0 bg-black opacity-30" />
<Dialog.Title className="absolute top-0 w-full text-center bg-white text-lg">
Point an ETH address QR code to camera
</Dialog.Title>
<div className="absolute inset-0 bg-transparent rounded min-w-max max-w-3xl w-full h-screen max-h-screen m-auto">
<QrReader
className="m-auto"
constraints={{}}
onResult={evaluateScan}
/>
</div>
</div>
</Dialog>
);
};
export default CameraScanner;

View File

@ -35,11 +35,16 @@ module.exports = {
}, },
textColor: { textColor: {
skin: { skin: {
button: withOpacity("--color-button-text"),
from: withOpacity("--color-from-text"), from: withOpacity("--color-from-text"),
}, },
}, },
backgroundColor: { backgroundColor: {
skin: { skin: {
"button-fill": withOpacity("--color-button-fill"),
"button-hover-fill": withOpacity("--color-button-hover-fill"),
from: withOpacity("--color-from-fill"), from: withOpacity("--color-from-fill"),
to: withOpacity("--color-to-fill"), to: withOpacity("--color-to-fill"),
"table-hover": withOpacity("--color-table-row-hover"), "table-hover": withOpacity("--color-table-row-hover"),