Feat/224 move token app into monorepo (#229)
* moved TFE into monorepo with some remaining errors * moved TFE into monorepo with some remaining errors - further files * add tailwind config, use etherscan link from toolkit, use web3 from lib * make app compatible with react router 6 * remove vega keys from app state and use from state from lib * comment out crowdin script injection * convert all buttons to use ui toolkit buttons * remove blueprint inputs and selects and replace with ui-toolkit * remove css resets * tidy button styles in wallet replace splash-screen with version from ui-toolkit * various style fixes * tidy up proposal list * add valid key to route children * Set custom port and config for token e2e tests * added env title e2e test * started some styling fixes - nav and home route * Added 'h-auto' to button height regex check * Added 'h-auto' to regex check to allow desired TFE button height * Removed scss and used tailwind for heading component * Woff files not woof :) * Proper nav h1 font size * Wallet card headings * Vega wallet button styles * Set project to use static hosted alpha font (cors being fixed separately) * Eth wallet button styles (unfinished) * Home route styles * Staking route styles and title calculation * Rewards route styles * Vega wallet container button style * Eth wallet disconnect button * Connect dialog title colour and spacing * Splash screen layout * Fixed a bunch of linting errors * Used 'Object.entries' instead of 'Array.from' to create iterable from object in 'use-search-params' * Removed use of 'any' from 'use-search-params' * Better simplification of 'use-search-params' * Removed package.json duplication errors, set most up-to-date version from duplicate options * Elvis for possible undefined in 'use-add-asset-to-wallet' * Removed redundant files * Removed old todo * Removed package.json redundant packages * Added dark class for dialog h1 text colour (required as the current scss gives a wrong default for this element) * update useAddAsset to use new provider * Ensure Jest has required methods * tidy up package.json * remove ts-ignores and use casts for dynamic grid imports * remove unused code from token-e2e * update to latest types from react 17 * Removed vegag wallet not running component as it should be handled by wallet lib * fix typing of contract addresses * type cast network string to Network enum in reduce * remove comment, issue 270 created instead * default associated wallet amounts to zero * update comment * delete unused staking-overview component, add note about withTranslation types to comment * re add proposal dates * enable source maps for build * add rest of env files for networks * remove crowdin script tags from index.html * add testing-library/jest-dom to types in test tsconfig * setup i18n for tests so that translations are used, proposal change table test * delete unused translation files and config * set sentry release to use commit ref * delete dex liquidity pages * remove unused useVegaLPStaking hook * use found id so no non null assertion needed * remove mocked graphql provider * remove commented out breadcrumb component * add comment and link to issue for syntax highlighter changes * fix any types in token-input, add link to ui-toolkit input changes * dont default allowance to zero as it affects rendering logic * fix spacing between callouts on associate page * adjust spacing between callout for association callout * fix alignment of ethereum splash screen * use ethereum connect dialog state for connect dialog * add infura provider as default * change from infura provider to JsonRpcProvider * remove unused Ethereum config * add custom webpack config to inject sentry plugin * delete commented out code for pending stake * add comment linking input elements issue for eth-address-input * move useEagerConnect to libs/wallet, add logic for connecting state so token app can load after connection has succeeded or failed * remove unused storage files, update web3 connector to render children if not actively connected Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
This commit is contained in:
parent
f0e4aded3a
commit
9591687a80
@ -1,7 +1,7 @@
|
||||
@font-face {
|
||||
font-family: AlphaLyrae;
|
||||
src: url('alpha-lyrae/AlphaLyrae-Medium.woff2') format('woof2'),
|
||||
url('alpha-lyrae/AlphaLyrae-Medium.woff') format('woof'),
|
||||
src: url('alpha-lyrae/AlphaLyrae-Medium.woff2') format('woff2'),
|
||||
url('alpha-lyrae/AlphaLyrae-Medium.woff') format('woff'),
|
||||
url('alpha-lyrae/AlphaLyrae-Medium.ttf') format('truetype'),
|
||||
url('alpha-lyrae/AlphaLyrae-Medium.eot') format('embedded-opentype'),
|
||||
url('alpha-lyrae/AlphaLyrae-Medium.otf') format('opentype');
|
||||
|
10
apps/token-e2e/.eslintrc.json
Normal file
10
apps/token-e2e/.eslintrc.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": ["plugin:cypress/recommended", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
14
apps/token-e2e/cypress.json
Normal file
14
apps/token-e2e/cypress.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"baseUrl": "http://localhost:4210",
|
||||
"projectId": "et4snf",
|
||||
"fileServerFolder": ".",
|
||||
"fixturesFolder": "./src/fixtures",
|
||||
"integrationFolder": "./src/integration",
|
||||
"modifyObstructiveCode": false,
|
||||
"supportFile": "./src/support/index.ts",
|
||||
"pluginsFile": false,
|
||||
"video": true,
|
||||
"videosFolder": "../../dist/cypress/apps/token-e2e/videos",
|
||||
"screenshotsFolder": "../../dist/cypress/apps/token-e2e/screenshots",
|
||||
"chromeWebSecurity": false
|
||||
}
|
28
apps/token-e2e/project.json
Normal file
28
apps/token-e2e/project.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"root": "apps/token-e2e",
|
||||
"sourceRoot": "apps/token-e2e/src",
|
||||
"projectType": "application",
|
||||
"targets": {
|
||||
"e2e": {
|
||||
"executor": "@nrwl/cypress:cypress",
|
||||
"options": {
|
||||
"cypressConfig": "apps/token-e2e/cypress.json",
|
||||
"devServerTarget": "token:serve"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"devServerTarget": "token:serve:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nrwl/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["apps/token-e2e/**/*.{js,ts}"]
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [],
|
||||
"implicitDependencies": ["token"]
|
||||
}
|
4
apps/token-e2e/src/fixtures/example.json
Normal file
4
apps/token-e2e/src/fixtures/example.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "Using fixtures to represent data",
|
||||
"email": "hello@cypress.io"
|
||||
}
|
12
apps/token-e2e/src/integration/app.spec.ts
Normal file
12
apps/token-e2e/src/integration/app.spec.ts
Normal file
@ -0,0 +1,12 @@
|
||||
const fairgroundSet = Cypress.env('FAIRGROUND');
|
||||
|
||||
describe('token', () => {
|
||||
beforeEach(() => cy.visit('/'));
|
||||
|
||||
it('should always have an header title based on environment', () => {
|
||||
cy.get('.nav h1').should(
|
||||
'have.text',
|
||||
`${fairgroundSet ? 'Fairground token' : '$VEGA TOKEN'}`
|
||||
);
|
||||
});
|
||||
});
|
1
apps/token-e2e/src/support/index.ts
Normal file
1
apps/token-e2e/src/support/index.ts
Normal file
@ -0,0 +1 @@
|
||||
import '@vegaprotocol/cypress';
|
10
apps/token-e2e/tsconfig.json
Normal file
10
apps/token-e2e/tsconfig.json
Normal file
@ -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"]
|
||||
}
|
11
apps/token/.babelrc
Normal file
11
apps/token/.babelrc
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@nrwl/react/babel",
|
||||
{
|
||||
"runtime": "automatic"
|
||||
}
|
||||
]
|
||||
],
|
||||
"plugins": []
|
||||
}
|
16
apps/token/.browserslistrc
Normal file
16
apps/token/.browserslistrc
Normal file
@ -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'.
|
30
apps/token/.env
Normal file
30
apps/token/.env
Normal file
@ -0,0 +1,30 @@
|
||||
# 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
|
||||
REACT_APP_VERSION=$npm_package_version
|
||||
REACT_APP_REPOSITORY_URL=$REPOSITORY_URL
|
||||
REACT_APP_BRANCH=$BRANCH
|
||||
REACT_APP_PULL_REQUEST=$PULL_REQUEST
|
||||
REACT_APP_HEAD=$HEAD
|
||||
REACT_APP_COMMIT_REF=$COMMIT_REF
|
||||
REACT_APP_CONTEXT=$CONTEXT
|
||||
REACT_APP_REVIEW_ID=$REVIEW_ID
|
||||
REACT_APP_INCOMING_HOOK_TITLE=$INCOMING_HOOK_TITLE
|
||||
REACT_APP_INCOMING_HOOK_URL=$INCOMING_HOOK_URL
|
||||
REACT_APP_INCOMING_HOOK_BODY=$INCOMING_HOOK_BODY
|
||||
REACT_APP_URL=$URL
|
||||
REACT_APP_DEPLOY_URL=$DEPLOY_URL
|
||||
REACT_APP_DEPLOY_PRIME_URL=$DEPLOY_PRIME_URL
|
||||
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV = "TESTNET"
|
||||
NX_VEGA_URL = "https://lb.testnet.vega.xyz/query"
|
||||
NX_ETHEREUM_CHAIN_ID = 3
|
||||
NX_ETHEREUM_PROVIDER_URL = "https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8"
|
||||
NX_ETHERSCAN_URL = "https://ropsten.etherscan.io"
|
||||
NX_FAIRGROUND = false
|
||||
|
||||
#Test configuration variables
|
||||
CYPRESS_FAIRGROUND = false
|
6
apps/token/.env.devent
Normal file
6
apps/token/.env.devent
Normal file
@ -0,0 +1,6 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV = "DEVNET"
|
||||
NX_VEGA_URL = "https://n04.d.vega.xyz/query"
|
||||
NX_ETHEREUM_CHAIN_ID = 3
|
||||
NX_ETHEREUM_PROVIDER_URL = "https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8"
|
||||
NX_ETHERSCAN_URL = "https://ropsten.etherscan.io"
|
6
apps/token/.env.mainnet
Normal file
6
apps/token/.env.mainnet
Normal file
@ -0,0 +1,6 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV = "MAINNET"
|
||||
NX_VEGA_URL = "https://api.token.vega.xyz/query"
|
||||
NX_ETHEREUM_CHAIN_ID = 1
|
||||
NX_ETHEREUM_PROVIDER_URL = "https://mainnet.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8"
|
||||
NX_ETHERSCAN_URL = "https://etherscan.io"
|
6
apps/token/.env.stagnet1
Normal file
6
apps/token/.env.stagnet1
Normal file
@ -0,0 +1,6 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV = "STAGNET"
|
||||
NX_VEGA_URL = "https://n03.s.vega.xyz/query"
|
||||
NX_ETHEREUM_CHAIN_ID = 3
|
||||
NX_ETHEREUM_PROVIDER_URL = "https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8"
|
||||
NX_ETHERSCAN_URL = "https://ropsten.etherscan.io"
|
6
apps/token/.env.stagnet2
Normal file
6
apps/token/.env.stagnet2
Normal file
@ -0,0 +1,6 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV = "STAGNET2"
|
||||
NX_VEGA_URL = "https://n03.stagnet2.vega.xyz/query"
|
||||
NX_ETHEREUM_CHAIN_ID = 3
|
||||
NX_ETHEREUM_PROVIDER_URL = "https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8"
|
||||
NX_ETHERSCAN_URL = "https://ropsten.etherscan.io"
|
6
apps/token/.env.testnet
Normal file
6
apps/token/.env.testnet
Normal file
@ -0,0 +1,6 @@
|
||||
# App configuration variables
|
||||
NX_VEGA_ENV = "TESTNET"
|
||||
NX_VEGA_URL = "https://lb.testnet.vega.xyz/query"
|
||||
NX_ETHEREUM_CHAIN_ID = 3
|
||||
NX_ETHEREUM_PROVIDER_URL = "https://ropsten.infura.io/v3/4f846e79e13f44d1b51bbd7ed9edefb8"
|
||||
NX_ETHERSCAN_URL = "https://ropsten.etherscan.io"
|
18
apps/token/.eslintrc.json
Normal file
18
apps/token/.eslintrc.json
Normal file
@ -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": {}
|
||||
}
|
||||
]
|
||||
}
|
11
apps/token/jest.config.js
Normal file
11
apps/token/jest.config.js
Normal file
@ -0,0 +1,11 @@
|
||||
module.exports = {
|
||||
displayName: 'token',
|
||||
preset: '../../jest.preset.js',
|
||||
transform: {
|
||||
'^(?!.*\\.(js|jsx|ts|tsx|css|json)$)': '@nrwl/react/plugins/jest',
|
||||
'^.+\\.[tj]sx?$': 'babel-jest',
|
||||
},
|
||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||
coverageDirectory: '../../coverage/apps/token',
|
||||
setupFilesAfterEnv: ['./src/setup-tests.ts'],
|
||||
};
|
10
apps/token/postcss.config.js
Normal file
10
apps/token/postcss.config.js
Normal file
@ -0,0 +1,10 @@
|
||||
const { join } = require('path');
|
||||
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {
|
||||
config: join(__dirname, 'tailwind.config.js'),
|
||||
},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
71
apps/token/project.json
Normal file
71
apps/token/project.json
Normal file
@ -0,0 +1,71 @@
|
||||
{
|
||||
"root": "apps/token",
|
||||
"sourceRoot": "apps/token/src",
|
||||
"projectType": "application",
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nrwl/web:webpack",
|
||||
"outputs": ["{options.outputPath}"],
|
||||
"defaultConfiguration": "production",
|
||||
"options": {
|
||||
"compiler": "babel",
|
||||
"outputPath": "dist/apps/token",
|
||||
"index": "apps/token/src/index.html",
|
||||
"baseHref": "/",
|
||||
"main": "apps/token/src/main.tsx",
|
||||
"polyfills": "apps/token/src/polyfills.ts",
|
||||
"tsConfig": "apps/token/tsconfig.app.json",
|
||||
"assets": ["apps/token/src/favicon.ico", "apps/token/src/assets"],
|
||||
"styles": ["apps/token/src/styles.scss"],
|
||||
"scripts": [],
|
||||
"webpackConfig": "apps/explorer/webpack.config.js"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "apps/token/src/environments/environment.ts",
|
||||
"with": "apps/token/src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": true,
|
||||
"namedChunks": false,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"executor": "@nrwl/web:dev-server",
|
||||
"options": {
|
||||
"port": 4210,
|
||||
"buildTarget": "token:build",
|
||||
"hmr": true
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "token:build:production",
|
||||
"hmr": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nrwl/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["apps/token/**/*.{ts,tsx,js,jsx}"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nrwl/jest:jest",
|
||||
"outputs": ["coverage/apps/token"],
|
||||
"options": {
|
||||
"jestConfig": "apps/token/jest.config.js",
|
||||
"passWithNoTests": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": []
|
||||
}
|
120
apps/token/src/__generated__/globalTypes.ts
generated
Normal file
120
apps/token/src/__generated__/globalTypes.ts
generated
Normal file
@ -0,0 +1,120 @@
|
||||
|
||||
/* tslint:disable */
|
||||
/* eslint-disable */
|
||||
// @generated
|
||||
// This file was automatically generated and should not be edited.
|
||||
|
||||
//==============================================================
|
||||
// START Enums and Input Objects
|
||||
//==============================================================
|
||||
|
||||
/**
|
||||
* The various account types we have (used by collateral)
|
||||
*/
|
||||
export enum AccountType {
|
||||
Bond = "Bond",
|
||||
External = "External",
|
||||
FeeInfrastructure = "FeeInfrastructure",
|
||||
FeeLiquidity = "FeeLiquidity",
|
||||
FeeMaker = "FeeMaker",
|
||||
General = "General",
|
||||
GlobalInsurance = "GlobalInsurance",
|
||||
GlobalReward = "GlobalReward",
|
||||
Insurance = "Insurance",
|
||||
LockWithdraw = "LockWithdraw",
|
||||
Margin = "Margin",
|
||||
PendingTransfers = "PendingTransfers",
|
||||
RewardLpReceivedFees = "RewardLpReceivedFees",
|
||||
RewardMakerReceivedFees = "RewardMakerReceivedFees",
|
||||
RewardMarketProposers = "RewardMarketProposers",
|
||||
RewardTakerPaidFees = "RewardTakerPaidFees",
|
||||
Settlement = "Settlement",
|
||||
}
|
||||
|
||||
export enum NodeStatus {
|
||||
NonValidator = "NonValidator",
|
||||
Validator = "Validator",
|
||||
}
|
||||
|
||||
/**
|
||||
* Reason for the proposal being rejected by the core node
|
||||
*/
|
||||
export enum ProposalRejectionReason {
|
||||
CloseTimeTooLate = "CloseTimeTooLate",
|
||||
CloseTimeTooSoon = "CloseTimeTooSoon",
|
||||
CouldNotInstantiateMarket = "CouldNotInstantiateMarket",
|
||||
EnactTimeTooLate = "EnactTimeTooLate",
|
||||
EnactTimeTooSoon = "EnactTimeTooSoon",
|
||||
IncompatibleTimestamps = "IncompatibleTimestamps",
|
||||
InsufficientTokens = "InsufficientTokens",
|
||||
InvalidAsset = "InvalidAsset",
|
||||
InvalidAssetDetails = "InvalidAssetDetails",
|
||||
InvalidFeeAmount = "InvalidFeeAmount",
|
||||
InvalidFutureMaturityTimestamp = "InvalidFutureMaturityTimestamp",
|
||||
InvalidFutureProduct = "InvalidFutureProduct",
|
||||
InvalidInstrumentSecurity = "InvalidInstrumentSecurity",
|
||||
InvalidRiskParameter = "InvalidRiskParameter",
|
||||
InvalidShape = "InvalidShape",
|
||||
MajorityThresholdNotReached = "MajorityThresholdNotReached",
|
||||
MarketMissingLiquidityCommitment = "MarketMissingLiquidityCommitment",
|
||||
MissingBuiltinAssetField = "MissingBuiltinAssetField",
|
||||
MissingCommitmentAmount = "MissingCommitmentAmount",
|
||||
MissingERC20ContractAddress = "MissingERC20ContractAddress",
|
||||
NetworkParameterInvalidKey = "NetworkParameterInvalidKey",
|
||||
NetworkParameterInvalidValue = "NetworkParameterInvalidValue",
|
||||
NetworkParameterValidationFailed = "NetworkParameterValidationFailed",
|
||||
NoProduct = "NoProduct",
|
||||
NoRiskParameters = "NoRiskParameters",
|
||||
NoTradingMode = "NoTradingMode",
|
||||
NodeValidationFailed = "NodeValidationFailed",
|
||||
OpeningAuctionDurationTooLarge = "OpeningAuctionDurationTooLarge",
|
||||
OpeningAuctionDurationTooSmall = "OpeningAuctionDurationTooSmall",
|
||||
ParticipationThresholdNotReached = "ParticipationThresholdNotReached",
|
||||
ProductMaturityIsPassed = "ProductMaturityIsPassed",
|
||||
UnsupportedProduct = "UnsupportedProduct",
|
||||
UnsupportedTradingMode = "UnsupportedTradingMode",
|
||||
}
|
||||
|
||||
/**
|
||||
* Various states a proposal can transition through:
|
||||
* Open ->
|
||||
* - Passed -> Enacted.
|
||||
* - Rejected.
|
||||
* Proposal can enter Failed state from any other state.
|
||||
*/
|
||||
export enum ProposalState {
|
||||
Declined = "Declined",
|
||||
Enacted = "Enacted",
|
||||
Failed = "Failed",
|
||||
Open = "Open",
|
||||
Passed = "Passed",
|
||||
Rejected = "Rejected",
|
||||
WaitingForNodeVote = "WaitingForNodeVote",
|
||||
}
|
||||
|
||||
/**
|
||||
* The status of the stake linking
|
||||
*/
|
||||
export enum StakeLinkingStatus {
|
||||
Accepted = "Accepted",
|
||||
Pending = "Pending",
|
||||
Rejected = "Rejected",
|
||||
}
|
||||
|
||||
export enum VoteValue {
|
||||
No = "No",
|
||||
Yes = "Yes",
|
||||
}
|
||||
|
||||
/**
|
||||
* The status of a withdrawal
|
||||
*/
|
||||
export enum WithdrawalStatus {
|
||||
Finalized = "Finalized",
|
||||
Open = "Open",
|
||||
Rejected = "Rejected",
|
||||
}
|
||||
|
||||
//==============================================================
|
||||
// END Enums and Input Objects
|
||||
//==============================================================
|
153
apps/token/src/app-loader.tsx
Normal file
153
apps/token/src/app-loader.tsx
Normal file
@ -0,0 +1,153 @@
|
||||
import * as Sentry from '@sentry/react';
|
||||
import { Splash } from '@vegaprotocol/ui-toolkit';
|
||||
import { useVegaWallet, useEagerConnect } from '@vegaprotocol/wallet';
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { SplashError } from './components/splash-error';
|
||||
import { SplashLoader } from './components/splash-loader';
|
||||
import { Flags } from './config';
|
||||
import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
} from './contexts/app-state/app-state-context';
|
||||
import { useContracts } from './contexts/contracts/contracts-context';
|
||||
import { useRefreshAssociatedBalances } from './hooks/use-refresh-associated-balances';
|
||||
import { getDataNodeUrl } from './lib/get-data-node-url';
|
||||
import { Connectors } from './lib/vega-connectors';
|
||||
|
||||
export const AppLoader = ({ children }: { children: React.ReactElement }) => {
|
||||
const { t } = useTranslation();
|
||||
const { account } = useWeb3React();
|
||||
const { keypair } = useVegaWallet();
|
||||
const { appDispatch } = useAppState();
|
||||
const { token, staking, vesting } = useContracts();
|
||||
const setAssociatedBalances = useRefreshAssociatedBalances();
|
||||
const [balancesLoaded, setBalancesLoaded] = React.useState(false);
|
||||
const vegaConnecting = useEagerConnect(Connectors);
|
||||
|
||||
const loaded = balancesLoaded && !vegaConnecting;
|
||||
|
||||
React.useEffect(() => {
|
||||
const run = async () => {
|
||||
try {
|
||||
const [
|
||||
supply,
|
||||
totalAssociatedWallet,
|
||||
totalAssociatedVesting,
|
||||
decimals,
|
||||
] = await Promise.all([
|
||||
token.totalSupply(),
|
||||
staking.totalStaked(),
|
||||
vesting.totalStaked(),
|
||||
token.decimals(),
|
||||
]);
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_TOKEN,
|
||||
decimals,
|
||||
totalSupply: supply,
|
||||
totalAssociated: totalAssociatedWallet.plus(totalAssociatedVesting),
|
||||
});
|
||||
setBalancesLoaded(true);
|
||||
} catch (err) {
|
||||
Sentry.captureException(err);
|
||||
}
|
||||
};
|
||||
|
||||
if (!Flags.NETWORK_DOWN) {
|
||||
run();
|
||||
}
|
||||
}, [token, appDispatch, staking, vesting]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (account && keypair) {
|
||||
setAssociatedBalances(account, keypair.pub);
|
||||
}
|
||||
}, [setAssociatedBalances, account, keypair]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const { base } = getDataNodeUrl();
|
||||
const networkLimitsEndpoint = new URL('/network/limits', base).href;
|
||||
const statsEndpoint = new URL('/statistics', base).href;
|
||||
|
||||
// eslint-disable-next-line
|
||||
let interval: any = null;
|
||||
|
||||
const getNetworkLimits = async () => {
|
||||
try {
|
||||
const [networkLimits, stats] = await Promise.all([
|
||||
fetch(networkLimitsEndpoint).then((res) => res.json()),
|
||||
fetch(statsEndpoint).then((res) => res.json()),
|
||||
]);
|
||||
|
||||
const restoreBlock = Number(
|
||||
networkLimits.networkLimits.bootstrapBlockCount
|
||||
);
|
||||
const currentBlock = Number(stats.statistics.blockHeight);
|
||||
|
||||
if (currentBlock <= restoreBlock) {
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_BANNER_MESSAGE,
|
||||
message: t('networkRestoring', {
|
||||
bootstrapBlockCount: restoreBlock,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!interval) {
|
||||
startPoll();
|
||||
}
|
||||
} else {
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_BANNER_MESSAGE,
|
||||
message: '',
|
||||
});
|
||||
|
||||
if (interval) {
|
||||
stopPoll();
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
Sentry.captureException(err);
|
||||
}
|
||||
};
|
||||
|
||||
const stopPoll = () => {
|
||||
clearInterval(interval);
|
||||
interval = null;
|
||||
};
|
||||
|
||||
const startPoll = () => {
|
||||
interval = setInterval(() => {
|
||||
getNetworkLimits();
|
||||
}, 10000);
|
||||
};
|
||||
|
||||
// Only begin polling if network limits flag is set, as this is a new API not yet on mainnet 7/3/22
|
||||
if (Flags.NETWORK_LIMITS) {
|
||||
getNetworkLimits();
|
||||
}
|
||||
|
||||
return () => {
|
||||
stopPoll();
|
||||
};
|
||||
}, [appDispatch, t]);
|
||||
|
||||
if (Flags.NETWORK_DOWN) {
|
||||
return (
|
||||
<Splash>
|
||||
<SplashError />
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
if (!loaded) {
|
||||
return (
|
||||
<Splash>
|
||||
<SplashLoader />
|
||||
</Splash>
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
};
|
14
apps/token/src/app.scss
Normal file
14
apps/token/src/app.scss
Normal file
@ -0,0 +1,14 @@
|
||||
@import "./styles/colors";
|
||||
|
||||
.app {
|
||||
max-width: 1300px;
|
||||
margin: 0 auto;
|
||||
display: grid;
|
||||
grid-template-rows: min-content 1fr min-content;
|
||||
min-height: 100%;
|
||||
|
||||
@media (min-width: 960px) {
|
||||
border-left: 1px solid $white;
|
||||
border-right: 1px solid $white;
|
||||
}
|
||||
}
|
58
apps/token/src/app.tsx
Normal file
58
apps/token/src/app.tsx
Normal file
@ -0,0 +1,58 @@
|
||||
import './i18n';
|
||||
import './app.scss';
|
||||
|
||||
import React from 'react';
|
||||
import { BrowserRouter as Router } from 'react-router-dom';
|
||||
|
||||
import { AppLoader } from './app-loader';
|
||||
import { AppBanner } from './components/app-banner';
|
||||
import { AppFooter } from './components/app-footer';
|
||||
import { BalanceManager } from './components/balance-manager';
|
||||
import { EthWallet } from './components/eth-wallet';
|
||||
import { GraphQlProvider } from './components/graphql-provider';
|
||||
import { TemplateSidebar } from './components/page-templates/template-sidebar';
|
||||
import { TransactionModal } from './components/transactions-modal';
|
||||
import { VegaWallet } from './components/vega-wallet';
|
||||
import { Web3Connector } from './components/web3-connector';
|
||||
import { AppStateProvider } from './contexts/app-state/app-state-provider';
|
||||
import { ContractsProvider } from './contexts/contracts/contracts-provider';
|
||||
import { AppRouter } from './routes';
|
||||
import { Web3Provider } from '@vegaprotocol/web3';
|
||||
import { Connectors } from './lib/web3-connectors';
|
||||
import { VegaWalletDialogs } from './components/vega-wallet-dialogs';
|
||||
import { VegaWalletProvider } from '@vegaprotocol/wallet';
|
||||
|
||||
function App() {
|
||||
const sideBar = React.useMemo(() => [<EthWallet />, <VegaWallet />], []);
|
||||
return (
|
||||
<GraphQlProvider>
|
||||
<Router>
|
||||
<AppStateProvider>
|
||||
<Web3Provider connectors={Connectors}>
|
||||
<Web3Connector>
|
||||
<VegaWalletProvider>
|
||||
<ContractsProvider>
|
||||
<AppLoader>
|
||||
<BalanceManager>
|
||||
<div className="app dark">
|
||||
<AppBanner />
|
||||
<TemplateSidebar sidebar={sideBar}>
|
||||
<AppRouter />
|
||||
</TemplateSidebar>
|
||||
<AppFooter />
|
||||
</div>
|
||||
<VegaWalletDialogs />
|
||||
<TransactionModal />
|
||||
</BalanceManager>
|
||||
</AppLoader>
|
||||
</ContractsProvider>
|
||||
</VegaWalletProvider>
|
||||
</Web3Connector>
|
||||
</Web3Provider>
|
||||
</AppStateProvider>
|
||||
</Router>
|
||||
</GraphQlProvider>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
BIN
apps/token/src/assets/apple-touch-icon.png
Normal file
BIN
apps/token/src/assets/apple-touch-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
apps/token/src/assets/logo.png
Normal file
BIN
apps/token/src/assets/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 547 B |
BIN
apps/token/src/assets/logo192.png
Normal file
BIN
apps/token/src/assets/logo192.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
20
apps/token/src/assets/manifest.json
Normal file
20
apps/token/src/assets/manifest.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"short_name": "Mainnet Stats",
|
||||
"name": "Vega Mainnet statistics",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
3
apps/token/src/assets/robots.txt
Normal file
3
apps/token/src/assets/robots.txt
Normal file
@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
@ -0,0 +1,22 @@
|
||||
.add-locked-token-address {
|
||||
p {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&__or-divider {
|
||||
display: flex;
|
||||
margin: 12px 0px;
|
||||
|
||||
hr {
|
||||
flex: 1;
|
||||
margin-top: 12px;
|
||||
&:first-child {
|
||||
margin-right: 12px;
|
||||
}
|
||||
&:last-child {
|
||||
margin-left: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
import './add-locked-token.scss';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ADDRESSES } from '../../config';
|
||||
import { useAddAssetSupported } from '../../hooks/use-add-asset-to-wallet';
|
||||
import vegaVesting from '../../images/vega_vesting.png';
|
||||
import { AddTokenButtonLink } from '../add-token-button/add-token-button';
|
||||
import { Callout } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const AddLockedTokenAddress = () => {
|
||||
const { t } = useTranslation();
|
||||
const addSupported = useAddAssetSupported();
|
||||
return (
|
||||
<Callout
|
||||
title={t(
|
||||
'Keep track of locked tokens in your wallet with the VEGA (VESTING) token.'
|
||||
)}
|
||||
>
|
||||
<div className="add-locked-token-address">
|
||||
{addSupported ? (
|
||||
<>
|
||||
<p>
|
||||
<AddTokenButtonLink
|
||||
address={ADDRESSES.lockedAddress}
|
||||
symbol="VEGA🔒"
|
||||
decimals={18}
|
||||
image={vegaVesting}
|
||||
/>
|
||||
</p>
|
||||
<div className="add-locked-token-address__or-divider">
|
||||
<hr />
|
||||
{t('Or')} <hr />
|
||||
</div>
|
||||
</>
|
||||
) : null}
|
||||
<p className="mb-0">
|
||||
{t(
|
||||
'The token address is {{address}}. Hit the add token button in your ERC20 wallet and enter this address.',
|
||||
{
|
||||
address: ADDRESSES.lockedAddress,
|
||||
}
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
</Callout>
|
||||
);
|
||||
};
|
1
apps/token/src/components/add-locked-token/index.tsx
Normal file
1
apps/token/src/components/add-locked-token/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export { AddLockedTokenAddress } from "./add-locked-token";
|
@ -0,0 +1,68 @@
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useAddAssetToWallet } from '../../hooks/use-add-asset-to-wallet';
|
||||
|
||||
export const AddTokenButtonLink = ({
|
||||
address,
|
||||
symbol,
|
||||
decimals,
|
||||
image,
|
||||
}: {
|
||||
address: string;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
image: string;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { add, addSupported } = useAddAssetToWallet(
|
||||
address,
|
||||
symbol,
|
||||
decimals,
|
||||
image
|
||||
);
|
||||
if (!addSupported) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Button variant="inline-link" className="add-token-button" onClick={add}>
|
||||
{t('addTokenToWallet')}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
export const AddTokenButton = ({
|
||||
address,
|
||||
symbol,
|
||||
decimals,
|
||||
image,
|
||||
size = 32,
|
||||
className = '',
|
||||
}: {
|
||||
address: string;
|
||||
symbol: string;
|
||||
decimals: number;
|
||||
image: string;
|
||||
size?: number;
|
||||
className?: string;
|
||||
}) => {
|
||||
const { add, addSupported } = useAddAssetToWallet(
|
||||
address,
|
||||
symbol,
|
||||
decimals,
|
||||
image
|
||||
);
|
||||
if (!addSupported) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Button variant="inline-link" className="add-token-button" onClick={add}>
|
||||
<img
|
||||
className={className}
|
||||
style={{ width: size, height: size }}
|
||||
alt="token-logo"
|
||||
src={image}
|
||||
/>
|
||||
</Button>
|
||||
);
|
||||
};
|
1
apps/token/src/components/add-token-button/index.tsx
Normal file
1
apps/token/src/components/add-token-button/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export { AddTokenButton, AddTokenButtonLink } from "./add-token-button";
|
19
apps/token/src/components/app-banner/app-banner.scss
Normal file
19
apps/token/src/components/app-banner/app-banner.scss
Normal file
@ -0,0 +1,19 @@
|
||||
@import "../../styles/colors";
|
||||
|
||||
.app-banner {
|
||||
background: $white;
|
||||
padding: 10px;
|
||||
color: $text-color-inverse;
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
&__icon {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
color: $vega-red3;
|
||||
font-size: 14px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
24
apps/token/src/components/app-banner/app-banner.tsx
Normal file
24
apps/token/src/components/app-banner/app-banner.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import "./app-banner.scss";
|
||||
|
||||
import { useAppState } from "../../contexts/app-state/app-state-context";
|
||||
import { Error } from "../icons";
|
||||
|
||||
export const AppBanner = () => {
|
||||
const {
|
||||
appState: { bannerMessage },
|
||||
} = useAppState();
|
||||
|
||||
// Return empty div so that grid placement remains correct
|
||||
if (!bannerMessage) return <div />;
|
||||
|
||||
return (
|
||||
<div className="app-banner" role="alert">
|
||||
<p>
|
||||
<span className="app-banner__icon">
|
||||
<Error />
|
||||
</span>
|
||||
{bannerMessage}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
};
|
1
apps/token/src/components/app-banner/index.ts
Normal file
1
apps/token/src/components/app-banner/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./app-banner";
|
12
apps/token/src/components/app-footer/app-footer.scss
Normal file
12
apps/token/src/components/app-footer/app-footer.scss
Normal file
@ -0,0 +1,12 @@
|
||||
.app-footer {
|
||||
padding: 20px;
|
||||
font-size: 14px;
|
||||
|
||||
> p {
|
||||
margin-bottom: 7px;
|
||||
|
||||
&:last-child {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
}
|
24
apps/token/src/components/app-footer/app-footer.tsx
Normal file
24
apps/token/src/components/app-footer/app-footer.tsx
Normal file
@ -0,0 +1,24 @@
|
||||
import './app-footer.scss';
|
||||
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
import { Links } from '../../config';
|
||||
|
||||
export const AppFooter = () => {
|
||||
return (
|
||||
<footer className="app-footer">
|
||||
<p>Version: {process.env['NX_COMMIT_REF'] || 'development'}</p>
|
||||
<p>
|
||||
<Trans
|
||||
i18nKey="footerLinksText"
|
||||
components={{
|
||||
/* eslint-disable */
|
||||
feedbackLink: <a href={Links.FEEDBACK} />,
|
||||
githubLink: <a href={Links.GITHUB} />,
|
||||
/* eslint-enable */
|
||||
}}
|
||||
/>
|
||||
</p>
|
||||
</footer>
|
||||
);
|
||||
};
|
1
apps/token/src/components/app-footer/index.ts
Normal file
1
apps/token/src/components/app-footer/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./app-footer";
|
@ -0,0 +1,70 @@
|
||||
import * as Sentry from "@sentry/react";
|
||||
import { useWeb3React } from "@web3-react/core";
|
||||
import React from "react";
|
||||
|
||||
import { ADDRESSES } from "../../config";
|
||||
import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
} from "../../contexts/app-state/app-state-context";
|
||||
import { useContracts } from "../../contexts/contracts/contracts-context";
|
||||
import { useGetAssociationBreakdown } from "../../hooks/use-get-association-breakdown";
|
||||
import { useGetUserTrancheBalances } from "../../hooks/use-get-user-tranche-balances";
|
||||
import { BigNumber } from "../../lib/bignumber";
|
||||
|
||||
export const BalanceManager = ({ children }: any) => {
|
||||
const contracts = useContracts();
|
||||
const { account } = useWeb3React();
|
||||
const { appDispatch } = useAppState();
|
||||
|
||||
const getUserTrancheBalances = useGetUserTrancheBalances(
|
||||
account || "",
|
||||
contracts?.vesting
|
||||
);
|
||||
const getAssociationBreakdown = useGetAssociationBreakdown(
|
||||
account || "",
|
||||
contracts?.staking,
|
||||
contracts?.vesting
|
||||
);
|
||||
|
||||
// update balances on connect to Ethereum
|
||||
React.useEffect(() => {
|
||||
const updateBalances = async () => {
|
||||
if (!account) return;
|
||||
try {
|
||||
const [balance, walletBalance, lien, allowance] = await Promise.all([
|
||||
contracts.vesting.getUserBalanceAllTranches(account),
|
||||
contracts.token.balanceOf(account),
|
||||
contracts.vesting.getLien(account),
|
||||
contracts.token.allowance(account, ADDRESSES.stakingBridge),
|
||||
]);
|
||||
appDispatch({
|
||||
type: AppStateActionType.UPDATE_ACCOUNT_BALANCES,
|
||||
balance: new BigNumber(balance),
|
||||
walletBalance,
|
||||
lien,
|
||||
allowance,
|
||||
});
|
||||
} catch (err) {
|
||||
Sentry.captureException(err);
|
||||
}
|
||||
};
|
||||
|
||||
updateBalances();
|
||||
}, [appDispatch, contracts?.token, contracts?.vesting, account]);
|
||||
|
||||
// This use effect hook is very expensive and is kept separate to prevent expensive reloading of data.
|
||||
React.useEffect(() => {
|
||||
if (account) {
|
||||
getUserTrancheBalances();
|
||||
}
|
||||
}, [account, getUserTrancheBalances]);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (account) {
|
||||
getAssociationBreakdown();
|
||||
}
|
||||
}, [account, getAssociationBreakdown]);
|
||||
|
||||
return children;
|
||||
};
|
1
apps/token/src/components/balance-manager/index.ts
Normal file
1
apps/token/src/components/balance-manager/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./balance-manager";
|
20
apps/token/src/components/bullet-header/bullet-header.scss
Normal file
20
apps/token/src/components/bullet-header/bullet-header.scss
Normal file
@ -0,0 +1,20 @@
|
||||
@import "../../styles/colors";
|
||||
|
||||
.bullet-header {
|
||||
font-size: 16px;
|
||||
border-top: 1px solid $white;
|
||||
padding: 8px 0 25px 0;
|
||||
text-transform: uppercase;
|
||||
font-weight: 300;
|
||||
margin: 35px 0 0 0;
|
||||
width: 100%;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 10px;
|
||||
background-color: $white;
|
||||
}
|
||||
}
|
17
apps/token/src/components/bullet-header/bullet-header.tsx
Normal file
17
apps/token/src/components/bullet-header/bullet-header.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import "./bullet-header.scss";
|
||||
|
||||
import React from "react";
|
||||
|
||||
interface BulletHeaderProps {
|
||||
tag: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
|
||||
children: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export const BulletHeader = ({ tag, children, style }: BulletHeaderProps) => {
|
||||
return React.createElement(
|
||||
tag,
|
||||
{ className: "bullet-header", style },
|
||||
children
|
||||
);
|
||||
};
|
1
apps/token/src/components/bullet-header/index.ts
Normal file
1
apps/token/src/components/bullet-header/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./bullet-header";
|
@ -0,0 +1,5 @@
|
||||
@import "../../styles/colors";
|
||||
|
||||
.connected-vega-key {
|
||||
color: $white;
|
||||
}
|
17
apps/token/src/components/connected-vega-key/index.tsx
Normal file
17
apps/token/src/components/connected-vega-key/index.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import "./connected-vega-key.scss";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { ConnectToVega } from "../../routes/staking/connect-to-vega";
|
||||
|
||||
export const ConnectedVegaKey = ({ pubKey }: { pubKey: string | null }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<section className="connected-vega-key">
|
||||
<strong data-testid="connected-vega-key-label">
|
||||
{pubKey ? t("Connected Vega key") : <ConnectToVega />}
|
||||
</strong>
|
||||
<p data-testid="connected-vega-key">{pubKey}</p>
|
||||
</section>
|
||||
);
|
||||
};
|
1004
apps/token/src/components/country-selector/country-data.js
Normal file
1004
apps/token/src/components/country-selector/country-data.js
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,65 @@
|
||||
import { MenuItem } from '@blueprintjs/core';
|
||||
import type { ItemPredicate } from '@blueprintjs/select';
|
||||
import { Suggest } from '@blueprintjs/select';
|
||||
|
||||
import type { ICountry } from '../../routes/claim/claim-form';
|
||||
import countryData from './country-data';
|
||||
|
||||
const CountrySuggest = Suggest.ofType<ICountry>();
|
||||
|
||||
export const filterCountry: ItemPredicate<ICountry> = (
|
||||
query,
|
||||
country,
|
||||
_index,
|
||||
exactMatch
|
||||
) => {
|
||||
const normalizedTitle = country.name.toLowerCase();
|
||||
const normalizedQuery = query.toLowerCase();
|
||||
|
||||
if (exactMatch) {
|
||||
return normalizedTitle === normalizedQuery;
|
||||
} else if (query.length === 2) {
|
||||
return normalizedQuery === country.code.toLowerCase();
|
||||
} else {
|
||||
return normalizedTitle.indexOf(normalizedQuery) >= 0;
|
||||
}
|
||||
};
|
||||
|
||||
export interface CountrySelectorProps {
|
||||
onSelectCountry: (countryCode: string) => void;
|
||||
code: string | null;
|
||||
}
|
||||
|
||||
export const CountrySelector = ({
|
||||
onSelectCountry,
|
||||
code,
|
||||
}: CountrySelectorProps) => {
|
||||
return (
|
||||
<div data-testid="country-selector">
|
||||
<CountrySuggest
|
||||
selectedItem={
|
||||
countryData.find((c) => c.code === code) || countryData[0]
|
||||
}
|
||||
items={countryData}
|
||||
itemRenderer={(item, { handleClick, modifiers }) => (
|
||||
<MenuItem
|
||||
data-testid={item.code}
|
||||
key={item.code}
|
||||
text={item.name}
|
||||
active={modifiers.active}
|
||||
disabled={modifiers.disabled}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
)}
|
||||
onItemSelect={(item) => {
|
||||
onSelectCountry(item.code);
|
||||
}}
|
||||
inputValueRenderer={(item) => item.name}
|
||||
popoverProps={{ minimal: true }}
|
||||
noResults={<MenuItem disabled={true} text="No results." />}
|
||||
itemPredicate={filterCountry}
|
||||
fill={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
1
apps/token/src/components/country-selector/index.ts
Normal file
1
apps/token/src/components/country-selector/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { CountrySelector } from "./country-selector";
|
@ -0,0 +1,50 @@
|
||||
@import '../../styles/colors';
|
||||
|
||||
.epoch-countdown {
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
margin: 0 0 5px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 12px;
|
||||
margin: 5px 0 0;
|
||||
}
|
||||
|
||||
.bp3-progress-bar {
|
||||
border: 1px solid $white;
|
||||
border-radius: 0;
|
||||
height: 21px;
|
||||
|
||||
.bp3-progress-meter {
|
||||
border-radius: 0;
|
||||
|
||||
.bp3-dark & {
|
||||
background-color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__title {
|
||||
display: flex;
|
||||
h3:first-child {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&__arrow {
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
display: inline-block;
|
||||
width: 5px;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
&__time-range {
|
||||
display: flex;
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
import './epoch-countdown.scss';
|
||||
|
||||
import { Intent, ProgressBar } from '@blueprintjs/core';
|
||||
import { format, formatDistanceStrict } from 'date-fns';
|
||||
import * as React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import arrow from '../../images/back.png';
|
||||
import { DATE_FORMAT_DETAILED } from '../../lib/date-formats';
|
||||
|
||||
export interface EpochCountdownProps {
|
||||
id: string;
|
||||
startDate: Date;
|
||||
endDate: Date;
|
||||
containerClass?: string;
|
||||
}
|
||||
|
||||
export function EpochCountdown({
|
||||
id,
|
||||
startDate,
|
||||
endDate,
|
||||
containerClass,
|
||||
}: EpochCountdownProps) {
|
||||
const { t } = useTranslation();
|
||||
const [now, setNow] = React.useState(Date.now());
|
||||
|
||||
// number between 0 and 1 for percentage progress
|
||||
const progress = React.useMemo(() => {
|
||||
const start = startDate.getTime();
|
||||
const end = endDate.getTime();
|
||||
|
||||
if (now > end) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// round it to make testing easier
|
||||
return Number(((now - start) / (end - start)).toFixed(2));
|
||||
}, [startDate, endDate, now]);
|
||||
|
||||
// format end date into readable 'time until' text
|
||||
const endsIn = React.useMemo(() => {
|
||||
if (endDate.getTime() > now) {
|
||||
return formatDistanceStrict(now, endDate);
|
||||
}
|
||||
return 0;
|
||||
}, [now, endDate]);
|
||||
|
||||
// start interval updating current time stamp until
|
||||
// its passed the end date
|
||||
React.useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
const d = Date.now();
|
||||
setNow(d);
|
||||
|
||||
if (d > endDate.getTime()) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [endDate]);
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="epoch-countdown"
|
||||
className={`${containerClass} epoch-countdown`}
|
||||
>
|
||||
<div className="epoch-countdown__title">
|
||||
<h3>
|
||||
{t('Epoch')} {id}
|
||||
</h3>
|
||||
<p>
|
||||
{endsIn
|
||||
? t('Next epoch in {{endText}}', { endText: endsIn })
|
||||
: t('Awaiting next epoch')}
|
||||
</p>
|
||||
</div>
|
||||
<ProgressBar
|
||||
animate={false}
|
||||
value={progress}
|
||||
stripes={false}
|
||||
intent={Intent.NONE}
|
||||
/>
|
||||
<div className="epoch-countdown__time-range">
|
||||
<p>{format(startDate, DATE_FORMAT_DETAILED)}</p>
|
||||
<div className="epoch-countdown__arrow">
|
||||
<img alt="arrow" src={arrow} />
|
||||
</div>
|
||||
<p>{format(endDate, DATE_FORMAT_DETAILED)}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
1
apps/token/src/components/epoch-countdown/index.tsx
Normal file
1
apps/token/src/components/epoch-countdown/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export * from "./epoch-countdown";
|
@ -0,0 +1,33 @@
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
} from '../../contexts/app-state/app-state-context';
|
||||
|
||||
interface EthConnectPrompProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export const EthConnectPrompt = ({ children }: EthConnectPrompProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { appDispatch } = useAppState();
|
||||
return (
|
||||
<>
|
||||
{children}
|
||||
<Button
|
||||
onClick={() =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
className="fill"
|
||||
data-testid="connect-to-eth-btn"
|
||||
>
|
||||
{t('connectEthWallet')}
|
||||
</Button>
|
||||
</>
|
||||
);
|
||||
};
|
1
apps/token/src/components/eth-connect-prompt/index.ts
Normal file
1
apps/token/src/components/eth-connect-prompt/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./eth-connect-promp";
|
@ -0,0 +1,10 @@
|
||||
.eth-wallet-container {
|
||||
flex-direction: row;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
svg.icon {
|
||||
width: 1.5rem;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
import './eth-wallet-container.scss';
|
||||
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
} from '../../contexts/app-state/app-state-context';
|
||||
import { Ethereum } from '../icons';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
interface EthWalletContainerProps {
|
||||
children: (address: string) => React.ReactElement;
|
||||
}
|
||||
|
||||
export const EthWalletContainer = ({ children }: EthWalletContainerProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { appDispatch } = useAppState();
|
||||
const { account } = useWeb3React();
|
||||
|
||||
if (!account) {
|
||||
return (
|
||||
<Button
|
||||
className="eth-wallet-container fill"
|
||||
data-testid="connect-to-eth-btn"
|
||||
onClick={() =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
<div>{t('connectEthWallet')}</div>
|
||||
<Ethereum />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
|
||||
return children(account);
|
||||
};
|
1
apps/token/src/components/eth-wallet-container/index.ts
Normal file
1
apps/token/src/components/eth-wallet-container/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./eth-wallet-container";
|
25
apps/token/src/components/eth-wallet/eth-wallet.scss
Normal file
25
apps/token/src/components/eth-wallet/eth-wallet.scss
Normal file
@ -0,0 +1,25 @@
|
||||
@import "../../styles/colors";
|
||||
@import "../../styles/fonts";
|
||||
|
||||
.eth-wallet {
|
||||
&__pending-tx-button {
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
justify-content: space-between;
|
||||
padding: 5px;
|
||||
background: $black;
|
||||
color: $white;
|
||||
flex-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
background: $black;
|
||||
}
|
||||
}
|
||||
|
||||
&__curr-key {
|
||||
font-family: $font-mono;
|
||||
padding: 0 8px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
244
apps/token/src/components/eth-wallet/eth-wallet.tsx
Normal file
244
apps/token/src/components/eth-wallet/eth-wallet.tsx
Normal file
@ -0,0 +1,244 @@
|
||||
import './eth-wallet.scss';
|
||||
|
||||
import { useWeb3React } from '@web3-react/core';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Colors } from '../../config';
|
||||
import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
} from '../../contexts/app-state/app-state-context';
|
||||
import { usePendingTransactions } from '../../hooks/use-pending-transactions';
|
||||
import vegaVesting from '../../images/vega_vesting.png';
|
||||
import vegaWhite from '../../images/vega_white.png';
|
||||
import { BigNumber } from '../../lib/bignumber';
|
||||
import { truncateMiddle } from '../../lib/truncate-middle';
|
||||
import { Routes } from '../../routes/router-config';
|
||||
import { LockedProgress } from '../locked-progress';
|
||||
import {
|
||||
WalletCard,
|
||||
WalletCardActions,
|
||||
WalletCardAsset,
|
||||
WalletCardContent,
|
||||
WalletCardHeader,
|
||||
WalletCardRow,
|
||||
} from '../wallet-card';
|
||||
import { Button, Loader } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
const removeLeadingAddressSymbol = (key: string) => {
|
||||
if (key && key.length > 2 && key.slice(0, 2) === '0x') {
|
||||
return truncateMiddle(key.substring(2));
|
||||
}
|
||||
return truncateMiddle(key);
|
||||
};
|
||||
|
||||
const AssociatedAmounts = ({
|
||||
associations,
|
||||
notAssociated,
|
||||
}: {
|
||||
associations: { [key: string]: BigNumber };
|
||||
notAssociated: BigNumber;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const vestingAssociationByVegaKey = React.useMemo(
|
||||
() =>
|
||||
Object.entries(associations).filter(([, amount]) =>
|
||||
amount.isGreaterThan(0)
|
||||
),
|
||||
[associations]
|
||||
);
|
||||
const associationAmounts = React.useMemo(() => {
|
||||
const totals = vestingAssociationByVegaKey.map(([, amount]) => amount);
|
||||
const associated = BigNumber.sum.apply(null, [new BigNumber(0), ...totals]);
|
||||
|
||||
return {
|
||||
total: associated.plus(notAssociated),
|
||||
associated,
|
||||
notAssociated,
|
||||
};
|
||||
}, [notAssociated, vestingAssociationByVegaKey]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<LockedProgress
|
||||
locked={associationAmounts.associated}
|
||||
unlocked={associationAmounts.notAssociated}
|
||||
total={associationAmounts.total}
|
||||
leftLabel={t('associated')}
|
||||
rightLabel={t('notAssociated')}
|
||||
leftColor={Colors.WHITE}
|
||||
rightColor={Colors.BLACK}
|
||||
light={true}
|
||||
/>
|
||||
{vestingAssociationByVegaKey.length ? (
|
||||
<>
|
||||
<hr style={{ borderStyle: 'dashed', color: Colors.TEXT }} />
|
||||
<WalletCardRow label="Associated with Vega keys" bold={true} />
|
||||
{vestingAssociationByVegaKey.map(([key, amount]) => {
|
||||
return (
|
||||
<WalletCardRow
|
||||
key={key}
|
||||
label={removeLeadingAddressSymbol(key)}
|
||||
value={amount}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const ConnectedKey = () => {
|
||||
const { t } = useTranslation();
|
||||
const { appState } = useAppState();
|
||||
const { walletBalance, totalLockedBalance, totalVestedBalance } = appState;
|
||||
|
||||
const totalInVestingContract = React.useMemo(() => {
|
||||
return totalLockedBalance.plus(totalVestedBalance);
|
||||
}, [totalLockedBalance, totalVestedBalance]);
|
||||
|
||||
const notAssociatedInContract = React.useMemo(() => {
|
||||
const totals = Object.values(
|
||||
appState.associationBreakdown.vestingAssociations
|
||||
);
|
||||
const associated = BigNumber.sum.apply(null, [new BigNumber(0), ...totals]);
|
||||
return totalInVestingContract.minus(associated);
|
||||
}, [
|
||||
appState.associationBreakdown.vestingAssociations,
|
||||
totalInVestingContract,
|
||||
]);
|
||||
|
||||
const walletWithAssociations = React.useMemo(() => {
|
||||
const totals = Object.values(
|
||||
appState.associationBreakdown.stakingAssociations
|
||||
);
|
||||
const associated = BigNumber.sum.apply(null, [new BigNumber(0), ...totals]);
|
||||
return walletBalance.plus(associated);
|
||||
}, [appState.associationBreakdown.stakingAssociations, walletBalance]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{totalVestedBalance.plus(totalLockedBalance).isEqualTo(0) ? null : (
|
||||
<>
|
||||
<WalletCardAsset
|
||||
image={vegaVesting}
|
||||
decimals={appState.decimals}
|
||||
name="VEGA"
|
||||
symbol="In vesting contract"
|
||||
balance={totalInVestingContract}
|
||||
/>
|
||||
<LockedProgress
|
||||
locked={totalLockedBalance}
|
||||
unlocked={totalVestedBalance}
|
||||
total={totalVestedBalance.plus(totalLockedBalance)}
|
||||
leftLabel={t('Locked')}
|
||||
rightLabel={t('Unlocked')}
|
||||
light={true}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
{!Object.keys(appState.associationBreakdown.vestingAssociations)
|
||||
.length ? null : (
|
||||
<AssociatedAmounts
|
||||
associations={appState.associationBreakdown.vestingAssociations}
|
||||
notAssociated={notAssociatedInContract}
|
||||
/>
|
||||
)}
|
||||
<WalletCardAsset
|
||||
image={vegaWhite}
|
||||
decimals={appState.decimals}
|
||||
name="VEGA"
|
||||
symbol="In Wallet"
|
||||
balance={walletWithAssociations}
|
||||
/>
|
||||
{!Object.keys(
|
||||
appState.associationBreakdown.stakingAssociations
|
||||
) ? null : (
|
||||
<AssociatedAmounts
|
||||
associations={appState.associationBreakdown.stakingAssociations}
|
||||
notAssociated={walletBalance}
|
||||
/>
|
||||
)}
|
||||
<WalletCardActions>
|
||||
<Link style={{ flex: 1 }} to={`${Routes.STAKING}/associate`}>
|
||||
<Button variant="primary" className="h-auto py-12 w-full">
|
||||
{t('associate')}
|
||||
</Button>
|
||||
</Link>
|
||||
<Link style={{ flex: 1 }} to={`${Routes.STAKING}/disassociate`}>
|
||||
<Button variant="primary" className="h-auto py-12 w-full">
|
||||
{t('disassociate')}
|
||||
</Button>
|
||||
</Link>
|
||||
</WalletCardActions>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const EthWallet = () => {
|
||||
const { t } = useTranslation();
|
||||
const { appDispatch } = useAppState();
|
||||
const { account, connector } = useWeb3React();
|
||||
const pendingTxs = usePendingTransactions();
|
||||
|
||||
return (
|
||||
<WalletCard>
|
||||
<WalletCardHeader>
|
||||
<h1 className="text-h3">{t('ethereumKey')}</h1>
|
||||
{account && (
|
||||
<div className="eth-wallet__curr-key">
|
||||
<div>{truncateMiddle(account)}</div>
|
||||
{pendingTxs && (
|
||||
<div>
|
||||
<Button
|
||||
className="eth-wallet__pending-tx-button"
|
||||
data-testid="pending-transactions-btn"
|
||||
onClick={() =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_TRANSACTION_OVERLAY,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
>
|
||||
<Loader size="small" />
|
||||
{t('pendingTransactions')}
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</WalletCardHeader>
|
||||
<WalletCardContent>
|
||||
{account ? (
|
||||
<ConnectedKey />
|
||||
) : (
|
||||
<Button
|
||||
className="fill button-secondary--inverted"
|
||||
onClick={() =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_ETH_WALLET_OVERLAY,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
data-test-id="connect-to-eth-wallet-button"
|
||||
>
|
||||
{t('connectEthWalletToAssociate')}
|
||||
</Button>
|
||||
)}
|
||||
{account && (
|
||||
<WalletCardActions>
|
||||
<button
|
||||
className="mt-4 underline"
|
||||
onClick={() => connector.deactivate()}
|
||||
>
|
||||
{t('disconnect')}
|
||||
</button>
|
||||
</WalletCardActions>
|
||||
)}
|
||||
</WalletCardContent>
|
||||
</WalletCard>
|
||||
);
|
||||
};
|
1
apps/token/src/components/eth-wallet/index.tsx
Normal file
1
apps/token/src/components/eth-wallet/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export * from "./eth-wallet";
|
@ -0,0 +1,12 @@
|
||||
import { ApolloProvider } from "@apollo/client";
|
||||
import React from "react";
|
||||
|
||||
import { client } from "../../lib/apollo-client";
|
||||
|
||||
export const GraphQlProvider = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
return <ApolloProvider client={client}>{children}</ApolloProvider>;
|
||||
};
|
1
apps/token/src/components/graphql-provider/index.ts
Normal file
1
apps/token/src/components/graphql-provider/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./graphql-provider";
|
15
apps/token/src/components/heading/heading.tsx
Normal file
15
apps/token/src/components/heading/heading.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
export interface HeadingProps {
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export const Heading = ({ title }: HeadingProps) => {
|
||||
if (!title) return null;
|
||||
|
||||
return (
|
||||
<header className="my-0 mx-auto">
|
||||
<h1 className="font-alpha font-normal text-h3 uppercase m-0 mb-4">
|
||||
{title}
|
||||
</h1>
|
||||
</header>
|
||||
);
|
||||
};
|
1
apps/token/src/components/heading/index.ts
Normal file
1
apps/token/src/components/heading/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { Heading } from "./heading";
|
8
apps/token/src/components/icons/error.tsx
Normal file
8
apps/token/src/components/icons/error.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
export const Error = () => (
|
||||
<svg className="icon" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M7.99-0.01c-4.42,0-8,3.58-8,8s3.58,8,8,8s8-3.58,8-8S12.41-0.01,7.99-0.01z
|
||||
M8.99,12.99h-2v-2h2V12.99z M8.99,9.99h-2v-7h2V9.99z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
16
apps/token/src/components/icons/ethereum.tsx
Normal file
16
apps/token/src/components/icons/ethereum.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
export function Ethereum() {
|
||||
return (
|
||||
<svg
|
||||
data-testid="icon-ethereum"
|
||||
className="icon icon-ethereum"
|
||||
viewBox="0 0 50 50"
|
||||
>
|
||||
<path d="M25.3999 0.160156L40.1999 24.6102L25.4499 18.3102L25.3999 0.160156Z" />
|
||||
<path d="M10.24 24.6102L25 0.160156L25.05 18.3102L10.24 24.6102Z" />
|
||||
<path d="M25 34.1305L10.24 25.1105L25.05 18.8105L25 34.1305Z" />
|
||||
<path d="M40.1999 25.1105L25.4499 18.8105L25.3999 34.1305L40.1999 25.1105Z" />
|
||||
<path d="M25.3999 37.23L40.1999 28.46L25.3999 49.31V37.23Z" />
|
||||
<path d="M25 37.23L10.24 28.46L25 49.31V37.23Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
10
apps/token/src/components/icons/hand-up.tsx
Normal file
10
apps/token/src/components/icons/hand-up.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
export const HandUp = () => (
|
||||
<svg className="icon" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M13.65,6.19c-0.34,0-0.64,0.11-0.88,0.29C12.6,6,12.09,5.64,11.48,5.64
|
||||
c-0.41,0-0.78,0.16-1.03,0.42c-0.23-0.37-0.67-0.63-1.19-0.63c-0.57,0-1.05,0.31-1.25,0.74L8,5.55V1.5C8,0.67,7.33,0,6.5,0
|
||||
S5,0.67,5,1.5v6.61C4.42,7.7,3.45,6.9,2.52,6.81C0.96,6.67,0.7,7.88,1.28,8.13c1.54,0.67,2.99,2.68,3.7,3.95
|
||||
C5.89,14.05,6.07,16,9.86,16c2.09,0,3.43-0.61,4.22-2.12C14.72,12.64,15,10.79,15,8.17V7.38C15,6.73,14.4,6.19,13.65,6.19z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
4
apps/token/src/components/icons/icons.scss
Normal file
4
apps/token/src/components/icons/icons.scss
Normal file
@ -0,0 +1,4 @@
|
||||
svg.icon {
|
||||
fill: currentColor;
|
||||
width: 1em;
|
||||
}
|
6
apps/token/src/components/icons/index.ts
Normal file
6
apps/token/src/components/icons/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import "./icons.scss";
|
||||
|
||||
export * from "./error";
|
||||
export * from "./tick";
|
||||
export * from "./hand-up";
|
||||
export * from "./ethereum";
|
9
apps/token/src/components/icons/tick.tsx
Normal file
9
apps/token/src/components/icons/tick.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
export const Tick = () => (
|
||||
<svg className="icon" viewBox="0 0 16 16">
|
||||
<path
|
||||
d="M14,3c-0.28,0-0.53,0.11-0.71,0.29L6,10.59L2.71,7.29C2.53,7.11,2.28,7,2,7
|
||||
C1.45,7,1,7.45,1,8c0,0.28,0.11,0.53,0.29,0.71l4,4C5.47,12.89,5.72,13,6,13s0.53-0.11,0.71-0.29l8-8C14.89,4.53,15,4.28,15,4
|
||||
C15,3.45,14.55,3,14,3z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
1
apps/token/src/components/key-value-table/index.tsx
Normal file
1
apps/token/src/components/key-value-table/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export { KeyValueTable, KeyValueTableRow } from './key-value-table'
|
@ -0,0 +1,85 @@
|
||||
@import "../../styles/colors";
|
||||
@import "../../styles/fonts";
|
||||
|
||||
$ns: "key-value-table";
|
||||
|
||||
.#{$ns}__header,
|
||||
.#{$ns}__footer {
|
||||
margin: 10px 0 5px;
|
||||
}
|
||||
|
||||
.#{$ns}__header {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
.#{$ns}__row {
|
||||
@media (max-width: 640px) {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.#{$ns} {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
margin-bottom: 10px;
|
||||
word-break: break-all;
|
||||
|
||||
tr {
|
||||
border-bottom: 1px solid $white;
|
||||
|
||||
&:first-child {
|
||||
border-top: 1px solid $white;
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
word-break: break-word;
|
||||
text-align: left;
|
||||
font-weight: 500;
|
||||
color: $white;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
vertical-align: top;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
td {
|
||||
color: $text-color;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.bp3-tag {
|
||||
margin: 0 5px 5px 0;
|
||||
}
|
||||
|
||||
&.#{$ns}--numerical {
|
||||
td {
|
||||
font-family: $font-mono;
|
||||
|
||||
& button {
|
||||
font-family: $font-main;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.#{$ns}--muted {
|
||||
tr {
|
||||
border-color: $gray1;
|
||||
|
||||
&:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
|
||||
import { KeyValueTable, KeyValueTableRow } from "./key-value-table";
|
||||
|
||||
const props = {
|
||||
title: "Title",
|
||||
};
|
||||
|
||||
it("Renders the correct elements", () => {
|
||||
const { container } = render(
|
||||
<KeyValueTable {...props}>
|
||||
<KeyValueTableRow>
|
||||
<td>My label</td>
|
||||
<td>My value</td>
|
||||
</KeyValueTableRow>
|
||||
<KeyValueTableRow>
|
||||
<td>My label 2</td>
|
||||
<td>My value 2</td>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
);
|
||||
|
||||
expect(screen.getByText(props.title)).toBeInTheDocument();
|
||||
|
||||
expect(container.querySelector(".key-value-table")).toBeInTheDocument();
|
||||
expect(container.querySelectorAll(".key-value-table__row")).toHaveLength(2);
|
||||
|
||||
const rows = container.querySelectorAll(".key-value-table__row");
|
||||
// Row 1
|
||||
expect(rows[0].firstChild).toHaveTextContent("My label");
|
||||
expect(rows[0].children[1]).toHaveTextContent("My value");
|
||||
|
||||
// Row 2
|
||||
expect(rows[1].firstChild).toHaveTextContent("My label 2");
|
||||
expect(rows[1].children[1]).toHaveTextContent("My value 2");
|
||||
});
|
||||
|
||||
it("Applies numeric class if prop is passed", () => {
|
||||
render(
|
||||
<KeyValueTable numerical={true} {...props}>
|
||||
<KeyValueTableRow>
|
||||
<td>My label</td>
|
||||
<td>My value</td>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("key-value-table")).toHaveClass(
|
||||
"key-value-table--numerical"
|
||||
);
|
||||
});
|
||||
|
||||
it("Applies muted class if prop is passed", () => {
|
||||
render(
|
||||
<KeyValueTable muted={true} {...props}>
|
||||
<KeyValueTableRow>
|
||||
<td>My label</td>
|
||||
<td>My value</td>
|
||||
</KeyValueTableRow>
|
||||
</KeyValueTable>
|
||||
);
|
||||
|
||||
expect(screen.getByTestId("key-value-table")).toHaveClass(
|
||||
"key-value-table--muted"
|
||||
);
|
||||
});
|
@ -0,0 +1,58 @@
|
||||
import "./key-value-table.scss";
|
||||
|
||||
import * as React from "react";
|
||||
|
||||
export interface KeyValueTableProps
|
||||
extends React.HTMLAttributes<HTMLTableElement> {
|
||||
title?: string;
|
||||
numerical?: boolean; // makes all values monospace
|
||||
children: React.ReactNode;
|
||||
muted?: boolean;
|
||||
}
|
||||
|
||||
export const KeyValueTable = ({
|
||||
title,
|
||||
numerical,
|
||||
children,
|
||||
muted,
|
||||
className,
|
||||
...rest
|
||||
}: KeyValueTableProps) => {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{title && <h3 className="key-value-table__header">{title}</h3>}
|
||||
<table
|
||||
data-testid="key-value-table"
|
||||
{...rest}
|
||||
className={`key-value-table ${className ? className : ""} ${
|
||||
numerical ? "key-value-table--numerical" : ""
|
||||
}
|
||||
${muted ? "key-value-table--muted" : ""}`}
|
||||
>
|
||||
<tbody>{children}</tbody>
|
||||
</table>
|
||||
</React.Fragment>
|
||||
);
|
||||
};
|
||||
|
||||
export interface KeyValueTableRowProps
|
||||
extends React.HTMLAttributes<HTMLTableRowElement> {
|
||||
children: [React.ReactNode, React.ReactNode];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const KeyValueTableRow = ({
|
||||
children,
|
||||
className,
|
||||
...rest
|
||||
}: KeyValueTableRowProps) => {
|
||||
return (
|
||||
<tr
|
||||
{...rest}
|
||||
className={`key-value-table__row ${className ? className : ""}`}
|
||||
>
|
||||
{children[0]}
|
||||
{children[1]}
|
||||
</tr>
|
||||
);
|
||||
};
|
1
apps/token/src/components/loader/index.ts
Normal file
1
apps/token/src/components/loader/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./loader";
|
20
apps/token/src/components/loader/loader.scss
Normal file
20
apps/token/src/components/loader/loader.scss
Normal file
@ -0,0 +1,20 @@
|
||||
.loader {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
background: white;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&--dark {
|
||||
span {
|
||||
background: black;
|
||||
}
|
||||
}
|
||||
}
|
35
apps/token/src/components/loader/loader.tsx
Normal file
35
apps/token/src/components/loader/loader.tsx
Normal file
@ -0,0 +1,35 @@
|
||||
import "./loader.scss";
|
||||
|
||||
import React from "react";
|
||||
|
||||
interface LoaderProps {
|
||||
invert?: boolean;
|
||||
}
|
||||
|
||||
export const Loader = ({ invert = false }: LoaderProps) => {
|
||||
const [, forceRender] = React.useState(false);
|
||||
const className = ["loader", invert ? "loader--dark" : ""].join(" ");
|
||||
|
||||
React.useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
forceRender((x) => !x);
|
||||
}, 100);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<span className={className}>
|
||||
{new Array(9).fill(null).map((_, i) => {
|
||||
return (
|
||||
<span
|
||||
key={i}
|
||||
style={{
|
||||
opacity: Math.random() > 0.5 ? 1 : 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</span>
|
||||
);
|
||||
};
|
1
apps/token/src/components/locked-progress/index.ts
Normal file
1
apps/token/src/components/locked-progress/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { LockedProgress } from "./locked-progress";
|
@ -0,0 +1,59 @@
|
||||
@import "../../styles/colors";
|
||||
@import "../../styles/fonts";
|
||||
|
||||
.tranche-item {
|
||||
&__progress {
|
||||
border-left: $white;
|
||||
border-left-style: solid;
|
||||
border-left-width: 1px;
|
||||
border-right: $white;
|
||||
border-right-style: solid;
|
||||
border-right-width: 1px;
|
||||
|
||||
&-contents {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 0 5px;
|
||||
font-family: $font-mono;
|
||||
padding: 2px 5px 2px 5px;
|
||||
color: $text-color-deemphasise;
|
||||
|
||||
&-indicator {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border: 1px solid $black;
|
||||
|
||||
&--left {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&--right {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&--light {
|
||||
gap: 0;
|
||||
padding: 2px 0;
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__progress-bar {
|
||||
display: flex;
|
||||
|
||||
&--light {
|
||||
border: 1px solid $black;
|
||||
}
|
||||
|
||||
&--locked {
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&--unlocked {
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
import './locked-progress.scss';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { Colors } from '../../config';
|
||||
import { formatNumber } from '../../lib/format-number';
|
||||
import type { BigNumber } from '../../lib/bignumber';
|
||||
|
||||
export interface LockedProgressProps {
|
||||
total: BigNumber;
|
||||
locked: BigNumber;
|
||||
unlocked: BigNumber;
|
||||
leftLabel: string;
|
||||
rightLabel: string;
|
||||
leftColor?: string;
|
||||
rightColor?: string;
|
||||
light?: boolean;
|
||||
}
|
||||
|
||||
export const LockedProgress = ({
|
||||
total,
|
||||
locked,
|
||||
unlocked,
|
||||
leftLabel,
|
||||
rightLabel,
|
||||
leftColor = Colors.PINK,
|
||||
rightColor = Colors.GREEN,
|
||||
light = false,
|
||||
}: LockedProgressProps) => {
|
||||
const lockedPercentage = React.useMemo(() => {
|
||||
return locked.div(total).times(100);
|
||||
}, [total, locked]);
|
||||
|
||||
const unlockedPercentage = React.useMemo(() => {
|
||||
return unlocked.div(total).times(100);
|
||||
}, [total, unlocked]);
|
||||
|
||||
return (
|
||||
<div className="tranche-item__progress">
|
||||
<div
|
||||
className={`tranche-item__progress-bar ${
|
||||
light ? 'tranche-item__progress-bar--light' : ''
|
||||
}`}
|
||||
>
|
||||
<div
|
||||
className="tranche-item__progress-bar--locked"
|
||||
style={{
|
||||
flex: isNaN(lockedPercentage.toNumber())
|
||||
? 0
|
||||
: lockedPercentage.toNumber(),
|
||||
backgroundColor: leftColor,
|
||||
}}
|
||||
></div>
|
||||
<div
|
||||
className="tranche-item__progress-bar--unlocked"
|
||||
style={{
|
||||
flex: isNaN(unlockedPercentage.toNumber())
|
||||
? 0
|
||||
: unlockedPercentage.toNumber(),
|
||||
backgroundColor: rightColor,
|
||||
}}
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
className={`tranche-item__progress-contents ${
|
||||
light ? 'tranche-item__progress-contents--light' : ''
|
||||
}`}
|
||||
>
|
||||
<span>
|
||||
<div
|
||||
className="tranche-item__progress-contents-indicator tranche-item__progress-contents-indicator--left"
|
||||
style={{
|
||||
backgroundColor: leftColor,
|
||||
}}
|
||||
></div>
|
||||
{leftLabel}
|
||||
</span>
|
||||
<span>
|
||||
{rightLabel}
|
||||
<div
|
||||
className="tranche-item__progress-contents-indicator tranche-item__progress-contents-indicator--right"
|
||||
style={{
|
||||
backgroundColor: rightColor,
|
||||
}}
|
||||
></div>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className={`tranche-item__progress-contents ${
|
||||
light ? 'tranche-item__progress-contents--light' : ''
|
||||
}`}
|
||||
>
|
||||
<span>{formatNumber(locked, 2)}</span>
|
||||
<span>{formatNumber(unlocked, 2)}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
1
apps/token/src/components/modal/index.ts
Normal file
1
apps/token/src/components/modal/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./modal";
|
59
apps/token/src/components/modal/modal.scss
Normal file
59
apps/token/src/components/modal/modal.scss
Normal file
@ -0,0 +1,59 @@
|
||||
@import "../../styles/colors";
|
||||
|
||||
.modal {
|
||||
left: calc(50vw - 200px);
|
||||
margin: 10vh 0;
|
||||
top: 0;
|
||||
width: 400px;
|
||||
background: $white;
|
||||
color: $black;
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
a {
|
||||
color: $black;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: $black;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&__title {
|
||||
text-align: center;
|
||||
border-bottom: solid 1px $white;
|
||||
padding-bottom: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
&__content {
|
||||
padding: 0 20px 20px 20px;
|
||||
> :first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
> :last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&--dark {
|
||||
background-color: $black;
|
||||
color: $white;
|
||||
border: 1px solid $white;
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
a {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
15
apps/token/src/components/modal/modal.tsx
Normal file
15
apps/token/src/components/modal/modal.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import "./modal.scss";
|
||||
|
||||
import React from "react";
|
||||
|
||||
interface ModalProps {
|
||||
children: React.ReactNode;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const Modal = ({ children, title }: ModalProps) => (
|
||||
<div>
|
||||
<h2 className="modal__title">{title}</h2>
|
||||
<div className="modal__content">{children}</div>
|
||||
</div>
|
||||
);
|
1
apps/token/src/components/nav/index.ts
Normal file
1
apps/token/src/components/nav/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./nav";
|
212
apps/token/src/components/nav/nav.scss
Normal file
212
apps/token/src/components/nav/nav.scss
Normal file
@ -0,0 +1,212 @@
|
||||
@import "../../styles/colors";
|
||||
|
||||
.nav {
|
||||
padding: 20px;
|
||||
padding: 10px !important;
|
||||
border-bottom: 1px solid $white;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background-color: $black;
|
||||
z-index: 15;
|
||||
|
||||
h1 {
|
||||
padding: 0;
|
||||
padding-left: 8px;
|
||||
margin: 0;
|
||||
color: $white;
|
||||
font-weight: 400;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
a {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&--inverted {
|
||||
border-bottom: none;
|
||||
background: $vega-yellow3 url("../../images/clouds.png");
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: 0 -300px;
|
||||
|
||||
h1 {
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
|
||||
&__inner {
|
||||
margin: 4px auto 0;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
|
||||
@media (min-width: 960px) {
|
||||
justify-content: flex-start;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
@media (min-width: 960px) {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
> button {
|
||||
background: none;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__logo {
|
||||
width: 30px;
|
||||
|
||||
&-container {
|
||||
text-transform: uppercase;
|
||||
height: 30px;
|
||||
display: inline-flex;
|
||||
margin-left: 8px;
|
||||
|
||||
@media (min-width: 960px) {
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 960px) {
|
||||
width: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
&__drawer {
|
||||
background-image: url("../../images/banner.png");
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: space-between;
|
||||
height: 100%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
&__drawer-section {
|
||||
padding: 12px;
|
||||
|
||||
&:first-child {
|
||||
border-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__drawer-button {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
gap: 4px;
|
||||
|
||||
&:focus,
|
||||
&:hover {
|
||||
background: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
span {
|
||||
display: block;
|
||||
width: 30px;
|
||||
height: 4px;
|
||||
background: $white;
|
||||
}
|
||||
|
||||
&--inverted {
|
||||
span {
|
||||
background: $black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__wallets-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
|
||||
> button {
|
||||
padding: 3px 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
display: flex;
|
||||
|
||||
.active {
|
||||
background: $vega-yellow3;
|
||||
color: $black;
|
||||
}
|
||||
|
||||
> a.active {
|
||||
&:hover {
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
|
||||
> a {
|
||||
color: $white;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
&--row {
|
||||
flex-direction: row;
|
||||
text-transform: uppercase;
|
||||
|
||||
> a {
|
||||
background: transparent;
|
||||
padding: 3px 10px;
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
|
||||
&--column {
|
||||
border-top: 1px solid $white;
|
||||
flex-direction: column;
|
||||
grid-gap: 0;
|
||||
gap: 0;
|
||||
|
||||
.active {
|
||||
background: $vega-yellow3;
|
||||
color: $black;
|
||||
}
|
||||
|
||||
> a {
|
||||
display: block;
|
||||
background: $black;
|
||||
color: $white;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
> a:not(:first-child) {
|
||||
border-top: 1px solid $white;
|
||||
}
|
||||
}
|
||||
|
||||
&--inverted {
|
||||
.active {
|
||||
background: $black;
|
||||
color: $white;
|
||||
}
|
||||
|
||||
> a {
|
||||
color: $black;
|
||||
text-decoration: none;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
> a.active {
|
||||
&:hover {
|
||||
color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
219
apps/token/src/components/nav/nav.tsx
Normal file
219
apps/token/src/components/nav/nav.tsx
Normal file
@ -0,0 +1,219 @@
|
||||
import './nav.scss';
|
||||
|
||||
import { Drawer } from '@blueprintjs/core';
|
||||
import debounce from 'lodash/debounce';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link, NavLink } from 'react-router-dom';
|
||||
|
||||
import { Flags } from '../../config';
|
||||
import {
|
||||
AppStateActionType,
|
||||
useAppState,
|
||||
} from '../../contexts/app-state/app-state-context';
|
||||
import vegaWhite from '../../images/vega_white.png';
|
||||
import { Routes } from '../../routes/router-config';
|
||||
import { EthWallet } from '../eth-wallet';
|
||||
import { VegaWallet } from '../vega-wallet';
|
||||
|
||||
export const Nav = () => {
|
||||
const [windowWidth, setWindowWidth] = React.useState(window.innerWidth);
|
||||
const isDesktop = windowWidth > 959;
|
||||
const inverted = Flags.FAIRGROUND;
|
||||
|
||||
React.useEffect(() => {
|
||||
const handleResizeDebounced = debounce(() => {
|
||||
setWindowWidth(window.innerWidth);
|
||||
}, 300);
|
||||
|
||||
window.addEventListener('resize', handleResizeDebounced);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResizeDebounced);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`nav nav-${isDesktop ? 'large' : 'small'} ${
|
||||
inverted ? 'nav--inverted' : ''
|
||||
}`}
|
||||
>
|
||||
{isDesktop && <NavHeader fairground={inverted} />}
|
||||
<div className="nav__inner">
|
||||
{!isDesktop && <NavHeader fairground={inverted} />}
|
||||
<div className="nav__actions">
|
||||
{isDesktop ? (
|
||||
<NavLinks inverted={inverted} isDesktop={isDesktop} />
|
||||
) : (
|
||||
<NavDrawer inverted={inverted} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NavHeader = ({ fairground }: { fairground: boolean }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="nav__logo-container">
|
||||
<Link to="/">
|
||||
{fairground ? (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="33"
|
||||
height="20"
|
||||
viewBox="0 0 200 116"
|
||||
fill="none"
|
||||
data-testid="fairground-icon"
|
||||
>
|
||||
<g clip-path="url(#clip0)">
|
||||
<path
|
||||
d="M70.5918 32.8569L70.5918 22.7932L60.5254 22.7932L60.5254 32.8569L70.5918 32.8569Z"
|
||||
fill="black"
|
||||
></path>
|
||||
<path
|
||||
d="M80.6641 83.2006L80.6641 73.1377L70.5977 73.1377L70.5977 83.2006L80.6641 83.2006Z"
|
||||
fill="black"
|
||||
></path>
|
||||
<path
|
||||
d="M70.5918 93.2409L70.5918 83.1772L60.5254 83.1772L60.5254 93.2409L70.5918 93.2409Z"
|
||||
fill="black"
|
||||
></path>
|
||||
<path
|
||||
d="M100.797 93.2636L100.797 73.1377L90.7305 73.1377L90.7305 93.2636L100.797 93.2636Z"
|
||||
fill="black"
|
||||
></path>
|
||||
<path
|
||||
d="M90.7285 103.33L90.7285 93.2671L80.662 93.2671L80.662 103.33L90.7285 103.33Z"
|
||||
fill="black"
|
||||
></path>
|
||||
<path
|
||||
d="M90.7285 22.8026L90.7285 12.74L80.662 12.74L80.662 22.8026L90.7285 22.8026Z"
|
||||
fill="black"
|
||||
></path>
|
||||
<path
|
||||
d="M110.869 12.6108L110.869 2.54785L100.803 2.54785L100.803 12.6108L110.869 12.6108Z"
|
||||
fill="black"
|
||||
></path>
|
||||
<path
|
||||
d="M120.934 103.326L120.934 73.1377L110.867 73.1377L110.867 103.326L120.934 103.326Z"
|
||||
fill="black"
|
||||
></path>
|
||||
<path
|
||||
d="M110.869 113.384L110.869 103.321L100.803 103.321L100.803 113.384L110.869 113.384Z"
|
||||
fill="black"
|
||||
></path>
|
||||
<path
|
||||
d="M161.328 52.9855L161.328 42.9226L151.262 42.9226L151.262 52.9855L161.328 52.9855Z"
|
||||
fill="black"
|
||||
></path>
|
||||
<path
|
||||
d="M20.133 83.187L30.3354 83.187L30.3354 73.124L40.4017 73.124L40.4017 63.0613L50.4681 63.0613L50.4681 73.124L60.5345 73.124L60.5345 63.0613L70.6008 63.0613L80.6672 63.0613L131.135 63.0613L131.135 113.376L161.334 113.376L161.334 103.313L171.4 103.313L171.4 93.25L181.467 93.25L181.467 83.187L191.533 83.187L191.533 63.0613L181.467 63.0613L181.467 73.1241L171.4 73.1241L171.4 83.187L161.334 83.187L161.334 73.1241L171.4 73.1241L171.4 63.0613L161.334 63.0613L151.268 63.0613L141.201 63.0613L141.201 52.9983L141.201 32.8726L161.334 32.8726L171.4 32.8726L171.4 63.0613L181.467 63.0613L181.467 52.9983L191.533 52.9983L191.533 32.8726L181.467 32.8726L181.467 22.8096L171.4 22.8096L171.4 12.7467L161.334 12.7467L161.334 2.54785L141.201 2.54785L131.135 2.54785L131.135 52.9983L120.933 52.9983L120.933 12.7467L110.866 12.7467L110.866 52.9983L100.8 52.9983L100.8 22.8096L90.7336 22.8096L90.7336 52.9983L80.6672 52.9983L80.6672 32.8726L70.6008 32.8726L70.6008 52.9983L60.5345 52.9983L60.5345 42.9354L50.4681 42.9354L50.4681 52.9983L40.4017 52.9983L40.4017 42.9354L30.3354 42.9354L30.3354 32.8726L20.133 32.8726L20.133 22.8096L0.000308081 22.8096L0.000307201 42.9354L10.0666 42.9354L10.0666 52.9983L20.133 52.9983L20.133 63.0613L10.0666 63.0613L10.0666 73.124L0.000305881 73.124L0.000305002 93.25L20.133 93.25L20.133 83.187Z"
|
||||
fill="black"
|
||||
></path>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="200" height="116" fill="none"></rect>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
) : (
|
||||
<img alt="Vega" src={vegaWhite} className="nav__logo" />
|
||||
)}
|
||||
</Link>
|
||||
<h1 className="text-h3">
|
||||
{fairground ? t('fairgroundTitle') : t('title')}
|
||||
</h1>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const NavDrawer = ({ inverted }: { inverted: boolean }) => {
|
||||
const { appState, appDispatch } = useAppState();
|
||||
return (
|
||||
<>
|
||||
<button
|
||||
onClick={() =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_DRAWER,
|
||||
isOpen: true,
|
||||
})
|
||||
}
|
||||
className={`nav__drawer-button ${
|
||||
inverted ? 'nav__drawer-button--inverted' : ''
|
||||
}`}
|
||||
>
|
||||
<span />
|
||||
<span />
|
||||
<span />
|
||||
</button>
|
||||
<Drawer
|
||||
isOpen={appState.drawerOpen}
|
||||
onClose={() =>
|
||||
appDispatch({
|
||||
type: AppStateActionType.SET_DRAWER,
|
||||
isOpen: false,
|
||||
})
|
||||
}
|
||||
size="80%"
|
||||
style={{ maxWidth: 420, border: '1px solid white' }}
|
||||
>
|
||||
<div className="nav__drawer">
|
||||
<div>
|
||||
<div className="nav__drawer-section">
|
||||
<EthWallet />
|
||||
</div>
|
||||
<div className="nav__drawer-section">
|
||||
<VegaWallet />
|
||||
</div>
|
||||
</div>
|
||||
<NavLinks inverted={false} isDesktop={false} />
|
||||
</div>
|
||||
</Drawer>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const NavLinks = ({
|
||||
isDesktop,
|
||||
inverted,
|
||||
}: {
|
||||
isDesktop: boolean;
|
||||
inverted: boolean;
|
||||
}) => {
|
||||
const { appDispatch } = useAppState();
|
||||
const { t } = useTranslation();
|
||||
const linkProps = {
|
||||
onClick: () =>
|
||||
appDispatch({ type: AppStateActionType.SET_DRAWER, isOpen: false }),
|
||||
};
|
||||
return (
|
||||
<nav
|
||||
className={`nav-links nav-links--${isDesktop ? 'row' : 'column'}
|
||||
${inverted ? 'nav-links--inverted' : ''}`}
|
||||
>
|
||||
<NavLink {...linkProps} to={Routes.HOME}>
|
||||
{t('Home')}
|
||||
</NavLink>
|
||||
<NavLink {...linkProps} to={Routes.VESTING}>
|
||||
{t('Vesting')}
|
||||
</NavLink>
|
||||
<NavLink {...linkProps} to={Routes.STAKING}>
|
||||
{t('Staking')}
|
||||
</NavLink>
|
||||
<NavLink {...linkProps} to={Routes.REWARDS}>
|
||||
{t('Rewards')}
|
||||
</NavLink>
|
||||
<NavLink {...linkProps} to={Routes.WITHDRAW}>
|
||||
{t('Withdraw')}
|
||||
</NavLink>
|
||||
<NavLink {...linkProps} to={Routes.GOVERNANCE}>
|
||||
{t('Governance')}
|
||||
</NavLink>
|
||||
</nav>
|
||||
);
|
||||
};
|
@ -0,0 +1,53 @@
|
||||
@import "../../styles/colors";
|
||||
|
||||
.template-sidebar {
|
||||
border-bottom: 1px solid $white;
|
||||
|
||||
@media (min-width: 960px) {
|
||||
display: grid;
|
||||
grid-template-rows: auto minmax(600px, 1fr);
|
||||
grid-template-columns: 1fr 450px;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
aside {
|
||||
padding: 20px;
|
||||
background-image: url("../../images/banner.png");
|
||||
background-size: 100% auto;
|
||||
}
|
||||
|
||||
aside {
|
||||
border-left: 1px solid white;
|
||||
}
|
||||
|
||||
header {
|
||||
grid-column: 1 / 3;
|
||||
grid-row: 1 / 1;
|
||||
}
|
||||
|
||||
main {
|
||||
grid-column: 1 / 2;
|
||||
grid-row: 2 / 3;
|
||||
}
|
||||
|
||||
aside {
|
||||
display: none;
|
||||
|
||||
@media (min-width: 960px) {
|
||||
display: block;
|
||||
grid-column: 2 / 3;
|
||||
grid-row: 1 / 3;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-bottom: 20px;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import "./template-sidebar.scss";
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { Nav } from "../nav";
|
||||
|
||||
export interface TemplateSidebarProps {
|
||||
children: React.ReactNode;
|
||||
sidebar: React.ReactNode[];
|
||||
}
|
||||
|
||||
export function TemplateSidebar({ children, sidebar }: TemplateSidebarProps) {
|
||||
return (
|
||||
<div className="template-sidebar">
|
||||
<Nav />
|
||||
<main>{children}</main>
|
||||
<aside>
|
||||
{sidebar.map((Component, i) => (
|
||||
<section key={i}>{Component}</section>
|
||||
))}
|
||||
</aside>
|
||||
</div>
|
||||
);
|
||||
}
|
1
apps/token/src/components/splash-error/index.ts
Normal file
1
apps/token/src/components/splash-error/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./splash-error";
|
23
apps/token/src/components/splash-error/splash-error.scss
Normal file
23
apps/token/src/components/splash-error/splash-error.scss
Normal file
@ -0,0 +1,23 @@
|
||||
@import "../../styles/colors";
|
||||
|
||||
.splash-error__icon {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
margin-bottom: 20px;
|
||||
|
||||
span {
|
||||
display: block;
|
||||
width: 10px;
|
||||
background: $white;
|
||||
|
||||
&:first-child {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
height: 10px;
|
||||
}
|
||||
}
|
||||
}
|
16
apps/token/src/components/splash-error/splash-error.tsx
Normal file
16
apps/token/src/components/splash-error/splash-error.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import "./splash-error.scss";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
export const SplashError = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<div className="splash-error__icon">
|
||||
<span />
|
||||
<span />
|
||||
</div>
|
||||
{t("networkDown")}
|
||||
</div>
|
||||
);
|
||||
};
|
1
apps/token/src/components/splash-loader/index.ts
Normal file
1
apps/token/src/components/splash-loader/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./splash-loader";
|
22
apps/token/src/components/splash-loader/splash-loader.scss
Normal file
22
apps/token/src/components/splash-loader/splash-loader.scss
Normal file
@ -0,0 +1,22 @@
|
||||
@import "../../styles/colors";
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
&__animation {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
div {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: white;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
32
apps/token/src/components/splash-loader/splash-loader.tsx
Normal file
32
apps/token/src/components/splash-loader/splash-loader.tsx
Normal file
@ -0,0 +1,32 @@
|
||||
import "./splash-loader.scss";
|
||||
|
||||
import React from "react";
|
||||
|
||||
export const SplashLoader = ({ text = "Loading" }: { text?: string }) => {
|
||||
const [, forceRender] = React.useState(false);
|
||||
React.useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
forceRender((x) => !x);
|
||||
}, 100);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="loading" data-testid="splash-loader">
|
||||
<div className="loading__animation">
|
||||
{new Array(25).fill(null).map((_, i) => {
|
||||
return (
|
||||
<div
|
||||
key={i}
|
||||
style={{
|
||||
opacity: Math.random() > 0.75 ? 1 : 0,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
<div>{text}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
39
apps/token/src/components/staking-method-radio/index.tsx
Normal file
39
apps/token/src/components/staking-method-radio/index.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { Radio, RadioGroup } from '@blueprintjs/core';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export enum StakingMethod {
|
||||
Contract = 'Contract',
|
||||
Wallet = 'Wallet',
|
||||
}
|
||||
|
||||
export const StakingMethodRadio = ({
|
||||
setSelectedStakingMethod,
|
||||
selectedStakingMethod,
|
||||
}: {
|
||||
selectedStakingMethod: string;
|
||||
setSelectedStakingMethod: React.Dispatch<any>;
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<RadioGroup
|
||||
inline={true}
|
||||
onChange={(e) => {
|
||||
// @ts-ignore can't recognise .value
|
||||
setSelectedStakingMethod(e.target.value);
|
||||
}}
|
||||
selectedValue={selectedStakingMethod}
|
||||
>
|
||||
<Radio
|
||||
data-testid="associate-radio-contract"
|
||||
label={t('Vesting contract')}
|
||||
value={StakingMethod.Contract}
|
||||
/>
|
||||
<Radio
|
||||
data-testid="associate-radio-wallet"
|
||||
label={t('Wallet')}
|
||||
value={StakingMethod.Wallet}
|
||||
/>
|
||||
</RadioGroup>
|
||||
);
|
||||
};
|
1
apps/token/src/components/stateful-button/index.ts
Normal file
1
apps/token/src/components/stateful-button/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from "./stateful-button";
|
@ -0,0 +1,12 @@
|
||||
@import "../../styles/colors";
|
||||
|
||||
.stateful-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
import './stateful-button.scss';
|
||||
|
||||
import type { ButtonHTMLAttributes } from 'react';
|
||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
||||
|
||||
export const StatefulButton = (
|
||||
props: ButtonHTMLAttributes<HTMLButtonElement>
|
||||
) => {
|
||||
const classProp = props.className || '';
|
||||
return <Button {...props} className={`stateful-button fill ${classProp}`} />;
|
||||
};
|
1
apps/token/src/components/syntax-highlighter/index.tsx
Normal file
1
apps/token/src/components/syntax-highlighter/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export { SyntaxHighlighter } from "./syntax-highlighter";
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user