From b3258d574630c178051eecfb51cf35dbab915cef Mon Sep 17 00:00:00 2001 From: George Oastler Date: Thu, 13 Oct 2022 12:32:48 +0100 Subject: [PATCH 01/37] added modal + props --- package.json | 6 +-- src/components/CaptchaComponent.tsx | 76 ++++++++++++++++------------- 2 files changed, 45 insertions(+), 37 deletions(-) diff --git a/package.json b/package.json index 5a07582..e1ae9de 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@prosopo/procaptcha-react", - "version": "0.1.8", + "version": "0.1.9", "author": "PROSOPO LIMITED ", "license": "Apache-2.0", "main": "./dist/index.js", @@ -15,8 +15,8 @@ "dependencies": { "@mui/material": "^5.8.3", "@mui/styles": "^5.8.3", - "@prosopo/i18n": "0.0.1", - "@prosopo/procaptcha": "0.1.8", + "@prosopo/i18n": "^0.0.1", + "@prosopo/procaptcha": "^0.1.8", "react": "^17.0.0", "react-dom": "^17.0.0" }, diff --git a/src/components/CaptchaComponent.tsx b/src/components/CaptchaComponent.tsx index 3f47c6e..ac3008b 100644 --- a/src/components/CaptchaComponent.tsx +++ b/src/components/CaptchaComponent.tsx @@ -30,15 +30,20 @@ import { CaptchaWidget } from "./CaptchaWidget"; import { useTranslation } from "@prosopo/i18n"; import { useStyles } from "../styles"; import { addDataAttr } from "../util"; +import { Alert } from "@mui/material"; -export function CaptchaComponent({ clientInterface }: { clientInterface: ProsopoCaptchaClient }) { +export function CaptchaComponent({ clientInterface, show = false }: { clientInterface: ProsopoCaptchaClient, show: boolean }) { const { t } = useTranslation(); const classes = useStyles(); const manager: ICaptchaContextReducer = useContext(CaptchaContextManager); - const [state, update] = useReducer(captchaStateReducer, { captchaIndex: 0, captchaSolution: [] }); + // the captcha state + update func + const [state, update] = useReducer(captchaStateReducer, { + captchaIndex: 0, // the index of the captcha we're on (1 captcha challenge contains >=1 captcha) + captchaSolution: [], // the solutions for the captcha (2d array corresponding to captcha) + }); const { account, contractAddress } = manager.state; const { captchaChallenge, captchaIndex, captchaSolution } = state; const totalCaptchas = captchaChallenge?.captchas.length ?? 0; @@ -76,42 +81,45 @@ export function CaptchaComponent({ clientInterface }: { clientInterface: Prosopo return ( - {account && captchaChallenge && - - - - - {t("WIDGET.SELECT_ALL", { target: captchaChallenge.captchas[captchaIndex].captcha.target })} - - - - + + + {!(show && captchaChallenge) + // no captcha challenge has been setup yet, render an alert + ? No captcha challenge active. + // else captcha challenge has been populated, render the challenge + : <> + + + {t("WIDGET.SELECT_ALL", { target: captchaChallenge.captchas[captchaIndex].captcha.target })} + + - + + + + {captchaChallenge?.captchas.map((_, index) => + )} + - - {captchaChallenge?.captchas.map((_, index) => - )} + + + + + + } - - - - - - - - - } + + ); } From 826e34204f6eaa1ea5ac59112ab24a1323c91614 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Thu, 13 Oct 2022 14:58:32 +0100 Subject: [PATCH 02/37] put captcha into a model and adjusted styling --- src/components/CaptchaComponent.tsx | 78 +++++++++++++++-------------- src/styles.ts | 14 +++--- 2 files changed, 47 insertions(+), 45 deletions(-) diff --git a/src/components/CaptchaComponent.tsx b/src/components/CaptchaComponent.tsx index ac3008b..10fcbe7 100644 --- a/src/components/CaptchaComponent.tsx +++ b/src/components/CaptchaComponent.tsx @@ -30,7 +30,7 @@ import { CaptchaWidget } from "./CaptchaWidget"; import { useTranslation } from "@prosopo/i18n"; import { useStyles } from "../styles"; import { addDataAttr } from "../util"; -import { Alert } from "@mui/material"; +import { Alert, Modal } from "@mui/material"; export function CaptchaComponent({ clientInterface, show = false }: { clientInterface: ProsopoCaptchaClient, show: boolean }) { @@ -79,48 +79,50 @@ export function CaptchaComponent({ clientInterface, show = false }: { clientInte // https://www.npmjs.com/package/i18next return ( - - - - - {!(show && captchaChallenge) - // no captcha challenge has been setup yet, render an alert - ? No captcha challenge active. - // else captcha challenge has been populated, render the challenge - : <> - - - {t("WIDGET.SELECT_ALL", { target: captchaChallenge.captchas[captchaIndex].captcha.target })} - - - - - - - {captchaChallenge?.captchas.map((_, index) => - )} + + + + + + {!(captchaChallenge) + // no captcha challenge has been setup yet, render an alert + ? No captcha challenge active. + // else captcha challenge has been populated, render the challenge + : <> + + + {t("WIDGET.SELECT_ALL", { target: captchaChallenge.captchas[captchaIndex].captcha.target })} + - - - - - - - } + + + + {captchaChallenge?.captchas.map((_, index) => + )} + + + + + + + + } + - + + ); } diff --git a/src/styles.ts b/src/styles.ts index 63c9c95..eb108e4 100644 --- a/src/styles.ts +++ b/src/styles.ts @@ -36,15 +36,14 @@ export const useStyles = makeStyles({ display: "flex", flexDirection: "column", background: "#FFFFFF", - border: "1px solid #CFCFCF", - boxShadow: "0px 2px 4px rgba(0, 0, 0, 0.2)" }, captchasHeader: { display: "flex", alignItems: "center", - backgroundColor: "#bdbdbd", - height: 80, - paddingLeft: 20 + backgroundColor: "#1976d2", + minHeight: 80, + padding: 20, + width: 460, }, captchasBody: { display: "flex", @@ -71,10 +70,11 @@ export const useStyles = makeStyles({ marginBottom: 10 }, captchaItemSelected: { - border: "2px solid #1976d2" + border: "4px solid #1976d2" }, captchasHeaderLabel: { - color: "#ffffff" + color: "#ffffff", + fontWeight: 700 }, dotsContainer: { display: "flex", From a8a3491e1c745509efa6151973b974248fd103eb Mon Sep 17 00:00:00 2001 From: George Oastler Date: Wed, 19 Oct 2022 15:23:07 +0100 Subject: [PATCH 03/37] added modal and icon overlay for selected imgs --- src/components/CaptchaWidget.tsx | 33 +++++++++---- src/styles.ts | 80 +++++++++++++++++++++++++++++++- 2 files changed, 101 insertions(+), 12 deletions(-) diff --git a/src/components/CaptchaWidget.tsx b/src/components/CaptchaWidget.tsx index 10586b4..fc52368 100644 --- a/src/components/CaptchaWidget.tsx +++ b/src/components/CaptchaWidget.tsx @@ -18,26 +18,39 @@ import { CaptchaResponseCaptcha } from "@prosopo/procaptcha"; import { useStyles } from "../styles"; import { addDataAttr } from "../util"; +import CheckBoxIcon from '@mui/icons-material/CheckBox'; +import { Badge, Box, ImageList, ImageListItem } from "@mui/material"; +import check from './check.svg'; export function CaptchaWidget({ challenge, solution, onChange }: {challenge: CaptchaResponseCaptcha, solution: string[], onChange: (hash: string) => void}) { - //const items = Array.from(Array(9).keys()); console.log("CHALLENGE", challenge); const items = challenge.captcha.items; - const classes = useStyles(); + const styles = useStyles(); return ( <> - {items.map((item, index) => onChange(item.hash ? item.hash : '')} /> - )} +
+ {items.map((item, index) => { + const selectedClass = solution.includes(item.hash ? item.hash : '') ? styles.itemSelected : styles.itemUnselected; + return ( +
onChange(item.hash ? item.hash : '')}> + {`Captcha +
+
+ +
+
+
+ ) + })} +
); } diff --git a/src/styles.ts b/src/styles.ts index eb108e4..3186822 100644 --- a/src/styles.ts +++ b/src/styles.ts @@ -25,6 +25,81 @@ const dot = { } export const useStyles = makeStyles({ + imageGrid: { + // expand to full height / width of parent + width: "100%", + height: "100%", + // display children in flex, spreading them evenly and wrapping when row length exceeded + display: "flex", + flexDirection: "row", + flexWrap: "wrap", + // the grid adds padding on right and bottom where items finish + paddingRight: "4px", + paddingBottom: "4px", + }, + itemContainer: { + // enable the items in the grid to grow in width to use up excess space + flexGrow: 1, + // default with is 30%. This leaves 10% (minus margins) to be spread between items + flexBasis: "30%", + // each item in the grid has padding on left and top + paddingTop: "4px", + paddingLeft: "4px", + }, + itemImage: { + width: "100%", // image should be full width / height of the item + backgroundColor: "black", // colour of the bands when letterboxing and image + display: "block", // removes whitespace below imgs + objectFit: "contain", // contain the entire image in the img tag + aspectRatio: "1/1", // force AR to be 1, letterboxing images with different aspect ratios + }, + itemOverlayContainer: { + // relative to where the element _should_ be positioned + position: "relative", + // make the overlay the full height/width of an item + width: "100%", + height: "100%", + // shift it up 100% to overlay the item element + top: "-100%", + // transition on opacity upon (de)selection + transitionDuration: "300ms", + transitionProperty: "opacity", + }, + itemOverlay: { + // make the overlay absolute positioned compare to its container + position: "absolute", + // spread across 100% width/height of the item box + top: 0, + left: 0, + bottom: 0, + right: 0, + height: "100%", + width: "100%", + // display overlays in center + display: "flex", + alignItems: "center", + justifyContent: "center", + // make bg half opacity, i.e. shadowing the item's img + backgroundColor: "rgba(0,0,0,0.5)", + }, + itemOverlayImage: { + // img must be displayed as block otherwise get's a bottom whitespace border + display: "block", + // how big the overlay icon is + width: "35%", + height: "35%", + }, + itemSelected: { + // when items are selected, make the overlay opacity 1, i.e. show the overlay + opacity: 1, + }, + itemUnselected: { + // when items are unselected, make the overlay opacity 0, i.e. hide the overlay + opacity: 0, + }, + + + root: { display: "flex", alignItems: "center", @@ -33,6 +108,7 @@ export const useStyles = makeStyles({ height: "100%" }, captchasContainer: { + maxWidth: "450px", display: "flex", flexDirection: "column", background: "#FFFFFF", @@ -50,8 +126,8 @@ export const useStyles = makeStyles({ width: 460, flexWrap: "wrap", height: "max-content", - paddingTop: 10, - paddingLeft: 10, + // paddingTop: 10, + // paddingLeft: 10, borderBottom: "1px solid #CFCFCF" }, captchasFooter: { From a95286e7d47a9fb2d27d7542b8538512873858f1 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Wed, 19 Oct 2022 15:34:21 +0100 Subject: [PATCH 04/37] fix header overflow --- src/styles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/styles.ts b/src/styles.ts index 3186822..c754682 100644 --- a/src/styles.ts +++ b/src/styles.ts @@ -119,7 +119,7 @@ export const useStyles = makeStyles({ backgroundColor: "#1976d2", minHeight: 80, padding: 20, - width: 460, + width: "100%", }, captchasBody: { display: "flex", From 824563857d99fed761ec0c785ae22f953f347fbf Mon Sep 17 00:00:00 2001 From: George Oastler Date: Wed, 19 Oct 2022 16:15:51 +0100 Subject: [PATCH 05/37] added reset captcha challenge functionality + altered layout to specify max- and min-width for responsive layout --- src/components/CaptchaComponent.tsx | 94 ++++++++++++++++++----------- 1 file changed, 58 insertions(+), 36 deletions(-) diff --git a/src/components/CaptchaComponent.tsx b/src/components/CaptchaComponent.tsx index 10fcbe7..52eabbc 100644 --- a/src/components/CaptchaComponent.tsx +++ b/src/components/CaptchaComponent.tsx @@ -26,7 +26,6 @@ import { import { CaptchaContextManager } from "./CaptchaManager"; import { CaptchaWidget } from "./CaptchaWidget"; - import { useTranslation } from "@prosopo/i18n"; import { useStyles } from "../styles"; import { addDataAttr } from "../util"; @@ -52,7 +51,6 @@ export function CaptchaComponent({ clientInterface, show = false }: { clientInte useEffect(() => { clientInterface.onLoad(manager.state.config['web2']); - }, []); useEffect(() => { @@ -75,6 +73,14 @@ export function CaptchaComponent({ clientInterface, show = false }: { clientInte } }, [account]); + const resetState = () => { + update({ + captchaIndex: 0, // the index of the captcha we're on (1 captcha challenge contains >=1 captcha) + captchaSolution: [], // the solutions for the captcha (2d array corresponding to captcha) + captchaChallenge: undefined, + }) + }; + // https://www.npmjs.com/package/i18next @@ -82,43 +88,59 @@ export function CaptchaComponent({ clientInterface, show = false }: { clientInte - - - {!(captchaChallenge) - // no captcha challenge has been setup yet, render an alert - ? No captcha challenge active. - // else captcha challenge has been populated, render the challenge - : <> - - - {t("WIDGET.SELECT_ALL", { target: captchaChallenge.captchas[captchaIndex].captcha.target })} - - - - - - - {captchaChallenge?.captchas.map((_, index) => - )} + + + + {!(captchaChallenge) + // no captcha challenge has been setup yet, render an alert + ? No captcha challenge active. + // else captcha challenge has been populated, render the challenge + : <> + + + {t("WIDGET.SELECT_ALL", { target: captchaChallenge.captchas[captchaIndex].captcha.target })} + - - - - - - - } + + + + {captchaChallenge?.captchas.map((_, index) => + )} + + + + + + + + + } + From 7ef0bad93aac474e1f4bcdd61cc91425d1acf996 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Wed, 19 Oct 2022 17:47:42 +0100 Subject: [PATCH 06/37] x and y overflow handling + simplified css --- src/styles.ts | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/src/styles.ts b/src/styles.ts index c754682..a3c4002 100644 --- a/src/styles.ts +++ b/src/styles.ts @@ -40,11 +40,13 @@ export const useStyles = makeStyles({ itemContainer: { // enable the items in the grid to grow in width to use up excess space flexGrow: 1, - // default with is 30%. This leaves 10% (minus margins) to be spread between items - flexBasis: "30%", + // make the width of each item 1/3rd of the width overall, i.e. 3 columns + flexBasis: "33.3333%", // each item in the grid has padding on left and top paddingTop: "4px", paddingLeft: "4px", + // include the padding / margin / border in the width + boxSizing: "border-box", }, itemImage: { width: "100%", // image should be full width / height of the item @@ -105,13 +107,20 @@ export const useStyles = makeStyles({ alignItems: "center", justifyContent: "center", width: "100%", - height: "100%" + height: "100%", }, captchasContainer: { - maxWidth: "450px", display: "flex", flexDirection: "column", background: "#FFFFFF", + minWidth: "300px", + }, + overflowContainer: { + overflowX: "auto", + overflowY: "auto", + width: "100%", + maxWidth: "450px", + maxHeight: "100%", }, captchasHeader: { display: "flex", @@ -122,13 +131,6 @@ export const useStyles = makeStyles({ width: "100%", }, captchasBody: { - display: "flex", - width: 460, - flexWrap: "wrap", - height: "max-content", - // paddingTop: 10, - // paddingLeft: 10, - borderBottom: "1px solid #CFCFCF" }, captchasFooter: { display: "flex", @@ -138,16 +140,6 @@ export const useStyles = makeStyles({ paddingLeft: 20, paddingRight: 20 }, - captchaItem: { - width: "140px !important", - borderRadius: 2, - height: "140px !important", - marginRight: 10, - marginBottom: 10 - }, - captchaItemSelected: { - border: "4px solid #1976d2" - }, captchasHeaderLabel: { color: "#ffffff", fontWeight: 700 From 1fefbfff38c0cf4434dead15ca8934a7f10059ef Mon Sep 17 00:00:00 2001 From: George Oastler Date: Fri, 21 Oct 2022 15:58:08 +0100 Subject: [PATCH 07/37] added theming --- src/components/CaptchaComponent.tsx | 112 ++++++++++++++-------------- src/components/CaptchaWidget.tsx | 40 +++++----- src/components/theme.ts | 13 ++++ 3 files changed, 92 insertions(+), 73 deletions(-) create mode 100644 src/components/theme.ts diff --git a/src/components/CaptchaComponent.tsx b/src/components/CaptchaComponent.tsx index 52eabbc..0e42cef 100644 --- a/src/components/CaptchaComponent.tsx +++ b/src/components/CaptchaComponent.tsx @@ -30,12 +30,15 @@ import { useTranslation } from "@prosopo/i18n"; import { useStyles } from "../styles"; import { addDataAttr } from "../util"; import { Alert, Modal } from "@mui/material"; +import ThemeProvider from "@mui/material/styles/ThemeProvider"; +import rootTheme from "./theme"; export function CaptchaComponent({ clientInterface, show = false }: { clientInterface: ProsopoCaptchaClient, show: boolean }) { const { t } = useTranslation(); const classes = useStyles(); + const theme = rootTheme; const manager: ICaptchaContextReducer = useContext(CaptchaContextManager); // the captcha state + update func @@ -85,66 +88,67 @@ export function CaptchaComponent({ clientInterface, show = false }: { clientInte // https://www.npmjs.com/package/i18next return ( - - - - - - - {!(captchaChallenge) - // no captcha challenge has been setup yet, render an alert - ? No captcha challenge active. - // else captcha challenge has been populated, render the challenge - : <> - - - {t("WIDGET.SELECT_ALL", { target: captchaChallenge.captchas[captchaIndex].captcha.target })} - - - - - - + + + + + + + + {!(captchaChallenge) + // no captcha challenge has been setup yet, render an alert + ? No captcha challenge active. + // else captcha challenge has been populated, render the challenge + : <> + + + {t("WIDGET.SELECT_ALL", { target: captchaChallenge.captchas[captchaIndex].captcha.target })} + + + + + + + {captchaChallenge?.captchas.map((_, index) => )} + + + + + + } - - - - - - - } - + - - - + + + ); } diff --git a/src/components/CaptchaWidget.tsx b/src/components/CaptchaWidget.tsx index fc52368..3f6e883 100644 --- a/src/components/CaptchaWidget.tsx +++ b/src/components/CaptchaWidget.tsx @@ -18,10 +18,9 @@ import { CaptchaResponseCaptcha } from "@prosopo/procaptcha"; import { useStyles } from "../styles"; import { addDataAttr } from "../util"; -import CheckBoxIcon from '@mui/icons-material/CheckBox'; -import { Badge, Box, ImageList, ImageListItem } from "@mui/material"; -import check from './check.svg'; - +import CheckIcon from '@mui/icons-material/Check'; +import { Box, Fade, Theme } from "@mui/material"; +import useTheme from "@mui/styles/useTheme"; export function CaptchaWidget({ challenge, solution, onChange }: {challenge: CaptchaResponseCaptcha, solution: string[], onChange: (hash: string) => void}) { @@ -29,28 +28,31 @@ export function CaptchaWidget({ challenge, solution, onChange }: console.log("CHALLENGE", challenge); const items = challenge.captcha.items; const styles = useStyles(); + const theme: Theme = useTheme(); return ( <> -
+ {items.map((item, index) => { - const selectedClass = solution.includes(item.hash ? item.hash : '') ? styles.itemSelected : styles.itemUnselected; return ( -
onChange(item.hash ? item.hash : '')}> - {`Captcha -
-
- -
-
-
+ onChange(item.hash ? item.hash : '')}> + + {`Captcha + + + + + + + + + ) })} -
+
); } diff --git a/src/components/theme.ts b/src/components/theme.ts new file mode 100644 index 0000000..a1dd661 --- /dev/null +++ b/src/components/theme.ts @@ -0,0 +1,13 @@ +import { orange, pink } from "@mui/material/colors"; +import red from "@mui/material/colors/red"; +import createTheme from "@mui/material/styles/createTheme"; + +export default createTheme({ + palette: { + primary: { + main: '#1976d2', + contrastText: "#fff" + }, + secondary: orange, + }, +}) \ No newline at end of file From 237d22731373a95ea6780d1a228e80574f5edc6a Mon Sep 17 00:00:00 2001 From: George Oastler Date: Fri, 21 Oct 2022 16:24:46 +0100 Subject: [PATCH 08/37] converted styles to mui v5 sx and theme --- src/components/CaptchaComponent.tsx | 71 +++++++++--- src/components/CaptchaWidget.tsx | 76 ++++++++++--- src/components/theme.ts | 3 +- src/styles.ts | 160 ---------------------------- 4 files changed, 120 insertions(+), 190 deletions(-) delete mode 100644 src/styles.ts diff --git a/src/components/CaptchaComponent.tsx b/src/components/CaptchaComponent.tsx index 0e42cef..97866cb 100644 --- a/src/components/CaptchaComponent.tsx +++ b/src/components/CaptchaComponent.tsx @@ -27,18 +27,15 @@ import { import { CaptchaContextManager } from "./CaptchaManager"; import { CaptchaWidget } from "./CaptchaWidget"; import { useTranslation } from "@prosopo/i18n"; -import { useStyles } from "../styles"; import { addDataAttr } from "../util"; import { Alert, Modal } from "@mui/material"; import ThemeProvider from "@mui/material/styles/ThemeProvider"; -import rootTheme from "./theme"; +import theme from "./theme"; export function CaptchaComponent({ clientInterface, show = false }: { clientInterface: ProsopoCaptchaClient, show: boolean }) { const { t } = useTranslation(); - const classes = useStyles(); - const theme = rootTheme; const manager: ICaptchaContextReducer = useContext(CaptchaContextManager); // the captcha state + update func @@ -88,33 +85,77 @@ export function CaptchaComponent({ clientInterface, show = false }: { clientInte // https://www.npmjs.com/package/i18next return ( - + - - - - + + + {!(captchaChallenge) // no captcha challenge has been setup yet, render an alert ? No captcha challenge active. // else captcha challenge has been populated, render the challenge : <> - - + + {t("WIDGET.SELECT_ALL", { target: captchaChallenge.captchas[captchaIndex].captcha.target })} - + - + {captchaChallenge?.captchas.map((_, index) => - )} + )} - + - - } - + )} - - ); + ) } -export default CaptchaComponent; +export default CaptchaComponent diff --git a/src/components/CaptchaManager.ts b/src/components/CaptchaManager.ts index 3b7dad0..c74887e 100644 --- a/src/components/CaptchaManager.ts +++ b/src/components/CaptchaManager.ts @@ -14,45 +14,51 @@ // You should have received a copy of the GNU General Public License // along with procaptcha-react. If not, see . -import { createContext, useReducer } from "react"; +import { createContext, useReducer } from 'react' import { + CaptchaEventCallbacks, + ICaptchaContextReducer, ICaptchaContextState, + ProsopoCaptchaClient, captchaContextReducer, captchaStatusReducer, - ICaptchaContextReducer, - ProsopoCaptchaClient, - CaptchaEventCallbacks, -} from "@prosopo/procaptcha"; - +} from '@prosopo/procaptcha' -export function useCaptcha(defaultContext: ICaptchaContextState, callbacks?: CaptchaEventCallbacks): ProsopoCaptchaClient { - const [context, updateContext] = useReducer(captchaContextReducer, defaultContext); - const [status, updateStatus] = useReducer(captchaStatusReducer, {}); - return new ProsopoCaptchaClient({ state: context, update: updateContext }, { state: status, update: updateStatus }, callbacks); +export function useCaptcha( + defaultContext: ICaptchaContextState, + callbacks?: CaptchaEventCallbacks +): ProsopoCaptchaClient { + const [context, updateContext] = useReducer(captchaContextReducer, defaultContext) + const [status, updateStatus] = useReducer(captchaStatusReducer, {}) + return new ProsopoCaptchaClient( + { state: context, update: updateContext }, + { state: status, update: updateStatus }, + callbacks + ) } export const CaptchaContextManager = createContext({ state: { config: { - "providerApi.baseURL": "", - "providerApi.prefix": "", - "dappAccount": "", - "dappUrl": "", - "solutionThreshold": 0, - "web2": false, - "prosopoContractAccount": "", - "accountCreator": { - "area" : {width: 0, height: 0}, - "offsetParameter" : 0, - "multiplier" : 0, - "fontSizeFactor" : 0, - "maxShadowBlur" : 0, - "numberOfRounds" : 0, - "seed" : 0 + 'providerApi.baseURL': '', + 'providerApi.prefix': '', + dappAccount: '', + dappUrl: '', + solutionThreshold: 0, + web2: false, + prosopoContractAccount: '', + accountCreator: { + area: { width: 0, height: 0 }, + offsetParameter: 0, + multiplier: 0, + fontSizeFactor: 0, + maxShadowBlur: 0, + numberOfRounds: 0, + seed: 0, }, - "dappName" :"", - "serverUrl": "" - } + dappName: '', + serverUrl: '', + }, }, update: () => {}, -} as ICaptchaContextReducer); +} as ICaptchaContextReducer) diff --git a/src/components/CaptchaWidget.tsx b/src/components/CaptchaWidget.tsx index 8cb3738..e80ec27 100644 --- a/src/components/CaptchaWidget.tsx +++ b/src/components/CaptchaWidget.tsx @@ -13,89 +13,113 @@ // // You should have received a copy of the GNU General Public License // along with procaptcha-react. If not, see . -import { CaptchaResponseCaptcha } from "@prosopo/procaptcha"; +import { CaptchaResponseCaptcha } from '@prosopo/procaptcha' -import CheckIcon from '@mui/icons-material/Check'; -import { Box, Fade, Theme } from "@mui/material"; -import useTheme from "@mui/styles/useTheme"; +import CheckIcon from '@mui/icons-material/Check' +import { Box, Fade, Theme } from '@mui/material' +import useTheme from '@mui/styles/useTheme' -export function CaptchaWidget({ challenge, solution, onChange }: - {challenge: CaptchaResponseCaptcha, solution: string[], onChange: (hash: string) => void}) { - - console.log("CHALLENGE", challenge); - const items = challenge.captcha.items; - const theme: Theme = useTheme(); +export function CaptchaWidget({ + challenge, + solution, + onChange, +}: { + challenge: CaptchaResponseCaptcha + solution: string[] + onChange: (hash: string) => void +}) { + console.log('CHALLENGE', challenge) + const items = challenge.captcha.items + const theme: Theme = useTheme() return ( <> - + {items.map((item, index) => { return ( - onChange(item.hash ? item.hash : '')}> - - {`Captcha onChange(item.hash ? item.hash : '')} + > + + {`Captcha - - - + + + @@ -104,7 +128,7 @@ export function CaptchaWidget({ challenge, solution, onChange }: })} - ); + ) } -export default CaptchaWidget; +export default CaptchaWidget diff --git a/src/components/ExtensionAccountSelect.tsx b/src/components/ExtensionAccountSelect.tsx index f9b34d7..54a549b 100644 --- a/src/components/ExtensionAccountSelect.tsx +++ b/src/components/ExtensionAccountSelect.tsx @@ -13,29 +13,36 @@ // // You should have received a copy of the GNU General Public License // along with procaptcha-react. If not, see . -import { SyntheticEvent } from "react"; -import Autocomplete from "@mui/material/Autocomplete"; -import TextField from "@mui/material/TextField"; -import { TExtensionAccount } from "@prosopo/procaptcha"; -import { useTranslation } from "@prosopo/i18n"; +import { SyntheticEvent } from 'react' +import Autocomplete from '@mui/material/Autocomplete' +import TextField from '@mui/material/TextField' +import { TExtensionAccount } from '@prosopo/procaptcha' +import { useTranslation } from '@prosopo/i18n' -export const ExtensionAccountSelect = ({value, options, onChange}: - {value?: TExtensionAccount, options: TExtensionAccount[], onChange: (value: TExtensionAccount | null) => void}) => { - const { t } = useTranslation(); +export const ExtensionAccountSelect = ({ + value, + options, + onChange, +}: { + value?: TExtensionAccount + options: TExtensionAccount[] + onChange: (value: TExtensionAccount | null) => void +}) => { + const { t } = useTranslation() return ( option.address === value.address} onChange={(event: SyntheticEvent, value: TExtensionAccount | null) => onChange(value)} - sx={{ width: 550 }} + sx={{ width: 550 }} getOptionLabel={(option: any) => `${option.meta.name}\n${option.address}`} - renderInput={(props) => } + renderInput={(props) => } /> - ); + ) } -export default ExtensionAccountSelect; +export default ExtensionAccountSelect diff --git a/src/components/index.ts b/src/components/index.ts index e7c611e..ab65dd7 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -13,7 +13,7 @@ // // You should have received a copy of the GNU General Public License // along with procaptcha-react. If not, see . -export * from './CaptchaManager'; -export * from './CaptchaWidget'; -export * from './CaptchaComponent'; -export * from './ExtensionAccountSelect'; +export * from './CaptchaManager' +export * from './CaptchaWidget' +export * from './CaptchaComponent' +export * from './ExtensionAccountSelect' diff --git a/src/components/theme.ts b/src/components/theme.ts index 4cb8657..f7f615a 100644 --- a/src/components/theme.ts +++ b/src/components/theme.ts @@ -1,12 +1,12 @@ -import orange from "@mui/material/colors/orange"; -import createTheme from "@mui/material/styles/createTheme"; +import orange from '@mui/material/colors/orange' +import createTheme from '@mui/material/styles/createTheme' export default createTheme({ palette: { primary: { main: '#1976d2', - contrastText: "#fff" + contrastText: '#fff', }, secondary: orange, }, -}) \ No newline at end of file +}) diff --git a/src/index.ts b/src/index.ts index 639306a..bd71933 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,5 +13,5 @@ // // You should have received a copy of the GNU General Public License // along with procaptcha-react. If not, see . -export * from './components'; -export * from './util'; +export * from './components' +export * from './util' diff --git a/src/util/index.ts b/src/util/index.ts index dcb0d12..14ecee8 100644 --- a/src/util/index.ts +++ b/src/util/index.ts @@ -1,26 +1,21 @@ function renameKeysForDataAttr(data: { [key: string]: string } = {}) { - return Object.keys(data).reduce( - (prev, curr) => ({ ...prev, [`data-${curr}`]: data[curr] }), - {} - ); + return Object.keys(data).reduce((prev, curr) => ({ ...prev, [`data-${curr}`]: data[curr] }), {}) } /** * maps any data to data attributes (mapped to { data-[key]: value }) - * + * * dev - only in development mode */ export function addDataAttr({ general, dev, }: { - general?: { [key: string]: string }; - dev?: { [key: string]: string }; + general?: { [key: string]: string } + dev?: { [key: string]: string } }) { return { ...renameKeysForDataAttr(general), - ...(process.env.NODE_ENV === "development" - ? renameKeysForDataAttr(dev) - : {}), - }; + ...(process.env.NODE_ENV === 'development' ? renameKeysForDataAttr(dev) : {}), + } } From 04150ea304f0a8c6d19ee6fee3d84bdc638439c1 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Thu, 3 Nov 2022 13:13:21 +0000 Subject: [PATCH 15/37] removed static fields --- src/components/CaptchaComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CaptchaComponent.tsx b/src/components/CaptchaComponent.tsx index ecc5725..5da9921 100644 --- a/src/components/CaptchaComponent.tsx +++ b/src/components/CaptchaComponent.tsx @@ -54,7 +54,7 @@ export function CaptchaComponent({ clientInterface, show = false }: { clientInte }, []); useEffect(() => { - const extension = clientInterface.getExtension(); + const extension = clientInterface.extension; if (contractAddress && extension) { extension.setDefaultAccount(); const defaultAccount = extension.getAccount(); From e02a4ffa5e4390de950f7bde4c9bbab4c7e2b3d0 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Thu, 3 Nov 2022 13:22:43 +0000 Subject: [PATCH 16/37] implemented tickbox --- src/components/Procaptcha.tsx | 70 +++++++++++++++++++++++++++++++++++ src/components/index.ts | 1 + 2 files changed, 71 insertions(+) create mode 100644 src/components/Procaptcha.tsx diff --git a/src/components/Procaptcha.tsx b/src/components/Procaptcha.tsx new file mode 100644 index 0000000..7f972de --- /dev/null +++ b/src/components/Procaptcha.tsx @@ -0,0 +1,70 @@ +import Typography from "@mui/material/Typography"; +import Box from "@mui/material/Box"; +import Checkbox from "@mui/material/Checkbox"; +import FormControlLabel from "@mui/material/FormControlLabel"; +import FormGroup from "@mui/material/FormGroup"; +import { useState } from "react"; +import EmojiPeopleIcon from '@mui/icons-material/EmojiPeople'; +import { useTheme } from "@emotion/react"; +import Link from "@mui/material/Link"; + +export const Procaptcha = () => { + + // client interface needs to be instantiated here to do the checks on the smart contract right away for already verified users + // this would auto-tick the tickbox + + + const [state, setState] = useState({ + checked: false, + showCaptcha: false, + }); + + const onChange = () => { + setState(current => { + // if not already showing the captcha + // and if the tickbox is not already ticked + if(!current.checked) { + // tickbox is currently unchecked + // trigger captcha challenge + // TODO + console.log("captcha") + } else { + console.log('captcha already done') + } + const next = {checked: true, showCaptcha: false, }; + return next; + }) + } + + return ( + + + + + + + + + + I am a human + + + + + Why must I prove I am human? + + + + + put logo here + + + + + ); +} \ No newline at end of file diff --git a/src/components/index.ts b/src/components/index.ts index e7c611e..71dd817 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -17,3 +17,4 @@ export * from './CaptchaManager'; export * from './CaptchaWidget'; export * from './CaptchaComponent'; export * from './ExtensionAccountSelect'; +export * from './Procaptcha'; \ No newline at end of file From 7cd233a39d2ebf862b7449efb956a4043fae12e5 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Mon, 7 Nov 2022 12:07:40 +0000 Subject: [PATCH 17/37] contract checks on server side --- src/components/CaptchaManager.ts | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/components/CaptchaManager.ts b/src/components/CaptchaManager.ts index c74887e..8c5ab7c 100644 --- a/src/components/CaptchaManager.ts +++ b/src/components/CaptchaManager.ts @@ -20,6 +20,7 @@ import { ICaptchaContextReducer, ICaptchaContextState, ProsopoCaptchaClient, + ProsopoClientConfig, captchaContextReducer, captchaStatusReducer, } from '@prosopo/procaptcha' @@ -40,13 +41,24 @@ export function useCaptcha( export const CaptchaContextManager = createContext({ state: { config: { - 'providerApi.baseURL': '', - 'providerApi.prefix': '', - dappAccount: '', - dappUrl: '', + defaultEnvironment: 'development', + logLevel: 'info', solutionThreshold: 0, web2: false, - prosopoContractAccount: '', + networks: { + development: { + endpoint: '', + prosopoContract: { + address: '', + name: '', + }, + dappContract: { + address: '', + name: '', + }, + accounts: ['//Alice', '//Bob', '//Charlie', '//Dave', '//Eve', '//Ferdie'], + }, + }, accountCreator: { area: { width: 0, height: 0 }, offsetParameter: 0, @@ -58,7 +70,7 @@ export const CaptchaContextManager = createContext({ }, dappName: '', serverUrl: '', - }, + } as ProsopoClientConfig, }, update: () => {}, } as ICaptchaContextReducer) From e4ce34c4fabaee92fae9abdf72faf60ba5650400 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Mon, 7 Nov 2022 14:20:28 +0000 Subject: [PATCH 18/37] empty challenge --- src/components/CaptchaComponent.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/CaptchaComponent.tsx b/src/components/CaptchaComponent.tsx index 70894c4..5c9896c 100644 --- a/src/components/CaptchaComponent.tsx +++ b/src/components/CaptchaComponent.tsx @@ -46,6 +46,7 @@ export function CaptchaComponent({ const [state, update] = useReducer(captchaStateReducer, { captchaIndex: 0, // the index of the captcha we're on (1 captcha challenge contains >=1 captcha) captchaSolution: [], // the solutions for the captcha (2d array corresponding to captcha) + captchaChallenge: {}, // the challenge object }) const { account, contractAddress } = manager.state const { captchaChallenge, captchaIndex, captchaSolution } = state From 75bc43d242aad63b17b3f1371e37e6452fafecf0 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Mon, 7 Nov 2022 14:21:13 +0000 Subject: [PATCH 19/37] remove empty captcha challenge --- src/components/CaptchaComponent.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/CaptchaComponent.tsx b/src/components/CaptchaComponent.tsx index 5c9896c..70894c4 100644 --- a/src/components/CaptchaComponent.tsx +++ b/src/components/CaptchaComponent.tsx @@ -46,7 +46,6 @@ export function CaptchaComponent({ const [state, update] = useReducer(captchaStateReducer, { captchaIndex: 0, // the index of the captcha we're on (1 captcha challenge contains >=1 captcha) captchaSolution: [], // the solutions for the captcha (2d array corresponding to captcha) - captchaChallenge: {}, // the challenge object }) const { account, contractAddress } = manager.state const { captchaChallenge, captchaIndex, captchaSolution } = state From 77dd9b0e7940b2899350f213f834b33d27a4a2b4 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Tue, 8 Nov 2022 11:25:58 +0000 Subject: [PATCH 20/37] verification checker --- src/components/CaptchaComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/CaptchaComponent.tsx b/src/components/CaptchaComponent.tsx index 70894c4..d0c9d58 100644 --- a/src/components/CaptchaComponent.tsx +++ b/src/components/CaptchaComponent.tsx @@ -54,7 +54,7 @@ export function CaptchaComponent({ const stateClientInterface = new ProsopoCaptchaStateClient(clientInterface, { state, update }) useEffect(() => { - clientInterface.onLoad(stateClientInterface.onSolved, manager.state.config['web2']) + clientInterface.onLoad(manager.state.config['web2']) }, []) useEffect(() => { From 213ac2443e35aa2fbb20b72e14b56e584bfef7ea Mon Sep 17 00:00:00 2001 From: George Oastler Date: Tue, 8 Nov 2022 17:15:47 +0000 Subject: [PATCH 21/37] procatpcha component housing tickbox and logo --- src/components/CaptchaComponent.tsx | 47 +++++---- src/components/CaptchaManager.ts | 3 +- src/components/Procaptcha.tsx | 155 +++++++++++++++++++--------- src/svg.d.ts | 1 + 4 files changed, 137 insertions(+), 69 deletions(-) create mode 100644 src/svg.d.ts diff --git a/src/components/CaptchaComponent.tsx b/src/components/CaptchaComponent.tsx index 5da9921..d545f39 100644 --- a/src/components/CaptchaComponent.tsx +++ b/src/components/CaptchaComponent.tsx @@ -33,11 +33,11 @@ import ThemeProvider from "@mui/material/styles/ThemeProvider"; import theme from "./theme"; -export function CaptchaComponent({ clientInterface, show = false }: { clientInterface: ProsopoCaptchaClient, show: boolean }) { +export function CaptchaComponent({ clientInterface, show }: { clientInterface: ProsopoCaptchaClient, show: boolean }) { const { t } = useTranslation(); - const manager: ICaptchaContextReducer = useContext(CaptchaContextManager); + const manager = useContext(CaptchaContextManager); // the captcha state + update func const [state, update] = useReducer(captchaStateReducer, { captchaIndex: 0, // the index of the captcha we're on (1 captcha challenge contains >=1 captcha) @@ -50,28 +50,35 @@ export function CaptchaComponent({ clientInterface, show = false }: { clientInte const stateClientInterface = new ProsopoCaptchaStateClient(clientInterface, { state, update }); useEffect(() => { - clientInterface.onLoad(manager.state.config['web2']); - }, []); - - useEffect(() => { - const extension = clientInterface.extension; - if (contractAddress && extension) { - extension.setDefaultAccount(); - const defaultAccount = extension.getAccount(); - if (defaultAccount) { - clientInterface.onAccountChange(defaultAccount); - } - } - }, [contractAddress]); - - useEffect(() => { - if (account && !captchaChallenge) { + console.log('useEffect show') + if(show) { + console.log('useEffect show true') stateClientInterface.onLoadCaptcha() .catch(error => { - clientInterface.status.update({ error }); + console.log(error) }); } - }, [account]); + }, [show]); + + // useEffect(() => { + // const extension = clientInterface.extension; + // if (contractAddress && extension) { + // extension.setDefaultAccount(); + // const defaultAccount = extension.getAccount(); + // if (defaultAccount) { + // clientInterface.onAccountChange(defaultAccount); + // } + // } + // }, [contractAddress]); + + // useEffect(() => { + // if (account && !captchaChallenge) { + // stateClientInterface.onLoadCaptcha() + // .catch(error => { + // console.log(error) + // }); + // } + // }, [account]); const resetState = () => { update({ diff --git a/src/components/CaptchaManager.ts b/src/components/CaptchaManager.ts index 3b7dad0..4db453c 100644 --- a/src/components/CaptchaManager.ts +++ b/src/components/CaptchaManager.ts @@ -27,8 +27,7 @@ import { export function useCaptcha(defaultContext: ICaptchaContextState, callbacks?: CaptchaEventCallbacks): ProsopoCaptchaClient { const [context, updateContext] = useReducer(captchaContextReducer, defaultContext); - const [status, updateStatus] = useReducer(captchaStatusReducer, {}); - return new ProsopoCaptchaClient({ state: context, update: updateContext }, { state: status, update: updateStatus }, callbacks); + return new ProsopoCaptchaClient({ state: context, update: updateContext }, callbacks); } export const CaptchaContextManager = createContext({ diff --git a/src/components/Procaptcha.tsx b/src/components/Procaptcha.tsx index 7f972de..7898d33 100644 --- a/src/components/Procaptcha.tsx +++ b/src/components/Procaptcha.tsx @@ -1,70 +1,131 @@ import Typography from "@mui/material/Typography"; import Box from "@mui/material/Box"; import Checkbox from "@mui/material/Checkbox"; -import FormControlLabel from "@mui/material/FormControlLabel"; -import FormGroup from "@mui/material/FormGroup"; -import { useState } from "react"; -import EmojiPeopleIcon from '@mui/icons-material/EmojiPeople'; +import { useEffect, useReducer, useState } from "react"; import { useTheme } from "@emotion/react"; import Link from "@mui/material/Link"; +import { captchaContextReducer, CaptchaEventCallbacks, captchaStatusReducer, GetCaptchaResponse, ICaptchaContextState, ICaptchaState, ProsopoCaptchaClient, ProsopoCaptchaConfig, ProsopoCaptchaStateClient, TCaptchaSubmitResult } from "@prosopo/procaptcha"; +import Modal from "@mui/material/Modal"; +import { CaptchaComponent } from "."; -export const Procaptcha = () => { +export interface ProcaptchaProps { + config: ProsopoCaptchaConfig; + callbacks: CaptchaEventCallbacks; +} - // client interface needs to be instantiated here to do the checks on the smart contract right away for already verified users - // this would auto-tick the tickbox +export const Procaptcha = (props: ProcaptchaProps) => { + + const callbacks = props.callbacks; + const config = props.config; + + // check all expected props are present + // TODO validate account can be found in the polk js extension + // TODO validate other props + const [ticked, setTicked] = useState(false); + const [showCaptcha, setShowCaptcha] = useState(false); + const [preApproveChecked, setPreApproveChecked] = useState(false); - const [state, setState] = useState({ - checked: false, - showCaptcha: false, + const [state, updateState] = useReducer(captchaContextReducer, {config}); + const client = new ProsopoCaptchaClient({ state: state, update: updateState }, { + onSolved: (result: TCaptchaSubmitResult, isHuman?: boolean) => { + callbacks.onSolved?.(result, isHuman); + setTicked(isHuman || false); + setShowCaptcha(false); + }, + onCancel: () => { + callbacks.onCancel?.(); + setShowCaptcha(false); + setTicked(false); + }, + onLoadCaptcha: (captchaChallenge: GetCaptchaResponse | Error) => { + callbacks.onLoadCaptcha?.(captchaChallenge); + }, + onSubmit: (result: TCaptchaSubmitResult | Error, captchaState: ICaptchaState) => { + callbacks.onSubmit?.(result, captchaState); + setShowCaptcha(false); + }, + onChange: (captchaSolution: string[][], index: number) => { + callbacks.onChange?.(captchaSolution, index); + }, }); - const onChange = () => { - setState(current => { - // if not already showing the captcha - // and if the tickbox is not already ticked - if(!current.checked) { - // tickbox is currently unchecked - // trigger captcha challenge - // TODO - console.log("captcha") + console.log(client) + console.log({ + ticked, + showCaptcha, + preApproveChecked, + }) + + useEffect(() => { + console.log(props) + console.log('Procaptcha: checking whether user is already approved'); + client.onLoad(async () => { + // only called if human, i.e. preapproved + console.log('preapproved') + setShowCaptcha(false); + setTicked(true); + // props.onPreApproved?.(); + }, config.web2).then(() => { + console.log('on load finished') + // onLoad() completed, preapprove checks complete + setPreApproveChecked(true); + }) + }, []); + + const onTick = () => { + if(ticked) { + // already approved, so do nothing + alert('You are already human'); + } else { + if(!preApproveChecked) { + // still waiting on preapprove checks, do nothing + console.log('cannot trigger challenge, waiting for preapprove checks to complete'); } else { - console.log('captcha already done') + // preapprove checks complete, not preapproved, so trigger captcha + setShowCaptcha(true); } - const next = {checked: true, showCaptcha: false, }; - return next; - }) + } } return ( - - - - - - - - - - I am a human - + + + + + + + + + + + + + + I am a human + + + {/* + Why must I prove I am human? + */} - - Why must I prove I am human? + + + +
+
+
- - - put logo here - -
); -} \ No newline at end of file +} + +const logo = 'Prosopo Logo Black' \ No newline at end of file diff --git a/src/svg.d.ts b/src/svg.d.ts new file mode 100644 index 0000000..c26f543 --- /dev/null +++ b/src/svg.d.ts @@ -0,0 +1 @@ +declare module '*.svg'; \ No newline at end of file From a5c789ba47d3894414d72ceac767a6bf5e6e6b92 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Tue, 8 Nov 2022 20:44:43 +0000 Subject: [PATCH 22/37] conclude auth_app_g merge --- src/components/CaptchaComponent.tsx | 21 ++-- src/components/Procaptcha.tsx | 173 ++++++++++++++++------------ src/components/index.ts | 9 +- 3 files changed, 111 insertions(+), 92 deletions(-) diff --git a/src/components/CaptchaComponent.tsx b/src/components/CaptchaComponent.tsx index 6e18bd0..7408f6a 100644 --- a/src/components/CaptchaComponent.tsx +++ b/src/components/CaptchaComponent.tsx @@ -17,12 +17,7 @@ import { useContext, useEffect, useReducer } from 'react' import Box from '@mui/material/Box' import Button from '@mui/material/Button' import Typography from '@mui/material/Typography' -import { - ICaptchaContextReducer, - ProsopoCaptchaClient, - ProsopoCaptchaStateClient, - captchaStateReducer, -} from '@prosopo/procaptcha' +import { ProsopoCaptchaClient, ProsopoCaptchaStateClient, captchaStateReducer } from '@prosopo/procaptcha' import { CaptchaContextManager } from './CaptchaManager' import { CaptchaWidget } from './CaptchaWidget' @@ -32,7 +27,6 @@ import { Alert, Modal } from '@mui/material' import ThemeProvider from '@mui/material/styles/ThemeProvider' import theme from './theme' -export function CaptchaComponent({ clientInterface, show }: { clientInterface: ProsopoCaptchaClient, show: boolean }) { export function CaptchaComponent({ clientInterface, show = false, @@ -42,7 +36,7 @@ export function CaptchaComponent({ }) { const { t } = useTranslation() - const manager = useContext(CaptchaContextManager); + const manager = useContext(CaptchaContextManager) // the captcha state + update func const [state, update] = useReducer(captchaStateReducer, { captchaIndex: 0, // the index of the captcha we're on (1 captcha challenge contains >=1 captcha) @@ -56,14 +50,13 @@ export function CaptchaComponent({ useEffect(() => { console.log('useEffect show') - if(show) { + if (show) { console.log('useEffect show true') - stateClientInterface.onLoadCaptcha() - .catch(error => { - console.log(error) - }); + stateClientInterface.onLoadCaptcha().catch((error) => { + console.log(error) + }) } - }, [show]); + }, [show]) // useEffect(() => { // const extension = clientInterface.extension; diff --git a/src/components/Procaptcha.tsx b/src/components/Procaptcha.tsx index 7898d33..62eb666 100644 --- a/src/components/Procaptcha.tsx +++ b/src/components/Procaptcha.tsx @@ -1,54 +1,63 @@ -import Typography from "@mui/material/Typography"; -import Box from "@mui/material/Box"; -import Checkbox from "@mui/material/Checkbox"; -import { useEffect, useReducer, useState } from "react"; -import { useTheme } from "@emotion/react"; -import Link from "@mui/material/Link"; -import { captchaContextReducer, CaptchaEventCallbacks, captchaStatusReducer, GetCaptchaResponse, ICaptchaContextState, ICaptchaState, ProsopoCaptchaClient, ProsopoCaptchaConfig, ProsopoCaptchaStateClient, TCaptchaSubmitResult } from "@prosopo/procaptcha"; -import Modal from "@mui/material/Modal"; -import { CaptchaComponent } from "."; +import Typography from '@mui/material/Typography' +import Box from '@mui/material/Box' +import Checkbox from '@mui/material/Checkbox' +import { useEffect, useReducer, useState } from 'react' +import Link from '@mui/material/Link' +import { + CaptchaEventCallbacks, + GetCaptchaResponse, + ICaptchaState, + ProsopoCaptchaClient, + ProsopoClientConfig, + SolvedData, + TCaptchaSubmitResult, + captchaContextReducer, +} from '@prosopo/procaptcha' +import { CaptchaComponent } from '.' export interface ProcaptchaProps { - config: ProsopoCaptchaConfig; - callbacks: CaptchaEventCallbacks; + config: ProsopoClientConfig + callbacks: CaptchaEventCallbacks } export const Procaptcha = (props: ProcaptchaProps) => { - - const callbacks = props.callbacks; - const config = props.config; + const callbacks = props.callbacks + const config = props.config // check all expected props are present // TODO validate account can be found in the polk js extension // TODO validate other props - - const [ticked, setTicked] = useState(false); - const [showCaptcha, setShowCaptcha] = useState(false); - const [preApproveChecked, setPreApproveChecked] = useState(false); - const [state, updateState] = useReducer(captchaContextReducer, {config}); - const client = new ProsopoCaptchaClient({ state: state, update: updateState }, { - onSolved: (result: TCaptchaSubmitResult, isHuman?: boolean) => { - callbacks.onSolved?.(result, isHuman); - setTicked(isHuman || false); - setShowCaptcha(false); - }, - onCancel: () => { - callbacks.onCancel?.(); - setShowCaptcha(false); - setTicked(false); - }, - onLoadCaptcha: (captchaChallenge: GetCaptchaResponse | Error) => { - callbacks.onLoadCaptcha?.(captchaChallenge); - }, - onSubmit: (result: TCaptchaSubmitResult | Error, captchaState: ICaptchaState) => { - callbacks.onSubmit?.(result, captchaState); - setShowCaptcha(false); - }, - onChange: (captchaSolution: string[][], index: number) => { - callbacks.onChange?.(captchaSolution, index); - }, - }); + const [ticked, setTicked] = useState(false) + const [showCaptcha, setShowCaptcha] = useState(false) + const [preApproveChecked, setPreApproveChecked] = useState(false) + + const [state, updateState] = useReducer(captchaContextReducer, { config }) + const client = new ProsopoCaptchaClient( + { state: state, update: updateState }, + { + onHuman: (solvedData: SolvedData) => { + callbacks.onHuman?.(solvedData) + setTicked(!solvedData.human) + setShowCaptcha(false) + }, + onCancel: () => { + callbacks.onCancel?.() + setShowCaptcha(false) + setTicked(false) + }, + onLoadCaptcha: (captchaChallenge: GetCaptchaResponse | Error) => { + callbacks.onLoadCaptcha?.(captchaChallenge) + }, + onSubmit: (result: TCaptchaSubmitResult | Error, captchaState: ICaptchaState) => { + callbacks.onSubmit?.(result, captchaState) + setShowCaptcha(false) + }, + onChange: (captchaSolution: string[][], index: number) => { + callbacks.onChange?.(captchaSolution, index) + }, + } + ) console.log(client) console.log({ @@ -59,44 +68,58 @@ export const Procaptcha = (props: ProcaptchaProps) => { useEffect(() => { console.log(props) - console.log('Procaptcha: checking whether user is already approved'); - client.onLoad(async () => { - // only called if human, i.e. preapproved - console.log('preapproved') - setShowCaptcha(false); - setTicked(true); - // props.onPreApproved?.(); - }, config.web2).then(() => { - console.log('on load finished') - // onLoad() completed, preapprove checks complete - setPreApproveChecked(true); - }) - }, []); + console.log('Procaptcha: checking whether user is already approved') + client + .onLoad(async () => { + // only called if human, i.e. preapproved + console.log('preapproved') + setShowCaptcha(false) + setTicked(true) + // props.onPreApproved?.(); + }, config.web2) + .then(() => { + console.log('on load finished') + // onLoad() completed, preapprove checks complete + setPreApproveChecked(true) + }) + }, []) const onTick = () => { - if(ticked) { + if (ticked) { // already approved, so do nothing - alert('You are already human'); + alert('You are already human') } else { - if(!preApproveChecked) { + if (!preApproveChecked) { // still waiting on preapprove checks, do nothing - console.log('cannot trigger challenge, waiting for preapprove checks to complete'); + console.log('cannot trigger challenge, waiting for preapprove checks to complete') } else { // preapprove checks complete, not preapproved, so trigger captcha - setShowCaptcha(true); + setShowCaptcha(true) } } } return ( - - - + + - - - - + + + + { sx={{ '& .MuiSvgIcon-root': { fontSize: 32 } }} /> - - - I am a human - + + I am a human {/* Why must I prove I am human? */} - + -
+
- ); + ) } -const logo = 'Prosopo Logo Black' \ No newline at end of file +const logo = + 'Prosopo Logo Black' diff --git a/src/components/index.ts b/src/components/index.ts index e7c611e..104663b 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -13,7 +13,8 @@ // // You should have received a copy of the GNU General Public License // along with procaptcha-react. If not, see . -export * from './CaptchaManager'; -export * from './CaptchaWidget'; -export * from './CaptchaComponent'; -export * from './ExtensionAccountSelect'; +export * from './CaptchaManager' +export * from './CaptchaWidget' +export * from './CaptchaComponent' +export * from './ExtensionAccountSelect' +export * from './Procaptcha' From 2e979ab15959bcd45cd617bd84d7075fa9e415f0 Mon Sep 17 00:00:00 2001 From: Chris Taylor Date: Wed, 9 Nov 2022 09:36:16 +0000 Subject: [PATCH 23/37] set tick properly --- src/components/Procaptcha.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Procaptcha.tsx b/src/components/Procaptcha.tsx index 62eb666..e42cc10 100644 --- a/src/components/Procaptcha.tsx +++ b/src/components/Procaptcha.tsx @@ -38,7 +38,7 @@ export const Procaptcha = (props: ProcaptchaProps) => { { onHuman: (solvedData: SolvedData) => { callbacks.onHuman?.(solvedData) - setTicked(!solvedData.human) + setTicked(solvedData.human) setShowCaptcha(false) }, onCancel: () => { From 75e61fc37c31636bbf401467bb960235540cf040 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Mon, 14 Nov 2022 10:53:00 +0000 Subject: [PATCH 24/37] use new state manager --- src/components/Procaptcha.tsx | 321 +++++++++++++++++++++++++--------- 1 file changed, 240 insertions(+), 81 deletions(-) diff --git a/src/components/Procaptcha.tsx b/src/components/Procaptcha.tsx index e42cc10..beca1a8 100644 --- a/src/components/Procaptcha.tsx +++ b/src/components/Procaptcha.tsx @@ -1,107 +1,266 @@ import Typography from '@mui/material/Typography' import Box from '@mui/material/Box' import Checkbox from '@mui/material/Checkbox' -import { useEffect, useReducer, useState } from 'react' +import { useRef, useState } from 'react' import Link from '@mui/material/Link' -import { - CaptchaEventCallbacks, - GetCaptchaResponse, - ICaptchaState, - ProsopoCaptchaClient, - ProsopoClientConfig, - SolvedData, - TCaptchaSubmitResult, - captchaContextReducer, -} from '@prosopo/procaptcha' -import { CaptchaComponent } from '.' +import { CaptchaEventCallbacks, ProsopoClientConfig } from '@prosopo/procaptcha' +import { Manager, ProcaptchaConfig, ProcaptchaState, ProcaptchaStateUpdater } from '@prosopo/procaptcha' export interface ProcaptchaProps { config: ProsopoClientConfig callbacks: CaptchaEventCallbacks } +const useRefAsState = (defaultValue): [T, (value: T) => void] => { + const ref = useRef(defaultValue) + const setter = (value: T) => { + ref.current = value + } + const value: T = ref.current + return [value, setter] +} + +const useProcaptcha = (): [ProcaptchaState, ProcaptchaStateUpdater] => { + // useRef == do not render on variable change + // useState == do render on variable change + // only need to render on visible variables changing + + const [isHuman, setIsHuman] = useState(false) + const [index, setIndex] = useState(-1) + const [solutions, setSolutions] = useState([]) + const [providerUrl, setProviderUrl] = useRefAsState('') + const [config, setConfig] = useRefAsState({}) + + const map = { + isHuman: setIsHuman, + index: setIndex, + solutions: setSolutions, + providerUrl: setProviderUrl, + config: setConfig, + } + + return [ + { + isHuman, + index, + solutions, + providerUrl, + config, + }, + (nextState: Partial) => { + if (nextState.solutions) { + // force a copy of the array to ensure a re-render + // nutshell: react doesn't look inside an array for changes, hence changes to the array need to result in a fresh array + nextState.solutions = nextState.solutions.slice() + } + + for (const key in nextState) { + const setter = map[key] + if (!setter) { + throw new Error(`Unknown key ${key}, cannot set state`) + } + setter(nextState[key]) + } + }, + ] + + // const state = { + // isHuman, + // } + // Object.defineProperty(state, 'isHuman', { + // get: () => isHuman, + // set: (value) => { + // console.log('setting ishuman in obj def') + // isHuman = value + // setIsHuman(value) + // }, + // }) + // return state + + // class State { + // get isHuman() { + // return isHuman + // } + + // set isHuman(value) { + // console.log('setting ishuman in class') + // isHuman = value + // setIsHuman(value) + // } + // } + + // const state = new State() + + // Object.defineProperty(state, 'isHuman', { + // enumerable: true, + // writable: true, + // // get: () => isHuman, + // // set: (value) => { + // // console.log('setting ishuman in obj def') + // // isHuman = value + // // setIsHuman(value) + // // }, + // }) + + // return state + + // class State { + + // isHuman: boolean + + // constructor() { + // this.isHuman = isHuman + // Object.defineProperty(this, 'isHuman', { + // enumerable: true, + // get: () => isHuman, + // set: (value) => { + // console.log('setting ishuman in obj def') + // isHuman = value + // setIsHuman(value) + // }, + // }) + // } + + // // get index() { + // // return index + // // } + + // // set index(value) { + // // index = value + // // setIndex(value) + // // } + + // // get solutions() { + // // return solutions + // // } + + // // set solutions(value) { + // // solutions = value + // // setSolutions(value) + // // } + + // // get providerUrl() { + // // return providerUrl + // // } + + // // set providerUrl(value) { + // // providerUrl = value + // // setProviderUrl(value) + // // } + // } + + // return new State() +} + export const Procaptcha = (props: ProcaptchaProps) => { const callbacks = props.callbacks const config = props.config - // check all expected props are present - // TODO validate account can be found in the polk js extension - // TODO validate other props + // // check all expected props are present + // // TODO validate account can be found in the polk js extension + // // TODO validate other props - const [ticked, setTicked] = useState(false) - const [showCaptcha, setShowCaptcha] = useState(false) - const [preApproveChecked, setPreApproveChecked] = useState(false) + // const [ticked, setTicked] = useState(false) + // const [showCaptcha, setShowCaptcha] = useState(false) + // const [preApproveChecked, setPreApproveChecked] = useState(false) - const [state, updateState] = useReducer(captchaContextReducer, { config }) - const client = new ProsopoCaptchaClient( - { state: state, update: updateState }, - { - onHuman: (solvedData: SolvedData) => { - callbacks.onHuman?.(solvedData) - setTicked(solvedData.human) - setShowCaptcha(false) - }, - onCancel: () => { - callbacks.onCancel?.() - setShowCaptcha(false) - setTicked(false) - }, - onLoadCaptcha: (captchaChallenge: GetCaptchaResponse | Error) => { - callbacks.onLoadCaptcha?.(captchaChallenge) - }, - onSubmit: (result: TCaptchaSubmitResult | Error, captchaState: ICaptchaState) => { - callbacks.onSubmit?.(result, captchaState) - setShowCaptcha(false) - }, - onChange: (captchaSolution: string[][], index: number) => { - callbacks.onChange?.(captchaSolution, index) - }, - } - ) + // const [state, updateState] = useReducer(captchaContextReducer, { config }) + // const client = new ProsopoCaptchaClient( + // { state: state, update: updateState }, + // { + // onHuman: (solvedData: SolvedData) => { + // callbacks.onHuman?.(solvedData) + // setTicked(solvedData.human) + // setShowCaptcha(false) + // }, + // onCancel: () => { + // callbacks.onCancel?.() + // setShowCaptcha(false) + // setTicked(false) + // }, + // onLoadCaptcha: (captchaChallenge: GetCaptchaResponse | Error) => { + // callbacks.onLoadCaptcha?.(captchaChallenge) + // }, + // onSubmit: (result: TCaptchaSubmitResult | Error, captchaState: ICaptchaState) => { + // callbacks.onSubmit?.(result, captchaState) + // setShowCaptcha(false) + // }, + // onChange: (captchaSolution: string[][], index: number) => { + // callbacks.onChange?.(captchaSolution, index) + // }, + // } + // ) + + // console.log(client) + // console.log({ + // ticked, + // showCaptcha, + // preApproveChecked, + // }) + + // useEffect(() => { + // console.log(props) + // console.log('Procaptcha: checking whether user is already approved') + // client + // .onLoad(async () => { + // // only called if human, i.e. preapproved + // console.log('preapproved') + // setShowCaptcha(false) + // setTicked(true) + // // props.onPreApproved?.(); + // }, config.web2) + // .then(() => { + // console.log('on load finished') + // // onLoad() completed, preapprove checks complete + // setPreApproveChecked(true) + // }) + // }, []) - console.log(client) - console.log({ - ticked, - showCaptcha, - preApproveChecked, - }) - - useEffect(() => { - console.log(props) - console.log('Procaptcha: checking whether user is already approved') - client - .onLoad(async () => { - // only called if human, i.e. preapproved - console.log('preapproved') - setShowCaptcha(false) - setTicked(true) - // props.onPreApproved?.(); - }, config.web2) - .then(() => { - console.log('on load finished') - // onLoad() completed, preapprove checks complete - setPreApproveChecked(true) - }) - }, []) + // const onTick = () => { + // if (ticked) { + // // already approved, so do nothing + // alert('You are already human') + // } else { + // if (!preApproveChecked) { + // // still waiting on preapprove checks, do nothing + // console.log('cannot trigger challenge, waiting for preapprove checks to complete') + // } else { + // // preapprove checks complete, not preapproved, so trigger captcha + // setShowCaptcha(true) + // } + // } + // } + const [state, updateState] = useProcaptcha() + const manager = Manager(state, updateState, {}) const onTick = () => { - if (ticked) { - // already approved, so do nothing - alert('You are already human') - } else { - if (!preApproveChecked) { - // still waiting on preapprove checks, do nothing - console.log('cannot trigger challenge, waiting for preapprove checks to complete') - } else { - // preapprove checks complete, not preapproved, so trigger captcha - setShowCaptcha(true) - } - } + console.log('onTick') + manager.start({ + address: '5EXaAvaSP1T4BMeHdtF2AudXq7ooRo6jHwi6HywenfSkedNa', + web2: false, + dappName: 'Prosopo', + dappUrl: 'https://localhost:9944', + defaultEnvironment: 'development', + networks: { + development: { + endpoint: process.env.REACT_APP_SUBSTRATE_ENDPOINT || '', + prosopoContract: { + address: process.env.REACT_APP_PROSOPO_CONTRACT_ADDRESS || '', + name: 'prosopo', + }, + dappContract: { + address: process.env.REACT_APP_DAPP_CONTRACT_ADDRESS || '', + name: 'dapp', + }, + }, + }, + }) } + const ticked = false return ( - + {/* */} Date: Mon, 14 Nov 2022 16:02:50 +0000 Subject: [PATCH 25/37] changed state update process --- src/components/Procaptcha.tsx | 212 +++------------------------------- 1 file changed, 18 insertions(+), 194 deletions(-) diff --git a/src/components/Procaptcha.tsx b/src/components/Procaptcha.tsx index beca1a8..5b26450 100644 --- a/src/components/Procaptcha.tsx +++ b/src/components/Procaptcha.tsx @@ -3,15 +3,15 @@ import Box from '@mui/material/Box' import Checkbox from '@mui/material/Checkbox' import { useRef, useState } from 'react' import Link from '@mui/material/Link' -import { CaptchaEventCallbacks, ProsopoClientConfig } from '@prosopo/procaptcha' -import { Manager, ProcaptchaConfig, ProcaptchaState, ProcaptchaStateUpdater } from '@prosopo/procaptcha' +import { ProcaptchaCallbacks } from '@prosopo/procaptcha' +import { Manager, ProcaptchaConfig, ProcaptchaState, StateUpdateFn } from '@prosopo/procaptcha' export interface ProcaptchaProps { - config: ProsopoClientConfig - callbacks: CaptchaEventCallbacks + config: ProcaptchaConfig + callbacks?: Partial } -const useRefAsState = (defaultValue): [T, (value: T) => void] => { +const useRefAsState = (defaultValue: T): [T, (value: T) => void] => { const ref = useRef(defaultValue) const setter = (value: T) => { ref.current = value @@ -20,7 +20,7 @@ const useRefAsState = (defaultValue): [T, (value: T) => void] => { return [value, setter] } -const useProcaptcha = (): [ProcaptchaState, ProcaptchaStateUpdater] => { +const useProcaptcha = (initConfig: ProcaptchaConfig): [ProcaptchaState, StateUpdateFn] => { // useRef == do not render on variable change // useState == do render on variable change // only need to render on visible variables changing @@ -29,24 +29,29 @@ const useProcaptcha = (): [ProcaptchaState, ProcaptchaStateUpdater] => { const [index, setIndex] = useState(-1) const [solutions, setSolutions] = useState([]) const [providerUrl, setProviderUrl] = useRefAsState('') - const [config, setConfig] = useRefAsState({}) + // take a deep copy of the config and store in state. This stops the config from being updated externally to this state management + const [config] = useState(() => { + return JSON.parse(JSON.stringify(initConfig)) + }) const map = { isHuman: setIsHuman, index: setIndex, solutions: setSolutions, providerUrl: setProviderUrl, - config: setConfig, + // don't provide method for updating config, should remain constant } return [ + // the state { isHuman, index, solutions, providerUrl, - config, + config: config, }, + // and method to update the state (nextState: Partial) => { if (nextState.solutions) { // force a copy of the array to ensure a re-render @@ -64,197 +69,16 @@ const useProcaptcha = (): [ProcaptchaState, ProcaptchaStateUpdater] => { }, ] - // const state = { - // isHuman, - // } - // Object.defineProperty(state, 'isHuman', { - // get: () => isHuman, - // set: (value) => { - // console.log('setting ishuman in obj def') - // isHuman = value - // setIsHuman(value) - // }, - // }) - // return state - - // class State { - // get isHuman() { - // return isHuman - // } - - // set isHuman(value) { - // console.log('setting ishuman in class') - // isHuman = value - // setIsHuman(value) - // } - // } - - // const state = new State() - - // Object.defineProperty(state, 'isHuman', { - // enumerable: true, - // writable: true, - // // get: () => isHuman, - // // set: (value) => { - // // console.log('setting ishuman in obj def') - // // isHuman = value - // // setIsHuman(value) - // // }, - // }) - - // return state - - // class State { - - // isHuman: boolean - - // constructor() { - // this.isHuman = isHuman - // Object.defineProperty(this, 'isHuman', { - // enumerable: true, - // get: () => isHuman, - // set: (value) => { - // console.log('setting ishuman in obj def') - // isHuman = value - // setIsHuman(value) - // }, - // }) - // } - - // // get index() { - // // return index - // // } - - // // set index(value) { - // // index = value - // // setIndex(value) - // // } - - // // get solutions() { - // // return solutions - // // } - - // // set solutions(value) { - // // solutions = value - // // setSolutions(value) - // // } - - // // get providerUrl() { - // // return providerUrl - // // } - - // // set providerUrl(value) { - // // providerUrl = value - // // setProviderUrl(value) - // // } - // } - - // return new State() } export const Procaptcha = (props: ProcaptchaProps) => { - const callbacks = props.callbacks const config = props.config + const callbacks = props.callbacks || {} - // // check all expected props are present - // // TODO validate account can be found in the polk js extension - // // TODO validate other props - - // const [ticked, setTicked] = useState(false) - // const [showCaptcha, setShowCaptcha] = useState(false) - // const [preApproveChecked, setPreApproveChecked] = useState(false) - - // const [state, updateState] = useReducer(captchaContextReducer, { config }) - // const client = new ProsopoCaptchaClient( - // { state: state, update: updateState }, - // { - // onHuman: (solvedData: SolvedData) => { - // callbacks.onHuman?.(solvedData) - // setTicked(solvedData.human) - // setShowCaptcha(false) - // }, - // onCancel: () => { - // callbacks.onCancel?.() - // setShowCaptcha(false) - // setTicked(false) - // }, - // onLoadCaptcha: (captchaChallenge: GetCaptchaResponse | Error) => { - // callbacks.onLoadCaptcha?.(captchaChallenge) - // }, - // onSubmit: (result: TCaptchaSubmitResult | Error, captchaState: ICaptchaState) => { - // callbacks.onSubmit?.(result, captchaState) - // setShowCaptcha(false) - // }, - // onChange: (captchaSolution: string[][], index: number) => { - // callbacks.onChange?.(captchaSolution, index) - // }, - // } - // ) - - // console.log(client) - // console.log({ - // ticked, - // showCaptcha, - // preApproveChecked, - // }) - - // useEffect(() => { - // console.log(props) - // console.log('Procaptcha: checking whether user is already approved') - // client - // .onLoad(async () => { - // // only called if human, i.e. preapproved - // console.log('preapproved') - // setShowCaptcha(false) - // setTicked(true) - // // props.onPreApproved?.(); - // }, config.web2) - // .then(() => { - // console.log('on load finished') - // // onLoad() completed, preapprove checks complete - // setPreApproveChecked(true) - // }) - // }, []) - - // const onTick = () => { - // if (ticked) { - // // already approved, so do nothing - // alert('You are already human') - // } else { - // if (!preApproveChecked) { - // // still waiting on preapprove checks, do nothing - // console.log('cannot trigger challenge, waiting for preapprove checks to complete') - // } else { - // // preapprove checks complete, not preapproved, so trigger captcha - // setShowCaptcha(true) - // } - // } - // } - - const [state, updateState] = useProcaptcha() - const manager = Manager(state, updateState, {}) + const [state, updateState] = useProcaptcha(config) + const manager = Manager(state, updateState, callbacks) const onTick = () => { - console.log('onTick') - manager.start({ - address: '5EXaAvaSP1T4BMeHdtF2AudXq7ooRo6jHwi6HywenfSkedNa', - web2: false, - dappName: 'Prosopo', - dappUrl: 'https://localhost:9944', - defaultEnvironment: 'development', - networks: { - development: { - endpoint: process.env.REACT_APP_SUBSTRATE_ENDPOINT || '', - prosopoContract: { - address: process.env.REACT_APP_PROSOPO_CONTRACT_ADDRESS || '', - name: 'prosopo', - }, - dappContract: { - address: process.env.REACT_APP_DAPP_CONTRACT_ADDRESS || '', - name: 'dapp', - }, - }, - }, - }) + manager.start() } const ticked = false From 0dc058056bb58ba35547378a9664437bbcb7769e Mon Sep 17 00:00:00 2001 From: George Oastler Date: Mon, 14 Nov 2022 19:50:51 +0000 Subject: [PATCH 26/37] added contract, provider, apis and challenge to state + onHuman event --- src/components/Procaptcha.tsx | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/src/components/Procaptcha.tsx b/src/components/Procaptcha.tsx index 5b26450..fb6e118 100644 --- a/src/components/Procaptcha.tsx +++ b/src/components/Procaptcha.tsx @@ -3,8 +3,9 @@ import Box from '@mui/material/Box' import Checkbox from '@mui/material/Checkbox' import { useRef, useState } from 'react' import Link from '@mui/material/Link' -import { ProcaptchaCallbacks } from '@prosopo/procaptcha' +import { GetCaptchaResponse, ProcaptchaCallbacks, ProsopoCaptchaApi, ProsopoContract, ProsopoRandomProviderResponse } from '@prosopo/procaptcha' import { Manager, ProcaptchaConfig, ProcaptchaState, StateUpdateFn } from '@prosopo/procaptcha' +import { ProviderApi } from '@prosopo/api' export interface ProcaptchaProps { config: ProcaptchaConfig @@ -27,18 +28,28 @@ const useProcaptcha = (initConfig: ProcaptchaConfig): [ProcaptchaState, StateUpd const [isHuman, setIsHuman] = useState(false) const [index, setIndex] = useState(-1) - const [solutions, setSolutions] = useState([]) - const [providerUrl, setProviderUrl] = useRefAsState('') + const [solutions, setSolutions] = useRefAsState([]) // take a deep copy of the config and store in state. This stops the config from being updated externally to this state management const [config] = useState(() => { return JSON.parse(JSON.stringify(initConfig)) }) + const [contract, setContract] = useRefAsState(undefined) + const [provider, setProvider] = useRefAsState(undefined) + const [captchaApi, setCaptchaApi] = useRefAsState(undefined) + const [showChallenge, setShowChallenge] = useState(false) + const [challenge, setChallenge] = useState(undefined) + const [providerApi, setProviderApi] = useRefAsState(undefined) const map = { isHuman: setIsHuman, index: setIndex, solutions: setSolutions, - providerUrl: setProviderUrl, + contract: setContract, + provider: setProvider, + captchaApi: setCaptchaApi, + showChallenge: setShowChallenge, + challenge: setChallenge, + providerApi: setProviderApi, // don't provide method for updating config, should remain constant } @@ -48,8 +59,13 @@ const useProcaptcha = (initConfig: ProcaptchaConfig): [ProcaptchaState, StateUpd isHuman, index, solutions, - providerUrl, - config: config, + config, + contract, + provider, + captchaApi, + showChallenge, + challenge, + providerApi, }, // and method to update the state (nextState: Partial) => { From f83bd16be687385117159c00ba58296f43d2a0de Mon Sep 17 00:00:00 2001 From: George Oastler Date: Mon, 21 Nov 2022 10:46:40 +0000 Subject: [PATCH 27/37] use new state management + force challenge to be defined in captcha component and widget --- src/components/CaptchaComponent.tsx | 293 ++++++++++------------------ src/components/CaptchaWidget.tsx | 134 +++++++------ src/components/Procaptcha.tsx | 25 ++- tsconfig.json | 8 - 4 files changed, 201 insertions(+), 259 deletions(-) diff --git a/src/components/CaptchaComponent.tsx b/src/components/CaptchaComponent.tsx index 7408f6a..f7053fb 100644 --- a/src/components/CaptchaComponent.tsx +++ b/src/components/CaptchaComponent.tsx @@ -13,226 +13,143 @@ // // You should have received a copy of the GNU General Public License // along with procaptcha-react. If not, see . -import { useContext, useEffect, useReducer } from 'react' import Box from '@mui/material/Box' import Button from '@mui/material/Button' import Typography from '@mui/material/Typography' -import { ProsopoCaptchaClient, ProsopoCaptchaStateClient, captchaStateReducer } from '@prosopo/procaptcha' +import { GetCaptchaResponse } from '@prosopo/procaptcha' -import { CaptchaContextManager } from './CaptchaManager' import { CaptchaWidget } from './CaptchaWidget' import { useTranslation } from '@prosopo/i18n' import { addDataAttr } from '../util' -import { Alert, Modal } from '@mui/material' import ThemeProvider from '@mui/material/styles/ThemeProvider' import theme from './theme' -export function CaptchaComponent({ - clientInterface, - show = false, -}: { - clientInterface: ProsopoCaptchaClient - show: boolean -}) { - const { t } = useTranslation() - - const manager = useContext(CaptchaContextManager) - // the captcha state + update func - const [state, update] = useReducer(captchaStateReducer, { - captchaIndex: 0, // the index of the captcha we're on (1 captcha challenge contains >=1 captcha) - captchaSolution: [], // the solutions for the captcha (2d array corresponding to captcha) - }) - const { account, contractAddress } = manager.state - const { captchaChallenge, captchaIndex, captchaSolution } = state - const totalCaptchas = captchaChallenge?.captchas.length ?? 0 - - const stateClientInterface = new ProsopoCaptchaStateClient(clientInterface, { state, update }) - - useEffect(() => { - console.log('useEffect show') - if (show) { - console.log('useEffect show true') - stateClientInterface.onLoadCaptcha().catch((error) => { - console.log(error) - }) - } - }, [show]) - - // useEffect(() => { - // const extension = clientInterface.extension; - // if (contractAddress && extension) { - // extension.setDefaultAccount(); - // const defaultAccount = extension.getAccount(); - // if (defaultAccount) { - // clientInterface.onAccountChange(defaultAccount); - // } - // } - // }, [contractAddress]); - - // useEffect(() => { - // if (account && !captchaChallenge) { - // stateClientInterface.onLoadCaptcha() - // .catch(error => { - // console.log(error) - // }); - // } - // }, [account]) - - const resetState = () => { - update({ - captchaIndex: 0, // the index of the captcha we're on (1 captcha challenge contains >=1 captcha) - captchaSolution: [], // the solutions for the captcha (2d array corresponding to captcha) - captchaChallenge: undefined, - }) - } +export interface CaptchaComponentProps { + challenge: GetCaptchaResponse + index: number + solutions: string[][] + onSubmit: () => void + onCancel: () => void + onClick: (hash: string) => void +} - // https://www.npmjs.com/package/i18next +export const CaptchaComponent = (props: CaptchaComponentProps) => { + const { t } = useTranslation() + console.log('CaptchaComponent', props) + const { challenge, index, solutions, onSubmit, onCancel, onClick } = props + const captcha = challenge.captchas[index] + const solution = solutions[index] return ( - + - {!captchaChallenge ? ( - // no captcha challenge has been setup yet, render an alert - No captcha challenge active. - ) : ( - // else captcha challenge has been populated, render the challenge - <> - - - {t('WIDGET.SELECT_ALL', { - target: captchaChallenge.captchas[captchaIndex].captcha.target, - })} - - + + {t('WIDGET.SELECT_ALL', { + target: props.challenge.captchas[props.index].captcha.target, + })} + + - - - - - {captchaChallenge?.captchas.map((_, index) => ( - - ))} - - - - - - - )} + + + + + {challenge.captchas.map((_, i) => ( + + ))} + + + + - +
) } diff --git a/src/components/CaptchaWidget.tsx b/src/components/CaptchaWidget.tsx index e80ec27..793de13 100644 --- a/src/components/CaptchaWidget.tsx +++ b/src/components/CaptchaWidget.tsx @@ -18,17 +18,24 @@ import { CaptchaResponseCaptcha } from '@prosopo/procaptcha' import CheckIcon from '@mui/icons-material/Check' import { Box, Fade, Theme } from '@mui/material' import useTheme from '@mui/styles/useTheme' +import { Item } from '@prosopo/datasets' -export function CaptchaWidget({ - challenge, - solution, - onChange, -}: { +export interface CaptchaWidgetProps { challenge: CaptchaResponseCaptcha solution: string[] - onChange: (hash: string) => void -}) { - console.log('CHALLENGE', challenge) + onClick: (hash: string) => void +} + +const getHash = (item: Item) => { + if (!item.hash) { + throw new Error('item.hash is undefined') + } + return item.hash +} + +export const CaptchaWidget = (props: CaptchaWidgetProps) => { + console.log('CaptchaWidget', props) + const { challenge, solution, onClick } = props const items = challenge.captcha.items const theme: Theme = useTheme() @@ -48,6 +55,7 @@ export function CaptchaWidget({ }} > {items.map((item, index) => { + const hash = getHash(item) return ( onChange(item.hash ? item.hash : '')} > - - {`Captcha - - - + onClick(hash)} + > + + {`Captcha + + - + > + + - - + +
) })} diff --git a/src/components/Procaptcha.tsx b/src/components/Procaptcha.tsx index fb6e118..f80e4cb 100644 --- a/src/components/Procaptcha.tsx +++ b/src/components/Procaptcha.tsx @@ -3,9 +3,17 @@ import Box from '@mui/material/Box' import Checkbox from '@mui/material/Checkbox' import { useRef, useState } from 'react' import Link from '@mui/material/Link' -import { GetCaptchaResponse, ProcaptchaCallbacks, ProsopoCaptchaApi, ProsopoContract, ProsopoRandomProviderResponse } from '@prosopo/procaptcha' +import { + GetCaptchaResponse, + ProcaptchaCallbacks, + ProsopoCaptchaApi, + ProsopoContract, + ProsopoRandomProviderResponse, +} from '@prosopo/procaptcha' import { Manager, ProcaptchaConfig, ProcaptchaState, StateUpdateFn } from '@prosopo/procaptcha' import { ProviderApi } from '@prosopo/api' +import { Alert, Modal } from '@mui/material' +import CaptchaComponent from './CaptchaComponent' export interface ProcaptchaProps { config: ProcaptchaConfig @@ -100,7 +108,20 @@ export const Procaptcha = (props: ProcaptchaProps) => { return ( - {/* */} + + {state.challenge ? ( + + ) : ( + No challenge + )} + Date: Mon, 21 Nov 2022 10:46:59 +0000 Subject: [PATCH 28/37] add loading placeholder to stop multiple captcha challenges being started --- src/components/Procaptcha.tsx | 65 +++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/src/components/Procaptcha.tsx b/src/components/Procaptcha.tsx index f80e4cb..a104c2c 100644 --- a/src/components/Procaptcha.tsx +++ b/src/components/Procaptcha.tsx @@ -12,8 +12,9 @@ import { } from '@prosopo/procaptcha' import { Manager, ProcaptchaConfig, ProcaptchaState, StateUpdateFn } from '@prosopo/procaptcha' import { ProviderApi } from '@prosopo/api' -import { Alert, Modal } from '@mui/material' +import { Alert, Backdrop, CircularProgress } from '@mui/material' import CaptchaComponent from './CaptchaComponent' +import theme from './theme' export interface ProcaptchaProps { config: ProcaptchaConfig @@ -36,7 +37,7 @@ const useProcaptcha = (initConfig: ProcaptchaConfig): [ProcaptchaState, StateUpd const [isHuman, setIsHuman] = useState(false) const [index, setIndex] = useState(-1) - const [solutions, setSolutions] = useRefAsState([]) + const [solutions, setSolutions] = useState([]) // take a deep copy of the config and store in state. This stops the config from being updated externally to this state management const [config] = useState(() => { return JSON.parse(JSON.stringify(initConfig)) @@ -44,9 +45,10 @@ const useProcaptcha = (initConfig: ProcaptchaConfig): [ProcaptchaState, StateUpd const [contract, setContract] = useRefAsState(undefined) const [provider, setProvider] = useRefAsState(undefined) const [captchaApi, setCaptchaApi] = useRefAsState(undefined) - const [showChallenge, setShowChallenge] = useState(false) + const [showModal, setShowModal] = useState(false) const [challenge, setChallenge] = useState(undefined) const [providerApi, setProviderApi] = useRefAsState(undefined) + const [loading, setLoading] = useState(false) const map = { isHuman: setIsHuman, @@ -55,9 +57,10 @@ const useProcaptcha = (initConfig: ProcaptchaConfig): [ProcaptchaState, StateUpd contract: setContract, provider: setProvider, captchaApi: setCaptchaApi, - showChallenge: setShowChallenge, + showModal: setShowModal, challenge: setChallenge, providerApi: setProviderApi, + loading: setLoading, // don't provide method for updating config, should remain constant } @@ -71,9 +74,10 @@ const useProcaptcha = (initConfig: ProcaptchaConfig): [ProcaptchaState, StateUpd contract, provider, captchaApi, - showChallenge, + showModal, challenge, providerApi, + loading, }, // and method to update the state (nextState: Partial) => { @@ -92,7 +96,6 @@ const useProcaptcha = (initConfig: ProcaptchaConfig): [ProcaptchaState, StateUpd } }, ] - } export const Procaptcha = (props: ProcaptchaProps) => { @@ -108,7 +111,7 @@ export const Procaptcha = (props: ProcaptchaProps) => { return ( - + theme.zIndex.drawer + 1 }}> {state.challenge ? ( { solutions={state.solutions} onSubmit={manager.submit} onCancel={manager.cancel} - onClick={manager.click} - /> + onClick={manager.onClick} + > ) : ( - No challenge + No challenge set. )} - + { flexWrap: 'wrap', }} > - - + + + + + + + + + I am a human From 9770ec7ef3158cfe13b876fed71413f5271a65c3 Mon Sep 17 00:00:00 2001 From: George Oastler Date: Mon, 21 Nov 2022 11:09:58 +0000 Subject: [PATCH 29/37] fixed onNext proceed to next challenge round --- src/components/CaptchaComponent.tsx | 5 +++-- src/components/Procaptcha.tsx | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/CaptchaComponent.tsx b/src/components/CaptchaComponent.tsx index f7053fb..2c8c3b3 100644 --- a/src/components/CaptchaComponent.tsx +++ b/src/components/CaptchaComponent.tsx @@ -31,12 +31,13 @@ export interface CaptchaComponentProps { onSubmit: () => void onCancel: () => void onClick: (hash: string) => void + onNext: () => void } export const CaptchaComponent = (props: CaptchaComponentProps) => { const { t } = useTranslation() console.log('CaptchaComponent', props) - const { challenge, index, solutions, onSubmit, onCancel, onClick } = props + const { challenge, index, solutions, onSubmit, onCancel, onClick, onNext } = props const captcha = challenge.captchas[index] const solution = solutions[index] @@ -140,7 +141,7 @@ export const CaptchaComponent = (props: CaptchaComponentProps) => {