Merge branch 'main' into atlas3
This commit is contained in:
commit
c2dc4a3e4f
|
@ -6,10 +6,9 @@ import { Charity } from 'common/charity'
|
||||||
import { useCharityTxns } from 'web/hooks/use-charity-txns'
|
import { useCharityTxns } from 'web/hooks/use-charity-txns'
|
||||||
import { manaToUSD } from '../../../common/util/format'
|
import { manaToUSD } from '../../../common/util/format'
|
||||||
import { Row } from '../layout/row'
|
import { Row } from '../layout/row'
|
||||||
import { Col } from '../layout/col'
|
|
||||||
|
|
||||||
export function CharityCard(props: { charity: Charity; match?: number }) {
|
export function CharityCard(props: { charity: Charity; match?: number }) {
|
||||||
const { charity, match } = props
|
const { charity } = props
|
||||||
const { slug, photo, preview, id, tags } = charity
|
const { slug, photo, preview, id, tags } = charity
|
||||||
|
|
||||||
const txns = useCharityTxns(id)
|
const txns = useCharityTxns(id)
|
||||||
|
@ -36,18 +35,18 @@ export function CharityCard(props: { charity: Charity; match?: number }) {
|
||||||
{raised > 0 && (
|
{raised > 0 && (
|
||||||
<>
|
<>
|
||||||
<Row className="mt-4 flex-1 items-end justify-center gap-6 text-gray-900">
|
<Row className="mt-4 flex-1 items-end justify-center gap-6 text-gray-900">
|
||||||
<Col>
|
<Row className="items-baseline gap-1">
|
||||||
<span className="text-3xl font-semibold">
|
<span className="text-3xl font-semibold">
|
||||||
{formatUsd(raised)}
|
{formatUsd(raised)}
|
||||||
</span>
|
</span>
|
||||||
<span>raised</span>
|
raised
|
||||||
</Col>
|
</Row>
|
||||||
{match && (
|
{/* {match && (
|
||||||
<Col className="text-gray-500">
|
<Col className="text-gray-500">
|
||||||
<span className="text-xl">+{formatUsd(match)}</span>
|
<span className="text-xl">+{formatUsd(match)}</span>
|
||||||
<span className="">match</span>
|
<span className="">match</span>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)} */}
|
||||||
</Row>
|
</Row>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
54
web/lib/firebase/auth.ts
Normal file
54
web/lib/firebase/auth.ts
Normal file
|
@ -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()
|
86
web/lib/firebase/server-auth.ts
Normal file
86
web/lib/firebase/server-auth.ts
Normal file
|
@ -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: {} }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ import {
|
||||||
import { getAuth } from 'firebase/auth'
|
import { getAuth } from 'firebase/auth'
|
||||||
import { ref, getStorage, uploadBytes, getDownloadURL } from 'firebase/storage'
|
import { ref, getStorage, uploadBytes, getDownloadURL } from 'firebase/storage'
|
||||||
import {
|
import {
|
||||||
onAuthStateChanged,
|
onIdTokenChanged,
|
||||||
GoogleAuthProvider,
|
GoogleAuthProvider,
|
||||||
signInWithPopup,
|
signInWithPopup,
|
||||||
} from 'firebase/auth'
|
} from 'firebase/auth'
|
||||||
|
@ -43,6 +43,7 @@ import utc from 'dayjs/plugin/utc'
|
||||||
dayjs.extend(utc)
|
dayjs.extend(utc)
|
||||||
|
|
||||||
import { track } from '@amplitude/analytics-browser'
|
import { track } from '@amplitude/analytics-browser'
|
||||||
|
import { deleteAuthCookies, setAuthCookies } from './auth'
|
||||||
|
|
||||||
export const users = coll<User>('users')
|
export const users = coll<User>('users')
|
||||||
export const privateUsers = coll<PrivateUser>('private-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)
|
const cachedUser = local?.getItem(CACHED_USER_KEY)
|
||||||
onUser(cachedUser && JSON.parse(cachedUser))
|
onUser(cachedUser && JSON.parse(cachedUser))
|
||||||
|
|
||||||
return onAuthStateChanged(auth, async (fbUser) => {
|
return onIdTokenChanged(auth, async (fbUser) => {
|
||||||
if (fbUser) {
|
if (fbUser) {
|
||||||
let user: User | null = await getUser(fbUser.uid)
|
let user: User | null = await getUser(fbUser.uid)
|
||||||
|
|
||||||
if (!user) {
|
if (!user) {
|
||||||
if (createUserPromise == null) {
|
if (createUserPromise == null) {
|
||||||
const local = safeLocalStorage()
|
const local = safeLocalStorage()
|
||||||
|
@ -204,17 +204,19 @@ export function listenForLogin(onUser: (user: User | null) => void) {
|
||||||
}
|
}
|
||||||
user = await createUserPromise
|
user = await createUserPromise
|
||||||
}
|
}
|
||||||
|
|
||||||
onUser(user)
|
onUser(user)
|
||||||
|
|
||||||
// Persist to local storage, to reduce login blink next time.
|
// Persist to local storage, to reduce login blink next time.
|
||||||
// Note: Cap on localStorage size is ~5mb
|
// Note: Cap on localStorage size is ~5mb
|
||||||
local?.setItem(CACHED_USER_KEY, JSON.stringify(user))
|
local?.setItem(CACHED_USER_KEY, JSON.stringify(user))
|
||||||
setCachedReferralInfoForUser(user)
|
setCachedReferralInfoForUser(user)
|
||||||
|
setAuthCookies(await fbUser.getIdToken(), fbUser.refreshToken)
|
||||||
} else {
|
} else {
|
||||||
// User logged out; reset to null
|
// User logged out; reset to null
|
||||||
onUser(null)
|
onUser(null)
|
||||||
|
createUserPromise = undefined
|
||||||
local?.removeItem(CACHED_USER_KEY)
|
local?.removeItem(CACHED_USER_KEY)
|
||||||
|
deleteAuthCookies()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
33
web/lib/util/cookie.ts
Normal file
33
web/lib/util/cookie.ts
Normal file
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,9 @@ import { checkoutURL } from 'web/lib/service/stripe'
|
||||||
import { Page } from 'web/components/page'
|
import { Page } from 'web/components/page'
|
||||||
import { useTracking } from 'web/hooks/use-tracking'
|
import { useTracking } from 'web/hooks/use-tracking'
|
||||||
import { trackCallback } from 'web/lib/service/analytics'
|
import { trackCallback } from 'web/lib/service/analytics'
|
||||||
|
import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth'
|
||||||
|
|
||||||
|
export const getServerSideProps = redirectIfLoggedOut('/')
|
||||||
|
|
||||||
export default function AddFundsPage() {
|
export default function AddFundsPage() {
|
||||||
const user = useUser()
|
const user = useUser()
|
||||||
|
|
|
@ -9,6 +9,9 @@ import { useContracts } from 'web/hooks/use-contracts'
|
||||||
import { mapKeys } from 'lodash'
|
import { mapKeys } from 'lodash'
|
||||||
import { useAdmin } from 'web/hooks/use-admin'
|
import { useAdmin } from 'web/hooks/use-admin'
|
||||||
import { contractPath } from 'web/lib/firebase/contracts'
|
import { contractPath } from 'web/lib/firebase/contracts'
|
||||||
|
import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth'
|
||||||
|
|
||||||
|
export const getServerSideProps = redirectIfLoggedOut('/')
|
||||||
|
|
||||||
function avatarHtml(avatarUrl: string) {
|
function avatarHtml(avatarUrl: string) {
|
||||||
return `<img
|
return `<img
|
||||||
|
|
|
@ -13,7 +13,6 @@ import { CharityCard } from 'web/components/charity/charity-card'
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
import { Spacer } from 'web/components/layout/spacer'
|
import { Spacer } from 'web/components/layout/spacer'
|
||||||
import { Page } from 'web/components/page'
|
import { Page } from 'web/components/page'
|
||||||
import { SiteLink } from 'web/components/site-link'
|
|
||||||
import { Title } from 'web/components/title'
|
import { Title } from 'web/components/title'
|
||||||
import { getAllCharityTxns } from 'web/lib/firebase/txns'
|
import { getAllCharityTxns } from 'web/lib/firebase/txns'
|
||||||
import { manaToUSD } from 'common/util/format'
|
import { manaToUSD } from 'common/util/format'
|
||||||
|
@ -21,6 +20,9 @@ import { quadraticMatches } from 'common/quadratic-funding'
|
||||||
import { Txn } from 'common/txn'
|
import { Txn } from 'common/txn'
|
||||||
import { useTracking } from 'web/hooks/use-tracking'
|
import { useTracking } from 'web/hooks/use-tracking'
|
||||||
import { searchInAny } from 'common/util/parse'
|
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() {
|
export async function getStaticProps() {
|
||||||
const txns = await getAllCharityTxns()
|
const txns = await getAllCharityTxns()
|
||||||
|
@ -34,6 +36,7 @@ export async function getStaticProps() {
|
||||||
])
|
])
|
||||||
const matches = quadraticMatches(txns, totalRaised)
|
const matches = quadraticMatches(txns, totalRaised)
|
||||||
const numDonors = uniqBy(txns, (txn) => txn.fromId).length
|
const numDonors = uniqBy(txns, (txn) => txn.fromId).length
|
||||||
|
const mostRecentDonor = await getUser(txns[txns.length - 1].fromId)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
|
@ -42,6 +45,7 @@ export async function getStaticProps() {
|
||||||
matches,
|
matches,
|
||||||
txns,
|
txns,
|
||||||
numDonors,
|
numDonors,
|
||||||
|
mostRecentDonor,
|
||||||
},
|
},
|
||||||
revalidate: 60,
|
revalidate: 60,
|
||||||
}
|
}
|
||||||
|
@ -50,22 +54,28 @@ export async function getStaticProps() {
|
||||||
type Stat = {
|
type Stat = {
|
||||||
name: string
|
name: string
|
||||||
stat: string
|
stat: string
|
||||||
|
url?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function DonatedStats(props: { stats: Stat[] }) {
|
function DonatedStats(props: { stats: Stat[] }) {
|
||||||
const { stats } = props
|
const { stats } = props
|
||||||
return (
|
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">
|
<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
|
<div
|
||||||
key={item.name}
|
key={stat.name}
|
||||||
className="overflow-hidden rounded-lg bg-white px-4 py-5 shadow sm:p-6"
|
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">
|
<dt className="truncate text-sm font-medium text-gray-500">
|
||||||
{item.name}
|
{stat.name}
|
||||||
</dt>
|
</dt>
|
||||||
|
|
||||||
<dd className="mt-1 text-3xl font-semibold text-gray-900">
|
<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>
|
</dd>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
@ -79,8 +89,9 @@ export default function Charity(props: {
|
||||||
matches: { [charityId: string]: number }
|
matches: { [charityId: string]: number }
|
||||||
txns: Txn[]
|
txns: Txn[]
|
||||||
numDonors: number
|
numDonors: number
|
||||||
|
mostRecentDonor: User
|
||||||
}) {
|
}) {
|
||||||
const { totalRaised, charities, matches, numDonors } = props
|
const { totalRaised, charities, matches, numDonors, mostRecentDonor } = props
|
||||||
|
|
||||||
const [query, setQuery] = useState('')
|
const [query, setQuery] = useState('')
|
||||||
const debouncedQuery = debounce(setQuery, 50)
|
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="w-full rounded px-4 py-6 sm:px-8 xl:w-[125%]">
|
||||||
<Col className="">
|
<Col className="">
|
||||||
<Title className="!mt-0" text="Manifold for Charity" />
|
<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{' '}
|
Through July 15, up to $25k of donations will be matched via{' '}
|
||||||
<SiteLink href="https://wtfisqf.com/" className="font-bold">
|
<SiteLink href="https://wtfisqf.com/" className="font-bold">
|
||||||
quadratic funding
|
quadratic funding
|
||||||
|
@ -116,7 +127,7 @@ export default function Charity(props: {
|
||||||
the FTX Future Fund
|
the FTX Future Fund
|
||||||
</SiteLink>
|
</SiteLink>
|
||||||
!
|
!
|
||||||
</span>
|
</span> */}
|
||||||
<DonatedStats
|
<DonatedStats
|
||||||
stats={[
|
stats={[
|
||||||
{
|
{
|
||||||
|
@ -128,8 +139,9 @@ export default function Charity(props: {
|
||||||
stat: `${numDonors}`,
|
stat: `${numDonors}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Matched via quadratic funding',
|
name: 'Most recent donor',
|
||||||
stat: manaToUSD(sum(Object.values(matches))),
|
stat: mostRecentDonor.name ?? 'Nobody',
|
||||||
|
url: `/${mostRecentDonor.username}`,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -28,6 +28,9 @@ import { GroupSelector } from 'web/components/groups/group-selector'
|
||||||
import { User } from 'common/user'
|
import { User } from 'common/user'
|
||||||
import { TextEditor, useTextEditor } from 'web/components/editor'
|
import { TextEditor, useTextEditor } from 'web/components/editor'
|
||||||
import { Checkbox } from 'web/components/checkbox'
|
import { Checkbox } from 'web/components/checkbox'
|
||||||
|
import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth'
|
||||||
|
|
||||||
|
export const getServerSideProps = redirectIfLoggedOut('/')
|
||||||
|
|
||||||
type NewQuestionParams = {
|
type NewQuestionParams = {
|
||||||
groupId?: string
|
groupId?: string
|
||||||
|
@ -55,10 +58,6 @@ export default function Create() {
|
||||||
}, [params.q])
|
}, [params.q])
|
||||||
|
|
||||||
const creator = useUser()
|
const creator = useUser()
|
||||||
useEffect(() => {
|
|
||||||
if (creator === null) router.push('/')
|
|
||||||
}, [creator, router])
|
|
||||||
|
|
||||||
if (!router.isReady || !creator) return <div />
|
if (!router.isReady || !creator) return <div />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -93,7 +92,7 @@ export default function Create() {
|
||||||
|
|
||||||
// Allow user to create a new contract
|
// Allow user to create a new contract
|
||||||
export function NewContract(props: {
|
export function NewContract(props: {
|
||||||
creator: User
|
creator?: User | null
|
||||||
question: string
|
question: string
|
||||||
params?: NewQuestionParams
|
params?: NewQuestionParams
|
||||||
}) {
|
}) {
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import Router, { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { PlusSmIcon } from '@heroicons/react/solid'
|
import { PlusSmIcon } from '@heroicons/react/solid'
|
||||||
|
|
||||||
import { Page } from 'web/components/page'
|
import { Page } from 'web/components/page'
|
||||||
import { Col } from 'web/components/layout/col'
|
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 { getSavedSort } from 'web/hooks/use-sort-and-query-params'
|
||||||
import { ContractSearch } from 'web/components/contract-search'
|
import { ContractSearch } from 'web/components/contract-search'
|
||||||
import { Contract } from 'common/contract'
|
import { Contract } from 'common/contract'
|
||||||
|
@ -12,19 +11,16 @@ import { ContractPageContent } from './[username]/[contractSlug]'
|
||||||
import { getContractFromSlug } from 'web/lib/firebase/contracts'
|
import { getContractFromSlug } from 'web/lib/firebase/contracts'
|
||||||
import { useTracking } from 'web/hooks/use-tracking'
|
import { useTracking } from 'web/hooks/use-tracking'
|
||||||
import { track } from 'web/lib/service/analytics'
|
import { track } from 'web/lib/service/analytics'
|
||||||
|
import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth'
|
||||||
|
|
||||||
|
export const getServerSideProps = redirectIfLoggedOut('/')
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
const user = useUser()
|
|
||||||
const [contract, setContract] = useContractPage()
|
const [contract, setContract] = useContractPage()
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
useTracking('view home')
|
useTracking('view home')
|
||||||
|
|
||||||
if (user === null) {
|
|
||||||
Router.replace('/')
|
|
||||||
return <></>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Page suspend={!!contract}>
|
<Page suspend={!!contract}>
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import Router from 'next/router'
|
|
||||||
|
|
||||||
import { Contract, getContractsBySlugs } from 'web/lib/firebase/contracts'
|
import { Contract, getContractsBySlugs } from 'web/lib/firebase/contracts'
|
||||||
import { Page } from 'web/components/page'
|
import { Page } from 'web/components/page'
|
||||||
import { LandingPagePanel } from 'web/components/landing-page-panel'
|
import { LandingPagePanel } from 'web/components/landing-page-panel'
|
||||||
import { Col } from 'web/components/layout/col'
|
import { Col } from 'web/components/layout/col'
|
||||||
import { useUser } from 'web/hooks/use-user'
|
|
||||||
import { ManifoldLogo } from 'web/components/nav/manifold-logo'
|
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:
|
// These hardcoded markets will be shown in the frontpage for signed-out users:
|
||||||
const hotContracts = await getContractsBySlugs([
|
const hotContracts = await getContractsBySlugs([
|
||||||
'will-max-go-to-prom-with-a-girl',
|
'will-max-go-to-prom-with-a-girl',
|
||||||
|
@ -22,23 +21,11 @@ export async function getStaticProps() {
|
||||||
'will-congress-hold-any-hearings-abo-e21f987033b3',
|
'will-congress-hold-any-hearings-abo-e21f987033b3',
|
||||||
'will-at-least-10-world-cities-have',
|
'will-at-least-10-world-cities-have',
|
||||||
])
|
])
|
||||||
|
return { props: { hotContracts } }
|
||||||
|
})
|
||||||
|
|
||||||
return {
|
export default function Home(props: { hotContracts: Contract[] }) {
|
||||||
props: { hotContracts },
|
|
||||||
revalidate: 60, // regenerate after a minute
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Home = (props: { hotContracts: Contract[] }) => {
|
|
||||||
const { hotContracts } = props
|
const { hotContracts } = props
|
||||||
|
|
||||||
const user = useUser()
|
|
||||||
|
|
||||||
if (user) {
|
|
||||||
Router.replace('/home')
|
|
||||||
return <></>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Page>
|
<Page>
|
||||||
<div className="px-4 pt-2 md:mt-0 lg:hidden">
|
<div className="px-4 pt-2 md:mt-0 lg:hidden">
|
||||||
|
@ -58,5 +45,3 @@ const Home = (props: { hotContracts: Contract[] }) => {
|
||||||
</Page>
|
</Page>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Home
|
|
||||||
|
|
|
@ -18,11 +18,14 @@ import { Avatar } from 'web/components/avatar'
|
||||||
import { RelativeTimestamp } from 'web/components/relative-timestamp'
|
import { RelativeTimestamp } from 'web/components/relative-timestamp'
|
||||||
import { UserLink } from 'web/components/user-page'
|
import { UserLink } from 'web/components/user-page'
|
||||||
import { CreateLinksButton } from 'web/components/manalinks/create-links-button'
|
import { CreateLinksButton } from 'web/components/manalinks/create-links-button'
|
||||||
|
import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth'
|
||||||
|
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import customParseFormat from 'dayjs/plugin/customParseFormat'
|
import customParseFormat from 'dayjs/plugin/customParseFormat'
|
||||||
dayjs.extend(customParseFormat)
|
dayjs.extend(customParseFormat)
|
||||||
|
|
||||||
|
export const getServerSideProps = redirectIfLoggedOut('/')
|
||||||
|
|
||||||
export function getManalinkUrl(slug: string) {
|
export function getManalinkUrl(slug: string) {
|
||||||
return `${location.protocol}//${location.host}/link/${slug}`
|
return `${location.protocol}//${location.host}/link/${slug}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { useEffect, useState } from 'react'
|
||||||
import { RefreshIcon } from '@heroicons/react/outline'
|
import { RefreshIcon } from '@heroicons/react/outline'
|
||||||
import Router from 'next/router'
|
|
||||||
|
|
||||||
import { AddFundsButton } from 'web/components/add-funds-button'
|
import { AddFundsButton } from 'web/components/add-funds-button'
|
||||||
import { Page } from 'web/components/page'
|
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 { defaultBannerUrl } from 'web/components/user-page'
|
||||||
import { SiteLink } from 'web/components/site-link'
|
import { SiteLink } from 'web/components/site-link'
|
||||||
import Textarea from 'react-expanding-textarea'
|
import Textarea from 'react-expanding-textarea'
|
||||||
|
import { redirectIfLoggedOut } from 'web/lib/firebase/server-auth'
|
||||||
|
|
||||||
|
export const getServerSideProps = redirectIfLoggedOut('/')
|
||||||
|
|
||||||
function EditUserField(props: {
|
function EditUserField(props: {
|
||||||
user: User
|
user: User
|
||||||
|
@ -134,8 +136,7 @@ export default function ProfilePage() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user === null) {
|
if (user == null) {
|
||||||
Router.replace('/')
|
|
||||||
return <></>
|
return <></>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,10 @@
|
||||||
import Router from 'next/router'
|
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.
|
// Deprecated: redirects to /portfolio.
|
||||||
// Eventually, this will be removed.
|
// Eventually, this will be removed.
|
||||||
export default function TradesPage() {
|
export default function TradesPage() {
|
||||||
const user = useUser()
|
Router.replace('/portfolio')
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (user === null) Router.replace('/')
|
|
||||||
else Router.replace('/portfolio')
|
|
||||||
})
|
|
||||||
|
|
||||||
return <></>
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user