Rewrite client for new public APIs to use fetch
instead of callables (#241)
* Rename `lib/firebase/api-call` -> `lib/firebase/fn-call` This relieves ambiguity now that we will be using our actual public API in the client. * Rewrite client API calls to createContract, placeBet * Tiny fixup for client market creation code
This commit is contained in:
parent
d4a49789d1
commit
20f4b97d8b
|
@ -4,7 +4,7 @@ import { useState } from 'react'
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
import { formatMoney } from 'common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { useUser } from 'web/hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { addLiquidity } from 'web/lib/firebase/api-call'
|
import { addLiquidity } from 'web/lib/firebase/fn-call'
|
||||||
import { AmountInput } from './amount-input'
|
import { AmountInput } from './amount-input'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { Answer } from 'common/answer'
|
||||||
import { DPM, FreeResponse, FullContract } from 'common/contract'
|
import { DPM, FreeResponse, FullContract } from 'common/contract'
|
||||||
import { BuyAmountInput } from '../amount-input'
|
import { BuyAmountInput } from '../amount-input'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { placeBet } from 'web/lib/firebase/api-call'
|
import { APIError, placeBet } from 'web/lib/firebase/api-call'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { Spacer } from '../layout/spacer'
|
import { Spacer } from '../layout/spacer'
|
||||||
import {
|
import {
|
||||||
|
@ -52,22 +52,26 @@ export function AnswerBetPanel(props: {
|
||||||
setError(undefined)
|
setError(undefined)
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
|
|
||||||
const result = await placeBet({
|
placeBet({
|
||||||
amount: betAmount,
|
amount: betAmount,
|
||||||
outcome: answerId,
|
outcome: answerId,
|
||||||
contractId: contract.id,
|
contractId: contract.id,
|
||||||
}).then((r) => r.data as any)
|
})
|
||||||
|
.then((r) => {
|
||||||
console.log('placed bet. Result:', result)
|
console.log('placed bet. Result:', r)
|
||||||
|
setIsSubmitting(false)
|
||||||
if (result?.status === 'success') {
|
setBetAmount(undefined)
|
||||||
setIsSubmitting(false)
|
props.closePanel()
|
||||||
setBetAmount(undefined)
|
})
|
||||||
props.closePanel()
|
.catch((e) => {
|
||||||
} else {
|
if (e instanceof APIError) {
|
||||||
setError(result?.message || 'Error placing bet')
|
setError(e.toString())
|
||||||
setIsSubmitting(false)
|
} else {
|
||||||
}
|
console.error(e)
|
||||||
|
setError('Error placing bet')
|
||||||
|
}
|
||||||
|
setIsSubmitting(false)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const betDisabled = isSubmitting || !betAmount || error
|
const betDisabled = isSubmitting || !betAmount || error
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { useState } from 'react'
|
||||||
|
|
||||||
import { DPM, FreeResponse, FullContract } from 'common/contract'
|
import { DPM, FreeResponse, FullContract } from 'common/contract'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { resolveMarket } from 'web/lib/firebase/api-call'
|
import { resolveMarket } from 'web/lib/firebase/fn-call'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { ChooseCancelSelector } from '../yes-no-selector'
|
import { ChooseCancelSelector } from '../yes-no-selector'
|
||||||
import { ResolveConfirmationButton } from '../confirmation-button'
|
import { ResolveConfirmationButton } from '../confirmation-button'
|
||||||
|
|
|
@ -5,7 +5,7 @@ import Textarea from 'react-expanding-textarea'
|
||||||
import { DPM, FreeResponse, FullContract } from 'common/contract'
|
import { DPM, FreeResponse, FullContract } from 'common/contract'
|
||||||
import { BuyAmountInput } from '../amount-input'
|
import { BuyAmountInput } from '../amount-input'
|
||||||
import { Col } from '../layout/col'
|
import { Col } from '../layout/col'
|
||||||
import { createAnswer } from 'web/lib/firebase/api-call'
|
import { createAnswer } from 'web/lib/firebase/fn-call'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import {
|
import {
|
||||||
formatMoney,
|
formatMoney,
|
||||||
|
|
|
@ -16,7 +16,8 @@ import {
|
||||||
import { Title } from './title'
|
import { Title } from './title'
|
||||||
import { firebaseLogin, User } from 'web/lib/firebase/users'
|
import { firebaseLogin, User } from 'web/lib/firebase/users'
|
||||||
import { Bet } from 'common/bet'
|
import { Bet } from 'common/bet'
|
||||||
import { placeBet, sellShares } from 'web/lib/firebase/api-call'
|
import { APIError, placeBet } from 'web/lib/firebase/api-call'
|
||||||
|
import { sellShares } from 'web/lib/firebase/fn-call'
|
||||||
import { AmountInput, BuyAmountInput } from './amount-input'
|
import { AmountInput, BuyAmountInput } from './amount-input'
|
||||||
import { InfoTooltip } from './info-tooltip'
|
import { InfoTooltip } from './info-tooltip'
|
||||||
import { BinaryOutcomeLabel } from './outcome-label'
|
import { BinaryOutcomeLabel } from './outcome-label'
|
||||||
|
@ -240,23 +241,27 @@ function BuyPanel(props: {
|
||||||
setError(undefined)
|
setError(undefined)
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
|
|
||||||
const result = await placeBet({
|
placeBet({
|
||||||
amount: betAmount,
|
amount: betAmount,
|
||||||
outcome: betChoice,
|
outcome: betChoice,
|
||||||
contractId: contract.id,
|
contractId: contract.id,
|
||||||
}).then((r) => r.data as any)
|
})
|
||||||
|
.then((r) => {
|
||||||
console.log('placed bet. Result:', result)
|
console.log('placed bet. Result:', r)
|
||||||
|
setIsSubmitting(false)
|
||||||
if (result?.status === 'success') {
|
setWasSubmitted(true)
|
||||||
setIsSubmitting(false)
|
setBetAmount(undefined)
|
||||||
setWasSubmitted(true)
|
if (onBuySuccess) onBuySuccess()
|
||||||
setBetAmount(undefined)
|
})
|
||||||
if (onBuySuccess) onBuySuccess()
|
.catch((e) => {
|
||||||
} else {
|
if (e instanceof APIError) {
|
||||||
setError(result?.message || 'Error placing bet')
|
setError(e.toString())
|
||||||
setIsSubmitting(false)
|
} else {
|
||||||
}
|
console.error(e)
|
||||||
|
setError('Error placing bet')
|
||||||
|
}
|
||||||
|
setIsSubmitting(false)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const betDisabled = isSubmitting || !betAmount || error
|
const betDisabled = isSubmitting || !betAmount || error
|
||||||
|
|
|
@ -22,7 +22,7 @@ import {
|
||||||
} from 'web/lib/firebase/contracts'
|
} from 'web/lib/firebase/contracts'
|
||||||
import { Row } from './layout/row'
|
import { Row } from './layout/row'
|
||||||
import { UserLink } from './user-page'
|
import { UserLink } from './user-page'
|
||||||
import { sellBet } from 'web/lib/firebase/api-call'
|
import { sellBet } from 'web/lib/firebase/fn-call'
|
||||||
import { ConfirmationButton } from './confirmation-button'
|
import { ConfirmationButton } from './confirmation-button'
|
||||||
import { OutcomeLabel, YesLabel, NoLabel } from './outcome-label'
|
import { OutcomeLabel, YesLabel, NoLabel } from './outcome-label'
|
||||||
import { filterDefined } from 'common/util/array'
|
import { filterDefined } from 'common/util/array'
|
||||||
|
|
|
@ -3,7 +3,7 @@ import { useRouter } from 'next/router'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { PlusCircleIcon } from '@heroicons/react/solid'
|
import { PlusCircleIcon } from '@heroicons/react/solid'
|
||||||
import { parseWordsAsTags } from 'common/util/parse'
|
import { parseWordsAsTags } from 'common/util/parse'
|
||||||
import { createFold } from 'web/lib/firebase/api-call'
|
import { createFold } from 'web/lib/firebase/fn-call'
|
||||||
import { foldPath } from 'web/lib/firebase/folds'
|
import { foldPath } from 'web/lib/firebase/folds'
|
||||||
import { toCamelCase } from 'common/util/format'
|
import { toCamelCase } from 'common/util/format'
|
||||||
import { ConfirmationButton } from '../confirmation-button'
|
import { ConfirmationButton } from '../confirmation-button'
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { User } from 'web/lib/firebase/users'
|
||||||
import { NumberCancelSelector } from './yes-no-selector'
|
import { NumberCancelSelector } from './yes-no-selector'
|
||||||
import { Spacer } from './layout/spacer'
|
import { Spacer } from './layout/spacer'
|
||||||
import { ResolveConfirmationButton } from './confirmation-button'
|
import { ResolveConfirmationButton } from './confirmation-button'
|
||||||
import { resolveMarket } from 'web/lib/firebase/api-call'
|
import { resolveMarket } from 'web/lib/firebase/fn-call'
|
||||||
import { NumericContract } from 'common/contract'
|
import { NumericContract } from 'common/contract'
|
||||||
import { BucketInput } from './bucket-input'
|
import { BucketInput } from './bucket-input'
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import { User } from 'web/lib/firebase/users'
|
||||||
import { YesNoCancelSelector } from './yes-no-selector'
|
import { YesNoCancelSelector } from './yes-no-selector'
|
||||||
import { Spacer } from './layout/spacer'
|
import { Spacer } from './layout/spacer'
|
||||||
import { ResolveConfirmationButton } from './confirmation-button'
|
import { ResolveConfirmationButton } from './confirmation-button'
|
||||||
import { resolveMarket } from 'web/lib/firebase/api-call'
|
import { resolveMarket } from 'web/lib/firebase/fn-call'
|
||||||
import { ProbabilitySelector } from './probability-selector'
|
import { ProbabilitySelector } from './probability-selector'
|
||||||
import { DPM_CREATOR_FEE } from 'common/fees'
|
import { DPM_CREATOR_FEE } from 'common/fees'
|
||||||
import { getProbability } from 'common/calculate'
|
import { getProbability } from 'common/calculate'
|
||||||
|
|
|
@ -1,80 +1,45 @@
|
||||||
import { httpsCallable } from 'firebase/functions'
|
import { auth } from './users'
|
||||||
import { Fold } from 'common/fold'
|
import { app, functions } from './init'
|
||||||
import { Txn } from 'common/txn'
|
|
||||||
import { User } from 'common/user'
|
|
||||||
import { randomString } from 'common/util/random'
|
|
||||||
import './init'
|
|
||||||
import { functions } from './init'
|
|
||||||
|
|
||||||
export const cloudFunction = <RequestData, ResponseData>(name: string) =>
|
export class APIError extends Error {
|
||||||
httpsCallable<RequestData, ResponseData>(functions, name)
|
code: number
|
||||||
|
constructor(code: number, message: string) {
|
||||||
export const createContract = cloudFunction('createContract')
|
super(message)
|
||||||
|
this.code = code
|
||||||
export const createFold = cloudFunction<
|
this.name = 'APIError'
|
||||||
{ name: string; about: string; tags: string[] },
|
|
||||||
{ status: 'error' | 'success'; message?: string; fold?: Fold }
|
|
||||||
>('createFold')
|
|
||||||
|
|
||||||
export const transact = cloudFunction<
|
|
||||||
Omit<Txn, 'id' | 'createdTime'>,
|
|
||||||
{ status: 'error' | 'success'; message?: string; txn?: Txn }
|
|
||||||
>('transact')
|
|
||||||
|
|
||||||
export const placeBet = cloudFunction('placeBet')
|
|
||||||
|
|
||||||
export const sellBet = cloudFunction('sellBet')
|
|
||||||
|
|
||||||
export const sellShares = cloudFunction<
|
|
||||||
{ contractId: string; shares: number; outcome: 'YES' | 'NO' },
|
|
||||||
{ status: 'error' | 'success'; message?: string }
|
|
||||||
>('sellShares')
|
|
||||||
|
|
||||||
export const createAnswer = cloudFunction<
|
|
||||||
{ contractId: string; text: string; amount: number },
|
|
||||||
{
|
|
||||||
status: 'error' | 'success'
|
|
||||||
message?: string
|
|
||||||
answerId?: string
|
|
||||||
betId?: string
|
|
||||||
}
|
}
|
||||||
>('createAnswer')
|
}
|
||||||
|
|
||||||
export const resolveMarket = cloudFunction<
|
export async function call(name: string, method: string, params: any) {
|
||||||
{
|
const user = auth.currentUser
|
||||||
outcome: string
|
if (user == null) {
|
||||||
value?: number
|
throw new Error('Must be signed in to make API calls.')
|
||||||
contractId: string
|
|
||||||
probabilityInt?: number
|
|
||||||
resolutions?: { [outcome: string]: number }
|
|
||||||
},
|
|
||||||
{ status: 'error' | 'success'; message?: string }
|
|
||||||
>('resolveMarket')
|
|
||||||
|
|
||||||
export const createUser: () => Promise<User | null> = () => {
|
|
||||||
let deviceToken = window.localStorage.getItem('device-token')
|
|
||||||
if (!deviceToken) {
|
|
||||||
deviceToken = randomString()
|
|
||||||
window.localStorage.setItem('device-token', deviceToken)
|
|
||||||
}
|
}
|
||||||
|
const token = await user.getIdToken()
|
||||||
return cloudFunction('createUser')({ deviceToken })
|
const region = functions.region
|
||||||
.then((r) => (r.data as any)?.user || null)
|
const projectId = app.options.projectId
|
||||||
.catch(() => null)
|
const url = `https://${region}-${projectId}.cloudfunctions.net/${name}`
|
||||||
|
const req = new Request(url, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
method: method,
|
||||||
|
body: JSON.stringify({ data: params }),
|
||||||
|
})
|
||||||
|
return await fetch(req).then(async (resp) => {
|
||||||
|
const json = (await resp.json()) as { [k: string]: any }
|
||||||
|
if (json.data.status == 'error') {
|
||||||
|
throw new APIError(resp.status, json.data.message)
|
||||||
|
}
|
||||||
|
return json.data
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const changeUserInfo = (data: {
|
export function createContract(params: any) {
|
||||||
username?: string
|
return call('createContract', 'POST', params)
|
||||||
name?: string
|
|
||||||
avatarUrl?: string
|
|
||||||
}) => {
|
|
||||||
return cloudFunction('changeUserInfo')(data)
|
|
||||||
.then((r) => r.data as { status: string; message?: string })
|
|
||||||
.catch((e) => ({ status: 'error', message: e.message }))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const addLiquidity = (data: { amount: number; contractId: string }) => {
|
export function placeBet(params: any) {
|
||||||
return cloudFunction('addLiquidity')(data)
|
return call('placeBet', 'POST', params)
|
||||||
.then((r) => r.data as { status: string })
|
|
||||||
.catch((e) => ({ status: 'error', message: e.message }))
|
|
||||||
}
|
}
|
||||||
|
|
76
web/lib/firebase/fn-call.ts
Normal file
76
web/lib/firebase/fn-call.ts
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
import { httpsCallable } from 'firebase/functions'
|
||||||
|
import { Fold } from 'common/fold'
|
||||||
|
import { Txn } from 'common/txn'
|
||||||
|
import { User } from 'common/user'
|
||||||
|
import { randomString } from 'common/util/random'
|
||||||
|
import './init'
|
||||||
|
import { functions } from './init'
|
||||||
|
|
||||||
|
export const cloudFunction = <RequestData, ResponseData>(name: string) =>
|
||||||
|
httpsCallable<RequestData, ResponseData>(functions, name)
|
||||||
|
|
||||||
|
export const createFold = cloudFunction<
|
||||||
|
{ name: string; about: string; tags: string[] },
|
||||||
|
{ status: 'error' | 'success'; message?: string; fold?: Fold }
|
||||||
|
>('createFold')
|
||||||
|
|
||||||
|
export const transact = cloudFunction<
|
||||||
|
Omit<Txn, 'id' | 'createdTime'>,
|
||||||
|
{ status: 'error' | 'success'; message?: string; txn?: Txn }
|
||||||
|
>('transact')
|
||||||
|
|
||||||
|
export const sellBet = cloudFunction('sellBet')
|
||||||
|
|
||||||
|
export const sellShares = cloudFunction<
|
||||||
|
{ contractId: string; shares: number; outcome: 'YES' | 'NO' },
|
||||||
|
{ status: 'error' | 'success'; message?: string }
|
||||||
|
>('sellShares')
|
||||||
|
|
||||||
|
export const createAnswer = cloudFunction<
|
||||||
|
{ contractId: string; text: string; amount: number },
|
||||||
|
{
|
||||||
|
status: 'error' | 'success'
|
||||||
|
message?: string
|
||||||
|
answerId?: string
|
||||||
|
betId?: string
|
||||||
|
}
|
||||||
|
>('createAnswer')
|
||||||
|
|
||||||
|
export const resolveMarket = cloudFunction<
|
||||||
|
{
|
||||||
|
outcome: string
|
||||||
|
value?: number
|
||||||
|
contractId: string
|
||||||
|
probabilityInt?: number
|
||||||
|
resolutions?: { [outcome: string]: number }
|
||||||
|
},
|
||||||
|
{ status: 'error' | 'success'; message?: string }
|
||||||
|
>('resolveMarket')
|
||||||
|
|
||||||
|
export const createUser: () => Promise<User | null> = () => {
|
||||||
|
let deviceToken = window.localStorage.getItem('device-token')
|
||||||
|
if (!deviceToken) {
|
||||||
|
deviceToken = randomString()
|
||||||
|
window.localStorage.setItem('device-token', deviceToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cloudFunction('createUser')({ deviceToken })
|
||||||
|
.then((r) => (r.data as any)?.user || null)
|
||||||
|
.catch(() => null)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const changeUserInfo = (data: {
|
||||||
|
username?: string
|
||||||
|
name?: string
|
||||||
|
avatarUrl?: string
|
||||||
|
}) => {
|
||||||
|
return cloudFunction('changeUserInfo')(data)
|
||||||
|
.then((r) => r.data as { status: string; message?: string })
|
||||||
|
.catch((e) => ({ status: 'error', message: e.message }))
|
||||||
|
}
|
||||||
|
|
||||||
|
export const addLiquidity = (data: { amount: number; contractId: string }) => {
|
||||||
|
return cloudFunction('addLiquidity')(data)
|
||||||
|
.then((r) => r.data as { status: string })
|
||||||
|
.catch((e) => ({ status: 'error', message: e.message }))
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ import _ from 'lodash'
|
||||||
|
|
||||||
import { app } from './init'
|
import { app } from './init'
|
||||||
import { PrivateUser, User } from 'common/user'
|
import { PrivateUser, User } from 'common/user'
|
||||||
import { createUser } from './api-call'
|
import { createUser } from './fn-call'
|
||||||
import { getValue, getValues, listenForValue, listenForValues } from './utils'
|
import { getValue, getValues, listenForValue, listenForValues } from './utils'
|
||||||
import { DAY_MS } from 'common/util/time'
|
import { DAY_MS } from 'common/util/time'
|
||||||
import { feed } from 'common/feed'
|
import { feed } from 'common/feed'
|
||||||
|
|
|
@ -10,7 +10,7 @@ import { Spacer } from 'web/components/layout/spacer'
|
||||||
import { User } from 'common/user'
|
import { User } from 'common/user'
|
||||||
import { useUser } from 'web/hooks/use-user'
|
import { useUser } from 'web/hooks/use-user'
|
||||||
import { Linkify } from 'web/components/linkify'
|
import { Linkify } from 'web/components/linkify'
|
||||||
import { transact } from 'web/lib/firebase/api-call'
|
import { transact } from 'web/lib/firebase/fn-call'
|
||||||
import { charities, Charity } from 'common/charity'
|
import { charities, Charity } from 'common/charity'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import Custom404 from '../404'
|
import Custom404 from '../404'
|
||||||
|
|
|
@ -124,26 +124,24 @@ export function NewContract(props: { question: string; tag?: string }) {
|
||||||
|
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
|
|
||||||
const result: any = await createContract(
|
try {
|
||||||
removeUndefinedProps({
|
const result = await createContract(
|
||||||
question,
|
removeUndefinedProps({
|
||||||
outcomeType,
|
question,
|
||||||
description,
|
outcomeType,
|
||||||
initialProb,
|
description,
|
||||||
ante,
|
initialProb,
|
||||||
closeTime,
|
ante,
|
||||||
tags: category ? [category] : undefined,
|
closeTime,
|
||||||
min,
|
tags: category ? [category] : undefined,
|
||||||
max,
|
min,
|
||||||
})
|
max,
|
||||||
).then((r) => r.data || {})
|
})
|
||||||
|
)
|
||||||
if (result.status !== 'success') {
|
await router.push(contractPath(result.contract as Contract))
|
||||||
console.log('error creating contract', result)
|
} catch (e) {
|
||||||
return
|
console.log('error creating contract', e)
|
||||||
}
|
}
|
||||||
|
|
||||||
await router.push(contractPath(result.contract as Contract))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const descriptionPlaceholder =
|
const descriptionPlaceholder =
|
||||||
|
|
|
@ -155,7 +155,7 @@ ${TEST_VALUE}
|
||||||
ante,
|
ante,
|
||||||
closeTime,
|
closeTime,
|
||||||
tags: parseWordsAsTags(tags),
|
tags: parseWordsAsTags(tags),
|
||||||
}).then((r) => (r.data as any).contract)
|
}).then((r) => r.contract)
|
||||||
|
|
||||||
setCreatedContracts((prev) => [...prev, contract])
|
setCreatedContracts((prev) => [...prev, contract])
|
||||||
}
|
}
|
||||||
|
@ -237,7 +237,7 @@ ${TEST_VALUE}
|
||||||
<label className="label mb-1 gap-2">
|
<label className="label mb-1 gap-2">
|
||||||
<span>Market ante</span>
|
<span>Market ante</span>
|
||||||
<InfoTooltip
|
<InfoTooltip
|
||||||
text={`Subsidize your market to encourage trading. Ante bets are set to match your initial probability.
|
text={`Subsidize your market to encourage trading. Ante bets are set to match your initial probability.
|
||||||
You earn ${0.01 * 100}% of trading volume.`}
|
You earn ${0.01 * 100}% of trading volume.`}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
|
|
@ -9,7 +9,7 @@ import { Title } from 'web/components/title'
|
||||||
import { usePrivateUser, useUser } from 'web/hooks/use-user'
|
import { usePrivateUser, useUser } from 'web/hooks/use-user'
|
||||||
import { formatMoney } from 'common/util/format'
|
import { formatMoney } from 'common/util/format'
|
||||||
import { cleanDisplayName, cleanUsername } from 'common/util/clean-username'
|
import { cleanDisplayName, cleanUsername } from 'common/util/clean-username'
|
||||||
import { changeUserInfo } from 'web/lib/firebase/api-call'
|
import { changeUserInfo } from 'web/lib/firebase/fn-call'
|
||||||
import { uploadImage } from 'web/lib/firebase/storage'
|
import { uploadImage } from 'web/lib/firebase/storage'
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
import { Row } from 'web/components/layout/row'
|
import { Row } from 'web/components/layout/row'
|
||||||
|
|
Loading…
Reference in New Issue
Block a user