diff --git a/api/resolvers/index.js b/api/resolvers/index.js index be4d925c..4f969d1b 100644 --- a/api/resolvers/index.js +++ b/api/resolvers/index.js @@ -11,7 +11,8 @@ import growth from './growth' import search from './search' import rewards from './rewards' import referrals from './referrals' +import price from './price' import { GraphQLJSONObject } from 'graphql-type-json' export default [user, item, message, wallet, lnurl, notifications, invite, sub, - upload, growth, search, rewards, referrals, { JSONObject: GraphQLJSONObject }] + upload, growth, search, rewards, referrals, price, { JSONObject: GraphQLJSONObject }] diff --git a/api/resolvers/price.js b/api/resolvers/price.js new file mode 100644 index 00000000..e9b41625 --- /dev/null +++ b/api/resolvers/price.js @@ -0,0 +1,32 @@ +const cache = new Map(); +const expiresIn = 30000; // in milliseconds + +async function getPrice(fiat) { + fiat ??= 'USD'; + if (cache.has(fiat)) { + const { price, createdAt } = cache.get(fiat) + const expired = createdAt + expiresIn < Date.now() + if (!expired) { + return price; + } + } + const url = `https://api.coinbase.com/v2/prices/BTC-${fiat}/spot`; + const price = await fetch(url) + .then((res) => res.json()) + .then((body) => parseFloat(body.data.amount)) + .catch((err) => { + console.error(err); + return -1; + }); + cache.set(fiat, { price, createdAt: Date.now() }); + return price; +} + +export default { + Query: { + price: async (parent, { fiatCurrency }, ctx) => { + const price = await getPrice(fiatCurrency); + return price; + }, + }, +}; diff --git a/api/ssrApollo.js b/api/ssrApollo.js index b40e4832..20a19782 100644 --- a/api/ssrApollo.js +++ b/api/ssrApollo.js @@ -10,7 +10,7 @@ import { print } from 'graphql' import lnd from './lnd' import search from './search' import { ME } from '../fragments/users' -import { getPrice } from '../components/price' +import { PRICE } from '../fragments/price' export default async function getSSRApolloClient (req, me = null) { const session = req && await getSession({ req }) @@ -47,7 +47,9 @@ export function getGetServerSideProps (query, variables = null, notFoundFunc, re query: ME }) - const price = await getPrice(me?.fiatCurrency) + const { data: { price } } = await client.query({ + query: PRICE, variables: { fiatCurrency: me?.fiatCurrency } + }) // we want to use client-side cache if (nodata && query) { diff --git a/api/typeDefs/index.js b/api/typeDefs/index.js index 8eb96b29..50d172b3 100644 --- a/api/typeDefs/index.js +++ b/api/typeDefs/index.js @@ -12,6 +12,7 @@ import upload from './upload' import growth from './growth' import rewards from './rewards' import referrals from './referrals' +import price from './price' const link = gql` type Query { @@ -28,4 +29,4 @@ const link = gql` ` export default [link, user, item, message, wallet, lnurl, notifications, invite, - sub, upload, growth, rewards, referrals] + sub, upload, growth, rewards, referrals, price] diff --git a/api/typeDefs/price.js b/api/typeDefs/price.js new file mode 100644 index 00000000..4bfdc295 --- /dev/null +++ b/api/typeDefs/price.js @@ -0,0 +1,7 @@ +import { gql } from 'apollo-server-micro' + +export default gql` + extend type Query { + price(fiatCurrency: String): Float + } +` diff --git a/components/job-form.js b/components/job-form.js index 78e89e40..91d93ee1 100644 --- a/components/job-form.js +++ b/components/job-form.js @@ -9,7 +9,7 @@ import styles from '../styles/post.module.css' import { useLazyQuery, gql, useMutation } from '@apollo/client' import { useRouter } from 'next/router' import Link from 'next/link' -import { CURRENCY_SYMBOLS, usePrice } from './price' +import { usePrice } from './price' import Avatar from './avatar' import BootstrapForm from 'react-bootstrap/Form' import Alert from 'react-bootstrap/Alert' @@ -38,9 +38,7 @@ function satsMin2Mo (minute) { function PriceHint ({ monthly }) { const me = useMe() - const price = usePrice() - - const fiatSymbol = CURRENCY_SYMBOLS[me?.fiatCurrency || 'USD'] + const { price, fiatSymbol } = usePrice() if (!price || !monthly) { return null diff --git a/components/price.js b/components/price.js index 76fa0f2f..4dd76b13 100644 --- a/components/price.js +++ b/components/price.js @@ -1,13 +1,13 @@ import React, { useContext, useEffect, useState } from 'react' +import { useQuery } from '@apollo/client' import { Button } from 'react-bootstrap' -import useSWR from 'swr' import { fixedDecimal } from '../lib/format' import { useMe } from './me' - -const fetcher = url => fetch(url).then(res => res.json()).catch() +import { PRICE } from '../fragments/price' export const PriceContext = React.createContext({ - price: null + price: null, + fiatSymbol: null }) export const CURRENCY_SYMBOLS = { @@ -20,24 +20,18 @@ export const CURRENCY_SYMBOLS = { ZAR: 'R ' } -const endpoint = (fiat) => `https://api.coinbase.com/v2/prices/BTC-${fiat ?? 'USD'}/spot` - -export async function getPrice (fiat) { - const data = await fetcher(endpoint(fiat)) - return data?.data?.amount +export function usePrice () { + return useContext(PriceContext) } export function PriceProvider ({ price, children }) { const me = useMe() - const { data } = useSWR( - endpoint(me?.fiatCurrency), - fetcher, - { - refreshInterval: 30000 - }) + const fiatCurrency = me?.fiatCurrency; + const { data } = useQuery(PRICE, { variables: { fiatCurrency }, pollInterval: 30000, fetchPolicy: 'cache-and-network' }) const contextValue = { - price: data?.data?.amount || price + price: data?.price || price, + fiatSymbol: CURRENCY_SYMBOLS[fiatCurrency] || '$' } return ( @@ -47,19 +41,12 @@ export function PriceProvider ({ price, children }) { ) } -export function usePrice () { - const { price } = useContext(PriceContext) - return price -} - export default function Price () { const [asSats, setAsSats] = useState(undefined) useEffect(() => { setAsSats(localStorage.getItem('asSats')) }, []) - const price = usePrice() - const me = useMe() - const fiatSymbol = CURRENCY_SYMBOLS[me?.fiatCurrency || 'USD'] + const { price, fiatSymbol } = usePrice() if (!price) return null diff --git a/fragments/price.js b/fragments/price.js new file mode 100644 index 00000000..39a18a83 --- /dev/null +++ b/fragments/price.js @@ -0,0 +1,6 @@ +import { gql } from '@apollo/client' + +export const PRICE = gql` + query price($fiatCurrency: String) { + price(fiatCurrency: $fiatCurrency) + }`