diff --git a/.github/README.md b/.github/README.md index 48f154588c..eb00f95835 100644 --- a/.github/README.md +++ b/.github/README.md @@ -1,15 +1,4 @@ -# Monorepo for the Centrifuge applications. - -## Setup - -Make sure you have installed Yarn and NVM. - -1. Use Node v14.15.1: `nvm use` -2. Install dependencies: `yarn install` -3. Install `husky`: `yarn postinstall` -4. Add `.env` files with the right environment variables to each project. - -It's also recommended to run Prettier automatically in your editor, e.g. using [this VS Code plugin](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode). +# Monorepo for the Centrifuge applications ## Preparing Envs (e.g when the dev chain data is reset) @@ -24,9 +13,10 @@ It's also recommended to run Prettier automatically in your editor, e.g. using [ Setup pure proxy to sign transactions (whitelisting & transfer tokens). -1. Run `/initProxies` to create the pure proxy, fund it, and give it sufficient permissions -2. Copy the resulting pure proxy address and add it to the env varibles: `MEMBERLIST_ADMIN_PURE_PROXY` (onboarding-api) and `REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY` (centrifuge-app) -3. Enable onboarding for each new pool under /issuer//investors +1. Use sudo in polkadot UI to give Alice enough currency to distribute (tokens.setBalance()). For currencyId select ForeignAsset and submit the transacton once with ForeignAsset 1 and once with ForeignAsset 2 +2. Run `/initProxies` to create the pure proxy, fund it, and give it sufficient permissions +3. Copy the resulting pure proxy address and add it to the env varibles: `MEMBERLIST_ADMIN_PURE_PROXY` (onboarding-api) and `REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY` (centrifuge-app) +4. Enable onboarding for each new pool under /issuer//investors ### Asset Originator POD Access diff --git a/.github/actions/prepare-deploy/action.yml b/.github/actions/prepare-deploy/action.yml index 5d07477106..490b0bb75b 100644 --- a/.github/actions/prepare-deploy/action.yml +++ b/.github/actions/prepare-deploy/action.yml @@ -70,6 +70,11 @@ runs: echo "function_name=${{ inputs.app_base_name }}-demo" >> $GITHUB_OUTPUT echo "front_url=${{ inputs.app_base_name }}-demo.k-f.dev" >> $GITHUB_OUTPUT echo "env_name=demo" >> $GITHUB_OUTPUT + elif ${{ contains(inputs.deploy_to, 'moonbeam-alpha') }}; then + # moonbeam-alpha + echo "function_name=${{ inputs.app_base_name }}-moonbeam-alpha" >> $GITHUB_OUTPUT + echo "front_url=${{ inputs.app_base_name }}-moonbeam-alpha.k-f.dev" >> $GITHUB_OUTPUT + echo "env_name=moonbeam-alpha" >> $GITHUB_OUTPUT elif ${{ github.ref == 'refs/heads/main' }}; then # DEV echo "function_name=${{ inputs.app_base_name }}-dev" >> $GITHUB_OUTPUT diff --git a/.github/workflows/moonbeam-alpha-deploy.yml b/.github/workflows/moonbeam-alpha-deploy.yml new file mode 100644 index 0000000000..16f0014455 --- /dev/null +++ b/.github/workflows/moonbeam-alpha-deploy.yml @@ -0,0 +1,34 @@ +name: "Moonbeam dev (alpha) deployments (manual)" +on: + push: + branches: main + pull_request: + paths: + - '.github/workflows/moonbeam-alpha-deploy.yml' + +jobs: + app-moonbeam-alpha: + uses: ./.github/workflows/centrifuge-app.yml + secrets: inherit + with: + deploy_env: moonbeam-alpha + + + pinning-moonbeam-alpha: + uses: ./.github/workflows/pinning-api.yml + secrets: inherit + with: + deploy_env: moonbeam-alpha + + + onboarding-moonbeam-alpha: + uses: ./.github/workflows/onboarding-api.yml + secrets: inherit + with: + deploy_env: moonbeam-alpha + + # faucet-moonbeam-alpha: + # uses: ./.github/workflows/faucet-api.yml + # secrets: inherit + # with: + # deploy_env: moonbeam-alpha \ No newline at end of file diff --git a/.gitignore b/.gitignore index 58ee203ebb..89a8e425fa 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,4 @@ yarn-error.log !.env.demo !.env.catalyst !.env.production +!.env.moonbeam-alpha diff --git a/centrifuge-app/.env-config/.env.altair b/centrifuge-app/.env-config/.env.altair index a45415d108..aa97b424d6 100644 --- a/centrifuge-app/.env-config/.env.altair +++ b/centrifuge-app/.env-config/.env.altair @@ -2,7 +2,7 @@ REACT_APP_COLLATOR_WSS_URL=wss://fullnode.altair.centrifuge.io REACT_APP_DEFAULT_NODE_URL= REACT_APP_DEFAULT_UNLIST_POOLS=false REACT_APP_FAUCET_URL='' -REACT_APP_IPFS_GATEWAY=https://altair.mypinata.cloud/ +REACT_APP_IPFS_GATEWAY=https://centrifuge.mypinata.cloud/ REACT_APP_IS_DEMO=false REACT_APP_NETWORK=altair REACT_APP_ONBOARDING_API_URL=https://europe-central2-centrifuge-production-x.cloudfunctions.net/onboarding-api-altair @@ -15,5 +15,6 @@ REACT_APP_TINLAKE_NETWORK=goerli REACT_APP_INFURA_KEY=bf808e7d3d924fbeb74672d9341d0550 REACT_APP_WHITELISTED_ACCOUNTS= REACT_APP_REWARDS_TREE_URL=https://storage.googleapis.com/rad-rewards-trees-kovan-staging/latest.json -REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=kALJqPUHFzDR2VkoQYWefPQyzjGzKznNny2smXGQpSf3aMw19 +REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a +REACT_APP_TINLAKE_SUBGRAPH_URL=https://graph.centrifuge.io/tinlake diff --git a/centrifuge-app/.env-config/.env.catalyst b/centrifuge-app/.env-config/.env.catalyst index 904670ff09..ef208e5934 100644 --- a/centrifuge-app/.env-config/.env.catalyst +++ b/centrifuge-app/.env-config/.env.catalyst @@ -2,7 +2,7 @@ REACT_APP_COLLATOR_WSS_URL=wss://fullnode.catalyst.cntrfg.com REACT_APP_DEFAULT_NODE_URL= REACT_APP_DEFAULT_UNLIST_POOLS=true REACT_APP_FAUCET_URL= -REACT_APP_IPFS_GATEWAY=https://altair.mypinata.cloud/ +REACT_APP_IPFS_GATEWAY=https://centrifuge.mypinata.cloud/ REACT_APP_IS_DEMO=false REACT_APP_NETWORK=centrifuge REACT_APP_ONBOARDING_API_URL=https://europe-central2-peak-vista-185616.cloudfunctions.net/onboarding-api-catalyst @@ -17,3 +17,4 @@ REACT_APP_WHITELISTED_ACCOUNTS= REACT_APP_REWARDS_TREE_URL=https://storage.googleapis.com/rad-rewards-trees-kovan-staging/latest.json REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=4bo2vNkwZtr2PuqppWwqya6dPC8MzxqZ4kgnAoTZyKo9Kxq8 REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a +REACT_APP_TINLAKE_SUBGRAPH_URL=https://graph.centrifuge.io/tinlake diff --git a/centrifuge-app/.env-config/.env.demo b/centrifuge-app/.env-config/.env.demo index 2df090c581..b655634f8a 100644 --- a/centrifuge-app/.env-config/.env.demo +++ b/centrifuge-app/.env-config/.env.demo @@ -2,7 +2,7 @@ REACT_APP_COLLATOR_WSS_URL=wss://fullnode.algol.cntrfg.com/public-ws REACT_APP_DEFAULT_NODE_URL=https://pod.algol.k-f.dev REACT_APP_DEFAULT_UNLIST_POOLS=true REACT_APP_FAUCET_URL=https://europe-central2-peak-vista-185616.cloudfunctions.net/faucet-api-demo -REACT_APP_IPFS_GATEWAY=https://altair.mypinata.cloud/ +REACT_APP_IPFS_GATEWAY=https://centrifuge.mypinata.cloud/ REACT_APP_IS_DEMO=true REACT_APP_ONBOARDING_API_URL=https://europe-central2-peak-vista-185616.cloudfunctions.net/onboarding-api-demo REACT_APP_PINNING_API_URL=https://europe-central2-peak-vista-185616.cloudfunctions.net/pinning-api-demo @@ -17,3 +17,4 @@ REACT_APP_NETWORK=centrifuge REACT_APP_REWARDS_TREE_URL=https://storage.googleapis.com/rad-rewards-trees-kovan-staging/latest.json REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=kALwmJutBq95s41U9fWnoApCUgvPqPGTh1GSmFnQh5f9fWo93 REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a +REACT_APP_TINLAKE_SUBGRAPH_URL=https://graph.centrifuge.io/tinlake diff --git a/centrifuge-app/.env-config/.env.development b/centrifuge-app/.env-config/.env.development index 83a78f8a75..504841d09b 100644 --- a/centrifuge-app/.env-config/.env.development +++ b/centrifuge-app/.env-config/.env.development @@ -2,7 +2,7 @@ REACT_APP_COLLATOR_WSS_URL=wss://fullnode.development.cntrfg.com REACT_APP_DEFAULT_NODE_URL=https://pod-development.k-f.dev REACT_APP_DEFAULT_UNLIST_POOLS=false REACT_APP_FAUCET_URL=https://europe-central2-peak-vista-185616.cloudfunctions.net/faucet-api-dev -REACT_APP_IPFS_GATEWAY=https://altair.mypinata.cloud/ +REACT_APP_IPFS_GATEWAY=https://centrifuge.mypinata.cloud/ REACT_APP_IS_DEMO=false REACT_APP_NETWORK=centrifuge REACT_APP_ONBOARDING_API_URL=https://europe-central2-peak-vista-185616.cloudfunctions.net/onboarding-api-dev @@ -14,6 +14,7 @@ REACT_APP_SUBSCAN_URL= REACT_APP_TINLAKE_NETWORK=goerli REACT_APP_INFURA_KEY=bf808e7d3d924fbeb74672d9341d0550 REACT_APP_WHITELISTED_ACCOUNTS= +REACT_APP_TINLAKE_SUBGRAPH_URL=https://graph.centrifuge.io/tinlake REACT_APP_REWARDS_TREE_URL=https://storage.googleapis.com/rad-rewards-trees-kovan-staging/latest.json REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a -REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=kAKfp33p1SHRq6d1BMtGndP7Cek6pH6oZKKUoA7wJXRUqf6FY +REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=kAJ27w29x7gHM75xajP2yXVLjVBaKmmUTxHwgRuCoAcWaoEiz diff --git a/centrifuge-app/.env-config/.env.example b/centrifuge-app/.env-config/.env.example index 2b26c1d0e5..6429fbfcc4 100644 --- a/centrifuge-app/.env-config/.env.example +++ b/centrifuge-app/.env-config/.env.example @@ -2,7 +2,7 @@ REACT_APP_COLLATOR_WSS_URL=wss://fullnode.development.cntrfg.com REACT_APP_DEFAULT_NODE_URL=https://pod.development.cntrfg.com REACT_APP_DEFAULT_UNLIST_POOLS= REACT_APP_FAUCET_URL=https://europe-central2-peak-vista-185616.cloudfunctions.net/faucetDev -REACT_APP_IPFS_GATEWAY=https://altair.mypinata.cloud/ +REACT_APP_IPFS_GATEWAY=https://centrifuge.mypinata.cloud/ REACT_APP_IS_DEMO=false REACT_APP_NETWORK=altair REACT_APP_ONBOARDING_API_URL=https://europe-central2-centrifuge-fargate-apps-dev.cloudfunctions.net/onboarding @@ -17,3 +17,4 @@ REACT_APP_WHITELISTED_ACCOUNTS='' REACT_APP_REWARDS_TREE_URL=https://storage.googleapis.com/rad-rewards-trees-kovan-staging/latest.json REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=kALJqPUHFzDR2VkoQYWefPQyzjGzKznNny2smXGQpSf3aMw19 REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a +REACT_APP_TINLAKE_SUBGRAPH_URL=https://graph.centrifuge.io/tinlake diff --git a/centrifuge-app/.env-config/.env.moonbeam-alpha b/centrifuge-app/.env-config/.env.moonbeam-alpha new file mode 100644 index 0000000000..2c9953161d --- /dev/null +++ b/centrifuge-app/.env-config/.env.moonbeam-alpha @@ -0,0 +1,20 @@ +REACT_APP_COLLATOR_WSS_URL=wss://fullnode.moonbase-dev.cntrfg.com/public-ws +REACT_APP_DEFAULT_NODE_URL=https://pod.moonbeam-alpha.k-f.dev +REACT_APP_DEFAULT_UNLIST_POOLS=true +REACT_APP_FAUCET_URL=https://europe-central2-peak-vista-185616.cloudfunctions.net/faucet-api-moonbean-alpha +REACT_APP_IPFS_GATEWAY=https://centrifuge.mypinata.cloud/ +REACT_APP_IS_DEMO=true +REACT_APP_ONBOARDING_API_URL=https://europe-central2-peak-vista-185616.cloudfunctions.net/onboarding-api-moonbean-alpha +REACT_APP_PINNING_API_URL=https://europe-central2-peak-vista-185616.cloudfunctions.net/pinning-api-moonbean-alpha +REACT_APP_POOL_CREATION_TYPE=immediate +REACT_APP_RELAY_WSS_URL=wss://frag-moonbase-relay-rpc-ws.g.moonbase.moonbeam.network +REACT_APP_SUBQUERY_URL=https://api.subquery.network/sq/centrifuge/pools-demo +REACT_APP_SUBSCAN_URL= +REACT_APP_TINLAKE_NETWORK=goerli +REACT_APP_INFURA_KEY=bf808e7d3d924fbeb74672d9341d0550 +REACT_APP_WHITELISTED_ACCOUNTS= +REACT_APP_NETWORK=centrifuge +REACT_APP_REWARDS_TREE_URL=https://storage.googleapis.com/rad-rewards-trees-kovan-staging/latest.json +REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=kALwmJutBq95s41U9fWnoApCUgvPqPGTh1GSmFnQh5f9fWo93 +REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a +REACT_APP_TINLAKE_SUBGRAPH_URL=https://graph.centrifuge.io/tinlake diff --git a/centrifuge-app/.env-config/.env.production b/centrifuge-app/.env-config/.env.production index b127592e39..a9c9ccc116 100644 --- a/centrifuge-app/.env-config/.env.production +++ b/centrifuge-app/.env-config/.env.production @@ -17,3 +17,4 @@ REACT_APP_WHITELISTED_ACCOUNTS='' REACT_APP_REWARDS_TREE_URL=https://storage.googleapis.com/rad-rewards-trees-mainnet-production/latest.json REACT_APP_MEMBERLIST_ADMIN_PURE_PROXY=kALJqPUHFzDR2VkoQYWefPQyzjGzKznNny2smXGQpSf3aMw19 REACT_APP_WALLETCONNECT_ID=c32fa79350803519804a67fcab0b742a +REACT_APP_TINLAKE_SUBGRAPH_URL=https://graph.centrifuge.io/tinlake diff --git a/centrifuge-app/README.md b/centrifuge-app/README.md index bfd4431dab..a69c22e00a 100644 --- a/centrifuge-app/README.md +++ b/centrifuge-app/README.md @@ -2,35 +2,50 @@ ## Data and UI Architecture -- `centrifuge-js`: fetch data from the chain or subquery. -- `fabric`: all design system elements (run storybook in fabric to see everything available). +UI -## Commands +- `centrifuge-js`: library to interact with the Centrifuge chain and subquery +- `fabric`: design system elements and components +- `centrifuge-react`: reusable React component and hooks (wallets, queries, transactions) -#### `yarn start` +Cloud functions -Running `yarn start` will start the following processes: -Start a development server that watches the different workspace modules and the react app (using Vite) +- `onboarding-api`: KYC/KYB and investor whitelisting +- `faucet-api`: dev chain faucet +- `pinning-api`: pin documents to Pinata (IPFS) -#### `yarn start:deps` -It will start a development mode on the dependencies (`fabric` & `centrifuge-js`), to allow HMR to work when making changes +Indexing -#### `yarn build` or `yarn build --mode $ENV` or `yarn build immutable` +- [pools-subql](https://github.com/centrifuge/pools-subql): subquery to index pools and assets -Build all dependencies, functions, and app with libraries. +## Development -## Other useful information +### Prerequisites + +- node v16 +- yarn -This app uses [`vite`](https://vitejs.dev/guide/) but serve, build and bundle. +### Setup -To reference env variables in code please use the vite standard `import.meta.env.ENV_VARIABLE`. +1. copy [.env.development](./.env-config/env.development) to `.env.development.local` +2. Install modules: + ```bash + $ yarn + ``` +3. Start the development server: + ```bash + $ yarn start + ``` +4. Open [http://localhost:3000](http://localhost:3000) in your browser + +## Other useful information -Check the Vite configuration file to find where we keep env file. Vite automatically grabs the right file when building with the `--mode` flag. [More info here](https://vitejs.dev/guide/env-and-mode.html) +This app uses [`vite`](https://vitejs.dev/guide/) to serve, build and bundle. -> in Netlify functions you still need to reference env variables with `process.env` +To reference env variables in code please use the viste standard `import.meta.env.ENV_VARIABLE`. ## Deployments -Up-to-date info in k-f's Knowledge Base: +Up-to-date info in k-f's Knowledge Base: -https://centrifuge.hackmd.io/MFsnRldyQSa4cadx11OtVg?view#Environments-amp-Deployments \ No newline at end of file +https://centrifuge.hackmd.io/MFsnRldyQSa4cadx11OtVg?view#Environments-amp-Deployments diff --git a/centrifuge-app/src/components/CardTotalValueLocked.tsx b/centrifuge-app/src/components/CardTotalValueLocked.tsx new file mode 100644 index 0000000000..c794c62fba --- /dev/null +++ b/centrifuge-app/src/components/CardTotalValueLocked.tsx @@ -0,0 +1,92 @@ +import { Box, Stack, Text, TextWithPlaceholder, Tooltip } from '@centrifuge/fabric' +import * as React from 'react' +import { useTheme } from 'styled-components' +import { config } from '../config' +import { formatDate } from '../utils/date' +import { Dec } from '../utils/Decimal' +import { formatBalance } from '../utils/formatting' +import { useListedPools } from '../utils/useListedPools' +import { DataPoint, TotalValueLocked } from './Charts/TotalValueLocked' +import { tooltipText } from './Tooltips' + +export function CardTotalValueLocked() { + const { colors } = useTheme() + const [hovered, setHovered] = React.useState(undefined) + const [, listedTokens] = useListedPools() + + const chartHeight = 100 + const balanceProps = { + as: 'strong', + fontSize: [28, 32], + } + const headingProps = { + as: 'h2', + variant: 'heading3', + } + + const totalValueLocked = React.useMemo(() => { + return ( + listedTokens + ?.map((tranche) => ({ + valueLocked: tranche.totalIssuance + .toDecimal() + .mul(tranche.tokenPrice?.toDecimal() ?? Dec(0)) + .toNumber(), + })) + .reduce((prev, curr) => prev.add(curr.valueLocked), Dec(0)) ?? Dec(0) + ) + }, [listedTokens]) + + return ( + + + {hovered ? ( + <> + + TVL on{' '} + + + {formatBalance(Dec(hovered?.tvl || 0), config.baseCurrency)} + + ) : ( + <> + + {tooltipText.tvl.label} + + + {formatBalance(Dec(totalValueLocked || 0), config.baseCurrency)} + + + )} + + + + + + + ) +} diff --git a/centrifuge-app/src/components/Charts/TotalValueLocked.tsx b/centrifuge-app/src/components/Charts/TotalValueLocked.tsx new file mode 100644 index 0000000000..dabe29c187 --- /dev/null +++ b/centrifuge-app/src/components/Charts/TotalValueLocked.tsx @@ -0,0 +1,104 @@ +import Decimal from 'decimal.js-light' +import * as React from 'react' +import { useQuery } from 'react-query' +import { Area, AreaChart, ResponsiveContainer, Tooltip } from 'recharts' +import { getTinlakeSubgraphTVL } from '../../utils/tinlake/getTinlakeSubgraphTVL' +import { useDailyTVL } from '../../utils/usePools' + +export type DataPoint = { + dateInMilliseconds: number + tvl: Decimal +} + +type TotalValueLockedProps = { + chainTVL: Decimal + setHovered: (entry: DataPoint | undefined) => void +} + +export function TotalValueLocked({ chainTVL, setHovered }: TotalValueLockedProps) { + const centrifugeTVL = useDailyTVL() + const tinlakeTVL = useDailyTinlakeTVL() + const chartColor = '#ff8c00' + + const chartData = React.useMemo(() => { + if (!tinlakeTVL || !centrifugeTVL) { + return [] + } + + const currentTVL = chainTVL + ? { + dateInMilliseconds: new Date().setHours(0, 0, 0, 0), + tvl: chainTVL, + } + : undefined + + return getMergedData([...tinlakeTVL, ...centrifugeTVL], currentTVL) + }, [tinlakeTVL, centrifugeTVL, chainTVL]) + + return ( + + { + if (val?.activePayload && val?.activePayload.length > 0) { + setHovered(val.activePayload[0].payload) + } + }} + onMouseLeave={() => { + setHovered(undefined) + }} + > + + + + + + + + } /> + + + ) +} + +function useDailyTinlakeTVL() { + const { data } = useQuery('use daily tinlake tvl', getTinlakeSubgraphTVL, { + staleTime: Infinity, + suspense: true, + }) + + return data +} + +function getMergedData(combined: DataPoint[], current?: DataPoint) { + const mergedMap = new Map() + + combined.forEach((entry) => { + const { dateInMilliseconds, tvl } = entry + + if (mergedMap.has(dateInMilliseconds)) { + mergedMap.set(dateInMilliseconds, mergedMap.get(dateInMilliseconds).add(tvl)) + } else { + mergedMap.set(dateInMilliseconds, tvl) + } + }) + + if (current) { + mergedMap.set(current.dateInMilliseconds, current.tvl) + } + + const merged = Array.from(mergedMap, ([dateInMilliseconds, tvl]) => ({ dateInMilliseconds, tvl })) + .sort((a, b) => a.dateInMilliseconds - b.dateInMilliseconds) + .map((entry) => ({ ...entry, tvl: entry.tvl.toNumber() })) + + return merged +} diff --git a/centrifuge-app/src/components/DataTable.tsx b/centrifuge-app/src/components/DataTable.tsx index 4cbae664fc..086635ba8f 100644 --- a/centrifuge-app/src/components/DataTable.tsx +++ b/centrifuge-app/src/components/DataTable.tsx @@ -4,6 +4,7 @@ import BN from 'bn.js' import * as React from 'react' import { Link, LinkProps } from 'react-router-dom' import styled from 'styled-components' +import { useElementScrollSize } from '../utils/useElementScrollSize' type GroupedProps = { groupIndex?: number @@ -68,6 +69,8 @@ export const DataTable = >({ ) const [currentSortKey, setCurrentSortKey] = React.useState(defaultSortKey || '') + const ref = React.useRef(null) + const { scrollWidth } = useElementScrollSize(ref) const updateSortOrder = (sortKey: Column['sortKey']) => { if (!sortKey) return @@ -82,8 +85,9 @@ export const DataTable = >({ }, [orderBy, data, currentSortKey, page, pageSize]) const showHeader = groupIndex === 0 || !groupIndex + return ( - + 0 ? scrollWidth : 'auto'}> {showHeader && columns.map((col, i) => ( @@ -189,7 +193,7 @@ const DataCol = styled(Text)<{ align: Column['align'] }>` white-space: nowrap; &:first-child { - padding-right: '16px'; + padding-right: 16px; } ${({ align }) => { switch (align) { @@ -197,14 +201,14 @@ const DataCol = styled(Text)<{ align: Column['align'] }>` return css({ justifyContent: 'flex-start', '&:last-child': { - paddingRight: '16px', + paddingRight: 16, }, }) case 'center': return css({ justifyContent: 'center', '&:last-child': { - paddingRight: '16px', + paddingRight: 16, }, }) case 'right': @@ -214,7 +218,7 @@ const DataCol = styled(Text)<{ align: Column['align'] }>` justifyContent: 'flex-end', '&:last-child': { - paddingRight: '16px', + paddingRight: 16, }, }) } diff --git a/centrifuge-app/src/components/DataTableGroup.tsx b/centrifuge-app/src/components/DataTableGroup.tsx index 90bcc8d8dd..18954db01d 100644 --- a/centrifuge-app/src/components/DataTableGroup.tsx +++ b/centrifuge-app/src/components/DataTableGroup.tsx @@ -2,9 +2,15 @@ import { Card, Stack } from '@centrifuge/fabric' import * as React from 'react' import { DataTableProps } from './DataTable' -export function DataTableGroup({ children }: { children: React.ReactElement[] }) { +export function DataTableGroup({ + children, + rounded = true, +}: { + children: React.ReactElement[] + rounded?: boolean +}) { return ( - + {React.Children.map(children, (child, index) => { return React.isValidElement(child) ? React.cloneElement(child, { diff --git a/centrifuge-app/src/components/DebugFlags/config.ts b/centrifuge-app/src/components/DebugFlags/config.ts index 99d41dc24f..714a67508f 100644 --- a/centrifuge-app/src/components/DebugFlags/config.ts +++ b/centrifuge-app/src/components/DebugFlags/config.ts @@ -1,4 +1,5 @@ import React from 'react' +import { config } from '../../config' import { ConvertEvmAddress } from './components/ConvertEvmAddress' const params = new URLSearchParams(typeof window !== 'undefined' ? window.location.search : {}) @@ -34,7 +35,7 @@ export type Key = | 'evmAddress' | 'batchMintNFTs' | 'persistDebugFlags' - | 'showAvalanche' + | 'showBase' | 'showUnusedFlags' | 'allowInvestBelowMin' | 'alternativeTheme' @@ -45,6 +46,8 @@ export type Key = | 'editAdminConfig' | 'showPodAccountCreation' | 'convertEvmAddress' + | 'showPortfolio' + | 'poolCreationType' export const flagsConfig: Record = { address: { @@ -68,7 +71,7 @@ export const flagsConfig: Record = { default: false, alwaysShow: true, }, - showAvalanche: { + showBase: { type: 'checkbox', default: false, alwaysShow: true, @@ -113,4 +116,18 @@ export const flagsConfig: Record = { default: null, alwaysShow: true, }, + showPortfolio: { + type: 'checkbox', + default: false, + alwaysShow: true, + }, + poolCreationType: { + type: 'select', + default: config.poolCreationType || 'immediate', + options: { + immediate: 'immediate', + propose: 'propose', + notePreimage: 'notePreimage', + }, + }, } diff --git a/centrifuge-app/src/components/Dialogs/ConfirmResendEmailVerificationDialog.tsx b/centrifuge-app/src/components/Dialogs/ConfirmResendEmailVerificationDialog.tsx index 58b9ac0cdc..0ca0eb4437 100644 --- a/centrifuge-app/src/components/Dialogs/ConfirmResendEmailVerificationDialog.tsx +++ b/centrifuge-app/src/components/Dialogs/ConfirmResendEmailVerificationDialog.tsx @@ -4,22 +4,23 @@ import { useSendVerifyEmail } from '../../pages/Onboarding/queries/useSendVerify type Props = { isDialogOpen: boolean setIsDialogOpen: (isDialogOpen: boolean) => void + currentEmail: string } -export const ConfirmResendEmailVerificationDialog = ({ isDialogOpen, setIsDialogOpen }: Props) => { +export const ConfirmResendEmailVerificationDialog = ({ isDialogOpen, setIsDialogOpen, currentEmail }: Props) => { const { mutate: sendVerifyEmail, isLoading } = useSendVerifyEmail() return ( setIsDialogOpen(false)} - title={Send Confirmation Email} + title={Send Confirmation Email} > - - - Are you sure you want to resend a confirmation email? - + + + Are you sure you want to resend a confirmation email to {currentEmail}? + diff --git a/centrifuge-app/src/components/Dialogs/EditOnboardingEmailAddressDialog.tsx b/centrifuge-app/src/components/Dialogs/EditOnboardingEmailAddressDialog.tsx index cb78df4cc0..3f39b9c3a5 100644 --- a/centrifuge-app/src/components/Dialogs/EditOnboardingEmailAddressDialog.tsx +++ b/centrifuge-app/src/components/Dialogs/EditOnboardingEmailAddressDialog.tsx @@ -1,4 +1,4 @@ -import { Box, Button, Dialog, Shelf, Stack, Text, TextInput } from '@centrifuge/fabric' +import { Button, Dialog, Shelf, Stack, Text, TextInput } from '@centrifuge/fabric' import * as React from 'react' import { useMutation } from 'react-query' import { string } from 'yup' @@ -49,27 +49,25 @@ export const EditOnboardingEmailAddressDialog = ({ isDialogOpen, setIsDialogOpen width="30%" isOpen={isLoading ? true : isDialogOpen} onClose={() => setIsDialogOpen(false)} - title={Edit Email Address} + title={Edit Email Address} > - - - - setNewEmail(event.target.value)} /> - - - - - - + + + setNewEmail(event.target.value)} /> + + + + + ) } diff --git a/centrifuge-app/src/components/GlobalStyle.tsx b/centrifuge-app/src/components/GlobalStyle.tsx index ebf029e575..18f1aa1aa0 100644 --- a/centrifuge-app/src/components/GlobalStyle.tsx +++ b/centrifuge-app/src/components/GlobalStyle.tsx @@ -27,4 +27,14 @@ export const GlobalStyle = createGlobalStyle` ul { list-style: none; } + + .visually-hidden { + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; + } ` diff --git a/centrifuge-app/src/components/InvestRedeem/InvestRedeem.tsx b/centrifuge-app/src/components/InvestRedeem/InvestRedeem.tsx index ebfbc91ad3..c69c072b6d 100644 --- a/centrifuge-app/src/components/InvestRedeem/InvestRedeem.tsx +++ b/centrifuge-app/src/components/InvestRedeem/InvestRedeem.tsx @@ -26,7 +26,7 @@ import css from '@styled-system/css' import Decimal from 'decimal.js-light' import { Field, FieldProps, Form, FormikErrors, FormikProvider, useFormik } from 'formik' import * as React from 'react' -import { useHistory } from 'react-router-dom' +import { useHistory, useParams } from 'react-router-dom' import styled from 'styled-components' import { Dec } from '../../utils/Decimal' import { formatBalance, roundDown } from '../../utils/formatting' @@ -310,19 +310,19 @@ function InvestRedeemInner({ view, setView, setTrancheId, networks }: InnerProps return null } -const OnboardingButton = ({ networks }: { networks: Network[] | undefined }) => { +const OnboardingButton = ({ networks }: { networks: Network[] | undefined; trancheId?: string }) => { const { showWallets, showNetworks, connectedType } = useWallet() const { state } = useInvestRedeem() - const pool = usePool(state.poolId) + const { pid: poolId } = useParams<{ pid: string }>() + const pool = usePool(poolId) const { data: metadata } = usePoolMetadata(pool) const isTinlakePool = pool.id.startsWith('0x') + const history = useHistory() const trancheName = state.trancheId.split('-')[1] === '0' ? 'junior' : 'senior' - const centPoolInvestStatus = metadata?.onboarding?.tranches?.[state.trancheId].openForOnboarding ? 'open' : 'closed' + const centPoolInvestStatus = metadata?.onboarding?.tranches?.[state?.trancheId]?.openForOnboarding ? 'open' : 'closed' const investStatus = isTinlakePool ? metadata?.pool?.newInvestmentsStatus?.[trancheName] : centPoolInvestStatus - const history = useHistory() - const getOnboardingButtonText = () => { if (connectedType) { if (investStatus === 'request') { @@ -534,7 +534,11 @@ function InvestForm({ onCancel, hasInvestment, autoFocus, investLabel = 'Invest' ) : changeOrderFormShown ? ( - renderInput(() => setChangeOrderFormShown(false)) + state.needsPoolCurrencyApproval ? ( + renderInput(onCancel, { onClick: actions.approvePoolCurrency, loading: isApproving }) + ) : ( + renderInput(onCancel) + ) ) : hasPendingOrder ? ( ) : changeOrderFormShown ? ( - renderInput(() => setChangeOrderFormShown(false)) + state.needsTrancheTokenApproval ? ( + renderInput(onCancel, { onClick: actions.approveTrancheToken, loading: isApproving }) + ) : ( + renderInput(onCancel) + ) ) : hasPendingOrder ? ( t.id === trancheId) const { data: metadata, isLoading: isMetadataLoading } = usePoolMetadata(pool) const trancheMeta = metadata?.tranches?.[trancheId] - const { - state: { combinedStakes }, - } = useLiquidityRewards() + const { state: liquidityState } = useLiquidityRewards() if (!tranche) throw new Error(`Token not found. Pool id: ${poolId}, token id: ${trancheId}`) @@ -35,7 +33,7 @@ export function InvestRedeemCentrifugeProvider({ poolId, trancheId, children }: const price = tranche.tokenPrice?.toDecimal() ?? Dec(1) const investToCollect = order?.payoutTokenAmount.toDecimal() ?? Dec(0) const pendingRedeem = order?.remainingRedeemToken.toDecimal() ?? Dec(0) - const stakedAmount = combinedStakes ?? Dec(0) + const stakedAmount = liquidityState?.combinedStakes ?? Dec(0) const combinedBalance = trancheBalance.add(investToCollect).add(pendingRedeem).add(stakedAmount) const investmentValue = combinedBalance.mul(price) const poolCurBalance = diff --git a/centrifuge-app/src/components/InvestRedeem/InvestRedeemProvider.tsx b/centrifuge-app/src/components/InvestRedeem/InvestRedeemProvider.tsx index 0d3f293db0..1db477855f 100644 --- a/centrifuge-app/src/components/InvestRedeem/InvestRedeemProvider.tsx +++ b/centrifuge-app/src/components/InvestRedeem/InvestRedeemProvider.tsx @@ -13,6 +13,7 @@ export function useInvestRedeem() { export function InvestRedeemProvider(props: Props) { const isTinlakePool = props.poolId.startsWith('0x') + const Comp = isTinlakePool ? InvestRedeemTinlakeProvider : InvestRedeemCentrifugeProvider return diff --git a/centrifuge-app/src/components/LayoutBase/BaseSection.tsx b/centrifuge-app/src/components/LayoutBase/BaseSection.tsx new file mode 100644 index 0000000000..ab523309f7 --- /dev/null +++ b/centrifuge-app/src/components/LayoutBase/BaseSection.tsx @@ -0,0 +1,15 @@ +import { Box, BoxProps } from '@centrifuge/fabric' +import * as React from 'react' +import { config } from './config' + +type BaseSectionProps = BoxProps & { + children: React.ReactNode +} + +export function BaseSection({ children, ...boxProps }: BaseSectionProps) { + return ( + + {children} + + ) +} diff --git a/centrifuge-app/src/components/LayoutBase/config.ts b/centrifuge-app/src/components/LayoutBase/config.ts new file mode 100644 index 0000000000..dcb172ee3c --- /dev/null +++ b/centrifuge-app/src/components/LayoutBase/config.ts @@ -0,0 +1,9 @@ +export const config = { + HEADER_HEIGHT: 60, + TOOLBAR_HEIGHT: 50, + LAYOUT_MAX_WIDTH: 1800, + SIDEBAR_WIDTH: 80, + SIDEBAR_WIDTH_EXTENDED: 220, + PADDING_MAIN: [2, 2, 3, 3, 5], + WALLET_WIDTH: [200, 264], +} diff --git a/centrifuge-app/src/components/LayoutBase/index.tsx b/centrifuge-app/src/components/LayoutBase/index.tsx new file mode 100644 index 0000000000..03ca4abf07 --- /dev/null +++ b/centrifuge-app/src/components/LayoutBase/index.tsx @@ -0,0 +1,61 @@ +import { WalletMenu } from '@centrifuge/centrifuge-react' +import * as React from 'react' +import { Footer } from '../Footer' +import { LoadBoundary } from '../LoadBoundary' +import { LogoLink } from '../LogoLink' +import { Menu } from '../Menu' +import { OnboardingStatus } from '../OnboardingStatus' +import { SideDrawerProps } from '../SideDrawer' +import { config } from './config' +import { + FooterContainer, + HeaderBackground, + Inner, + LogoContainer, + MainContainer, + Root, + ToolbarContainer, + WalletContainer, + WalletInner, + WalletPositioner, +} from './styles' + +type LayoutBaseProps = { + children?: React.ReactNode + sideDrawer?: React.ReactElement +} + +export function LayoutBase({ children, sideDrawer }: LayoutBaseProps) { + return ( + + + + + + + + + + + + ]} /> + + + + + + + + + + {children} + + + +