noobify welcome demo (#699)
* noobify welcome * also beginning to add greyscale color scheme
This commit is contained in:
parent
87f6949d80
commit
ae2e7dfe30
|
@ -40,6 +40,7 @@ export type User = {
|
||||||
referredByContractId?: string
|
referredByContractId?: string
|
||||||
referredByGroupId?: string
|
referredByGroupId?: string
|
||||||
lastPingTime?: number
|
lastPingTime?: number
|
||||||
|
shouldShowWelcome?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const STARTING_BALANCE = ENV_CONFIG.startingBalance ?? 1000
|
export const STARTING_BALANCE = ENV_CONFIG.startingBalance ?? 1000
|
||||||
|
|
|
@ -22,7 +22,7 @@ service cloud.firestore {
|
||||||
allow read;
|
allow read;
|
||||||
allow update: if resource.data.id == request.auth.uid
|
allow update: if resource.data.id == request.auth.uid
|
||||||
&& request.resource.data.diff(resource.data).affectedKeys()
|
&& request.resource.data.diff(resource.data).affectedKeys()
|
||||||
.hasOnly(['bio', 'bannerUrl', 'website', 'twitterHandle', 'discordHandle', 'followedCategories', 'lastPingTime']);
|
.hasOnly(['bio', 'bannerUrl', 'website', 'twitterHandle', 'discordHandle', 'followedCategories', 'lastPingTime','shouldShowWelcome']);
|
||||||
// User referral rules
|
// User referral rules
|
||||||
allow update: if resource.data.id == request.auth.uid
|
allow update: if resource.data.id == request.auth.uid
|
||||||
&& request.resource.data.diff(resource.data).affectedKeys()
|
&& request.resource.data.diff(resource.data).affectedKeys()
|
||||||
|
|
|
@ -77,6 +77,7 @@ export const createuser = newEndpoint(opts, async (req, auth) => {
|
||||||
creatorVolumeCached: { daily: 0, weekly: 0, monthly: 0, allTime: 0 },
|
creatorVolumeCached: { daily: 0, weekly: 0, monthly: 0, allTime: 0 },
|
||||||
followerCountCached: 0,
|
followerCountCached: 0,
|
||||||
followedCategories: DEFAULT_CATEGORIES,
|
followedCategories: DEFAULT_CATEGORIES,
|
||||||
|
shouldShowWelcome: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
await firestore.collection('users').doc(auth.uid).create(user)
|
await firestore.collection('users').doc(auth.uid).create(user)
|
||||||
|
|
|
@ -39,8 +39,10 @@ export function Button(props: {
|
||||||
color === 'yellow' && 'bg-yellow-400 text-white hover:bg-yellow-500',
|
color === 'yellow' && 'bg-yellow-400 text-white hover:bg-yellow-500',
|
||||||
color === 'blue' && 'bg-blue-400 text-white hover:bg-blue-500',
|
color === 'blue' && 'bg-blue-400 text-white hover:bg-blue-500',
|
||||||
color === 'indigo' && 'bg-indigo-500 text-white hover:bg-indigo-600',
|
color === 'indigo' && 'bg-indigo-500 text-white hover:bg-indigo-600',
|
||||||
color === 'gray' && 'bg-gray-100 text-gray-600 hover:bg-gray-200',
|
color === 'gray' &&
|
||||||
color === 'gray-white' && 'bg-white text-gray-500 hover:bg-gray-200',
|
'bg-greyscale-1 text-greyscale-7 hover:bg-greyscale-2',
|
||||||
|
color === 'gray-white' &&
|
||||||
|
'text-greyscale-6 hover:bg-greyscale-2 bg-white',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
|
|
@ -15,8 +15,8 @@ export function PillButton(props: {
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'cursor-pointer select-none whitespace-nowrap rounded-full',
|
'cursor-pointer select-none whitespace-nowrap rounded-full',
|
||||||
selected
|
selected
|
||||||
? ['text-white', color ?? 'bg-gray-700']
|
? ['text-white', color ?? 'bg-greyscale-6']
|
||||||
: 'bg-gray-100 hover:bg-gray-200',
|
: 'bg-greyscale-2 hover:bg-greyscale-3',
|
||||||
big ? 'px-8 py-2' : 'px-3 py-1.5 text-sm'
|
big ? 'px-8 py-2' : 'px-3 py-1.5 text-sm'
|
||||||
)}
|
)}
|
||||||
onClick={onSelect}
|
onClick={onSelect}
|
||||||
|
|
173
web/components/onboarding/welcome.tsx
Normal file
173
web/components/onboarding/welcome.tsx
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/solid'
|
||||||
|
import clsx from 'clsx'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useUser } from 'web/hooks/use-user'
|
||||||
|
import { updateUser } from 'web/lib/firebase/users'
|
||||||
|
import { Col } from '../layout/col'
|
||||||
|
import { Modal } from '../layout/modal'
|
||||||
|
import { Row } from '../layout/row'
|
||||||
|
import { Title } from '../title'
|
||||||
|
|
||||||
|
export default function Welcome() {
|
||||||
|
const user = useUser()
|
||||||
|
const [open, setOpen] = useState(true)
|
||||||
|
const [page, setPage] = useState(0)
|
||||||
|
const TOTAL_PAGES = 4
|
||||||
|
|
||||||
|
function increasePage() {
|
||||||
|
if (page < TOTAL_PAGES - 1) {
|
||||||
|
setPage(page + 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function decreasePage() {
|
||||||
|
if (page > 0) {
|
||||||
|
setPage(page - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setUserHasSeenWelcome() {
|
||||||
|
if (user) {
|
||||||
|
await updateUser(user.id, { ['shouldShowWelcome']: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!user || !user.shouldShowWelcome) {
|
||||||
|
return <></>
|
||||||
|
} else
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
setOpen={(newOpen) => {
|
||||||
|
setUserHasSeenWelcome()
|
||||||
|
setOpen(newOpen)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Col className="h-[32rem] place-content-between rounded-md bg-white px-8 py-6 text-sm font-light md:h-[40rem] md:text-lg">
|
||||||
|
{page === 0 && <Page0 />}
|
||||||
|
{page === 1 && <Page1 />}
|
||||||
|
{page === 2 && <Page2 />}
|
||||||
|
{page === 3 && <Page3 />}
|
||||||
|
<Col>
|
||||||
|
<Row className="place-content-between">
|
||||||
|
<ChevronLeftIcon
|
||||||
|
className={clsx(
|
||||||
|
'h-10 w-10 text-gray-400 hover:text-gray-500',
|
||||||
|
page === 0 ? 'disabled invisible' : ''
|
||||||
|
)}
|
||||||
|
onClick={decreasePage}
|
||||||
|
/>
|
||||||
|
<PageIndicator page={page} totalpages={TOTAL_PAGES} />
|
||||||
|
<ChevronRightIcon
|
||||||
|
className={clsx(
|
||||||
|
'h-10 w-10 text-indigo-500 hover:text-indigo-600',
|
||||||
|
page === TOTAL_PAGES - 1 ? 'disabled invisible' : ''
|
||||||
|
)}
|
||||||
|
onClick={increasePage}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
<u
|
||||||
|
className="self-center text-xs text-gray-500"
|
||||||
|
onClick={() => {
|
||||||
|
setOpen(false)
|
||||||
|
setUserHasSeenWelcome()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
I got the gist, exit welcome
|
||||||
|
</u>
|
||||||
|
</Col>
|
||||||
|
</Col>
|
||||||
|
</Modal>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function PageIndicator(props: { page: number; totalpages: number }) {
|
||||||
|
const { page, totalpages } = props
|
||||||
|
return (
|
||||||
|
<Row>
|
||||||
|
{[...Array(totalpages)].map((e, i) => (
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
'mx-1.5 my-auto h-1.5 w-1.5 rounded-full',
|
||||||
|
i === page ? 'bg-indigo-500' : 'bg-gray-300'
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</Row>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Page0() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<img
|
||||||
|
className="h-2/3 w-2/3 place-self-center object-contain"
|
||||||
|
src="/welcome/manipurple.png"
|
||||||
|
/>
|
||||||
|
<Title className="text-center" text="Welcome to Manifold Markets!" />
|
||||||
|
<p>
|
||||||
|
Manifold Markets is a place where anyone can ask a question about the
|
||||||
|
future.
|
||||||
|
</p>
|
||||||
|
<div className="mt-4">For example,</div>
|
||||||
|
<div className="mt-2 font-normal text-indigo-700">
|
||||||
|
“Will Michelle Obama be the next president of the United States?”
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Page1() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
Your question becomes a prediction market that people can bet{' '}
|
||||||
|
<span className="font-normal text-indigo-700">mana (M$)</span> on.
|
||||||
|
</p>
|
||||||
|
<div className="mt-8 font-semibold">The core idea</div>
|
||||||
|
<div className="mt-2">
|
||||||
|
If people have to put their mana where their mouth is, you’ll get a
|
||||||
|
pretty accurate answer!
|
||||||
|
</div>
|
||||||
|
<video loop autoPlay className="my-4 h-full w-full">
|
||||||
|
<source src="/welcome/mana-example.mp4" type="video/mp4" />
|
||||||
|
Your browser does not support video
|
||||||
|
</video>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Page2() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p>
|
||||||
|
<span className="mt-4 font-normal text-indigo-700">Mana (M$)</span> is
|
||||||
|
the play money you bet with. You can also turn it into a real donation
|
||||||
|
to charity, at a 100:1 ratio.
|
||||||
|
</p>
|
||||||
|
<div className="mt-8 font-semibold">Example</div>
|
||||||
|
<p className="mt-2">
|
||||||
|
When you donate <span className="font-semibold">M$1000</span> to
|
||||||
|
Givewell, Manifold sends them{' '}
|
||||||
|
<span className="font-semibold">$10 USD</span>.
|
||||||
|
</p>
|
||||||
|
<video loop autoPlay className="my-4 h-full w-full">
|
||||||
|
<source src="/welcome/charity.mp4" type="video/mp4" />
|
||||||
|
Your browser does not support video
|
||||||
|
</video>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function Page3() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<img className="mx-auto object-contain" src="/welcome/treasure.png" />
|
||||||
|
<Title className="mx-auto" text="Let's start predicting!" />
|
||||||
|
<p className="mb-8">
|
||||||
|
As a thank you for signing up, we’ve sent you{' '}
|
||||||
|
<span className="font-normal text-indigo-700">M$1000 Mana</span>{' '}
|
||||||
|
</p>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ export function Title(props: { text: string; className?: string }) {
|
||||||
return (
|
return (
|
||||||
<h1
|
<h1
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'my-4 inline-block text-2xl text-indigo-700 sm:my-6 sm:text-3xl',
|
'my-4 inline-block text-2xl font-normal text-indigo-700 sm:my-6 sm:text-3xl',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
|
|
|
@ -6,6 +6,7 @@ import Script from 'next/script'
|
||||||
import { usePreserveScroll } from 'web/hooks/use-preserve-scroll'
|
import { usePreserveScroll } from 'web/hooks/use-preserve-scroll'
|
||||||
import { QueryClient, QueryClientProvider } from 'react-query'
|
import { QueryClient, QueryClientProvider } from 'react-query'
|
||||||
import { AuthProvider } from 'web/components/auth-context'
|
import { AuthProvider } from 'web/components/auth-context'
|
||||||
|
import Welcome from 'web/components/onboarding/welcome'
|
||||||
|
|
||||||
function firstLine(msg: string) {
|
function firstLine(msg: string) {
|
||||||
return msg.replace(/\r?\n.*/s, '')
|
return msg.replace(/\r?\n.*/s, '')
|
||||||
|
@ -78,9 +79,9 @@ function MyApp({ Component, pageProps }: AppProps) {
|
||||||
content="width=device-width, initial-scale=1, maximum-scale=1"
|
content="width=device-width, initial-scale=1, maximum-scale=1"
|
||||||
/>
|
/>
|
||||||
</Head>
|
</Head>
|
||||||
|
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<Welcome {...pageProps} />
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
|
|
@ -242,6 +242,7 @@ export default function GroupPage(props: {
|
||||||
href: groupPath(group.slug, 'about'),
|
href: groupPath(group.slug, 'about'),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const tabIndex = tabs.map((t) => t.title).indexOf(page ?? GROUP_CHAT_SLUG)
|
const tabIndex = tabs.map((t) => t.title).indexOf(page ?? GROUP_CHAT_SLUG)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
BIN
web/public/welcome/charity.mp4
Executable file
BIN
web/public/welcome/charity.mp4
Executable file
Binary file not shown.
BIN
web/public/welcome/mana-example.mp4
Executable file
BIN
web/public/welcome/mana-example.mp4
Executable file
Binary file not shown.
BIN
web/public/welcome/manipurple.png
Normal file
BIN
web/public/welcome/manipurple.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
BIN
web/public/welcome/treasure.png
Normal file
BIN
web/public/welcome/treasure.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 41 KiB |
|
@ -18,6 +18,15 @@ module.exports = {
|
||||||
backgroundImage: {
|
backgroundImage: {
|
||||||
'world-trading': "url('/world-trading-background.webp')",
|
'world-trading': "url('/world-trading-background.webp')",
|
||||||
},
|
},
|
||||||
|
colors: {
|
||||||
|
'greyscale-1': '#FBFBFF',
|
||||||
|
'greyscale-2': '#E7E7F4',
|
||||||
|
'greyscale-3': '#D8D8EB',
|
||||||
|
'greyscale-4': '#B1B1C7',
|
||||||
|
'greyscale-5': '#9191A7',
|
||||||
|
'greyscale-6': '#66667C',
|
||||||
|
'greyscale-7': '#111140',
|
||||||
|
},
|
||||||
typography: {
|
typography: {
|
||||||
quoteless: {
|
quoteless: {
|
||||||
css: {
|
css: {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user