2022-08-09 05:43:04 +00:00
|
|
|
import React, { useState } from 'react'
|
2022-05-16 03:41:07 +00:00
|
|
|
import { RefreshIcon } from '@heroicons/react/outline'
|
2022-02-04 03:04:56 +00:00
|
|
|
|
2022-05-09 13:04:36 +00:00
|
|
|
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 { formatMoney } from 'common/util/format'
|
|
|
|
import { cleanDisplayName, cleanUsername } from 'common/util/clean-username'
|
2022-07-10 22:03:15 +00:00
|
|
|
import { changeUserInfo } from 'web/lib/firebase/api'
|
2022-05-09 13:04:36 +00:00
|
|
|
import { uploadImage } from 'web/lib/firebase/storage'
|
|
|
|
import { Col } from 'web/components/layout/col'
|
|
|
|
import { Row } from 'web/components/layout/row'
|
2022-08-09 05:43:04 +00:00
|
|
|
import { User, PrivateUser } from 'common/user'
|
|
|
|
import {
|
|
|
|
getUser,
|
|
|
|
getPrivateUser,
|
|
|
|
updateUser,
|
|
|
|
updatePrivateUser,
|
|
|
|
} from 'web/lib/firebase/users'
|
2022-05-09 13:04:36 +00:00
|
|
|
import { defaultBannerUrl } from 'web/components/user-page'
|
|
|
|
import { SiteLink } from 'web/components/site-link'
|
2022-02-18 01:16:58 +00:00
|
|
|
import Textarea from 'react-expanding-textarea'
|
2022-07-19 07:50:11 +00:00
|
|
|
import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth'
|
|
|
|
|
2022-08-09 05:43:04 +00:00
|
|
|
export const getServerSideProps = redirectIfLoggedOut('/', async (_, creds) => {
|
|
|
|
const [user, privateUser] = await Promise.all([
|
|
|
|
getUser(creds.user.uid),
|
|
|
|
getPrivateUser(creds.user.uid),
|
|
|
|
])
|
|
|
|
return { props: { user, privateUser } }
|
|
|
|
})
|
2022-02-18 01:16:58 +00:00
|
|
|
|
|
|
|
function EditUserField(props: {
|
|
|
|
user: User
|
2022-05-26 21:41:24 +00:00
|
|
|
field: 'bio' | 'website' | 'bannerUrl' | 'twitterHandle' | 'discordHandle'
|
2022-02-18 01:16:58 +00:00
|
|
|
label: string
|
|
|
|
}) {
|
|
|
|
const { user, field, label } = props
|
|
|
|
const [value, setValue] = useState(user[field] ?? '')
|
|
|
|
|
|
|
|
async function updateField() {
|
|
|
|
// Note: We trim whitespace before uploading to Firestore
|
|
|
|
await updateUser(user.id, { [field]: value.trim() })
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<div>
|
|
|
|
<label className="label">{label}</label>
|
|
|
|
|
|
|
|
{field === 'bio' ? (
|
|
|
|
<Textarea
|
2022-03-25 16:27:28 +00:00
|
|
|
className="textarea textarea-bordered w-full resize-none"
|
2022-02-18 01:16:58 +00:00
|
|
|
value={value}
|
|
|
|
onChange={(e) => setValue(e.target.value)}
|
|
|
|
onBlur={updateField}
|
|
|
|
/>
|
|
|
|
) : (
|
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
className="input input-bordered"
|
|
|
|
value={value}
|
|
|
|
onChange={(e) => setValue(e.target.value || '')}
|
|
|
|
onBlur={updateField}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
2022-02-04 03:04:56 +00:00
|
|
|
|
2022-08-09 05:43:04 +00:00
|
|
|
export default function ProfilePage(props: {
|
|
|
|
user: User
|
|
|
|
privateUser: PrivateUser
|
|
|
|
}) {
|
|
|
|
const { user, privateUser } = props
|
|
|
|
const [avatarUrl, setAvatarUrl] = useState(user.avatarUrl || '')
|
2022-02-04 03:04:56 +00:00
|
|
|
const [avatarLoading, setAvatarLoading] = useState(false)
|
2022-08-09 05:43:04 +00:00
|
|
|
const [name, setName] = useState(user.name)
|
|
|
|
const [username, setUsername] = useState(user.username)
|
|
|
|
const [apiKey, setApiKey] = useState(privateUser.apiKey || '')
|
2022-05-16 03:41:07 +00:00
|
|
|
|
2022-02-04 03:04:56 +00:00
|
|
|
const updateDisplayName = async () => {
|
|
|
|
const newName = cleanDisplayName(name)
|
|
|
|
if (newName) {
|
|
|
|
setName(newName)
|
2022-08-09 05:43:04 +00:00
|
|
|
await changeUserInfo({ name: newName }).catch((_) => setName(user.name))
|
2022-02-04 03:04:56 +00:00
|
|
|
} else {
|
2022-08-09 05:43:04 +00:00
|
|
|
setName(user.name)
|
2022-02-04 03:04:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const updateUsername = async () => {
|
|
|
|
const newUsername = cleanUsername(username)
|
|
|
|
if (newUsername) {
|
|
|
|
setUsername(newUsername)
|
2022-07-08 22:00:03 +00:00
|
|
|
await changeUserInfo({ username: newUsername }).catch((_) =>
|
2022-08-09 05:43:04 +00:00
|
|
|
setUsername(user.username)
|
2022-07-08 22:00:03 +00:00
|
|
|
)
|
2022-02-04 03:04:56 +00:00
|
|
|
} else {
|
2022-08-09 05:43:04 +00:00
|
|
|
setUsername(user.username)
|
2022-02-04 03:04:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-16 03:41:07 +00:00
|
|
|
const updateApiKey = async (e: React.MouseEvent) => {
|
|
|
|
const newApiKey = crypto.randomUUID()
|
2022-08-09 05:43:04 +00:00
|
|
|
setApiKey(newApiKey)
|
|
|
|
await updatePrivateUser(user.id, { apiKey: newApiKey }).catch(() => {
|
|
|
|
setApiKey(privateUser.apiKey || '')
|
|
|
|
})
|
2022-05-16 03:41:07 +00:00
|
|
|
e.preventDefault()
|
|
|
|
}
|
|
|
|
|
2022-02-04 03:04:56 +00:00
|
|
|
const fileHandler = async (event: any) => {
|
|
|
|
const file = event.target.files[0]
|
|
|
|
|
|
|
|
setAvatarLoading(true)
|
|
|
|
|
2022-08-09 05:43:04 +00:00
|
|
|
await uploadImage(user.username, file)
|
2022-02-04 03:04:56 +00:00
|
|
|
.then(async (url) => {
|
|
|
|
await changeUserInfo({ avatarUrl: url })
|
|
|
|
setAvatarUrl(url)
|
|
|
|
setAvatarLoading(false)
|
|
|
|
})
|
|
|
|
.catch(() => {
|
|
|
|
setAvatarLoading(false)
|
2022-08-09 05:43:04 +00:00
|
|
|
setAvatarUrl(user.avatarUrl || '')
|
2022-02-04 03:04:56 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return (
|
|
|
|
<Page>
|
|
|
|
<SEO title="Profile" description="User profile settings" url="/profile" />
|
|
|
|
|
2022-02-11 18:40:22 +00:00
|
|
|
<Col className="max-w-lg rounded bg-white p-6 shadow-md sm:mx-auto">
|
2022-02-04 03:04:56 +00:00
|
|
|
<Row className="justify-between">
|
2022-02-18 01:16:58 +00:00
|
|
|
<Title className="!mt-0" text="Edit Profile" />
|
2022-08-09 05:43:04 +00:00
|
|
|
<SiteLink className="btn btn-primary" href={`/${user.username}`}>
|
2022-02-18 01:16:58 +00:00
|
|
|
Done
|
|
|
|
</SiteLink>
|
2022-02-04 03:04:56 +00:00
|
|
|
</Row>
|
|
|
|
<Col className="gap-4">
|
|
|
|
<Row className="items-center gap-4">
|
|
|
|
{avatarLoading ? (
|
|
|
|
<button className="btn btn-ghost btn-lg btn-circle loading"></button>
|
|
|
|
) : (
|
|
|
|
<>
|
|
|
|
<img
|
|
|
|
src={avatarUrl}
|
|
|
|
width={80}
|
|
|
|
height={80}
|
2022-02-11 18:40:22 +00:00
|
|
|
className="flex items-center justify-center rounded-full bg-gray-400"
|
2022-02-04 03:04:56 +00:00
|
|
|
/>
|
2022-02-18 01:16:58 +00:00
|
|
|
<input type="file" name="file" onChange={fileHandler} />
|
2022-02-04 03:04:56 +00:00
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</Row>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
<label className="label">Display name</label>
|
2022-02-18 01:16:58 +00:00
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
placeholder="Display name"
|
|
|
|
className="input input-bordered"
|
|
|
|
value={name}
|
|
|
|
onChange={(e) => setName(e.target.value || '')}
|
|
|
|
onBlur={updateDisplayName}
|
|
|
|
/>
|
2022-02-04 03:04:56 +00:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
<label className="label">Username</label>
|
2022-02-18 01:16:58 +00:00
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
placeholder="Username"
|
|
|
|
className="input input-bordered"
|
|
|
|
value={username}
|
|
|
|
onChange={(e) => setUsername(e.target.value || '')}
|
|
|
|
onBlur={updateUsername}
|
|
|
|
/>
|
2022-02-04 03:04:56 +00:00
|
|
|
</div>
|
|
|
|
|
2022-08-09 05:43:04 +00:00
|
|
|
{/* TODO: Allow users with M$ 2000 of assets to set custom banners */}
|
|
|
|
{/* <EditUserField
|
2022-02-18 01:16:58 +00:00
|
|
|
user={user}
|
|
|
|
field="bannerUrl"
|
|
|
|
label="Banner Url"
|
|
|
|
isEditing={isEditing}
|
|
|
|
/> */}
|
2022-08-09 05:43:04 +00:00
|
|
|
<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
|
|
|
|
key={field}
|
|
|
|
user={user}
|
|
|
|
field={field}
|
|
|
|
label={label}
|
|
|
|
/>
|
|
|
|
))}
|
2022-02-18 01:16:58 +00:00
|
|
|
|
2022-02-04 03:04:56 +00:00
|
|
|
<div>
|
|
|
|
<label className="label">Email</label>
|
|
|
|
<div className="ml-1 text-gray-500">
|
2022-08-09 05:43:04 +00:00
|
|
|
{privateUser.email ?? '\u00a0'}
|
2022-02-04 03:04:56 +00:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div>
|
|
|
|
<label className="label">Balance</label>
|
2022-02-11 18:40:22 +00:00
|
|
|
<Row className="ml-1 items-start gap-4 text-gray-500">
|
2022-08-09 05:43:04 +00:00
|
|
|
{formatMoney(user.balance)}
|
2022-02-04 03:04:56 +00:00
|
|
|
<AddFundsButton />
|
|
|
|
</Row>
|
|
|
|
</div>
|
2022-05-16 03:41:07 +00:00
|
|
|
|
|
|
|
<div>
|
|
|
|
<label className="label">API key</label>
|
|
|
|
<div className="input-group w-full">
|
|
|
|
<input
|
|
|
|
type="text"
|
|
|
|
placeholder="Click refresh to generate key"
|
|
|
|
className="input input-bordered w-full"
|
|
|
|
value={apiKey}
|
|
|
|
readOnly
|
|
|
|
/>
|
|
|
|
<button
|
|
|
|
className="btn btn-primary btn-square p-2"
|
|
|
|
onClick={updateApiKey}
|
|
|
|
>
|
|
|
|
<RefreshIcon />
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
2022-02-04 03:04:56 +00:00
|
|
|
</Col>
|
|
|
|
</Col>
|
|
|
|
</Page>
|
|
|
|
)
|
|
|
|
}
|