Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import { useAccount, useBalance } from "wagmi";
import { useWithdrawFromTradeExecutor } from "@/hooks/tradeWallet/useWithdrawFromTradeExecutor";
import { useTokenBalance } from "@/hooks/useTokenBalance";

import AmountInput from "@/components/AmountInput";
import LightButton from "@/components/LightButton";

import CloseIcon from "@/assets/svg/close-icon.svg";

import AmountInput from "@/components/AmountInput";
import { Tokens, TokenType } from "@/consts/tokens";

interface WithdrawInterfaceProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import { useMarketsStore } from "@/store/markets";
import { usePredictionMarkets } from "@/hooks/usePredictionMarkets";

import LightButton from "@/components/LightButton";
import { ScrollFade } from "@/components/ScrollFade";

import CloseIcon from "@/assets/svg/close-icon.svg";
import ArrowDown from "@/assets/svg/long-arrow-down.svg";
import ArrowUp from "@/assets/svg/long-arrow-up.svg";

import { formatWithPrecision, isUndefined } from "@/utils";
import { ScrollFade } from "@/components/ScrollFade";

const Header: React.FC = () => {
const markets = usePredictionMarkets();
Expand Down
159 changes: 159 additions & 0 deletions src/app/factory/components/ChildFields.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import React from "react";

import { NumberField, TextField } from "@kleros/ui-components-library";

import { useFactoryStore } from "@/store/factory";

import DateTimeInput from "./DateTimeInput";
import Field from "./Field";

interface ChildFieldsProps {
index: number;
}

const ChildFields: React.FC<ChildFieldsProps> = ({ index }) => {
const child = useFactoryStore((s) => s.children[index]);
const set = useFactoryStore((s) => s.setChildField);
const isDeploying = useFactoryStore((s) => s.isDeploying);

if (!child) return null;

return (
<div className="flex w-full flex-col gap-4 px-1 py-3">
<Field
label="Scalar question (resolved)"
tooltip="Filled from the template and this child’s parent outcome + ticker. You can edit it directly; it will refresh if you change the template, outcome label, or ticker."
>
<TextField
aria-label={`child-${index}-name`}
value={child.marketName}
onChange={(v) => set(index, "marketName", v)}
isDisabled={isDeploying}
placeholder="IMDb score if A is watched (0-10)"
/>
</Field>

<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<Field
label="Low outcome label"
tooltip="Branch label for the lower scalar leg (e.g. DOWN)."
>
<TextField
aria-label={`child-${index}-low-label`}
value={child.outcomeLabelLow}
onChange={(v) => set(index, "outcomeLabelLow", v)}
isDisabled={isDeploying}
placeholder="DOWN"
/>
</Field>
<Field
label="High outcome label"
tooltip="Branch label for the upper scalar leg (e.g. UP)."
>
<TextField
aria-label={`child-${index}-high-label`}
value={child.outcomeLabelHigh}
onChange={(v) => set(index, "outcomeLabelHigh", v)}
isDisabled={isDeploying}
placeholder="UP"
/>
</Field>
<Field
label="Low token ticker"
tooltip="Wrapped ERC20 ticker for the low leg. Max 31 bytes."
>
<TextField
aria-label={`child-${index}-low-token`}
value={child.tokenNameLow}
onChange={(v) => set(index, "tokenNameLow", v)}
isDisabled={isDeploying}
placeholder="A_SC_DN"
/>
</Field>
<Field
label="High token ticker"
tooltip="Wrapped ERC20 ticker for the high leg. Max 31 bytes."
>
<TextField
aria-label={`child-${index}-high-token`}
value={child.tokenNameHigh}
onChange={(v) => set(index, "tokenNameHigh", v)}
isDisabled={isDeploying}
placeholder="A_SC_UP"
/>
</Field>
<Field
label="Lower bound"
tooltip="Numeric lower bound for the scalar range. Whole number, must be < upper bound."
>
<TextField
aria-label={`child-${index}-lower-bound`}
value={child.lowerBound}
onChange={(v) => set(index, "lowerBound", v)}
isDisabled={isDeploying}
placeholder="0"
/>
</Field>
<Field
label="Upper bound"
tooltip="Numeric upper bound for the scalar range. Whole number, must satisfy upperBound < 2^256 - 2."
>
<TextField
aria-label={`child-${index}-upper-bound`}
value={child.upperBound}
onChange={(v) => set(index, "upperBound", v)}
isDisabled={isDeploying}
placeholder="10"
/>
</Field>
<Field
label="Min bond (xDAI)"
tooltip="Reality.eth minimum bond for this child question."
>
<NumberField
aria-label={`child-${index}-min-bond`}
value={Number(child.minBond) || 0}
onChange={(v) =>
set(index, "minBond", Number.isNaN(v) ? "0" : String(v))
}
isDisabled={isDeploying}
minValue={0}
/>
</Field>
<Field
label="Opening time"
tooltip="When Reality.eth opens this child question for answers."
>
<DateTimeInput
value={child.openingTime}
onChange={(v) => set(index, "openingTime", v)}
isDisabled={isDeploying}
/>
</Field>
<Field label="Category" tooltip="Reality.eth metadata category.">
<TextField
aria-label={`child-${index}-category`}
value={child.category}
onChange={(v) => set(index, "category", v)}
isDisabled={isDeploying}
placeholder="movies"
/>
</Field>
<Field
label="Language"
tooltip="Reality.eth language code (ISO 639-1)."
>
<TextField
aria-label={`child-${index}-lang`}
value={child.lang}
onChange={(v) => set(index, "lang", v)}
isDisabled={isDeploying}
placeholder="en"
/>
</Field>
</div>
</div>
);
};

export default ChildFields;
87 changes: 87 additions & 0 deletions src/app/factory/components/ChildrenForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import React from "react";

import { Accordion, Card, Tag, TextField } from "@kleros/ui-components-library";
import clsx from "clsx";

import {
PHASED_CHILD_BATCH_SIZE,
PHASED_THRESHOLD,
useFactoryStore,
} from "@/store/factory";

import ChildFields from "./ChildFields";
import Field from "./Field";

const ChildrenForm: React.FC = () => {
const outcomes = useFactoryStore((s) => s.parent.outcomes);
const childQuestionTemplate = useFactoryStore(
(s) => s.parent.childQuestionTemplate,
);
const setParentField = useFactoryStore((s) => s.setParentField);
const isDeploying = useFactoryStore((s) => s.isDeploying);
const childrenCount = useFactoryStore((s) => s.children.length);
const isPhased = childrenCount > PHASED_THRESHOLD;
const phasedSignatures = isPhased
? 1 + Math.ceil(childrenCount / PHASED_CHILD_BATCH_SIZE)
: 1;

return (
<Card
round
className="flex h-auto w-full flex-col gap-4 p-5 md:p-8"
aria-label="children-form"
>
<div className="flex flex-wrap items-start justify-between gap-3">
<div className="flex flex-col gap-1">
<h2 className="text-klerosUIComponentsPrimaryText text-lg font-semibold">
Scalar child markets
<span className="text-klerosUIComponentsSecondaryText ml-2 text-sm font-normal">
(one per parent outcome)
</span>
</h2>
<p className="text-klerosUIComponentsSecondaryText text-xs">
Each child is a scalar market bound to a single parent outcome
index. Set one question template below; each child’s Reality text is
built from its parent outcome label and ticker.
</p>
</div>
<Tag
text={
isPhased
? `Phased deploy · ${phasedSignatures} signatures`
: "Atomic deploy · 1 signature"
}
className={clsx(isPhased && "bg-klerosUIComponentsMediumBlue")}
/>
</div>

<Field
label="Child question template"
tooltip="Placeholders: ${outcome} or ${marketName} = that row’s parent outcome label; ${token} = its wrapped ticker (Outcomes section). Editing the template refreshes every child question. Editing one outcome or ticker updates only that child’s resolved question."
>
<TextField
aria-label="child-question-template"
value={childQuestionTemplate}
onChange={(v) => setParentField("childQuestionTemplate", v)}
isDisabled={isDeploying}
placeholder="Score if ${outcome} is watched (0-10)"
/>
</Field>

<Accordion
aria-label="children-accordion"
className={clsx(
"w-full max-w-full",
"[&_#expand-button]:bg-klerosUIComponentsLightBackground",
"[&_#expand-button_p]:font-semibold",
)}
items={outcomes.map((outcome, idx) => ({
title: `Child ${idx + 1} · ${outcome || `Outcome ${idx}`}`,
body: <ChildFields index={idx} />,
}))}
/>
</Card>
);
};

export default ChildrenForm;
42 changes: 42 additions & 0 deletions src/app/factory/components/DateTimeInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from "react";

import clsx from "clsx";

interface DateTimeInputProps {
value: number;
onChange: (unixSeconds: number) => void;
isDisabled?: boolean;
}

const toLocalInputValue = (unixSeconds: number) => {
if (!unixSeconds || Number.isNaN(unixSeconds)) return "";
const date = new Date(unixSeconds * 1000);
if (Number.isNaN(date.getTime())) return "";
const tz = date.getTimezoneOffset() * 60_000;
return new Date(date.getTime() - tz).toISOString().slice(0, 16);
};

const DateTimeInput: React.FC<DateTimeInputProps> = ({
value,
onChange,
isDisabled,
}) => (
<input
type="datetime-local"
value={toLocalInputValue(value)}
disabled={isDisabled}
onChange={(e) => {
const next = e.target.valueAsNumber;
if (Number.isNaN(next)) return;
onChange(Math.floor(next / 1000));
}}
className={clsx(
"border-klerosUIComponentsStroke rounded-base h-11.25 w-full border px-3",
"bg-klerosUIComponentsWhiteBackground text-klerosUIComponentsPrimaryText text-sm",
"focus:border-klerosUIComponentsPrimaryBlue focus:outline-none",
"disabled:opacity-60",
)}
/>
);

export default DateTimeInput;
Loading