Merge pull request #192 from ekzyis/103-add-other-currencies
Support other currencies
This commit is contained in:
commit
0ff9bbc92d
|
@ -43,7 +43,7 @@ export function getGetServerSideProps (query, variables = null, notFoundFunc, re
|
||||||
query: ME_SSR
|
query: ME_SSR
|
||||||
})
|
})
|
||||||
|
|
||||||
const price = await getPrice()
|
const price = await getPrice(me?.fiatCurrency)
|
||||||
|
|
||||||
// we want to use client-side cache
|
// we want to use client-side cache
|
||||||
if (nodata && query) {
|
if (nodata && query) {
|
||||||
|
|
|
@ -29,7 +29,7 @@ export default gql`
|
||||||
|
|
||||||
extend type Mutation {
|
extend type Mutation {
|
||||||
setName(name: String!): Boolean
|
setName(name: String!): Boolean
|
||||||
setSettings(tipDefault: Int!, noteItemSats: Boolean!, noteEarning: Boolean!,
|
setSettings(tipDefault: Int!, fiatCurrency: String!, noteItemSats: Boolean!, noteEarning: Boolean!,
|
||||||
noteAllDescendants: Boolean!, noteMentions: Boolean!, noteDeposits: Boolean!,
|
noteAllDescendants: Boolean!, noteMentions: Boolean!, noteDeposits: Boolean!,
|
||||||
noteInvites: Boolean!, noteJobIndicator: Boolean!, hideInvoiceDesc: Boolean!,
|
noteInvites: Boolean!, noteJobIndicator: Boolean!, hideInvoiceDesc: Boolean!,
|
||||||
wildWestMode: Boolean!, greeterMode: Boolean!): User
|
wildWestMode: Boolean!, greeterMode: Boolean!): User
|
||||||
|
@ -59,6 +59,7 @@ export default gql`
|
||||||
hasNewNotes: Boolean!
|
hasNewNotes: Boolean!
|
||||||
hasInvites: Boolean!
|
hasInvites: Boolean!
|
||||||
tipDefault: Int!
|
tipDefault: Int!
|
||||||
|
fiatCurrency: String!
|
||||||
bio: Item
|
bio: Item
|
||||||
bioId: Int
|
bioId: Int
|
||||||
photoId: Int
|
photoId: Int
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Formik, Form as FormikForm, useFormikContext, useField, FieldArray } fr
|
||||||
import React, { useEffect, useRef, useState } from 'react'
|
import React, { useEffect, useRef, useState } from 'react'
|
||||||
import copy from 'clipboard-copy'
|
import copy from 'clipboard-copy'
|
||||||
import Thumb from '../svgs/thumb-up-fill.svg'
|
import Thumb from '../svgs/thumb-up-fill.svg'
|
||||||
import { Col, Dropdown, Nav } from 'react-bootstrap'
|
import { Col, Dropdown as BootstrapDropdown, Nav } from 'react-bootstrap'
|
||||||
import Markdown from '../svgs/markdown-line.svg'
|
import Markdown from '../svgs/markdown-line.svg'
|
||||||
import styles from './form.module.css'
|
import styles from './form.module.css'
|
||||||
import Text from '../components/text'
|
import Text from '../components/text'
|
||||||
|
@ -269,10 +269,10 @@ export function InputUserSuggest ({ label, groupClassName, ...props }) {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Dropdown show={suggestions.array.length > 0}>
|
<BootstrapDropdown show={suggestions.array.length > 0}>
|
||||||
<Dropdown.Menu className={styles.suggestionsMenu}>
|
<BootstrapDropdown.Menu className={styles.suggestionsMenu}>
|
||||||
{suggestions.array.map((v, i) =>
|
{suggestions.array.map((v, i) =>
|
||||||
<Dropdown.Item
|
<BootstrapDropdown.Item
|
||||||
key={v.name}
|
key={v.name}
|
||||||
active={suggestions.index === i}
|
active={suggestions.index === i}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
@ -281,9 +281,9 @@ export function InputUserSuggest ({ label, groupClassName, ...props }) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{v.name}
|
{v.name}
|
||||||
</Dropdown.Item>)}
|
</BootstrapDropdown.Item>)}
|
||||||
</Dropdown.Menu>
|
</BootstrapDropdown.Menu>
|
||||||
</Dropdown>
|
</BootstrapDropdown>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -429,3 +429,21 @@ export function SyncForm ({
|
||||||
</Formik>
|
</Formik>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function Dropdown ({ label, items, groupClassName, ...props }) {
|
||||||
|
const [field, _, helper] = useField({ ...props, type: 'input' })
|
||||||
|
return (
|
||||||
|
<FormGroup label={label} className={groupClassName}>
|
||||||
|
<BootstrapDropdown>
|
||||||
|
<BootstrapDropdown.Toggle>
|
||||||
|
{field.value}
|
||||||
|
</BootstrapDropdown.Toggle>
|
||||||
|
<BootstrapDropdown.Menu>
|
||||||
|
{items.map(item => (
|
||||||
|
<BootstrapDropdown.Item onSelect={() => helper.setValue(item)}>{item}</BootstrapDropdown.Item>
|
||||||
|
))}
|
||||||
|
</BootstrapDropdown.Menu>
|
||||||
|
</BootstrapDropdown>
|
||||||
|
</FormGroup>
|
||||||
|
)
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ import styles from '../styles/post.module.css'
|
||||||
import { useLazyQuery, gql, useMutation } from '@apollo/client'
|
import { useLazyQuery, gql, useMutation } from '@apollo/client'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import { usePrice } from './price'
|
import { CURRENCY_SYMBOLS, usePrice } from './price'
|
||||||
import Avatar from './avatar'
|
import Avatar from './avatar'
|
||||||
import BootstrapForm from 'react-bootstrap/Form'
|
import BootstrapForm from 'react-bootstrap/Form'
|
||||||
import Alert from 'react-bootstrap/Alert'
|
import Alert from 'react-bootstrap/Alert'
|
||||||
|
@ -36,13 +36,16 @@ function satsMin2Mo (minute) {
|
||||||
|
|
||||||
function PriceHint ({ monthly }) {
|
function PriceHint ({ monthly }) {
|
||||||
const price = usePrice()
|
const price = usePrice()
|
||||||
|
const { fiatCurrency } = useMe();
|
||||||
|
const fiatSymbol = CURRENCY_SYMBOLS[fiatCurrency]
|
||||||
|
|
||||||
if (!price || !monthly) {
|
if (!price || !monthly) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const fixed = (n, f) => Number.parseFloat(n).toFixed(f)
|
const fixed = (n, f) => Number.parseFloat(n).toFixed(f)
|
||||||
const fiat = fixed((price / 100000000) * monthly, 0)
|
const fiat = fixed((price / 100000000) * monthly, 0)
|
||||||
|
|
||||||
return <span className='text-muted'>{monthly} sats/mo which is ${fiat}/mo</span>
|
return <span className='text-muted'>{monthly} sats/mo which is {fiatSymbol}{fiat}/mo</span>
|
||||||
}
|
}
|
||||||
|
|
||||||
// need to recent list items
|
// need to recent list items
|
||||||
|
|
|
@ -2,6 +2,7 @@ import React, { useContext, useEffect, useState } from 'react'
|
||||||
import { Button } from 'react-bootstrap'
|
import { Button } from 'react-bootstrap'
|
||||||
import useSWR from 'swr'
|
import useSWR from 'swr'
|
||||||
import { fixedDecimal } from '../lib/format'
|
import { fixedDecimal } from '../lib/format'
|
||||||
|
import { useMe } from './me'
|
||||||
|
|
||||||
const fetcher = url => fetch(url).then(res => res.json()).catch()
|
const fetcher = url => fetch(url).then(res => res.json()).catch()
|
||||||
|
|
||||||
|
@ -9,16 +10,26 @@ export const PriceContext = React.createContext({
|
||||||
price: null
|
price: null
|
||||||
})
|
})
|
||||||
|
|
||||||
const ENDPOINT = 'https://api.coinbase.com/v2/prices/BTC-USD/spot'
|
export const CURRENCY_SYMBOLS = {
|
||||||
|
'AUD': '$',
|
||||||
|
'CAD': '$',
|
||||||
|
'EUR': '€',
|
||||||
|
'GBP': '£',
|
||||||
|
'USD': '$',
|
||||||
|
'NZD': '$'
|
||||||
|
}
|
||||||
|
|
||||||
export async function getPrice () {
|
const endpoint = (fiat) => `https://api.coinbase.com/v2/prices/BTC-${fiat ?? 'USD'}/spot`
|
||||||
const data = await fetcher(ENDPOINT)
|
|
||||||
|
export async function getPrice (fiat) {
|
||||||
|
const data = await fetcher(endpoint(fiat))
|
||||||
return data?.data?.amount
|
return data?.data?.amount
|
||||||
}
|
}
|
||||||
|
|
||||||
export function PriceProvider ({ price, children }) {
|
export function PriceProvider ({ price, children }) {
|
||||||
|
const me = useMe()
|
||||||
const { data } = useSWR(
|
const { data } = useSWR(
|
||||||
ENDPOINT,
|
endpoint(me?.fiatCurrency),
|
||||||
fetcher,
|
fetcher,
|
||||||
{
|
{
|
||||||
refreshInterval: 30000
|
refreshInterval: 30000
|
||||||
|
@ -45,8 +56,9 @@ export default function Price () {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setAsSats(localStorage.getItem('asSats'))
|
setAsSats(localStorage.getItem('asSats'))
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const price = usePrice()
|
const price = usePrice()
|
||||||
|
const me = useMe()
|
||||||
|
const fiatSymbol = CURRENCY_SYMBOLS[me?.fiatCurrency || 'USD'];
|
||||||
|
|
||||||
if (!price) return null
|
if (!price) return null
|
||||||
|
|
||||||
|
@ -66,7 +78,7 @@ export default function Price () {
|
||||||
if (asSats === 'yep') {
|
if (asSats === 'yep') {
|
||||||
return (
|
return (
|
||||||
<Button className='text-reset p-0' onClick={handleClick} variant='link'>
|
<Button className='text-reset p-0' onClick={handleClick} variant='link'>
|
||||||
{fixedDecimal(100000000 / price, 0) + ' sats/$'}
|
{fixedDecimal(100000000 / price, 0) + ` sats/${fiatSymbol}`}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -81,7 +93,7 @@ export default function Price () {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button className='text-reset p-0' onClick={handleClick} variant='link'>
|
<Button className='text-reset p-0' onClick={handleClick} variant='link'>
|
||||||
{'$' + fixedDecimal(price, 0)}
|
{fiatSymbol + fixedDecimal(price, 0)}
|
||||||
</Button>
|
</Button>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ export const ME = gql`
|
||||||
freeComments
|
freeComments
|
||||||
hasNewNotes
|
hasNewNotes
|
||||||
tipDefault
|
tipDefault
|
||||||
|
fiatCurrency
|
||||||
bioId
|
bioId
|
||||||
hasInvites
|
hasInvites
|
||||||
upvotePopover
|
upvotePopover
|
||||||
|
@ -41,6 +42,7 @@ export const ME_SSR = gql`
|
||||||
freePosts
|
freePosts
|
||||||
freeComments
|
freeComments
|
||||||
tipDefault
|
tipDefault
|
||||||
|
fiatCurrency
|
||||||
bioId
|
bioId
|
||||||
upvotePopover
|
upvotePopover
|
||||||
tipPopover
|
tipPopover
|
||||||
|
@ -61,6 +63,7 @@ export const ME_SSR = gql`
|
||||||
export const SETTINGS_FIELDS = gql`
|
export const SETTINGS_FIELDS = gql`
|
||||||
fragment SettingsFields on User {
|
fragment SettingsFields on User {
|
||||||
tipDefault
|
tipDefault
|
||||||
|
fiatCurrency
|
||||||
noteItemSats
|
noteItemSats
|
||||||
noteEarning
|
noteEarning
|
||||||
noteAllDescendants
|
noteAllDescendants
|
||||||
|
@ -90,11 +93,11 @@ ${SETTINGS_FIELDS}
|
||||||
export const SET_SETTINGS =
|
export const SET_SETTINGS =
|
||||||
gql`
|
gql`
|
||||||
${SETTINGS_FIELDS}
|
${SETTINGS_FIELDS}
|
||||||
mutation setSettings($tipDefault: Int!, $noteItemSats: Boolean!, $noteEarning: Boolean!,
|
mutation setSettings($tipDefault: Int!, $fiatCurrency: String!, $noteItemSats: Boolean!, $noteEarning: Boolean!,
|
||||||
$noteAllDescendants: Boolean!, $noteMentions: Boolean!, $noteDeposits: Boolean!,
|
$noteAllDescendants: Boolean!, $noteMentions: Boolean!, $noteDeposits: Boolean!,
|
||||||
$noteInvites: Boolean!, $noteJobIndicator: Boolean!, $hideInvoiceDesc: Boolean!,
|
$noteInvites: Boolean!, $noteJobIndicator: Boolean!, $hideInvoiceDesc: Boolean!,
|
||||||
$wildWestMode: Boolean!, $greeterMode: Boolean!) {
|
$wildWestMode: Boolean!, $greeterMode: Boolean!) {
|
||||||
setSettings(tipDefault: $tipDefault, noteItemSats: $noteItemSats,
|
setSettings(tipDefault: $tipDefault, fiatCurrency: $fiatCurrency, noteItemSats: $noteItemSats,
|
||||||
noteEarning: $noteEarning, noteAllDescendants: $noteAllDescendants,
|
noteEarning: $noteEarning, noteAllDescendants: $noteAllDescendants,
|
||||||
noteMentions: $noteMentions, noteDeposits: $noteDeposits, noteInvites: $noteInvites,
|
noteMentions: $noteMentions, noteDeposits: $noteDeposits, noteInvites: $noteInvites,
|
||||||
noteJobIndicator: $noteJobIndicator, hideInvoiceDesc: $hideInvoiceDesc, wildWestMode: $wildWestMode,
|
noteJobIndicator: $noteJobIndicator, hideInvoiceDesc: $hideInvoiceDesc, wildWestMode: $wildWestMode,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Checkbox, Form, Input, SubmitButton } from '../components/form'
|
import { Checkbox, Form, Input, SubmitButton, Dropdown } from '../components/form'
|
||||||
import * as Yup from 'yup'
|
import * as Yup from 'yup'
|
||||||
import { Alert, Button, InputGroup, Modal } from 'react-bootstrap'
|
import { Alert, Button, InputGroup, Modal } from 'react-bootstrap'
|
||||||
import LayoutCenter from '../components/layout-center'
|
import LayoutCenter from '../components/layout-center'
|
||||||
|
@ -12,12 +12,16 @@ import { LightningAuth } from '../components/lightning-auth'
|
||||||
import { SETTINGS, SET_SETTINGS } from '../fragments/users'
|
import { SETTINGS, SET_SETTINGS } from '../fragments/users'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import Info from '../components/info'
|
import Info from '../components/info'
|
||||||
|
import { CURRENCY_SYMBOLS } from '../components/price'
|
||||||
|
|
||||||
export const getServerSideProps = getGetServerSideProps(SETTINGS)
|
export const getServerSideProps = getGetServerSideProps(SETTINGS)
|
||||||
|
|
||||||
|
const supportedCurrencies = Object.keys(CURRENCY_SYMBOLS)
|
||||||
|
|
||||||
export const SettingsSchema = Yup.object({
|
export const SettingsSchema = Yup.object({
|
||||||
tipDefault: Yup.number().typeError('must be a number').required('required')
|
tipDefault: Yup.number().typeError('must be a number').required('required')
|
||||||
.positive('must be positive').integer('must be whole')
|
.positive('must be positive').integer('must be whole'),
|
||||||
|
fiatCurrency: Yup.string().required('required').oneOf(supportedCurrencies)
|
||||||
})
|
})
|
||||||
|
|
||||||
const warningMessage = 'If I logout, even accidentally, I will never be able to access my account again'
|
const warningMessage = 'If I logout, even accidentally, I will never be able to access my account again'
|
||||||
|
@ -54,6 +58,7 @@ export default function Settings ({ data: { settings } }) {
|
||||||
<Form
|
<Form
|
||||||
initial={{
|
initial={{
|
||||||
tipDefault: settings?.tipDefault || 21,
|
tipDefault: settings?.tipDefault || 21,
|
||||||
|
fiatCurrency: settings?.fiatCurrency || 'USD',
|
||||||
noteItemSats: settings?.noteItemSats,
|
noteItemSats: settings?.noteItemSats,
|
||||||
noteEarning: settings?.noteEarning,
|
noteEarning: settings?.noteEarning,
|
||||||
noteAllDescendants: settings?.noteAllDescendants,
|
noteAllDescendants: settings?.noteAllDescendants,
|
||||||
|
@ -66,8 +71,8 @@ export default function Settings ({ data: { settings } }) {
|
||||||
greeterMode: settings?.greeterMode
|
greeterMode: settings?.greeterMode
|
||||||
}}
|
}}
|
||||||
schema={SettingsSchema}
|
schema={SettingsSchema}
|
||||||
onSubmit={async ({ tipDefault, ...values }) => {
|
onSubmit={async ({ tipDefault, fiatCurrency, ...values }) => {
|
||||||
await setSettings({ variables: { tipDefault: Number(tipDefault), ...values } })
|
await setSettings({ variables: { tipDefault: Number(tipDefault), fiatCurrency, ...values } })
|
||||||
setSuccess('settings saved')
|
setSuccess('settings saved')
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@ -79,6 +84,12 @@ export default function Settings ({ data: { settings } }) {
|
||||||
autoFocus
|
autoFocus
|
||||||
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
|
||||||
/>
|
/>
|
||||||
|
<Dropdown
|
||||||
|
label='fiat currency'
|
||||||
|
name='fiatCurrency'
|
||||||
|
items={supportedCurrencies}
|
||||||
|
required
|
||||||
|
/>
|
||||||
<div className='form-label'>notify me when ...</div>
|
<div className='form-label'>notify me when ...</div>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
label='I stack sats from posts and comments'
|
label='I stack sats from posts and comments'
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "users" ADD COLUMN "fiatCurrency" TEXT NOT NULL DEFAULT 'USD';
|
|
@ -36,6 +36,7 @@ model User {
|
||||||
freePosts Int @default(2)
|
freePosts Int @default(2)
|
||||||
checkedNotesAt DateTime?
|
checkedNotesAt DateTime?
|
||||||
tipDefault Int @default(10)
|
tipDefault Int @default(10)
|
||||||
|
fiatCurrency String @default("USD")
|
||||||
pubkey String? @unique
|
pubkey String? @unique
|
||||||
trust Float @default(0)
|
trust Float @default(0)
|
||||||
upvoteTrust Float @default(0)
|
upvoteTrust Float @default(0)
|
||||||
|
|
Loading…
Reference in New Issue