1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

Merge pull request #69 from oceanprotocol/feature/user-preferences

User preferences
This commit is contained in:
Matthias Kretschmann 2020-09-10 15:10:52 +02:00 committed by GitHub
commit 87b0e65b98
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 324 additions and 64 deletions

View File

@ -4,5 +4,8 @@ module.exports = {
marketFeeAddress: marketFeeAddress:
process.env.GATSBY_MARKET_FEE_ADDRESS || process.env.GATSBY_MARKET_FEE_ADDRESS ||
'0x903322C7E45A60d7c8C3EA236c5beA9Af86310c7', '0x903322C7E45A60d7c8C3EA236c5beA9Af86310c7',
marketFeeAmount: process.env.GATSBY_MARKET_FEE_AMOUNT || '0.1' // in % marketFeeAmount: process.env.GATSBY_MARKET_FEE_AMOUNT || '0.1', // in %
// Used for conversion display, can be whatever coingecko API supports
// see: https://api.coingecko.com/api/v3/simple/supported_vs_currencies
currencies: ['EUR', 'USD', 'ETH', 'BTC']
} }

View File

@ -3,6 +3,7 @@ import { FormikProps, connect } from 'formik'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
import omit from 'lodash.omit' import omit from 'lodash.omit'
import isEqual from 'react-fast-compare' import isEqual from 'react-fast-compare'
import { Logger } from '@oceanprotocol/lib'
export interface PersistProps { export interface PersistProps {
name: string name: string
@ -21,7 +22,7 @@ class PersistImpl extends React.Component<
saveForm = debounce((data: FormikProps<any>) => { saveForm = debounce((data: FormikProps<any>) => {
const dataToSave = this.omitIgnoredFields(data) const dataToSave = this.omitIgnoredFields(data)
console.log('data tosave', dataToSave) Logger.log('data to save', dataToSave)
if (this.props.isSessionStorage) { if (this.props.isSessionStorage) {
window.sessionStorage.setItem(this.props.name, JSON.stringify(dataToSave)) window.sessionStorage.setItem(this.props.name, JSON.stringify(dataToSave))
} else { } else {
@ -31,10 +32,10 @@ class PersistImpl extends React.Component<
omitIgnoredFields = (data: FormikProps<any>) => { omitIgnoredFields = (data: FormikProps<any>) => {
const { ignoreFields } = this.props const { ignoreFields } = this.props
console.log('omit fiel', ignoreFields) Logger.log('omitted fields', ignoreFields)
const { values, touched, errors } = data const { values, touched, errors } = data
console.log('vale', values, omit(values, ignoreFields)) Logger.log('values', values, omit(values, ignoreFields))
return ignoreFields return ignoreFields
? omit( ? omit(
{ {

View File

@ -135,9 +135,8 @@
/* Size modifiers */ /* Size modifiers */
.small { .small {
composes: input;
font-size: var(--font-size-small); font-size: var(--font-size-small);
min-height: 32px; min-height: 34px;
padding: calc(var(--spacer) / 4); padding: calc(var(--spacer) / 4);
} }
@ -146,8 +145,8 @@
} }
.selectSmall { .selectSmall {
composes: select; composes: small;
height: 32px; height: 34px;
padding-right: 2rem; padding-right: 2rem;
/* custom arrow */ /* custom arrow */

View File

@ -10,14 +10,27 @@ const DefaultInput = (props: InputProps) => (
<input className={styles.input} id={props.name} {...props} /> <input className={styles.input} id={props.name} {...props} />
) )
export default function InputElement(props: InputProps): ReactElement { export default function InputElement({
const { type, options, name, prefix, postfix } = props type,
options,
name,
prefix,
postfix,
small,
field,
...props
}: InputProps): ReactElement {
switch (type) { switch (type) {
case 'select': case 'select':
return ( return (
<select id={name} className={styles.select} {...props}> <select
id={name}
className={`${styles.select} ${small && styles.selectSmall}`}
{...props}
>
{field !== undefined && field.value === '' && (
<option value="">---</option> <option value="">---</option>
)}
{options && {options &&
options options
.sort((a: string, b: string) => a.localeCompare(b)) .sort((a: string, b: string) => a.localeCompare(b))
@ -29,7 +42,9 @@ export default function InputElement(props: InputProps): ReactElement {
</select> </select>
) )
case 'textarea': case 'textarea':
return <textarea id={name} className={styles.input} {...props} /> return (
<textarea name={name} id={name} className={styles.input} {...props} />
)
case 'radio': case 'radio':
case 'checkbox': case 'checkbox':
return ( return (
@ -41,6 +56,7 @@ export default function InputElement(props: InputProps): ReactElement {
className={styles.radio} className={styles.radio}
id={slugify(option)} id={slugify(option)}
type={type} type={type}
name={name}
{...props} {...props}
/> />
<label className={styles.radioLabel} htmlFor={slugify(option)}> <label className={styles.radioLabel} htmlFor={slugify(option)}>
@ -51,20 +67,20 @@ export default function InputElement(props: InputProps): ReactElement {
</div> </div>
) )
case 'files': case 'files':
return <FilesInput {...props} /> return <FilesInput name={name} {...field} {...props} />
case 'price': case 'price':
return <Price {...props} /> return <Price name={name} {...field} {...props} />
case 'terms': case 'terms':
return <Terms {...props} /> return <Terms name={name} options={options} {...field} {...props} />
default: default:
return prefix || postfix ? ( return prefix || postfix ? (
<div className={`${prefix ? styles.prefixGroup : styles.postfixGroup}`}> <div className={`${prefix ? styles.prefixGroup : styles.postfixGroup}`}>
{prefix && <div className={styles.prefix}>{prefix}</div>} {prefix && <div className={styles.prefix}>{prefix}</div>}
<DefaultInput type={type || 'text'} {...props} /> <DefaultInput name={name} type={type || 'text'} {...props} />
{postfix && <div className={styles.postfix}>{postfix}</div>} {postfix && <div className={styles.postfix}>{postfix}</div>}
</div> </div>
) : ( ) : (
<DefaultInput type={type || 'text'} {...props} /> <DefaultInput name={name} type={type || 'text'} {...props} />
) )
} }
} }

View File

@ -37,10 +37,20 @@ export interface InputProps {
prefix?: string prefix?: string
postfix?: string postfix?: string
step?: string step?: string
defaultChecked?: boolean
small?: boolean
} }
export default function Input(props: Partial<InputProps>): ReactElement { export default function Input(props: Partial<InputProps>): ReactElement {
const { required, name, label, help, additionalComponent, field } = props const {
required,
name,
label,
help,
additionalComponent,
small,
field
} = props
const hasError = const hasError =
props.form && props.form &&
@ -60,7 +70,7 @@ export default function Input(props: Partial<InputProps>): ReactElement {
<Label htmlFor={name} required={required}> <Label htmlFor={name} required={required}>
{label} {label}
</Label> </Label>
<InputElement {...field} {...props} /> <InputElement small={small} {...field} {...props} />
{field && ( {field && (
<div className={styles.error}> <div className={styles.error}>

View File

@ -4,12 +4,11 @@ import { fetchData, isBrowser } from '../../../utils'
import styles from './Conversion.module.css' import styles from './Conversion.module.css'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import { formatCurrency } from '@coingecko/cryptoformat' import { formatCurrency } from '@coingecko/cryptoformat'
import { useUserPreferences } from '../../../providers/UserPreferences'
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
const currencies = 'EUR' // comma-separated list
const url = `https://api.coingecko.com/api/v3/simple/price?ids=ocean-protocol&vs_currencies=${currencies}&include_24hr_change=true`
export default function Conversion({ export default function Conversion({
price, price,
update = true, update = true,
@ -19,23 +18,30 @@ export default function Conversion({
update?: boolean update?: boolean
className?: string className?: string
}): ReactElement { }): ReactElement {
const [priceEur, setPriceEur] = useState('0.00') const { appConfig } = useSiteMetadata()
const tokenId = 'ocean-protocol'
const currencies = appConfig.currencies.join(',') // comma-separated list
const url = `https://api.coingecko.com/api/v3/simple/price?ids=${tokenId}&vs_currencies=${currencies}&include_24hr_change=true`
const { currency } = useUserPreferences()
const [priceConverted, setPriceConverted] = useState('0.00')
const styleClasses = cx({ const styleClasses = cx({
conversion: true, conversion: true,
[className]: className [className]: className
}) })
const onSuccess = async (data: { 'ocean-protocol': { eur: number } }) => { const onSuccess = async (data: { [tokenId]: { [key: string]: number } }) => {
if (!data) return if (!data) return
if (!price || price === '' || price === '0') { if (!price || price === '' || price === '0') {
setPriceEur('0.00') setPriceConverted('0.00')
return return
} }
const { eur } = data['ocean-protocol'] const values = data[tokenId]
const converted = eur * Number(price) const fiatValue = values[currency.toLowerCase()]
setPriceEur(`${formatCurrency(converted, 'EUR', undefined, true)}`) const converted = fiatValue * Number(price)
setPriceConverted(`${formatCurrency(converted, currency, undefined, true)}`)
} }
useEffect(() => { useEffect(() => {
@ -46,7 +52,7 @@ export default function Conversion({
if (isBrowser && price !== '0') { if (isBrowser && price !== '0') {
getData() getData()
} }
}, [price]) }, [price, currency])
if (update) { if (update) {
// Fetch new prices periodically with swr // Fetch new prices periodically with swr
@ -61,7 +67,7 @@ export default function Conversion({
className={styleClasses} className={styleClasses}
title="Approximation based on current spot price on Coingecko" title="Approximation based on current spot price on Coingecko"
> >
{priceEur} EUR {priceConverted} {currency}
</span> </span>
) )
} }

View File

@ -1,9 +1,12 @@
import React, { ReactElement, ReactNode } from 'react' import React, { ReactElement, ReactNode } from 'react'
import classNames from 'classnames/bind'
import loadable from '@loadable/component' import loadable from '@loadable/component'
import { useSpring, animated } from 'react-spring' import { useSpring, animated } from 'react-spring'
import styles from './Tooltip.module.css' import styles from './Tooltip.module.css'
import { ReactComponent as Info } from '../../images/info.svg' import { ReactComponent as Info } from '../../images/info.svg'
const cx = classNames.bind(styles)
const Tippy = loadable(() => import('@tippyjs/react/headless')) const Tippy = loadable(() => import('@tippyjs/react/headless'))
const animation = { const animation = {
@ -22,12 +25,14 @@ export default function Tooltip({
content, content,
children, children,
trigger, trigger,
disabled disabled,
className
}: { }: {
content: ReactNode content: ReactNode
children?: ReactNode children?: ReactNode
trigger?: string trigger?: string
disabled?: boolean disabled?: boolean
className?: string
}): ReactElement { }): ReactElement {
const [props, setSpring] = useSpring(() => animation.from) const [props, setSpring] = useSpring(() => animation.from)
@ -47,6 +52,11 @@ export default function Tooltip({
}) })
} }
const styleClasses = cx({
tooltip: true,
[className]: className
})
return ( return (
<Tippy <Tippy
interactive interactive
@ -69,10 +79,10 @@ export default function Tooltip({
onMount={onMount} onMount={onMount}
onHide={onHide} onHide={onHide}
fallback={ fallback={
<div className={styles.tooltip}>{children || <DefaultTrigger />}</div> <div className={styleClasses}>{children || <DefaultTrigger />}</div>
} }
> >
<div className={styles.tooltip}>{children || <DefaultTrigger />}</div> <div className={styleClasses}>{children || <DefaultTrigger />}</div>
</Tippy> </Tippy>
) )
} }

View File

@ -6,6 +6,7 @@ import Tabs from '../../../atoms/Tabs'
import Fixed from './Fixed' import Fixed from './Fixed'
import Dynamic from './Dynamic' import Dynamic from './Dynamic'
import { useField } from 'formik' import { useField } from 'formik'
import { useUserPreferences } from '../../../../providers/UserPreferences'
const query = graphql` const query = graphql`
query PriceFieldQuery { query PriceFieldQuery {
@ -35,6 +36,7 @@ const query = graphql`
` `
export default function Price(props: InputProps): ReactElement { export default function Price(props: InputProps): ReactElement {
const { debug } = useUserPreferences()
const data = useStaticQuery(query) const data = useStaticQuery(query)
const content = data.content.edges[0].node.childPagesJson.price const content = data.content.edges[0].node.childPagesJson.price
@ -89,9 +91,11 @@ export default function Price(props: InputProps): ReactElement {
return ( return (
<div className={styles.price}> <div className={styles.price}>
<Tabs items={tabs} handleTabChange={handleTabChange} /> <Tabs items={tabs} handleTabChange={handleTabChange} />
{debug === true && (
<pre> <pre>
<code>{JSON.stringify(field.value)}</code> <code>{JSON.stringify(field.value)}</code>
</pre> </pre>
)}
</div> </div>
) )
} }

View File

@ -58,6 +58,7 @@
.navigation li { .navigation li {
display: inline-block; display: inline-block;
vertical-align: middle;
} }
.navigation button, .navigation button,

View File

@ -6,6 +6,7 @@ import styles from './Menu.module.css'
import { useSiteMetadata } from '../../hooks/useSiteMetadata' import { useSiteMetadata } from '../../hooks/useSiteMetadata'
import { ReactComponent as Logo } from '@oceanprotocol/art/logo/logo.svg' import { ReactComponent as Logo } from '@oceanprotocol/art/logo/logo.svg'
import Container from '../atoms/Container' import Container from '../atoms/Container'
import UserPreferences from './UserPreferences'
const Wallet = loadable(() => import('./Wallet')) const Wallet = loadable(() => import('./Wallet'))
@ -49,6 +50,9 @@ export default function Menu(): ReactElement {
<MenuLink item={item} /> <MenuLink item={item} />
</li> </li>
))} ))}
<li>
<UserPreferences />
</li>
</ul> </ul>
</Container> </Container>
</nav> </nav>

View File

@ -0,0 +1,26 @@
import React, { ReactElement, ChangeEvent } from 'react'
import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
import { useUserPreferences } from '../../../providers/UserPreferences'
import Input from '../../atoms/Input'
export default function Currency(): ReactElement {
const { currency, setCurrency } = useUserPreferences()
const { appConfig } = useSiteMetadata()
return (
<li>
<Input
name="currency"
label="Currency"
help="Select your preferred currency."
type="select"
options={appConfig.currencies}
value={currency}
onChange={(e: ChangeEvent<HTMLSelectElement>) =>
setCurrency(e.target.value)
}
small
/>
</li>
)
}

View File

@ -0,0 +1,21 @@
import React, { ReactElement } from 'react'
import { useUserPreferences } from '../../../providers/UserPreferences'
import FormHelp from '../../atoms/Input/Help'
import InputElement from '../../atoms/Input/InputElement'
export default function Debug(): ReactElement {
const { debug, setDebug } = useUserPreferences()
return (
<li>
<InputElement
name="debug"
type="checkbox"
options={['Debug Mode']}
defaultChecked={debug === true}
onChange={() => setDebug(!debug)}
/>
<FormHelp>Show geeky information in some places.</FormHelp>
</li>
)
}

View File

@ -0,0 +1,36 @@
.preferences {
padding: calc(var(--spacer) / 2);
margin-left: var(--spacer);
cursor: pointer;
}
.icon {
fill: var(--brand-grey-light);
transition: fill 0.2s ease-out;
}
.preferences:hover .icon,
.preferences:focus .icon,
.preferences:active .icon,
.preferences[aria-expanded='true'] .icon {
fill: var(--brand-grey);
}
.preferencesDetails {
padding: calc(var(--spacer) / 2);
}
.preferencesDetails li > div {
margin-bottom: 0;
}
.preferencesDetails li {
border-bottom: 1px solid var(--brand-grey-lighter);
margin-bottom: calc(var(--spacer) / 2);
}
.preferencesDetails li:last-child,
.preferencesDetails li:last-child p {
border-bottom: none;
margin-bottom: 0;
}

View File

@ -0,0 +1,23 @@
import React, { ReactElement } from 'react'
import Tooltip from '../../atoms/Tooltip'
import { ReactComponent as Cog } from '../../../images/cog.svg'
import styles from './index.module.css'
import Currency from './Currency'
import Debug from './Debug'
export default function UserPreferences(): ReactElement {
return (
<Tooltip
content={
<ul className={styles.preferencesDetails}>
<Currency />
<Debug />
</ul>
}
trigger="click focus"
className={styles.preferences}
>
<Cog aria-label="Preferences" className={styles.icon} />
</Tooltip>
)
}

View File

@ -8,6 +8,7 @@ import Token from './Token'
import { Balance } from './' import { Balance } from './'
import PriceUnit from '../../../atoms/Price/PriceUnit' import PriceUnit from '../../../atoms/Price/PriceUnit'
import Actions from './Actions' import Actions from './Actions'
import { useUserPreferences } from '../../../../providers/UserPreferences'
// TODO: handle and display all fees somehow // TODO: handle and display all fees somehow
@ -22,6 +23,7 @@ export default function Add({
totalPoolTokens: string totalPoolTokens: string
totalBalance: Balance totalBalance: Balance
}): ReactElement { }): ReactElement {
const { debug } = useUserPreferences()
const { ocean, accountId, balance } = useOcean() const { ocean, accountId, balance } = useOcean()
const [amount, setAmount] = useState('') const [amount, setAmount] = useState('')
const [swapFee, setSwapFee] = useState<string>() const [swapFee, setSwapFee] = useState<string>()
@ -89,7 +91,7 @@ export default function Add({
<div className={styles.output}> <div className={styles.output}>
<div> <div>
<p>You will receive</p> <p>You will receive</p>
<Token symbol="BPT" balance={newPoolTokens} /> {debug === true && <Token symbol="BPT" balance={newPoolTokens} />}
<Token symbol="% of pool" balance={newPoolShare} /> <Token symbol="% of pool" balance={newPoolShare} />
</div> </div>
<div> <div>

View File

@ -12,6 +12,7 @@ import Remove from './Remove'
import Tooltip from '../../../atoms/Tooltip' import Tooltip from '../../../atoms/Tooltip'
import Conversion from '../../../atoms/Price/Conversion' import Conversion from '../../../atoms/Price/Conversion'
import EtherscanLink from '../../../atoms/EtherscanLink' import EtherscanLink from '../../../atoms/EtherscanLink'
import { useUserPreferences } from '../../../../providers/UserPreferences'
export interface Balance { export interface Balance {
ocean: string ocean: string
@ -23,6 +24,7 @@ export interface Balance {
*/ */
export default function Pool({ ddo }: { ddo: DDO }): ReactElement { export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
const { debug } = useUserPreferences()
const { ocean, accountId } = useOcean() const { ocean, accountId } = useOcean()
const { price, poolAddress } = useMetadata(ddo) const { price, poolAddress } = useMetadata(ddo)
@ -149,7 +151,7 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
</h3> </h3>
<Token symbol="OCEAN" balance={userBalance.ocean} /> <Token symbol="OCEAN" balance={userBalance.ocean} />
<Token symbol={dtSymbol} balance={userBalance.dt} /> <Token symbol={dtSymbol} balance={userBalance.dt} />
<Token symbol="BPT" balance={poolTokens} /> {debug === true && <Token symbol="BPT" balance={poolTokens} />}
<Token symbol="% of pool" balance={poolShare} /> <Token symbol="% of pool" balance={poolShare} />
</div> </div>
@ -157,7 +159,9 @@ export default function Pool({ ddo }: { ddo: DDO }): ReactElement {
<h3 className={styles.title}>Pool Statistics</h3> <h3 className={styles.title}>Pool Statistics</h3>
<Token symbol="OCEAN" balance={totalBalance.ocean} /> <Token symbol="OCEAN" balance={totalBalance.ocean} />
<Token symbol={dtSymbol} balance={totalBalance.dt} /> <Token symbol={dtSymbol} balance={totalBalance.dt} />
{debug === true && (
<Token symbol="BPT" balance={totalPoolTokens} /> <Token symbol="BPT" balance={totalPoolTokens} />
)}
</div> </div>
</div> </div>

View File

@ -8,6 +8,7 @@ import MetaSecondary from './MetaSecondary'
import styles from './index.module.css' import styles from './index.module.css'
import AssetActions from '../AssetActions' import AssetActions from '../AssetActions'
import { DDO } from '@oceanprotocol/lib' import { DDO } from '@oceanprotocol/lib'
import { useUserPreferences } from '../../../providers/UserPreferences'
export interface AssetContentProps { export interface AssetContentProps {
metadata: MetadataMarket metadata: MetadataMarket
@ -21,6 +22,7 @@ export default function AssetContent({
}: AssetContentProps): ReactElement { }: AssetContentProps): ReactElement {
const { datePublished } = metadata.main const { datePublished } = metadata.main
const { description, categories } = metadata.additionalInformation const { description, categories } = metadata.additionalInformation
const { debug } = useUserPreferences()
return ( return (
<article className={styles.grid}> <article className={styles.grid}>
@ -52,9 +54,11 @@ export default function AssetContent({
{/* <DeleteAction ddo={ddo} /> */} {/* <DeleteAction ddo={ddo} /> */}
</div> </div>
{debug === true && (
<pre> <pre>
<code>{JSON.stringify(ddo, null, 2)}</code> <code>{JSON.stringify(ddo, null, 2)}</code>
</pre> </pre>
)}
</div> </div>
<div> <div>
<div className={styles.sticky}> <div className={styles.sticky}>

View File

@ -12,6 +12,7 @@ import { transformPublishFormToMetadata } from './utils'
import Preview from './Preview' import Preview from './Preview'
import { MetadataPublishForm } from '../../../@types/MetaData' import { MetadataPublishForm } from '../../../@types/MetaData'
import { useSiteMetadata } from '../../../hooks/useSiteMetadata' import { useSiteMetadata } from '../../../hooks/useSiteMetadata'
import { useUserPreferences } from '../../../providers/UserPreferences'
export default function PublishPage({ export default function PublishPage({
content content
@ -19,6 +20,7 @@ export default function PublishPage({
content: { form: FormContent } content: { form: FormContent }
}): ReactElement { }): ReactElement {
const { marketFeeAddress, marketFeeAmount } = useSiteMetadata() const { marketFeeAddress, marketFeeAmount } = useSiteMetadata()
const { debug } = useUserPreferences()
const { publish, publishError, isLoading, publishStepText } = usePublish() const { publish, publishError, isLoading, publishStepText } = usePublish()
const navigate = useNavigate() const navigate = useNavigate()
@ -85,6 +87,8 @@ export default function PublishPage({
</div> </div>
</aside> </aside>
{debug === true && (
<>
<div> <div>
<h5>Collected Form Values</h5> <h5>Collected Form Values</h5>
<pre> <pre>
@ -106,6 +110,8 @@ export default function PublishPage({
</div> </div>
</> </>
)} )}
</>
)}
</Formik> </Formik>
</article> </article>
) )

View File

@ -8,6 +8,7 @@ import {
ConfigHelperNetworkName, ConfigHelperNetworkName,
ConfigHelperNetworkId ConfigHelperNetworkId
} from '@oceanprotocol/lib/dist/node/utils/ConfigHelper' } from '@oceanprotocol/lib/dist/node/utils/ConfigHelper'
import { UserPreferencesProvider } from '../providers/UserPreferences'
export function getOceanConfig( export function getOceanConfig(
network: ConfigHelperNetworkName | ConfigHelperNetworkId network: ConfigHelperNetworkName | ConfigHelperNetworkId
@ -30,8 +31,10 @@ export default function wrapRootElement({
initialConfig={oceanInitialConfig} initialConfig={oceanInitialConfig}
web3ModalOpts={web3ModalOpts} web3ModalOpts={web3ModalOpts}
> >
<UserPreferencesProvider>
<NetworkMonitor /> <NetworkMonitor />
{element} {element}
</UserPreferencesProvider>
</OceanProvider> </OceanProvider>
) )
} }

View File

@ -18,6 +18,7 @@ const query = graphql`
network network
marketFeeAddress marketFeeAddress
marketFeeAmount marketFeeAmount
currencies
} }
} }
} }

3
src/images/cog.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="20" height="20" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path d="M17.1593 10.98C17.1993 10.66 17.2293 10.34 17.2293 10C17.2293 9.66 17.1993 9.34 17.1593 9.02L19.2693 7.37C19.4593 7.22 19.5093 6.95 19.3893 6.73L17.3893 3.27C17.2693 3.05 16.9993 2.97 16.7793 3.05L14.2893 4.05C13.7693 3.65 13.2093 3.32 12.5993 3.07L12.2193 0.42C12.1893 0.18 11.9793 0 11.7293 0H7.72933C7.47933 0 7.26933 0.18 7.23933 0.42L6.85933 3.07C6.24933 3.32 5.68933 3.66 5.16933 4.05L2.67933 3.05C2.44933 2.96 2.18933 3.05 2.06933 3.27L0.0693316 6.73C-0.0606684 6.95 -0.000668302 7.22 0.189332 7.37L2.29933 9.02C2.25933 9.34 2.22933 9.67 2.22933 10C2.22933 10.33 2.25933 10.66 2.29933 10.98L0.189332 12.63C-0.000668302 12.78 -0.0506684 13.05 0.0693316 13.27L2.06933 16.73C2.18933 16.95 2.45933 17.03 2.67933 16.95L5.16933 15.95C5.68933 16.35 6.24933 16.68 6.85933 16.93L7.23933 19.58C7.26933 19.82 7.47933 20 7.72933 20H11.7293C11.9793 20 12.1893 19.82 12.2193 19.58L12.5993 16.93C13.2093 16.68 13.7693 16.34 14.2893 15.95L16.7793 16.95C17.0093 17.04 17.2693 16.95 17.3893 16.73L19.3893 13.27C19.5093 13.05 19.4593 12.78 19.2693 12.63L17.1593 10.98V10.98ZM9.72933 13.5C7.79933 13.5 6.22933 11.93 6.22933 10C6.22933 8.07 7.79933 6.5 9.72933 6.5C11.6593 6.5 13.2293 8.07 13.2293 10C13.2293 11.93 11.6593 13.5 9.72933 13.5Z" />
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,77 @@
import React, {
createContext,
useContext,
ReactElement,
ReactNode,
useState,
useEffect
} from 'react'
import { Logger } from '@oceanprotocol/lib'
import { LogLevel } from '@oceanprotocol/lib/dist/node/utils/Logger'
interface UserPreferencesValue {
debug: boolean
currency: string
setDebug?: (value: boolean) => void
setCurrency?: (value: string) => void
}
const UserPreferencesContext = createContext(null)
const localStorageKey = 'ocean-user-preferences'
function getLocalStorage() {
const storageParsed =
typeof window !== 'undefined' &&
JSON.parse(window.localStorage.getItem(localStorageKey))
return storageParsed
}
function setLocalStorage(values: UserPreferencesValue) {
return (
typeof window !== 'undefined' &&
window.localStorage.setItem(localStorageKey, JSON.stringify(values))
)
}
function UserPreferencesProvider({
children
}: {
children: ReactNode
}): ReactElement {
const localStorage = getLocalStorage()
// Set default values from localStorage
const [debug, setDebug] = useState<boolean>(
(localStorage && localStorage.debug) || false
)
const [currency, setCurrency] = useState<string>(
(localStorage && localStorage.currency) || 'EUR'
)
// Write values to localStorage on change
useEffect(() => {
setLocalStorage({ debug, currency })
}, [debug, currency])
// Set ocen-lib-js log levels, default: Error
useEffect(() => {
debug === true
? Logger.setLevel(LogLevel.Verbose)
: Logger.setLevel(LogLevel.Error)
}, [debug])
return (
<UserPreferencesContext.Provider
value={{ debug, currency, setDebug, setCurrency } as UserPreferencesValue}
>
{children}
</UserPreferencesContext.Provider>
)
}
// Helper hook to access the provider values
const useUserPreferences = (): UserPreferencesValue =>
useContext(UserPreferencesContext)
export { UserPreferencesProvider, useUserPreferences, UserPreferencesValue }