diff --git a/dapp-oeth/pages/_app.js b/dapp-oeth/pages/_app.js index 970ca5b8d6..820a7676c7 100644 --- a/dapp-oeth/pages/_app.js +++ b/dapp-oeth/pages/_app.js @@ -1,4 +1,5 @@ import React, { useEffect, useState } from 'react' +import dynamic from 'next/dynamic' import { useRouter } from 'next/router' import Head from 'next/head' import { useCookies } from 'react-cookie' @@ -79,6 +80,10 @@ const client = createClient({ connectors, }) +const GeoFenceCheck = dynamic(() => import('components/GeoFenceCheck'), { + ssr: false, +}) + function App({ Component, pageProps, err }) { const { address: account, isConnected: active } = useAccount() const [locale, setLocale] = useState('en_US') @@ -125,6 +130,7 @@ function App({ Component, pageProps, err }) { + diff --git a/dapp-oeth/src/components/GeoFenceCheck.js b/dapp-oeth/src/components/GeoFenceCheck.js new file mode 100644 index 0000000000..f1d477f35f --- /dev/null +++ b/dapp-oeth/src/components/GeoFenceCheck.js @@ -0,0 +1,241 @@ +import { useState } from 'react' +import { Modal } from 'react-bootstrap' +import useLocalStorage from 'hooks/useLocalStorage' + +const GeoFenceCheck = () => { + const { data: hasConfirmedGeoLocation, onSetItem } = useLocalStorage( + '@originprotocol/oeth-geo-check', + false + ) + + const [isChecked, setIsChecked] = useState(false) + + const onAckGeoFence = () => { + onSetItem(true) + } + + return ( + <> + +
+
+

Restricted Access

+
+
+

+ The Origin Ether dapp is not available to restricted + jurisdictions. Before proceeding, please carefully read the + following: +

+
+
    +
  • + You confirm that you are not a resident of, citizen of, + located in, incorporated in, or have a registered office in + the United States or any country or region currently currently + subject to sanctions by the United States. +
  • +
  • + You affirm that you are not a subject of economic or trade + sanctions administered or enforced by any governmental + authority or otherwise designated on any list of prohibited or + restricted parties, including the list maintained by the + Office of Foreign Assets Control of the U.S. Department of the + Treasury. +
  • +
  • + You agree not to use any VPN or other privacy or anonymization + tools or techniques to attempt to circumvent these eligibility + restrictions. +
  • +
  • + You are lawfully permitted to access this site. You understand + and accept the risks associated with using Origin Ether. +
  • +
+
+
+ +
+
+ +
+
+ + + ) +} + +export default GeoFenceCheck diff --git a/dapp-oeth/src/hooks/useLocalStorage.js b/dapp-oeth/src/hooks/useLocalStorage.js new file mode 100644 index 0000000000..548a18236e --- /dev/null +++ b/dapp-oeth/src/hooks/useLocalStorage.js @@ -0,0 +1,129 @@ +import { useCallback, useEffect, useState } from 'react' + +const LOCAL_STORAGE_CHANGE_EVENT_NAME = 'onLocalStorageChange' + +const isTypeOfLocalStorageChanged = (evt) => { + return !!evt && evt.type === LOCAL_STORAGE_CHANGE_EVENT_NAME +} + +const isBrowser = () => { + return typeof window !== 'undefined' && typeof window.document !== 'undefined' +} + +const writeStorage = (key, value) => { + if (!isBrowser()) { + return + } + + try { + window.localStorage.setItem( + key, + typeof value === 'object' ? JSON.stringify(value) : `${value}` + ) + window.dispatchEvent( + new CustomEvent(LOCAL_STORAGE_CHANGE_EVENT_NAME, { + detail: { key, value }, + }) + ) + } catch (err) { + if ( + err instanceof TypeError && + err.message.includes('circular structure') + ) { + throw new TypeError( + 'The object that was given to the writeStorage function has circular references.\n' + + 'For more information, check here: ' + + 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value' + ) + } + throw err + } +} + +const deleteFromStorage = (key) => { + if (!isBrowser()) { + return + } + window.localStorage.removeItem(key) + window.dispatchEvent( + new CustomEvent(LOCAL_STORAGE_CHANGE_EVENT_NAME, { + detail: { key, value: null }, + }) + ) +} + +const tryParse = (value) => { + try { + return JSON.parse(value) + } catch { + return value + } +} + +const useLocalStorage = (key, defaultValue) => { + const [localState, updateLocalState] = useState( + window.localStorage.getItem(key) === null + ? defaultValue + : tryParse(window.localStorage.getItem(key)) + ) + + const onLocalStorageChange = useCallback( + (event) => { + if (isTypeOfLocalStorageChanged(event)) { + if (event.detail.key === key) { + updateLocalState(event.detail.value) + } + } else { + if (event.key === key) { + updateLocalState( + event.newValue === null ? null : tryParse(event.newValue) + ) + } + } + }, + [updateLocalState, key] + ) + + useEffect(() => { + if (!isBrowser()) { + return + } + + const listener = (e) => { + onLocalStorageChange(e) + } + + window.addEventListener(LOCAL_STORAGE_CHANGE_EVENT_NAME, listener) + + // The storage event only works in the context of other documents (eg. other browser tabs) + window.addEventListener('storage', listener) + + // Write default value to the local storage if there currently isn't any value there. + if (window.localStorage.getItem(key) === null && defaultValue !== null) { + writeStorage(key, defaultValue) + } + + return () => { + window.removeEventListener(LOCAL_STORAGE_CHANGE_EVENT_NAME, listener) + window.removeEventListener('storage', listener) + } + }, [key, defaultValue, onLocalStorageChange]) + + const writeState = useCallback( + (value) => + value instanceof Function + ? writeStorage(key, value(localState)) + : writeStorage(key, value), + [key] + ) + + const deleteState = useCallback(() => deleteFromStorage(key), [key]) + + return { + data: localState ?? defaultValue, + onSetItem: writeState, + onRemoveItem: deleteState, + } +} + +export default useLocalStorage diff --git a/dapp/pages/_app.js b/dapp/pages/_app.js index 0bc51c94af..48a19b98a4 100644 --- a/dapp/pages/_app.js +++ b/dapp/pages/_app.js @@ -1,5 +1,6 @@ import React, { useEffect, useState } from 'react' import { useRouter } from 'next/router' +import dynamic from 'next/dynamic' import Head from 'next/head' import { useCookies } from 'react-cookie' import { useStoreState } from 'pullstate' @@ -80,6 +81,10 @@ const client = createClient({ connectors, }) +const GeoFenceCheck = dynamic(() => import('components/GeoFenceCheck'), { + ssr: false, +}) + function App({ Component, pageProps, err }) { const [locale, setLocale] = useState('en_US') const { address: account, isConnected: active } = useAccount() @@ -133,6 +138,7 @@ function App({ Component, pageProps, err }) { + diff --git a/dapp/src/components/GeoFenceCheck.js b/dapp/src/components/GeoFenceCheck.js new file mode 100644 index 0000000000..6430e68b76 --- /dev/null +++ b/dapp/src/components/GeoFenceCheck.js @@ -0,0 +1,235 @@ +import { useState } from 'react' +import { Modal } from 'react-bootstrap' +import useLocalStorage from 'hooks/useLocalStorage' + +const GeoFenceCheck = () => { + const { data: hasConfirmedGeoLocation, onSetItem } = useLocalStorage( + '@originprotocol/ousd-geo-check', + false + ) + + const [isChecked, setIsChecked] = useState(false) + + const onAckGeoFence = () => { + onSetItem(true) + } + + return ( + <> + +
+
+

Restricted Access

+
+
+

+ The Origin Dollar dapp is not available to restricted + jurisdictions. Before proceeding, please carefully read the + following: +

+
+
    +
  • + You confirm that you are not a resident of, citizen of, + located in, incorporated in, or have a registered office in + the United States or any country or region currently currently + subject to sanctions by the United States. +
  • +
  • + You affirm that you are not a subject of economic or trade + sanctions administered or enforced by any governmental + authority or otherwise designated on any list of prohibited or + restricted parties, including the list maintained by the + Office of Foreign Assets Control of the U.S. Department of the + Treasury. +
  • +
  • + You agree not to use any VPN or other privacy or anonymization + tools or techniques to attempt to circumvent these eligibility + restrictions. +
  • +
  • + You are lawfully permitted to access this site. You understand + and accept the risks associated with using Origin Dollar. +
  • +
+
+
+ +
+
+ +
+
+ + + ) +} + +export default GeoFenceCheck diff --git a/dapp/src/hooks/useLocalStorage.js b/dapp/src/hooks/useLocalStorage.js new file mode 100644 index 0000000000..548a18236e --- /dev/null +++ b/dapp/src/hooks/useLocalStorage.js @@ -0,0 +1,129 @@ +import { useCallback, useEffect, useState } from 'react' + +const LOCAL_STORAGE_CHANGE_EVENT_NAME = 'onLocalStorageChange' + +const isTypeOfLocalStorageChanged = (evt) => { + return !!evt && evt.type === LOCAL_STORAGE_CHANGE_EVENT_NAME +} + +const isBrowser = () => { + return typeof window !== 'undefined' && typeof window.document !== 'undefined' +} + +const writeStorage = (key, value) => { + if (!isBrowser()) { + return + } + + try { + window.localStorage.setItem( + key, + typeof value === 'object' ? JSON.stringify(value) : `${value}` + ) + window.dispatchEvent( + new CustomEvent(LOCAL_STORAGE_CHANGE_EVENT_NAME, { + detail: { key, value }, + }) + ) + } catch (err) { + if ( + err instanceof TypeError && + err.message.includes('circular structure') + ) { + throw new TypeError( + 'The object that was given to the writeStorage function has circular references.\n' + + 'For more information, check here: ' + + 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value' + ) + } + throw err + } +} + +const deleteFromStorage = (key) => { + if (!isBrowser()) { + return + } + window.localStorage.removeItem(key) + window.dispatchEvent( + new CustomEvent(LOCAL_STORAGE_CHANGE_EVENT_NAME, { + detail: { key, value: null }, + }) + ) +} + +const tryParse = (value) => { + try { + return JSON.parse(value) + } catch { + return value + } +} + +const useLocalStorage = (key, defaultValue) => { + const [localState, updateLocalState] = useState( + window.localStorage.getItem(key) === null + ? defaultValue + : tryParse(window.localStorage.getItem(key)) + ) + + const onLocalStorageChange = useCallback( + (event) => { + if (isTypeOfLocalStorageChanged(event)) { + if (event.detail.key === key) { + updateLocalState(event.detail.value) + } + } else { + if (event.key === key) { + updateLocalState( + event.newValue === null ? null : tryParse(event.newValue) + ) + } + } + }, + [updateLocalState, key] + ) + + useEffect(() => { + if (!isBrowser()) { + return + } + + const listener = (e) => { + onLocalStorageChange(e) + } + + window.addEventListener(LOCAL_STORAGE_CHANGE_EVENT_NAME, listener) + + // The storage event only works in the context of other documents (eg. other browser tabs) + window.addEventListener('storage', listener) + + // Write default value to the local storage if there currently isn't any value there. + if (window.localStorage.getItem(key) === null && defaultValue !== null) { + writeStorage(key, defaultValue) + } + + return () => { + window.removeEventListener(LOCAL_STORAGE_CHANGE_EVENT_NAME, listener) + window.removeEventListener('storage', listener) + } + }, [key, defaultValue, onLocalStorageChange]) + + const writeState = useCallback( + (value) => + value instanceof Function + ? writeStorage(key, value(localState)) + : writeStorage(key, value), + [key] + ) + + const deleteState = useCallback(() => deleteFromStorage(key), [key]) + + return { + data: localState ?? defaultValue, + onSetItem: writeState, + onRemoveItem: deleteState, + } +} + +export default useLocalStorage diff --git a/dapp/styles/globals.css b/dapp/styles/globals.css index 673ff3e8b9..e49e74b1d1 100644 --- a/dapp/styles/globals.css +++ b/dapp/styles/globals.css @@ -45,33 +45,117 @@ main { } main.dapp { - background-image: -webkit-gradient( linear, left top, left bottom, color-stop( 451px, #183140 ), color-stop( 451px, #183140 ), color-stop( 451px, #fafbfc ) ); /* Chrome, Safari4+ */ - background-image: -webkit-linear-gradient( top, #183140 451px, #183140 451px, #fafbfc 451px ); /* Chrome10+, Safari5.1+ */ - background-image: -moz-linear-gradient( top, #183140 451px, #183140 451px, #fafbfc 451px ); /* Fx3.6+ */ - background-image: linear-gradient( #183140 451px, #183140 451px, #fafbfc 451px ); /* W3C */ + background-image: -webkit-gradient( + linear, + left top, + left bottom, + color-stop(451px, #183140), + color-stop(451px, #183140), + color-stop(451px, #fafbfc) + ); /* Chrome, Safari4+ */ + background-image: -webkit-linear-gradient( + top, + #183140 451px, + #183140 451px, + #fafbfc 451px + ); /* Chrome10+, Safari5.1+ */ + background-image: -moz-linear-gradient( + top, + #183140 451px, + #183140 451px, + #fafbfc 451px + ); /* Fx3.6+ */ + background-image: linear-gradient( + #183140 451px, + #183140 451px, + #fafbfc 451px + ); /* W3C */ padding-bottom: 150px; } main.dapp.medium { - background-image: -webkit-gradient( linear, left top, left bottom, color-stop( 300px, #183140 ), color-stop( 300px, #183140 ), color-stop( 300px, #fafbfc ) ); /* Chrome, Safari4+ */ - background-image: -webkit-linear-gradient( top, #183140 300px, #183140 300px, #fafbfc 300px ); /* Chrome10+, Safari5.1+ */ - background-image: -moz-linear-gradient( top, #183140 300px, #183140 300px, #fafbfc 300px ); /* Fx3.6+ */ - background-image: linear-gradient( #183140 300px, #183140 300px, #fafbfc 300px ); /* W3C */ + background-image: -webkit-gradient( + linear, + left top, + left bottom, + color-stop(300px, #183140), + color-stop(300px, #183140), + color-stop(300px, #fafbfc) + ); /* Chrome, Safari4+ */ + background-image: -webkit-linear-gradient( + top, + #183140 300px, + #183140 300px, + #fafbfc 300px + ); /* Chrome10+, Safari5.1+ */ + background-image: -moz-linear-gradient( + top, + #183140 300px, + #183140 300px, + #fafbfc 300px + ); /* Fx3.6+ */ + background-image: linear-gradient( + #183140 300px, + #183140 300px, + #fafbfc 300px + ); /* W3C */ } main.dapp.short { - background-image: -webkit-gradient( linear, left top, left bottom, color-stop( 94px, #183140 ), color-stop( 94px, #183140 ), color-stop( 94px, #fafbfc ) ); /* Chrome, Safari4+ */ - background-image: -webkit-linear-gradient( top, #183140 94px, #183140 94px, #fafbfc 94px ); /* Chrome10+, Safari5.1+ */ - background-image: -moz-linear-gradient( top, #183140 94px, #183140 94px, #fafbfc 94px ); /* Fx3.6+ */ - background-image: linear-gradient( #183140 94px, #183140 94px, #fafbfc 94px ); /* W3C */ + background-image: -webkit-gradient( + linear, + left top, + left bottom, + color-stop(94px, #183140), + color-stop(94px, #183140), + color-stop(94px, #fafbfc) + ); /* Chrome, Safari4+ */ + background-image: -webkit-linear-gradient( + top, + #183140 94px, + #183140 94px, + #fafbfc 94px + ); /* Chrome10+, Safari5.1+ */ + background-image: -moz-linear-gradient( + top, + #183140 94px, + #183140 94px, + #fafbfc 94px + ); /* Fx3.6+ */ + background-image: linear-gradient( + #183140 94px, + #183140 94px, + #fafbfc 94px + ); /* W3C */ padding-bottom: 132px; } main.dapp.shorter { - background-image: -webkit-gradient( linear, left top, left bottom, color-stop( 282px, #183140 ), color-stop( 282px, #183140 ), color-stop( 282px, #fafbfc ) ); /* Chrome, Safari4+ */ - background-image: -webkit-linear-gradient( top, #183140 282px, #183140 282px, #fafbfc 282px ); /* Chrome10+, Safari5.1+ */ - background-image: -moz-linear-gradient( top, #183140 282px, #183140 282px, #fafbfc 282px ); /* Fx3.6+ */ - background-image: linear-gradient( #183140 282px, #183140 282px, #fafbfc 282px ); /* W3C */ + background-image: -webkit-gradient( + linear, + left top, + left bottom, + color-stop(282px, #183140), + color-stop(282px, #183140), + color-stop(282px, #fafbfc) + ); /* Chrome, Safari4+ */ + background-image: -webkit-linear-gradient( + top, + #183140 282px, + #183140 282px, + #fafbfc 282px + ); /* Chrome10+, Safari5.1+ */ + background-image: -moz-linear-gradient( + top, + #183140 282px, + #183140 282px, + #fafbfc 282px + ); /* Fx3.6+ */ + background-image: linear-gradient( + #183140 282px, + #183140 282px, + #fafbfc 282px + ); /* W3C */ padding-bottom: 132px; } @@ -105,33 +189,57 @@ section.dim { .gradient1 { color: #141519; - background-image: -webkit-linear-gradient(left, #fedba8 -3.29%, #cf75d5 106.42%); + background-image: -webkit-linear-gradient( + left, + #fedba8 -3.29%, + #cf75d5 106.42% + ); } .text-gradient1 { - background-image: -webkit-linear-gradient(left, #fedba8 -3.29%, #cf75d5 106.42%); + background-image: -webkit-linear-gradient( + left, + #fedba8 -3.29%, + #cf75d5 106.42% + ); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .gradient2 { color: #fafbfb; - background-image: -webkit-linear-gradient(left, #8C66FC -28.99%, #0274F1 144.97%); + background-image: -webkit-linear-gradient( + left, + #8c66fc -28.99%, + #0274f1 144.97% + ); } .text-gradient2 { - background-image: -webkit-linear-gradient(left, #8C66FC -28.99%, #0274F1 144.97%); + background-image: -webkit-linear-gradient( + left, + #8c66fc -28.99%, + #0274f1 144.97% + ); -webkit-background-clip: text; -webkit-text-fill-color: transparent; } .gradient3 { - background-image: -webkit-linear-gradient(left, #B361E6 -28.99%, #6A36FC 144.97%); + background-image: -webkit-linear-gradient( + left, + #b361e6 -28.99%, + #6a36fc 144.97% + ); } .gradient4 { color: #fafbfb; - background-image: -webkit-linear-gradient(left, #0d0033 -28.99%, #001124 144.97%); + background-image: -webkit-linear-gradient( + left, + #0d0033 -28.99%, + #001124 144.97% + ); } .mono { @@ -312,7 +420,10 @@ section.dim { } .Toastify__toast--error { - +} + +.modal-content { + background-color: transparent !important; } @media (max-width: 992px) { @@ -329,10 +440,31 @@ section.dim { } main.dapp.shorter { - background-image: -webkit-gradient( linear, left top, left bottom, color-stop( 439px, #183140 ), color-stop( 439px, #183140 ), color-stop( 439px, #fafbfc ) ); /* Chrome, Safari4+ */ - background-image: -webkit-linear-gradient( top, #183140 439px, #183140 439px, #fafbfc 439px ); /* Chrome10+, Safari5.1+ */ - background-image: -moz-linear-gradient( top, #183140 439px, #183140 439px, #fafbfc 439px ); /* Fx3.6+ */ - background-image: linear-gradient( #183140 439px, #183140 439px, #fafbfc 439px ); /* W3C */ + background-image: -webkit-gradient( + linear, + left top, + left bottom, + color-stop(439px, #183140), + color-stop(439px, #183140), + color-stop(439px, #fafbfc) + ); /* Chrome, Safari4+ */ + background-image: -webkit-linear-gradient( + top, + #183140 439px, + #183140 439px, + #fafbfc 439px + ); /* Chrome10+, Safari5.1+ */ + background-image: -moz-linear-gradient( + top, + #183140 439px, + #183140 439px, + #fafbfc 439px + ); /* Fx3.6+ */ + background-image: linear-gradient( + #183140 439px, + #183140 439px, + #fafbfc 439px + ); /* W3C */ padding-bottom: 132px; } } @@ -347,4 +479,4 @@ section.dim { .container.nav { max-width: none !important; } -} \ No newline at end of file +}