manifold/web/lib/firebase/manalinks.ts
Austin Chen 6cc2d8af58
Manalink: Send mana to anyone via link (#114)
* Set up Firestore structure for mana bounty links

* Split up manalinks into successes and failures

* Allow clients to create manalinks

* Track txnId and successful users

* Store custom amounts in the link

* List all manalinks you've created

* Support backend for claiming manalinks

* Add some more error handling

* Tweak readme

* Fix typescript breakage

* Revert "Convert common imports in functions to be absolute"

This reverts commit c03518e906.

* Scaffolding so `claimManalink` works

* Clean up imports

* Barebones endpoint to claim mana

* Fix rules to only allow link creators to query

* Design out claim giftcard

* List all claimed transactions

* Style in a more awesome card

* Fix import

* Padding tweak

* Fix useManalinkTxns hook

* /send -> /link

* Tidy up some details

* Do a bunch of random manalinks work

* Fix up LinksTable to build

* Clean up LinksTable an absurd amount

* Basic details functionality on manalinks table

* Work on manalink claim stuff

* Fix up some merge mess

* Not-signed-in flow implemented

* Better manalinks table

* Only show outstanding links in table

* Use new `ManalinkTxn` type

* /link -> /links

* Change manalinks page UI to use nice looking tabs

* Many fixes to manalinks UI

* Default to 1 use

* Tidying up

* Some copy changes based on feedback

* Add required index

Co-authored-by: Marshall Polaris <marshall@pol.rs>
2022-06-23 01:07:52 -07:00

95 lines
2.2 KiB
TypeScript

import {
collection,
getDoc,
orderBy,
query,
setDoc,
where,
} from 'firebase/firestore'
import { doc } from 'firebase/firestore'
import { Manalink } from '../../../common/manalink'
import { db } from './init'
import { customAlphabet } from 'nanoid'
import { listenForValues } from './utils'
import { useEffect, useState } from 'react'
export async function createManalink(data: {
fromId: string
amount: number
expiresTime: number | null
maxUses: number | null
message: string
}) {
const { fromId, amount, expiresTime, maxUses, message } = data
// At 100 IDs per hour, using this alphabet and 8 chars, there's a 1% chance of collision in 2 years
// See https://zelark.github.io/nano-id-cc/
const nanoid = customAlphabet(
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ',
8
)
const slug = nanoid()
if (amount <= 0 || isNaN(amount) || !isFinite(amount)) return null
const manalink: Manalink = {
slug,
fromId,
amount,
token: 'M$',
createdTime: Date.now(),
expiresTime,
maxUses,
claimedUserIds: [],
claims: [],
message,
}
const ref = doc(db, 'manalinks', slug)
await setDoc(ref, manalink)
return slug
}
const manalinkCol = collection(db, 'manalinks')
// TODO: This required an index, make sure to also set up in prod
function listUserManalinks(fromId?: string) {
return query(
manalinkCol,
where('fromId', '==', fromId),
orderBy('createdTime', 'desc')
)
}
export async function getManalink(slug: string) {
const docSnap = await getDoc(doc(db, 'manalinks', slug))
return docSnap.data() as Manalink
}
export function useManalink(slug: string) {
const [manalink, setManalink] = useState<Manalink | null>(null)
useEffect(() => {
if (slug) {
getManalink(slug).then(setManalink)
}
}, [slug])
return manalink
}
export function listenForUserManalinks(
fromId: string | undefined,
setLinks: (links: Manalink[]) => void
) {
return listenForValues<Manalink>(listUserManalinks(fromId), setLinks)
}
export const useUserManalinks = (fromId: string) => {
const [links, setLinks] = useState<Manalink[]>([])
useEffect(() => {
return listenForUserManalinks(fromId, setLinks)
}, [fromId])
return links
}