Fix up several pages to load user data on the server (#722)
* Fix up several pages to load user data on the server * Add key prop to `EditUserField`
This commit is contained in:
parent
5649161348
commit
e7f1d3924b
|
@ -52,6 +52,11 @@ export async function getUser(userId: string) {
|
|||
return (await getDoc(doc(users, userId))).data()!
|
||||
}
|
||||
|
||||
export async function getPrivateUser(userId: string) {
|
||||
/* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
|
||||
return (await getDoc(doc(users, userId))).data()!
|
||||
}
|
||||
|
||||
export async function getUserByUsername(username: string) {
|
||||
// Find a user whose username matches the given username, or null if no such user exists.
|
||||
const q = query(users, where('username', '==', username), limit(1))
|
||||
|
|
|
@ -4,7 +4,7 @@ import clsx from 'clsx'
|
|||
import dayjs from 'dayjs'
|
||||
import Textarea from 'react-expanding-textarea'
|
||||
import { Spacer } from 'web/components/layout/spacer'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { getUser } from 'web/lib/firebase/users'
|
||||
import { Contract, contractPath } from 'web/lib/firebase/contracts'
|
||||
import { createMarket } from 'web/lib/firebase/api'
|
||||
import { FIXED_ANTE } from 'common/antes'
|
||||
|
@ -33,7 +33,10 @@ import { Title } from 'web/components/title'
|
|||
import { SEO } from 'web/components/SEO'
|
||||
import { MultipleChoiceAnswers } from 'web/components/answers/multiple-choice-answers'
|
||||
|
||||
export const getServerSideProps = redirectIfLoggedOut('/')
|
||||
export const getServerSideProps = redirectIfLoggedOut('/', async (_, creds) => {
|
||||
const user = await getUser(creds.user.uid)
|
||||
return { props: { user } }
|
||||
})
|
||||
|
||||
type NewQuestionParams = {
|
||||
groupId?: string
|
||||
|
@ -49,8 +52,9 @@ type NewQuestionParams = {
|
|||
initValue?: string
|
||||
}
|
||||
|
||||
export default function Create() {
|
||||
export default function Create(props: { user: User }) {
|
||||
useTracking('view create page')
|
||||
const { user } = props
|
||||
const router = useRouter()
|
||||
const params = router.query as NewQuestionParams
|
||||
// TODO: Not sure why Question is pulled out as its own component;
|
||||
|
@ -60,8 +64,7 @@ export default function Create() {
|
|||
setQuestion(params.q ?? '')
|
||||
}, [params.q])
|
||||
|
||||
const creator = useUser()
|
||||
if (!router.isReady || !creator) return <div />
|
||||
if (!router.isReady) return <div />
|
||||
|
||||
return (
|
||||
<Page>
|
||||
|
@ -93,7 +96,7 @@ export default function Create() {
|
|||
</div>
|
||||
</form>
|
||||
<Spacer h={6} />
|
||||
<NewContract question={question} params={params} creator={creator} />
|
||||
<NewContract question={question} params={params} creator={user} />
|
||||
</div>
|
||||
</div>
|
||||
</Page>
|
||||
|
@ -102,7 +105,7 @@ export default function Create() {
|
|||
|
||||
// Allow user to create a new contract
|
||||
export function NewContract(props: {
|
||||
creator?: User | null
|
||||
creator: User
|
||||
question: string
|
||||
params?: NewQuestionParams
|
||||
}) {
|
||||
|
@ -120,14 +123,14 @@ export function NewContract(props: {
|
|||
const [answers, setAnswers] = useState<string[]>([]) // for multiple choice
|
||||
|
||||
useEffect(() => {
|
||||
if (groupId && creator)
|
||||
if (groupId)
|
||||
getGroup(groupId).then((group) => {
|
||||
if (group && canModifyGroupContracts(group, creator.id)) {
|
||||
setSelectedGroup(group)
|
||||
setShowGroupSelector(false)
|
||||
}
|
||||
})
|
||||
}, [creator, groupId])
|
||||
}, [creator.id, groupId])
|
||||
const [ante, _setAnte] = useState(FIXED_ANTE)
|
||||
|
||||
// If params.closeTime is set, extract out the specified date and time
|
||||
|
@ -152,7 +155,7 @@ export function NewContract(props: {
|
|||
? dayjs(`${closeDate}T${closeHoursMinutes}`).valueOf()
|
||||
: undefined
|
||||
|
||||
const balance = creator?.balance || 0
|
||||
const balance = creator.balance || 0
|
||||
|
||||
const min = minString ? parseFloat(minString) : undefined
|
||||
const max = maxString ? parseFloat(maxString) : undefined
|
||||
|
@ -214,7 +217,7 @@ export function NewContract(props: {
|
|||
|
||||
async function submit() {
|
||||
// TODO: Tell users why their contract is invalid
|
||||
if (!creator || !isValid) return
|
||||
if (!isValid) return
|
||||
setIsSubmitting(true)
|
||||
try {
|
||||
const result = await createMarket(
|
||||
|
@ -249,8 +252,6 @@ export function NewContract(props: {
|
|||
}
|
||||
}
|
||||
|
||||
if (!creator) return <></>
|
||||
|
||||
return (
|
||||
<div>
|
||||
<label className="label">
|
||||
|
|
|
@ -11,10 +11,11 @@ import { Page } from 'web/components/page'
|
|||
import { SEO } from 'web/components/SEO'
|
||||
import { Title } from 'web/components/title'
|
||||
import { Subtitle } from 'web/components/subtitle'
|
||||
import { useUser } from 'web/hooks/use-user'
|
||||
import { getUser } from 'web/lib/firebase/users'
|
||||
import { useUserManalinks } from 'web/lib/firebase/manalinks'
|
||||
import { useUserById } from 'web/hooks/use-user'
|
||||
import { ManalinkTxn } from 'common/txn'
|
||||
import { User } from 'common/user'
|
||||
import { Avatar } from 'web/components/avatar'
|
||||
import { RelativeTimestamp } from 'web/components/relative-timestamp'
|
||||
import { UserLink } from 'web/components/user-page'
|
||||
|
@ -27,15 +28,19 @@ import { Manalink } from 'common/manalink'
|
|||
import { REFERRAL_AMOUNT } from 'common/user'
|
||||
|
||||
const LINKS_PER_PAGE = 24
|
||||
export const getServerSideProps = redirectIfLoggedOut('/')
|
||||
|
||||
export const getServerSideProps = redirectIfLoggedOut('/', async (_, creds) => {
|
||||
const user = await getUser(creds.user.uid)
|
||||
return { props: { user } }
|
||||
})
|
||||
|
||||
export function getManalinkUrl(slug: string) {
|
||||
return `${location.protocol}//${location.host}/link/${slug}`
|
||||
}
|
||||
|
||||
export default function LinkPage() {
|
||||
const user = useUser()
|
||||
const links = useUserManalinks(user?.id ?? '')
|
||||
export default function LinkPage(props: { user: User }) {
|
||||
const { user } = props
|
||||
const links = useUserManalinks(user.id ?? '')
|
||||
// const manalinkTxns = useManalinkTxns(user?.id ?? '')
|
||||
const [highlightedSlug, setHighlightedSlug] = useState('')
|
||||
const unclaimedLinks = links.filter(
|
||||
|
@ -44,10 +49,6 @@ export default function LinkPage() {
|
|||
(l.expiresTime == null || l.expiresTime > Date.now())
|
||||
)
|
||||
|
||||
if (user == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<SEO
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Tabs } from 'web/components/layout/tabs'
|
||||
import { usePrivateUser } from 'web/hooks/use-user'
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { Notification, notification_source_types } from 'common/notification'
|
||||
import { Avatar, EmptyAvatar } from 'web/components/avatar'
|
||||
|
@ -13,9 +12,8 @@ import {
|
|||
MANIFOLD_AVATAR_URL,
|
||||
MANIFOLD_USERNAME,
|
||||
PrivateUser,
|
||||
User,
|
||||
} from 'common/user'
|
||||
import { getUser } from 'web/lib/firebase/users'
|
||||
import { getPrivateUser } from 'web/lib/firebase/users'
|
||||
import clsx from 'clsx'
|
||||
import { RelativeTimestamp } from 'web/components/relative-timestamp'
|
||||
import { Linkify } from 'web/components/linkify'
|
||||
|
@ -35,7 +33,6 @@ import { formatMoney } from 'common/util/format'
|
|||
import { groupPath } from 'web/lib/firebase/groups'
|
||||
import { UNIQUE_BETTOR_BONUS_AMOUNT } from 'common/numeric-constants'
|
||||
import { groupBy, sum, uniq } from 'lodash'
|
||||
import Custom404 from 'web/pages/404'
|
||||
import { track } from '@amplitude/analytics-browser'
|
||||
import { Pagination } from 'web/components/pagination'
|
||||
import { useWindowSize } from 'web/hooks/use-window-size'
|
||||
|
@ -49,13 +46,12 @@ const MULTIPLE_USERS_KEY = 'multipleUsers'
|
|||
const HIGHLIGHT_CLASS = 'bg-indigo-50'
|
||||
|
||||
export const getServerSideProps = redirectIfLoggedOut('/', async (_, creds) => {
|
||||
const user = await getUser(creds.user.uid)
|
||||
return { props: { user } }
|
||||
const privateUser = await getPrivateUser(creds.user.uid)
|
||||
return { props: { privateUser } }
|
||||
})
|
||||
|
||||
export default function Notifications(props: { user: User }) {
|
||||
const { user } = props
|
||||
const privateUser = usePrivateUser(user?.id)
|
||||
export default function Notifications(props: { privateUser: PrivateUser }) {
|
||||
const { privateUser } = props
|
||||
const local = safeLocalStorage()
|
||||
let localNotifications = [] as Notification[]
|
||||
const localSavedNotificationGroups = local?.getItem('notification-groups')
|
||||
|
@ -67,7 +63,6 @@ export default function Notifications(props: { user: User }) {
|
|||
.flat()
|
||||
}
|
||||
|
||||
if (!user) return <Custom404 />
|
||||
return (
|
||||
<Page>
|
||||
<div className={'px-2 pt-4 sm:px-4 lg:pt-0'}>
|
||||
|
@ -81,17 +76,11 @@ export default function Notifications(props: { user: User }) {
|
|||
tabs={[
|
||||
{
|
||||
title: 'Notifications',
|
||||
content: privateUser ? (
|
||||
content: (
|
||||
<NotificationsList
|
||||
privateUser={privateUser}
|
||||
cachedNotifications={localNotifications}
|
||||
/>
|
||||
) : (
|
||||
<div className={'min-h-[100vh]'}>
|
||||
<RenderNotificationGroups
|
||||
notificationGroups={localNotificationGroups}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
|
|
|
@ -1,25 +1,35 @@
|
|||
import React, { useEffect, useState } from 'react'
|
||||
import React, { useState } from 'react'
|
||||
import { RefreshIcon } from '@heroicons/react/outline'
|
||||
|
||||
import { AddFundsButton } from 'web/components/add-funds-button'
|
||||
import { Page } from 'web/components/page'
|
||||
import { SEO } from 'web/components/SEO'
|
||||
import { Title } from 'web/components/title'
|
||||
import { usePrivateUser, useUser } from 'web/hooks/use-user'
|
||||
import { formatMoney } from 'common/util/format'
|
||||
import { cleanDisplayName, cleanUsername } from 'common/util/clean-username'
|
||||
import { changeUserInfo } from 'web/lib/firebase/api'
|
||||
import { uploadImage } from 'web/lib/firebase/storage'
|
||||
import { Col } from 'web/components/layout/col'
|
||||
import { Row } from 'web/components/layout/row'
|
||||
import { User } from 'common/user'
|
||||
import { updateUser, updatePrivateUser } from 'web/lib/firebase/users'
|
||||
import { User, PrivateUser } from 'common/user'
|
||||
import {
|
||||
getUser,
|
||||
getPrivateUser,
|
||||
updateUser,
|
||||
updatePrivateUser,
|
||||
} from 'web/lib/firebase/users'
|
||||
import { defaultBannerUrl } from 'web/components/user-page'
|
||||
import { SiteLink } from 'web/components/site-link'
|
||||
import Textarea from 'react-expanding-textarea'
|
||||
import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth'
|
||||
|
||||
export const getServerSideProps = redirectIfLoggedOut('/')
|
||||
export const getServerSideProps = redirectIfLoggedOut('/', async (_, creds) => {
|
||||
const [user, privateUser] = await Promise.all([
|
||||
getUser(creds.user.uid),
|
||||
getPrivateUser(creds.user.uid),
|
||||
])
|
||||
return { props: { user, privateUser } }
|
||||
})
|
||||
|
||||
function EditUserField(props: {
|
||||
user: User
|
||||
|
@ -58,64 +68,45 @@ function EditUserField(props: {
|
|||
)
|
||||
}
|
||||
|
||||
export default function ProfilePage() {
|
||||
const user = useUser()
|
||||
const privateUser = usePrivateUser(user?.id)
|
||||
|
||||
const [avatarUrl, setAvatarUrl] = useState(user?.avatarUrl || '')
|
||||
export default function ProfilePage(props: {
|
||||
user: User
|
||||
privateUser: PrivateUser
|
||||
}) {
|
||||
const { user, privateUser } = props
|
||||
const [avatarUrl, setAvatarUrl] = useState(user.avatarUrl || '')
|
||||
const [avatarLoading, setAvatarLoading] = useState(false)
|
||||
const [name, setName] = useState(user?.name || '')
|
||||
const [username, setUsername] = useState(user?.username || '')
|
||||
const [apiKey, setApiKey] = useState(privateUser?.apiKey || '')
|
||||
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
setAvatarUrl(user.avatarUrl || '')
|
||||
setName(user.name || '')
|
||||
setUsername(user.username || '')
|
||||
}
|
||||
}, [user])
|
||||
|
||||
useEffect(() => {
|
||||
if (privateUser) {
|
||||
setApiKey(privateUser.apiKey || '')
|
||||
}
|
||||
}, [privateUser])
|
||||
const [name, setName] = useState(user.name)
|
||||
const [username, setUsername] = useState(user.username)
|
||||
const [apiKey, setApiKey] = useState(privateUser.apiKey || '')
|
||||
|
||||
const updateDisplayName = async () => {
|
||||
const newName = cleanDisplayName(name)
|
||||
|
||||
if (newName) {
|
||||
setName(newName)
|
||||
await changeUserInfo({ name: newName }).catch((_) =>
|
||||
setName(user?.name || '')
|
||||
)
|
||||
await changeUserInfo({ name: newName }).catch((_) => setName(user.name))
|
||||
} else {
|
||||
setName(user?.name || '')
|
||||
setName(user.name)
|
||||
}
|
||||
}
|
||||
|
||||
const updateUsername = async () => {
|
||||
const newUsername = cleanUsername(username)
|
||||
|
||||
if (newUsername) {
|
||||
setUsername(newUsername)
|
||||
await changeUserInfo({ username: newUsername }).catch((_) =>
|
||||
setUsername(user?.username || '')
|
||||
setUsername(user.username)
|
||||
)
|
||||
} else {
|
||||
setUsername(user?.username || '')
|
||||
setUsername(user.username)
|
||||
}
|
||||
}
|
||||
|
||||
const updateApiKey = async (e: React.MouseEvent) => {
|
||||
const newApiKey = crypto.randomUUID()
|
||||
if (user?.id != null) {
|
||||
setApiKey(newApiKey)
|
||||
await updatePrivateUser(user.id, { apiKey: newApiKey }).catch(() => {
|
||||
setApiKey(privateUser?.apiKey || '')
|
||||
})
|
||||
}
|
||||
setApiKey(newApiKey)
|
||||
await updatePrivateUser(user.id, { apiKey: newApiKey }).catch(() => {
|
||||
setApiKey(privateUser.apiKey || '')
|
||||
})
|
||||
e.preventDefault()
|
||||
}
|
||||
|
||||
|
@ -124,7 +115,7 @@ export default function ProfilePage() {
|
|||
|
||||
setAvatarLoading(true)
|
||||
|
||||
await uploadImage(user?.username || 'default', file)
|
||||
await uploadImage(user.username, file)
|
||||
.then(async (url) => {
|
||||
await changeUserInfo({ avatarUrl: url })
|
||||
setAvatarUrl(url)
|
||||
|
@ -132,14 +123,10 @@ export default function ProfilePage() {
|
|||
})
|
||||
.catch(() => {
|
||||
setAvatarLoading(false)
|
||||
setAvatarUrl(user?.avatarUrl || '')
|
||||
setAvatarUrl(user.avatarUrl || '')
|
||||
})
|
||||
}
|
||||
|
||||
if (user == null) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<SEO title="Profile" description="User profile settings" url="/profile" />
|
||||
|
@ -147,7 +134,7 @@ export default function ProfilePage() {
|
|||
<Col className="max-w-lg rounded bg-white p-6 shadow-md sm:mx-auto">
|
||||
<Row className="justify-between">
|
||||
<Title className="!mt-0" text="Edit Profile" />
|
||||
<SiteLink className="btn btn-primary" href={`/${user?.username}`}>
|
||||
<SiteLink className="btn btn-primary" href={`/${user.username}`}>
|
||||
Done
|
||||
</SiteLink>
|
||||
</Row>
|
||||
|
@ -192,54 +179,53 @@ export default function ProfilePage() {
|
|||
/>
|
||||
</div>
|
||||
|
||||
{user && (
|
||||
<>
|
||||
{/* TODO: Allow users with M$ 2000 of assets to set custom banners */}
|
||||
{/* <EditUserField
|
||||
{/* TODO: Allow users with M$ 2000 of assets to set custom banners */}
|
||||
{/* <EditUserField
|
||||
user={user}
|
||||
field="bannerUrl"
|
||||
label="Banner Url"
|
||||
isEditing={isEditing}
|
||||
/> */}
|
||||
<label className="label">
|
||||
Banner image{' '}
|
||||
<span className="text-sm text-gray-400">
|
||||
Not editable for now
|
||||
</span>
|
||||
</label>
|
||||
<div
|
||||
className="h-32 w-full bg-cover bg-center sm:h-40"
|
||||
style={{
|
||||
backgroundImage: `url(${
|
||||
user.bannerUrl || defaultBannerUrl(user.id)
|
||||
})`,
|
||||
}}
|
||||
/>
|
||||
<label className="label">
|
||||
Banner image{' '}
|
||||
<span className="text-sm text-gray-400">Not editable for now</span>
|
||||
</label>
|
||||
<div
|
||||
className="h-32 w-full bg-cover bg-center sm:h-40"
|
||||
style={{
|
||||
backgroundImage: `url(${
|
||||
user.bannerUrl || defaultBannerUrl(user.id)
|
||||
})`,
|
||||
}}
|
||||
/>
|
||||
|
||||
{(
|
||||
[
|
||||
['bio', 'Bio'],
|
||||
['website', 'Website URL'],
|
||||
['twitterHandle', 'Twitter'],
|
||||
['discordHandle', 'Discord'],
|
||||
] as const
|
||||
).map(([field, label]) => (
|
||||
<EditUserField user={user} field={field} label={label} />
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
{(
|
||||
[
|
||||
['bio', 'Bio'],
|
||||
['website', 'Website URL'],
|
||||
['twitterHandle', 'Twitter'],
|
||||
['discordHandle', 'Discord'],
|
||||
] as const
|
||||
).map(([field, label]) => (
|
||||
<EditUserField
|
||||
key={field}
|
||||
user={user}
|
||||
field={field}
|
||||
label={label}
|
||||
/>
|
||||
))}
|
||||
|
||||
<div>
|
||||
<label className="label">Email</label>
|
||||
<div className="ml-1 text-gray-500">
|
||||
{privateUser?.email ?? '\u00a0'}
|
||||
{privateUser.email ?? '\u00a0'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="label">Balance</label>
|
||||
<Row className="ml-1 items-start gap-4 text-gray-500">
|
||||
{formatMoney(user?.balance || 0)}
|
||||
{formatMoney(user.balance)}
|
||||
<AddFundsButton />
|
||||
</Row>
|
||||
</div>
|
||||
|
|
Loading…
Reference in New Issue
Block a user