diff --git a/common/util/parse.ts b/common/util/parse.ts index 79419ae1..5f78125e 100644 --- a/common/util/parse.ts +++ b/common/util/parse.ts @@ -8,3 +8,14 @@ export function parseTags(text: string) { tagSet.forEach((tag) => uniqueTags.push(tag)) return uniqueTags } + +export function parseWordsAsTags(text: string) { + const regex = /(?:^|\s)(?:[a-z0-9_]+)/gi + const matches = (text.match(regex) || []) + .map((match) => match.trim()) + .filter((tag) => tag) + const tagSet = new Set(matches) + const uniqueTags: string[] = [] + tagSet.forEach((tag) => uniqueTags.push(tag)) + return uniqueTags +} diff --git a/web/pages/fold/[foldSlug]/index.tsx b/web/pages/fold/[foldSlug]/index.tsx index b38cd2e8..07408fec 100644 --- a/web/pages/fold/[foldSlug]/index.tsx +++ b/web/pages/fold/[foldSlug]/index.tsx @@ -6,7 +6,11 @@ import { Title } from '../../../components/title' import { Bet, listAllBets } from '../../../lib/firebase/bets' import { listAllComments } from '../../../lib/firebase/comments' import { Contract } from '../../../lib/firebase/contracts' -import { getFoldBySlug, getFoldContracts } from '../../../lib/firebase/folds' +import { + foldPath, + getFoldBySlug, + getFoldContracts, +} from '../../../lib/firebase/folds' import { ActivityFeed, findActiveContracts } from '../../activity' import { TagsList } from '../../../components/tags-list' import { Row } from '../../../components/layout/row' @@ -85,29 +89,29 @@ export default function FoldPage(props: { - <Row className="items-center mb-2 flex-wrap"> + <Row className="items-center gap-2 mb-2 flex-wrap"> {isCurator && ( <> - <SiteLink className="text-sm " href={`/fold/${fold.slug}/edit`}> + <SiteLink className="text-sm " href={foldPath(fold, 'edit')}> Edit </SiteLink> - <div className="ml-2 mr-2">•</div> + <div className="text-gray-500">•</div> </> )} - <SiteLink - className="text-sm" - href={`/fold/${fold.slug}/leaderboards`} - > + <SiteLink className="text-sm" href={foldPath(fold, 'leaderboards')}> Leaderboards </SiteLink> - <div className="ml-2 mr-2">•</div> - <div className="text-sm text-gray-500 mr-1">Curated by</div> - <UserLink - className="text-sm text-neutral" - name={curator.name} - username={curator.username} - /> + <div className="text-gray-500">•</div> + <Row> + <div className="text-sm text-gray-500 mr-1">Curated by</div> + <UserLink + className="text-sm text-neutral" + name={curator.name} + username={curator.username} + /> + </Row> </Row> + <TagsList tags={tags.map((tag) => `#${tag}`)} /> <Spacer h={4} /> diff --git a/web/pages/folds.tsx b/web/pages/folds.tsx index 847d0173..d8414845 100644 --- a/web/pages/folds.tsx +++ b/web/pages/folds.tsx @@ -1,9 +1,14 @@ import _ from 'lodash' +import { useState } from 'react' import { Fold } from '../../common/fold' +import { parseWordsAsTags } from '../../common/util/parse' +import { ConfirmationButton } from '../components/confirmation-button' import { Col } from '../components/layout/col' import { Row } from '../components/layout/row' +import { Spacer } from '../components/layout/spacer' import { Page } from '../components/page' import { SiteLink } from '../components/site-link' +import { TagsList } from '../components/tags-list' import { Title } from '../components/title' import { UserLink } from '../components/user-page' import { foldPath, listAllFolds } from '../lib/firebase/folds' @@ -31,21 +36,97 @@ export default function Folds(props: { folds: Fold[]; curators: User[] }) { return ( <Page> - <Title text="Folds" /> + <Col className="items-center"> + <Col className="max-w-2xl w-full px-2 sm:px-0"> + <Row className="justify-between items-center"> + <Title text="Folds" /> + <ConfirmationButton + id="create-fold" + openModelBtn={{ + label: 'Create a fold', + className: 'btn-primary btn-sm', + }} + submitBtn={{ label: 'Create', className: 'btn-primary' }} + onSubmit={() => {}} + > + <Title className="!mt-0" text="Create a fold" /> - <Col className="gap-4"> - {folds.map((fold, index) => ( - <Row className="items-center"> - <SiteLink href={foldPath(fold)}>{fold.name}</SiteLink> - <div className="text-sm text-gray-500 ml-4 mr-1">Curated by</div> - <UserLink - className="text-sm text-neutral" - name={curators[index].name} - username={curators[index].username} - /> + <div className="text-gray-500"> + <div>A fold is a view of markets that match selected tags.</div> + <div> + You can further include or exclude individual markets. + </div> + </div> + + <Spacer h={4} /> + + <CreateFoldForm /> + </ConfirmationButton> </Row> - ))} + + <Col className="gap-4"> + {folds.map((fold, index) => ( + <Row className="items-center gap-2"> + <SiteLink href={foldPath(fold)}>{fold.name}</SiteLink> + <div /> + <div className="text-sm text-gray-500">12 followers</div> + <div className="text-gray-500">•</div> + <Row> + <div className="text-sm text-gray-500 mr-1">Curated by</div> + <UserLink + className="text-sm text-neutral" + name={curators[index].name} + username={curators[index].username} + /> + </Row> + </Row> + ))} + </Col> + </Col> </Col> </Page> ) } + +function CreateFoldForm() { + const [name, setName] = useState('') + const [tags, setTags] = useState('') + const [isSubmitting, setIsSubmitting] = useState(false) + + return ( + <form> + <div className="form-control w-full"> + <label className="label"> + <span className="mb-1">Fold name</span> + </label> + + <input + placeholder="Your fold name" + className="input input-bordered resize-none" + disabled={isSubmitting} + value={name} + onChange={(e) => setName(e.target.value || '')} + /> + </div> + + <Spacer h={4} /> + + <div className="form-control w-full"> + <label className="label"> + <span className="mb-1">Tags</span> + </label> + + <input + placeholder="Politics, Economics, Rationality" + className="input input-bordered resize-none" + disabled={isSubmitting} + value={tags} + onChange={(e) => setTags(e.target.value || '')} + /> + </div> + + <Spacer h={4} /> + <TagsList tags={parseWordsAsTags(tags)} /> + </form> + ) +}