diff --git a/web/components/contract/contract-info-dialog.tsx b/web/components/contract/contract-info-dialog.tsx
index 12fd8dd9..3e51902b 100644
--- a/web/components/contract/contract-info-dialog.tsx
+++ b/web/components/contract/contract-info-dialog.tsx
@@ -21,6 +21,7 @@ import { Title } from '../title'
import { TweetButton } from '../tweet-button'
import { InfoTooltip } from '../info-tooltip'
import { TagsInput } from 'web/components/tags-input'
+import { DuplicateContractButton } from '../copy-contract-button'
export const contractDetailsButtonClassName =
'group flex items-center rounded-md px-3 py-2 text-sm font-medium cursor-pointer hover:bg-gray-100 text-gray-400 hover:text-gray-500'
@@ -71,6 +72,7 @@ export function ContractInfoDialog(props: { contract: Contract; bets: Bet[] }) {
tweetText={getTweetText(contract, false)}
/>
+
diff --git a/web/components/copy-contract-button.tsx b/web/components/copy-contract-button.tsx
new file mode 100644
index 00000000..ad378878
--- /dev/null
+++ b/web/components/copy-contract-button.tsx
@@ -0,0 +1,54 @@
+import { DuplicateIcon } from '@heroicons/react/outline'
+import clsx from 'clsx'
+import { Contract } from 'common/contract'
+import { getMappedValue } from 'common/pseudo-numeric'
+import { trackCallback } from 'web/lib/service/analytics'
+
+export function DuplicateContractButton(props: {
+ contract: Contract
+ className?: string
+}) {
+ const { contract, className } = props
+
+ return (
+
+
+ Duplicate
+
+ )
+}
+
+// Pass along the Uri to create a new contract
+function duplicateContractHref(contract: Contract) {
+ const params = {
+ q: contract.question,
+ closeTime: contract.closeTime || 0,
+ description: contract.description,
+ outcomeType: contract.outcomeType,
+ } as Record
+
+ if (contract.outcomeType === 'PSEUDO_NUMERIC') {
+ params.min = contract.min
+ params.max = contract.max
+ params.isLogScale = contract.isLogScale
+ params.initValue = getMappedValue(contract)(contract.initialProbability)
+ }
+
+ return (
+ `/create?` +
+ Object.entries(params)
+ .map(([key, value]) => `${key}=${encodeURIComponent(value)}`)
+ .join('&')
+ )
+}
diff --git a/web/pages/create.tsx b/web/pages/create.tsx
index 6a5f96ae..83bea10f 100644
--- a/web/pages/create.tsx
+++ b/web/pages/create.tsx
@@ -28,14 +28,32 @@ import { GroupSelector } from 'web/components/groups/group-selector'
import { CATEGORIES } from 'common/categories'
import { User } from 'common/user'
-export default function Create() {
- const [question, setQuestion] = useState('')
- // get query params:
- const router = useRouter()
- const { groupId } = router.query as { groupId: string }
- useTracking('view create page')
- const creator = useUser()
+type NewQuestionParams = {
+ groupId?: string
+ q: string
+ type: string
+ description: string
+ closeTime: string
+ outcomeType: string
+ // Params for PSEUDO_NUMERIC outcomeType
+ min?: string
+ max?: string
+ isLogScale?: string
+ initValue?: string
+}
+export default function Create() {
+ useTracking('view create page')
+ const router = useRouter()
+ const params = router.query as NewQuestionParams
+ // TODO: Not sure why Question is pulled out as its own component;
+ // Maybe merge into newContract and then we don't need useEffect here.
+ const [question, setQuestion] = useState('')
+ useEffect(() => {
+ setQuestion(params.q ?? '')
+ }, [params.q])
+
+ const creator = useUser()
useEffect(() => {
if (creator === null) router.push('/')
}, [creator, router])
@@ -65,11 +83,7 @@ export default function Create() {
-
+
@@ -80,18 +94,21 @@ export default function Create() {
export function NewContract(props: {
creator: User
question: string
- groupId?: string
+ params?: NewQuestionParams
}) {
- const { creator, question, groupId } = props
- const [outcomeType, setOutcomeType] = useState('BINARY')
+ const { creator, question, params } = props
+ const { groupId, initValue } = params ?? {}
+ const [outcomeType, setOutcomeType] = useState(
+ (params?.outcomeType as outcomeType) ?? 'BINARY'
+ )
const [initialProb] = useState(50)
- const [minString, setMinString] = useState('')
- const [maxString, setMaxString] = useState('')
- const [isLogScale, setIsLogScale] = useState(false)
- const [initialValueString, setInitialValueString] = useState('')
+ const [minString, setMinString] = useState(params?.min ?? '')
+ const [maxString, setMaxString] = useState(params?.max ?? '')
+ const [isLogScale, setIsLogScale] = useState(!!params?.isLogScale)
+ const [initialValueString, setInitialValueString] = useState(initValue)
- const [description, setDescription] = useState('')
+ const [description, setDescription] = useState(params?.description ?? '')
// const [tagText, setTagText] = useState(tag ?? '')
// const tags = parseWordsAsTags(tagText)
useEffect(() => {
@@ -113,10 +130,18 @@ export function NewContract(props: {
// }, [ante, creator])
// const [anteError, setAnteError] = useState()
+
+ // If params.closeTime is set, extract out the specified date and time
// By default, close the market a week from today
const weekFromToday = dayjs().add(7, 'day').format('YYYY-MM-DD')
- const [closeDate, setCloseDate] = useState(weekFromToday)
- const [closeHoursMinutes, setCloseHoursMinutes] = useState('23:59')
+ const timeInMs = Number(params?.closeTime ?? 0)
+ const initDate = timeInMs
+ ? dayjs(timeInMs).format('YYYY-MM-DD')
+ : weekFromToday
+ const initTime = timeInMs ? dayjs(timeInMs).format('HH:mm') : '23:59'
+ const [closeDate, setCloseDate] = useState(initDate)
+ const [closeHoursMinutes, setCloseHoursMinutes] = useState(initTime)
+
const [marketInfoText, setMarketInfoText] = useState('')
const [isSubmitting, setIsSubmitting] = useState(false)
const [selectedGroup, setSelectedGroup] = useState(