Skip to content

Commit

Permalink
Merge pull request #6 from klntsky/vishtar/features
Browse files Browse the repository at this point in the history
Rate notifications, postgresql in a docker container
  • Loading branch information
Vishtar authored Jul 8, 2024
2 parents 23719d1 + 6168f72 commit 17efcd0
Show file tree
Hide file tree
Showing 142 changed files with 18,761 additions and 5,076 deletions.
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,13 @@ EXPRESS_PORT=
TONAPI_TOKEN=
NOTIFICATION_RATE_UP=2
NOTIFICATION_RATE_DOWN=0.5
LIMIT_WALLETS_FOR_USER=10
SECONDS_FROM_PURCHASE_WITH_ROLLBACK_POSSIBILITY=60
# AMQP
AMQP_ENDPOINT=amqp://localhost:5673
# PostgreSQL
POSTGRES_HOST=127.0.0.1
POSTGRES_PORT=5432
POSTGRES_USER=username
POSTGRES_PASSWORD=password
POSTGRES_DB=default_database
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Ton 2x Bot

Twice as simple. Trading tools for everyone!
<p align="center">
<img alt="Ton 2x Bot" src="./frontend/public/logo.png" style='width: 160px;' />
<br></br>
Twice as simple. Trading tools for everyone!
</p>


## Description

Expand All @@ -21,13 +26,13 @@ It proactively notifies users of their gains via Telegram, making it easy to man

## Links

- [Bot](https://t.me/ton_2x_bot)
- [Telegram Bot](https://t.me/ton_2x_bot)
- [Announcements](https://t.me/ton_2x_en)
- [Announcements in Russian](https://t.me/ton_2x_ru)
- [DoraHack: Ton 2x Bot](https://dorahacks.io/buidl/13230)

## Tests

All tests are located in the [tests](./tests) directory. Currently, there is one unit test for the main business function:
All tests are located in the [tests](./tests) directory. Currently, there is one unit test (with 6 scenarios) for the main business function:

- [tests/getNotifications.test.ts](./tests/getNotifications.test.ts)
21 changes: 21 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
services:
postgres:
image: postgres:latest
env_file:
- .env
ports:
- 5432:5432
volumes:
- ./data/postgres:/var/lib/postgresql/data/

rabbitmq:
image: rabbitmq:3
hostname: rabbitmq
container_name: ton2x-rabbitmq
restart: unless-stopped
ports:
- 5673:5673
environment:
- RABBITMQ_NODE_PORT=5673
volumes:
- ./data/rabbitmq:/var/lib/rabbitmq/mnesia
8 changes: 7 additions & 1 deletion drizzle.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import process from 'process';
import 'dotenv/config'
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
schema: './src/db/schema/index.ts',
out: './src/db/migrations',
dialect: 'postgresql',
dbCredentials: {
url: 'file://data/postgresql',
host: process.env.POSTGRES_HOST,
port: Number(process.env.POSTGRES_PORT),
user: process.env.POSTGRES_USER,
password: process.env.POSTGRES_PASSWORD,
database: process.env.POSTGRES_DATABASE!,
},
});
Binary file added frontend/public/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added frontend/public/logo_80x80.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 41 additions & 14 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { useTonConnectModal, useTonConnectUI } from '@tonconnect/ui-react'
// TODO: replace '@tma.js/sdk-react' (deprecated) with '@telegram-apps/sdk-react'
import {
bindMiniAppCSSVars,
bindThemeParamsCSSVars,
useMiniApp,
useThemeParams,
useViewport,
} from '@tma.js/sdk-react'
import { retrieveLaunchParams } from '@tma.js/sdk'

import { Chart } from './components/Chart'
import { Charts, Button } from './components'
import { usePostData } from './hooks'
import { useTranslation } from 'react-i18next'
import { TGetWalletDataResponse } from './types'

export const App = () => {
const launchParams = retrieveLaunchParams()
Expand All @@ -20,8 +24,30 @@ export const App = () => {
const [tonConnectUI] = useTonConnectUI()
const themeParams = useThemeParams()
const miniApp = useMiniApp()
const miniAppViewport = useViewport()
const { mutate } = usePostData()
miniApp.ready()
const { t } = useTranslation()
const [walletsCount, setWalletsCount] = useState(0)

const onClickLinkAnothgerWalletButton = async () => {
if (tonConnectUI.connected) {
await tonConnectUI.disconnect()
}
modal.open()
}

const onUpdateChartsData = async (data: TGetWalletDataResponse) => {
setWalletsCount(data.walletsTotal)
if (data.walletsTotal === 0) {
modal.open()
}
}

useEffect(() => {
if (miniAppViewport) {
miniAppViewport.expand()
}
}, [miniAppViewport])

useEffect(() => {
return bindMiniAppCSSVars(miniApp, themeParams)
Expand All @@ -38,15 +64,11 @@ export const App = () => {
// }, [modal.state.status]);

useEffect(() => {
modal.open()
miniApp.ready()

tonConnectUI.onStatusChange(wallet => {
const launchParams = retrieveLaunchParams()
console.log(123, {
id: launchParams.initData?.user?.id,
address: wallet?.account.address,
})
if (!wallet?.account.address) return
const launchParams = retrieveLaunchParams()
mutate({
url: '/postUserWallet',
data: {
Expand All @@ -55,15 +77,20 @@ export const App = () => {
},
})
})
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

return (
<div className="App">
{tonConnectUI.account?.address ? (
<Chart
address={tonConnectUI.account.address}
userId={launchParams.initData.user.id}
/>
<Charts
walletsCount={walletsCount}
userId={launchParams.initData.user.id}
onUpdate={onUpdateChartsData}
/>
{walletsCount > 0 ? (
<Button onClick={onClickLinkAnothgerWalletButton}>
{t('button.linkAnotherWallet')}
</Button>
) : null}
</div>
)
Expand Down
11 changes: 11 additions & 0 deletions frontend/src/components/Button/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { ButtonHTMLAttributes } from 'react'
import s from './style.module.css'

export const Button = (props: ButtonHTMLAttributes<HTMLButtonElement>) => {
return (
<button
{...props}
className={[s.primaryButton, s.className].join(' ')}
></button>
)
}
17 changes: 17 additions & 0 deletions frontend/src/components/Button/style.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.primaryButton {
background-color: #0093ff;
border: none;
border-radius: 20px;
color: white;
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
font-weight: 520;
width: fit-content;
height: fit-content;
}

.primaryButton:hover {
background-color: #007acc;
}
Original file line number Diff line number Diff line change
@@ -1,37 +1,72 @@
import { AreaChart, Badge, BadgeDelta, Card, Flex } from '@tremor/react'
import { badgeType, chartColor, formatDataToChart } from '../utils'
import { useFetchRates } from '../hooks/useFetchRates'
import { Loader } from './Loader'
import { badgeType, chartColor, formatDataToChart } from '../../utils'
import { useFetchRates } from '../../hooks/useFetchRates'
import { Loader } from '..'
import { useTranslation } from 'react-i18next'
import s from './style.module.css'
import { useEffect, useRef } from 'react'
import { TGetWalletDataResponse } from '../../types'

export const Chart = (props: { address: string; userId: number }) => {
export const Charts = (props: {
walletsCount: number
userId: number
onUpdate?: (data: TGetWalletDataResponse) => void
}) => {
const { t } = useTranslation()
const { data, isLoading } = useFetchRates(props)
const walletsCountRef = useRef(0)
const { data, isLoading } = useFetchRates({
walletsCount: 0,
userId: props.userId,
})

useEffect(() => {
if (
walletsCountRef.current !== 0 ||
(walletsCountRef.current === 0 && props.walletsCount !== 0)
) {
walletsCountRef.current = props.walletsCount
}
}, [props.walletsCount])

useEffect(() => {
if (props.onUpdate && data) {
props.onUpdate(data)
}
}, [data])

if (isLoading) {
return (
<Flex justifyContent="center" alignItems="center" className="h-screen">
<Flex justifyContent="center" alignItems="center" className="h-dvh">
<Loader />
</Flex>
)
}
if (!data?.length) {

if (!data) {
return (
<Flex justifyContent="center" alignItems="center">
<h2 className="text-2xl">{t('label.error')}</h2>
</Flex>
)
}

if (!data.jettons.length) {
return (
<Flex justifyContent="center" alignItems="center" className="h-screen">
<h2 className="text-2xl text-slate-600">{t('label.noJettons')}</h2>
<Flex justifyContent="center" alignItems="center">
<h2 className="text-2xl">{t('label.noJettons')}</h2>
</Flex>
)
}

if (data) {
return (
<div>
<h1 className="w-full text-3xl text-slate-700 text-center">
<div className={s.charts}>
<h1 className={`${s.yourJettonsHeader} w-full text-3xl text-center`}>
{t('label.yourJettons')}
</h1>
{data
? data.map(obj => (
<Card className="max-w-[550px] mt-4">
? data.jettons.map(obj => (
<Card className="max-w-[550px] mt-4" key={obj.address}>
<Flex justifyContent="between" alignItems="center">
<Flex
justifyContent="start"
Expand All @@ -45,13 +80,13 @@ export const Chart = (props: { address: string; userId: number }) => {
className="shadow rounded-full max-w-full h-auto align-middle border-none"
/>
</div>
<h1 className="text-2xl text-slate-500 pl-2">
<h1 className={`${s.symbolHeader} text-2xl pl-2`}>
{obj.symbol}
</h1>
</Flex>
{obj.pnlPercentage !== 0 ? (
<div className="flex">
<h3 className="text-xl text-slate-500 pr-2">{}</h3>
<h3 className="text-xl pr-2">{}</h3>
<BadgeDelta
size="lg"
deltaType={badgeType(obj.pnlPercentage)}
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/components/Charts/style.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.charts {
width: 100%;
}
6 changes: 5 additions & 1 deletion frontend/src/components/Loader/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import s from './styles.module.css'

export const Loader = () => {
return <div className={s.loader}></div>
return (
<div className={s.loader}>
<img src={'./logo_80x80.png'} alt="Logo" className={s.logo} />
</div>
)
}
37 changes: 20 additions & 17 deletions frontend/src/components/Loader/styles.module.css
Original file line number Diff line number Diff line change
@@ -1,22 +1,25 @@
.loader {
width: 45px;
aspect-ratio: 0.75;
--c: no-repeat linear-gradient(#2c2594 0 0);
background: var(--c) 0% 50%, var(--c) 50% 50%, var(--c) 100% 50%;
background-size: 20% 50%;
animation: l6 1s infinite linear;
display: flex;
align-items: center;
justify-content: center;
width: 80px;
overflow: hidden;
position: relative;
}
@keyframes l6 {
20% {
background-position: 0% 0%, 50% 50%, 100% 50%;
}
40% {
background-position: 0% 100%, 50% 0%, 100% 50%;
}
60% {
background-position: 0% 50%, 50% 100%, 100% 0%;

.logo {
width: 100%;
height: auto;
animation: pulse 1.5s infinite ease-in-out;
}

@keyframes pulse {
0%, 100% {
opacity: 1;
transform: scale(0.9);
}
80% {
background-position: 0% 50%, 50% 50%, 100% 100%;
50% {
opacity: 0.5;
transform: scale(1);
}
}
3 changes: 3 additions & 0 deletions frontend/src/components/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { Button } from './Button'
export { Charts } from './Charts'
export { Loader } from './Loader'
Loading

0 comments on commit 17efcd0

Please sign in to comment.