manifold/web/lib/firebase/users.ts
Austin Chen a17342e55c
New, pretty user page (#48)
* Show bio, banner, and social links on user page

* Minor tweaks

* Allow edits to their user page

* Fix build; add a white ring around users

* From userpage, link to /profile (which is always editable now)

* Update userpage SEO

* Clean up Profile menu

* Fixes according to Stephen's code review
2022-02-17 17:16:58 -08:00

170 lines
4.6 KiB
TypeScript

import {
getFirestore,
doc,
setDoc,
getDoc,
collection,
query,
where,
limit,
getDocs,
orderBy,
updateDoc,
} from 'firebase/firestore'
import { getAuth } from 'firebase/auth'
import { ref, getStorage, uploadBytes, getDownloadURL } from 'firebase/storage'
import {
onAuthStateChanged,
GoogleAuthProvider,
signInWithPopup,
} from 'firebase/auth'
import { app } from './init'
import { PrivateUser, User } from '../../../common/user'
import { createUser } from './api-call'
import { getValues, listenForValue, listenForValues } from './utils'
export type { User }
const db = getFirestore(app)
export const auth = getAuth(app)
export async function getUser(userId: string) {
const docSnap = await getDoc(doc(db, 'users', userId))
return docSnap.data() as User
}
export async function getUserByUsername(username: string) {
// Find a user whose username matches the given username, or null if no such user exists.
const userCollection = collection(db, 'users')
const q = query(userCollection, where('username', '==', username), limit(1))
const docs = await getDocs(q)
const users = docs.docs.map((doc) => doc.data() as User)
return users[0] || null
}
export async function setUser(userId: string, user: User) {
await setDoc(doc(db, 'users', userId), user)
}
export async function updateUser(userId: string, update: Partial<User>) {
await updateDoc(doc(db, 'users', userId), { ...update })
}
export function listenForUser(
userId: string,
setUser: (user: User | null) => void
) {
const userRef = doc(db, 'users', userId)
return listenForValue<User>(userRef, setUser)
}
export function listenForPrivateUser(
userId: string,
setPrivateUser: (privateUser: PrivateUser | null) => void
) {
const userRef = doc(db, 'private-users', userId)
return listenForValue<PrivateUser>(userRef, setPrivateUser)
}
const CACHED_USER_KEY = 'CACHED_USER_KEY'
// used to avoid weird race condition
let createUserPromise: Promise<User | null> | undefined = undefined
export function listenForLogin(onUser: (user: User | null) => void) {
const cachedUser = localStorage.getItem(CACHED_USER_KEY)
onUser(cachedUser ? JSON.parse(cachedUser) : null)
if (!cachedUser) createUser() // warm up cloud function
return onAuthStateChanged(auth, async (fbUser) => {
if (fbUser) {
let user: User | null = await getUser(fbUser.uid)
if (!user) {
if (!createUserPromise) {
createUserPromise = createUser()
}
user = (await createUserPromise) || null
}
onUser(user)
// Persist to local storage, to reduce login blink next time.
// Note: Cap on localStorage size is ~5mb
localStorage.setItem(CACHED_USER_KEY, JSON.stringify(user))
} else {
// User logged out; reset to null
onUser(null)
localStorage.removeItem(CACHED_USER_KEY)
createUserPromise = undefined
}
})
}
export async function firebaseLogin() {
const provider = new GoogleAuthProvider()
signInWithPopup(auth, provider)
}
export async function firebaseLogout() {
auth.signOut()
}
const storage = getStorage(app)
// Example: uploadData('avatars/ajfi8iejsf.png', data)
export async function uploadData(
path: string,
data: ArrayBuffer | Blob | Uint8Array
) {
const uploadRef = ref(storage, path)
// Uploaded files should be cached for 1 day, then revalidated
// See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control
const metadata = { cacheControl: 'public, max-age=86400, must-revalidate' }
await uploadBytes(uploadRef, data, metadata)
return await getDownloadURL(uploadRef)
}
export async function listAllUsers() {
const userCollection = collection(db, 'users')
const q = query(userCollection)
const docs = await getDocs(q)
return docs.docs.map((doc) => doc.data() as User)
}
export function listenForAllUsers(setUsers: (users: User[]) => void) {
const userCollection = collection(db, 'users')
const q = query(userCollection)
listenForValues(q, setUsers)
}
export function listenForPrivateUsers(
setUsers: (users: PrivateUser[]) => void
) {
const userCollection = collection(db, 'private-users')
const q = query(userCollection)
listenForValues(q, setUsers)
}
const topTradersQuery = query(
collection(db, 'users'),
orderBy('totalPnLCached', 'desc'),
limit(21)
)
export async function getTopTraders() {
const users = await getValues<User>(topTradersQuery)
return users.filter((user) => user.username !== 'SG').slice(0, 20)
}
const topCreatorsQuery = query(
collection(db, 'users'),
orderBy('creatorVolumeCached', 'desc'),
limit(20)
)
export function getTopCreators() {
return getValues<User>(topCreatorsQuery)
}