Skip to content

Commit

Permalink
Merge pull request #39 from Levana-Protocol/perp-4074/popup-disclaimer
Browse files Browse the repository at this point in the history
PERP-4074 | Popup disclaimer
  • Loading branch information
lvn-rusty-dragon authored Sep 21, 2024
2 parents fa2e203 + e273b3b commit cfae8e8
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 43 deletions.
3 changes: 3 additions & 0 deletions frontend/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Outlet, ScrollRestoration } from "react-router-dom"
import { Navbar } from "@common/Navbar"
import { Footer } from "@common/Footer"
import { Geoblock } from "@common/Geoblock"
import { TermsDisclaimer } from "@common/TermsDisclaimer"

const App = () => {
return (
Expand All @@ -13,6 +14,8 @@ const App = () => {
<Outlet />
</Geoblock>
<Footer />

<TermsDisclaimer />
<ScrollRestoration />
</Stack>
)
Expand Down
138 changes: 138 additions & 0 deletions frontend/src/components/common/TermsDisclaimer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import { useState } from "react"
import {
Button,
Checkbox,
DialogActions,
DialogContent,
DialogTitle,
FormControl,
Link,
Modal,
ModalDialog,
} from "@mui/joy"
import { Link as RouterLink } from "react-router-dom"
import { Controller, useForm } from "react-hook-form"

import { stylesOnlyAt } from "@utils/styles"

const DISCLAIMER_URL = "https://static.levana.finance/legal/disclaimer.pdf"
const TERMS_ACCEPTED_KEY = "terms_accepted"
const TERMS_ACCEPTED_VALUE = "true"

interface DisclaimerFormValues {
readTerms: boolean
agreeTerms: boolean
}

const TermsDisclaimer = () => {
const [accepted, setAccepted] = useState(
localStorage.getItem(TERMS_ACCEPTED_KEY) === TERMS_ACCEPTED_VALUE,
)

const form = useForm<DisclaimerFormValues>({
defaultValues: {
readTerms: false,
agreeTerms: false,
},
})

const onSubmit = (formValues: DisclaimerFormValues) => {
const readTerms = formValues.readTerms
const agreeTerms = formValues.agreeTerms

if (readTerms && agreeTerms) {
localStorage.setItem(TERMS_ACCEPTED_KEY, TERMS_ACCEPTED_VALUE)
setAccepted(true)
} else {
return Promise.reject()
}
}

const canSubmit = form.formState.isValid

return (
<Modal open={!accepted}>
<ModalDialog component="form" onSubmit={form.handleSubmit(onSubmit)}>
<DialogTitle sx={stylesOnlyAt("xs", { mb: 4 })}>
Terms of Service
</DialogTitle>

<DialogContent>
<Controller
name="readTerms"
control={form.control}
rules={{
required: "This field is required",
}}
render={({ field, fieldState }) => (
<FormControl error={!!fieldState.error} sx={{ mb: 1 }}>
<Checkbox
value={`${field.value}`}
onChange={(e) => {
field.onChange(e.currentTarget.checked)
}}
label={
<>
I have read the complete disclaimer in the terms of
service.
</>
}
variant="solid"
/>
<Link
component={RouterLink}
to={DISCLAIMER_URL}
title="Disclaimer document"
aria-label="Open disclaimer document"
target="_blank"
rel="noreferrer"
sx={{ ml: 3.75 }}
>
Read disclaimer
</Link>
</FormControl>
)}
/>
<Controller
name="agreeTerms"
control={form.control}
rules={{
required: "This field is required",
}}
render={({ field, fieldState }) => (
<FormControl error={!!fieldState.error}>
<Checkbox
value={`${field.value}`}
onChange={(e) => {
field.onChange(e.currentTarget.checked)
}}
label={
<>
I agree to the terms of service and I live in a country
where I'm allowed to participate in Prediction markets.
</>
}
variant="solid"
/>
</FormControl>
)}
/>
</DialogContent>

<DialogActions>
<Button
type="submit"
color="primary"
size="lg"
aria-label="Agree to the terms stated"
disabled={!canSubmit}
>
Accept
</Button>
</DialogActions>
</ModalDialog>
</Modal>
)
}

export { TermsDisclaimer }
6 changes: 5 additions & 1 deletion frontend/src/config/theme.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,10 @@ const theme = extendTheme({
lg: ".75rem",
xl: "1.5rem",
},
zIndex: {
modal: 1000000,
tooltip: 1000300,
},
components: {
JoyAlert: {
defaultProps: {
Expand Down Expand Up @@ -702,7 +706,7 @@ const ThemeProvider = (props: PropsWithChildren) => {
},

".notistack-SnackbarContainer": {
zIndex: 1000000,
zIndex: 1000600,
},

".notistack-Snackbar": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import { useCurrentAccount } from "@config/chain"
import type { Market, OutcomeId } from "@api/queries/Market"
import { balancesQuery } from "@api/queries/Balances"
import { coinPricesQuery } from "@api/queries/Prices"
import { OutcomeField } from "../OutcomeField"
import { CoinsAmountField } from "../CoinsAmountField"
import { useMarketBuyForm } from "./form"
import { getSharesPurchased } from "@utils/shares"
import { getPurchaseResult } from "@utils/shares"
import { useLatestFormValues } from "@utils/forms"
import { Coins, USD } from "@utils/coins"
import { useMarketBuyForm } from "./form"
import { OutcomeField } from "../OutcomeField"
import { CoinsAmountField } from "../CoinsAmountField"

const MarketBuyForm = (props: { market: Market }) => {
const { market } = props
Expand Down Expand Up @@ -56,53 +56,48 @@ const MarketBuyForm = (props: { market: Market }) => {
{form.formState.isSubmitting ? "Placing bet..." : "Place bet"}
</Button>

{coinsAmount && formValues.betOutcome && (
<ShowSharesPurchased
market={market}
betOutcome={formValues.betOutcome}
coinsAmount={coinsAmount}
/>
)}
<ShowSharesPurchased
market={market}
betOutcome={formValues.betOutcome ?? undefined}
coinsAmount={coinsAmount}
/>
</Stack>
</FormProvider>
)
}

interface ShowSharesPurchasedProps {
market: Market
betOutcome: OutcomeId
coinsAmount: Coins
betOutcome: OutcomeId | undefined
coinsAmount: Coins | undefined
}

const ShowSharesPurchased = (props: ShowSharesPurchasedProps) => {
const { outcome, fees, liquidity } = props.betOutcome
? (() => {
const { outcome, fees, liquidity } = getSharesPurchased(
props.market,
props.betOutcome,
props.coinsAmount,
)
return {
outcome: outcome.toFormat(false),
fees: fees.toFormat(true),
liquidity: liquidity.toFormat(true),
}
})()
: { outcome: "-", fees: "-", liquidity: "-" }
const { market, betOutcome, coinsAmount } = props
const purchaseResult =
betOutcome !== undefined && coinsAmount !== undefined
? getPurchaseResult(market, betOutcome, coinsAmount)
: undefined

return (
<>
<Stack direction="row" justifyContent="space-between">
<Typography level="body-sm">Estimated shares</Typography>
<Typography level="body-sm">{outcome}</Typography>
<Typography level="body-sm">
{purchaseResult?.shares.toFormat(false) ?? "-"}
</Typography>
</Stack>
<Stack direction="row" justifyContent="space-between">
<Typography level="body-sm">Liquidity deposit</Typography>
<Typography level="body-sm">{liquidity}</Typography>
<Typography level="body-sm">
{purchaseResult?.liquidity.toFormat(true) ?? "-"}
</Typography>
</Stack>
<Stack direction="row" justifyContent="space-between">
<Typography level="body-sm">Fees</Typography>
<Typography level="body-sm">{fees}</Typography>
<Typography level="body-sm">
{purchaseResult?.liquidity.toFormat(true) ?? "-"}
</Typography>
</Stack>
</>
)
Expand Down
8 changes: 4 additions & 4 deletions frontend/src/pages/Market/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,16 @@ const MarketPage = () => {
xs: buildGridAreas([
"title",
"outcomes",
"positions",
"liquidity",
...(account.isConnected ? ["positions", "liquidity"] : []),
"description",
"betting",
]),
md: buildGridAreas([
"title betting",
"outcomes betting",
"positions betting",
"liquidity betting",
...(account.isConnected
? ["positions betting", "liquidity betting"]
: []),
"description betting",
"rest betting",
]),
Expand Down
16 changes: 8 additions & 8 deletions frontend/src/utils/shares.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import BigNumber from "bignumber.js"

import type { Market, OutcomeId } from "@api/queries/Market"
import type { Positions } from "@api/queries/Positions"
import { LIQUDITY_PORTION } from "@api/mutations/PlaceBet"
import {
Asset,
Coins,
Expand All @@ -10,7 +11,6 @@ import {
type Denom,
} from "./coins"
import { formatToSignificantDigits, unitsToValue, valueToUnits } from "./number"
import { LIQUDITY_PORTION } from "@api/mutations/PlaceBet"

class Shares extends Asset {
static symbol = "shares"
Expand Down Expand Up @@ -119,8 +119,8 @@ interface PoolSize {
returned: BigNumber[]
}

interface SharesPurchased {
outcome: Shares
interface PurchaseResult {
shares: Shares
liquidity: Coins
fees: Coins
}
Expand All @@ -144,11 +144,11 @@ const addToPool = (poolInit: BigNumber[], amount: BigNumber): PoolSize => {
return { pool, returned }
}

const getSharesPurchased = (
const getPurchaseResult = (
market: Market,
selectedOutcomeString: OutcomeId,
buyAmountTotalCoins: Coins,
): SharesPurchased => {
): PurchaseResult => {
// To calculate the shares properly, we need to follow the same steps as
// are taken by the contract, namely:
//
Expand Down Expand Up @@ -203,17 +203,17 @@ const getSharesPurchased = (
const purchasedShares = pool[selectedOutcome].minus(selectedPool)

return {
outcome: Shares.fromCollateralUnits(market.denom, purchasedShares),
shares: Shares.fromCollateralUnits(market.denom, purchasedShares),
liquidity: Coins.fromUnits(buyAmountTotalCoins.denom, liquidity),
fees: Coins.fromUnits(buyAmountTotalCoins.denom, fees),
}
}

export {
Shares,
type PurchaseResult,
getShares,
getPotentialWinnings,
getOddsForOutcome,
getSharesPurchased,
getPurchaseResult,
}
export type { SharesPurchased }

0 comments on commit cfae8e8

Please sign in to comment.