Skip to content

Commit

Permalink
Merge pull request #191 from napse-invest/feature/bot
Browse files Browse the repository at this point in the history
Feature/bot
  • Loading branch information
Xenepix authored Feb 14, 2024
2 parents 11eea18 + bad81ad commit d983fb1
Show file tree
Hide file tree
Showing 6 changed files with 414 additions and 258 deletions.
48 changes: 37 additions & 11 deletions desktop-app/renderer/api/spaces/spaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,36 +76,62 @@ export async function createSpace(
return response as AxiosResponse<NapseSpace>
}

// Investments related
export interface Investment {
// Invest related
export interface Operation {
ticker: string
amount: number
}
export async function spacePossibleInvestments(
searchParams: ReturnType<typeof useSearchParams>,
space: RetrievedNapseSpace
): Promise<AxiosResponse<Investment[]>> {
): Promise<AxiosResponse<Operation[]>> {
const response = await request(
searchParams,
'GET',
`/api/space/${space.uuid}/invest/`
)
return response as AxiosResponse<Investment[]>
return response as AxiosResponse<Operation[]>
}

export async function spaceInvest(
searchParams: ReturnType<typeof useSearchParams>,
space: RetrievedNapseSpace,
investment: Investment
): Promise<AxiosResponse<Investment>> {
const formated_investment = convertInterfaceToSnakeCaseDict(investment)
console.log('investment', investment)
console.log('formated_investment', formated_investment)
investment: Operation
): Promise<AxiosResponse<Operation>> {
const formated_operation = convertInterfaceToSnakeCaseDict(investment)
const response = await request(
searchParams,
'POST',
`/api/space/${space.uuid}/invest/`,
formated_investment
formated_operation
)
return response as AxiosResponse<Investment>
return response as AxiosResponse<Operation>
}

// Withdraw related
export async function spacePossibleWithdraws(
searchParams: ReturnType<typeof useSearchParams>,
space: RetrievedNapseSpace
): Promise<AxiosResponse<Operation[]>> {
const response = await request(
searchParams,
'GET',
`/api/space/${space.uuid}/withdraw/`
)
return response as AxiosResponse<Operation[]>
}

export async function spaceWithdraw(
searchParams: ReturnType<typeof useSearchParams>,
space: RetrievedNapseSpace,
investment: Operation
): Promise<AxiosResponse<Operation>> {
const formated_operation = convertInterfaceToSnakeCaseDict(investment)
const response = await request(
searchParams,
'POST',
`/api/space/${space.uuid}/withdraw/`,
formated_operation
)
return response as AxiosResponse<Operation>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
import { Operation } from '@/api/spaces/spaces'
import CustomForm from '@/components/custom/selectedObject/inputs'
import { Button } from '@/components/ui/button'
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger
} from '@/components/ui/dialog'
import { DialogClose } from '@radix-ui/react-dialog'

import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger
} from '@/components/ui/tooltip'
import { useToast } from '@/components/ui/use-toast'
import { ArrowDownOnSquareIcon } from '@heroicons/react/24/outline'
import { AxiosResponse } from 'axios'
import { ReadonlyURLSearchParams, useSearchParams } from 'next/navigation'
import { useEffect, useState } from 'react'
import * as z from 'zod'
import { useOperationContext } from './operationContext'

const OperationSchema = z.object({
ticker: z
.string()
.min(2, { message: 'You have to give an existing currency.' })
.max(8, { message: 'You have to give an existing currency.' }),
amount: z.number()
})

interface ObjectWithName extends Object {
name: string
}

type getCallbackType<T> = (
searchParams: ReadonlyURLSearchParams,
object: T
) => Promise<AxiosResponse<Operation[], any>>
type postCallbackType<T> = (
searchParams: ReadonlyURLSearchParams,
object: T,
investment: Operation
) => Promise<AxiosResponse<Operation, any>>

export function InvestMoneyActionButton<T extends ObjectWithName>({
object,
getPossibleInvestmentsCallback,
postInvestmentCallback
}: {
object: T
getPossibleInvestmentsCallback: getCallbackType<T>
postInvestmentCallback: postCallbackType<T>
}): JSX.Element {
const { toast } = useToast()
const searchParams = useSearchParams()
const [possibleInvestments, setPossibleInvestments] = useState<Operation[]>(
[]
)
const [selectedTicker, setSelectedTicker] = useState<string>('')
const { setTriggerRefresh } = useOperationContext()

useEffect(() => {
const fetchPossibleInvestments = async () => {
try {
const response = await getPossibleInvestmentsCallback(
searchParams,
object
)
setPossibleInvestments(response.data)
} catch (error) {
console.error(error)
setPossibleInvestments([])
}
}
if (searchParams.get('server')) {
fetchPossibleInvestments()
}
}, [searchParams, object, getPossibleInvestmentsCallback])

const PossibleInvestmentTickerSelection = possibleInvestments.reduce(
(obj, item) => {
obj[item.ticker] = item.ticker
return obj
},
{} as { [key: string]: string }
)

return (
<Dialog
onOpenChange={() => {
setSelectedTicker(' ')
}}
>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div>
<DialogTrigger asChild>
<Button variant="outline" size="icon" onClick={() => {}}>
<ArrowDownOnSquareIcon
className="h-6 w-6"
strokeWidth={1.2}
/>
</Button>
</DialogTrigger>
</div>
</TooltipTrigger>
<TooltipContent>
<p>Deposit</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<DialogContent>
<DialogHeader>
<DialogTitle>Invest on {object.name}</DialogTitle>
<DialogDescription>
Allocate money of your exchange account to the space.
</DialogDescription>
</DialogHeader>

<CustomForm<Operation>
inputs={[
{
label: 'Ticker',
key: 'ticker',
type: 'select',
possibilities: PossibleInvestmentTickerSelection,
zod: z.string(),
placeholder: 'Select a ticker',
setter: setSelectedTicker
},
{
label: 'Amount',
key: 'amount',
type: 'input',
zod: z.number(),
default: 0,
placeholder: 0
}
]}
onSubmit={async (values) => {
const newInvestment: Operation = {
ticker: selectedTicker,
amount: values.amount
}
try {
const response = await postInvestmentCallback(
searchParams,
object,
newInvestment
)
console.log('response::', response)
if (response.status === 200) {
// Trigger space refresh
setTriggerRefresh((prev) => !prev)
}

toast({
title: 'Investment',
description: 'You have invested money !'
})
} catch (error) {
console.error(error)
toast({
title: 'Investment',
description: 'You cannot invest money yet.',
variant: 'destructive'
})
}
document.getElementById('close-invest-button')?.click()
}}
buttonDescription="Done"
/>

<DialogDescription>
{possibleInvestments.find((inv) => inv.ticker === selectedTicker)
?.amount
? `max: ${possibleInvestments.find(
(inv) => inv.ticker === selectedTicker
)?.amount} ${selectedTicker}`
: ''}
</DialogDescription>
</DialogContent>
<DialogClose id="close-invest-button" />
</Dialog>
)
}

export function WithdrawMoneyActionButton<T extends ObjectWithName>({
object,
getPossibleWithdrawCallback,
postWithdrawCallback
}: {
object: T
getPossibleWithdrawCallback: getCallbackType<T>
postWithdrawCallback: postCallbackType<T>
}): JSX.Element {
return (
<Dialog>
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div>
<DialogTrigger asChild>
<Button variant="outline" size="icon" onClick={() => {}}>
<ArrowDownOnSquareIcon
className="h-6 w-6"
strokeWidth={1.2}
/>
</Button>
</DialogTrigger>
</div>
</TooltipTrigger>
<TooltipContent>
<p>Deposit</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
<DialogContent>
<DialogHeader>
<DialogTitle>Withdraw from {object.name}</DialogTitle>
<DialogDescription>
You deallocate money from the space. He won&apos;t be able to manage
it
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
)
}

export default function OperationMoneyActionButton<T extends ObjectWithName>({
object,
getPossibleInvestmentsCallback,
postInvestmentCallback,
getPossibleWithdrawCallback,
postWithdrawCallback
}: {
object: T
getPossibleInvestmentsCallback: getCallbackType<T>
postInvestmentCallback: postCallbackType<T>
getPossibleWithdrawCallback: getCallbackType<T>
postWithdrawCallback: postCallbackType<T>
}): JSX.Element {
return (
<div className="flex flex-row gap-4">
<InvestMoneyActionButton
object={object}
getPossibleInvestmentsCallback={getPossibleInvestmentsCallback}
postInvestmentCallback={postInvestmentCallback}
/>
<WithdrawMoneyActionButton
object={object}
getPossibleWithdrawCallback={getPossibleWithdrawCallback}
postWithdrawCallback={postWithdrawCallback}
/>
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ReactNode, createContext, useContext, useState } from 'react'

const OperationContext = createContext<{
triggerRefresh: boolean
setTriggerRefresh: React.Dispatch<React.SetStateAction<boolean>>
}>({
triggerRefresh: false,
setTriggerRefresh: () => {}
})

export const useOperationContext = () => useContext(OperationContext)

export const OperationProvider: React.FC<{ children: ReactNode }> = ({
children
}) => {
const [triggerRefresh, setTriggerRefresh] = useState(false)

return (
<OperationContext.Provider value={{ triggerRefresh, setTriggerRefresh }}>
{children}
</OperationContext.Provider>
)
}
Loading

0 comments on commit d983fb1

Please sign in to comment.