diff --git a/common/envs/prod.ts b/common/envs/prod.ts index f8aaf4cc..5bd12095 100644 --- a/common/envs/prod.ts +++ b/common/envs/prod.ts @@ -22,6 +22,7 @@ export type EnvConfig = { // Currency controls fixedAnte?: number startingBalance?: number + referralBonus?: number } type FirebaseConfig = { diff --git a/common/user.ts b/common/user.ts index 1995ce34..0dac5a19 100644 --- a/common/user.ts +++ b/common/user.ts @@ -45,7 +45,7 @@ export type User = { export const STARTING_BALANCE = ENV_CONFIG.startingBalance ?? 1000 // for sus users, i.e. multiple sign ups for same person export const SUS_STARTING_BALANCE = ENV_CONFIG.startingBalance ?? 10 -export const REFERRAL_AMOUNT = 500 +export const REFERRAL_AMOUNT = ENV_CONFIG.referralBonus ?? 500 export type PrivateUser = { id: string // same as User.id username: string // denormalized from User diff --git a/web/components/button.tsx b/web/components/button.tsx index 8cdeacdd..d279d9a0 100644 --- a/web/components/button.tsx +++ b/web/components/button.tsx @@ -39,7 +39,7 @@ export function Button(props: { color === 'yellow' && 'bg-yellow-400 text-white hover:bg-yellow-500', color === 'blue' && 'bg-blue-400 text-white hover:bg-blue-500', color === 'indigo' && 'bg-indigo-500 text-white hover:bg-indigo-600', - color === 'gray' && 'bg-gray-200 text-gray-700 hover:bg-gray-300', + color === 'gray' && 'bg-gray-100 text-gray-600 hover:bg-gray-200', className )} disabled={disabled} diff --git a/web/components/charity/charity-card.tsx b/web/components/charity/charity-card.tsx index 31995284..fc327b9f 100644 --- a/web/components/charity/charity-card.tsx +++ b/web/components/charity/charity-card.tsx @@ -6,10 +6,9 @@ import { Charity } from 'common/charity' import { useCharityTxns } from 'web/hooks/use-charity-txns' import { manaToUSD } from '../../../common/util/format' import { Row } from '../layout/row' -import { Col } from '../layout/col' export function CharityCard(props: { charity: Charity; match?: number }) { - const { charity, match } = props + const { charity } = props const { slug, photo, preview, id, tags } = charity const txns = useCharityTxns(id) @@ -36,18 +35,18 @@ export function CharityCard(props: { charity: Charity; match?: number }) { {raised > 0 && ( <> - + {formatUsd(raised)} - raised - - {match && ( + raised + + {/* {match && ( +{formatUsd(match)} match - )} + )} */} )} diff --git a/web/components/contract-search.tsx b/web/components/contract-search.tsx index 78b28a94..013208d8 100644 --- a/web/components/contract-search.tsx +++ b/web/components/contract-search.tsx @@ -188,7 +188,11 @@ export function ContractSearch(props: { {toPairs(filterOptions).map(([label, f]) => { return ( - setFilter(f)}> + setFilter(f)} + > {label} ) diff --git a/web/components/contract/contract-description.tsx b/web/components/contract/contract-description.tsx index d9864186..f9db0cd9 100644 --- a/web/components/contract/contract-description.tsx +++ b/web/components/contract/contract-description.tsx @@ -24,13 +24,10 @@ export function ContractDescription(props: { return (
{isCreator || isAdmin ? ( - + ) : ( )} - {isAdmin && !isCreator && ( -
(👆 admin powers)
- )}
) } @@ -39,8 +36,8 @@ function editTimestamp() { return `${dayjs().format('MMM D, h:mma')}: ` } -function RichEditContract(props: { contract: Contract }) { - const { contract } = props +function RichEditContract(props: { contract: Contract; isAdmin?: boolean }) { + const { contract, isAdmin } = props const [editing, setEditing] = useState(false) const [editingQ, setEditingQ] = useState(false) const [isSubmitting, setIsSubmitting] = useState(false) @@ -90,9 +87,11 @@ function RichEditContract(props: { contract: Contract }) { <> - + + {isAdmin && 'Admin: '} - diff --git a/web/components/limit-bets.tsx b/web/components/limit-bets.tsx index 70e06d79..7e32db25 100644 --- a/web/components/limit-bets.tsx +++ b/web/components/limit-bets.tsx @@ -178,7 +178,7 @@ export function OrderBookButton(props: { - <Col className="justify-start gap-2 lg:flex-row lg:items-start"> + <Row className="hidden items-start justify-start gap-2 md:flex"> <LimitOrderTable limitBets={yesBets} contract={contract} @@ -189,6 +189,13 @@ export function OrderBookButton(props: { contract={contract} isYou={false} /> + </Row> + <Col className="md:hidden"> + <LimitOrderTable + limitBets={limitBets} + contract={contract} + isYou={false} + /> </Col> </Col> </Modal> diff --git a/web/components/portfolio/portfolio-value-section.tsx b/web/components/portfolio/portfolio-value-section.tsx index 1fabbd06..fa50365b 100644 --- a/web/components/portfolio/portfolio-value-section.tsx +++ b/web/components/portfolio/portfolio-value-section.tsx @@ -15,17 +15,17 @@ export const PortfolioValueSection = memo( const lastPortfolioMetrics = last(portfolioHistory) const [portfolioPeriod, setPortfolioPeriod] = useState<Period>('allTime') - // PATCH: If portfolio history started on June 1st, then we label it as "Since June" - // instead of "All time" - const allTimeLabel = - portfolioHistory[0].timestamp < Date.parse('2022-06-20T00:00:00.000Z') - ? 'Since June' - : 'All time' - if (portfolioHistory.length === 0 || !lastPortfolioMetrics) { return <></> } + // PATCH: If portfolio history started on June 1st, then we label it as "Since June" + // instead of "All time" + const allTimeLabel = + lastPortfolioMetrics.timestamp < Date.parse('2022-06-20T00:00:00.000Z') + ? 'Since June' + : 'All time' + return ( <div> <Row className="gap-8"> diff --git a/web/lib/firebase/auth.ts b/web/lib/firebase/auth.ts new file mode 100644 index 00000000..d1c440ec --- /dev/null +++ b/web/lib/firebase/auth.ts @@ -0,0 +1,54 @@ +import { PROJECT_ID } from 'common/envs/constants' +import { setCookie, getCookies } from '../util/cookie' +import { IncomingMessage, ServerResponse } from 'http' + +const TOKEN_KINDS = ['refresh', 'id'] as const +type TokenKind = typeof TOKEN_KINDS[number] + +const getAuthCookieName = (kind: TokenKind) => { + const suffix = `${PROJECT_ID}_${kind}`.toUpperCase().replaceAll('-', '_') + return `FIREBASE_TOKEN_${suffix}` +} + +const ID_COOKIE_NAME = getAuthCookieName('id') +const REFRESH_COOKIE_NAME = getAuthCookieName('refresh') + +export const getAuthCookies = (request?: IncomingMessage) => { + const data = request != null ? request.headers.cookie ?? '' : document.cookie + const cookies = getCookies(data) + return { + idToken: cookies[ID_COOKIE_NAME] as string | undefined, + refreshToken: cookies[REFRESH_COOKIE_NAME] as string | undefined, + } +} + +export const setAuthCookies = ( + idToken?: string, + refreshToken?: string, + response?: ServerResponse +) => { + // these tokens last an hour + const idMaxAge = idToken != null ? 60 * 60 : 0 + const idCookie = setCookie(ID_COOKIE_NAME, idToken ?? '', [ + ['path', '/'], + ['max-age', idMaxAge.toString()], + ['samesite', 'lax'], + ['secure'], + ]) + // these tokens don't expire + const refreshMaxAge = refreshToken != null ? 60 * 60 * 24 * 365 * 10 : 0 + const refreshCookie = setCookie(REFRESH_COOKIE_NAME, refreshToken ?? '', [ + ['path', '/'], + ['max-age', refreshMaxAge.toString()], + ['samesite', 'lax'], + ['secure'], + ]) + if (response != null) { + response.setHeader('Set-Cookie', [idCookie, refreshCookie]) + } else { + document.cookie = idCookie + document.cookie = refreshCookie + } +} + +export const deleteAuthCookies = () => setAuthCookies() diff --git a/web/lib/firebase/server-auth.ts b/web/lib/firebase/server-auth.ts new file mode 100644 index 00000000..5f828683 --- /dev/null +++ b/web/lib/firebase/server-auth.ts @@ -0,0 +1,86 @@ +import * as admin from 'firebase-admin' +import fetch from 'node-fetch' +import { IncomingMessage, ServerResponse } from 'http' +import { FIREBASE_CONFIG, PROJECT_ID } from 'common/envs/constants' +import { getAuthCookies, setAuthCookies } from './auth' +import { GetServerSideProps, GetServerSidePropsContext } from 'next' + +const ensureApp = async () => { + // Note: firebase-admin can only be imported from a server context, + // because it relies on Node standard library dependencies. + if (admin.apps.length === 0) { + // never initialize twice + return admin.initializeApp({ projectId: PROJECT_ID }) + } + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return admin.apps[0]! +} + +const requestFirebaseIdToken = async (refreshToken: string) => { + // See https://firebase.google.com/docs/reference/rest/auth/#section-refresh-token + const refreshUrl = new URL('https://securetoken.googleapis.com/v1/token') + refreshUrl.searchParams.append('key', FIREBASE_CONFIG.apiKey) + const result = await fetch(refreshUrl.toString(), { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams({ + grant_type: 'refresh_token', + refresh_token: refreshToken, + }), + }) + if (!result.ok) { + throw new Error(`Could not refresh ID token: ${await result.text()}`) + } + return (await result.json()) as any +} + +type RequestContext = { + req: IncomingMessage + res: ServerResponse +} + +export const getServerAuthenticatedUid = async (ctx: RequestContext) => { + const app = await ensureApp() + const auth = app.auth() + const { idToken, refreshToken } = getAuthCookies(ctx.req) + + // If we have a valid ID token, verify the user immediately with no network trips. + // If the ID token doesn't verify, we'll have to refresh it to see who they are. + // If they don't have any tokens, then we have no idea who they are. + if (idToken != null) { + try { + return (await auth.verifyIdToken(idToken))?.uid + } catch (e) { + if (refreshToken != null) { + const resp = await requestFirebaseIdToken(refreshToken) + setAuthCookies(resp.id_token, resp.refresh_token, ctx.res) + return (await auth.verifyIdToken(resp.id_token))?.uid + } + } + } + return undefined +} + +export const redirectIfLoggedIn = (dest: string, fn?: GetServerSideProps) => { + return async (ctx: GetServerSidePropsContext) => { + const uid = await getServerAuthenticatedUid(ctx) + if (uid == null) { + return fn != null ? await fn(ctx) : { props: {} } + } else { + return { redirect: { destination: dest, permanent: false } } + } + } +} + +export const redirectIfLoggedOut = (dest: string, fn?: GetServerSideProps) => { + return async (ctx: GetServerSidePropsContext) => { + const uid = await getServerAuthenticatedUid(ctx) + if (uid == null) { + return { redirect: { destination: dest, permanent: false } } + } else { + return fn != null ? await fn(ctx) : { props: {} } + } + } +} diff --git a/web/lib/firebase/users.ts b/web/lib/firebase/users.ts index f3242a7e..77c5c48d 100644 --- a/web/lib/firebase/users.ts +++ b/web/lib/firebase/users.ts @@ -16,7 +16,7 @@ import { import { getAuth } from 'firebase/auth' import { ref, getStorage, uploadBytes, getDownloadURL } from 'firebase/storage' import { - onAuthStateChanged, + onIdTokenChanged, GoogleAuthProvider, signInWithPopup, } from 'firebase/auth' @@ -43,6 +43,7 @@ import utc from 'dayjs/plugin/utc' dayjs.extend(utc) import { track } from '@amplitude/analytics-browser' +import { deleteAuthCookies, setAuthCookies } from './auth' export const users = coll<User>('users') export const privateUsers = coll<PrivateUser>('private-users') @@ -188,10 +189,9 @@ export function listenForLogin(onUser: (user: User | null) => void) { const cachedUser = local?.getItem(CACHED_USER_KEY) onUser(cachedUser && JSON.parse(cachedUser)) - return onAuthStateChanged(auth, async (fbUser) => { + return onIdTokenChanged(auth, async (fbUser) => { if (fbUser) { let user: User | null = await getUser(fbUser.uid) - if (!user) { if (createUserPromise == null) { const local = safeLocalStorage() @@ -204,17 +204,19 @@ export function listenForLogin(onUser: (user: User | null) => void) { } user = await createUserPromise } - onUser(user) // Persist to local storage, to reduce login blink next time. // Note: Cap on localStorage size is ~5mb local?.setItem(CACHED_USER_KEY, JSON.stringify(user)) setCachedReferralInfoForUser(user) + setAuthCookies(await fbUser.getIdToken(), fbUser.refreshToken) } else { // User logged out; reset to null onUser(null) + createUserPromise = undefined local?.removeItem(CACHED_USER_KEY) + deleteAuthCookies() } }) } diff --git a/web/lib/util/cookie.ts b/web/lib/util/cookie.ts new file mode 100644 index 00000000..14999fd4 --- /dev/null +++ b/web/lib/util/cookie.ts @@ -0,0 +1,33 @@ +type CookieOptions = string[][] + +const encodeCookie = (name: string, val: string) => { + return `${name}=${encodeURIComponent(val)}` +} + +const decodeCookie = (cookie: string) => { + const parts = cookie.trim().split('=') + if (parts.length < 2) { + throw new Error(`Invalid cookie contents: ${cookie}`) + } + const rest = parts.slice(1).join('') // there may be more = in the value + return [parts[0], decodeURIComponent(rest)] as const +} + +export const setCookie = (name: string, val: string, opts?: CookieOptions) => { + const parts = [encodeCookie(name, val)] + if (opts != null) { + parts.push(...opts.map((opt) => opt.join('='))) + } + return parts.join('; ') +} + +// Note that this intentionally ignores the case where multiple cookies have +// the same name but different paths. Hopefully we never need to think about it. +export const getCookies = (cookies: string) => { + const data = cookies.trim() + if (!data) { + return {} + } else { + return Object.fromEntries(data.split(';').map(decodeCookie)) + } +} diff --git a/web/pages/add-funds.tsx b/web/pages/add-funds.tsx index f680d47b..ed25a21a 100644 --- a/web/pages/add-funds.tsx +++ b/web/pages/add-funds.tsx @@ -8,6 +8,9 @@ import { checkoutURL } from 'web/lib/service/stripe' import { Page } from 'web/components/page' import { useTracking } from 'web/hooks/use-tracking' import { trackCallback } from 'web/lib/service/analytics' +import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth' + +export const getServerSideProps = redirectIfLoggedOut('/') export default function AddFundsPage() { const user = useUser() diff --git a/web/pages/admin.tsx b/web/pages/admin.tsx index e709e875..81f23ba9 100644 --- a/web/pages/admin.tsx +++ b/web/pages/admin.tsx @@ -9,6 +9,9 @@ import { useContracts } from 'web/hooks/use-contracts' import { mapKeys } from 'lodash' import { useAdmin } from 'web/hooks/use-admin' import { contractPath } from 'web/lib/firebase/contracts' +import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth' + +export const getServerSideProps = redirectIfLoggedOut('/') function avatarHtml(avatarUrl: string) { return `<img diff --git a/web/pages/charity/index.tsx b/web/pages/charity/index.tsx index 92e6b69f..b1cfc353 100644 --- a/web/pages/charity/index.tsx +++ b/web/pages/charity/index.tsx @@ -13,7 +13,6 @@ import { CharityCard } from 'web/components/charity/charity-card' import { Col } from 'web/components/layout/col' import { Spacer } from 'web/components/layout/spacer' import { Page } from 'web/components/page' -import { SiteLink } from 'web/components/site-link' import { Title } from 'web/components/title' import { getAllCharityTxns } from 'web/lib/firebase/txns' import { manaToUSD } from 'common/util/format' @@ -21,6 +20,9 @@ import { quadraticMatches } from 'common/quadratic-funding' import { Txn } from 'common/txn' import { useTracking } from 'web/hooks/use-tracking' import { searchInAny } from 'common/util/parse' +import { getUser } from 'web/lib/firebase/users' +import { SiteLink } from 'web/components/site-link' +import { User } from 'common/user' export async function getStaticProps() { const txns = await getAllCharityTxns() @@ -34,6 +36,7 @@ export async function getStaticProps() { ]) const matches = quadraticMatches(txns, totalRaised) const numDonors = uniqBy(txns, (txn) => txn.fromId).length + const mostRecentDonor = await getUser(txns[txns.length - 1].fromId) return { props: { @@ -42,6 +45,7 @@ export async function getStaticProps() { matches, txns, numDonors, + mostRecentDonor, }, revalidate: 60, } @@ -50,22 +54,28 @@ export async function getStaticProps() { type Stat = { name: string stat: string + url?: string } function DonatedStats(props: { stats: Stat[] }) { const { stats } = props return ( <dl className="mt-3 grid grid-cols-1 gap-5 rounded-lg bg-gradient-to-r from-pink-300 via-purple-300 to-indigo-400 p-4 sm:grid-cols-3"> - {stats.map((item) => ( + {stats.map((stat) => ( <div - key={item.name} + key={stat.name} className="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6" > <dt className="truncate text-sm font-medium text-gray-500"> - {item.name} + {stat.name} </dt> + <dd className="mt-1 text-3xl font-semibold text-gray-900"> - {item.stat} + {stat.url ? ( + <SiteLink href={stat.url}>{stat.stat}</SiteLink> + ) : ( + <span>{stat.stat}</span> + )} </dd> </div> ))} @@ -79,8 +89,9 @@ export default function Charity(props: { matches: { [charityId: string]: number } txns: Txn[] numDonors: number + mostRecentDonor: User }) { - const { totalRaised, charities, matches, numDonors } = props + const { totalRaised, charities, matches, numDonors, mostRecentDonor } = props const [query, setQuery] = useState('') const debouncedQuery = debounce(setQuery, 50) @@ -106,7 +117,7 @@ export default function Charity(props: { <Col className="w-full rounded px-4 py-6 sm:px-8 xl:w-[125%]"> <Col className=""> <Title className="!mt-0" text="Manifold for Charity" /> - <span className="text-gray-600"> + {/* <span className="text-gray-600"> Through July 15, up to $25k of donations will be matched via{' '} <SiteLink href="https://wtfisqf.com/" className="font-bold"> quadratic funding @@ -116,7 +127,7 @@ export default function Charity(props: { the FTX Future Fund </SiteLink> ! - </span> + </span> */} <DonatedStats stats={[ { @@ -128,8 +139,9 @@ export default function Charity(props: { stat: `${numDonors}`, }, { - name: 'Matched via quadratic funding', - stat: manaToUSD(sum(Object.values(matches))), + name: 'Most recent donor', + stat: mostRecentDonor.name ?? 'Nobody', + url: `/${mostRecentDonor.username}`, }, ]} /> diff --git a/web/pages/create.tsx b/web/pages/create.tsx index a3801223..45eb120f 100644 --- a/web/pages/create.tsx +++ b/web/pages/create.tsx @@ -28,6 +28,9 @@ import { GroupSelector } from 'web/components/groups/group-selector' import { User } from 'common/user' import { TextEditor, useTextEditor } from 'web/components/editor' import { Checkbox } from 'web/components/checkbox' +import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth' + +export const getServerSideProps = redirectIfLoggedOut('/') type NewQuestionParams = { groupId?: string @@ -55,10 +58,6 @@ export default function Create() { }, [params.q]) const creator = useUser() - useEffect(() => { - if (creator === null) router.push('/') - }, [creator, router]) - if (!router.isReady || !creator) return <div /> return ( @@ -93,7 +92,7 @@ export default function Create() { // Allow user to create a new contract export function NewContract(props: { - creator: User + creator?: User | null question: string params?: NewQuestionParams }) { diff --git a/web/pages/home.tsx b/web/pages/home.tsx index 98d5036e..6aa99a07 100644 --- a/web/pages/home.tsx +++ b/web/pages/home.tsx @@ -1,10 +1,9 @@ import React, { useEffect, useState } from 'react' -import Router, { useRouter } from 'next/router' +import { useRouter } from 'next/router' import { PlusSmIcon } from '@heroicons/react/solid' import { Page } from 'web/components/page' import { Col } from 'web/components/layout/col' -import { useUser } from 'web/hooks/use-user' import { getSavedSort } from 'web/hooks/use-sort-and-query-params' import { ContractSearch } from 'web/components/contract-search' import { Contract } from 'common/contract' @@ -12,19 +11,16 @@ import { ContractPageContent } from './[username]/[contractSlug]' import { getContractFromSlug } from 'web/lib/firebase/contracts' import { useTracking } from 'web/hooks/use-tracking' import { track } from 'web/lib/service/analytics' +import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth' + +export const getServerSideProps = redirectIfLoggedOut('/') const Home = () => { - const user = useUser() const [contract, setContract] = useContractPage() const router = useRouter() useTracking('view home') - if (user === null) { - Router.replace('/') - return <></> - } - return ( <> <Page suspend={!!contract}> diff --git a/web/pages/index.tsx b/web/pages/index.tsx index 904fc014..44683a4f 100644 --- a/web/pages/index.tsx +++ b/web/pages/index.tsx @@ -1,14 +1,13 @@ import React from 'react' -import Router from 'next/router' import { Contract, getContractsBySlugs } from 'web/lib/firebase/contracts' import { Page } from 'web/components/page' import { LandingPagePanel } from 'web/components/landing-page-panel' import { Col } from 'web/components/layout/col' -import { useUser } from 'web/hooks/use-user' import { ManifoldLogo } from 'web/components/nav/manifold-logo' +import { redirectIfLoggedIn } from 'web/lib/firebase/server-auth' -export async function getStaticProps() { +export const getServerSideProps = redirectIfLoggedIn('/home', async (_) => { // These hardcoded markets will be shown in the frontpage for signed-out users: const hotContracts = await getContractsBySlugs([ 'will-max-go-to-prom-with-a-girl', @@ -22,23 +21,11 @@ export async function getStaticProps() { 'will-congress-hold-any-hearings-abo-e21f987033b3', 'will-at-least-10-world-cities-have', ]) + return { props: { hotContracts } } +}) - return { - props: { hotContracts }, - revalidate: 60, // regenerate after a minute - } -} - -const Home = (props: { hotContracts: Contract[] }) => { +export default function Home(props: { hotContracts: Contract[] }) { const { hotContracts } = props - - const user = useUser() - - if (user) { - Router.replace('/home') - return <></> - } - return ( <Page> <div className="px-4 pt-2 md:mt-0 lg:hidden"> @@ -58,5 +45,3 @@ const Home = (props: { hotContracts: Contract[] }) => { </Page> ) } - -export default Home diff --git a/web/pages/links.tsx b/web/pages/links.tsx index eb029b6a..09c19bb5 100644 --- a/web/pages/links.tsx +++ b/web/pages/links.tsx @@ -18,6 +18,7 @@ import { Avatar } from 'web/components/avatar' import { RelativeTimestamp } from 'web/components/relative-timestamp' import { UserLink } from 'web/components/user-page' import { CreateLinksButton } from 'web/components/manalinks/create-links-button' +import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth' import dayjs from 'dayjs' import customParseFormat from 'dayjs/plugin/customParseFormat' @@ -26,6 +27,7 @@ import { Pagination } from 'web/components/pagination' dayjs.extend(customParseFormat) const LINKS_PER_PAGE = 24 +export const getServerSideProps = redirectIfLoggedOut('/') export function getManalinkUrl(slug: string) { return `${location.protocol}//${location.host}/link/${slug}` diff --git a/web/pages/profile.tsx b/web/pages/profile.tsx index b80698ae..541f5de9 100644 --- a/web/pages/profile.tsx +++ b/web/pages/profile.tsx @@ -1,6 +1,5 @@ import React, { useEffect, useState } from 'react' import { RefreshIcon } from '@heroicons/react/outline' -import Router from 'next/router' import { AddFundsButton } from 'web/components/add-funds-button' import { Page } from 'web/components/page' @@ -18,6 +17,9 @@ import { 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('/') function EditUserField(props: { user: User @@ -134,8 +136,7 @@ export default function ProfilePage() { }) } - if (user === null) { - Router.replace('/') + if (user == null) { return <></> } diff --git a/web/pages/trades.tsx b/web/pages/trades.tsx index 55a08bc6..a29fb7f0 100644 --- a/web/pages/trades.tsx +++ b/web/pages/trades.tsx @@ -1,17 +1,10 @@ import Router from 'next/router' -import { useEffect } from 'react' +import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth' -import { useUser } from 'web/hooks/use-user' +export const getServerSideProps = redirectIfLoggedOut('/') // Deprecated: redirects to /portfolio. // Eventually, this will be removed. export default function TradesPage() { - const user = useUser() - - useEffect(() => { - if (user === null) Router.replace('/') - else Router.replace('/portfolio') - }) - - return <></> + Router.replace('/portfolio') }