diff --git a/apps/simple-trading-app-e2e/.eslintrc.json b/apps/simple-trading-app-e2e/.eslintrc.json new file mode 100644 index 000000000..696cb8b12 --- /dev/null +++ b/apps/simple-trading-app-e2e/.eslintrc.json @@ -0,0 +1,10 @@ +{ + "extends": ["plugin:cypress/recommended", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/simple-trading-app-e2e/cypress.json b/apps/simple-trading-app-e2e/cypress.json new file mode 100644 index 000000000..a76041db9 --- /dev/null +++ b/apps/simple-trading-app-e2e/cypress.json @@ -0,0 +1,16 @@ +{ + "baseUrl": "http://localhost:4200", + "fileServerFolder": ".", + "testFiles": "*.{ts,feature,features}", + "ignoreTestFiles": "**/*.js", + "fixturesFolder": "./src/fixtures", + "integrationFolder": "./src/integration", + "modifyObstructiveCode": false, + "supportFile": "./src/support/index.ts", + "pluginsFile": false, + "video": true, + "videosFolder": "../../dist/cypress/apps/simple-trading-app-e2e/videos", + "screenshotsFolder": "../../dist/cypress/apps/simple-trading-app-e2e/screenshots", + "chromeWebSecurity": false, + "projectId": "et4snf" +} diff --git a/apps/simple-trading-app-e2e/project.json b/apps/simple-trading-app-e2e/project.json new file mode 100644 index 000000000..080a1350d --- /dev/null +++ b/apps/simple-trading-app-e2e/project.json @@ -0,0 +1,28 @@ +{ + "root": "apps/simple-trading-app-e2e", + "sourceRoot": "apps/simple-trading-app-e2e/src", + "projectType": "application", + "targets": { + "e2e": { + "executor": "@nrwl/cypress:cypress", + "options": { + "cypressConfig": "apps/simple-trading-app-e2e/cypress.json", + "devServerTarget": "simple-trading-app:serve" + }, + "configurations": { + "production": { + "devServerTarget": "simple-trading-app:serve:production" + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/simple-trading-app-e2e/**/*.{js,ts}"] + } + } + }, + "tags": [], + "implicitDependencies": ["simple-trading-app"] +} diff --git a/apps/simple-trading-app-e2e/src/support/app.po.ts b/apps/simple-trading-app-e2e/src/support/app.po.ts new file mode 100644 index 000000000..e69de29bb diff --git a/apps/simple-trading-app-e2e/src/support/commands.ts b/apps/simple-trading-app-e2e/src/support/commands.ts new file mode 100644 index 000000000..07164efbe --- /dev/null +++ b/apps/simple-trading-app-e2e/src/support/commands.ts @@ -0,0 +1,9 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** diff --git a/apps/simple-trading-app-e2e/src/support/index.ts b/apps/simple-trading-app-e2e/src/support/index.ts new file mode 100644 index 000000000..3d469a6b6 --- /dev/null +++ b/apps/simple-trading-app-e2e/src/support/index.ts @@ -0,0 +1,17 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands'; diff --git a/apps/simple-trading-app-e2e/tsconfig.json b/apps/simple-trading-app-e2e/tsconfig.json new file mode 100644 index 000000000..c4f818ecd --- /dev/null +++ b/apps/simple-trading-app-e2e/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "sourceMap": false, + "outDir": "../../dist/out-tsc", + "allowJs": true, + "types": ["cypress", "node"] + }, + "include": ["src/**/*.ts", "src/**/*.js"] +} diff --git a/apps/simple-trading-app/.babelrc b/apps/simple-trading-app/.babelrc new file mode 100644 index 000000000..61641ec8a --- /dev/null +++ b/apps/simple-trading-app/.babelrc @@ -0,0 +1,11 @@ +{ + "presets": [ + [ + "@nrwl/react/babel", + { + "runtime": "automatic" + } + ] + ], + "plugins": [] +} diff --git a/apps/simple-trading-app/.browserslistrc b/apps/simple-trading-app/.browserslistrc new file mode 100644 index 000000000..83f282df3 --- /dev/null +++ b/apps/simple-trading-app/.browserslistrc @@ -0,0 +1,16 @@ +# This file is used by: +# 1. autoprefixer to adjust CSS to support the below specified browsers +# 2. babel preset-env to adjust included polyfills +# +# For additional information regarding the format and rule options, please see: +# https://github.com/browserslist/browserslist#queries +# +# If you need to support different browsers in production, you may tweak the list below. + +last 1 Chrome version +last 1 Firefox version +last 2 Edge major versions +last 2 Safari major version +last 2 iOS major versions +Firefox ESR +not IE 9-11 # For IE 9-11 support, remove 'not'. diff --git a/apps/simple-trading-app/.env b/apps/simple-trading-app/.env new file mode 100644 index 000000000..e3e9ddb62 --- /dev/null +++ b/apps/simple-trading-app/.env @@ -0,0 +1,19 @@ +# React Environment Variables +# https://facebook.github.io/create-react-app/docs/adding-custom-environment-variables#expanding-environment-variables-in-env + +# Netlify Environment Variables +# https://www.netlify.com/docs/continuous-deployment/#environment-variables +NX_VERSION=$npm_package_version +NX_REPOSITORY_URL=$REPOSITORY_URL +NX_BRANCH=$BRANCH +NX_PULL_REQUEST=$PULL_REQUEST +NX_HEAD=$HEAD +NX_COMMIT_REF=$COMMIT_REF +NX_CONTEXT=$CONTEXT +NX_REVIEW_ID=$REVIEW_ID +NX_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE +NX_INCOMING_HOOK_URL=$INCOMING_HOOK_URL +NX_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY +NX_URL=$URL +NX_DEPLOY_URL=$DEPLOY_URL +NX_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL diff --git a/apps/simple-trading-app/.env.capsule b/apps/simple-trading-app/.env.capsule new file mode 100644 index 000000000..f3412a07c --- /dev/null +++ b/apps/simple-trading-app/.env.capsule @@ -0,0 +1,4 @@ +# App configuration variables +NX_VEGA_URL = "http://localhost:3028/query" +NX_VEGA_ENV = 'LOCAL' +NX_VEGA_REST = 'http://localhost:3029' diff --git a/apps/simple-trading-app/.env.devent b/apps/simple-trading-app/.env.devent new file mode 100644 index 000000000..ead76f976 --- /dev/null +++ b/apps/simple-trading-app/.env.devent @@ -0,0 +1,4 @@ +# App configuration variables +NX_VEGA_URL = "https://n04.d.vega.xyz/query" +NX_VEGA_ENV = 'DEVNET' +NX_VEGA_REST = 'https://n04.d.vega.xyz/datanode/rest' diff --git a/apps/simple-trading-app/.env.mainnet b/apps/simple-trading-app/.env.mainnet new file mode 100644 index 000000000..0308b0ed1 --- /dev/null +++ b/apps/simple-trading-app/.env.mainnet @@ -0,0 +1,4 @@ +# App configuration variables +NX_VEGA_URL = "https://api.token.vega.xyz/query" +NX_VEGA_ENV = 'MAINNET' +NX_VEGA_REST = 'https://api.token.vega.xyz/' diff --git a/apps/simple-trading-app/.env.stagnet1 b/apps/simple-trading-app/.env.stagnet1 new file mode 100644 index 000000000..e1b300dca --- /dev/null +++ b/apps/simple-trading-app/.env.stagnet1 @@ -0,0 +1,4 @@ +# App configuration variables +NX_VEGA_URL = "https://n03.s.vega.xyz/query" +NX_VEGA_ENV = 'STAGNET' +NX_VEGA_REST = 'https://n03.s.vega.xyz/datanode/rest' diff --git a/apps/simple-trading-app/.env.stagnet2 b/apps/simple-trading-app/.env.stagnet2 new file mode 100644 index 000000000..514d87608 --- /dev/null +++ b/apps/simple-trading-app/.env.stagnet2 @@ -0,0 +1,4 @@ +# App configuration variables +NX_VEGA_URL = "https://n03.stagnet2.vega.xyz/query" +NX_VEGA_ENV = 'STAGNET2' +NX_VEGA_REST = 'https://n01.stagnet2.vega.xyz/datanode/rest' diff --git a/apps/simple-trading-app/.env.testnet b/apps/simple-trading-app/.env.testnet new file mode 100644 index 000000000..818f209ad --- /dev/null +++ b/apps/simple-trading-app/.env.testnet @@ -0,0 +1,4 @@ +# App configuration variables +NX_VEGA_URL = "https://lb.testnet.vega.xyz/query" +NX_VEGA_ENV = 'TESTNET' +NX_VEGA_REST = 'https://lb.testnet.vega.xyz/datanode/rest' diff --git a/apps/simple-trading-app/.eslintrc.json b/apps/simple-trading-app/.eslintrc.json new file mode 100644 index 000000000..734ddacee --- /dev/null +++ b/apps/simple-trading-app/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/apps/simple-trading-app/jest.config.js b/apps/simple-trading-app/jest.config.js new file mode 100644 index 000000000..04aee0216 --- /dev/null +++ b/apps/simple-trading-app/jest.config.js @@ -0,0 +1,11 @@ +module.exports = { + displayName: 'simple-trading-app', + preset: '../../jest.preset.js', + transform: { + '^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest', + '^.+\\.[tj]sx?$': ['babel-jest', { presets: ['@nrwl/next/babel'] }], + }, + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'], + coverageDirectory: '../../coverage/apps/simple-trading-app', + setupFilesAfterEnv: ['./setup-tests.ts'], +}; diff --git a/apps/simple-trading-app/postcss.config.js b/apps/simple-trading-app/postcss.config.js new file mode 100644 index 000000000..cbdd9c22c --- /dev/null +++ b/apps/simple-trading-app/postcss.config.js @@ -0,0 +1,10 @@ +const { join } = require('path'); + +module.exports = { + plugins: { + tailwindcss: { + config: join(__dirname, 'tailwind.config.js'), + }, + autoprefixer: {}, + }, +}; diff --git a/apps/simple-trading-app/project.json b/apps/simple-trading-app/project.json new file mode 100644 index 000000000..82331f164 --- /dev/null +++ b/apps/simple-trading-app/project.json @@ -0,0 +1,74 @@ +{ + "root": "apps/simple-trading-app", + "sourceRoot": "apps/simple-trading-app/src", + "projectType": "application", + "targets": { + "build": { + "executor": "@nrwl/web:webpack", + "outputs": ["{options.outputPath}"], + "defaultConfiguration": "production", + "options": { + "compiler": "babel", + "outputPath": "dist/apps/simple-trading-app", + "index": "apps/simple-trading-app/src/index.html", + "baseHref": "/", + "main": "apps/simple-trading-app/src/main.tsx", + "polyfills": "apps/simple-trading-app/src/polyfills.ts", + "tsConfig": "apps/simple-trading-app/tsconfig.app.json", + "assets": [ + "apps/simple-trading-app/src/favicon.ico", + "apps/simple-trading-app/src/assets" + ], + "styles": ["apps/simple-trading-app/src/styles.scss"], + "scripts": [], + "webpackConfig": "@nrwl/react/plugins/webpack" + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "apps/simple-trading-app/src/environments/environment.ts", + "with": "apps/simple-trading-app/src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "namedChunks": false, + "extractLicenses": true, + "vendorChunk": false + } + } + }, + "serve": { + "executor": "@nrwl/web:dev-server", + "options": { + "buildTarget": "simple-trading-app:build", + "hmr": true, + "port": 4001 + }, + "configurations": { + "production": { + "buildTarget": "simple-trading-app:build:production", + "hmr": false + } + } + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["apps/simple-trading-app/**/*.{ts,tsx,js,jsx}"] + } + }, + "test": { + "executor": "@nrwl/jest:jest", + "outputs": ["coverage/apps/simple-trading-app"], + "options": { + "jestConfig": "apps/simple-trading-app/jest.config.js", + "passWithNoTests": true + } + } + }, + "tags": [] +} diff --git a/apps/simple-trading-app/setup-tests.ts b/apps/simple-trading-app/setup-tests.ts new file mode 100644 index 000000000..dabdc544a --- /dev/null +++ b/apps/simple-trading-app/setup-tests.ts @@ -0,0 +1,15 @@ +import '@testing-library/jest-dom'; + +Object.defineProperty(window, 'matchMedia', { + writable: true, + value: jest.fn().mockImplementation((query) => ({ + matches: false, + media: query, + onchange: null, + addListener: jest.fn(), // Deprecated + removeListener: jest.fn(), // Deprecated + addEventListener: jest.fn(), + removeEventListener: jest.fn(), + dispatchEvent: jest.fn(), + })), +}); diff --git a/apps/simple-trading-app/src/app/app.tsx b/apps/simple-trading-app/src/app/app.tsx new file mode 100644 index 000000000..111ef5bb2 --- /dev/null +++ b/apps/simple-trading-app/src/app/app.tsx @@ -0,0 +1,77 @@ +import { useState, useMemo } from 'react'; +import { ApolloProvider } from '@apollo/client'; +import { ThemeContext } from '@vegaprotocol/react-helpers'; +import { useThemeSwitcher } from '@vegaprotocol/react-helpers'; +import { createClient } from './lib/apollo-client'; +import { DATA_SOURCES } from './config'; +import { + VegaConnectDialog, + VegaManageDialog, + VegaWalletProvider, +} from '@vegaprotocol/wallet'; +import { DealTicketContainer } from './components/deal-ticket'; +import { VegaWalletConnectButton } from './components/vega-wallet-connect-button'; +import { ThemeSwitcher } from '@vegaprotocol/ui-toolkit'; +import { Connectors } from './lib/vega-connectors'; +import '../styles.scss'; +import { AppLoader } from './components/app-loader'; + +function App() { + const [theme, toggleTheme] = useThemeSwitcher(); + const [vegaWallet, setVegaWallet] = useState({ + connect: false, + manage: false, + }); + + const client = useMemo(() => createClient(DATA_SOURCES.dataNodeUrl), []); + + return ( + + + + +
+
+
+ + setVegaWallet((x) => ({ ...x, connect: open })) + } + setManageDialog={(open) => + setVegaWallet((x) => ({ ...x, manage: open })) + } + /> + +
+
+
+
+ +
+
+ + setVegaWallet((x) => ({ ...x, connect: open })) + } + /> + + setVegaWallet((x) => ({ ...x, manage: open })) + } + /> +
+
+
+
+
+ ); +} + +export default App; diff --git a/apps/simple-trading-app/src/app/components/app-loader/index.tsx b/apps/simple-trading-app/src/app/components/app-loader/index.tsx new file mode 100644 index 000000000..da120d394 --- /dev/null +++ b/apps/simple-trading-app/src/app/components/app-loader/index.tsx @@ -0,0 +1,19 @@ +import { useEagerConnect } from '@vegaprotocol/wallet'; +import type { ReactNode } from 'react'; +import { Connectors } from '../../lib/vega-connectors'; + +interface AppLoaderProps { + children: ReactNode; +} + +/** + * Component to handle any app initialization, startup querys and other things + * that must happen for it can be used + */ +export function AppLoader({ children }: AppLoaderProps) { + // Get keys from vega wallet immediately + useEagerConnect(Connectors); + + // eslint-disable-next-line react/jsx-no-useless-fragment + return <>{children}; +} diff --git a/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-container.tsx b/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-container.tsx new file mode 100644 index 000000000..73b0453c0 --- /dev/null +++ b/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-container.tsx @@ -0,0 +1,18 @@ +import type { DealTicketContainerProps } from '@vegaprotocol/deal-ticket'; +import { + DealTicketManager, + DealTicketContainer as Container, +} from '@vegaprotocol/deal-ticket'; +import { DealTicketSteps } from './deal-ticket-steps'; + +export const DealTicketContainer = ({ marketId }: DealTicketContainerProps) => { + return ( + + {(data) => ( + + + + )} + + ); +}; diff --git a/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-steps.tsx b/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-steps.tsx new file mode 100644 index 000000000..a68270fc2 --- /dev/null +++ b/apps/simple-trading-app/src/app/components/deal-ticket/deal-ticket-steps.tsx @@ -0,0 +1,155 @@ +import * as React from 'react'; +import type { FormEvent } from 'react'; +import Box from '@mui/material/Box'; +import { Stepper } from '../stepper'; +import type { Order, DealTicketQuery_market } from '@vegaprotocol/deal-ticket'; +import { + ExpirySelector, + SideSelector, + SubmitButton, + TimeInForceSelector, + TypeSelector, + useOrderState, + useOrderSubmit, + DealTicketLimitForm, + DealTicketMarketForm, +} from '@vegaprotocol/deal-ticket'; +import { + OrderSide, + OrderTimeInForce, + OrderType, + VegaTxStatus, +} from '@vegaprotocol/wallet'; +import { addDecimal } from '@vegaprotocol/react-helpers'; + +interface DealTicketMarketProps { + market: DealTicketQuery_market; +} + +const DEFAULT_ORDER: Order = { + type: OrderType.Market, + side: OrderSide.Buy, + size: '1', + timeInForce: OrderTimeInForce.IOC, +}; + +export const DealTicketSteps = ({ market }: DealTicketMarketProps) => { + const [order, updateOrder] = useOrderState(DEFAULT_ORDER); + const { submit, transaction } = useOrderSubmit(market); + + const transactionStatus = + transaction.status === VegaTxStatus.Requested || + transaction.status === VegaTxStatus.Pending + ? 'pending' + : 'default'; + + let ticket = null; + + if (order.type === OrderType.Market) { + ticket = ( + updateOrder({ size })} + price={ + market.depth.lastTrade + ? addDecimal(market.depth.lastTrade.price, market.decimalPlaces) + : undefined + } + quoteName={market.tradableInstrument.instrument.product.quoteName} + /> + ); + } else if (order.type === OrderType.Limit) { + ticket = ( + updateOrder({ size })} + onPriceChange={(price) => updateOrder({ price })} + /> + ); + } else { + throw new Error('Invalid ticket type'); + } + + const handleSubmit = (e: FormEvent): Promise => { + e.preventDefault(); + return submit(order); + }; + + const steps = [ + { + label: 'Select Asset', + description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`, + component:
, + }, + { + label: 'Select Order Type', + description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`, + component: ( + updateOrder({ type })} + /> + ), + }, + { + label: 'Select Market Position', + description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`, + component: ( + updateOrder({ side })} + /> + ), + }, + { + label: 'Select Order Size', + description: + 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.', + component: ticket, + }, + { + label: 'Select Time In Force', + description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`, + component: ( + <> + updateOrder({ timeInForce })} + /> + {order.timeInForce === OrderTimeInForce.GTT && ( + { + if (date) { + updateOrder({ expiration: date }); + } + }} + /> + )} + + ), + }, + { + label: 'Review & Submit Order', + description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.`, + component: ( + + + + ), + disabled: true, + }, + ]; + + return ( +
+ + + ); +}; diff --git a/apps/simple-trading-app/src/app/components/deal-ticket/index.ts b/apps/simple-trading-app/src/app/components/deal-ticket/index.ts new file mode 100644 index 000000000..1ead649d1 --- /dev/null +++ b/apps/simple-trading-app/src/app/components/deal-ticket/index.ts @@ -0,0 +1 @@ +export { DealTicketContainer } from './deal-ticket-container'; diff --git a/apps/simple-trading-app/src/app/components/stepper/index.ts b/apps/simple-trading-app/src/app/components/stepper/index.ts new file mode 100644 index 000000000..68094e791 --- /dev/null +++ b/apps/simple-trading-app/src/app/components/stepper/index.ts @@ -0,0 +1 @@ +export { default as Stepper } from './stepper'; diff --git a/apps/simple-trading-app/src/app/components/stepper/stepper.tsx b/apps/simple-trading-app/src/app/components/stepper/stepper.tsx new file mode 100644 index 000000000..3a9bb0006 --- /dev/null +++ b/apps/simple-trading-app/src/app/components/stepper/stepper.tsx @@ -0,0 +1,93 @@ +import * as React from 'react'; +import type { ReactNode } from 'react'; +import Box from '@mui/material/Box'; +import { Button } from '@vegaprotocol/ui-toolkit'; +import Stepper from '@mui/material/Stepper'; +import Step from '@mui/material/Step'; +import StepLabel from '@mui/material/StepLabel'; +import StepContent from '@mui/material/StepContent'; +import Paper from '@mui/material/Paper'; +import Divider from '@mui/material/Divider'; +import Typography from '@mui/material/Typography'; + +type Step = { + label: string; + description: string; + component: ReactNode; + disabled?: boolean; +}; + +interface StepperProps { + steps: Step[]; +} + +export default ({ steps }: StepperProps) => { + const [activeStep, setActiveStep] = React.useState(0); + + const handleClick = (index: typeof activeStep) => { + setActiveStep(index); + }; + + const handleNext = () => { + setActiveStep((prevActiveStep) => prevActiveStep + 1); + }; + + const handleBack = () => { + setActiveStep((prevActiveStep) => prevActiveStep - 1); + }; + + const handleReset = () => { + setActiveStep(0); + }; + + return ( + + + {steps.map((step, index) => ( + + Last step + ) : null + } + style={{ cursor: 'pointer' }} + onClick={() => handleClick(index)} + > + {step.label} + + + {step.description} + {step.component} + + +
+ + +
+
+
+
+ ))} +
+ {activeStep === steps.length && ( + + All steps completed - you're finished + + + )} +
+ ); +}; diff --git a/apps/simple-trading-app/src/app/components/vega-wallet-connect-button/index.tsx b/apps/simple-trading-app/src/app/components/vega-wallet-connect-button/index.tsx new file mode 100644 index 000000000..e923d3aa7 --- /dev/null +++ b/apps/simple-trading-app/src/app/components/vega-wallet-connect-button/index.tsx @@ -0,0 +1 @@ +export { VegaWalletConnectButton } from './vega-wallet-connect-button'; diff --git a/apps/simple-trading-app/src/app/components/vega-wallet-connect-button/vega-wallet-connect-button.tsx b/apps/simple-trading-app/src/app/components/vega-wallet-connect-button/vega-wallet-connect-button.tsx new file mode 100644 index 000000000..7f3be067c --- /dev/null +++ b/apps/simple-trading-app/src/app/components/vega-wallet-connect-button/vega-wallet-connect-button.tsx @@ -0,0 +1,38 @@ +import { truncateByChars } from '@vegaprotocol/react-helpers'; +import { useVegaWallet } from '@vegaprotocol/wallet'; + +export interface VegaWalletConnectButtonProps { + setConnectDialog: (isOpen: boolean) => void; + setManageDialog: (isOpen: boolean) => void; +} + +export const VegaWalletConnectButton = ({ + setConnectDialog, + setManageDialog, +}: VegaWalletConnectButtonProps) => { + const { keypair } = useVegaWallet(); + const isConnected = keypair !== null; + + const handleClick = () => { + if (isConnected) { + setManageDialog(true); + } else { + setConnectDialog(true); + } + }; + + return ( + + {isConnected && ( + Vega key: + )} + + + ); +}; diff --git a/apps/simple-trading-app/src/app/config/index.tsx b/apps/simple-trading-app/src/app/config/index.tsx new file mode 100644 index 000000000..b309e30f9 --- /dev/null +++ b/apps/simple-trading-app/src/app/config/index.tsx @@ -0,0 +1,5 @@ +export const DATA_SOURCES = { + dataNodeUrl: process.env['NX_VEGA_URL'] as string, + envName: process.env['NX_VEGA_ENV'] as string, + restEndpoint: process.env['NX_VEGA_REST'] as string, +}; diff --git a/apps/simple-trading-app/src/app/lib/apollo-client.tsx b/apps/simple-trading-app/src/app/lib/apollo-client.tsx new file mode 100644 index 000000000..2b2ed69bd --- /dev/null +++ b/apps/simple-trading-app/src/app/lib/apollo-client.tsx @@ -0,0 +1,53 @@ +import { ApolloClient, from, HttpLink, InMemoryCache } from '@apollo/client'; +import { onError } from '@apollo/client/link/error'; +import { RetryLink } from '@apollo/client/link/retry'; + +export function createClient(base?: string) { + if (!base) { + throw new Error('Base must be passed into createClient!'); + } + const gqlPath = 'query'; + const urlHTTP = new URL(gqlPath, base); + const urlWS = new URL(gqlPath, base); + // Replace http with ws, preserving if its a secure connection eg. https => wss + urlWS.protocol = urlWS.protocol.replace('http', 'ws'); + + const cache = new InMemoryCache({ + typePolicies: { + Query: {}, + Account: { + keyFields: false, + fields: { + balanceFormatted: {}, + }, + }, + Node: { + keyFields: false, + }, + }, + }); + + const retryLink = new RetryLink({ + delay: { + initial: 300, + max: 10000, + jitter: true, + }, + }); + + const httpLink = new HttpLink({ + uri: urlHTTP.href, + credentials: 'same-origin', + }); + + const errorLink = onError(({ graphQLErrors, networkError }) => { + console.log(graphQLErrors); + console.log(networkError); + }); + + return new ApolloClient({ + connectToDevTools: process.env['NODE_ENV'] === 'development', + link: from([errorLink, retryLink, httpLink]), + cache, + }); +} diff --git a/apps/simple-trading-app/src/app/lib/vega-connectors.ts b/apps/simple-trading-app/src/app/lib/vega-connectors.ts new file mode 100644 index 000000000..4b3a52863 --- /dev/null +++ b/apps/simple-trading-app/src/app/lib/vega-connectors.ts @@ -0,0 +1,7 @@ +import { RestConnector } from '@vegaprotocol/wallet'; + +export const rest = new RestConnector(); + +export const Connectors = { + rest, +}; diff --git a/apps/simple-trading-app/src/assets/.gitkeep b/apps/simple-trading-app/src/assets/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/apps/simple-trading-app/src/environments/environment.prod.ts b/apps/simple-trading-app/src/environments/environment.prod.ts new file mode 100644 index 000000000..c9669790b --- /dev/null +++ b/apps/simple-trading-app/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true, +}; diff --git a/apps/simple-trading-app/src/environments/environment.ts b/apps/simple-trading-app/src/environments/environment.ts new file mode 100644 index 000000000..7ed83767f --- /dev/null +++ b/apps/simple-trading-app/src/environments/environment.ts @@ -0,0 +1,6 @@ +// This file can be replaced during build by using the `fileReplacements` array. +// When building for production, this file is replaced with `environment.prod.ts`. + +export const environment = { + production: false, +}; diff --git a/apps/simple-trading-app/src/favicon.ico b/apps/simple-trading-app/src/favicon.ico new file mode 100644 index 000000000..317ebcb23 Binary files /dev/null and b/apps/simple-trading-app/src/favicon.ico differ diff --git a/apps/simple-trading-app/src/index.html b/apps/simple-trading-app/src/index.html new file mode 100644 index 000000000..3e4617763 --- /dev/null +++ b/apps/simple-trading-app/src/index.html @@ -0,0 +1,14 @@ + + + + + Simple Trading App + + + + + + +
+ + diff --git a/apps/simple-trading-app/src/main.tsx b/apps/simple-trading-app/src/main.tsx new file mode 100644 index 000000000..0bb92da1b --- /dev/null +++ b/apps/simple-trading-app/src/main.tsx @@ -0,0 +1,14 @@ +import { StrictMode } from 'react'; +import * as ReactDOM from 'react-dom'; +import { BrowserRouter } from 'react-router-dom'; + +import App from './app/app'; + +ReactDOM.render( + + + + + , + document.getElementById('root') +); diff --git a/apps/simple-trading-app/src/polyfills.ts b/apps/simple-trading-app/src/polyfills.ts new file mode 100644 index 000000000..2adf3d05b --- /dev/null +++ b/apps/simple-trading-app/src/polyfills.ts @@ -0,0 +1,7 @@ +/** + * Polyfill stable language features. These imports will be optimized by `@babel/preset-env`. + * + * See: https://github.com/zloirock/core-js#babel + */ +import 'core-js/stable'; +import 'regenerator-runtime/runtime'; diff --git a/apps/simple-trading-app/src/styles.scss b/apps/simple-trading-app/src/styles.scss new file mode 100644 index 000000000..85e89379b --- /dev/null +++ b/apps/simple-trading-app/src/styles.scss @@ -0,0 +1,9 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +html, +body, +#__next { + @apply h-full; +} diff --git a/apps/simple-trading-app/tailwind.config.js b/apps/simple-trading-app/tailwind.config.js new file mode 100644 index 000000000..3c7b5a348 --- /dev/null +++ b/apps/simple-trading-app/tailwind.config.js @@ -0,0 +1,14 @@ +const { join } = require('path'); +const { createGlobPatternsForDependencies } = require('@nrwl/next/tailwind'); +const theme = require('../../libs/tailwindcss-config/src/theme'); +const vegaCustomClasses = require('../../libs/tailwindcss-config/src/vega-custom-classes'); + +module.exports = { + content: [ + join(__dirname, 'src/**/*.{js,ts,jsx,tsx}'), + ...createGlobPatternsForDependencies(__dirname), + ], + darkMode: 'class', + theme, + plugins: [vegaCustomClasses], +}; diff --git a/apps/simple-trading-app/tsconfig.app.json b/apps/simple-trading-app/tsconfig.app.json new file mode 100644 index 000000000..252904bb7 --- /dev/null +++ b/apps/simple-trading-app/tsconfig.app.json @@ -0,0 +1,22 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "types": ["node"] + }, + "files": [ + "../../node_modules/@nrwl/react/typings/cssmodule.d.ts", + "../../node_modules/@nrwl/react/typings/image.d.ts" + ], + "exclude": [ + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/*.spec.js", + "**/*.test.js", + "**/*.spec.jsx", + "**/*.test.jsx" + ], + "include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"] +} diff --git a/apps/simple-trading-app/tsconfig.json b/apps/simple-trading-app/tsconfig.json new file mode 100644 index 000000000..9657042e4 --- /dev/null +++ b/apps/simple-trading-app/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "jsx": "react-jsx", + "allowJs": true, + "esModuleInterop": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "noImplicitOverride": true, + "noPropertyAccessFromIndexSignature": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/apps/simple-trading-app/tsconfig.spec.json b/apps/simple-trading-app/tsconfig.spec.json new file mode 100644 index 000000000..5b4e7354a --- /dev/null +++ b/apps/simple-trading-app/tsconfig.spec.json @@ -0,0 +1,20 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"], + "jsx": "react" + }, + "include": [ + "**/*.test.ts", + "**/*.spec.ts", + "**/*.test.tsx", + "**/*.spec.tsx", + "**/*.test.js", + "**/*.spec.js", + "**/*.test.jsx", + "**/*.spec.jsx", + "**/*.d.ts" + ] +} diff --git a/libs/deal-ticket/src/deal-ticket-container.tsx b/libs/deal-ticket/src/deal-ticket-container.tsx index 4de05bd76..f6575e476 100644 --- a/libs/deal-ticket/src/deal-ticket-container.tsx +++ b/libs/deal-ticket/src/deal-ticket-container.tsx @@ -1,7 +1,10 @@ import { AsyncRenderer, Splash } from '@vegaprotocol/ui-toolkit'; import { gql, useQuery } from '@apollo/client'; import { DealTicketManager } from './deal-ticket-manager'; -import type { DealTicketQuery } from './__generated__/DealTicketQuery'; +import type { + DealTicketQuery, + DealTicketQuery_market, +} from './__generated__/DealTicketQuery'; import { t } from '@vegaprotocol/react-helpers'; const DEAL_TICKET_QUERY = gql` @@ -29,11 +32,19 @@ const DEAL_TICKET_QUERY = gql` } `; -interface DealTicketContainerProps { +type childrenProps = { + market: DealTicketQuery_market; +}; + +export interface DealTicketContainerProps { marketId: string; + children?(props: childrenProps): JSX.Element; } -export const DealTicketContainer = ({ marketId }: DealTicketContainerProps) => { +export const DealTicketContainer = ({ + marketId, + children, +}: DealTicketContainerProps) => { const { data, loading, error } = useQuery(DEAL_TICKET_QUERY, { variables: { marketId }, }); @@ -41,7 +52,11 @@ export const DealTicketContainer = ({ marketId }: DealTicketContainerProps) => { return ( data={data} loading={loading} error={error}> {data && data.market ? ( - + children ? ( + children(data) + ) : ( + + ) ) : (

{t('Could not load market')}

diff --git a/libs/deal-ticket/src/deal-ticket-limit-form.tsx b/libs/deal-ticket/src/deal-ticket-limit-form.tsx new file mode 100644 index 000000000..52a1ee3c1 --- /dev/null +++ b/libs/deal-ticket/src/deal-ticket-limit-form.tsx @@ -0,0 +1,45 @@ +import { FormGroup, Input } from '@vegaprotocol/ui-toolkit'; + +export interface DealTicketLimitFormProps { + quoteName: string; + price?: string; + size: string; + onSizeChange: (size: string) => void; + onPriceChange: (price: string) => void; +} + +export const DealTicketLimitForm = ({ + size, + price, + onSizeChange, + onPriceChange, + quoteName, +}: DealTicketLimitFormProps) => { + return ( +
+
+ + onSizeChange(e.target.value)} + className="w-full" + type="number" + data-testid="order-size" + /> + +
+
@
+
+ + onPriceChange(e.target.value)} + className="w-full" + type="number" + data-testid="order-price" + /> + +
+
+ ); +}; diff --git a/libs/deal-ticket/src/deal-ticket-limit.tsx b/libs/deal-ticket/src/deal-ticket-limit.tsx index a8fa08443..590acb0c5 100644 --- a/libs/deal-ticket/src/deal-ticket-limit.tsx +++ b/libs/deal-ticket/src/deal-ticket-limit.tsx @@ -1,9 +1,9 @@ -import { FormGroup, Input } from '@vegaprotocol/ui-toolkit'; import { OrderTimeInForce } from '@vegaprotocol/wallet'; import type { TransactionStatus } from './deal-ticket'; import { ExpirySelector } from './expiry-selector'; import { SideSelector } from './side-selector'; import { SubmitButton } from './submit-button'; +import { DealTicketLimitForm } from './deal-ticket-limit-form'; import { TimeInForceSelector } from './time-in-force-selector'; import { TypeSelector } from './type-selector'; import type { Order } from './use-order-state'; @@ -26,34 +26,13 @@ export const DealTicketLimit = ({ <> updateOrder({ type })} /> updateOrder({ side })} /> -
-
- - updateOrder({ size: e.target.value })} - className="w-full" - type="number" - data-testid="order-size" - /> - -
-
@
-
- - updateOrder({ price: e.target.value })} - className="w-full" - type="number" - data-testid="order-price" - /> - -
-
+ updateOrder({ size })} + onPriceChange={(price) => updateOrder({ price })} + /> updateOrder({ timeInForce })} diff --git a/libs/deal-ticket/src/deal-ticket-manager.tsx b/libs/deal-ticket/src/deal-ticket-manager.tsx index 3966dbdf3..f0d46eec8 100644 --- a/libs/deal-ticket/src/deal-ticket-manager.tsx +++ b/libs/deal-ticket/src/deal-ticket-manager.tsx @@ -1,3 +1,4 @@ +import type { ReactNode } from 'react'; import { useEffect, useState } from 'react'; import { Dialog, Intent } from '@vegaprotocol/ui-toolkit'; import { OrderStatus } from '@vegaprotocol/types'; @@ -7,11 +8,15 @@ import { useOrderSubmit } from './use-order-submit'; import { OrderDialog } from './order-dialog'; import type { DealTicketQuery_market } from './__generated__/DealTicketQuery'; -interface DealTicketManagerProps { +export interface DealTicketManagerProps { market: DealTicketQuery_market; + children?: ReactNode | ReactNode[]; } -export const DealTicketManager = ({ market }: DealTicketManagerProps) => { +export const DealTicketManager = ({ + market, + children, +}: DealTicketManagerProps) => { const [orderDialogOpen, setOrderDialogOpen] = useState(false); const { submit, transaction, finalizedOrder, reset } = useOrderSubmit(market); @@ -47,16 +52,18 @@ export const DealTicketManager = ({ market }: DealTicketManagerProps) => { return ( <> - + {children || ( + + )} { diff --git a/libs/deal-ticket/src/deal-ticket-market-form.tsx b/libs/deal-ticket/src/deal-ticket-market-form.tsx new file mode 100644 index 000000000..1e06a1903 --- /dev/null +++ b/libs/deal-ticket/src/deal-ticket-market-form.tsx @@ -0,0 +1,41 @@ +import { FormGroup, Input } from '@vegaprotocol/ui-toolkit'; + +export interface DealTicketMarketFormProps { + quoteName?: string; + price?: string; + size: string; + onSizeChange: (size: string) => void; +} + +export const DealTicketMarketForm = ({ + size, + onSizeChange, + price, + quoteName, +}: DealTicketMarketFormProps) => { + return ( +
+
+ + onSizeChange(e.target.value)} + className="w-full" + type="number" + data-testid="order-size" + /> + +
+
@
+
+ {price && quoteName ? ( + <> + ~{price} {quoteName} + + ) : ( + '-' + )} +
+
+ ); +}; diff --git a/libs/deal-ticket/src/deal-ticket-market.tsx b/libs/deal-ticket/src/deal-ticket-market.tsx index 2caac612e..78598a517 100644 --- a/libs/deal-ticket/src/deal-ticket-market.tsx +++ b/libs/deal-ticket/src/deal-ticket-market.tsx @@ -1,12 +1,12 @@ -import { addDecimal } from '@vegaprotocol/react-helpers'; -import { FormGroup, Input } from '@vegaprotocol/ui-toolkit'; import type { TransactionStatus } from './deal-ticket'; import { SideSelector } from './side-selector'; +import { DealTicketMarketForm } from './deal-ticket-market-form'; import { SubmitButton } from './submit-button'; import { TimeInForceSelector } from './time-in-force-selector'; import { TypeSelector } from './type-selector'; import type { Order } from './use-order-state'; import type { DealTicketQuery_market } from './__generated__/DealTicketQuery'; +import { addDecimal } from '@vegaprotocol/react-helpers'; interface DealTicketMarketProps { order: Order; @@ -25,30 +25,16 @@ export const DealTicketMarket = ({ <> updateOrder({ type })} /> updateOrder({ side })} /> -
-
- - updateOrder({ size: e.target.value })} - className="w-full" - type="number" - data-testid="order-size" - /> - -
-
@
-
- {market.depth.lastTrade ? ( - <> - ~{addDecimal(market.depth.lastTrade.price, market.decimalPlaces)}{' '} - {market.tradableInstrument.instrument.product.quoteName} - - ) : ( - '-' - )} -
-
+ updateOrder({ size })} + price={ + market.depth.lastTrade + ? addDecimal(market.depth.lastTrade.price, market.decimalPlaces) + : undefined + } + quoteName={market.tradableInstrument.instrument.product.quoteName} + /> updateOrder({ timeInForce })} diff --git a/libs/deal-ticket/src/index.ts b/libs/deal-ticket/src/index.ts index e07da0cad..de2d768ec 100644 --- a/libs/deal-ticket/src/index.ts +++ b/libs/deal-ticket/src/index.ts @@ -1,5 +1,15 @@ +export * from './expiry-selector'; +export * from './type-selector'; +export * from './time-in-force-selector'; +export * from './submit-button'; +export * from './side-selector'; export * from './deal-ticket'; +export * from './deal-ticket-limit-form'; +export * from './deal-ticket-market-form'; +export * from './deal-ticket-manager'; +export * from './order-dialog'; export * from './use-order-state'; +export * from './use-order-submit'; export * from './deal-ticket-container'; export * from './__generated__/DealTicketQuery'; export * from './__generated__/OrderEvent'; diff --git a/libs/tailwindcss-config/src/theme.js b/libs/tailwindcss-config/src/theme.js index 16cc6c429..e141474a6 100644 --- a/libs/tailwindcss-config/src/theme.js +++ b/libs/tailwindcss-config/src/theme.js @@ -6,6 +6,8 @@ module.exports = { sm: '640px', md: '768px', lg: '960px', + xl: '1280px', + xxl: '1536px', }, colors: { transparent: 'transparent', diff --git a/package.json b/package.json index 2d4a9f414..495d7286d 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,9 @@ }, "private": true, "dependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.6.2", "@apollo/client": "^3.5.8", "@blueprintjs/core": "^3.47.0", "@blueprintjs/icons": "^3.32.0", diff --git a/workspace.json b/workspace.json index 0fd344b2e..96feca1c0 100644 --- a/workspace.json +++ b/workspace.json @@ -14,6 +14,8 @@ "order-list": "libs/order-list", "positions": "libs/positions", "react-helpers": "libs/react-helpers", + "simple-trading-app": "apps/simple-trading-app", + "simple-trading-app-e2e": "apps/simple-trading-app-e2e", "static": "apps/static", "stats": "apps/stats", "stats-e2e": "apps/stats-e2e", diff --git a/yarn.lock b/yarn.lock index 3dbaf16e5..9d4ab5b34 100644 --- a/yarn.lock +++ b/yarn.lock @@ -672,7 +672,7 @@ dependencies: "@babel/helper-plugin-utils" "^7.14.5" -"@babel/plugin-syntax-jsx@^7.16.7": +"@babel/plugin-syntax-jsx@^7.12.13", "@babel/plugin-syntax-jsx@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz#50b6571d13f764266a113d77c82b4a6508bbe665" integrity sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q== @@ -1217,7 +1217,7 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.14.8", "@babel/runtime@^7.16.0", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.14.8", "@babel/runtime@^7.16.0", "@babel/runtime@^7.16.3", "@babel/runtime@^7.17.2", "@babel/runtime@^7.17.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.17.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72" integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg== @@ -1413,6 +1413,24 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== +"@emotion/babel-plugin@^11.7.1": + version "11.9.2" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz#723b6d394c89fb2ef782229d92ba95a740576e95" + integrity sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw== + dependencies: + "@babel/helper-module-imports" "^7.12.13" + "@babel/plugin-syntax-jsx" "^7.12.13" + "@babel/runtime" "^7.13.10" + "@emotion/hash" "^0.8.0" + "@emotion/memoize" "^0.7.5" + "@emotion/serialize" "^1.0.2" + babel-plugin-macros "^2.6.1" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.0.13" + "@emotion/cache@^10.0.27": version "10.0.29" resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-10.0.29.tgz#87e7e64f412c060102d589fe7c6dc042e6f9d1e0" @@ -1423,6 +1441,17 @@ "@emotion/utils" "0.11.3" "@emotion/weak-memoize" "0.2.5" +"@emotion/cache@^11.7.1": + version "11.7.1" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.7.1.tgz#08d080e396a42e0037848214e8aa7bf879065539" + integrity sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A== + dependencies: + "@emotion/memoize" "^0.7.4" + "@emotion/sheet" "^1.1.0" + "@emotion/utils" "^1.0.0" + "@emotion/weak-memoize" "^0.2.5" + stylis "4.0.13" + "@emotion/core@^10.1.1": version "10.3.1" resolved "https://registry.yarnpkg.com/@emotion/core/-/core-10.3.1.tgz#4021b6d8b33b3304d48b0bb478485e7d7421c69d" @@ -1444,7 +1473,7 @@ "@emotion/utils" "0.11.3" babel-plugin-emotion "^10.0.27" -"@emotion/hash@0.8.0": +"@emotion/hash@0.8.0", "@emotion/hash@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413" integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow== @@ -1456,11 +1485,36 @@ dependencies: "@emotion/memoize" "0.7.4" +"@emotion/is-prop-valid@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz#34ad6e98e871aa6f7a20469b602911b8b11b3a95" + integrity sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ== + dependencies: + "@emotion/memoize" "^0.7.4" + "@emotion/memoize@0.7.4": version "0.7.4" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.4.tgz#19bf0f5af19149111c40d98bb0cf82119f5d9eeb" integrity sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw== +"@emotion/memoize@^0.7.4", "@emotion/memoize@^0.7.5": + version "0.7.5" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50" + integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ== + +"@emotion/react@^11.9.0": + version "11.9.0" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.9.0.tgz#b6d42b1db3bd7511e7a7c4151dc8bc82e14593b8" + integrity sha512-lBVSF5d0ceKtfKCDQJveNAtkC7ayxpVlgOohLgXqRwqWr9bOf4TZAFFyIcNngnV6xK6X4x2ZeXq7vliHkoVkxQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@emotion/babel-plugin" "^11.7.1" + "@emotion/cache" "^11.7.1" + "@emotion/serialize" "^1.0.3" + "@emotion/utils" "^1.1.0" + "@emotion/weak-memoize" "^0.2.5" + hoist-non-react-statics "^3.3.1" + "@emotion/serialize@^0.11.15", "@emotion/serialize@^0.11.16": version "0.11.16" resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-0.11.16.tgz#dee05f9e96ad2fb25a5206b6d759b2d1ed3379ad" @@ -1472,11 +1526,27 @@ "@emotion/utils" "0.11.3" csstype "^2.5.7" +"@emotion/serialize@^1.0.2", "@emotion/serialize@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.3.tgz#99e2060c26c6292469fb30db41f4690e1c8fea63" + integrity sha512-2mSSvgLfyV3q+iVh3YWgNlUc2a9ZlDU7DjuP5MjK3AXRR0dYigCrP99aeFtaB2L/hjfEZdSThn5dsZ0ufqbvsA== + dependencies: + "@emotion/hash" "^0.8.0" + "@emotion/memoize" "^0.7.4" + "@emotion/unitless" "^0.7.5" + "@emotion/utils" "^1.0.0" + csstype "^3.0.2" + "@emotion/sheet@0.9.4": version "0.9.4" resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-0.9.4.tgz#894374bea39ec30f489bbfc3438192b9774d32e5" integrity sha512-zM9PFmgVSqBw4zL101Q0HrBVTGmpAxFZH/pYx/cjJT5advXguvcgjHFTCaIO3enL/xr89vK2bh0Mfyj9aa0ANA== +"@emotion/sheet@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.1.0.tgz#56d99c41f0a1cda2726a05aa6a20afd4c63e58d2" + integrity sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g== + "@emotion/styled-base@^10.3.0": version "10.3.0" resolved "https://registry.yarnpkg.com/@emotion/styled-base/-/styled-base-10.3.0.tgz#9aa2c946100f78b47316e4bc6048321afa6d4e36" @@ -1495,12 +1565,23 @@ "@emotion/styled-base" "^10.3.0" babel-plugin-emotion "^10.0.27" +"@emotion/styled@^11.8.1": + version "11.8.1" + resolved "https://registry.yarnpkg.com/@emotion/styled/-/styled-11.8.1.tgz#856f6f63aceef0eb783985fa2322e2bf66d04e17" + integrity sha512-OghEVAYBZMpEquHZwuelXcRjRJQOVayvbmNR0zr174NHdmMgrNkLC6TljKC5h9lZLkN5WGrdUcrKlOJ4phhoTQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@emotion/babel-plugin" "^11.7.1" + "@emotion/is-prop-valid" "^1.1.2" + "@emotion/serialize" "^1.0.2" + "@emotion/utils" "^1.1.0" + "@emotion/stylis@0.8.5": version "0.8.5" resolved "https://registry.yarnpkg.com/@emotion/stylis/-/stylis-0.8.5.tgz#deacb389bd6ee77d1e7fcaccce9e16c5c7e78e04" integrity sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ== -"@emotion/unitless@0.7.5": +"@emotion/unitless@0.7.5", "@emotion/unitless@^0.7.5": version "0.7.5" resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed" integrity sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg== @@ -1510,7 +1591,12 @@ resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-0.11.3.tgz#a759863867befa7e583400d322652a3f44820924" integrity sha512-0o4l6pZC+hI88+bzuaX/6BgOvQVhbt2PfmxauVaYOGgbsAw14wdKyvMCZXnsnsHys94iadcF+RG/wZyx6+ZZBw== -"@emotion/weak-memoize@0.2.5": +"@emotion/utils@^1.0.0", "@emotion/utils@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.1.0.tgz#86b0b297f3f1a0f2bdb08eeac9a2f49afd40d0cf" + integrity sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ== + +"@emotion/weak-memoize@0.2.5", "@emotion/weak-memoize@^0.2.5": version "0.2.5" resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== @@ -2308,6 +2394,86 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@mui/base@5.0.0-alpha.78": + version "5.0.0-alpha.78" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-alpha.78.tgz#9d0ee8c913969f60cd7072a6082e3c0652be0b74" + integrity sha512-5L+GNe2M9/tFjQpjK2r837+kzRg/l6D5R9SQbG1wmSWejw5Ei8P+KXIgS/NLNi9g7dUT8bnCyzz9AZKQX1Jsfg== + dependencies: + "@babel/runtime" "^7.17.2" + "@emotion/is-prop-valid" "^1.1.2" + "@mui/types" "^7.1.3" + "@mui/utils" "^5.6.1" + "@popperjs/core" "^2.11.5" + clsx "^1.1.1" + prop-types "^15.7.2" + react-is "^17.0.2" + +"@mui/material@^5.6.2": + version "5.6.3" + resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.6.3.tgz#a5b6cd1b0417fca67a65172530ff5573ab7bc453" + integrity sha512-2VovFsbCEhic29NYoBF7zFrpH2sEOlKXXDhGjzxmWiI9OnC3SX63hapWunjaVsiRINVnjuMHuW1MOs4UtV8Gfg== + dependencies: + "@babel/runtime" "^7.17.2" + "@mui/base" "5.0.0-alpha.78" + "@mui/system" "^5.6.3" + "@mui/types" "^7.1.3" + "@mui/utils" "^5.6.1" + "@types/react-transition-group" "^4.4.4" + clsx "^1.1.1" + csstype "^3.0.11" + hoist-non-react-statics "^3.3.2" + prop-types "^15.7.2" + react-is "^17.0.2" + react-transition-group "^4.4.2" + +"@mui/private-theming@^5.6.2": + version "5.6.2" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.6.2.tgz#c42da32f8b9481ba12885176c0168a355727c189" + integrity sha512-IbrSfFXfiZdyhRMC2bgGTFtb16RBQ5mccmjeh3MtAERWuepiCK7gkW5D9WhEsfTu6iez+TEjeUKSgmMHlsM2mg== + dependencies: + "@babel/runtime" "^7.17.2" + "@mui/utils" "^5.6.1" + prop-types "^15.7.2" + +"@mui/styled-engine@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.6.1.tgz#e2c859a4dbdd65af89e77703a0725285aef471fd" + integrity sha512-jEhH6TBY8jc9S8yVncXmoTYTbATjEu44RMFXj6sIYfKr5NArVwTwRo3JexLL0t3BOAiYM4xsFLgfKEIvB9SAeQ== + dependencies: + "@babel/runtime" "^7.17.2" + "@emotion/cache" "^11.7.1" + prop-types "^15.7.2" + +"@mui/system@^5.6.3": + version "5.6.3" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.6.3.tgz#a03ad6d61b0b7d304a8af60374e27e71e7818ef7" + integrity sha512-4SRi52a4ttZ2S4EHEDE8arVNuKqyQLTYUTF80WAZ0tQwnG20qwlBtzcrywCGItmVAMl7RUaYopyWOx3yVPvrmQ== + dependencies: + "@babel/runtime" "^7.17.2" + "@mui/private-theming" "^5.6.2" + "@mui/styled-engine" "^5.6.1" + "@mui/types" "^7.1.3" + "@mui/utils" "^5.6.1" + clsx "^1.1.1" + csstype "^3.0.11" + prop-types "^15.7.2" + +"@mui/types@^7.1.3": + version "7.1.3" + resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.1.3.tgz#d7636f3046110bcccc63e6acfd100e2ad9ca712a" + integrity sha512-DDF0UhMBo4Uezlk+6QxrlDbchF79XG6Zs0zIewlR4c0Dt6GKVFfUtzPtHCH1tTbcSlq/L2bGEdiaoHBJ9Y1gSA== + +"@mui/utils@^5.6.1": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.6.1.tgz#4ab79a21bd481555d9a588f4b18061b3c28ea5db" + integrity sha512-CPrzrkiBusCZBLWu0Sg5MJvR3fKJyK3gKecLVX012LULyqg2U64Oz04BKhfkbtBrPBbSQxM+DWW9B1c9hmV9nQ== + dependencies: + "@babel/runtime" "^7.17.2" + "@types/prop-types" "^15.7.4" + "@types/react-is" "^16.7.1 || ^17.0.0" + prop-types "^15.7.2" + react-is "^17.0.2" + "@napi-rs/triples@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@napi-rs/triples/-/triples-1.0.3.tgz#76d6d0c3f4d16013c61e45dfca5ff1e6c31ae53c" @@ -3054,7 +3220,7 @@ schema-utils "^3.0.0" source-map "^0.7.3" -"@popperjs/core@^2.5.4", "@popperjs/core@^2.6.0": +"@popperjs/core@^2.11.5", "@popperjs/core@^2.5.4", "@popperjs/core@^2.6.0": version "2.11.5" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.5.tgz#db5a11bf66bdab39569719555b0f76e138d7bd64" integrity sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw== @@ -5320,7 +5486,7 @@ resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.0.tgz#a1c3809b0ad61c62cac6d4e0c56d610c910b7654" integrity sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ== -"@types/prop-types@*": +"@types/prop-types@*", "@types/prop-types@^15.7.4": version "15.7.5" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== @@ -5368,6 +5534,13 @@ dependencies: "@types/react" "^17" +"@types/react-is@^16.7.1 || ^17.0.0": + version "17.0.3" + resolved "https://registry.yarnpkg.com/@types/react-is/-/react-is-17.0.3.tgz#2d855ba575f2fc8d17ef9861f084acc4b90a137a" + integrity sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw== + dependencies: + "@types/react" "*" + "@types/react-router-dom@5.3.1": version "5.3.1" resolved "https://registry.yarnpkg.com/@types/react-router-dom/-/react-router-dom-5.3.1.tgz#76700ccce6529413ec723024b71f01fc77a4a980" @@ -5399,6 +5572,13 @@ dependencies: "@types/react" "*" +"@types/react-transition-group@^4.4.4": + version "4.4.4" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.4.tgz#acd4cceaa2be6b757db61ed7b432e103242d163e" + integrity sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug== + dependencies: + "@types/react" "*" + "@types/react-virtualized-auto-sizer@^1.0.0", "@types/react-virtualized-auto-sizer@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.1.tgz#b3187dae1dfc4c15880c9cfc5b45f2719ea6ebd4" @@ -7325,7 +7505,7 @@ babel-plugin-jest-hoist@^27.5.1: "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" -babel-plugin-macros@^2.0.0, babel-plugin-macros@^2.8.0: +babel-plugin-macros@^2.0.0, babel-plugin-macros@^2.6.1, babel-plugin-macros@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138" integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg== @@ -9411,7 +9591,7 @@ csstype@^2.5.7: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.20.tgz#9229c65ea0b260cf4d3d997cb06288e36a8d6dda" integrity sha512-/WwNkdXfckNgw6S5R125rrW8ez139lBHWouiBvX8dfMFtcn6V81REDqnH7+CRpRipfYlyU1CmOnOxrmGcFOjeA== -csstype@^3.0.2, csstype@^3.0.4: +csstype@^3.0.11, csstype@^3.0.2, csstype@^3.0.4: version "3.0.11" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.11.tgz#d66700c5eacfac1940deb4e3ee5642792d85cd33" integrity sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw== @@ -10049,6 +10229,14 @@ dom-helpers@^3.4.0: dependencies: "@babel/runtime" "^7.1.2" +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dom-serializer@0: version "0.2.2" resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.2.2.tgz#1afb81f533717175d478655debc5e332d9f9bb51" @@ -12369,7 +12557,7 @@ hmac-drbg@^1.0.1: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.1, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -17571,6 +17759,16 @@ react-transition-group@2.9.0, react-transition-group@^2.9.0: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" +react-transition-group@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470" + integrity sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react-use-websocket@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/react-use-websocket/-/react-use-websocket-3.0.0.tgz#754cb8eea76f55d31c5676d4abe3e573bc2cea04" @@ -19385,6 +19583,11 @@ stylis@3.5.4: resolved "https://registry.yarnpkg.com/stylis/-/stylis-3.5.4.tgz#f665f25f5e299cf3d64654ab949a57c768b73fbe" integrity sha512-8/3pSmthWM7lsPBKv7NXkzn2Uc9W7NotcwGNpJaa3k7WMM1XDCA4MgT5k/8BIexd5ydZdboXtU90XH9Ec4Bv/Q== +stylis@4.0.13: + version "4.0.13" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91" + integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag== + stylus-loader@^6.2.0: version "6.2.0" resolved "https://registry.yarnpkg.com/stylus-loader/-/stylus-loader-6.2.0.tgz#0ba499e744e7fb9d9b3977784c8639728a7ced8c"