diff --git a/.github/workflows/dev.yml b/.github/workflows/dev.yml index 46238f9f..890d2c0c 100644 --- a/.github/workflows/dev.yml +++ b/.github/workflows/dev.yml @@ -27,6 +27,8 @@ jobs: --build-arg NEXT_PUBLIC_PROCESSOR_ENABLE_TRPC=true --build-arg NEXT_PUBLIC_STATIC=true --build-arg ADMIN_PASSWORD=password + --build-arg GITHUB_TOKEN=${{ secrets.PRO_GITHUB_TOKEN }} + -f pro.Dockerfile -t opentrader/opentrader:latest . - name: Push Docker Image diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..92908991 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "pro"] + path = pro + url = git@github.com:bludnic/opentrader-pro.git diff --git a/.npmrc b/.npmrc index 330b9103..ced46397 100644 --- a/.npmrc +++ b/.npmrc @@ -1,2 +1,3 @@ public-hoist-pattern[]=*prisma* auto-install-peers=true +lockfile=false diff --git a/Dockerfile b/Dockerfile index 57cabb1c..57047a86 100644 --- a/Dockerfile +++ b/Dockerfile @@ -73,8 +73,8 @@ RUN corepack enable WORKDIR /app -COPY --from=installer /app/apps/frontend/out ./apps/frontend/out -COPY --from=installer /app/apps/processor ./apps/processor +COPY --from=installer /app/pro/frontend/out ./pro/frontend/out +COPY --from=installer /app/pro/processor ./pro/processor COPY --from=installer /app/package.json ./package.json COPY --from=installer /app/pnpm-lock.yaml ./pnpm-lock.yaml COPY --from=installer /app/pnpm-workspace.yaml ./pnpm-workspace.yaml @@ -93,9 +93,9 @@ RUN addgroup --system --gid 1001 expressjs RUN adduser --system --uid 1001 expressjs USER expressjs -COPY --from=optimizer /app/apps/frontend/out ./apps/frontend/out -COPY --from=optimizer /app/apps/processor ./apps/processor +COPY --from=optimizer /app/pro/frontend/out ./pro/frontend/out +COPY --from=optimizer /app/pro/processor ./pro/processor COPY --from=optimizer /app/node_modules ./node_modules -WORKDIR /app/apps/processor +WORKDIR /app/pro/processor CMD node dist/main.js diff --git a/apps/frontend/.env b/apps/frontend/.env deleted file mode 120000 index c7360fb8..00000000 --- a/apps/frontend/.env +++ /dev/null @@ -1 +0,0 @@ -../../.env \ No newline at end of file diff --git a/apps/frontend/.eslintrc.js b/apps/frontend/.eslintrc.js deleted file mode 100644 index 0eec699b..00000000 --- a/apps/frontend/.eslintrc.js +++ /dev/null @@ -1,5 +0,0 @@ -/** @type {import("eslint").Linter.Config} */ -module.exports = { - root: true, - extends: ["@opentrader/eslint-config/next.js"], -}; diff --git a/apps/frontend/.gitignore b/apps/frontend/.gitignore deleted file mode 100644 index 050841de..00000000 --- a/apps/frontend/.gitignore +++ /dev/null @@ -1,40 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# next.js -/.next/ -/out/ - -# production -/build - -# misc -.DS_Store -*.pem - -# debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# local env files -.env.local -.env.development.local -.env.test.local -.env.production.local - -# vercel -.vercel - -# typescript -*.tsbuildinfo - -# coingecko -src/lib/coingecko/client diff --git a/apps/frontend/Dockerfile b/apps/frontend/Dockerfile deleted file mode 100644 index 763e7c24..00000000 --- a/apps/frontend/Dockerfile +++ /dev/null @@ -1,80 +0,0 @@ -FROM node:18-alpine AS base - -# The web Dockerfile is copy-pasted into our main docs at /docs/handbook/deploying-with-docker. -# Make sure you update this Dockerfile, the Dockerfile in the web workspace and copy that over to Dockerfile in the docs. - -FROM base AS builder -# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. -RUN apk add --no-cache libc6-compat -RUN apk update -# Install pnpm -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -RUN corepack enable -# Set working directory -WORKDIR /app -RUN pnpm add turbo -g -COPY . . -RUN turbo prune frontend --docker - -# Add lockfile and package.json's of isolated subworkspace -FROM base AS installer -RUN apk add --no-cache libc6-compat -RUN apk update -WORKDIR /app - -# Install pnpm & turbo -ENV PNPM_HOME="/pnpm" -ENV PATH="$PNPM_HOME:$PATH" -RUN corepack enable -RUN pnpm add turbo -g - -# First install dependencies (as they change less often) -COPY .gitignore .gitignore -COPY --from=builder /app/out/json/ . -# Copy Prisma Schema as it is not included in `/json` dir -COPY --from=builder /app/out/full/packages/prisma/src/schema.prisma ./packages/prisma/src/schema.prisma -RUN --mount=type=cache,id=pnpm,target=/root/.local/share/pnpm/store pnpm fetch -RUN pnpm install --offline - -# Build the project and its dependencies -COPY --from=builder /app/out/full/ . -COPY turbo.json turbo.json - -# Uncomment and use build args to enable remote caching -# ARG TURBO_TEAM -# ENV TURBO_TEAM=$TURBO_TEAM - -# ARG TURBO_TOKEN -# ENV TURBO_TOKEN=$TURBO_TOKEN - -ARG DATABASE_URL -ENV DATABASE_URL=$DATABASE_URL - -ARG NEXT_PUBLIC_PROCESSOR_URL -ENV NEXT_PUBLIC_PROCESSOR_URL=$NEXT_PUBLIC_PROCESSOR_URL - -ARG NEXT_PUBLIC_PROCESSOR_ENABLE_TRPC -ENV NEXT_PUBLIC_PROCESSOR_ENABLE_TRPC=$NEXT_PUBLIC_PROCESSOR_ENABLE_TRPC - -RUN turbo run prisma:deploy --filter=./packages/prisma -RUN turbo run build --filter=frontend - -FROM base AS runner -WORKDIR /app - -# Don't run production as root -RUN addgroup --system --gid 1001 nodejs -RUN adduser --system --uid 1001 nextjs -USER nextjs - -COPY --from=installer /app/apps/frontend/next.config.js . -COPY --from=installer /app/apps/frontend/package.json . - -# Automatically leverage output traces to reduce image size -# https://nextjs.org/docs/advanced-features/output-file-tracing -COPY --from=installer --chown=nextjs:nodejs /app/apps/frontend/.next/standalone ./ -COPY --from=installer --chown=nextjs:nodejs /app/apps/frontend/.next/static ./apps/frontend/.next/static -COPY --from=installer --chown=nextjs:nodejs /app/apps/frontend/public ./apps/frontend/public - -CMD node apps/frontend/server.js diff --git a/apps/frontend/README.md b/apps/frontend/README.md deleted file mode 100644 index 91909466..00000000 --- a/apps/frontend/README.md +++ /dev/null @@ -1,21 +0,0 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -$ cp .env.sample .env -$ npm run bootstrap - -$ npm run dev -``` - -## Coingecko Developer API - -- Swagger: https://www.coingecko.com/en/api/documentation -- Unofficial Swagger (better types): https://app.swaggerhub.com/apis/starksm64/CoinGecko/3.0 - -## CoinMarketCap Developer API - -- Swagger: https://pro-api.coinmarketcap.com/swagger.json diff --git a/apps/frontend/jest.config.js b/apps/frontend/jest.config.js deleted file mode 100644 index 85467739..00000000 --- a/apps/frontend/jest.config.js +++ /dev/null @@ -1,29 +0,0 @@ -// jest.config.js -const nextJest = require('next/jest') - -const createJestConfig = nextJest({ - // Provide the path to your Next.js app to load next.config.js and .env files in your test environment - dir: './', -}) - -// Add any custom config to be passed to Jest -/** @type {import('jest').Config} */ -const customJestConfig = { - // Add more setup options before each test is run - // setupFilesAfterEnv: ['/jest.setup.js'], - // if using TypeScript with a baseUrl set to the root directory then you need the below for alias' to work - moduleDirectories: ['node_modules', '/'], - - // If you're using [Module Path Aliases](https://nextjs.org/docs/advanced-features/module-path-aliases), - // you will have to add the moduleNameMapper in order for jest to resolve your absolute paths. - // The paths have to be matching with the paths option within the compilerOptions in the tsconfig.json - // For example: - - moduleNameMapper: { - '@/(.*)$': '/src/$1', - }, - testEnvironment: 'jest-environment-jsdom', -} - -// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async -module.exports = createJestConfig(customJestConfig) diff --git a/apps/frontend/next-env.d.ts b/apps/frontend/next-env.d.ts deleted file mode 100644 index 4f11a03d..00000000 --- a/apps/frontend/next-env.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -/// -/// - -// NOTE: This file should not be edited -// see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/frontend/next.config.js b/apps/frontend/next.config.js deleted file mode 100644 index a88b100e..00000000 --- a/apps/frontend/next.config.js +++ /dev/null @@ -1,30 +0,0 @@ -const { z } = require("zod"); - -const envValidationSchema = z.object({ - NEXT_PUBLIC_PROCESSOR_URL: z.string().optional(), - NEXT_PUBLIC_PROCESSOR_ENABLE_TRPC: z.enum(["true", ""]).optional(), - NEXT_PUBLIC_STATIC: z.enum(["true", ""]).optional(), - DATABASE_URL: z.string().min(1), - ADMIN_PASSWORD: z.string().min(1), -}); -envValidationSchema.parse(process.env); // validate ENV schema - -/** @type {import('next').NextConfig} */ -module.exports = { - output: process.env.NEXT_PUBLIC_STATIC === "true" ? "export" : "standalone", - reactStrictMode: true, - webpack: (config) => { - // Solves: Module not found: `bufferutil` and `utf-8-validate` - // when importing `ccxt` in a Server Component (#57) - config.externals.push({ - "utf-8-validate": "commonjs utf-8-validate", - bufferutil: "commonjs bufferutil", - }); - - return config; - }, - experimental: { - optimizePackageImports: ["@mui/base", "@mui/joy"], - missingSuspenseWithCSRBailout: false, // error when building app: https://nextjs.org/docs/messages/missing-suspense-with-csr-bailout - }, -}; diff --git a/apps/frontend/package.json b/apps/frontend/package.json deleted file mode 100644 index 3430eea8..00000000 --- a/apps/frontend/package.json +++ /dev/null @@ -1,78 +0,0 @@ -{ - "name": "frontend", - "version": "0.0.1", - "scripts": { - "dev": "next dev", - "build": "next build", - "build:static": "NEXT_PUBLIC_STATIC=true next build", - "start": "next start", - "start:prod": "next start", - "lint": "eslint . --quiet", - "lint:fix": "eslint . --fix", - "test:unit": "jest" - }, - "dependencies": { - "@emotion/cache": "^11.11.0", - "@emotion/react": "^11.11.4", - "@emotion/server": "^11.11.0", - "@emotion/styled": "^11.11.5", - "@mui/base": "5.0.0-beta.40", - "@mui/icons-material": "^5.15.17", - "@mui/joy": "5.0.0-beta.36", - "@opentrader/exchanges": "workspace:*", - "@opentrader/tools": "workspace:*", - "@opentrader/trpc": "workspace:*", - "@reduxjs/toolkit": "^1.9.7", - "@tanstack/react-query": "^4.36.1", - "@trpc/client": "^10.45.2", - "@trpc/react-query": "^10.45.2", - "@trpc/server": "^10.45.2", - "axios": "^1.6.8", - "big.js": "^6.2.1", - "ccxt": "4.3.27", - "clsx": "^2.1.1", - "date-fns": "^2.30.0", - "final-form": "^4.20.10", - "lightweight-charts": "^4.1.4", - "lodash": "^4.17.21", - "next": "14.2.3", - "react": "18.3.1", - "react-dom": "18.3.1", - "react-final-form": "^6.5.9", - "react-number-format": "^5.3.4", - "react-redux": "^8.1.3", - "react-window": "^1.8.10", - "redux": "^4.2.1", - "server-only": "^0.0.1", - "superjson": "^1.13.3", - "usehooks-ts": "^2.16.0", - "zod": "3.22.4" - }, - "devDependencies": { - "@next/eslint-plugin-next": "^14.2.3", - "@opentrader/eslint-config": "workspace:*", - "@opentrader/tsconfig": "workspace:*", - "@opentrader/types": "workspace:*", - "@types/big.js": "^6.2.2", - "@types/eslint": "^8.56.10", - "@types/jest": "^29.5.12", - "@types/lodash": "^4.17.1", - "@types/node": "^20.12.11", - "@types/react": "18.3.1", - "@types/react-window": "^1.8.8", - "eslint": "8.54.0", - "jest": "^29.7.0", - "jest-environment-jsdom": "^29.7.0", - "typescript": "^5.2.2" - }, - "husky": { - "hooks": { - "pre-commit": "lint-staged" - } - }, - "lint-staged": { - "*.{js,ts,tsx}": [ - "eslint --fix" - ] - } -} diff --git a/apps/frontend/public/exchanges/logos/64x64/binance.png b/apps/frontend/public/exchanges/logos/64x64/binance.png deleted file mode 100644 index f606e247..00000000 Binary files a/apps/frontend/public/exchanges/logos/64x64/binance.png and /dev/null differ diff --git a/apps/frontend/public/exchanges/logos/64x64/bybit.png b/apps/frontend/public/exchanges/logos/64x64/bybit.png deleted file mode 100644 index e6a8e8ca..00000000 Binary files a/apps/frontend/public/exchanges/logos/64x64/bybit.png and /dev/null differ diff --git a/apps/frontend/public/exchanges/logos/64x64/coinbase.png b/apps/frontend/public/exchanges/logos/64x64/coinbase.png deleted file mode 100644 index 1d5f59cf..00000000 Binary files a/apps/frontend/public/exchanges/logos/64x64/coinbase.png and /dev/null differ diff --git a/apps/frontend/public/exchanges/logos/64x64/gateio.png b/apps/frontend/public/exchanges/logos/64x64/gateio.png deleted file mode 100644 index 103dc146..00000000 Binary files a/apps/frontend/public/exchanges/logos/64x64/gateio.png and /dev/null differ diff --git a/apps/frontend/public/exchanges/logos/64x64/kraken.png b/apps/frontend/public/exchanges/logos/64x64/kraken.png deleted file mode 100644 index b33f6caa..00000000 Binary files a/apps/frontend/public/exchanges/logos/64x64/kraken.png and /dev/null differ diff --git a/apps/frontend/public/exchanges/logos/64x64/okx.png b/apps/frontend/public/exchanges/logos/64x64/okx.png deleted file mode 100644 index 39203506..00000000 Binary files a/apps/frontend/public/exchanges/logos/64x64/okx.png and /dev/null differ diff --git a/apps/frontend/public/favicon.ico b/apps/frontend/public/favicon.ico deleted file mode 100644 index e0925e7f..00000000 Binary files a/apps/frontend/public/favicon.ico and /dev/null differ diff --git a/apps/frontend/public/logo.png b/apps/frontend/public/logo.png deleted file mode 100644 index a48d0446..00000000 Binary files a/apps/frontend/public/logo.png and /dev/null differ diff --git a/apps/frontend/public/robots.txt b/apps/frontend/public/robots.txt deleted file mode 100644 index 1f53798b..00000000 --- a/apps/frontend/public/robots.txt +++ /dev/null @@ -1,2 +0,0 @@ -User-agent: * -Disallow: / diff --git a/apps/frontend/src/app/api/trpc/[trpc]/route.ts b/apps/frontend/src/app/api/trpc/[trpc]/route.ts deleted file mode 100644 index 76e6033c..00000000 --- a/apps/frontend/src/app/api/trpc/[trpc]/route.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { appRouter } from "@opentrader/trpc"; -import { fetchRequestHandler } from "@trpc/server/adapters/fetch"; -import { cache } from "@opentrader/exchanges"; -import { PrismaCacheProvider } from "@opentrader/exchanges/server"; -import { headers } from "next/headers"; - -cache.setCacheProvider(new PrismaCacheProvider()); - -const FRONTEND_ENABLE_TRPC = !process.env.NEXT_PUBLIC_PROCESSOR_ENABLE_TRPC; - -const handler = (req: Request) => - fetchRequestHandler({ - endpoint: "/api/trpc", - req, - router: appRouter, - createContext: () => { - const password = headers().get("Authorization"); - - if (password === process.env.ADMIN_PASSWORD) { - return { - user: { - id: 1, - password: "huitebe", - email: "nu@nahui", - displayName: "Hui tebe", - role: "Admin" as const, - }, - }; - } - - return { - user: null, - }; - }, - }); - -export const GET = FRONTEND_ENABLE_TRPC ? handler : undefined; -export const POST = FRONTEND_ENABLE_TRPC ? handler : undefined; - -export const generateStaticParams = - process.env.NEXT_PUBLIC_STATIC === "true" - ? () => { - // Workaround: - // Next.js throws an error when building a static app and empty array is returned. - // Error: Page "/api/trpc/[trpc]" is missing "generateStaticParams()". - return [ - { - trpc: "_", - }, - ]; - } - : undefined; - -// export { handler as GET, handler as POST }; diff --git a/apps/frontend/src/app/dashboard/accounts/page.tsx b/apps/frontend/src/app/dashboard/accounts/page.tsx deleted file mode 100644 index 19793e56..00000000 --- a/apps/frontend/src/app/dashboard/accounts/page.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import dynamic from "next/dynamic"; -import React from "react"; -import Loading from "src/components/accounts/loading"; - -const AccountsPage = dynamic(() => import("src/components/accounts/page"), { - loading: Loading, - ssr: false, -}); - -export default function Page() { - return ; -} diff --git a/apps/frontend/src/app/dashboard/bot/[id]/page.tsx b/apps/frontend/src/app/dashboard/bot/[id]/page.tsx deleted file mode 100644 index 57c6675a..00000000 --- a/apps/frontend/src/app/dashboard/bot/[id]/page.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import dynamic from "next/dynamic"; -import React from "react"; -import BotDetailsLoading from "src/components/bot/bot-details/loading"; -import { toPage } from "src/utils/next/toPage"; - -const BotDetailsPage = dynamic( - () => import("src/components/bot/bot-details/page"), - { - loading: BotDetailsLoading, - ssr: false, - }, -); - -type Props = { - params: { - id: string; - }; -}; - -export default function Page({ params }: Props) { - const botId = Number(params.id); - - if (process.env.NEXT_PUBLIC_STATIC === "true") { - return ( -
- Page is disabled due to the static export. Go to{" "} - {toPage("bot/:id", botId)} instead -
- ); - } - - return ; -} - -// Workaround: -// Next.js throws an error when building a static app and empty array is returned. -// Error: Page "/api/trpc/[trpc]" is missing "generateStaticParams()" -export const generateStaticParams = () => [{ id: "_" }]; diff --git a/apps/frontend/src/app/dashboard/bot/info/page.tsx b/apps/frontend/src/app/dashboard/bot/info/page.tsx deleted file mode 100644 index 0102f015..00000000 --- a/apps/frontend/src/app/dashboard/bot/info/page.tsx +++ /dev/null @@ -1,23 +0,0 @@ -"use client"; - -import lazy from "next/dynamic"; -import { useSearchParams } from "next/navigation"; -import React from "react"; -import BotDetailsLoading from "src/components/bot/bot-details/loading"; - -const BotDetailsPage = lazy( - () => import("src/components/bot/bot-details/page"), - { - loading: BotDetailsLoading, - ssr: false, - }, -); - -// This page is used only when building a static app (`export`) -// It is a replacement of dynamic path `/bot/[id]/page.tsx` -export default function Page() { - const params = useSearchParams(); - const botId = Number(params.get("id")); - - return ; -} diff --git a/apps/frontend/src/app/dashboard/grid-bot/[id]/page.tsx b/apps/frontend/src/app/dashboard/grid-bot/[id]/page.tsx deleted file mode 100644 index 508c93ab..00000000 --- a/apps/frontend/src/app/dashboard/grid-bot/[id]/page.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import dynamic from "next/dynamic"; -import React from "react"; -import BotDetailsLoading from "src/components/grid-bot/bot-details/loading"; -import { toPage } from "src/utils/next/toPage"; - -const BotDetailsPage = dynamic( - () => import("src/components/grid-bot/bot-details/page"), - { - loading: BotDetailsLoading, - ssr: false, - }, -); - -type Props = { - params: { - id: string; - }; -}; - -export default function Page({ params }: Props) { - const botId = Number(params.id); - - if (process.env.NEXT_PUBLIC_STATIC === "true") { - return ( -
- Page is disabled due to the static export. Go to{" "} - {toPage("grid-bot/:id", botId)} instead -
- ); - } - - return ; -} - -// Workaround: -// Next.js throws an error when building a static app and empty array is returned. -// Error: Page "/api/trpc/[trpc]" is missing "generateStaticParams()" -export const generateStaticParams = () => [{ id: "_" }]; diff --git a/apps/frontend/src/app/dashboard/grid-bot/create/page.tsx b/apps/frontend/src/app/dashboard/grid-bot/create/page.tsx deleted file mode 100644 index 00ef11e5..00000000 --- a/apps/frontend/src/app/dashboard/grid-bot/create/page.tsx +++ /dev/null @@ -1,16 +0,0 @@ -"use client"; - -import dynamic from "next/dynamic"; -import CreatBotLoading from "src/components/grid-bot/create-bot/loading"; - -const CreateBotPage = dynamic( - () => import("src/components/grid-bot/create-bot/page"), - { - loading: CreatBotLoading, - ssr: false, - }, -); - -export default function Page() { - return ; -} diff --git a/apps/frontend/src/app/dashboard/grid-bot/info/page.tsx b/apps/frontend/src/app/dashboard/grid-bot/info/page.tsx deleted file mode 100644 index 27948432..00000000 --- a/apps/frontend/src/app/dashboard/grid-bot/info/page.tsx +++ /dev/null @@ -1,23 +0,0 @@ -"use client"; - -import lazy from "next/dynamic"; -import { useSearchParams } from "next/navigation"; -import React from "react"; -import BotDetailsLoading from "src/components/grid-bot/bot-details/loading"; - -const BotDetailsPage = lazy( - () => import("src/components/grid-bot/bot-details/page"), - { - loading: BotDetailsLoading, - ssr: false, - }, -); - -// This page is used only when building a static app (`export`) -// It is a replacement of dynamic path `/grid-bot/[id]/page.tsx` -export default function Page() { - const params = useSearchParams(); - const botId = Number(params.get("id")); - - return ; -} diff --git a/apps/frontend/src/app/dashboard/grid-bot/page.tsx b/apps/frontend/src/app/dashboard/grid-bot/page.tsx deleted file mode 100644 index f465f4d1..00000000 --- a/apps/frontend/src/app/dashboard/grid-bot/page.tsx +++ /dev/null @@ -1,17 +0,0 @@ -"use client"; - -import dynamic from "next/dynamic"; -import React from "react"; -import BotListLoading from "src/components/grid-bot/bots-list/loading"; - -const BotListPage = dynamic( - () => import("src/components/grid-bot/bots-list/page"), - { - loading: BotListLoading, - ssr: false, - }, -); - -export default function Page() { - return ; -} diff --git a/apps/frontend/src/app/dashboard/layout.tsx b/apps/frontend/src/app/dashboard/layout.tsx deleted file mode 100644 index a514e1f3..00000000 --- a/apps/frontend/src/app/dashboard/layout.tsx +++ /dev/null @@ -1,65 +0,0 @@ -"use client"; - -import type { ReactNode } from "react"; -import { useState } from "react"; -import { ThemeSwitcher } from "src/ui/navigation/ThemeSwitcher"; -import { AppBar } from "src/ui/navigation/AppBar"; -import { AppVersionInfo } from "src/ui/navigation/AppVersionInfo"; -import { FlexSpacer } from "src/ui/FlexSpacer"; -import { LeftNavigation } from "src/ui/navigation/LeftNavigation"; -import { Logo } from "src/ui/Logo"; -import { Main } from "src/ui/Main"; -import { TopNavigation } from "src/ui/navigation/TopNavigation"; -import { AppDrawer } from "src/ui/navigation/AppDrawer"; -import { MobileDrawer } from "src/ui/navigation/MobileDrawer"; -import { DrawerToggler } from "src/ui/navigation/DrawerToggler"; - -type Props = { - children: ReactNode; -}; - -export default function DashboardLayout({ children }: Props) { - const [drawerOpen, setDrawerOpen] = useState(false); - - const size = drawerOpen ? "md" : "sm"; - - return ( -
- - { - setDrawerOpen(!drawerOpen); - }} - open={drawerOpen} - /> - - - - - - - - - - - - - - - { - setDrawerOpen(false); - }} - open={drawerOpen} - > - - - - - - - -
{children}
-
- ); -} diff --git a/apps/frontend/src/app/dashboard/login/page.tsx b/apps/frontend/src/app/dashboard/login/page.tsx deleted file mode 100644 index 1d2fd80f..00000000 --- a/apps/frontend/src/app/dashboard/login/page.tsx +++ /dev/null @@ -1,5 +0,0 @@ -import { LoginForm } from "src/components/login/LoginForm"; - -export default async function Page() { - return ; -} diff --git a/apps/frontend/src/app/dashboard/page.tsx b/apps/frontend/src/app/dashboard/page.tsx deleted file mode 100644 index ee2ed23b..00000000 --- a/apps/frontend/src/app/dashboard/page.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import { LoginForm } from "src/components/login/LoginForm"; - -export default async function Page() { - return ( -
- -
- ); -} diff --git a/apps/frontend/src/app/layout.tsx b/apps/frontend/src/app/layout.tsx deleted file mode 100644 index a0100fbf..00000000 --- a/apps/frontend/src/app/layout.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { cache } from "@opentrader/exchanges"; -import { PrismaCacheProvider } from "@opentrader/exchanges/server"; -import { ThemeProvider } from "src/providers/ThemeProvider"; -import { StoreProvider } from "src/providers/StoreProvider"; -import { TrpcProvider } from "src/providers/TrpcProvider"; -import { TRPCApiErrorProvider } from "src/ui/errors/api"; -import { SnackbarProvider } from "src/ui/snackbar"; -import { ConfirmationDialogProvider } from "src/ui/confirmation-dialog"; - -cache.setCacheProvider(new PrismaCacheProvider()); - -export const metadata = { - title: "Opentrader", - description: "Opensource Trading Bots", -}; - -export default function RootLayout({ - children, -}: { - children: React.ReactNode; -}) { - return ( - - - - - - - - {children} - - - - - - - - ); -} diff --git a/apps/frontend/src/app/page.tsx b/apps/frontend/src/app/page.tsx deleted file mode 100644 index aa2ab86f..00000000 --- a/apps/frontend/src/app/page.tsx +++ /dev/null @@ -1,6 +0,0 @@ -import { redirect } from "next/navigation"; -import { toPage } from "src/utils/next/toPage"; - -export default async function Page() { - redirect(toPage("accounts")); -} diff --git a/apps/frontend/src/components/accounts/AccountsListTable/AccountsListTable.tsx b/apps/frontend/src/components/accounts/AccountsListTable/AccountsListTable.tsx deleted file mode 100644 index fecc66de..00000000 --- a/apps/frontend/src/components/accounts/AccountsListTable/AccountsListTable.tsx +++ /dev/null @@ -1,121 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import React, { useState } from "react"; -import Table from "@mui/joy/Table"; -import Sheet from "@mui/joy/Sheet"; -import type { TExchangeAccount } from "src/types/trpc"; -import { AccountsListTableRow } from "./AccountsListTableRow"; -import { AccountsListTableToolbar } from "./AccountsListTableToolbar"; -import { AccountsListTableHead } from "./AccountsListTableHead"; - -type AccountsListTableProps = { - accounts: TExchangeAccount[]; - onCreateAccountClick: () => void; - onEditAccountClick: (account: TExchangeAccount) => void; -}; - -export const AccountsListTable: FC = (props) => { - const { accounts, onCreateAccountClick, onEditAccountClick } = props; - - const [selected, setSelected] = useState([]); - - const isSelected = (accountId: number) => selected.includes(accountId); - - const handleClick = (event: React.MouseEvent, accountId: number) => { - const selectedIndex = selected.indexOf(accountId); - let newSelected: readonly number[] = []; - - if (selectedIndex === -1) { - newSelected = newSelected.concat(selected, accountId); - } else if (selectedIndex === 0) { - newSelected = newSelected.concat(selected.slice(1)); - } else if (selectedIndex === selected.length - 1) { - newSelected = newSelected.concat(selected.slice(0, -1)); - } else if (selectedIndex > 0) { - newSelected = newSelected.concat( - selected.slice(0, selectedIndex), - selected.slice(selectedIndex + 1), - ); - } - - setSelected(newSelected); - }; - - const handleSelectAllClick = (event: React.ChangeEvent) => { - if (event.target.checked) { - const newSelected = accounts.map((account) => account.id); - setSelected(newSelected); - return; - } - setSelected([]); - }; - - return ( - - { - const account = accounts.find( - (account) => account.id === selected[0], - ); - - if (account) { - onEditAccountClick(account); - } else { - console.log(`AccountsListTable: Account ${selected[0]} not found`); - } - }} - title="Exchange Accounts" - /> - - - theme.vars.palette.success.softBg, - "& thead th:nth-child(1)": { - width: "40px", - }, - "& thead th:nth-child(2)": { - width: "40px", - }, - "& thead th:nth-child(3)": { - width: "360px", - }, - "& thead th:nth-child(4)": { - // width: "100%", - }, - "& tr > *:nth-child(n+5)": { textAlign: "right" }, - }} - > - - - - {accounts.map((account) => { - const isItemSelected = isSelected(account.id); - - return ( - { - handleClick(e, accountId); - }} - selected={isItemSelected} - /> - ); - })} - -
-
- ); -}; diff --git a/apps/frontend/src/components/accounts/AccountsListTable/AccountsListTableHead.tsx b/apps/frontend/src/components/accounts/AccountsListTable/AccountsListTableHead.tsx deleted file mode 100644 index 92b92f7e..00000000 --- a/apps/frontend/src/components/accounts/AccountsListTable/AccountsListTableHead.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import type { FC } from "react"; -import React from "react"; -import Checkbox from "@mui/joy/Checkbox"; - -type AccountsListTableHeadProps = { - numSelected: number; - rowCount: number; - onSelectAllClick: (e: React.ChangeEvent) => void; -}; - -export const AccountsListTableHead: FC = ( - props, -) => { - const { onSelectAllClick, numSelected, rowCount } = props; - - return ( - - - - 0 && numSelected === rowCount} - color="primary" - indeterminate={numSelected > 0 && numSelected < rowCount} - onChange={onSelectAllClick} - slotProps={{ - input: { - "aria-label": "select all accounts", - }, - }} - sx={{ verticalAlign: "sub" }} - /> - - - ID - - Exchange - - Credentials - - Created - - - ); -}; diff --git a/apps/frontend/src/components/accounts/AccountsListTable/AccountsListTableRow.tsx b/apps/frontend/src/components/accounts/AccountsListTable/AccountsListTableRow.tsx deleted file mode 100644 index a0a02b7e..00000000 --- a/apps/frontend/src/components/accounts/AccountsListTable/AccountsListTableRow.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import Typography from "@mui/joy/Typography"; -import type { FC } from "react"; -import React from "react"; -import Checkbox from "@mui/joy/Checkbox"; -import Chip from "@mui/joy/Chip"; -import Box from "@mui/joy/Box"; -import Tooltip from "@mui/joy/Tooltip"; -import type { TExchangeAccount } from "src/types/trpc"; -import { ExchangeIcon } from "src/ui/icons/ExchangeIcon"; -import { formatDateTime } from "src/utils/date/formatDateTime"; - -type AccountsListTableRowProps = { - account: TExchangeAccount; - selected: boolean; - onClick: ( - e: React.MouseEvent, - accountId: number, - ) => void; -}; - -export const AccountsListTableRow: FC = (props) => { - const { account, selected, onClick } = props; - - return ( - - { - onClick(e, account.id); - }} - > - - - - {account.id} - - - - - - - {account.name} - - - - - {account.credentials.isDemoAccount ? "Demo" : "Real"} - - - - - - -
{JSON.stringify(account.credentials, null, 2)}
- - - {formatDateTime(account.createdAt.getTime())} - - ); -}; diff --git a/apps/frontend/src/components/accounts/AccountsListTable/AccountsListTableToolbar.tsx b/apps/frontend/src/components/accounts/AccountsListTable/AccountsListTableToolbar.tsx deleted file mode 100644 index 897d3d97..00000000 --- a/apps/frontend/src/components/accounts/AccountsListTable/AccountsListTableToolbar.tsx +++ /dev/null @@ -1,78 +0,0 @@ -import type { FC } from "react"; -import React from "react"; -import Typography from "@mui/joy/Typography"; -import AddOutlinedIcon from "@mui/icons-material/AddOutlined"; -import EditOutlinedIcon from "@mui/icons-material/EditOutlined"; -import Box from "@mui/joy/Box"; -import Tooltip from "@mui/joy/Tooltip"; -import IconButton from "@mui/joy/IconButton"; - -type AccountsListTableToolbarProps = { - title: string; - numSelected: number; - onCreateAccountClick: () => void; - onEditAccountClick: () => void; -}; - -export const AccountsListTableToolbar: FC = ( - props, -) => { - const { numSelected, title, onCreateAccountClick, onEditAccountClick } = - props; - - return ( - 0 && { - bgcolor: "background.level1", - }), - borderTopLeftRadius: "var(--unstable_actionRadius)", - borderTopRightRadius: "var(--unstable_actionRadius)", - }} - > - {numSelected > 0 ? ( - - {numSelected} selected - - ) : ( - - {title} - - )} - -
- {numSelected > 0 ? ( - - - - - - ) : ( - - { - onCreateAccountClick(); - }} - size="md" - variant="outlined" - > - - - - )} -
-
- ); -}; diff --git a/apps/frontend/src/components/accounts/AccountsListTable/index.ts b/apps/frontend/src/components/accounts/AccountsListTable/index.ts deleted file mode 100644 index 28c08a13..00000000 --- a/apps/frontend/src/components/accounts/AccountsListTable/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./AccountsListTable"; diff --git a/apps/frontend/src/components/accounts/CreateAccountDialog/CreateAccountDialog.tsx b/apps/frontend/src/components/accounts/CreateAccountDialog/CreateAccountDialog.tsx deleted file mode 100644 index d5ead222..00000000 --- a/apps/frontend/src/components/accounts/CreateAccountDialog/CreateAccountDialog.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import type { FC } from "react"; -import React from "react"; -import Modal from "@mui/joy/Modal"; -import ModalDialog from "@mui/joy/ModalDialog"; -import DialogTitle from "@mui/joy/DialogTitle"; -import { useSnackbar } from "src/ui/snackbar"; -import { CreateAccountForm } from "../CreateAccountForm/CreateAccountForm"; - -type NewAccountDialogProps = { - open: boolean; - onClose: () => void; - onCreated: () => void; -}; - -export const CreateAccountDialog: FC = (props) => { - const { open, onClose, onCreated } = props; - const { showSnackbar } = useSnackbar(); - - return ( - - - New exchange account - - { - onCreated(); - onClose(); - - showSnackbar("Account created"); - }} - onError={(error) => { - showSnackbar(JSON.stringify(error), { - color: "danger", - autoHideDuration: 5000, - }); - console.log(error); - }} - /> - - - ); -}; diff --git a/apps/frontend/src/components/accounts/CreateAccountForm/CreateAccountForm.tsx b/apps/frontend/src/components/accounts/CreateAccountForm/CreateAccountForm.tsx deleted file mode 100644 index 93a28834..00000000 --- a/apps/frontend/src/components/accounts/CreateAccountForm/CreateAccountForm.tsx +++ /dev/null @@ -1,143 +0,0 @@ -import Box from "@mui/joy/Box"; -import Divider from "@mui/joy/Divider"; -import type { FC } from "react"; -import React, { useEffect } from "react"; -import { ExchangeCode } from "@opentrader/types"; -import { Form } from "react-final-form"; -import type { FormApi } from "final-form"; -import Grid from "@mui/joy/Grid"; -import Button from "@mui/joy/Button"; -import { tClient } from "src/lib/trpc/client"; -import { AccountNameField } from "./fields/AccountNameField"; -import { ApiKeyField } from "./fields/ApiKeyField"; -import type { CreateExchangeAccountFormValues } from "./types"; -import { ExchangeCodeField } from "./fields/ExchangeCodeField"; -import { IsDemoAccountField } from "./fields/IsDemoAccountField"; -import { PassphraseField } from "./fields/PassphraseField"; -import { SecretKeyField } from "./fields/SecretKeyField"; - -type CreateAccountFormProps = { - onCreated: () => void; - onError: (error?: unknown) => void; - debug?: boolean; -}; - -export const CreateAccountForm: FC = (props) => { - const { onCreated, onError, debug } = props; - - const { mutateAsync, isLoading, isSuccess, isError, error } = - tClient.exchangeAccount.create.useMutation(); - - useEffect(() => { - if (isSuccess) { - onCreated(); - } - }, [isSuccess]); - - useEffect(() => { - if (isError) { - onError(error); - } - }, [isError]); - - const initialValues: CreateExchangeAccountFormValues = { - // account - name: "", - exchangeCode: ExchangeCode.OKX, - - // credentials - apiKey: "", - secretKey: "", - password: "", - isDemoAccount: false, - }; - - const handleSubmit = async ( - values: CreateExchangeAccountFormValues, - form: FormApi, - ) => { - const data = await mutateAsync(values); - form.reset(); - - return data; - }; - - const validate = ( - values: CreateExchangeAccountFormValues, - ): - | Partial> - | undefined => { - if (!values.exchangeCode) { - return { exchangeCode: "Required" }; - } - - if (!values.name) { - return { name: "Required" }; - } - - if (!values.apiKey) { - return { apiKey: "Required" }; - } - - if (!values.secretKey) { - return { secretKey: "Required" }; - } - }; - - return ( - - - initialValues={initialValues} - onSubmit={handleSubmit} - render={({ handleSubmit, submitting, values, hasValidationErrors }) => ( -
void handleSubmit()}> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {debug ?
{JSON.stringify(values, null, 2)}
: null} -
- )} - validate={validate} - /> -
- ); -}; diff --git a/apps/frontend/src/components/accounts/CreateAccountForm/UpdateAccountForm.tsx b/apps/frontend/src/components/accounts/CreateAccountForm/UpdateAccountForm.tsx deleted file mode 100644 index 9bbd69d0..00000000 --- a/apps/frontend/src/components/accounts/CreateAccountForm/UpdateAccountForm.tsx +++ /dev/null @@ -1,155 +0,0 @@ -import Button from "@mui/joy/Button"; -import type { FC } from "react"; -import React, { useEffect } from "react"; -import { Form } from "react-final-form"; -import type { FormApi } from "final-form"; -import Box from "@mui/joy/Box"; -import Grid from "@mui/joy/Grid"; -import Divider from "@mui/joy/Divider"; -import { tClient } from "src/lib/trpc/client"; -import type { TExchangeAccount } from "src/types/trpc"; -import { AccountIdField } from "./fields/AccountIdField"; -import { AccountNameField } from "./fields/AccountNameField"; -import { ApiKeyField } from "./fields/ApiKeyField"; -import type { UpdateExchangeAccountFormValues } from "./types"; -import { ExchangeCodeField } from "./fields/ExchangeCodeField"; -import { IsDemoAccountField } from "./fields/IsDemoAccountField"; -import { PassphraseField } from "./fields/PassphraseField"; -import { SecretKeyField } from "./fields/SecretKeyField"; - -type UpdateAccountFormProps = { - onUpdated: () => void; - onError: (error?: unknown) => void; - account: TExchangeAccount; - debug?: boolean; -}; - -export const UpdateAccountForm: FC = (props) => { - const { onUpdated, onError, account, debug } = props; - - const { mutateAsync, isLoading, isSuccess, isError, error } = - tClient.exchangeAccount.update.useMutation(); - - useEffect(() => { - if (isSuccess) { - onUpdated(); - } - }, [isSuccess]); - - useEffect(() => { - if (isError) { - onError(error); - } - }, [isError]); - - const initialValues: UpdateExchangeAccountFormValues = { - // account - name: account.name, - exchangeCode: account.exchangeCode, - - // credentials - apiKey: account.apiKey, - secretKey: account.secretKey, - password: account.password, - isDemoAccount: account.isDemoAccount, - }; - - const handleSubmit = async ( - values: UpdateExchangeAccountFormValues, - _form: FormApi, - ) => { - const data = await mutateAsync({ - id: account.id, - body: values, - }); - - return data; - }; - - const validate = ( - values: UpdateExchangeAccountFormValues, - ): - | Partial> - | undefined => { - if (!values.exchangeCode) { - return { exchangeCode: "Required" }; - } - - if (!values.name) { - return { name: "Required" }; - } - - if (!values.apiKey) { - return { apiKey: "Required" }; - } - - if (!values.secretKey) { - return { secretKey: "Required" }; - } - - if (!values.password) { - return { password: "Required" }; - } - }; - - return ( - - - initialValues={initialValues} - onSubmit={handleSubmit} - render={({ handleSubmit, submitting, values, hasValidationErrors }) => ( -
void handleSubmit()}> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {debug ?
{JSON.stringify(values, null, 2)}
: null} -
- )} - validate={validate} - /> -
- ); -}; diff --git a/apps/frontend/src/components/accounts/CreateAccountForm/fields/AccountIdField.tsx b/apps/frontend/src/components/accounts/CreateAccountForm/fields/AccountIdField.tsx deleted file mode 100644 index e97d7f02..00000000 --- a/apps/frontend/src/components/accounts/CreateAccountForm/fields/AccountIdField.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { FC } from "react"; -import React from "react"; -import FormControl from "@mui/joy/FormControl"; -import FormLabel from "@mui/joy/FormLabel"; -import Input from "@mui/joy/Input"; - -type AccountIdFieldProps = { - disabled?: boolean; - value: number; -}; - -export const AccountIdField: FC = (props) => { - const { disabled, value } = props; - - return ( - - Account ID - - - - ); -}; diff --git a/apps/frontend/src/components/accounts/CreateAccountForm/fields/AccountNameField.tsx b/apps/frontend/src/components/accounts/CreateAccountForm/fields/AccountNameField.tsx deleted file mode 100644 index 64a7a968..00000000 --- a/apps/frontend/src/components/accounts/CreateAccountForm/fields/AccountNameField.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import type { FC } from "react"; -import React from "react"; -import FormControl from "@mui/joy/FormControl"; -import FormLabel from "@mui/joy/FormLabel"; -import Input from "@mui/joy/Input"; -import { Field } from "react-final-form"; -import type { CreateExchangeAccountFormValues } from "../types"; - -const fieldName: keyof CreateExchangeAccountFormValues = "name"; - -export const AccountNameField: FC = () => { - return ( - name={fieldName}> - {({ input }) => ( - - Account name - - - - )} - - ); -}; diff --git a/apps/frontend/src/components/accounts/CreateAccountForm/fields/ApiKeyField.tsx b/apps/frontend/src/components/accounts/CreateAccountForm/fields/ApiKeyField.tsx deleted file mode 100644 index 8940027b..00000000 --- a/apps/frontend/src/components/accounts/CreateAccountForm/fields/ApiKeyField.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import FormControl from "@mui/joy/FormControl"; -import FormLabel from "@mui/joy/FormLabel"; -import Input from "@mui/joy/Input"; -import type { FC } from "react"; -import React from "react"; -import { Field } from "react-final-form"; -import type { CreateExchangeAccountFormValues } from "../types"; - -const fieldName: keyof CreateExchangeAccountFormValues = "apiKey"; - -export const ApiKeyField: FC = () => { - return ( - name={fieldName}> - {({ input }) => ( - - API Key - - - - )} - - ); -}; diff --git a/apps/frontend/src/components/accounts/CreateAccountForm/fields/ExchangeCodeField.tsx b/apps/frontend/src/components/accounts/CreateAccountForm/fields/ExchangeCodeField.tsx deleted file mode 100644 index ce568005..00000000 --- a/apps/frontend/src/components/accounts/CreateAccountForm/fields/ExchangeCodeField.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { ExchangeCode } from "@opentrader/types"; -import React from "react"; -import Select from "@mui/joy/Select"; -import Option from "@mui/joy/Option"; -import { Field } from "react-final-form"; -import type { CreateExchangeAccountFormValues } from "../types"; - -const fieldName: keyof CreateExchangeAccountFormValues = "exchangeCode"; - -const exchangeCodes = Object.values(ExchangeCode); - -export function ExchangeCodeField() { - return ( - name={fieldName}> - {({ input }) => ( - - )} - - ); -} diff --git a/apps/frontend/src/components/accounts/CreateAccountForm/fields/IsDemoAccountField.tsx b/apps/frontend/src/components/accounts/CreateAccountForm/fields/IsDemoAccountField.tsx deleted file mode 100644 index 373a8de7..00000000 --- a/apps/frontend/src/components/accounts/CreateAccountForm/fields/IsDemoAccountField.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import type { FC } from "react"; -import React from "react"; -import Checkbox from "@mui/joy/Checkbox"; -import { Field } from "react-final-form"; -import type { CreateExchangeAccountFormValues } from "../types"; - -const fieldName: keyof CreateExchangeAccountFormValues = "isDemoAccount"; - -export const IsDemoAccountField: FC = () => { - return ( - - {({ input }) => ( - - )} - - ); -}; diff --git a/apps/frontend/src/components/accounts/CreateAccountForm/fields/PassphraseField.tsx b/apps/frontend/src/components/accounts/CreateAccountForm/fields/PassphraseField.tsx deleted file mode 100644 index 7a9d61ec..00000000 --- a/apps/frontend/src/components/accounts/CreateAccountForm/fields/PassphraseField.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import FormControl from "@mui/joy/FormControl"; -import FormLabel from "@mui/joy/FormLabel"; -import Input from "@mui/joy/Input"; -import type { FC } from "react"; -import React from "react"; -import { Field } from "react-final-form"; -import type { CreateExchangeAccountFormValues } from "../types"; - -const fieldName: keyof CreateExchangeAccountFormValues = "password"; - -export const PassphraseField: FC = () => { - return ( - name={fieldName}> - {({ input }) => ( - - Password - - - - )} - - ); -}; diff --git a/apps/frontend/src/components/accounts/CreateAccountForm/fields/SecretKeyField.tsx b/apps/frontend/src/components/accounts/CreateAccountForm/fields/SecretKeyField.tsx deleted file mode 100644 index 3e00f591..00000000 --- a/apps/frontend/src/components/accounts/CreateAccountForm/fields/SecretKeyField.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import FormControl from "@mui/joy/FormControl"; -import FormLabel from "@mui/joy/FormLabel"; -import Input from "@mui/joy/Input"; -import type { FC } from "react"; -import React from "react"; -import { Field } from "react-final-form"; -import type { CreateExchangeAccountFormValues } from "../types"; - -const fieldName: keyof CreateExchangeAccountFormValues = "secretKey"; - -export const SecretKeyField: FC = () => { - return ( - name={fieldName}> - {({ input }) => ( - - Secret key - - - - )} - - ); -}; diff --git a/apps/frontend/src/components/accounts/CreateAccountForm/types.ts b/apps/frontend/src/components/accounts/CreateAccountForm/types.ts deleted file mode 100644 index 679a284b..00000000 --- a/apps/frontend/src/components/accounts/CreateAccountForm/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { RouterInput } from "src/lib/trpc/types"; - -export type CreateExchangeAccountFormValues = - RouterInput["exchangeAccount"]["create"]; - -export type UpdateExchangeAccountFormValues = - RouterInput["exchangeAccount"]["update"]["body"]; diff --git a/apps/frontend/src/components/accounts/UpdateAccountDialog/UpdateAccountDialog.tsx b/apps/frontend/src/components/accounts/UpdateAccountDialog/UpdateAccountDialog.tsx deleted file mode 100644 index b7cc46f0..00000000 --- a/apps/frontend/src/components/accounts/UpdateAccountDialog/UpdateAccountDialog.tsx +++ /dev/null @@ -1,44 +0,0 @@ -import type { FC } from "react"; -import React from "react"; -import Modal from "@mui/joy/Modal"; -import ModalDialog from "@mui/joy/ModalDialog"; -import DialogTitle from "@mui/joy/DialogTitle"; -import type { TExchangeAccount } from "src/types/trpc"; -import { useSnackbar } from "src/ui/snackbar"; -import { UpdateAccountForm } from "../CreateAccountForm/UpdateAccountForm"; - -type UpdateAccountDialogProps = { - open: boolean; - onClose: () => void; - onCreated: () => void; - account: TExchangeAccount; -}; - -export const UpdateAccountDialog: FC = (props) => { - const { open, onClose, onCreated, account } = props; - const { showSnackbar } = useSnackbar(); - - return ( - - - Edit exchange account - - { - showSnackbar(JSON.stringify(error), { - color: "danger", - }); - console.log(error); - }} - onUpdated={() => { - onCreated(); - onClose(); - - showSnackbar("Account updated"); - }} - /> - - - ); -}; diff --git a/apps/frontend/src/components/accounts/loading.tsx b/apps/frontend/src/components/accounts/loading.tsx deleted file mode 100644 index 21f29931..00000000 --- a/apps/frontend/src/components/accounts/loading.tsx +++ /dev/null @@ -1,24 +0,0 @@ -"use client"; - -import Box from "@mui/joy/Box"; -import Skeleton from "@mui/joy/Skeleton"; - -export default function Loading() { - return ( - - - - ); -} diff --git a/apps/frontend/src/components/accounts/page.tsx b/apps/frontend/src/components/accounts/page.tsx deleted file mode 100644 index 3a7a7f62..00000000 --- a/apps/frontend/src/components/accounts/page.tsx +++ /dev/null @@ -1,54 +0,0 @@ -"use client"; - -import Box from "@mui/joy/Box"; -import React, { useState } from "react"; -import { AccountsListTable } from "src/components/accounts/AccountsListTable"; -import { CreateAccountDialog } from "src/components/accounts/CreateAccountDialog/CreateAccountDialog"; -import { UpdateAccountDialog } from "src/components/accounts/UpdateAccountDialog/UpdateAccountDialog"; -import { tClient } from "src/lib/trpc/client"; -import type { TExchangeAccount } from "src/types/trpc"; - -export default function AccountsPage() { - const [createDialogOpen, setCreateDialogOpen] = useState(false); - const [updateDialogOpen, setUpdateDialogOpen] = useState(false); - - const [selectedAccount, setSelectedAccount] = - useState(null); - - const [exchangeAccounts, { refetch }] = - tClient.exchangeAccount.list.useSuspenseQuery(); - - return ( - - { - setCreateDialogOpen(true); - }} - onEditAccountClick={(account) => { - setSelectedAccount(account); - setUpdateDialogOpen(true); - }} - /> - - { - setCreateDialogOpen(false); - }} - onCreated={() => void refetch()} - open={createDialogOpen} - /> - - {selectedAccount ? ( - { - setUpdateDialogOpen(false); - }} - onCreated={() => void refetch()} - open={updateDialogOpen} - /> - ) : null} - - ); -} diff --git a/apps/frontend/src/components/bot/bot-details/BotActions/RunBotTemplateButton.tsx b/apps/frontend/src/components/bot/bot-details/BotActions/RunBotTemplateButton.tsx deleted file mode 100644 index 507cfd3c..00000000 --- a/apps/frontend/src/components/bot/bot-details/BotActions/RunBotTemplateButton.tsx +++ /dev/null @@ -1,48 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import React from "react"; -import clsx from "clsx"; -import Button from "@mui/joy/Button"; -import { tClient } from "src/lib/trpc/client"; -import type { TGridBot } from "src/types/trpc"; -import { useSnackbar } from "src/ui/snackbar"; - -const componentName = "RunBotTemplateButton"; -const classes = { - root: `${componentName}-root`, -}; - -type RunBotTemplateButtonProps = { - className?: string; - bot: TGridBot; -}; - -export const RunBotTemplateButton: FC = (props) => { - const { className, bot } = props; - const { showSnackbar } = useSnackbar(); - - const { isLoading, mutate } = tClient.bot.manualProcess.useMutation({ - onSuccess() { - showSnackbar("Bot template executed"); - }, - }); - - return ( - - ); -}; diff --git a/apps/frontend/src/components/bot/bot-details/BotChart/BotChart.tsx b/apps/frontend/src/components/bot/bot-details/BotChart/BotChart.tsx deleted file mode 100644 index fc160406..00000000 --- a/apps/frontend/src/components/bot/bot-details/BotChart/BotChart.tsx +++ /dev/null @@ -1,71 +0,0 @@ -"use client"; - -import Skeleton from "@mui/joy/Skeleton"; -import { composeSymbolId } from "@opentrader/tools"; -import type { FC } from "react"; -import React, { Suspense, useState } from "react"; -import type { BarSize } from "@opentrader/types"; -import { tClient } from "src/lib/trpc/client"; -import { Chart, CHART_HEIGHT } from "src/ui/charts/Chart"; -import { FlexSpacer } from "src/ui/FlexSpacer"; -import { ExchangeAccountSelect } from "src/ui/selects/ExchangeAccountSelect"; -import { SymbolSelect } from "src/ui/selects/SymbolSelect"; -import { BarSizeSelect } from "src/ui/selects/BarSizeSelect"; - -const timeframes = ["1d", "4h", "1h", "5m", "1m"] as const; -export type ChartBarSize = Extract; - -type BotChartProps = { - botId: number; -}; - -const NOOP = () => void 0; - -export const BotChart: FC = ({ botId }) => { - const [bot] = tClient.bot.getOne.useSuspenseQuery(botId); - const [exchangeAccount] = tClient.exchangeAccount.getOne.useSuspenseQuery( - bot.exchangeAccountId, - ); - const [symbol] = tClient.symbol.getOne.useSuspenseQuery({ - symbolId: composeSymbolId( - exchangeAccount.exchangeCode, - bot.baseCurrency, - bot.quoteCurrency, - ), - }); - - const [barSize, setBarSize] = useState("1h"); - - return ( - - } - > - - - - - - { - setBarSize(value); - }} - value={barSize} - whitelist={timeframes} - /> - - - - - ); -}; diff --git a/apps/frontend/src/components/bot/bot-details/BotChart/index.ts b/apps/frontend/src/components/bot/bot-details/BotChart/index.ts deleted file mode 100644 index 0e3e0ca7..00000000 --- a/apps/frontend/src/components/bot/bot-details/BotChart/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./BotChart"; diff --git a/apps/frontend/src/components/bot/bot-details/BotSettings/BotSettings.tsx b/apps/frontend/src/components/bot/bot-details/BotSettings/BotSettings.tsx deleted file mode 100644 index 755ead64..00000000 --- a/apps/frontend/src/components/bot/bot-details/BotSettings/BotSettings.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import Chip from "@mui/joy/Chip"; -import List from "@mui/joy/List"; -import ListDivider from "@mui/joy/ListDivider"; -import type { FC } from "react"; -import DataObjectIcon from "@mui/icons-material/DataObject"; -import type { TBot } from "src/types/trpc"; -import { StatusSettingsListItem } from "src/components/common/bot/settings/StatusSettingListItem"; -import { PairSettingListItem } from "src/components/common/bot/settings/PairSettingListItem"; -import { SettingListItem } from "src/components/common/bot/settings/SettingListItem"; - -type BotSettingsProps = { - bot: TBot; -}; - -export const BotSettings: FC = ({ bot }) => { - return ( - - - - - - - - - - } name="Template"> - - {bot.template} - - - - ); -}; diff --git a/apps/frontend/src/components/bot/bot-details/BotSettings/BotSettingsCard.tsx b/apps/frontend/src/components/bot/bot-details/BotSettings/BotSettingsCard.tsx deleted file mode 100644 index 7892af47..00000000 --- a/apps/frontend/src/components/bot/bot-details/BotSettings/BotSettingsCard.tsx +++ /dev/null @@ -1,49 +0,0 @@ -"use client"; - -import Box from "@mui/joy/Box"; -import Card from "@mui/joy/Card"; -import Typography from "@mui/joy/Typography"; -import type { FC } from "react"; -import React from "react"; -import { SyncClosedOrdersButton } from "src/components/debug/SyncClosedOrdersButton"; -import { tClient } from "src/lib/trpc/client"; -import { BotAdditionalActions } from "src/components/common/bot/actions/BotAdditionalActions"; -import { RunBotTemplateButton } from "src/components/common/bot/actions/buttons/RunBotTemplateButton"; -import { StartStopBotButton } from "src/components/common/bot/actions/buttons/StartStopBotButton"; -import { DeleteBotButton } from "src/components/common/bot/actions/buttons/DeleteBotButton"; -import { BotActions } from "src/components/common/bot/actions/BotActions"; -import { BotSettings } from "./BotSettings"; - -type BotSettingsCardProps = { - botId: number; -}; - -export const BotSettingsCard: FC = ({ botId }) => { - const [bot] = tClient.bot.getOne.useSuspenseQuery(botId); - - return ( - - - - {bot.name} - - - - #{bot.id} - - - - - - - - - - - - - - - - ); -}; diff --git a/apps/frontend/src/components/bot/bot-details/BotSettings/index.ts b/apps/frontend/src/components/bot/bot-details/BotSettings/index.ts deleted file mode 100644 index 4052877a..00000000 --- a/apps/frontend/src/components/bot/bot-details/BotSettings/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./BotSettingsCard"; diff --git a/apps/frontend/src/components/bot/bot-details/BotSettingsForm/BotSettingsForm.tsx b/apps/frontend/src/components/bot/bot-details/BotSettingsForm/BotSettingsForm.tsx deleted file mode 100644 index 0cb805b9..00000000 --- a/apps/frontend/src/components/bot/bot-details/BotSettingsForm/BotSettingsForm.tsx +++ /dev/null @@ -1,45 +0,0 @@ -"use client"; - -import Button from "@mui/joy/Button"; -import { tClient } from "src/lib/trpc/client"; - -type Props = { - botId: number; -}; - -export function BotSettingsForm({ botId }: Props) { - const { data } = tClient.bot.getOne.useQuery(botId); - const { isLoading, mutate } = tClient.bot.update.useMutation(); - - const handleMutate = () => { - mutate({ - botId, - data: { - settings: { - template: { - name: "LowCapStrategy", - params: { - expectedDrop: 0.5, // 0.5% - }, - }, - symbols: ["BTC/USDT", "ETH/USDT", "UNI/USDT", "OKB/USDT"], - strategy: { - timeframe: "1m", - runOn: { - candleClosed: true, - orderFilled: true, - interval: 10000, - }, - }, - }, - }, - }); - }; - - return ( -
-
{JSON.stringify(data, null, 2)}
- -
- ); -} diff --git a/apps/frontend/src/components/bot/bot-details/loading.tsx b/apps/frontend/src/components/bot/bot-details/loading.tsx deleted file mode 100644 index 576db806..00000000 --- a/apps/frontend/src/components/bot/bot-details/loading.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import Grid from "@mui/joy/Grid"; -import Skeleton from "@mui/joy/Skeleton"; -import React from "react"; - -export default function Loading() { - return ( - - - - - - - - - - ); -} diff --git a/apps/frontend/src/components/bot/bot-details/page.tsx b/apps/frontend/src/components/bot/bot-details/page.tsx deleted file mode 100644 index f4555ba5..00000000 --- a/apps/frontend/src/components/bot/bot-details/page.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import Grid from "@mui/joy/Grid"; -import Skeleton from "@mui/joy/Skeleton"; -import React, { Suspense } from "react"; -import { ProfitsCard } from "src/components/common/smart-trades/ProfitsCard"; -import { CHART_HEIGHT } from "src/ui/charts/Chart"; -import { SmartTradesTable } from "src/components/common/smart-trades/SmartTradesTable"; -import { BotSettingsCard } from "./BotSettings/BotSettingsCard"; -import { BotChart } from "./BotChart"; - -type Props = { - botId: number; -}; - -export default function BotDetailsPage(props: Props) { - const { botId } = props; - - return ( - - - - } - > - - - - - - - } - > - - - - - - - } - > - - - - - - - } - > - - - - - ); -} diff --git a/apps/frontend/src/components/common/bot/BotStatusSwitcher.tsx b/apps/frontend/src/components/common/bot/BotStatusSwitcher.tsx deleted file mode 100644 index bd81de72..00000000 --- a/apps/frontend/src/components/common/bot/BotStatusSwitcher.tsx +++ /dev/null @@ -1,113 +0,0 @@ -"use client"; - -import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord"; -import type { SxProps } from "@mui/joy/styles/types"; -import type { FC } from "react"; -import React from "react"; -import clsx from "clsx"; -import type { ChipProps } from "@mui/joy/Chip"; -import Chip from "@mui/joy/Chip"; -import { styled } from "@mui/joy/styles"; -import { tClient } from "src/lib/trpc/client"; -import type { TBot } from "src/types/trpc"; -import { useSnackbar } from "src/ui/snackbar"; - -const componentName = "BotStatusChip"; -const classes = { - root: `${componentName}-root`, -}; -const StyledChip = styled(Chip)(() => ({ - /* Styles applied to the root element. */ - [`&.${classes.root}`]: {}, -})); - -type BotStatusSwitcherProps = { - className?: string; - bot: TBot; - sx?: SxProps; - size?: ChipProps["size"]; -}; - -export const BotStatusSwitcher: FC = (props) => { - const { className, bot, sx, size } = props; - const { showSnackbar } = useSnackbar(); - const tUtils = tClient.useUtils(); - - const invalidateState = () => { - // workaround, until refactored to one endpoint - void tUtils.gridBot.getOne.invalidate(bot.id); - void tUtils.bot.getOne.invalidate(bot.id); - - void tUtils.bot.activeSmartTrades.invalidate({ botId: bot.id }); - void tUtils.bot.completedSmartTrades.invalidate({ botId: bot.id }); - }; - - const startBot = tClient.bot.start.useMutation({ - onSuccess() { - invalidateState(); - - showSnackbar("Bot has been enabled"); - }, - }); - - const stopBot = tClient.bot.stop.useMutation({ - onSuccess() { - invalidateState(); - - showSnackbar("Bot has been stopped"); - }, - }); - - if (startBot.isLoading || stopBot.isLoading) { - return ( - } - sx={sx} - variant="outlined" - > - {startBot.isLoading ? "Starting..." : "Stopping..."} - - ); - } - - if (bot.enabled) { - return ( - { - stopBot.mutate({ - botId: bot.id, - }); - }} - size={size} - startDecorator={} - sx={sx} - variant="outlined" - > - Running - - ); - } - - return ( - { - startBot.mutate({ - botId: bot.id, - }); - }} - size={size} - startDecorator={} - sx={sx} - variant="outlined" - > - Disabled - - ); -}; diff --git a/apps/frontend/src/components/common/bot/actions/BotActions.tsx b/apps/frontend/src/components/common/bot/actions/BotActions.tsx deleted file mode 100644 index d57c7ab6..00000000 --- a/apps/frontend/src/components/common/bot/actions/BotActions.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import type { FC, ReactNode } from "react"; - -type BotActionsProps = { - children: ReactNode; -}; - -export const BotActions: FC = ({ children }) => { - return <>{children}; -}; diff --git a/apps/frontend/src/components/common/bot/actions/BotAdditionalActions.tsx b/apps/frontend/src/components/common/bot/actions/BotAdditionalActions.tsx deleted file mode 100644 index a0684c79..00000000 --- a/apps/frontend/src/components/common/bot/actions/BotAdditionalActions.tsx +++ /dev/null @@ -1,54 +0,0 @@ -"use client"; - -import Accordion from "@mui/joy/Accordion"; -import AccordionGroup from "@mui/joy/AccordionGroup"; -import AccordionSummary, { - accordionSummaryClasses, -} from "@mui/joy/AccordionSummary"; -import AccordionDetails, { - accordionDetailsClasses, -} from "@mui/joy/AccordionDetails"; -import type { FC, ReactNode } from "react"; -import React from "react"; - -type BotAdditionalActionsProps = { - children: ReactNode; -}; - -export const BotAdditionalActions: FC = ({ - children, -}) => { - return ( - ({ - // maxWidth: 400, - mx: -2, - mb: -2, - borderRadius: "md", - [`& .${accordionSummaryClasses.button}`]: { - color: theme.vars.palette.text.tertiary, - }, - [`& .${accordionDetailsClasses.root}`]: { - backgroundColor: "transparent", - color: theme.vars.palette.text.primary, - border: 0, - }, - [`& .${accordionDetailsClasses.content}`]: { - gap: "0.75rem", - - [`&.${accordionDetailsClasses.expanded}`]: { - paddingBlock: "0.75rem", - }, - }, - })} - transition="0.2s" - variant="plain" - > - - More actions - {children} - - - ); -}; diff --git a/apps/frontend/src/components/common/bot/actions/buttons/CronPlacePendingOrderButton.tsx b/apps/frontend/src/components/common/bot/actions/buttons/CronPlacePendingOrderButton.tsx deleted file mode 100644 index f6b86bca..00000000 --- a/apps/frontend/src/components/common/bot/actions/buttons/CronPlacePendingOrderButton.tsx +++ /dev/null @@ -1,101 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import React from "react"; -import clsx from "clsx"; -import Button from "@mui/joy/Button"; -import Tooltip from "@mui/joy/Tooltip"; -import { tClient } from "src/lib/trpc/client"; -import type { TGridBot, TPendingSmartTrade } from "src/types/trpc"; -import { useSnackbar } from "src/ui/snackbar"; - -const componentName = "CronPlacePendingOrderButton"; -const classes = { - root: `${componentName}-root`, -}; - -type CronPlacePendingOrderButtonProps = { - className?: string; - bot: TGridBot; - smartTrades?: TPendingSmartTrade[]; -}; - -function buildTooltipTable(smartTrades: TPendingSmartTrade[]) { - const SM_COLUMN_LENGTH = 5; - const LG_COLUMN_LENGTH = 12; - - const idColumn = "id".padEnd(SM_COLUMN_LENGTH); - const buyColumn = "buy".padEnd(LG_COLUMN_LENGTH); - const sellColumn = "sell".padEnd(LG_COLUMN_LENGTH); - const tableHead = `${idColumn} ${buyColumn} ${sellColumn}`; - - let tableBody = ""; - - for (const smartTrade of smartTrades) { - tableBody += "\n"; - - const id = smartTrade.id.toString().padEnd(SM_COLUMN_LENGTH); - const entryPrice = smartTrade.entryOrder - .price!.toString() - .padEnd(LG_COLUMN_LENGTH); - const tpPrice = smartTrade.takeProfitOrder - .price!.toString() - .padEnd(LG_COLUMN_LENGTH); - - tableBody += `${id} ${entryPrice} ${tpPrice}`; - } - - return `${tableHead}${tableBody}`; -} - -// @deprecated -export const CronPlacePendingOrderButton: FC< - CronPlacePendingOrderButtonProps -> = (props) => { - const { className, bot, smartTrades = [] } = props; - const { showSnackbar } = useSnackbar(); - - const { isLoading, mutate } = tClient.bot.cronPlaceLimitOrders.useMutation({ - onSuccess() { - showSnackbar("Orders has been placed"); - }, - }); - - const buttonNode = ( - - ); - - if (smartTrades.length > 0) { - return ( - {buildTooltipTable(smartTrades)}}> - {buttonNode} - - ); - } - - return ( - - ); -}; diff --git a/apps/frontend/src/components/common/bot/actions/buttons/DeleteBotButton.tsx b/apps/frontend/src/components/common/bot/actions/buttons/DeleteBotButton.tsx deleted file mode 100644 index 2e5836d9..00000000 --- a/apps/frontend/src/components/common/bot/actions/buttons/DeleteBotButton.tsx +++ /dev/null @@ -1,92 +0,0 @@ -"use client"; - -import { useRouter } from "next/navigation"; -import type { FC } from "react"; -import React from "react"; -import clsx from "clsx"; -import Button from "@mui/joy/Button"; -import { tClient } from "src/lib/trpc/client"; -import type { TBot } from "src/types/trpc"; -import { useConfirmationDialog } from "src/ui/confirmation-dialog"; -import { useSnackbar } from "src/ui/snackbar"; -import { toPage } from "src/utils/next/toPage"; - -const componentName = "DeleteBotButton"; -const classes = { - root: `${componentName}-root`, -}; - -type DeleteBotButtonProps = { - className?: string; - bot: TBot; -}; - -export const DeleteBotButton: FC = ({ - className, - bot, -}) => { - const router = useRouter(); - const { showSnackbar } = useSnackbar(); - const { showConfirmDialog } = useConfirmationDialog(); - const tUtils = tClient.useUtils(); - - const invalidateState = () => { - void tUtils.bot.list.invalidate(); - void tUtils.gridBot.list.invalidate(); - }; - - const deleteBot = tClient.bot.delete.useMutation({ - onSuccess() { - invalidateState(); - - showSnackbar("Bot has been deleted"); - - setTimeout(() => { - router.push(toPage("grid-bot")); - }, 1500); - }, - }); - - if (deleteBot.isLoading) { - return ( - - ); - } - - return ( - - ); -}; diff --git a/apps/frontend/src/components/common/bot/actions/buttons/RunBotTemplateButton.tsx b/apps/frontend/src/components/common/bot/actions/buttons/RunBotTemplateButton.tsx deleted file mode 100644 index 9eed0e11..00000000 --- a/apps/frontend/src/components/common/bot/actions/buttons/RunBotTemplateButton.tsx +++ /dev/null @@ -1,48 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import React from "react"; -import clsx from "clsx"; -import Button from "@mui/joy/Button"; -import { tClient } from "src/lib/trpc/client"; -import type { TBot } from "src/types/trpc"; -import { useSnackbar } from "src/ui/snackbar"; - -const componentName = "RunBotTemplateButton"; -const classes = { - root: `${componentName}-root`, -}; - -type RunBotTemplateButtonProps = { - className?: string; - bot: TBot; -}; - -export const RunBotTemplateButton: FC = (props) => { - const { className, bot } = props; - const { showSnackbar } = useSnackbar(); - - const { isLoading, mutate } = tClient.bot.manualProcess.useMutation({ - onSuccess() { - showSnackbar("Bot template executed"); - }, - }); - - return ( - - ); -}; diff --git a/apps/frontend/src/components/common/bot/actions/buttons/StartStopBotButton.tsx b/apps/frontend/src/components/common/bot/actions/buttons/StartStopBotButton.tsx deleted file mode 100644 index 6c4dd5b0..00000000 --- a/apps/frontend/src/components/common/bot/actions/buttons/StartStopBotButton.tsx +++ /dev/null @@ -1,101 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import React from "react"; -import clsx from "clsx"; -import Button from "@mui/joy/Button"; -import { tClient } from "src/lib/trpc/client"; -import type { TBot } from "src/types/trpc"; -import { useSnackbar } from "src/ui/snackbar"; - -const componentName = "StartStopBotButton"; -const classes = { - root: `${componentName}-root`, -}; - -type StartStopBotButtonProps = { - className?: string; - bot: TBot; -}; - -export const StartStopBotButton: FC = ({ - className, - bot, -}) => { - const { showSnackbar } = useSnackbar(); - const tUtils = tClient.useUtils(); - - const invalidateState = () => { - // workaround, until merged into one endpoint - void tUtils.gridBot.getOne.invalidate(bot.id); - void tUtils.bot.getOne.invalidate(bot.id); - - void tUtils.bot.activeSmartTrades.invalidate({ botId: bot.id }); - void tUtils.bot.completedSmartTrades.invalidate({ botId: bot.id }); - }; - - const startBot = tClient.bot.start.useMutation({ - onSuccess() { - invalidateState(); - - showSnackbar("Bot has been enabled"); - }, - }); - - const stopBot = tClient.bot.stop.useMutation({ - onSuccess() { - invalidateState(); - - showSnackbar("Bot has been stopped"); - }, - }); - - if (startBot.isLoading || stopBot.isLoading) { - return ( - - ); - } - - if (bot.enabled) { - return ( - - ); - } - - return ( - - ); -}; diff --git a/apps/frontend/src/components/common/bot/actions/buttons/SyncOrdersButton.tsx b/apps/frontend/src/components/common/bot/actions/buttons/SyncOrdersButton.tsx deleted file mode 100644 index 23d2a4af..00000000 --- a/apps/frontend/src/components/common/bot/actions/buttons/SyncOrdersButton.tsx +++ /dev/null @@ -1,49 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import React from "react"; -import clsx from "clsx"; -import Button from "@mui/joy/Button"; -import { tClient } from "src/lib/trpc/client"; -import type { TBot } from "src/types/trpc"; -import { useSnackbar } from "src/ui/snackbar"; - -const componentName = "RunBotTemplateButton"; -const classes = { - root: `${componentName}-root`, -}; - -type SyncOrdersButtonProps = { - className?: string; - bot: TBot; -}; - -// @deprecated -export const SyncOrdersButton: FC = (props) => { - const { className, bot } = props; - const { showSnackbar } = useSnackbar(); - - const { isLoading, mutate } = tClient.bot.syncOrders.useMutation({ - onSuccess() { - showSnackbar("Orders have been synced"); - }, - }); - - return ( - - ); -}; diff --git a/apps/frontend/src/components/common/bot/settings/PairSettingListItem.tsx b/apps/frontend/src/components/common/bot/settings/PairSettingListItem.tsx deleted file mode 100644 index af9715e0..00000000 --- a/apps/frontend/src/components/common/bot/settings/PairSettingListItem.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import Box from "@mui/joy/Box"; -import ListItem from "@mui/joy/ListItem"; -import ListItemContent from "@mui/joy/ListItemContent"; -import ListItemDecorator from "@mui/joy/ListItemDecorator"; -import CurrencyBitcoinIcon from "@mui/icons-material/CurrencyBitcoin"; -import Typography from "@mui/joy/Typography"; -import type { FC } from "react"; -import { CryptoIcon } from "src/ui/icons/CryptoIcon"; -import type { TBot } from "src/types/trpc"; - -type PairSettingListItemProps = { - bot: TBot; -}; - -export const PairSettingListItem: FC = ({ bot }) => { - return ( - - - - - Pair - - - - - {bot.baseCurrency}/{bot.quoteCurrency} - - - - ); -}; diff --git a/apps/frontend/src/components/common/bot/settings/SettingListItem.tsx b/apps/frontend/src/components/common/bot/settings/SettingListItem.tsx deleted file mode 100644 index 205dc1b3..00000000 --- a/apps/frontend/src/components/common/bot/settings/SettingListItem.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import ListItem from "@mui/joy/ListItem"; -import ListItemContent from "@mui/joy/ListItemContent"; -import ListItemDecorator from "@mui/joy/ListItemDecorator"; -import Typography from "@mui/joy/Typography"; -import type { FC, ReactNode } from "react"; - -type SettingListItemProps = { - icon: ReactNode; - /** - * Option name - */ - name: string; - /** - * Option value - */ - children: ReactNode; -}; - -export const SettingListItem: FC = ({ - icon, - name, - children, -}) => { - return ( - - {icon} - {name} - {children} - - ); -}; diff --git a/apps/frontend/src/components/common/bot/settings/StatusSettingListItem.tsx b/apps/frontend/src/components/common/bot/settings/StatusSettingListItem.tsx deleted file mode 100644 index 615c4b32..00000000 --- a/apps/frontend/src/components/common/bot/settings/StatusSettingListItem.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import ListItem from "@mui/joy/ListItem"; -import ListItemContent from "@mui/joy/ListItemContent"; -import ListItemDecorator from "@mui/joy/ListItemDecorator"; -import CircleIcon from "@mui/icons-material/Circle"; -import type { FC } from "react"; -import { BotStatusSwitcher } from "src/components/common/bot/BotStatusSwitcher"; -import type { TBot } from "src/types/trpc"; - -type StatusSettingsListItemProps = { - bot: TBot; -}; - -export const StatusSettingsListItem: FC = ({ - bot, -}) => { - return ( - - - - - Status - - - - ); -}; diff --git a/apps/frontend/src/components/common/smart-trades/ProfitsCard/Profit.tsx b/apps/frontend/src/components/common/smart-trades/ProfitsCard/Profit.tsx deleted file mode 100644 index 66557ada..00000000 --- a/apps/frontend/src/components/common/smart-trades/ProfitsCard/Profit.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import Typography from "@mui/joy/Typography"; -import type { FC } from "react"; - -type ProfitProps = { - size?: "sm" | "md" | "lg"; - profit: number; - currency: string; -}; - -export const Profit: FC = ({ profit, currency, size = "md" }) => { - const isPositive = profit >= 0; - const sign = isPositive ? "+" : ""; - const color = isPositive - ? "var(--joy-palette-success-500)" - : "var(--joy-palette-danger-500)"; - - return ( - - {sign} - {profit.toFixed(2)} -   - {currency} - - ); -}; diff --git a/apps/frontend/src/components/common/smart-trades/ProfitsCard/ProfitDetails.tsx b/apps/frontend/src/components/common/smart-trades/ProfitsCard/ProfitDetails.tsx deleted file mode 100644 index 8e34e166..00000000 --- a/apps/frontend/src/components/common/smart-trades/ProfitsCard/ProfitDetails.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import type { FC } from "react"; -import Box from "@mui/joy/Box"; -import List from "@mui/joy/List"; -import ListItem from "@mui/joy/ListItem"; -import ListItemContent from "@mui/joy/ListItemContent"; -import Typography from "@mui/joy/Typography"; -import type { TCompletedSmartTrade } from "src/types/trpc"; -import { calcProfitFromSmartTrade } from "src/utils/smart-trades/calcProfitFromSmartTrade"; - -type ProfitDetailsProps = { - smartTrade: TCompletedSmartTrade; -}; - -export const ProfitDetails: FC = ({ smartTrade }) => { - const { id, entryOrder, takeProfitOrder } = smartTrade; - - const { fee } = calcProfitFromSmartTrade(smartTrade); - - return ( - - SmartTrade - - - ID - - {id} - - - - Qty - {takeProfitOrder.quantity} {smartTrade.baseCurrency} - - - - Buy - {entryOrder.filledPrice?.toFixed(2)} {smartTrade.quoteCurrency} - - - - Sell - {takeProfitOrder.filledPrice?.toFixed(2)} {smartTrade.quoteCurrency} - - - - Fee - {fee.toFixed(2)} {smartTrade.quoteCurrency} - - - - ); -}; diff --git a/apps/frontend/src/components/common/smart-trades/ProfitsCard/ProfitItem.tsx b/apps/frontend/src/components/common/smart-trades/ProfitsCard/ProfitItem.tsx deleted file mode 100644 index 27d37553..00000000 --- a/apps/frontend/src/components/common/smart-trades/ProfitsCard/ProfitItem.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import type { FC } from "react"; -import React from "react"; -import Typography from "@mui/joy/Typography"; -import formatDistanceToNow from "date-fns/formatDistanceToNow"; -import ListItemContent from "@mui/joy/ListItemContent"; -import ListItem from "@mui/joy/ListItem"; -import ListItemDecorator from "@mui/joy/ListItemDecorator"; -import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined"; -import Tooltip from "@mui/joy/Tooltip"; -import type { TCompletedSmartTrade } from "src/types/trpc"; -import { formatDateTime } from "src/utils/date/formatDateTime"; -import { calcProfitFromSmartTrade } from "src/utils/smart-trades/calcProfitFromSmartTrade"; -import { ProfitDetails } from "./ProfitDetails"; -import { Profit } from "./Profit"; - -type ProfitItemProps = { - smartTrade: TCompletedSmartTrade; -}; - -export const ProfitItem: FC = ({ smartTrade }) => { - const { netProfit } = calcProfitFromSmartTrade(smartTrade); - - const { filledAt } = smartTrade.takeProfitOrder; - const dateLabel = filledAt - ? formatDateTime(filledAt.getTime()) - : "Missing date"; - - return ( - - - - - - - - - - {filledAt ? formatDistanceToNow(filledAt) : "Missing date"} - - - - }> - - - - ); -}; diff --git a/apps/frontend/src/components/common/smart-trades/ProfitsCard/ProfitsCard.tsx b/apps/frontend/src/components/common/smart-trades/ProfitsCard/ProfitsCard.tsx deleted file mode 100644 index 1a184227..00000000 --- a/apps/frontend/src/components/common/smart-trades/ProfitsCard/ProfitsCard.tsx +++ /dev/null @@ -1,71 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import React from "react"; -import Box from "@mui/joy/Box"; -import Card from "@mui/joy/Card"; -import CardContent from "@mui/joy/CardContent"; -import ListDivider from "@mui/joy/ListDivider"; -import Tooltip from "@mui/joy/Tooltip"; -import Typography from "@mui/joy/Typography"; -import List from "@mui/joy/List"; -import { tClient } from "src/lib/trpc/client"; -import { calcTotalProfitFromSmartTrades } from "src/utils/smart-trades/calcTotalProfitFromSmartTrades"; -import { ProfitItem } from "./ProfitItem"; -import { Profit } from "./Profit"; - -type ProfitsCardProps = { - botId: number; -}; - -export const ProfitsCard: FC = ({ botId }) => { - const [bot] = tClient.bot.getOne.useSuspenseQuery(botId); - const [smartTrades] = tClient.bot.completedSmartTrades.useSuspenseQuery({ - botId, - }); - - const totalProfit = calcTotalProfitFromSmartTrades(smartTrades); - - return ( - - - - Profits - - - {smartTrades.length > 0 ? ( - - - - - - ) : null} - - - {smartTrades.length > 0 ? ( - - - {smartTrades.map((smartTrade, i) => ( - - - {i < smartTrades.length - 1 ? : null} - - ))} - - - ) : ( - - No profits yet - - )} - - ); -}; diff --git a/apps/frontend/src/components/common/smart-trades/ProfitsCard/index.ts b/apps/frontend/src/components/common/smart-trades/ProfitsCard/index.ts deleted file mode 100644 index 8e5ad82c..00000000 --- a/apps/frontend/src/components/common/smart-trades/ProfitsCard/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./ProfitsCard"; diff --git a/apps/frontend/src/components/common/smart-trades/SmartTradesTable/NoActiveSmartTradesPlaceholder.tsx b/apps/frontend/src/components/common/smart-trades/SmartTradesTable/NoActiveSmartTradesPlaceholder.tsx deleted file mode 100644 index ab459477..00000000 --- a/apps/frontend/src/components/common/smart-trades/SmartTradesTable/NoActiveSmartTradesPlaceholder.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import Typography from "@mui/joy/Typography"; - -export function NoActiveSmartTradesPlaceholder() { - return ( - - - - - No active SmartTrades. Please start the bot. - - - - - ); -} diff --git a/apps/frontend/src/components/common/smart-trades/SmartTradesTable/SmartTradeStatus.tsx b/apps/frontend/src/components/common/smart-trades/SmartTradesTable/SmartTradeStatus.tsx deleted file mode 100644 index e0334556..00000000 --- a/apps/frontend/src/components/common/smart-trades/SmartTradesTable/SmartTradeStatus.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import Chip from "@mui/joy/Chip"; -import type { FC } from "react"; -import type { TActiveSmartTrade } from "src/types/trpc"; - -type SmartTradeStatusProps = { - smartTrade: TActiveSmartTrade; -}; - -export const SmartTradeStatus: FC = ({ smartTrade }) => { - const isPositionOpen = smartTrade.entryOrder.status === "Filled"; - - if (isPositionOpen) { - return ( - - Selling - - ); - } - - return ( - - Buying - - ); -}; diff --git a/apps/frontend/src/components/common/smart-trades/SmartTradesTable/SmartTradesTable.tsx b/apps/frontend/src/components/common/smart-trades/SmartTradesTable/SmartTradesTable.tsx deleted file mode 100644 index c192e5a7..00000000 --- a/apps/frontend/src/components/common/smart-trades/SmartTradesTable/SmartTradesTable.tsx +++ /dev/null @@ -1,57 +0,0 @@ -"use client"; - -import Sheet from "@mui/joy/Sheet"; -import Table from "@mui/joy/Table"; -import type { FC } from "react"; -import { tClient } from "src/lib/trpc/client"; -import { SmartTradesTableHead } from "src/components/common/smart-trades/SmartTradesTable/SmartTradesTableHead"; -import { SmartTradesTableItem } from "src/components/common/smart-trades/SmartTradesTable/SmartTradesTableItem"; -import { NoActiveSmartTradesPlaceholder } from "src/components/common/smart-trades/SmartTradesTable/NoActiveSmartTradesPlaceholder"; - -type SmartTradesTableProps = { - botId: number; -}; - -export const SmartTradesTable: FC = ({ botId }) => { - const [smartTrades] = tClient.bot.activeSmartTrades.useSuspenseQuery({ - botId, - }); - - return ( - - - theme.vars.palette.success.softBg, - "& thead th:nth-child(1)": { - width: "40px", - }, - "& thead th:nth-child(2)": { - width: "30%", - }, - "& tr > *:nth-child(n+3)": { textAlign: "right" }, - }} - > - - - {smartTrades.length > 0 ? ( - - {smartTrades.map((smartTrade) => ( - - ))} - - ) : ( - - )} -
-
- ); -}; diff --git a/apps/frontend/src/components/common/smart-trades/SmartTradesTable/SmartTradesTableHead.tsx b/apps/frontend/src/components/common/smart-trades/SmartTradesTable/SmartTradesTableHead.tsx deleted file mode 100644 index 5a79945c..00000000 --- a/apps/frontend/src/components/common/smart-trades/SmartTradesTable/SmartTradesTableHead.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import type { FC } from "react"; -import { ID_COLUMN_MIN_WIDTH } from "src/components/common/smart-trades/SmartTradesTable/constants"; - -export const SmartTradesTableHead: FC = () => { - return ( - - - - ID - - - Quantity - - Price - Status - Orders status - Amount - Created - Ref - - - ); -}; diff --git a/apps/frontend/src/components/common/smart-trades/SmartTradesTable/SmartTradesTableItem.tsx b/apps/frontend/src/components/common/smart-trades/SmartTradesTable/SmartTradesTableItem.tsx deleted file mode 100644 index bb1b2fab..00000000 --- a/apps/frontend/src/components/common/smart-trades/SmartTradesTable/SmartTradesTableItem.tsx +++ /dev/null @@ -1,89 +0,0 @@ -import List from "@mui/joy/List"; -import ListItem from "@mui/joy/ListItem"; -import Tooltip from "@mui/joy/Tooltip"; -import Big from "big.js"; -import type { FC } from "react"; -import React from "react"; -import type { TActiveSmartTrade } from "src/types/trpc"; -import { formatDateTime } from "src/utils/date/formatDateTime"; -import { SmartTradeStatus } from "src/components/common/smart-trades/SmartTradesTable/SmartTradeStatus"; -import { ID_COLUMN_MIN_WIDTH } from "src/components/common/smart-trades/SmartTradesTable/constants"; - -type SmartTradeTableItemProps = { - smartTrade: TActiveSmartTrade; -}; - -export const SmartTradesTableItem: FC = ({ - smartTrade, -}) => { - const isPositionOpen = smartTrade.entryOrder.status === "Filled"; - const amount = new Big(smartTrade.entryOrder.price || 0) - .times(smartTrade.entryOrder.quantity) - .toFixed(2) - .toString(); - const createdAt = formatDateTime(new Date(smartTrade.createdAt).getTime()); - const { entryOrder, takeProfitOrder } = smartTrade; - const price = isPositionOpen ? takeProfitOrder.price : entryOrder.price; - - return ( - - - - - Entry Order ID: {entryOrder.id}: - {entryOrder.exchangeOrderId ?? "null"} - - - TP Order ID: {takeProfitOrder.id}: - {takeProfitOrder.exchangeOrderId} - - - } - > -
{smartTrade.id}
-
- - - {smartTrade.entryOrder.quantity} {smartTrade.baseCurrency} - - - - - {price} {smartTrade.quoteCurrency} - - - - - - - -
- {smartTrade.entryOrder.status} - / - {smartTrade.takeProfitOrder.status} -
- - - {" "} - {amount} {smartTrade.quoteCurrency} - - {createdAt} - {smartTrade.ref} - - ); -}; diff --git a/apps/frontend/src/components/common/smart-trades/SmartTradesTable/constants.ts b/apps/frontend/src/components/common/smart-trades/SmartTradesTable/constants.ts deleted file mode 100644 index feafd191..00000000 --- a/apps/frontend/src/components/common/smart-trades/SmartTradesTable/constants.ts +++ /dev/null @@ -1 +0,0 @@ -export const ID_COLUMN_MIN_WIDTH = 64; diff --git a/apps/frontend/src/components/common/smart-trades/SmartTradesTable/index.ts b/apps/frontend/src/components/common/smart-trades/SmartTradesTable/index.ts deleted file mode 100644 index a6d54242..00000000 --- a/apps/frontend/src/components/common/smart-trades/SmartTradesTable/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "src/components/common/smart-trades/SmartTradesTable/SmartTradesTable"; diff --git a/apps/frontend/src/components/debug/SyncClosedOrdersButton.tsx b/apps/frontend/src/components/debug/SyncClosedOrdersButton.tsx deleted file mode 100644 index c7e03243..00000000 --- a/apps/frontend/src/components/debug/SyncClosedOrdersButton.tsx +++ /dev/null @@ -1,62 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import React, { useEffect } from "react"; -import clsx from "clsx"; -import Button from "@mui/joy/Button"; -import { tClient } from "src/lib/trpc/client"; -import { useSnackbar } from "src/ui/snackbar"; - -const componentName = "SyncClosedOrdersButton"; -const classes = { - root: `${componentName}-root`, -}; - -type SyncClosedOrdersButtonProps = { - className?: string; - polling?: boolean; -}; - -export const SyncClosedOrdersButton: FC = ( - props, -) => { - const { className, polling } = props; - const { showSnackbar } = useSnackbar(); - - const { isLoading, mutate, mutateAsync } = - tClient.cron.syncClosedOrders.useMutation({ - onSuccess() { - showSnackbar("Orders have been synced"); - }, - }); - - useEffect(() => { - if (!polling) return; - - const timer = setInterval(() => { - void mutateAsync().then((data) => { - console.log("response", data); - }); - }, 15000); - - return () => { - clearInterval(timer); - }; - }, [polling]); - - return ( - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/bot-details/BotSettings/BotSettings.tsx b/apps/frontend/src/components/grid-bot/bot-details/BotSettings/BotSettings.tsx deleted file mode 100644 index 17314a6b..00000000 --- a/apps/frontend/src/components/grid-bot/bot-details/BotSettings/BotSettings.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import DataObjectIcon from "@mui/icons-material/DataObject"; -import Chip from "@mui/joy/Chip"; -import List from "@mui/joy/List"; -import ListDivider from "@mui/joy/ListDivider"; -import type { FC } from "react"; -import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward"; -import ArrowUpwardIcon from "@mui/icons-material/ArrowUpward"; -import FormatListNumberedRtlIcon from "@mui/icons-material/FormatListNumberedRtl"; -import NumbersIcon from "@mui/icons-material/Numbers"; -import type { TGridBot } from "src/types/trpc"; -import { calcAverageQuantityPerGrid } from "src/utils/grid-bot/calcAverageQuantityPerGrid"; -import { findHighestGridLinePrice } from "src/utils/grid-bot/findHighestGridLinePrice"; -import { findLowestGridLinePrice } from "src/utils/grid-bot/findLowestGridLinePrice"; -import { StatusSettingsListItem } from "src/components/common/bot/settings/StatusSettingListItem"; -import { PairSettingListItem } from "src/components/common/bot/settings/PairSettingListItem"; -import { SettingListItem } from "src/components/common/bot/settings/SettingListItem"; - -type BotSettingsProps = { - bot: TGridBot; -}; - -export const BotSettings: FC = ({ bot }) => { - const lowPrice = findLowestGridLinePrice(bot.settings.gridLines); - const highPrice = findHighestGridLinePrice(bot.settings.gridLines); - const averageQuantityPerGrid = calcAverageQuantityPerGrid( - bot.settings.gridLines, - ); - - return ( - - - - - - - - - - } name="High price"> - {highPrice} {bot.quoteCurrency} - - - - - } name="Low price"> - {lowPrice} {bot.quoteCurrency} - - - - - } name="Av. quantity per grid"> - {averageQuantityPerGrid.toFixed(6)} {bot.baseCurrency} - - - - - } name="Grid levels"> - {bot.settings.gridLines.length} - - - - - } name="Template"> - - {bot.template} - - - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/bot-details/BotSettings/BotSettingsCard.tsx b/apps/frontend/src/components/grid-bot/bot-details/BotSettings/BotSettingsCard.tsx deleted file mode 100644 index 7788dca4..00000000 --- a/apps/frontend/src/components/grid-bot/bot-details/BotSettings/BotSettingsCard.tsx +++ /dev/null @@ -1,49 +0,0 @@ -"use client"; - -import Box from "@mui/joy/Box"; -import Card from "@mui/joy/Card"; -import Typography from "@mui/joy/Typography"; -import type { FC } from "react"; -import React from "react"; -import { SyncClosedOrdersButton } from "src/components/debug/SyncClosedOrdersButton"; -import { tClient } from "src/lib/trpc/client"; -import { BotAdditionalActions } from "src/components/common/bot/actions/BotAdditionalActions"; -import { RunBotTemplateButton } from "src/components/common/bot/actions/buttons/RunBotTemplateButton"; -import { StartStopBotButton } from "src/components/common/bot/actions/buttons/StartStopBotButton"; -import { DeleteBotButton } from "src/components/common/bot/actions/buttons/DeleteBotButton"; -import { BotActions } from "src/components/common/bot/actions/BotActions"; -import { BotSettings } from "./BotSettings"; - -type BotSettingsCardProps = { - botId: number; -}; - -export const BotSettingsCard: FC = ({ botId }) => { - const [bot] = tClient.gridBot.getOne.useSuspenseQuery(botId); - - return ( - - - - {bot.name} - - - - #{bot.id} - - - - - - - - - - - - - - - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/bot-details/BotSettings/index.ts b/apps/frontend/src/components/grid-bot/bot-details/BotSettings/index.ts deleted file mode 100644 index 4052877a..00000000 --- a/apps/frontend/src/components/grid-bot/bot-details/BotSettings/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./BotSettingsCard"; diff --git a/apps/frontend/src/components/grid-bot/bot-details/GridBotSettings/GridBotSettings.tsx b/apps/frontend/src/components/grid-bot/bot-details/GridBotSettings/GridBotSettings.tsx deleted file mode 100644 index 549fd789..00000000 --- a/apps/frontend/src/components/grid-bot/bot-details/GridBotSettings/GridBotSettings.tsx +++ /dev/null @@ -1,56 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import React from "react"; -import Grid from "@mui/joy/Grid"; -import Divider from "@mui/joy/Divider"; -import type { TGridBot, TSymbol } from "src/types/trpc"; -import { calcAverageQuantityPerGrid } from "src/utils/grid-bot/calcAverageQuantityPerGrid"; -import { findHighestGridLinePrice } from "src/utils/grid-bot/findHighestGridLinePrice"; -import { findLowestGridLinePrice } from "src/utils/grid-bot/findLowestGridLinePrice"; -import { SettingInput } from "./SettingInput"; - -type SimpleGridFormProps = { - bot: TGridBot; - symbol: TSymbol; -}; - -export const GridBotSettings: FC = ({ bot }) => { - const highPrice = findHighestGridLinePrice(bot.settings.gridLines); - const lowPrice = findLowestGridLinePrice(bot.settings.gridLines); - const averageQuantityPerGrid = calcAverageQuantityPerGrid( - bot.settings.gridLines, - ); - - return ( - - - Bot settings - - - - - - - - - - - - - - - - - - - - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/bot-details/GridBotSettings/SettingInput.tsx b/apps/frontend/src/components/grid-bot/bot-details/GridBotSettings/SettingInput.tsx deleted file mode 100644 index 9ac23ef9..00000000 --- a/apps/frontend/src/components/grid-bot/bot-details/GridBotSettings/SettingInput.tsx +++ /dev/null @@ -1,20 +0,0 @@ -"use client"; - -import FormControl from "@mui/joy/FormControl"; -import FormLabel from "@mui/joy/FormLabel"; -import Input from "@mui/joy/Input"; -import type { FC } from "react"; -import React from "react"; - -type SettingInputProps = { - label: string; - value: number; -}; - -export const SettingInput: FC = ({ value, label }) => ( - - {label} - - - -); diff --git a/apps/frontend/src/components/grid-bot/bot-details/GridBotSettings/index.ts b/apps/frontend/src/components/grid-bot/bot-details/GridBotSettings/index.ts deleted file mode 100644 index f81bed0d..00000000 --- a/apps/frontend/src/components/grid-bot/bot-details/GridBotSettings/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./GridBotSettings"; diff --git a/apps/frontend/src/components/grid-bot/bot-details/GridDetailChart/GridDetailChart.tsx b/apps/frontend/src/components/grid-bot/bot-details/GridDetailChart/GridDetailChart.tsx deleted file mode 100644 index 94c95c78..00000000 --- a/apps/frontend/src/components/grid-bot/bot-details/GridDetailChart/GridDetailChart.tsx +++ /dev/null @@ -1,109 +0,0 @@ -"use client"; - -import Box from "@mui/joy/Box"; -import Skeleton from "@mui/joy/Skeleton"; -import { composeSymbolId } from "@opentrader/tools"; -import type { FC } from "react"; -import React, { Suspense, useMemo, useState } from "react"; -import type { BarSize } from "@opentrader/types"; -import { tClient } from "src/lib/trpc/client"; -import { Chart, CHART_HEIGHT, ChartOptions } from "src/ui/charts/Chart"; -import { FlexSpacer } from "src/ui/FlexSpacer"; -import { ExchangeAccountSelect } from "src/ui/selects/ExchangeAccountSelect"; -import { SymbolSelect } from "src/ui/selects/SymbolSelect"; -import { BarSizeSelect } from "src/ui/selects/BarSizeSelect"; -import { computePriceLines } from "./utils/computePriceLines"; -import { computeTradeMarkers } from "./utils/computeTradeMarkers"; - -const timeframes = ["1d", "4h", "1h", "5m", "1m"] as const; -export type ChartBarSize = Extract; - -type GridChartProps = { - botId: number; -}; - -const NOOP = () => void 0; - -export const GridDetailChart: FC = ({ botId }) => { - const [bot] = tClient.gridBot.getOne.useSuspenseQuery(botId); - const [exchangeAccount] = tClient.exchangeAccount.getOne.useSuspenseQuery( - bot.exchangeAccountId, - ); - const [symbol] = tClient.symbol.getOne.useSuspenseQuery({ - symbolId: composeSymbolId( - exchangeAccount.exchangeCode, - bot.baseCurrency, - bot.quoteCurrency, - ), - }); - const [activeSmartTrades] = tClient.bot.activeSmartTrades.useSuspenseQuery({ - botId, - }); - const [completedSmartTrades] = - tClient.bot.completedSmartTrades.useSuspenseQuery({ - botId, - }); - - const priceLines = useMemo( - () => computePriceLines(activeSmartTrades), - [activeSmartTrades], - ); - - const [barSize, setBarSize] = useState("1h"); - const [showPriceLines, setShowPriceLines] = useState(true); - - const tradeMarkers = useMemo( - () => computeTradeMarkers(completedSmartTrades, barSize), - [completedSmartTrades, barSize], - ); - const [showTradeMarkers, setShowTradeMarkers] = useState(false); - - return ( - - } - > - - - - - - { - setBarSize(value); - }} - value={barSize} - whitelist={timeframes} - /> - - - - - - - - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/bot-details/GridDetailChart/index.ts b/apps/frontend/src/components/grid-bot/bot-details/GridDetailChart/index.ts deleted file mode 100644 index d56e3a6a..00000000 --- a/apps/frontend/src/components/grid-bot/bot-details/GridDetailChart/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./GridDetailChart"; diff --git a/apps/frontend/src/components/grid-bot/bot-details/GridDetailChart/utils/computePriceLines.ts b/apps/frontend/src/components/grid-bot/bot-details/GridDetailChart/utils/computePriceLines.ts deleted file mode 100644 index b6cf57f6..00000000 --- a/apps/frontend/src/components/grid-bot/bot-details/GridDetailChart/utils/computePriceLines.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { CreatePriceLineOptions } from "lightweight-charts"; -import type { TActiveSmartTrade } from "src/types/trpc"; -import { computePriceLine } from "src/utils/charts"; -import { getWaitingGridLinePrice } from "src/utils/grid-bot/getWaitingGridLinePrice"; - -export function computePriceLines( - smartTrades: TActiveSmartTrade[], -): CreatePriceLineOptions[] { - if (smartTrades.length === 0) { - return []; - } - - const waitingPriceLine = getWaitingGridLinePrice(smartTrades); - const otherPriceLines = smartTrades.map((smartTrade) => { - const isPositionOpen = smartTrade.entryOrder.status === "Filled"; - - if (isPositionOpen) { - return smartTrade.takeProfitOrder.price!; - } - - return smartTrade.entryOrder.price!; - }); - - const priceLines = [...otherPriceLines, waitingPriceLine].sort( - (left, right) => left - right, - ); - - return priceLines.map((priceLine) => - computePriceLine(priceLine, priceLines, waitingPriceLine), - ); -} diff --git a/apps/frontend/src/components/grid-bot/bot-details/GridDetailChart/utils/computeTradeMarkers.ts b/apps/frontend/src/components/grid-bot/bot-details/GridDetailChart/utils/computeTradeMarkers.ts deleted file mode 100644 index ac3a53db..00000000 --- a/apps/frontend/src/components/grid-bot/bot-details/GridDetailChart/utils/computeTradeMarkers.ts +++ /dev/null @@ -1,130 +0,0 @@ -import Big from "big.js"; -import type { TCompletedSmartTrade } from "src/types/trpc"; -import { computeMarker } from "src/utils/charts/marker"; -import { roundTimestamp } from "src/utils/charts/barSize"; -import type { ChartBarSize } from "../GridDetailChart"; - -type Order = { - smartTradeId: number; - orderId: number; - quantity: number; - filledPrice: number; - filledAt: number; - side: "buy" | "sell"; -}; - -type Marker = { - timestamp: number; - ordersCount: number; - averagePrice: number; - side: "buy" | "sell"; -}; - -type MarkersMap = Record; - -/** - * Creates orders buy/sell markers for `lightweight-charts` - * - * @param smartTrades - SmartTrades - * @param timeframe - Timeframe - */ -export function computeTradeMarkers( - smartTrades: TCompletedSmartTrade[], - timeframe: ChartBarSize, -) { - const orders = computeOrders(smartTrades); - - const buyMarkers = stackOrders( - orders.filter((order) => order.side === "buy"), - timeframe, - ); - const sellMarkers = stackOrders( - orders.filter((order) => order.side === "sell"), - timeframe, - ); - - const markers = [...buyMarkers, ...sellMarkers].sort( - (left, right) => left.timestamp - right.timestamp, - ); - - return markers.map((marker) => - computeMarker(marker.ordersCount, marker.timestamp, marker.side), - ); -} - -/** - * 1. Decompose buy/sell order of a SmartTrade - * 2. Merge them into a flat array - * 3. Sort by timestamp - * - * @param smartTrades - SmartTrades - */ -function computeOrders(smartTrades: TCompletedSmartTrade[]): Order[] { - if (smartTrades.length === 0) { - return []; - } - - const orders = smartTrades - .flatMap((smartTrade) => [ - smartTrade.entryOrder, - smartTrade.takeProfitOrder, - ]) - // normalize the data - .map((order) => { - if (order.filledPrice === null) { - throw new Error("computeOrders: filledPrice is null"); - } - - const normalizedOrder: Order = { - smartTradeId: order.smartTradeId, - orderId: order.id, - quantity: order.quantity, - filledPrice: order.filledPrice, - filledAt: roundTimestamp(order.filledAt.getTime(), "1m"), - side: order.side === "Buy" ? "buy" : "sell", - }; - - return normalizedOrder; - }) - // lightweight-charts: data must be asc ordered by time - .sort((left, right) => left.filledAt - right.filledAt); - - return orders; -} - -/** - * Stack multiple orders on same candlestick into one `Marker`. - * - * @param orders - Orders - * @param timeframe - Timeframe - */ -function stackOrders(orders: Order[], timeframe: ChartBarSize): Marker[] { - const markersMap = orders.reduce((acc, order) => { - const timestamp = roundTimestamp(order.filledAt, timeframe); - let marker = acc[timestamp]; - - if (marker) { - marker.ordersCount += 1; - marker.averagePrice = Number( - Big(marker.averagePrice + order.filledPrice) - .div(2) - .toNumber() - .toFixed(4), - ); - } else { - marker = { - timestamp, - ordersCount: 1, - averagePrice: order.filledPrice, - side: order.side, - }; - } - - return { - ...acc, - [timestamp]: marker, - }; - }, {}); - - return Object.values(markersMap); -} diff --git a/apps/frontend/src/components/grid-bot/bot-details/loading.tsx b/apps/frontend/src/components/grid-bot/bot-details/loading.tsx deleted file mode 100644 index 576db806..00000000 --- a/apps/frontend/src/components/grid-bot/bot-details/loading.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import Grid from "@mui/joy/Grid"; -import Skeleton from "@mui/joy/Skeleton"; -import React from "react"; - -export default function Loading() { - return ( - - - - - - - - - - ); -} diff --git a/apps/frontend/src/components/grid-bot/bot-details/page.tsx b/apps/frontend/src/components/grid-bot/bot-details/page.tsx deleted file mode 100644 index 717473b5..00000000 --- a/apps/frontend/src/components/grid-bot/bot-details/page.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import Grid from "@mui/joy/Grid"; -import Skeleton from "@mui/joy/Skeleton"; -import React, { Suspense } from "react"; -import { BotSettingsCard } from "src/components/grid-bot/bot-details/BotSettings"; -import { GridDetailChart } from "src/components/grid-bot/bot-details/GridDetailChart"; -import { ProfitsCard } from "src/components/common/smart-trades/ProfitsCard"; -import { SmartTradesTable } from "src/components/common/smart-trades/SmartTradesTable"; -import { CHART_HEIGHT } from "src/ui/charts/Chart"; - -type Props = { - botId: number; -}; - -export default function BotDetailsPage(props: Props) { - const { botId } = props; - - return ( - - - - } - > - - - - - - - } - > - - - - - - - } - > - - - - - - - } - > - - - - - ); -} diff --git a/apps/frontend/src/components/grid-bot/bots-list/BotCard/BotCard.tsx b/apps/frontend/src/components/grid-bot/bots-list/BotCard/BotCard.tsx deleted file mode 100644 index 10043f43..00000000 --- a/apps/frontend/src/components/grid-bot/bots-list/BotCard/BotCard.tsx +++ /dev/null @@ -1,166 +0,0 @@ -"use client"; - -import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward"; -import ArrowUpwardIcon from "@mui/icons-material/ArrowUpward"; -import FormatListNumberedRtlIcon from "@mui/icons-material/FormatListNumberedRtl"; -import NumbersIcon from "@mui/icons-material/Numbers"; -import Box from "@mui/joy/Box"; -import Card from "@mui/joy/Card"; -import CardContent from "@mui/joy/CardContent"; -import Divider from "@mui/joy/Divider"; -import Link from "next/link"; -import List from "@mui/joy/List"; -import ListItem from "@mui/joy/ListItem"; -import ListItemDecorator from "@mui/joy/ListItemDecorator"; -import ListItemContent from "@mui/joy/ListItemContent"; -import Tooltip from "@mui/joy/Tooltip"; -import Typography from "@mui/joy/Typography"; -import clsx from "clsx"; -import type { FC } from "react"; -import { styled } from "@mui/joy/styles"; -import type { SxProps } from "@mui/joy/styles/types"; -import type { TGridBot } from "src/types/trpc"; -import { calcAverageQuantityPerGrid } from "src/utils/grid-bot/calcAverageQuantityPerGrid"; -import { findHighestGridLinePrice } from "src/utils/grid-bot/findHighestGridLinePrice"; -import { findLowestGridLinePrice } from "src/utils/grid-bot/findLowestGridLinePrice"; -import { toPage } from "src/utils/next/toPage"; -import { BotStatusIndicator } from "./BotStatusIndicator"; -import { Bull } from "./Bull"; - -const componentName = "BotCard"; -const classes = { - root: `${componentName}-root`, - botTitle: `${componentName}-bot-title`, -}; - -const Root = styled(Card)(() => ({ - /* Styles applied to the root element. */ - [`& .${classes.root}`]: {}, - [`& .${classes.botTitle}`]: { - color: "inherit", - textDecoration: "none", - - "&:hover": { - cursor: "pointer", - textDecoration: "underline", - }, - }, -})); - -export type BotCardProps = { - className?: string; - bot: TGridBot; - sx?: SxProps; -}; - -export const BotCard: FC = (props) => { - const { bot, sx, className } = props; - - // @todo - // const initialInvestmentInQuote = calcInitialInvestmentInQuote( - // bot.initialInvestment - // ); - const initialInvestmentInQuote = "hz"; // @todo - const averageQuantityPerGrid = calcAverageQuantityPerGrid( - bot.settings.gridLines, - ); - const gridLevels = bot.settings.gridLines.length; - - const lowPrice = findLowestGridLinePrice(bot.settings.gridLines); - const highPrice = findHighestGridLinePrice(bot.settings.gridLines); - - return ( - - - - - - {bot.name} - - - - - - - - {bot.baseCurrency}/{bot.quoteCurrency} {" "} - - - {initialInvestmentInQuote} {bot.quoteCurrency} - - - - - - - - - - - - - - - - High price - - {`${highPrice} ${bot.quoteCurrency}`} - - - - - - - - - - Low price - - {`${lowPrice} ${bot.quoteCurrency}`} - - - - - - - - - - - - Avg. Qty. per grid - - {`${averageQuantityPerGrid.toFixed(4)} ${bot.baseCurrency}`} - - - - - - - - - - - Grid levels - - {gridLevels} - - - - - - - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/bots-list/BotCard/BotStatusIndicator.tsx b/apps/frontend/src/components/grid-bot/bots-list/BotCard/BotStatusIndicator.tsx deleted file mode 100644 index 5e2e0f78..00000000 --- a/apps/frontend/src/components/grid-bot/bots-list/BotCard/BotStatusIndicator.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import FiberManualRecordIcon from "@mui/icons-material/FiberManualRecord"; -import type { SxProps } from "@mui/joy/styles/types"; -import type { FC } from "react"; -import React from "react"; -import clsx from "clsx"; -import Chip from "@mui/joy/Chip"; -import { styled } from "@mui/joy/styles"; -import type { TGridBot } from "src/types/trpc"; - -const componentName = "BotStatusIndicator"; -const classes = { - root: `${componentName}-root`, -}; -const StyledChip = styled(Chip)(() => ({ - /* Styles applied to the root element. */ - [`&.${classes.root}`]: {}, -})); - -type BotStatusIndicatorProps = { - className?: string; - bot: TGridBot; - sx?: SxProps; -}; - -export const BotStatusIndicator: FC = (props) => { - const { className, bot, sx } = props; - - if (bot.enabled) { - return ( - } - sx={sx} - variant="soft" - > - Running - - ); - } - - return ( - } - sx={sx} - variant="soft" - > - Disabled - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/bots-list/BotCard/Bull.tsx b/apps/frontend/src/components/grid-bot/bots-list/BotCard/Bull.tsx deleted file mode 100644 index 69c5785d..00000000 --- a/apps/frontend/src/components/grid-bot/bots-list/BotCard/Bull.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import type { FC } from "react"; -import Box from "@mui/joy/Box"; - -export const Bull: FC = () => { - return ( - - • - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/bots-list/BotCard/index.ts b/apps/frontend/src/components/grid-bot/bots-list/BotCard/index.ts deleted file mode 100644 index ea6d693c..00000000 --- a/apps/frontend/src/components/grid-bot/bots-list/BotCard/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./BotCard"; diff --git a/apps/frontend/src/components/grid-bot/bots-list/BotList.tsx b/apps/frontend/src/components/grid-bot/bots-list/BotList.tsx deleted file mode 100644 index 0e76d6e1..00000000 --- a/apps/frontend/src/components/grid-bot/bots-list/BotList.tsx +++ /dev/null @@ -1,21 +0,0 @@ -"use client"; - -import Grid from "@mui/joy/Grid"; -import type { FC } from "react"; -import React from "react"; -import { BotCard } from "src/components/grid-bot/bots-list/BotCard"; -import { tClient } from "src/lib/trpc/client"; - -export const BotList: FC = () => { - const [bots] = tClient.gridBot.list.useSuspenseQuery(); - - return ( - - {bots.map((bot) => ( - - - - ))} - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/bots-list/BotListSkeleton.tsx b/apps/frontend/src/components/grid-bot/bots-list/BotListSkeleton.tsx deleted file mode 100644 index 0eabe8cf..00000000 --- a/apps/frontend/src/components/grid-bot/bots-list/BotListSkeleton.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import Grid from "@mui/joy/Grid"; -import Skeleton from "@mui/joy/Skeleton"; -import type { FC } from "react"; -import React from "react"; - -const BOTS_LENGTH = 8; - -export const BotListSkeleton: FC = () => { - const bots = Array.from({ length: BOTS_LENGTH }); - - return ( - - {bots.map((_, i) => ( - - - - ))} - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/bots-list/loading.tsx b/apps/frontend/src/components/grid-bot/bots-list/loading.tsx deleted file mode 100644 index 3cabd4a5..00000000 --- a/apps/frontend/src/components/grid-bot/bots-list/loading.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from "react"; -import Button from "@mui/joy/Button"; -import Grid from "@mui/joy/Grid"; -import NextLink from "next/link"; -import { toPage } from "src/utils/next/toPage"; -import { BotListSkeleton } from "./BotListSkeleton"; - -export default function Loading() { - return ( - - - - - - - - - - ); -} diff --git a/apps/frontend/src/components/grid-bot/bots-list/page.tsx b/apps/frontend/src/components/grid-bot/bots-list/page.tsx deleted file mode 100644 index ccb21cf0..00000000 --- a/apps/frontend/src/components/grid-bot/bots-list/page.tsx +++ /dev/null @@ -1,27 +0,0 @@ -"use client"; - -import NextLink from "next/link"; -import Button from "@mui/joy/Button"; -import Grid from "@mui/joy/Grid"; -import React, { Suspense } from "react"; -import { BotList } from "src/components/grid-bot/bots-list/BotList"; -import { BotListSkeleton } from "src/components/grid-bot/bots-list/BotListSkeleton"; -import { toPage } from "src/utils/next/toPage"; - -export default function BotPage() { - return ( - - - - - - - }> - - - - - ); -} diff --git a/apps/frontend/src/components/grid-bot/create-bot/GridChart/GridChart.tsx b/apps/frontend/src/components/grid-bot/create-bot/GridChart/GridChart.tsx deleted file mode 100644 index 0c53ac78..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/GridChart/GridChart.tsx +++ /dev/null @@ -1,91 +0,0 @@ -"use client"; - -import Box from "@mui/joy/Box"; -import Skeleton from "@mui/joy/Skeleton"; -import type { IGridLine } from "@opentrader/types"; -import type { FC } from "react"; -import React, { Suspense, useDeferredValue, useMemo, useState } from "react"; -import type { GridBotFormChartBarSize } from "src/store/bot-form"; -import { TIMEFRAMES } from "src/store/bot-form/constants"; -import { Chart, ChartOptions } from "src/ui/charts/Chart"; -import { CHART_HEIGHT } from "src/ui/charts/Chart/constants"; -import { ExchangeAccountField } from "src/components/grid-bot/create-bot/form/fields/ExchangeAccountField"; -import { PairField } from "src/components/grid-bot/create-bot/form/fields/PairField"; -import { FlexSpacer } from "src/ui/FlexSpacer"; -import { BarSizeSelect } from "src/ui/selects/BarSizeSelect"; -import { InputSkeleton } from "src/ui/InputSkeleton"; -import { computePriceLines } from "./utils"; - -type GridChartProps = { - symbolId: string; - barSize: GridBotFormChartBarSize; - onBarSizeChange?: (value: GridBotFormChartBarSize) => void; - gridLines: IGridLine[]; - currentAssetPrice: number; -}; - -export const GridChart: FC = ({ - symbolId, - barSize, - onBarSizeChange, - gridLines, - currentAssetPrice, -}) => { - const deferredSymbolId = useDeferredValue(symbolId); - const isStale = symbolId !== deferredSymbolId; - - const priceLines = useMemo( - () => computePriceLines(gridLines, currentAssetPrice), - [gridLines, currentAssetPrice], - ); - const [showPriceLines, setShowPriceLines] = useState(true); - - return ( - - } - > - - }> - - - - }> - - - - { - if (onBarSizeChange) { - onBarSizeChange(value); - } - }} - value={barSize} - whitelist={TIMEFRAMES} - /> - - - - - - - - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/GridChart/index.ts b/apps/frontend/src/components/grid-bot/create-bot/GridChart/index.ts deleted file mode 100644 index f9855c40..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/GridChart/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./GridChart"; diff --git a/apps/frontend/src/components/grid-bot/create-bot/GridChart/utils.ts b/apps/frontend/src/components/grid-bot/create-bot/GridChart/utils.ts deleted file mode 100644 index 29da361c..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/GridChart/utils.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { IGridLine } from "@opentrader/types"; -import type { CreatePriceLineOptions } from "lightweight-charts"; -import { computePriceLine } from "src/utils/charts"; -import { waitingPriceFromCurrentAssetPrice } from "src/utils/grid-bot/waitingPriceFromCurrentAssetPrice"; - -export function computePriceLines( - gridLines: IGridLine[], - currentAssetPrice: number, -): CreatePriceLineOptions[] { - const prices = gridLines.map((gridLine) => gridLine.price); - const waitingPrice = waitingPriceFromCurrentAssetPrice( - prices, - currentAssetPrice, - ); - - return prices.map((price) => computePriceLine(price, prices, waitingPrice)); -} diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/AdvancedGridForm/AdvancedGridForm.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/AdvancedGridForm/AdvancedGridForm.tsx deleted file mode 100644 index e216eca7..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/AdvancedGridForm/AdvancedGridForm.tsx +++ /dev/null @@ -1,40 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import React from "react"; -import Grid from "@mui/joy/Grid"; -import Button from "@mui/joy/Button"; -import { addGridLine } from "src/store/bot-form"; -import { selectGridLines } from "src/store/bot-form/selectors"; -import { useAppDispatch, useAppSelector } from "src/store/hooks"; -import { AdvancedGridFormItem } from "./AdvancedGridFormItem"; - -export const AdvancedGridForm: FC = () => { - const dispatch = useAppDispatch(); - const gridLines = useAppSelector(selectGridLines); - - const handleAddGridLine = () => { - dispatch( - addGridLine({ - price: 1, // @todo calc from store - quantity: 1, // @todo calc from store - }), - ); - }; - - const inverseIndex = (index: number) => gridLines.length - 1 - index; - - return ( - - - {gridLines.map((gridLine, i) => ( - - ))} - - - - - - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/AdvancedGridForm/AdvancedGridFormItem.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/AdvancedGridForm/AdvancedGridFormItem.tsx deleted file mode 100644 index 74dba3fa..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/AdvancedGridForm/AdvancedGridFormItem.tsx +++ /dev/null @@ -1,50 +0,0 @@ -import Grid from "@mui/joy/Grid"; -import Skeleton from "@mui/joy/Skeleton"; -import type { FC } from "react"; -import React, { Suspense } from "react"; -import { selectGridLines } from "src/store/bot-form/selectors"; -import { useAppSelector } from "src/store/hooks"; -import { RemoveGridLineButton } from "./RemoveGridLineButton"; -import { GridLinePriceField } from "./fields/GridLinePriceField"; -import { GridLineQuantityField } from "./fields/GridLineQuantityField"; - -type AdvancedGridFormItemProps = { - gridLineIndex: number; -}; - -export const AdvancedGridFormItem: FC = (props) => { - const { gridLineIndex } = props; - - const gridLines = useAppSelector(selectGridLines); - - // The last gridLine is just a SELL order, - // so the quantity is specified in the prev gridLine. - const isDisabled = gridLineIndex === gridLines.length - 1; - - return ( - - - } - > - - - - - - } - > - - - - - - - - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/AdvancedGridForm/RemoveGridLineButton.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/AdvancedGridForm/RemoveGridLineButton.tsx deleted file mode 100644 index e68f420e..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/AdvancedGridForm/RemoveGridLineButton.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import Button from "@mui/joy/Button"; -import type { FC } from "react"; -import React from "react"; -import { removeGridLine } from "src/store/bot-form"; -import { useAppDispatch } from "src/store/hooks"; - -type RemoveGridLineButtonProps = { - gridLineIndex: number; - className?: string; -}; - -export const RemoveGridLineButton: FC = (props) => { - const { className, gridLineIndex } = props; - - const dispatch = useAppDispatch(); - - const handleRemove = () => { - dispatch(removeGridLine(gridLineIndex)); - }; - - return ( - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/AdvancedGridForm/fields/GridLinePriceField.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/AdvancedGridForm/fields/GridLinePriceField.tsx deleted file mode 100644 index d8064bd7..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/AdvancedGridForm/fields/GridLinePriceField.tsx +++ /dev/null @@ -1,61 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import React, { useEffect, useState } from "react"; -import { PriceInput } from "src/ui/inputs/PriceInput"; -import { tClient } from "src/lib/trpc/client"; -import { updateGridLinePrice } from "src/store/bot-form"; -import { selectGridLine, selectSymbolId } from "src/store/bot-form/selectors"; -import { useAppDispatch, useAppSelector } from "src/store/hooks"; - -type GridLinePriceFieldProps = { - gridLineIndex: number; - className?: string; -}; - -export const GridLinePriceField: FC = (props) => { - const { className, gridLineIndex } = props; - - const dispatch = useAppDispatch(); - - const { price: reduxValue } = useAppSelector(selectGridLine(gridLineIndex)); - const [value, setValue] = useState(String(reduxValue)); - useEffect(() => { - setValue(`${reduxValue}`); - }, [reduxValue]); - - const symbolId = useAppSelector(selectSymbolId); - const [symbol] = tClient.symbol.getOne.useSuspenseQuery({ - symbolId, - }); - - const handleChange = (e: React.ChangeEvent) => { - setValue(e.target.value); - }; - - const handleBlur = () => { - if (value.length > 0) { - dispatch( - updateGridLinePrice({ - gridLineIndex, - price: Number(value), - }), - ); - } else { - setValue(String(reduxValue)); - } - }; - - return ( - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/AdvancedGridForm/fields/GridLineQuantityField.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/AdvancedGridForm/fields/GridLineQuantityField.tsx deleted file mode 100644 index 1ff215c7..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/AdvancedGridForm/fields/GridLineQuantityField.tsx +++ /dev/null @@ -1,67 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import React, { useEffect, useState } from "react"; -import { QuantityInput } from "src/ui/inputs/QuantityInput"; -import { tClient } from "src/lib/trpc/client"; -import { updateGridLineQuantity } from "src/store/bot-form"; -import { selectGridLine, selectSymbolId } from "src/store/bot-form/selectors"; -import { useAppDispatch, useAppSelector } from "src/store/hooks"; - -type GridLineQuantityFieldProps = { - gridLineIndex: number; - disabled?: boolean; - className?: string; -}; - -export const GridLineQuantityField: FC = ( - props, -) => { - const { className, gridLineIndex, disabled } = props; - - const dispatch = useAppDispatch(); - - const symbolId = useAppSelector(selectSymbolId); - const [symbol] = tClient.symbol.getOne.useSuspenseQuery({ - symbolId, - }); - - const { quantity: reduxValue } = useAppSelector( - selectGridLine(gridLineIndex), - ); - const [value, setValue] = useState(`${reduxValue}`); - useEffect(() => { - setValue(`${reduxValue}`); - }, [reduxValue]); - - const handleChange = (e: React.ChangeEvent) => { - setValue(e.target.value); - }; - - const handleBlur = () => { - if (!isNaN(Number(value))) { - dispatch( - updateGridLineQuantity({ - gridLineIndex, - quantity: Number(value), - }), - ); - } else { - setValue(`${reduxValue}`); - } - }; - - return ( - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/AdvancedGridForm/index.ts b/apps/frontend/src/components/grid-bot/create-bot/form/AdvancedGridForm/index.ts deleted file mode 100644 index 28e22017..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/AdvancedGridForm/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./AdvancedGridForm"; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/CreateGridForm.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/CreateGridForm.tsx deleted file mode 100644 index 8f63a756..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/CreateGridForm.tsx +++ /dev/null @@ -1,136 +0,0 @@ -"use client"; - -import Box from "@mui/joy/Box"; -import Divider from "@mui/joy/Divider"; -import Skeleton from "@mui/joy/Skeleton"; -import { calcGridLinesWithPriceFilter } from "@opentrader/tools"; -import type { FC } from "react"; -import React, { Suspense } from "react"; -import Grid from "@mui/joy/Grid"; -import Card from "@mui/joy/Card"; -import { InputSkeleton } from "src/ui/InputSkeleton"; -import { useIsStale } from "src/hooks/useIsStale"; -import { tClient } from "src/lib/trpc/client"; -import { - setHighPrice, - setLowPrice, - setQuantityPerGrid, - updateGridLines, -} from "src/store/bot-form"; -import { - selectGridLinesNumber, - selectHighPrice, - selectLowPrice, - selectQuantityPerGrid, - selectSymbolId, -} from "src/store/bot-form/selectors"; -import { useAppDispatch, useAppSelector } from "src/store/hooks"; -import { SubmitButton } from "./SubmitButton"; -import { FormTypeTabs } from "./FormTypeTabs"; -import { BotNameField } from "./fields/BotNameField"; -import { AdvancedGridForm } from "./AdvancedGridForm"; -import { InvestmentField } from "./fields/InvestmentField"; -import { SimpleGridForm } from "./SimpleGridForm"; - -export const CreateGridBotForm: FC = () => { - const dispatch = useAppDispatch(); - - // Update `quantityPerGrid` when symbol change - const symbolId = useAppSelector(selectSymbolId); - const { data: symbol } = tClient.symbol.getOne.useQuery( - { symbolId }, - { - refetchOnWindowFocus: false, - }, - ); - if (useIsStale(symbol)) { - if (symbol) { - const quantityPerGrid = - symbol.filters.limits.amount?.min?.toString() || ""; - dispatch(setQuantityPerGrid(quantityPerGrid)); - } - } - - // Update high/low prices when symbol change - const { data: options } = tClient.gridBot.formOptions.useQuery( - { symbolId }, - { - refetchOnWindowFocus: false, - }, - ); - if (useIsStale(options)) { - if (options) { - dispatch(setHighPrice(options.highPrice)); - dispatch(setLowPrice(options.lowPrice)); - } - } - - // Recalculate grid lines - const highPrice = useAppSelector(selectHighPrice); - const lowPrice = useAppSelector(selectLowPrice); - const gridLinesNumber = useAppSelector(selectGridLinesNumber); - const quantityPerGrid = useAppSelector(selectQuantityPerGrid); - - const isStale = [ - useIsStale(highPrice), - useIsStale(lowPrice), - useIsStale(gridLinesNumber), - useIsStale(quantityPerGrid), - useIsStale(symbol), - ].includes(true); - - if (isStale) { - if (symbol) { - const gridLines = calcGridLinesWithPriceFilter( - highPrice, - lowPrice, - gridLinesNumber, - Number(quantityPerGrid), - symbol.filters, - ); - - dispatch(updateGridLines(gridLines)); - } - } - - return ( - - - } - > - - - } - simpleForm={} - /> - - - - - - - }> - - - - - - - - - - - - - - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/FormTypeTabs.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/FormTypeTabs.tsx deleted file mode 100644 index 9c853c46..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/FormTypeTabs.tsx +++ /dev/null @@ -1,83 +0,0 @@ -import Tab, { tabClasses } from "@mui/joy/Tab"; -import TabList from "@mui/joy/TabList"; -import Tabs from "@mui/joy/Tabs"; -import TabPanel from "@mui/joy/TabPanel"; -import type { FC, ReactNode } from "react"; -import React from "react"; -import type { GridBotFormType } from "src/store/bot-form"; -import { changeFormType } from "src/store/bot-form"; -import { selectFormType } from "src/store/bot-form/selectors"; -import { useAppDispatch, useAppSelector } from "src/store/hooks"; - -const FormTypes: Record = { - simple: "simple", - advanced: "advanced", -}; - -type FormTypeTabsProps = { - simpleForm: ReactNode; - advancedForm: ReactNode; -}; - -export const FormTypeTabs: FC = ({ - simpleForm, - advancedForm, -}) => { - const formType = useAppSelector(selectFormType); - const dispatch = useAppDispatch(); - - const handleChange = ( - event: React.SyntheticEvent | null, - newValue: GridBotFormType | null, - ) => { - // disallow un-toggling current button - if (newValue === null) return; - - dispatch(changeFormType(newValue)); - }; - - return ( - { - handleChange(event, newValue as GridBotFormType); - }} - sx={{ - borderRadius: "md", - overflow: "auto", - boxShadow: "sm", - }} - value={formType} - > - - - Easy form - - - Advanced form - - - - {simpleForm} - {advancedForm} - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/SimpleGridForm/SimpleGridForm.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/SimpleGridForm/SimpleGridForm.tsx deleted file mode 100644 index e0b61b32..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/SimpleGridForm/SimpleGridForm.tsx +++ /dev/null @@ -1,38 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import React, { Suspense } from "react"; -import Grid from "@mui/joy/Grid"; -import { InputSkeleton } from "src/ui/InputSkeleton"; -import { QuantityPerGridField } from "./fields/QuantityPerGridField"; -import { GridLevelsField } from "./fields/GridLevelsField"; -import { HighPriceField } from "./fields/HighPriceField"; -import { LowPriceField } from "./fields/LowPriceField"; - -export const SimpleGridForm: FC = () => { - return ( - - - }> - - - - - - }> - - - - - - }> - - - - - - - - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/SimpleGridForm/fields/GridLevelsField.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/SimpleGridForm/fields/GridLevelsField.tsx deleted file mode 100644 index aafd8449..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/SimpleGridForm/fields/GridLevelsField.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import type { FC } from "react"; -import React, { useState } from "react"; -import FormControl from "@mui/joy/FormControl"; -import FormLabel from "@mui/joy/FormLabel"; -import Input from "@mui/joy/Input"; -import { changeGridLinesNumber } from "src/store/bot-form"; -import { selectGridLinesNumber } from "src/store/bot-form/selectors"; -import { useAppDispatch, useAppSelector } from "src/store/hooks"; - -type GridLevelsFieldProps = { - disabled?: boolean; - readOnly?: boolean; -}; - -const fieldName = "gridLevels" as const; - -export const GridLevelsField: FC = (props) => { - const { disabled, readOnly } = props; - const dispatch = useAppDispatch(); - - const reduxValue = useAppSelector(selectGridLinesNumber); - const [value, setValue] = useState(reduxValue); - - const handleChange = (e: React.ChangeEvent) => { - setValue(e.target.valueAsNumber); - }; - - const handleBlur = () => { - if (Number.isInteger(value)) { - dispatch(changeGridLinesNumber(value)); - } else { - setValue(reduxValue); - } - }; - - return ( - - Grid levels - - - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/SimpleGridForm/fields/HighPriceField.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/SimpleGridForm/fields/HighPriceField.tsx deleted file mode 100644 index 7542a27d..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/SimpleGridForm/fields/HighPriceField.tsx +++ /dev/null @@ -1,55 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import React, { useEffect, useState } from "react"; -import { PriceInput } from "src/ui/inputs/PriceInput"; -import { tClient } from "src/lib/trpc/client"; -import { changeHighPrice } from "src/store/bot-form"; -import { selectHighPrice, selectSymbolId } from "src/store/bot-form/selectors"; -import { useAppDispatch, useAppSelector } from "src/store/hooks"; - -type HighPriceFieldProps = { - disabled?: boolean; - readOnly?: boolean; -}; - -export const HighPriceField: FC = (props) => { - const { disabled, readOnly } = props; - - const symbolId = useAppSelector(selectSymbolId); - const [symbol] = tClient.symbol.getOne.useSuspenseQuery({ symbolId }); - - const dispatch = useAppDispatch(); - - const reduxValue = useAppSelector(selectHighPrice); - const [value, setValue] = useState(`${reduxValue}`); - - useEffect(() => { - setValue(`${reduxValue}`); - }, [reduxValue]); - - const handleChange = (e: React.ChangeEvent) => { - setValue(e.target.value); - }; - - const handleBlur = () => { - if (value.length > 0) { - dispatch(changeHighPrice(Number(value))); - } else { - setValue(`${reduxValue}`); - } - }; - - return ( - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/SimpleGridForm/fields/LowPriceField.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/SimpleGridForm/fields/LowPriceField.tsx deleted file mode 100644 index 67f840c3..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/SimpleGridForm/fields/LowPriceField.tsx +++ /dev/null @@ -1,54 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import React, { useEffect, useState } from "react"; -import { PriceInput } from "src/ui/inputs/PriceInput"; -import { tClient } from "src/lib/trpc/client"; -import { changeLowPrice } from "src/store/bot-form"; -import { selectLowPrice, selectSymbolId } from "src/store/bot-form/selectors"; -import { useAppDispatch, useAppSelector } from "src/store/hooks"; - -type LowPriceFieldProps = { - disabled?: boolean; - readOnly?: boolean; -}; - -export const LowPriceField: FC = (props) => { - const { disabled, readOnly } = props; - const symbolId = useAppSelector(selectSymbolId); - const [symbol] = tClient.symbol.getOne.useSuspenseQuery({ symbolId }); - - const dispatch = useAppDispatch(); - - const reduxValue = useAppSelector(selectLowPrice); - const [value, setValue] = useState(`${reduxValue}`); - - useEffect(() => { - setValue(`${reduxValue}`); - }, [reduxValue]); - - const handleChange = (e: React.ChangeEvent) => { - setValue(e.target.value); - }; - - const handleBlur = () => { - if (value.length > 0) { - dispatch(changeLowPrice(Number(value))); - } else { - setValue(`${reduxValue}`); - } - }; - - return ( - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/SimpleGridForm/fields/QuantityPerGridField.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/SimpleGridForm/fields/QuantityPerGridField.tsx deleted file mode 100644 index 7aa500bb..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/SimpleGridForm/fields/QuantityPerGridField.tsx +++ /dev/null @@ -1,58 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import React, { useEffect, useState } from "react"; -import { tClient } from "src/lib/trpc/client"; -import { changeQuantityPerGrid } from "src/store/bot-form"; -import { - selectQuantityPerGrid, - selectSymbolId, -} from "src/store/bot-form/selectors"; -import { useAppDispatch, useAppSelector } from "src/store/hooks"; -import { QuantityInput } from "src/ui/inputs/QuantityInput"; - -type QuantityPerGridFieldProps = { - disabled?: boolean; - readOnly?: boolean; -}; - -export const QuantityPerGridField: FC = (props) => { - const { disabled, readOnly } = props; - const symbolId = useAppSelector(selectSymbolId); - const [symbol] = tClient.symbol.getOne.useSuspenseQuery({ symbolId }); - - const dispatch = useAppDispatch(); - - const reduxValue = useAppSelector(selectQuantityPerGrid); - const [value, setValue] = useState(reduxValue); - - useEffect(() => { - setValue(reduxValue); - }, [reduxValue]); - - const handleChange = (e: React.ChangeEvent) => { - setValue(e.target.value); - }; - - const handleBlur = () => { - if (!isNaN(Number(value))) { - dispatch(changeQuantityPerGrid(value)); - } else { - setValue(`${reduxValue}`); - } - }; - - return ( - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/SimpleGridForm/index.ts b/apps/frontend/src/components/grid-bot/create-bot/form/SimpleGridForm/index.ts deleted file mode 100644 index d7b18144..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/SimpleGridForm/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./SimpleGridForm"; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/SubmitButton.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/SubmitButton.tsx deleted file mode 100644 index f59599f9..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/SubmitButton.tsx +++ /dev/null @@ -1,49 +0,0 @@ -"use client"; - -import CircularProgress from "@mui/joy/CircularProgress"; -import Button from "@mui/joy/Button"; -import { useRouter } from "next/navigation"; -import type { FC } from "react"; -import React from "react"; -import { tClient } from "src/lib/trpc/client"; -import { selectBotFormState } from "src/store/bot-form/selectors"; -import { useAppSelector } from "src/store/hooks"; -import { useSnackbar } from "src/ui/snackbar"; -import { toPage } from "src/utils/next/toPage"; -import { mapFormToDto } from "./helpers/mapFormToDto"; - -export const SubmitButton: FC = () => { - const { showSnackbar } = useSnackbar(); - const router = useRouter(); - - const botFormState = useAppSelector(selectBotFormState); - - const { mutate, isLoading } = tClient.gridBot.create.useMutation({ - onSuccess(bot) { - showSnackbar("Bot created successfully"); - - setTimeout(() => { - router.push(toPage("grid-bot/:id", bot.id)); - }, 1000); - }, - }); - - const handleSubmit = () => { - const dto = mapFormToDto(botFormState); - - mutate(dto); - }; - - return ( - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/fields/BotNameField.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/fields/BotNameField.tsx deleted file mode 100644 index b2253b6b..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/fields/BotNameField.tsx +++ /dev/null @@ -1,61 +0,0 @@ -"use client"; - -import FormControl from "@mui/joy/FormControl"; -import FormHelperText from "@mui/joy/FormHelperText"; -import FormLabel from "@mui/joy/FormLabel"; -import IconButton from "@mui/joy/IconButton"; -import Input from "@mui/joy/Input"; -import type { FC } from "react"; -import React, { useEffect, useState } from "react"; -import ReplayIcon from "@mui/icons-material/Replay"; -import { changeBotName } from "src/store/bot-form"; -import { selectBotName } from "src/store/bot-form/selectors"; -import { useAppDispatch, useAppSelector } from "src/store/hooks"; -import { generateBotName } from "src/utils/bot-name-generator"; - -export const BotNameField: FC = () => { - const dispatch = useAppDispatch(); - - const reduxValue = useAppSelector(selectBotName); - const [value, setValue] = useState(reduxValue); - useEffect(() => { - setValue(`${reduxValue}`); - }, [reduxValue]); - - const handleChange = (e: React.ChangeEvent) => { - setValue(e.target.value); - }; - - const handleBlur = () => { - if (value.length > 0) { - dispatch(changeBotName(value)); - } else { - setValue(reduxValue); - } - }; - - const regenerateBotName = () => { - dispatch(changeBotName(generateBotName())); - }; - - const errorMessage = value.length === 0 ? "Must be defined" : null; - - return ( - - Bot name - - - - - } - onBlur={handleBlur} - onChange={handleChange} - value={value} - /> - - {errorMessage ? {errorMessage} : null} - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/fields/ExchangeAccountField.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/fields/ExchangeAccountField.tsx deleted file mode 100644 index c6c4ec55..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/fields/ExchangeAccountField.tsx +++ /dev/null @@ -1,38 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import React from "react"; -import { ExchangeAccountSelect } from "src/ui/selects/ExchangeAccountSelect"; -import { tClient } from "src/lib/trpc/client"; -import { setExchangeAccountId, setExchangeCode } from "src/store/bot-form"; -import { selectExchangeAccountId } from "src/store/bot-form/selectors"; -import { useAppDispatch, useAppSelector } from "src/store/hooks"; -import type { TExchangeAccount } from "src/types/trpc"; - -export const ExchangeAccountField: FC = () => { - const dispatch = useAppDispatch(); - const [exchangeAccounts] = tClient.exchangeAccount.list.useSuspenseQuery(); - - const id = useAppSelector(selectExchangeAccountId); - const handleIdChange = (exchange: TExchangeAccount | null) => { - if (exchange === null) { - throw new Error( - "ExchangeAccountField: Cannot reset exchange account input", - ); - } - - dispatch(setExchangeAccountId(exchange.id)); - dispatch(setExchangeCode(exchange.exchangeCode)); - }; - - const selectedExchange = exchangeAccounts.find( - (exchange) => exchange.id === id, - ); - - return ( - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/fields/FormTypeField.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/fields/FormTypeField.tsx deleted file mode 100644 index abdd8d4a..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/fields/FormTypeField.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import Tabs from "@mui/joy/Tabs"; -import TabList from "@mui/joy/TabList"; -import Tab from "@mui/joy/Tab"; -import type { FC } from "react"; -import React from "react"; -import type { GridBotFormType } from "src/store/bot-form"; -import { changeFormType } from "src/store/bot-form"; -import { selectFormType } from "src/store/bot-form/selectors"; -import { useAppDispatch, useAppSelector } from "src/store/hooks"; - -export const FormTypeField: FC = () => { - const formType = useAppSelector(selectFormType); - const dispatch = useAppDispatch(); - - const handleChange = ( - event: React.SyntheticEvent | null, - newValue: GridBotFormType | null, - ) => { - // disallow un-toggling current button - if (newValue === null) return; - - dispatch(changeFormType(newValue)); - }; - - return ( - { - handleChange(event, newValue as GridBotFormType); - }} - sx={{ - mt: -2, - mx: -2, - borderRadius: "md", - overflow: "auto", - }} - value={formType} - > - - - Easy form - - - Advanced form - - - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/fields/InvestmentField/InvestmentField.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/fields/InvestmentField/InvestmentField.tsx deleted file mode 100644 index 7282049b..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/fields/InvestmentField/InvestmentField.tsx +++ /dev/null @@ -1,58 +0,0 @@ -"use client"; - -import type { FC } from "react"; -import React from "react"; -import { computeInvestmentAmount, decomposeSymbolId } from "@opentrader/tools"; -import FormControl from "@mui/joy/FormControl"; -import FormLabel from "@mui/joy/FormLabel"; -import Input from "@mui/joy/Input"; -import { tClient } from "src/lib/trpc/client"; -import { selectGridLines, selectSymbolId } from "src/store/bot-form/selectors"; -import { useAppSelector } from "src/store/hooks"; -import { InvestmentFieldHelperText } from "./InvestmentFieldHelperText"; - -type InvestmentFieldProps = { - className?: string; -}; - -export const InvestmentField: FC = (props) => { - const { className } = props; - - const symbolId = useAppSelector(selectSymbolId); - const { quoteCurrency } = decomposeSymbolId(symbolId); - - const [currentAssetPrice] = tClient.symbol.price.useSuspenseQuery({ - symbolId, - }); - const [symbol] = tClient.symbol.getOne.useSuspenseQuery({ - symbolId, - }); - - const gridLines = useAppSelector(selectGridLines); - - const { baseCurrencyAmount, quoteCurrencyAmount, totalInQuoteCurrency } = - computeInvestmentAmount(symbol, gridLines, currentAssetPrice.price); - - const inputId = "investment-field"; - const label = "Investment"; - - return ( - - {label} - - {quoteCurrency}} - id={inputId} - type="number" - value={totalInQuoteCurrency} - /> - - - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/fields/InvestmentField/InvestmentFieldHelperText.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/fields/InvestmentField/InvestmentFieldHelperText.tsx deleted file mode 100644 index 5644e975..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/fields/InvestmentField/InvestmentFieldHelperText.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { decomposeSymbolId } from "@opentrader/tools"; -import FormHelperText from "@mui/joy/FormHelperText"; -import type { FC } from "react"; -import { selectSymbolId } from "src/store/bot-form/selectors"; -import { useAppSelector } from "src/store/hooks"; - -type InvestmentFieldHelperTextProps = { - baseCurrencyAmount: string; // numeric string - quoteCurrencyAmount: string; // numeric string - totalInQuoteCurrency: string; // numeric string -}; - -export const InvestmentFieldHelperText: FC = ({ - baseCurrencyAmount, - quoteCurrencyAmount, - totalInQuoteCurrency, -}) => { - const symbolId = useAppSelector(selectSymbolId); - const { baseCurrency, quoteCurrency } = decomposeSymbolId(symbolId); - - return ( - - - {baseCurrencyAmount} {baseCurrency} - - + - - {quoteCurrencyAmount} {quoteCurrency} - - - - {totalInQuoteCurrency} {quoteCurrency} - - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/fields/InvestmentField/index.ts b/apps/frontend/src/components/grid-bot/create-bot/form/fields/InvestmentField/index.ts deleted file mode 100644 index 88495f65..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/fields/InvestmentField/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./InvestmentField"; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/fields/PairField.tsx b/apps/frontend/src/components/grid-bot/create-bot/form/fields/PairField.tsx deleted file mode 100644 index 4dde2944..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/fields/PairField.tsx +++ /dev/null @@ -1,38 +0,0 @@ -"use client"; - -import type { ISymbolInfo } from "@opentrader/types"; -import type { FC } from "react"; -import React from "react"; -import { SymbolSelect } from "src/ui/selects/SymbolSelect"; -import { tClient } from "src/lib/trpc/client"; -import { changeSymbolId } from "src/store/bot-form"; -import { - selectExchangeCode, - selectSymbolId, -} from "src/store/bot-form/selectors"; -import { useAppDispatch, useAppSelector } from "src/store/hooks"; - -export const PairField: FC = () => { - const dispatch = useAppDispatch(); - const exchangeCode = useAppSelector(selectExchangeCode); - const [symbols] = tClient.symbol.list.useSuspenseQuery(exchangeCode); - - const symbolId = useAppSelector(selectSymbolId); - const handleSymbolIdChange = (symbol: ISymbolInfo | null) => { - if (symbol === null) { - throw new Error("PairField: Cannot reset symbol input"); - } - - dispatch(changeSymbolId(symbol.symbolId)); - }; - - const symbol = symbols.find((symbol) => symbol.symbolId === symbolId); - - return ( - - ); -}; diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/helpers/mapFormToDto.ts b/apps/frontend/src/components/grid-bot/create-bot/form/helpers/mapFormToDto.ts deleted file mode 100644 index 140d367e..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/helpers/mapFormToDto.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { decomposeSymbolId } from "@opentrader/tools"; -import type { GridBotFormState } from "src/store/bot-form"; -import type { TGridBotCreateInput } from "src/types/trpc"; - -export function mapFormToDto(state: GridBotFormState): TGridBotCreateInput { - const { gridLines, symbolId, exchangeAccountId, botName } = state; - - const { baseCurrency, quoteCurrency } = decomposeSymbolId(symbolId); - - return { - exchangeAccountId, - data: { - name: botName, - settings: { - gridLines, - }, - baseCurrency, - quoteCurrency, - }, - }; -} diff --git a/apps/frontend/src/components/grid-bot/create-bot/form/index.ts b/apps/frontend/src/components/grid-bot/create-bot/form/index.ts deleted file mode 100644 index 8fed3934..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/form/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./CreateGridForm"; diff --git a/apps/frontend/src/components/grid-bot/create-bot/hooks/usePagaData.ts b/apps/frontend/src/components/grid-bot/create-bot/hooks/usePagaData.ts deleted file mode 100644 index dc10f8a4..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/hooks/usePagaData.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { tClient } from "src/lib/trpc/client"; - -export function usePageData() { - const [exchangeAccounts] = tClient.exchangeAccount.list.useSuspenseQuery(); - const exchangeAccount = exchangeAccounts[0]; - - const [symbols] = tClient.symbol.list.useSuspenseQuery( - exchangeAccount.exchangeCode, - ); - - // assume that every exchange has a BTC/USDT pair - // if not, then get the first one - const symbol = - symbols.find((symbol) => symbol.currencyPair === "BTC/USDT") || symbols[0]; - - const [{ price: currentAssetPrice }] = tClient.symbol.price.useSuspenseQuery({ - symbolId: symbol.symbolId, - }); - - const [{ lowPrice, highPrice }] = - tClient.gridBot.formOptions.useSuspenseQuery({ - symbolId: symbol.symbolId, - }); - - return { - exchangeAccount, - symbol, - currentAssetPrice, - lowPrice, - highPrice, - }; -} diff --git a/apps/frontend/src/components/grid-bot/create-bot/loading.tsx b/apps/frontend/src/components/grid-bot/create-bot/loading.tsx deleted file mode 100644 index 576db806..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/loading.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import Grid from "@mui/joy/Grid"; -import Skeleton from "@mui/joy/Skeleton"; -import React from "react"; - -export default function Loading() { - return ( - - - - - - - - - - ); -} diff --git a/apps/frontend/src/components/grid-bot/create-bot/page.tsx b/apps/frontend/src/components/grid-bot/create-bot/page.tsx deleted file mode 100644 index 8e6a4c37..00000000 --- a/apps/frontend/src/components/grid-bot/create-bot/page.tsx +++ /dev/null @@ -1,72 +0,0 @@ -"use client"; - -import React from "react"; -import Grid from "@mui/joy/Grid"; -import { TRPCClientErrorBoundary } from "src/ui/errors/suspense"; -import { useIsFirstRender } from "src/hooks/useIsFirstRender"; -import type { GridBotFormChartBarSize } from "src/store/bot-form"; -import { - changeBarSize, - setBotName, - setExchangeAccountId, - setExchangeCode, - setHighPrice, - setLowPrice, - setQuantityPerGrid, - setSymbolId, - selectBarSize, - selectGridLines, - selectSymbolId, -} from "src/store/bot-form"; -import { useAppDispatch, useAppSelector } from "src/store/hooks"; -import { generateBotName } from "src/utils/bot-name-generator"; -import { GridChart } from "./GridChart"; -import { CreateGridBotForm } from "./form"; -import { usePageData } from "./hooks/usePagaData"; - -export default function CreateGridBotPage() { - const { exchangeAccount, symbol, lowPrice, highPrice, currentAssetPrice } = - usePageData(); - const dispatch = useAppDispatch(); - - const isFirstRender = useIsFirstRender(); - if (isFirstRender) { - dispatch(setExchangeAccountId(exchangeAccount.id)); - dispatch(setExchangeCode(exchangeAccount.exchangeCode)); - dispatch(setSymbolId(symbol.symbolId)); - dispatch( - setQuantityPerGrid(symbol.filters.limits.amount?.min?.toString() || ""), - ); - dispatch(setLowPrice(lowPrice)); - dispatch(setHighPrice(highPrice)); - dispatch(setBotName(generateBotName())); - } - - const symbolId = useAppSelector(selectSymbolId); - - const barSize = useAppSelector(selectBarSize); - const handleBarSizeChange = (barSize: GridBotFormChartBarSize) => - dispatch(changeBarSize(barSize)); - - const gridLines = useAppSelector(selectGridLines); - - return ( - - - - - - - - - - - - ); -} diff --git a/apps/frontend/src/components/login/LoginForm.tsx b/apps/frontend/src/components/login/LoginForm.tsx deleted file mode 100644 index 19a7294f..00000000 --- a/apps/frontend/src/components/login/LoginForm.tsx +++ /dev/null @@ -1,77 +0,0 @@ -"use client"; - -import Sheet from "@mui/joy/Sheet"; -import Typography from "@mui/joy/Typography"; -import FormControl from "@mui/joy/FormControl"; -import FormLabel from "@mui/joy/FormLabel"; -import Input from "@mui/joy/Input"; -import Button from "@mui/joy/Button"; -import { useRouter } from "next/navigation"; -import { useState } from "react"; -import { toPage } from "src/utils/next/toPage"; - -export function LoginForm() { - const [email, setEmail] = useState("opentrader"); - const [password, setPassword] = useState(""); - const router = useRouter(); - - const handleLogin = () => { - window.localStorage.setItem("ADMIN_PASSWORD", password); - router.replace(toPage("grid-bot")); - }; - - return ( - - - Welcome Trader! - - Sign in to continue. - - - Username - - setEmail(e.target.value)} - placeholder="username" - type="text" - value={email} - /> - - - - Password - - setPassword(e.target.value)} - placeholder="password" - type="password" - value={password} - /> - - - - - ); -} diff --git a/apps/frontend/src/hooks/useIsFirstRender.ts b/apps/frontend/src/hooks/useIsFirstRender.ts deleted file mode 100644 index 302b5a9d..00000000 --- a/apps/frontend/src/hooks/useIsFirstRender.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { useRef } from "react"; - -export function useIsFirstRender(): boolean { - const isFirst = useRef(true); - - if (isFirst.current) { - isFirst.current = false; - - return true; - } - - return isFirst.current; -} diff --git a/apps/frontend/src/hooks/useIsStale.ts b/apps/frontend/src/hooks/useIsStale.ts deleted file mode 100644 index adfd4063..00000000 --- a/apps/frontend/src/hooks/useIsStale.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { useState } from "react"; - -export function useIsStale(value: T) { - const [prevValue, setPrevValue] = useState(value); - if (value !== prevValue) { - setPrevValue(value); - - return true; - } - - return false; -} diff --git a/apps/frontend/src/lib/react-query/client.ts b/apps/frontend/src/lib/react-query/client.ts deleted file mode 100644 index 9fd68f57..00000000 --- a/apps/frontend/src/lib/react-query/client.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { QueryClient } from "@tanstack/react-query"; - -export const rqc = new QueryClient(); diff --git a/apps/frontend/src/lib/trpc/client.ts b/apps/frontend/src/lib/trpc/client.ts deleted file mode 100644 index 6e074b99..00000000 --- a/apps/frontend/src/lib/trpc/client.ts +++ /dev/null @@ -1,5 +0,0 @@ -import type { AppRouter } from "@opentrader/trpc"; -// 👆 **type-only** import -import { createTRPCReact } from "@trpc/react-query"; - -export const tClient = createTRPCReact({}); diff --git a/apps/frontend/src/lib/trpc/getBaseUrl.ts b/apps/frontend/src/lib/trpc/getBaseUrl.ts deleted file mode 100644 index 470368ef..00000000 --- a/apps/frontend/src/lib/trpc/getBaseUrl.ts +++ /dev/null @@ -1,28 +0,0 @@ -export function getBaseUrl(): string { - const isProduction = process.env.NODE_ENV === "production"; - if (isProduction) { - if (typeof window !== "undefined") { - const customURL = localStorage.getItem("APP_URL"); - - // browser should use relative path - return customURL ? customURL : ""; - } - } - - if (process.env.NEXT_PUBLIC_PROCESSOR_ENABLE_TRPC) { - return `${process.env.NEXT_PUBLIC_PROCESSOR_URL}`; - } - - if (typeof window !== "undefined") - // browser should use relative path - return ""; - if (process.env.VERCEL_URL) - // reference for vercel.com - return `https://${process.env.VERCEL_URL}`; - if (process.env.RENDER_INTERNAL_HOSTNAME) - // reference for render.com - return `http://${process.env.RENDER_INTERNAL_HOSTNAME}:${process.env.PORT}`; - - // assume localhost - return `http://localhost:${process.env.PORT ?? 3000}`; -} diff --git a/apps/frontend/src/lib/trpc/index.ts b/apps/frontend/src/lib/trpc/index.ts deleted file mode 100644 index dc5bb0a1..00000000 --- a/apps/frontend/src/lib/trpc/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -import superjson from "superjson"; -import { createTRPCProxyClient, httpBatchLink } from "@trpc/client"; -import type { AppRouter } from "@opentrader/trpc"; -import { getBaseUrl } from "src/lib/trpc/getBaseUrl"; -// 👆 **type-only** import - -const url = `${getBaseUrl()}/api/trpc`; - -// @todo remove this and use instead the client provided from `lib/trpc/client.ts` and `lib/trpc/server.ts` -export const trpc = createTRPCProxyClient({ - transformer: superjson, - links: [ - httpBatchLink({ - url, - // You can pass any HTTP headers you wish here - async headers() { - return { - // authorization: getAuthCookie(), - }; - }, - }), - ], -}); diff --git a/apps/frontend/src/lib/trpc/server.ts b/apps/frontend/src/lib/trpc/server.ts deleted file mode 100644 index 9170fb31..00000000 --- a/apps/frontend/src/lib/trpc/server.ts +++ /dev/null @@ -1,12 +0,0 @@ -import "server-only"; -import { appRouter } from "@opentrader/trpc"; - -export const tServer = appRouter.createCaller({ - user: { - id: 1, - password: "huitebe", - email: "nu@nahui.su", - displayName: null, - role: "Admin", // @todo remove context - }, -}); diff --git a/apps/frontend/src/lib/trpc/types.ts b/apps/frontend/src/lib/trpc/types.ts deleted file mode 100644 index 6e17d80c..00000000 --- a/apps/frontend/src/lib/trpc/types.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { inferRouterInputs, inferRouterOutputs } from "@trpc/server"; -import type { AppRouter } from "@opentrader/trpc"; -// 👆 **type-only** import - -// @see https://trpc.io/docs/client/vanilla/infer-types -export type RouterInput = inferRouterInputs; -export type RouterOutput = inferRouterOutputs; diff --git a/apps/frontend/src/providers/StoreProvider.tsx b/apps/frontend/src/providers/StoreProvider.tsx deleted file mode 100644 index 7b148f8d..00000000 --- a/apps/frontend/src/providers/StoreProvider.tsx +++ /dev/null @@ -1,13 +0,0 @@ -"use client"; - -import type { FC, ReactNode } from "react"; -import { Provider } from "react-redux"; -import { store } from "src/store"; - -type StoreProviderProps = { - children: ReactNode; -}; - -export const StoreProvider: FC = ({ children }) => { - return {children}; -}; diff --git a/apps/frontend/src/providers/ThemeProvider/EmotionCache.tsx b/apps/frontend/src/providers/ThemeProvider/EmotionCache.tsx deleted file mode 100644 index 75c94678..00000000 --- a/apps/frontend/src/providers/ThemeProvider/EmotionCache.tsx +++ /dev/null @@ -1,99 +0,0 @@ -"use client"; -import * as React from "react"; -import createCache from "@emotion/cache"; -import { useServerInsertedHTML } from "next/navigation"; -import { CacheProvider as DefaultCacheProvider } from "@emotion/react"; -import type { - EmotionCache, - Options as OptionsOfCreateCache, -} from "@emotion/cache"; - -export type NextAppDirEmotionCacheProviderProps = { - // This is the options passed to createCache() from 'import createCache from "@emotion/cache"' - options: Omit; - // Default: 'import { CacheProvider } from "@emotion/react"' - CacheProvider?: (props: { - value: EmotionCache; - children: React.ReactNode; - }) => JSX.Element | null; - children: React.ReactNode; -}; - -// Adapted from https://github.com/garronej/tss-react/blob/main/src/next/appDir.tsx -export default function NextAppDirEmotionCacheProvider( - props: NextAppDirEmotionCacheProviderProps, -) { - const { options, CacheProvider = DefaultCacheProvider, children } = props; - - const [registry] = React.useState(() => { - const cache = createCache(options); - cache.compat = true; - // eslint-disable-next-line @typescript-eslint/unbound-method -- need better typing - const prevInsert = cache.insert; - let inserted: { name: string; isGlobal: boolean }[] = []; - cache.insert = (...args) => { - const [selector, serialized] = args; - - if (cache.inserted[serialized.name] === undefined) { - inserted.push({ - name: serialized.name, - isGlobal: !selector, - }); - } - return prevInsert(...args); - }; - const flush = () => { - const prevInserted = inserted; - inserted = []; - return prevInserted; - }; - return { cache, flush }; - }); - - useServerInsertedHTML(() => { - const inserted = registry.flush(); - if (inserted.length === 0) { - return null; - } - let styles = ""; - let dataEmotionAttribute = registry.cache.key; - - const globals: { - name: string; - style: string; - }[] = []; - - inserted.forEach(({ name, isGlobal }) => { - const style = registry.cache.inserted[name]; - - if (typeof style !== "boolean") { - if (isGlobal) { - globals.push({ name, style }); - } else { - styles += style; - dataEmotionAttribute += ` ${name}`; - } - } - }); - - return ( - <> - {globals.map(({ name, style }) => ( -