Skip to content

Commit

Permalink
Ingawei/buy panel (#3226)
Browse files Browse the repository at this point in the history
  • Loading branch information
ingawei authored Dec 17, 2024
1 parent 8c7332c commit e70205b
Show file tree
Hide file tree
Showing 9 changed files with 385 additions and 24 deletions.
2 changes: 1 addition & 1 deletion mani/components/Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import Animated, {
import { SafeAreaView } from 'react-native-safe-area-context'

import { TokenSlider } from './TokenSlider'
import { SliderHeader } from './layout/slider-header'
import { SliderHeader } from './layout/SliderHeader'
import { useBottomTabOverflow } from './ui/TabBarBackground.ios'
import { useColor } from 'hooks/useColor'
import { ThemedView } from './ThemedView'
Expand Down
2 changes: 1 addition & 1 deletion mani/components/buttons/YesNoButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export function YesNoButton(props: ButtonProps) {
return (
<Button
{...props}
title={isYes ? 'Yes' : 'No'}
title={props.title ?? (isYes ? 'Yes' : 'No')}
style={[{ width: getWidth(props.size) }, props.style]}
textProps={{
weight: 'normal',
Expand Down
18 changes: 8 additions & 10 deletions mani/components/contract/FeedCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,17 @@ import { Button } from 'components/buttons/Button'
import { YesNoButton } from 'components/buttons/YesNoButtons'
import { getDisplayProbability } from 'common/calculate'
import { AnswerProbability, BinaryProbability } from './Probability'
import { useState } from 'react'
import { BetPanel } from './bet/BetPanel'
import { BinaryBetButtons } from './bet/BinaryBetButtons'
import { MultiBetButtons } from './bet/MultiBetButtons'

export function FeedCard({ contract }: { contract: Contract }) {
const isBinaryMc = isBinaryMulti(contract)
const isMultipleChoice =
contract.outcomeType == 'MULTIPLE_CHOICE' && !isBinaryMc
const isBinary = !isBinaryMc && !isMultipleChoice

const [betPanelOpen, setBetPanelOpen] = useState(false)
const color = useColor()
return (
<Col
Expand Down Expand Up @@ -93,17 +97,14 @@ export function FeedCard({ contract }: { contract: Contract }) {
>
{answer.text}
</ThemedText>
<Row style={{ gap: 12, alignItems: 'center' }}>
<Row style={{ gap: 12, alignItems: 'center', flexShrink: 0 }}>
<AnswerProbability
contract={contract as MultiContract}
answerId={answer.id}
size="md"
style={{ flexShrink: 0 }}
/>
<Row style={{ gap: 8, alignItems: 'center' }}>
<YesNoButton variant="yes" size="xs" />
<YesNoButton variant="no" size="xs" />
</Row>
<MultiBetButtons contract={contract} answerId={answer.id} />
</Row>
</Row>
))}
Expand All @@ -116,10 +117,7 @@ export function FeedCard({ contract }: { contract: Contract }) {
</Row>
</>
) : (
<Row style={{ gap: 12, alignItems: 'center' }}>
<YesNoButton variant="yes" size="sm" style={{ flex: 1 }} />
<YesNoButton variant="no" size="sm" style={{ flex: 1 }} />
</Row>
<BinaryBetButtons contract={contract} />
)}
</Col>
)
Expand Down
158 changes: 158 additions & 0 deletions mani/components/contract/bet/BetInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { Col } from 'components/layout/col'
import { Row } from 'components/layout/row'
import { NumberText } from 'components/NumberText'
import { Colors } from 'constants/Colors'
import { useColor } from 'hooks/useColor'
import { useTokenMode } from 'hooks/useTokenMode'
import React, { useState } from 'react'
import {
Image,
View,
Text,
StyleSheet,
Pressable,
TextInput,
Keyboard,
} from 'react-native'

export function BetAmountInput({
minAmount = 0,
maxAmount = 1000,
setAmount,
amount,
}: {
minAmount?: number
maxAmount?: number
amount: number
setAmount: (amount: number) => void
}) {
const [displayValue, setDisplayValue] = useState<string>(amount.toString())

const handleAmountChange = (newAmount: number) => {
const validAmount = Math.min(Math.max(newAmount, minAmount), maxAmount)
setAmount(validAmount)
setDisplayValue(validAmount.toString())
}

const handleTextInput = (text: string) => {
// First, just update what's displayed
const cleanedText = text
.replace(/[^0-9.]/g, '') // Allow only numbers and decimal point
.replace(/(\..*)\./g, '$1') // Allow only one decimal point
.replace(/^0+(?=\d)/, '')

const numberValue = Math.min(parseFloat(cleanedText) || 0, maxAmount)
const roundedValue = Math.round(numberValue * 100) / 100

if (roundedValue == maxAmount) {
setDisplayValue(maxAmount.toString())
} else if (cleanedText.split('.')[1]?.length > 2) {
// If there are more than 2 decimal places, show the rounded value
setDisplayValue(roundedValue.toString())
} else {
setDisplayValue(cleanedText)
}
setAmount(roundedValue)
}

const { mode } = useTokenMode()
const color = useColor()

return (
<View
style={{
borderRadius: 6,
}}
>
<Row
style={{
alignItems: 'center',
borderWidth: 1,
borderColor: color.border,
padding: 8,
borderRadius: 6,
gap: 8,
justifyContent: 'space-between',
width: '100%',
}}
>
<Row style={{ alignItems: 'center', gap: 8, flex: 1 }}>
<Image
style={{
width: 48,
height: 48,
}}
source={
mode === 'play'
? require('assets/images/masses_mana.png')
: require('assets/images/masses_sweeps.png')
}
/>
<TextInput
style={{
fontFamily: 'JetBrainsMono',
fontSize: 48,
fontWeight: 'bold',
color: 'white',
flex: 1,
}}
value={displayValue}
onChangeText={handleTextInput}
keyboardType="decimal-pad"
autoFocus={true}
returnKeyType="done"
onSubmitEditing={() => {
// Handle what happens when enter/done is pressed
// For example, you might want to blur the input
Keyboard.dismiss()
}}
/>
</Row>
<Col style={{ gap: 8 }}>
<Pressable
style={[
styles.incrementButton,
amount + 10 > maxAmount && styles.disabledButton,
]}
onPress={(e) => {
e.stopPropagation()
handleAmountChange(amount + 10)
}}
disabled={amount + 10 > maxAmount}
>
<NumberText style={styles.incrementButtonText}>+10</NumberText>
</Pressable>
<Pressable
style={[
styles.incrementButton,
amount + 50 > maxAmount && styles.disabledButton,
]}
onPress={(e) => {
e.stopPropagation()
handleAmountChange(amount + 50)
}}
disabled={amount + 50 > maxAmount}
>
<NumberText style={styles.incrementButtonText}>+50</NumberText>
</Pressable>
</Col>
</Row>
</View>
)
}

const styles = StyleSheet.create({
incrementButton: {
backgroundColor: Colors.grayButtonBackground,
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 6,
},
incrementButtonText: {
color: 'white',
fontSize: 12,
},
disabledButton: {
opacity: 0.5,
},
})
116 changes: 116 additions & 0 deletions mani/components/contract/bet/BetPanel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import { Contract } from 'common/contract'
import { Col } from 'components/layout/col'
import { ThemedText } from 'components/ThemedText'
import { useColor } from 'hooks/useColor'
import React, { useState } from 'react'
import {
Modal,
TouchableWithoutFeedback,
View,
KeyboardAvoidingView,
Platform,
ScrollView,
} from 'react-native'
import { BetAmountInput } from './BetInput'
import { Row } from 'components/layout/row'
import { NumberText } from 'components/NumberText'
import { Button } from 'components/buttons/Button'
import { YesNoButton } from 'components/buttons/YesNoButtons'
export type BinaryOutcomes = 'YES' | 'NO'

export function BetPanel({
contract,
open,
setOpen,
outcome,
answerId,
}: {
contract: Contract
open: boolean
setOpen: (open: boolean) => void
outcome: BinaryOutcomes
answerId?: string
}) {
const color = useColor()
const [amount, setAmount] = useState(0)

const answer =
answerId && 'answers' in contract
? contract.answers.find((a) => a.id === answerId)
: null

// TODO: figure out keyboard clicking behavior
return (
<Modal
visible={open}
animationType="slide"
transparent={true}
onRequestClose={() => setOpen(false)}
>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{ flex: 1 }}
>
<TouchableWithoutFeedback onPress={() => setOpen(false)}>
<Col
style={{
flex: 1,
justifyContent: 'flex-end',
backgroundColor: color.modalOverlay,
}}
>
<TouchableWithoutFeedback onPress={(e) => e.stopPropagation()}>
<Col
style={{
backgroundColor: color.backgroundSecondary,
padding: 20,
borderTopLeftRadius: 20,
borderTopRightRadius: 20,
width: '100%',
maxHeight: '70%',
minHeight: 400,
justifyContent: 'space-between',
}}
>
<Col style={{ gap: 4 }}>
<ThemedText size="lg" weight="semibold">
{contract.question}
</ThemedText>

<ThemedText size="md" color={color.textSecondary}>
{answer && answer.text}
</ThemedText>
</Col>
<BetAmountInput amount={amount} setAmount={setAmount} />
<Col style={{ gap: 8 }}>
<Row
style={{ justifyContent: 'space-between', width: '100%' }}
>
<ThemedText color={color.textTertiary} size="lg">
Payout if win
</ThemedText>

{/* TODO: get real payout */}
<NumberText size="lg" weight="semibold">
${(amount * 2).toFixed(2)}{' '}
<ThemedText color={color.profitText}>(+200%)</ThemedText>
</NumberText>
</Row>
<YesNoButton
variant={outcome === 'YES' ? 'yes' : 'no'}
size="lg"
title={`Buy ${outcome === 'YES' ? 'Yes' : 'No'}`}
onPress={() => {
// TODO: add bet logic
setOpen(false)
}}
/>
</Col>
</Col>
</TouchableWithoutFeedback>
</Col>
</TouchableWithoutFeedback>
</KeyboardAvoidingView>
</Modal>
)
}
40 changes: 40 additions & 0 deletions mani/components/contract/bet/BinaryBetButtons.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Contract } from 'common/contract'
import { YesNoButton } from 'components/buttons/YesNoButtons'
import { Row } from 'components/layout/row'
import { useState } from 'react'
import { BetPanel, BinaryOutcomes } from './BetPanel'

export function BinaryBetButtons({ contract }: { contract: Contract }) {
const [openBetPanel, setOpenBetPanel] = useState(false)
const [outcome, setOutcome] = useState<BinaryOutcomes>('YES')

const handleBetClick = (selectedOutcome: BinaryOutcomes) => {
setOutcome(selectedOutcome)
setOpenBetPanel(true)
}

return (
<>
<Row style={{ gap: 12, alignItems: 'center' }}>
<YesNoButton
onPress={() => handleBetClick('YES')}
variant="yes"
size="sm"
style={{ flex: 1 }}
/>
<YesNoButton
onPress={() => handleBetClick('NO')}
variant="no"
size="sm"
style={{ flex: 1 }}
/>
</Row>
<BetPanel
contract={contract}
open={openBetPanel}
setOpen={setOpenBetPanel}
outcome={outcome}
/>
</>
)
}
Loading

0 comments on commit e70205b

Please sign in to comment.