Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0603018f09 | |||
| 677b6de0f8 | |||
| 27f965ef63 | |||
| e72347d87a | |||
| 0260df61de | |||
| e986d7ca22 | |||
| df925b013b | |||
| d7867ff475 | |||
| b9c008f019 | |||
| a7bd94af82 | |||
| 1be5cbda6d | |||
| 4c48657fca | |||
| 72488fd5a3 | |||
| 896f6e48d9 | |||
| f53e9664da | |||
| ff5b832017 | |||
| 9f0ab1323b | |||
| c9384ff23e | |||
| f8390d7eac | |||
| 408b565935 | |||
| f82444f212 | |||
| fd11f0166d | |||
| 186d0352cf | |||
| f01477ea70 | |||
| 6bfe97be5d | |||
| feeca008ac | |||
| cba21bb2e0 | |||
| 318592653c |
@@ -14,6 +14,7 @@
|
|||||||
"crypto*",
|
"crypto*",
|
||||||
"stream*",
|
"stream*",
|
||||||
"env-paths",
|
"env-paths",
|
||||||
"open"
|
"open",
|
||||||
|
"base64-inline-loader"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -20,8 +20,6 @@ jobs:
|
|||||||
REACT_APP_BEE_HOST: https://api.test-node.staging.ethswarm.org/
|
REACT_APP_BEE_HOST: https://api.test-node.staging.ethswarm.org/
|
||||||
REACT_APP_BEE_DEBUG_HOST: https://debug.test-node.staging.ethswarm.org/
|
REACT_APP_BEE_DEBUG_HOST: https://debug.test-node.staging.ethswarm.org/
|
||||||
REACT_APP_DEV_MODE: 1
|
REACT_APP_DEV_MODE: 1
|
||||||
REACT_APP_SENTRY_KEY: ${{ secrets.SENTRY_KEY }}
|
|
||||||
REACT_APP_SENTRY_ENVIRONMENT: 'preview'
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
@@ -60,9 +58,6 @@ jobs:
|
|||||||
- name: Types check
|
- name: Types check
|
||||||
run: npm run check:types
|
run: npm run check:types
|
||||||
|
|
||||||
- name: Types build
|
|
||||||
run: npm run compile:types
|
|
||||||
|
|
||||||
- name: Update supported Bee action
|
- name: Update supported Bee action
|
||||||
uses: ethersphere/update-supported-bee-action@v1
|
uses: ethersphere/update-supported-bee-action@v1
|
||||||
if: github.ref == 'refs/heads/master'
|
if: github.ref == 'refs/heads/master'
|
||||||
|
|||||||
@@ -15,16 +15,6 @@ jobs:
|
|||||||
node-version: 18
|
node-version: 18
|
||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run compile:types
|
|
||||||
- run: npm publish --access public
|
- run: npm publish --access public
|
||||||
env:
|
env:
|
||||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||||
- name: Create Sentry release
|
|
||||||
uses: getsentry/action-release@v1
|
|
||||||
env:
|
|
||||||
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
|
|
||||||
SENTRY_ORG: ${{ secrets.SENTRY_ORG }}
|
|
||||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
|
||||||
with:
|
|
||||||
sourcemaps: ./build/static/js
|
|
||||||
version: ${{ github.ref }}
|
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ jobs:
|
|||||||
registry-url: 'https://registry.npmjs.org'
|
registry-url: 'https://registry.npmjs.org'
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm run build
|
- run: npm run build
|
||||||
env:
|
|
||||||
REACT_APP_SENTRY_KEY: ${{ secrets.SENTRY_KEY }}
|
|
||||||
REACT_APP_SENTRY_ENVIRONMENT: 'pages'
|
|
||||||
- run: echo "dashboard.ethswarm.org" > ./build/CNAME
|
- run: echo "dashboard.ethswarm.org" > ./build/CNAME
|
||||||
- name: Deploy to gh-pages
|
- name: Deploy to gh-pages
|
||||||
uses: peaceiris/actions-gh-pages@v3
|
uses: peaceiris/actions-gh-pages@v3
|
||||||
|
|||||||
@@ -1,5 +1,50 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## [0.19.3](https://github.com/ethersphere/bee-dashboard/compare/v0.19.2...v0.19.3) (2022-08-24)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* pass isBeeDesktop from provider to hook ([#525](https://github.com/ethersphere/bee-dashboard/issues/525)) ([677b6de](https://github.com/ethersphere/bee-dashboard/commit/677b6de0f82b02e1487420e3c08fbd19a949f97b))
|
||||||
|
|
||||||
|
## [0.19.2](https://github.com/ethersphere/bee-dashboard/compare/v0.19.1...v0.19.2) (2022-08-08)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* remove sentry ([#520](https://github.com/ethersphere/bee-dashboard/issues/520)) ([0260df6](https://github.com/ethersphere/bee-dashboard/commit/0260df61de0619202a819b79820cfbef6e3757ae))
|
||||||
|
|
||||||
|
## [0.19.1](https://github.com/ethersphere/bee-dashboard/compare/v0.19.0...v0.19.1) (2022-08-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* compile types when building the library ([#516](https://github.com/ethersphere/bee-dashboard/issues/516)) ([df925b0](https://github.com/ethersphere/bee-dashboard/commit/df925b013bb02a16d308a86050ec8e0e0e361ff7))
|
||||||
|
|
||||||
|
## [0.19.0](https://github.com/ethersphere/bee-dashboard/compare/v0.18.2...v0.19.0) (2022-08-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* add loading state to wallet balance ([#508](https://github.com/ethersphere/bee-dashboard/issues/508)) ([b9c008f](https://github.com/ethersphere/bee-dashboard/commit/b9c008f019f5bfe005d11f0208e90ca21b274e97))
|
||||||
|
* add wallet endpoint and display blockchain name ([#492](https://github.com/ethersphere/bee-dashboard/issues/492)) ([fd11f01](https://github.com/ethersphere/bee-dashboard/commit/fd11f0166d77a89a2b350f16f9254af91441089d))
|
||||||
|
* check whether the app runs within bee-desktop is now an environment variable ([#490](https://github.com/ethersphere/bee-dashboard/issues/490)) ([f01477e](https://github.com/ethersphere/bee-dashboard/commit/f01477ea70e6c343461cce6c1bcee3d738c076de))
|
||||||
|
* don't display duplicate notifications with snackbar ([#488](https://github.com/ethersphere/bee-dashboard/issues/488)) ([feeca00](https://github.com/ethersphere/bee-dashboard/commit/feeca008acd523f16e7efdde2fa92ec98fde8bc9))
|
||||||
|
* log errors to console when showing error notification ([#489](https://github.com/ethersphere/bee-dashboard/issues/489)) ([6bfe97b](https://github.com/ethersphere/bee-dashboard/commit/6bfe97be5d69adeb13dafad2016d1c76a9d2e67c))
|
||||||
|
* make blockchain JSON RPC configurable from settings ([#494](https://github.com/ethersphere/bee-dashboard/issues/494)) ([408b565](https://github.com/ethersphere/bee-dashboard/commit/408b565935a59759af6d3e252ceae6981fb60eb6))
|
||||||
|
* pass isBeeDesktop as Bee Dashboard component property ([#510](https://github.com/ethersphere/bee-dashboard/issues/510)) ([4c48657](https://github.com/ethersphere/bee-dashboard/commit/4c48657fca0c22d5d4aaf220ffb278ac67001e1f))
|
||||||
|
* reintroduce new bee version notification ([#500](https://github.com/ethersphere/bee-dashboard/issues/500)) ([f53e966](https://github.com/ethersphere/bee-dashboard/commit/f53e9664da8f4d1b6803de09f45a92493957d79d))
|
||||||
|
* set default rpc endpoint ([#485](https://github.com/ethersphere/bee-dashboard/issues/485)) ([cba21bb](https://github.com/ethersphere/bee-dashboard/commit/cba21bb2e0e70e0ada01402d44e9ee61a55173cf))
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* correct website upload path ([#483](https://github.com/ethersphere/bee-dashboard/issues/483)) ([186d035](https://github.com/ethersphere/bee-dashboard/commit/186d0352cfda0408cf7add535d9247a85ce3796d))
|
||||||
|
* disable swarm invitation outside of Swarm Desktop ([#497](https://github.com/ethersphere/bee-dashboard/issues/497)) ([f8390d7](https://github.com/ethersphere/bee-dashboard/commit/f8390d7eacc082a7b0a4551a3bc1572e3ce3463e))
|
||||||
|
* handle unicode filename and website uploads ([#491](https://github.com/ethersphere/bee-dashboard/issues/491)) ([f82444f](https://github.com/ethersphere/bee-dashboard/commit/f82444f2124cad8bccead01a33cbc9f51d126acf))
|
||||||
|
* if the node has error, disable pages that can never load ([#502](https://github.com/ethersphere/bee-dashboard/issues/502)) ([896f6e4](https://github.com/ethersphere/bee-dashboard/commit/896f6e48d988bbc0fe82a63a44bc82b035f30073))
|
||||||
|
* new bee version notification is only shown if user bee version is detected ([#512](https://github.com/ethersphere/bee-dashboard/issues/512)) ([1be5cbd](https://github.com/ethersphere/bee-dashboard/commit/1be5cbda6de073f1e569ab92f178d815548a461b))
|
||||||
|
|
||||||
## [0.18.2](https://github.com/ethersphere/bee-dashboard/compare/v0.18.1...v0.18.2) (2022-07-06)
|
## [0.18.2](https://github.com/ethersphere/bee-dashboard/compare/v0.18.1...v0.18.2) (2022-07-06)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,16 +13,16 @@
|
|||||||
**Warning: This project is in alpha state. There might (and most probably will) be changes in the future to its API and
|
**Warning: This project is in alpha state. There might (and most probably will) be changes in the future to its API and
|
||||||
working. Also, no guarantees can be made about its stability, efficiency, and security at this stage.**
|
working. Also, no guarantees can be made about its stability, efficiency, and security at this stage.**
|
||||||
|
|
||||||
This project is intended to be used with **Bee version <!-- SUPPORTED_BEE_START -->1.6.0-6ceadd35<!-- SUPPORTED_BEE_END -->**.
|
This project is intended to be used with **Bee version <!-- SUPPORTED_BEE_START -->1.7.0-bbf13011<!-- SUPPORTED_BEE_END -->**.
|
||||||
Using it with older or newer Bee versions is not recommended and may not work. Stay up to date by joining the
|
Using it with older or newer Bee versions is not recommended and may not work. Stay up to date by joining the
|
||||||
[official Discord](https://discord.gg/GU22h2utj6) and by keeping an eye on the
|
[official Discord](https://discord.gg/GU22h2utj6) and by keeping an eye on the
|
||||||
[releases tab](https://github.com/ethersphere/bee-dashboard/releases).
|
[releases tab](https://github.com/ethersphere/bee-dashboard/releases).
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
| Node Setup | Upload Files | Download Content | Accounting | Postage Stamps |
|
| Node Setup | Upload Files | Download Content | Accounting | Settings |
|
||||||
| ------------------------------------ | -------------------------------------- | ------------------------------------------ | ----------------------------------------- | ---------------------------------------- |
|
| ------------------------------------ | -------------------------------------- | ------------------------------------------ | ----------------------------------------- | ---------------------------------------- |
|
||||||
|  |  |  |  |  |
|
|  |  |  |  |  |
|
||||||
|
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
@@ -96,12 +96,14 @@ The Bee Dashboard runs in development mode on [http://localhost:3031/](http://lo
|
|||||||
|
|
||||||
> Setting the `REACT_APP_DEV_MODE=1` environment variable, or opening Bee Dashboard with the query string `?devMode=1` loosens some checks. This makes it possible to develop Bee Dashboard without having connected peers and chequebook properly set up, effectively supporting the dev mode of Bee itself.
|
> Setting the `REACT_APP_DEV_MODE=1` environment variable, or opening Bee Dashboard with the query string `?devMode=1` loosens some checks. This makes it possible to develop Bee Dashboard without having connected peers and chequebook properly set up, effectively supporting the dev mode of Bee itself.
|
||||||
|
|
||||||
#### Bee Desktop development
|
#### Swarm Desktop development
|
||||||
|
|
||||||
If you want to develop Bee Dashboard in the Bee Desktop mode, then spin up `bee-desktop` to the point where you see Bee Dashboard (eq. install Bee etc.) and:
|
If you want to develop Bee Dashboard in the Swarm Desktop mode, then spin up `swarm-desktop` to the point where you see Bee Dashboard (eq. install Bee etc.) and:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
echo "REACT_APP_BEE_DESKTOP_URL=http://localhost:3000" > .env.development.local
|
echo "REACT_APP_BEE_DESKTOP_URL=http://localhost:3000
|
||||||
|
REACT_APP_BEE_DESKTOP_ENABLED=true" > .env.development.local
|
||||||
|
|
||||||
npm start
|
npm start
|
||||||
npm run desktop # This will inject the API key to the Dashboard
|
npm run desktop # This will inject the API key to the Dashboard
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ethersphere/bee-dashboard",
|
"name": "@ethersphere/bee-dashboard",
|
||||||
"version": "0.18.2",
|
"version": "0.19.3",
|
||||||
"description": "An app which helps users to setup their Bee node and do actions like cash out cheques",
|
"description": "An app which helps users to setup their Bee node and do actions like cash out cheques",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"bee",
|
"bee",
|
||||||
@@ -26,14 +26,12 @@
|
|||||||
"url": "https://github.com/ethersphere/bee-dashboard.git"
|
"url": "https://github.com/ethersphere/bee-dashboard.git"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ethersphere/bee-js": "^4.1.1",
|
"@ethersphere/bee-js": "^5.0.0",
|
||||||
"@ethersphere/manifest-js": "1.2.1",
|
"@ethersphere/manifest-js": "1.2.1",
|
||||||
"@ethersphere/swarm-cid": "^0.1.0",
|
"@ethersphere/swarm-cid": "^0.1.0",
|
||||||
"@material-ui/core": "4.12.3",
|
"@material-ui/core": "4.12.3",
|
||||||
"@material-ui/icons": "4.11.2",
|
"@material-ui/icons": "4.11.2",
|
||||||
"@material-ui/lab": "4.0.0-alpha.57",
|
"@material-ui/lab": "4.0.0-alpha.57",
|
||||||
"@sentry/react": "^7.1.1",
|
|
||||||
"@sentry/tracing": "^7.1.1",
|
|
||||||
"assert": "^2.0.0",
|
"assert": "^2.0.0",
|
||||||
"axios": "0.24.0",
|
"axios": "0.24.0",
|
||||||
"bignumber.js": "9.0.1",
|
"bignumber.js": "9.0.1",
|
||||||
@@ -50,9 +48,9 @@
|
|||||||
"notistack": "1.0.10",
|
"notistack": "1.0.10",
|
||||||
"opener": "1.5.2",
|
"opener": "1.5.2",
|
||||||
"qrcode.react": "1.0.1",
|
"qrcode.react": "1.0.1",
|
||||||
"react": ">= 17.0.2",
|
"react": ">=17.0.0 || >=18.0.0",
|
||||||
"react-copy-to-clipboard": "5.0.4",
|
"react-copy-to-clipboard": "5.0.4",
|
||||||
"react-dom": ">= 17.0.2",
|
"react-dom": ">=17.0.0 || >=18.0.0",
|
||||||
"react-identicons": "1.2.5",
|
"react-identicons": "1.2.5",
|
||||||
"react-router": "6.2.1",
|
"react-router": "6.2.1",
|
||||||
"react-router-dom": "6.2.1",
|
"react-router-dom": "6.2.1",
|
||||||
@@ -92,6 +90,7 @@
|
|||||||
"babel-loader": "8.1.0",
|
"babel-loader": "8.1.0",
|
||||||
"babel-plugin-syntax-dynamic-import": "6.18.0",
|
"babel-plugin-syntax-dynamic-import": "6.18.0",
|
||||||
"babel-plugin-tsconfig-paths": "1.0.2",
|
"babel-plugin-tsconfig-paths": "1.0.2",
|
||||||
|
"base64-inline-loader": "^2.0.1",
|
||||||
"cors": "^2.8.5",
|
"cors": "^2.8.5",
|
||||||
"depcheck": "^1.4.3",
|
"depcheck": "^1.4.3",
|
||||||
"env-paths": "^3.0.0",
|
"env-paths": "^3.0.0",
|
||||||
@@ -110,7 +109,9 @@
|
|||||||
"file-loader": "6.2.0",
|
"file-loader": "6.2.0",
|
||||||
"open": "^8.4.0",
|
"open": "^8.4.0",
|
||||||
"prettier": "2.4.1",
|
"prettier": "2.4.1",
|
||||||
|
"puppeteer": "^15.4.0",
|
||||||
"react-scripts": "^5.0.1",
|
"react-scripts": "^5.0.1",
|
||||||
|
"rimraf": "^3.0.2",
|
||||||
"ts-node": "^10.8.1",
|
"ts-node": "^10.8.1",
|
||||||
"typescript": "4.7.3",
|
"typescript": "4.7.3",
|
||||||
"web-vitals": "2.1.2",
|
"web-vitals": "2.1.2",
|
||||||
@@ -126,9 +127,10 @@
|
|||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"desktop": "node ./desktop.mjs",
|
"desktop": "node ./desktop.mjs",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"build:component": "webpack --mode=production",
|
"build:component": "rimraf ./lib && webpack --mode=production && npm run compile:types",
|
||||||
"compile:types": "tsc --project tsconfig.lib.json --emitDeclarationOnly --declaration",
|
"compile:types": "tsc --project tsconfig.lib.json --emitDeclarationOnly --declaration",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
|
"test:ui": "node ui-test/index.js",
|
||||||
"serve": "node ./serve.js",
|
"serve": "node ./serve.js",
|
||||||
"depcheck": "depcheck .",
|
"depcheck": "depcheck .",
|
||||||
"lint": "eslint --fix \"src/**/*.ts\" \"src/**/*.tsx\" && prettier --write \"src/**/*.ts\" \"src/**/*.tsx\"",
|
"lint": "eslint --fix \"src/**/*.ts\" \"src/**/*.tsx\" && prettier --write \"src/**/*.ts\" \"src/**/*.tsx\"",
|
||||||
@@ -154,7 +156,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12.0.0",
|
"node": ">=14.0.0",
|
||||||
"npm": ">=6.9.0",
|
"npm": ">=6.9.0",
|
||||||
"bee": ">=0.6.0"
|
"bee": ">=0.6.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { ThemeProvider } from '@material-ui/core/styles'
|
|||||||
import { SnackbarProvider } from 'notistack'
|
import { SnackbarProvider } from 'notistack'
|
||||||
import React, { ReactElement } from 'react'
|
import React, { ReactElement } from 'react'
|
||||||
import { HashRouter as Router } from 'react-router-dom'
|
import { HashRouter as Router } from 'react-router-dom'
|
||||||
import * as Sentry from '@sentry/react'
|
|
||||||
import './App.css'
|
import './App.css'
|
||||||
import Dashboard from './layout/Dashboard'
|
import Dashboard from './layout/Dashboard'
|
||||||
import { Provider as BeeProvider } from './providers/Bee'
|
import { Provider as BeeProvider } from './providers/Bee'
|
||||||
@@ -13,48 +12,49 @@ import { Provider as PlatformProvider } from './providers/Platform'
|
|||||||
import { Provider as SettingsProvider } from './providers/Settings'
|
import { Provider as SettingsProvider } from './providers/Settings'
|
||||||
import { Provider as StampsProvider } from './providers/Stamps'
|
import { Provider as StampsProvider } from './providers/Stamps'
|
||||||
import { Provider as TopUpProvider } from './providers/TopUp'
|
import { Provider as TopUpProvider } from './providers/TopUp'
|
||||||
|
import { Provider as BalanceProvider } from './providers/WalletBalance'
|
||||||
import BaseRouter from './routes'
|
import BaseRouter from './routes'
|
||||||
import { theme } from './theme'
|
import { theme } from './theme'
|
||||||
import { config } from './config'
|
|
||||||
import ItsBroken from './layout/ItsBroken'
|
|
||||||
import { initSentry } from './utils/sentry'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
beeApiUrl?: string
|
beeApiUrl?: string
|
||||||
beeDebugApiUrl?: string
|
beeDebugApiUrl?: string
|
||||||
lockedApiSettings?: boolean
|
lockedApiSettings?: boolean
|
||||||
|
isBeeDesktop?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.SENTRY_KEY) {
|
const App = ({ beeApiUrl, beeDebugApiUrl, lockedApiSettings, isBeeDesktop }: Props): ReactElement => {
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
initSentry().catch(e => console.error(e))
|
|
||||||
}
|
|
||||||
|
|
||||||
const App = ({ beeApiUrl, beeDebugApiUrl, lockedApiSettings }: Props): ReactElement => {
|
|
||||||
const mainApp = (
|
const mainApp = (
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<SettingsProvider beeApiUrl={beeApiUrl} beeDebugApiUrl={beeDebugApiUrl} lockedApiSettings={lockedApiSettings}>
|
<SettingsProvider
|
||||||
|
beeApiUrl={beeApiUrl}
|
||||||
|
beeDebugApiUrl={beeDebugApiUrl}
|
||||||
|
lockedApiSettings={lockedApiSettings}
|
||||||
|
isBeeDesktop={isBeeDesktop}
|
||||||
|
>
|
||||||
<TopUpProvider>
|
<TopUpProvider>
|
||||||
<BeeProvider>
|
<BeeProvider>
|
||||||
<StampsProvider>
|
<BalanceProvider>
|
||||||
<FileProvider>
|
<StampsProvider>
|
||||||
<FeedsProvider>
|
<FileProvider>
|
||||||
<PlatformProvider>
|
<FeedsProvider>
|
||||||
<SnackbarProvider anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}>
|
<PlatformProvider>
|
||||||
<Router>
|
<SnackbarProvider preventDuplicate anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}>
|
||||||
<>
|
<Router>
|
||||||
<CssBaseline />
|
<>
|
||||||
<Dashboard>
|
<CssBaseline />
|
||||||
<BaseRouter />
|
<Dashboard>
|
||||||
</Dashboard>
|
<BaseRouter />
|
||||||
</>
|
</Dashboard>
|
||||||
</Router>
|
</>
|
||||||
</SnackbarProvider>
|
</Router>
|
||||||
</PlatformProvider>
|
</SnackbarProvider>
|
||||||
</FeedsProvider>
|
</PlatformProvider>
|
||||||
</FileProvider>
|
</FeedsProvider>
|
||||||
</StampsProvider>
|
</FileProvider>
|
||||||
|
</StampsProvider>
|
||||||
|
</BalanceProvider>
|
||||||
</BeeProvider>
|
</BeeProvider>
|
||||||
</TopUpProvider>
|
</TopUpProvider>
|
||||||
</SettingsProvider>
|
</SettingsProvider>
|
||||||
@@ -62,18 +62,6 @@ const App = ({ beeApiUrl, beeDebugApiUrl, lockedApiSettings }: Props): ReactElem
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
||||||
// Displays Report Dialog when some component crashes
|
|
||||||
if (config.SENTRY_KEY) {
|
|
||||||
return (
|
|
||||||
<Sentry.ErrorBoundary
|
|
||||||
showDialog
|
|
||||||
fallback={({ error, componentStack, resetError }) => <ItsBroken message={error.message} />}
|
|
||||||
>
|
|
||||||
{mainApp}
|
|
||||||
</Sentry.ErrorBoundary>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return mainApp
|
return mainApp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ export default function CheckoutModal({ peerId, uncashedAmount }: Props): ReactE
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
.catch((e: Error) => {
|
.catch((e: Error) => {
|
||||||
|
console.error(e) // eslint-disable-line
|
||||||
enqueueSnackbar(<span>Error: {e.message}</span>, { variant: 'error' })
|
enqueueSnackbar(<span>Error: {e.message}</span>, { variant: 'error' })
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { Component, ErrorInfo, ReactElement } from 'react'
|
import { Component, ErrorInfo, ReactElement } from 'react'
|
||||||
import ItsBroken from '../layout/ItsBroken'
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: ReactElement
|
children: ReactElement
|
||||||
@@ -27,7 +26,8 @@ export default class ErrorBoundary extends Component<Props, State> {
|
|||||||
|
|
||||||
render(): ReactElement {
|
render(): ReactElement {
|
||||||
if (this.state.error) {
|
if (this.state.error) {
|
||||||
return <ItsBroken message={this.state.error.message} />
|
// You can render any custom fallback UI
|
||||||
|
return <h1>Something went wrong. Error: {this.state.error.message}</h1>
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.props.children
|
return this.props.children
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
import { ReactElement, useEffect, useState } from 'react'
|
|
||||||
import * as Sentry from '@sentry/react'
|
|
||||||
import { Link } from '@material-ui/core'
|
|
||||||
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
|
|
||||||
import MessageSquare from 'remixicon-react/Message2LineIcon'
|
|
||||||
|
|
||||||
import config from '../config'
|
|
||||||
import SideBarItem from './SideBarItem'
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
|
||||||
createStyles({
|
|
||||||
link: {
|
|
||||||
color: '#9f9f9f',
|
|
||||||
textDecoration: 'none',
|
|
||||||
'&:hover': {
|
|
||||||
textDecoration: 'none',
|
|
||||||
|
|
||||||
// https://github.com/mui-org/material-ui/issues/22543
|
|
||||||
'@media (hover: none)': {
|
|
||||||
textDecoration: 'none',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
icon: {
|
|
||||||
height: theme.spacing(4),
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses Sentry DNS so it could be transformed into API call
|
|
||||||
* Sentry DNS like https://1asfasdf2312asdf3@o132123.ingest.sentry.io/13123123
|
|
||||||
*/
|
|
||||||
const SENTRY_PARSING_REGEX = /^https:\/\/(?<key>\w+)@(?<sub>\w+)\.ingest\.sentry\.io\/(?<path>\d+)$/gm
|
|
||||||
|
|
||||||
async function isSentryReachable(): Promise<boolean> {
|
|
||||||
const key = config.SENTRY_KEY
|
|
||||||
|
|
||||||
if (!key) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const match = SENTRY_PARSING_REGEX.exec(key)
|
|
||||||
|
|
||||||
if (!match) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = `https://${match.groups?.sub}.ingest.sentry.io/api/${match.groups?.path}/envelope/?sentry_key=${match.groups?.key}`
|
|
||||||
|
|
||||||
try {
|
|
||||||
await fetch(url, { method: 'POST' })
|
|
||||||
|
|
||||||
// Since we got some reply (even though most probably with some error) that means Sentry is reachable ==> lets provide the Feedback form
|
|
||||||
return true
|
|
||||||
} catch (e) {
|
|
||||||
// If an error was thrown than the request was blocked by the browser so Sentry is not accessible to us
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function showFeedbackForm(): void {
|
|
||||||
const eventId = Sentry.captureMessage('User feedback')
|
|
||||||
Sentry.showReportDialog({
|
|
||||||
eventId,
|
|
||||||
title: 'Provide us feedback!',
|
|
||||||
subtitle: 'Share with us what you like and/or dislike.',
|
|
||||||
subtitle2: 'We will be very happy.',
|
|
||||||
labelComments: 'What is your impression about this app?',
|
|
||||||
labelSubmit: 'Send Feedback',
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Feedback(): ReactElement {
|
|
||||||
const [sentryEnabled, setSentryEnabled] = useState(false)
|
|
||||||
const classes = useStyles()
|
|
||||||
|
|
||||||
// Run this only on component mount to verify once that Sentry is reachable
|
|
||||||
useEffect(() => {
|
|
||||||
isSentryReachable().then(result => {
|
|
||||||
setSentryEnabled(result)
|
|
||||||
})
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
if (sentryEnabled) {
|
|
||||||
return (
|
|
||||||
<Link onClick={showFeedbackForm} className={classes.link}>
|
|
||||||
<SideBarItem iconStart={<MessageSquare className={classes.icon} />} label={<span>Send feedback</span>} />
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return <></>
|
|
||||||
}
|
|
||||||
@@ -9,13 +9,11 @@ import HomeIcon from 'remixicon-react/Home3LineIcon'
|
|||||||
import SettingsIcon from 'remixicon-react/Settings2LineIcon'
|
import SettingsIcon from 'remixicon-react/Settings2LineIcon'
|
||||||
import AccountIcon from 'remixicon-react/Wallet3LineIcon'
|
import AccountIcon from 'remixicon-react/Wallet3LineIcon'
|
||||||
import { Context as BeeContext } from '../providers/Bee'
|
import { Context as BeeContext } from '../providers/Bee'
|
||||||
import { Context as TopUpContext } from '../providers/TopUp'
|
import { Context as SettingsContext } from '../providers/Settings'
|
||||||
import DashboardLogo from '../assets/dashboard-logo.svg'
|
import DashboardLogo from '../assets/dashboard-logo.svg'
|
||||||
import DesktopLogo from '../assets/desktop-logo.svg'
|
import DesktopLogo from '../assets/desktop-logo.svg'
|
||||||
import { config } from '../config'
|
import { config } from '../config'
|
||||||
import { useIsBeeDesktop } from '../hooks/apiHooks'
|
|
||||||
import { ROUTES } from '../routes'
|
import { ROUTES } from '../routes'
|
||||||
import Feedback from './Feedback'
|
|
||||||
import SideBarItem from './SideBarItem'
|
import SideBarItem from './SideBarItem'
|
||||||
import SideBarStatus from './SideBarStatus'
|
import SideBarStatus from './SideBarStatus'
|
||||||
import { BeeModes } from '@ethersphere/bee-js'
|
import { BeeModes } from '@ethersphere/bee-js'
|
||||||
@@ -68,8 +66,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
|||||||
|
|
||||||
export default function SideBar(): ReactElement {
|
export default function SideBar(): ReactElement {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
const { isBeeDesktop } = useIsBeeDesktop()
|
const { isBeeDesktop } = useContext(SettingsContext)
|
||||||
const { providerUrl } = useContext(TopUpContext)
|
|
||||||
const { nodeInfo } = useContext(BeeContext)
|
const { nodeInfo } = useContext(BeeContext)
|
||||||
|
|
||||||
const navBarItems = [
|
const navBarItems = [
|
||||||
@@ -86,7 +83,7 @@ export default function SideBar(): ReactElement {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Account',
|
label: 'Account',
|
||||||
path: providerUrl === null ? ROUTES.WALLET : ROUTES.ACCOUNT_WALLET,
|
path: ROUTES.ACCOUNT_WALLET,
|
||||||
icon: AccountIcon,
|
icon: AccountIcon,
|
||||||
pathMatcherSubstring: '/account/',
|
pathMatcherSubstring: '/account/',
|
||||||
},
|
},
|
||||||
@@ -135,7 +132,6 @@ export default function SideBar(): ReactElement {
|
|||||||
<Link to={ROUTES.STATUS} className={classes.link}>
|
<Link to={ROUTES.STATUS} className={classes.link}>
|
||||||
<SideBarStatus path={ROUTES.STATUS} />
|
<SideBarStatus path={ROUTES.STATUS} />
|
||||||
</Link>
|
</Link>
|
||||||
<Feedback />
|
|
||||||
</List>
|
</List>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ export default function WithdrawDepositModal({
|
|||||||
setOpen(false)
|
setOpen(false)
|
||||||
enqueueSnackbar(`${successMessage} Transaction ${transactionHash}`, { variant: 'success' })
|
enqueueSnackbar(`${successMessage} Transaction ${transactionHash}`, { variant: 'success' })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error(e) // eslint-disable-line
|
||||||
enqueueSnackbar(`${errorMessage} Error: ${(e as Error).message}`, { variant: 'error' })
|
enqueueSnackbar(`${errorMessage} Error: ${(e as Error).message}`, { variant: 'error' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,14 +5,12 @@ class Config {
|
|||||||
public readonly BEE_DOCS_HOST: string
|
public readonly BEE_DOCS_HOST: string
|
||||||
public readonly BEE_DISCORD_HOST: string
|
public readonly BEE_DISCORD_HOST: string
|
||||||
public readonly GITHUB_REPO_URL: string
|
public readonly GITHUB_REPO_URL: string
|
||||||
|
public readonly BEE_DESKTOP_ENABLED: boolean
|
||||||
public readonly BEE_DESKTOP_URL: string
|
public readonly BEE_DESKTOP_URL: string
|
||||||
public readonly SENTRY_KEY: string | undefined
|
public readonly DEFAULT_RPC_URL: string
|
||||||
public readonly SENTRY_ENVIRONMENT: string | undefined
|
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.BEE_API_HOST = sessionStorage.getItem('api_host') ?? process.env.REACT_APP_BEE_HOST ?? 'http://localhost:1633'
|
this.BEE_API_HOST = sessionStorage.getItem('api_host') ?? process.env.REACT_APP_BEE_HOST ?? 'http://localhost:1633'
|
||||||
this.SENTRY_KEY = process.env.REACT_APP_SENTRY_KEY
|
|
||||||
this.SENTRY_ENVIRONMENT = process.env.REACT_APP_SENTRY_ENVIRONMENT
|
|
||||||
this.BEE_DEBUG_API_HOST =
|
this.BEE_DEBUG_API_HOST =
|
||||||
sessionStorage.getItem('debug_api_host') ?? process.env.REACT_APP_BEE_DEBUG_HOST ?? 'http://localhost:1635'
|
sessionStorage.getItem('debug_api_host') ?? process.env.REACT_APP_BEE_DEBUG_HOST ?? 'http://localhost:1635'
|
||||||
this.BLOCKCHAIN_EXPLORER_URL =
|
this.BLOCKCHAIN_EXPLORER_URL =
|
||||||
@@ -20,7 +18,9 @@ class Config {
|
|||||||
this.BEE_DOCS_HOST = process.env.REACT_APP_BEE_DOCS_HOST ?? 'https://docs.ethswarm.org/docs/'
|
this.BEE_DOCS_HOST = process.env.REACT_APP_BEE_DOCS_HOST ?? 'https://docs.ethswarm.org/docs/'
|
||||||
this.BEE_DISCORD_HOST = process.env.REACT_APP_BEE_DISCORD_HOST ?? 'https://discord.gg/eKr9XPv7'
|
this.BEE_DISCORD_HOST = process.env.REACT_APP_BEE_DISCORD_HOST ?? 'https://discord.gg/eKr9XPv7'
|
||||||
this.GITHUB_REPO_URL = process.env.REACT_APP_BEE_GITHUB_REPO_URL ?? 'https://api.github.com/repos/ethersphere/bee'
|
this.GITHUB_REPO_URL = process.env.REACT_APP_BEE_GITHUB_REPO_URL ?? 'https://api.github.com/repos/ethersphere/bee'
|
||||||
|
this.BEE_DESKTOP_ENABLED = process.env.REACT_APP_BEE_DESKTOP_ENABLED === 'true'
|
||||||
this.BEE_DESKTOP_URL = process.env.REACT_APP_BEE_DESKTOP_URL ?? window.location.origin
|
this.BEE_DESKTOP_URL = process.env.REACT_APP_BEE_DESKTOP_URL ?? window.location.origin
|
||||||
|
this.DEFAULT_RPC_URL = process.env.REACT_APP_DEFAULT_RPC_URL ?? 'https://xdai.fairdatasociety.org'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { renderHook } from '@testing-library/react-hooks'
|
import { renderHook } from '@testing-library/react-hooks'
|
||||||
import express from 'express'
|
|
||||||
import cors from 'cors'
|
import cors from 'cors'
|
||||||
|
import express from 'express'
|
||||||
import type { Server } from 'http'
|
import type { Server } from 'http'
|
||||||
import { useIsBeeDesktop } from './apiHooks'
|
import { useBeeDesktop } from './apiHooks'
|
||||||
|
|
||||||
interface AddressInfo {
|
interface AddressInfo {
|
||||||
address: string
|
address: string
|
||||||
@@ -10,7 +10,7 @@ interface AddressInfo {
|
|||||||
port: number
|
port: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export function mockServer(data: Record<string | number | symbol, string>): Promise<Server> {
|
export function mockServer(data: Record<string | number | symbol, string | boolean>): Promise<Server> {
|
||||||
const app = express()
|
const app = express()
|
||||||
app.use(cors())
|
app.use(cors())
|
||||||
|
|
||||||
@@ -26,48 +26,28 @@ export function mockServer(data: Record<string | number | symbol, string>): Prom
|
|||||||
}
|
}
|
||||||
|
|
||||||
let serverCorrect: Server
|
let serverCorrect: Server
|
||||||
let serverWrong: Server
|
|
||||||
|
|
||||||
let serverCorrectURL: string
|
let serverCorrectURL: string
|
||||||
let serverWrongURL: string
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
serverCorrect = await mockServer({ name: 'bee-desktop' })
|
serverCorrect = await mockServer({ autoUpdateEnabled: true, version: '0.1.0' })
|
||||||
const portServerCorrect = (serverCorrect.address() as AddressInfo).port
|
const portServerCorrect = (serverCorrect.address() as AddressInfo).port
|
||||||
serverCorrectURL = `http://localhost:${portServerCorrect}`
|
serverCorrectURL = `http://localhost:${portServerCorrect}`
|
||||||
|
|
||||||
serverWrong = await mockServer({ foo: 'bar' })
|
|
||||||
const portServerWrong = (serverWrong.address() as AddressInfo).port
|
|
||||||
serverWrongURL = `http://localhost:${portServerWrong}`
|
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
afterAll(async () => {
|
||||||
await new Promise(resolve => serverCorrect.close(resolve))
|
await new Promise(resolve => serverCorrect.close(resolve))
|
||||||
await new Promise(resolve => serverWrong.close(resolve))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('useIsBeeDesktop', () => {
|
describe('useBeeDesktop', () => {
|
||||||
it('should fail when connected to wrong server', async () => {
|
it('should not have error when connected to bee-desktop', async () => {
|
||||||
const { result, waitFor } = renderHook(() => useIsBeeDesktop({ BEE_DESKTOP_URL: serverWrongURL }))
|
const { result, waitFor } = renderHook(() => useBeeDesktop(true, { BEE_DESKTOP_URL: serverCorrectURL }))
|
||||||
|
|
||||||
expect(result.current.isLoading).toBe(true)
|
|
||||||
expect(result.current.isBeeDesktop).toBe(false)
|
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(result.current.isLoading).toBe(false)
|
expect(result.current.isLoading).toBe(false)
|
||||||
})
|
})
|
||||||
expect(result.current.isBeeDesktop).toBe(false)
|
expect(result.current.desktopAutoUpdateEnabled).toBe(true)
|
||||||
})
|
expect(result.current.beeDesktopVersion).toBe('0.1.0')
|
||||||
|
expect(result.current.error).toBe(null)
|
||||||
it('should return isBeeDesktop true when connected to bee-desktop', async () => {
|
|
||||||
const { result, waitFor } = renderHook(() => useIsBeeDesktop({ BEE_DESKTOP_URL: serverCorrectURL }))
|
|
||||||
|
|
||||||
expect(result.current.isLoading).toBe(true)
|
|
||||||
expect(result.current.isBeeDesktop).toBe(false)
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
expect(result.current.isLoading).toBe(false)
|
|
||||||
})
|
|
||||||
expect(result.current.isBeeDesktop).toBe(true)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { config } from '../config'
|
import { config } from '../config'
|
||||||
import { getJson } from '../utils/net'
|
|
||||||
import { getLatestBeeDesktopVersion } from '../utils/desktop'
|
import { getLatestBeeDesktopVersion } from '../utils/desktop'
|
||||||
|
import { getJson } from '../utils/net'
|
||||||
|
|
||||||
export interface LatestBeeReleaseHook {
|
export interface LatestBeeReleaseHook {
|
||||||
latestBeeRelease: LatestBeeRelease | null
|
latestBeeRelease: LatestBeeRelease | null
|
||||||
@@ -10,8 +10,8 @@ export interface LatestBeeReleaseHook {
|
|||||||
error: Error | null
|
error: Error | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IsBeeDesktopHook {
|
export interface BeeDesktopHook {
|
||||||
isBeeDesktop: boolean
|
error: Error | null
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
beeDesktopVersion: string
|
beeDesktopVersion: string
|
||||||
desktopAutoUpdateEnabled: boolean
|
desktopAutoUpdateEnabled: boolean
|
||||||
@@ -25,36 +25,34 @@ interface Config {
|
|||||||
BEE_DESKTOP_URL: string
|
BEE_DESKTOP_URL: string
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export const useBeeDesktop = (isBeeDesktop = false, conf: Config = config): BeeDesktopHook => {
|
||||||
* Detect if the dashboard is run within bee-desktop
|
|
||||||
*
|
|
||||||
* @returns isBeeDesktop true if this is run within bee-desktop
|
|
||||||
*/
|
|
||||||
export const useIsBeeDesktop = (conf: Config = config): IsBeeDesktopHook => {
|
|
||||||
const [isBeeDesktop, setIsBeeDesktop] = useState<boolean>(false)
|
|
||||||
const [desktopAutoUpdateEnabled, setDesktopAutoUpdateEnabled] = useState<boolean>(true)
|
const [desktopAutoUpdateEnabled, setDesktopAutoUpdateEnabled] = useState<boolean>(true)
|
||||||
const [beeDesktopVersion, setBeeDesktopVersion] = useState<string>('')
|
const [beeDesktopVersion, setBeeDesktopVersion] = useState<string>('')
|
||||||
const [isLoading, setLoading] = useState<boolean>(true)
|
const [isLoading, setLoading] = useState<boolean>(true)
|
||||||
|
const [error, setError] = useState<Error | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
axios
|
if (!isBeeDesktop) {
|
||||||
.get(`${conf.BEE_DESKTOP_URL}/info`)
|
setLoading(false)
|
||||||
.then(res => {
|
setError(null)
|
||||||
if (res.data?.name === 'bee-desktop') {
|
} else {
|
||||||
setIsBeeDesktop(true)
|
axios
|
||||||
|
.get(`${conf.BEE_DESKTOP_URL}/info`)
|
||||||
|
.then(res => {
|
||||||
setBeeDesktopVersion(res.data?.version)
|
setBeeDesktopVersion(res.data?.version)
|
||||||
setDesktopAutoUpdateEnabled(res.data?.autoUpdateEnabled)
|
setDesktopAutoUpdateEnabled(res.data?.autoUpdateEnabled)
|
||||||
} else setIsBeeDesktop(false)
|
setError(null)
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(e => {
|
||||||
setIsBeeDesktop(false)
|
setError(e)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
})
|
})
|
||||||
}, [conf])
|
}
|
||||||
|
}, [conf, isBeeDesktop])
|
||||||
|
|
||||||
return { isBeeDesktop, isLoading, beeDesktopVersion, desktopAutoUpdateEnabled }
|
return { error, isLoading, beeDesktopVersion, desktopAutoUpdateEnabled }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function checkNewVersion(conf: Config): Promise<string> {
|
async function checkNewVersion(conf: Config): Promise<string> {
|
||||||
@@ -70,7 +68,7 @@ async function checkNewVersion(conf: Config): Promise<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useNewBeeDesktopVersion(isBeeDesktop: boolean, conf: Config = config): NewDesktopVersionHook {
|
export function useNewBeeDesktopVersion(isBeeDesktop: boolean, conf: Config = config): NewDesktopVersionHook {
|
||||||
const [newBeeDesktopVersion, setNewNewBeeDesktopVersion] = useState<string>('')
|
const [newBeeDesktopVersion, setNewBeeDesktopVersion] = useState<string>('')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isBeeDesktop) {
|
if (!isBeeDesktop) {
|
||||||
@@ -79,7 +77,7 @@ export function useNewBeeDesktopVersion(isBeeDesktop: boolean, conf: Config = co
|
|||||||
|
|
||||||
checkNewVersion(conf).then(version => {
|
checkNewVersion(conf).then(version => {
|
||||||
if (version !== '') {
|
if (version !== '') {
|
||||||
setNewNewBeeDesktopVersion(version)
|
setNewBeeDesktopVersion(version)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [isBeeDesktop, conf])
|
}, [isBeeDesktop, conf])
|
||||||
|
|||||||
@@ -5,11 +5,9 @@ import { useSnackbar } from 'notistack'
|
|||||||
import CloseIcon from 'remixicon-react/CloseCircleLineIcon'
|
import CloseIcon from 'remixicon-react/CloseCircleLineIcon'
|
||||||
import ErrorBoundary from '../components/ErrorBoundary'
|
import ErrorBoundary from '../components/ErrorBoundary'
|
||||||
import SideBar from '../components/SideBar'
|
import SideBar from '../components/SideBar'
|
||||||
import { Context } from '../providers/Bee'
|
import { Context as BeeContext } from '../providers/Bee'
|
||||||
import config from '../config'
|
import { Context as SettingsContext } from '../providers/Settings'
|
||||||
import * as Sentry from '@sentry/react'
|
import { useNewBeeDesktopVersion } from '../hooks/apiHooks'
|
||||||
import ItsBroken from './ItsBroken'
|
|
||||||
import { useIsBeeDesktop, useNewBeeDesktopVersion } from '../hooks/apiHooks'
|
|
||||||
import { BEE_DESKTOP_LATEST_RELEASE_PAGE } from '../utils/desktop'
|
import { BEE_DESKTOP_LATEST_RELEASE_PAGE } from '../utils/desktop'
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
const useStyles = makeStyles((theme: Theme) =>
|
||||||
@@ -28,11 +26,52 @@ interface Props {
|
|||||||
const Dashboard = (props: Props): ReactElement => {
|
const Dashboard = (props: Props): ReactElement => {
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
||||||
const { isLoading } = useContext(Context)
|
const { isLoading, isLatestBeeVersion, latestBeeRelease, latestBeeVersionUrl, latestUserVersion } =
|
||||||
const { isBeeDesktop } = useIsBeeDesktop()
|
useContext(BeeContext)
|
||||||
|
const { isBeeDesktop } = useContext(SettingsContext)
|
||||||
const { newBeeDesktopVersion } = useNewBeeDesktopVersion(isBeeDesktop)
|
const { newBeeDesktopVersion } = useNewBeeDesktopVersion(isBeeDesktop)
|
||||||
const { enqueueSnackbar, closeSnackbar } = useSnackbar()
|
const { enqueueSnackbar, closeSnackbar } = useSnackbar()
|
||||||
|
|
||||||
|
// New version of Bee client notification
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isLoading && !isBeeDesktop && !isLatestBeeVersion && latestBeeRelease && latestUserVersion) {
|
||||||
|
enqueueSnackbar(`There is new Bee version ${latestBeeRelease?.name}!`, {
|
||||||
|
variant: 'warning',
|
||||||
|
preventDuplicate: true,
|
||||||
|
key: 'beeNewVersion',
|
||||||
|
persist: true,
|
||||||
|
action: key => (
|
||||||
|
<React.Fragment>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
window.open(latestBeeVersionUrl)
|
||||||
|
closeSnackbar(key)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Download release
|
||||||
|
</Button>
|
||||||
|
<IconButton
|
||||||
|
onClick={() => {
|
||||||
|
closeSnackbar(key)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CloseIcon />
|
||||||
|
</IconButton>
|
||||||
|
</React.Fragment>
|
||||||
|
),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
closeSnackbar,
|
||||||
|
enqueueSnackbar,
|
||||||
|
isLatestBeeVersion,
|
||||||
|
isBeeDesktop,
|
||||||
|
latestBeeRelease,
|
||||||
|
latestBeeVersionUrl,
|
||||||
|
isLoading,
|
||||||
|
latestUserVersion,
|
||||||
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (newBeeDesktopVersion !== '') {
|
if (newBeeDesktopVersion !== '') {
|
||||||
enqueueSnackbar(`There is new Swarm Dashboard version ${newBeeDesktopVersion}!`, {
|
enqueueSnackbar(`There is new Swarm Dashboard version ${newBeeDesktopVersion}!`, {
|
||||||
@@ -75,25 +114,13 @@ const Dashboard = (props: Props): ReactElement => {
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
let errorBoundaryWithContent
|
|
||||||
|
|
||||||
if (config.SENTRY_KEY) {
|
|
||||||
errorBoundaryWithContent = (
|
|
||||||
<Sentry.ErrorBoundary
|
|
||||||
showDialog
|
|
||||||
fallback={({ error, componentStack, resetError }) => <ItsBroken message={error.message} />}
|
|
||||||
>
|
|
||||||
{content}
|
|
||||||
</Sentry.ErrorBoundary>
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
errorBoundaryWithContent = <ErrorBoundary>{content}</ErrorBoundary>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex' }}>
|
<div style={{ display: 'flex' }}>
|
||||||
<SideBar />
|
<SideBar />
|
||||||
<Container className={classes.content}>{errorBoundaryWithContent}</Container>
|
<Container className={classes.content}>
|
||||||
|
{' '}
|
||||||
|
<ErrorBoundary>{content}</ErrorBoundary>
|
||||||
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
import { Container } from '@material-ui/core'
|
|
||||||
import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'
|
|
||||||
import { ReactElement } from 'react'
|
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) =>
|
|
||||||
createStyles({
|
|
||||||
content: {
|
|
||||||
backgroundColor: theme.palette.background.default,
|
|
||||||
minHeight: '100vh',
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
errorMsg: {
|
|
||||||
marginTop: '30px',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
message: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Provide some nicer design
|
|
||||||
const ItsBroken = ({ message }: Props): ReactElement => {
|
|
||||||
const classes = useStyles()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<Container className={classes.content}>
|
|
||||||
<h1>Ups, there was a problem 😅</h1>
|
|
||||||
<h3 className={classes.errorMsg}>Error: {message}</h3>
|
|
||||||
</Container>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ItsBroken
|
|
||||||
@@ -10,13 +10,18 @@ import ExpandableListItemActions from '../../../components/ExpandableListItemAct
|
|||||||
import ExpandableListItemKey from '../../../components/ExpandableListItemKey'
|
import ExpandableListItemKey from '../../../components/ExpandableListItemKey'
|
||||||
import { Loading } from '../../../components/Loading'
|
import { Loading } from '../../../components/Loading'
|
||||||
import { SwarmButton } from '../../../components/SwarmButton'
|
import { SwarmButton } from '../../../components/SwarmButton'
|
||||||
import { Context } from '../../../providers/Bee'
|
import TroubleshootConnectionCard from '../../../components/TroubleshootConnectionCard'
|
||||||
|
import { CheckState, Context as BeeContext } from '../../../providers/Bee'
|
||||||
|
import { Context as SettingsContext } from '../../../providers/Settings'
|
||||||
|
import { Context as BalanceProvider } from '../../../providers/WalletBalance'
|
||||||
import { ROUTES } from '../../../routes'
|
import { ROUTES } from '../../../routes'
|
||||||
import { AccountNavigation } from '../AccountNavigation'
|
import { AccountNavigation } from '../AccountNavigation'
|
||||||
import { Header } from '../Header'
|
import { Header } from '../Header'
|
||||||
|
|
||||||
export function AccountWallet(): ReactElement {
|
export function AccountWallet(): ReactElement {
|
||||||
const { balance, nodeAddresses, nodeInfo } = useContext(Context)
|
const { nodeAddresses, nodeInfo, status } = useContext(BeeContext)
|
||||||
|
const { isBeeDesktop } = useContext(SettingsContext)
|
||||||
|
const { balance } = useContext(BalanceProvider)
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
@@ -29,9 +34,11 @@ export function AccountWallet(): ReactElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function onDeposit() {
|
function onDeposit() {
|
||||||
navigate(ROUTES.CONFIRMATION)
|
navigate(ROUTES.TOP_UP)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status.all === CheckState.ERROR) return <TroubleshootConnectionCard />
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Header />
|
<Header />
|
||||||
@@ -65,9 +72,11 @@ export function AccountWallet(): ReactElement {
|
|||||||
<SwarmButton onClick={onCheckTransactions} iconType={Link}>
|
<SwarmButton onClick={onCheckTransactions} iconType={Link}>
|
||||||
Check transactions on Blockscout
|
Check transactions on Blockscout
|
||||||
</SwarmButton>
|
</SwarmButton>
|
||||||
<SwarmButton onClick={onInvite} iconType={Gift}>
|
{isBeeDesktop && (
|
||||||
Invite to Swarm...
|
<SwarmButton onClick={onInvite} iconType={Gift}>
|
||||||
</SwarmButton>
|
Invite to Swarm...
|
||||||
|
</SwarmButton>
|
||||||
|
)}
|
||||||
</ExpandableListItemActions>
|
</ExpandableListItemActions>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ export function Download(): ReactElement {
|
|||||||
if (message.includes('Not Found: Not Found')) {
|
if (message.includes('Not Found: Not Found')) {
|
||||||
message = 'The specified hash was not found.'
|
message = 'The specified hash was not found.'
|
||||||
}
|
}
|
||||||
|
console.error(error) // eslint-disable-line
|
||||||
enqueueSnackbar(<span>Error: {message || 'Unknown'}</span>, { variant: 'error' })
|
enqueueSnackbar(<span>Error: {message || 'Unknown'}</span>, { variant: 'error' })
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ export function Upload(): ReactElement {
|
|||||||
let fls: FilePath[] = files.map(f => packageFile(f)) // Apart from packaging, this is needed to not modify the original files array as it can trigger effects
|
let fls: FilePath[] = files.map(f => packageFile(f)) // Apart from packaging, this is needed to not modify the original files array as it can trigger effects
|
||||||
let indexDocument: string | undefined = undefined // This means we assume it's folder
|
let indexDocument: string | undefined = undefined // This means we assume it's folder
|
||||||
|
|
||||||
if (files.length === 1) indexDocument = files[0].name
|
if (files.length === 1) indexDocument = unescape(encodeURIComponent(files[0].name))
|
||||||
else if (files.length > 1) {
|
else if (files.length > 1) {
|
||||||
const idx = detectIndexHtml(files)
|
const idx = detectIndexHtml(files)
|
||||||
|
|
||||||
@@ -147,6 +147,7 @@ export function Upload(): ReactElement {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(e => {
|
.catch(e => {
|
||||||
|
console.error(e) // eslint-disable-line
|
||||||
enqueueSnackbar(`Error uploading: ${e.message}`, { variant: 'error' })
|
enqueueSnackbar(`Error uploading: ${e.message}`, { variant: 'error' })
|
||||||
setUploading(false)
|
setUploading(false)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ import ExpandableListItemKey from '../../components/ExpandableListItemKey'
|
|||||||
import { HistoryHeader } from '../../components/HistoryHeader'
|
import { HistoryHeader } from '../../components/HistoryHeader'
|
||||||
import { Loading } from '../../components/Loading'
|
import { Loading } from '../../components/Loading'
|
||||||
import { SwarmButton } from '../../components/SwarmButton'
|
import { SwarmButton } from '../../components/SwarmButton'
|
||||||
import { Context as BeeContext } from '../../providers/Bee'
|
import { Context as BalanceProvider } from '../../providers/WalletBalance'
|
||||||
import { Context as TopUpContext } from '../../providers/TopUp'
|
import { Context as TopUpContext } from '../../providers/TopUp'
|
||||||
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
import { createGiftWallet } from '../../utils/desktop'
|
import { createGiftWallet } from '../../utils/desktop'
|
||||||
import { ResolvedWallet } from '../../utils/wallet'
|
import { ResolvedWallet } from '../../utils/wallet'
|
||||||
import { Token } from '../../models/Token'
|
import { Token } from '../../models/Token'
|
||||||
@@ -21,15 +22,15 @@ const GIFT_WALLET_FUND_DAI_AMOUNT = Token.fromDecimal('0.1', 18)
|
|||||||
const GIFT_WALLET_FUND_BZZ_AMOUNT = Token.fromDecimal('0.5', 16)
|
const GIFT_WALLET_FUND_BZZ_AMOUNT = Token.fromDecimal('0.5', 16)
|
||||||
|
|
||||||
export default function Index(): ReactElement {
|
export default function Index(): ReactElement {
|
||||||
const { giftWallets, addGiftWallet, provider } = useContext(TopUpContext)
|
const { giftWallets, addGiftWallet } = useContext(TopUpContext)
|
||||||
const { balance } = useContext(BeeContext)
|
const { provider } = useContext(SettingsContext)
|
||||||
|
const { balance } = useContext(BalanceProvider)
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [balances, setBalances] = useState<ResolvedWallet[]>([])
|
const [balances, setBalances] = useState<ResolvedWallet[]>([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function mapGiftWallets() {
|
async function mapGiftWallets() {
|
||||||
if (!provider) return
|
|
||||||
const results = []
|
const results = []
|
||||||
for (const giftWallet of giftWallets) {
|
for (const giftWallet of giftWallets) {
|
||||||
results.push(await ResolvedWallet.make(giftWallet, provider))
|
results.push(await ResolvedWallet.make(giftWallet, provider))
|
||||||
@@ -52,6 +53,7 @@ export default function Index(): ReactElement {
|
|||||||
await createGiftWallet(wallet.address)
|
await createGiftWallet(wallet.address)
|
||||||
enqueueSnackbar('Succesfully funded gift wallet', { variant: 'success' })
|
enqueueSnackbar('Succesfully funded gift wallet', { variant: 'success' })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error) // eslint-disable-line
|
||||||
enqueueSnackbar(`Failed to fund gift wallet: ${error}`, { variant: 'error' })
|
enqueueSnackbar(`Failed to fund gift wallet: ${error}`, { variant: 'error' })
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import { ReactElement, useContext } from 'react'
|
|
||||||
import { Button } from '@material-ui/core'
|
import { Button } from '@material-ui/core'
|
||||||
import Wallet from 'remixicon-react/Wallet3LineIcon'
|
import { ReactElement, useContext } from 'react'
|
||||||
|
import { useNavigate } from 'react-router'
|
||||||
import ExchangeFunds from 'remixicon-react/ExchangeFundsLineIcon'
|
import ExchangeFunds from 'remixicon-react/ExchangeFundsLineIcon'
|
||||||
import Upload from 'remixicon-react/UploadLineIcon'
|
import Upload from 'remixicon-react/UploadLineIcon'
|
||||||
|
import Wallet from 'remixicon-react/Wallet3LineIcon'
|
||||||
import { Context as BeeContext } from '../../providers/Bee'
|
|
||||||
import Card from '../../components/Card'
|
import Card from '../../components/Card'
|
||||||
import Map from '../../components/Map'
|
|
||||||
import ExpandableListItem from '../../components/ExpandableListItem'
|
import ExpandableListItem from '../../components/ExpandableListItem'
|
||||||
import { useNavigate } from 'react-router'
|
import Map from '../../components/Map'
|
||||||
|
import { useBeeDesktop, useNewBeeDesktopVersion } from '../../hooks/apiHooks'
|
||||||
|
import { Context as BeeContext } from '../../providers/Bee'
|
||||||
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
|
import { Context as BalanceProvider } from '../../providers/WalletBalance'
|
||||||
import { ROUTES } from '../../routes'
|
import { ROUTES } from '../../routes'
|
||||||
import { useIsBeeDesktop, useNewBeeDesktopVersion } from '../../hooks/apiHooks'
|
import { chainIdToName } from '../../utils/chain'
|
||||||
import { BEE_DESKTOP_LATEST_RELEASE_PAGE } from '../../utils/desktop'
|
import { BEE_DESKTOP_LATEST_RELEASE_PAGE } from '../../utils/desktop'
|
||||||
import NodeInfoCard from './NodeInfoCard'
|
import NodeInfoCard from './NodeInfoCard'
|
||||||
|
|
||||||
@@ -22,13 +24,24 @@ export default function Status(): ReactElement {
|
|||||||
latestBeeVersionUrl,
|
latestBeeVersionUrl,
|
||||||
topology,
|
topology,
|
||||||
nodeInfo,
|
nodeInfo,
|
||||||
balance,
|
|
||||||
chequebookBalance,
|
chequebookBalance,
|
||||||
|
chainId,
|
||||||
} = useContext(BeeContext)
|
} = useContext(BeeContext)
|
||||||
const { isBeeDesktop, beeDesktopVersion } = useIsBeeDesktop()
|
const { isBeeDesktop } = useContext(SettingsContext)
|
||||||
|
const { balance, error } = useContext(BalanceProvider)
|
||||||
|
const { beeDesktopVersion } = useBeeDesktop(isBeeDesktop)
|
||||||
const { newBeeDesktopVersion } = useNewBeeDesktopVersion(isBeeDesktop)
|
const { newBeeDesktopVersion } = useNewBeeDesktopVersion(isBeeDesktop)
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
let balanceText = 'Loading...'
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
balanceText = 'Could not load...'
|
||||||
|
console.error(error) // eslint-disable-line
|
||||||
|
} else if (balance) {
|
||||||
|
balanceText = `${balance.bzz.toSignificantDigits(4)} xBZZ | ${balance.dai.toSignificantDigits(4)} xDAI`
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'stretch', alignContent: 'stretch' }}>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'stretch', alignContent: 'stretch' }}>
|
||||||
@@ -42,7 +55,7 @@ export default function Status(): ReactElement {
|
|||||||
onClick: () => navigate(ROUTES.ACCOUNT_WALLET),
|
onClick: () => navigate(ROUTES.ACCOUNT_WALLET),
|
||||||
}}
|
}}
|
||||||
icon={<Wallet />}
|
icon={<Wallet />}
|
||||||
title={`${balance?.bzz.toSignificantDigits(4)} xBZZ | ${balance?.dai.toSignificantDigits(4)} xDAI`}
|
title={balanceText}
|
||||||
subtitle="Current wallet balance."
|
subtitle="Current wallet balance."
|
||||||
status="ok"
|
status="ok"
|
||||||
/>
|
/>
|
||||||
@@ -51,7 +64,7 @@ export default function Status(): ReactElement {
|
|||||||
buttonProps={{
|
buttonProps={{
|
||||||
iconType: Wallet,
|
iconType: Wallet,
|
||||||
children: 'Setup wallet',
|
children: 'Setup wallet',
|
||||||
onClick: () => navigate(ROUTES.WALLET),
|
onClick: () => navigate(ROUTES.TOP_UP),
|
||||||
}}
|
}}
|
||||||
icon={<Upload />}
|
icon={<Upload />}
|
||||||
title="Your wallet is not setup."
|
title="Your wallet is not setup."
|
||||||
@@ -85,7 +98,7 @@ export default function Status(): ReactElement {
|
|||||||
icon={<ExchangeFunds />}
|
icon={<ExchangeFunds />}
|
||||||
title={
|
title={
|
||||||
chequebookBalance?.availableBalance
|
chequebookBalance?.availableBalance
|
||||||
? `${chequebookBalance.availableBalance.toFixedDecimal(4)} xBZZ`
|
? `${chequebookBalance.availableBalance.toSignificantDigits(4)} xBZZ`
|
||||||
: 'No available balance.'
|
: 'No available balance.'
|
||||||
}
|
}
|
||||||
subtitle="Chequebook not setup."
|
subtitle="Chequebook not setup."
|
||||||
@@ -145,6 +158,7 @@ export default function Status(): ReactElement {
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ExpandableListItem label="Mode" value={nodeInfo?.beeMode} />
|
<ExpandableListItem label="Mode" value={nodeInfo?.beeMode} />
|
||||||
|
{chainId && <ExpandableListItem label="Blockchain network" value={chainIdToName(chainId)} />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
import { Box, createStyles, Grid, makeStyles, Typography } from '@material-ui/core'
|
|
||||||
import { ReactElement, useContext, useState } from 'react'
|
|
||||||
import Check from 'remixicon-react/CheckLineIcon'
|
|
||||||
import BankCard from 'remixicon-react/BankCard2LineIcon'
|
|
||||||
import MoneyDollarCircle from 'remixicon-react/MoneyDollarCircleLineIcon'
|
|
||||||
import Gift from 'remixicon-react/GiftLineIcon'
|
|
||||||
import { useNavigate } from 'react-router'
|
|
||||||
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
|
|
||||||
import { HistoryHeader } from '../../components/HistoryHeader'
|
|
||||||
import { SwarmButton } from '../../components/SwarmButton'
|
|
||||||
import { ROUTES } from '../../routes'
|
|
||||||
import { Context as BeeContext } from '../../providers/Bee'
|
|
||||||
import { Context as TopUpContext } from '../../providers/TopUp'
|
|
||||||
import { useIsBeeDesktop } from '../../hooks/apiHooks'
|
|
||||||
import { BeeModes } from '@ethersphere/bee-js'
|
|
||||||
import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop'
|
|
||||||
import { Loading } from '../../components/Loading'
|
|
||||||
import { useSnackbar } from 'notistack'
|
|
||||||
|
|
||||||
const useStyles = makeStyles(() =>
|
|
||||||
createStyles({
|
|
||||||
checkWrapper: {
|
|
||||||
background: 'rgba(0, 230, 118, 0.25)',
|
|
||||||
borderRadius: 99999,
|
|
||||||
width: '180px',
|
|
||||||
height: '180px',
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
justifyContent: 'center',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
const MINIMUM_XDAI = '0.05'
|
|
||||||
const MINIMUM_XBZZ = '0.1'
|
|
||||||
|
|
||||||
export default function Confirmation(): ReactElement {
|
|
||||||
const navigate = useNavigate()
|
|
||||||
const styles = useStyles()
|
|
||||||
const { isBeeDesktop } = useIsBeeDesktop()
|
|
||||||
const { balance, nodeInfo } = useContext(BeeContext)
|
|
||||||
const { providerUrl } = useContext(TopUpContext)
|
|
||||||
const [loading, setLoading] = useState(false)
|
|
||||||
const { enqueueSnackbar } = useSnackbar()
|
|
||||||
|
|
||||||
const canUpgradeToLightNode =
|
|
||||||
isBeeDesktop &&
|
|
||||||
nodeInfo?.beeMode === BeeModes.ULTRA_LIGHT &&
|
|
||||||
balance?.dai.toDecimal.gte(MINIMUM_XDAI) &&
|
|
||||||
balance?.bzz.toDecimal.gte(MINIMUM_XBZZ)
|
|
||||||
|
|
||||||
async function restart() {
|
|
||||||
if (!providerUrl) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
setLoading(true)
|
|
||||||
try {
|
|
||||||
await upgradeToLightNode(providerUrl)
|
|
||||||
await restartBeeNode()
|
|
||||||
enqueueSnackbar('Upgraded to light node', { variant: 'success' })
|
|
||||||
navigate(ROUTES.RESTART_LIGHT)
|
|
||||||
} catch (error) {
|
|
||||||
enqueueSnackbar(`Failed to upgrade: ${error}`, { variant: 'error' })
|
|
||||||
}
|
|
||||||
setLoading(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!balance) {
|
|
||||||
return <Loading />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<HistoryHeader>Connect to the blockchain</HistoryHeader>
|
|
||||||
<Grid container direction="column" alignItems="center">
|
|
||||||
<Box mb={6}>
|
|
||||||
<div className={styles.checkWrapper}>
|
|
||||||
<Check size={100} color="#ededed" />
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
<Box mb={1}>
|
|
||||||
<Typography style={{ fontWeight: 'bold' }}>Your node's RPC endpoint is set up correctly!</Typography>
|
|
||||||
</Box>
|
|
||||||
<Box mb={4}>
|
|
||||||
<Typography align="center">Lastly, you will need to top-up your node wallet.</Typography>
|
|
||||||
<Typography align="center">
|
|
||||||
If you're not familiar with cryptocurrencies, you can start with a bank card.
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<ExpandableListItemActions>
|
|
||||||
<SwarmButton iconType={Gift} onClick={() => navigate(ROUTES.TOP_UP_GIFT_CODE)}>
|
|
||||||
Use a gift code
|
|
||||||
</SwarmButton>
|
|
||||||
<SwarmButton iconType={MoneyDollarCircle} onClick={() => navigate(ROUTES.TOP_UP_CRYPTO)}>
|
|
||||||
Use DAI
|
|
||||||
</SwarmButton>
|
|
||||||
<SwarmButton iconType={BankCard} onClick={() => navigate(ROUTES.TOP_UP_BANK_CARD)}>
|
|
||||||
Get started with bank card
|
|
||||||
</SwarmButton>
|
|
||||||
</ExpandableListItemActions>
|
|
||||||
{canUpgradeToLightNode && (
|
|
||||||
<>
|
|
||||||
<Box mt={8} mb={2}>
|
|
||||||
<Typography align="center">
|
|
||||||
It seems that you have enough balance to upgrade your bee node to light node. By upgrading you will gain
|
|
||||||
access to file upload and faster downloads.
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<ExpandableListItemActions>
|
|
||||||
<SwarmButton iconType={Check} onClick={restart} disabled={loading} loading={loading}>
|
|
||||||
Upgrade now
|
|
||||||
</SwarmButton>
|
|
||||||
<div />
|
|
||||||
</ExpandableListItemActions>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Grid>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import { Box, Typography } from '@material-ui/core'
|
|
||||||
import { useSnackbar } from 'notistack'
|
|
||||||
import { ReactElement, useContext, useState } from 'react'
|
|
||||||
import Check from 'remixicon-react/CheckLineIcon'
|
|
||||||
import { useNavigate } from 'react-router'
|
|
||||||
import { providers } from 'ethers'
|
|
||||||
import { HistoryHeader } from '../../components/HistoryHeader'
|
|
||||||
import { SwarmButton } from '../../components/SwarmButton'
|
|
||||||
import { SwarmTextInput } from '../../components/SwarmTextInput'
|
|
||||||
import { Context } from '../../providers/TopUp'
|
|
||||||
import { ROUTES } from '../../routes'
|
|
||||||
import { Rpc } from '../../utils/rpc'
|
|
||||||
|
|
||||||
export default function Index(): ReactElement {
|
|
||||||
const { providerUrl, setProviderUrl } = useContext(Context)
|
|
||||||
const [localProviderUrl, setLocalProviderUrl] = useState(providerUrl)
|
|
||||||
|
|
||||||
const { enqueueSnackbar } = useSnackbar()
|
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
async function onSubmit() {
|
|
||||||
if (!localProviderUrl) return
|
|
||||||
|
|
||||||
try {
|
|
||||||
await Rpc.eth_getBlockByNumber(new providers.JsonRpcProvider(localProviderUrl))
|
|
||||||
enqueueSnackbar('Connected to RPC provider successfully.', { variant: 'success' })
|
|
||||||
setProviderUrl(localProviderUrl)
|
|
||||||
navigate(ROUTES.CONFIRMATION)
|
|
||||||
} catch (error) {
|
|
||||||
enqueueSnackbar('Could not connect to RPC provider.', { variant: 'error' })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<HistoryHeader>Connect to the blockchain</HistoryHeader>
|
|
||||||
<Box mb={1}>
|
|
||||||
<Typography style={{ fontWeight: 'bold' }}>Set up RPC endpoint</Typography>
|
|
||||||
</Box>
|
|
||||||
<Box mb={4}>
|
|
||||||
<Typography>
|
|
||||||
To connect to and retrieve data from the blockchain, you'll need to connect to a publicly-provided node
|
|
||||||
via the node's RPC endpoint. If you're not familiar with this, please read{' '}
|
|
||||||
<a
|
|
||||||
href="https://medium.com/ethereum-swarm/upgrading-swarm-deskotp-app-beta-from-an-ultra-light-to-a-light-node-65d52cab7f2c"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
this guide
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</Typography>
|
|
||||||
</Box>
|
|
||||||
<Box mb={2}>
|
|
||||||
<SwarmTextInput
|
|
||||||
name="rpc-endpoint"
|
|
||||||
label="RPC Endpoint"
|
|
||||||
onChange={event => setLocalProviderUrl(event.target.value)}
|
|
||||||
defaultValue={providerUrl || ''}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
<SwarmButton iconType={Check} onClick={onSubmit}>
|
|
||||||
Connect
|
|
||||||
</SwarmButton>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -2,11 +2,27 @@ import CircularProgress from '@material-ui/core/CircularProgress'
|
|||||||
import { ReactElement, useContext } from 'react'
|
import { ReactElement, useContext } from 'react'
|
||||||
import ExpandableList from '../../components/ExpandableList'
|
import ExpandableList from '../../components/ExpandableList'
|
||||||
import ExpandableListItemInput from '../../components/ExpandableListItemInput'
|
import ExpandableListItemInput from '../../components/ExpandableListItemInput'
|
||||||
|
import { Context as BeeContext } from '../../providers/Bee'
|
||||||
import { Context as SettingsContext } from '../../providers/Settings'
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
|
import { useSnackbar } from 'notistack'
|
||||||
|
|
||||||
export default function SettingsPage(): ReactElement {
|
export default function SettingsPage(): ReactElement {
|
||||||
const { apiUrl, apiDebugUrl, setApiUrl, setDebugApiUrl, lockedApiSettings, config, isLoading } =
|
const {
|
||||||
useContext(SettingsContext)
|
apiUrl,
|
||||||
|
apiDebugUrl,
|
||||||
|
setApiUrl,
|
||||||
|
setDebugApiUrl,
|
||||||
|
lockedApiSettings,
|
||||||
|
cors,
|
||||||
|
dataDir,
|
||||||
|
ensResolver,
|
||||||
|
providerUrl,
|
||||||
|
isLoading,
|
||||||
|
isBeeDesktop,
|
||||||
|
setAndPersistJsonRpcProvider,
|
||||||
|
} = useContext(SettingsContext)
|
||||||
|
const { refresh } = useContext(BeeContext)
|
||||||
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return (
|
return (
|
||||||
@@ -16,31 +32,46 @@ export default function SettingsPage(): ReactElement {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run within Bee Desktop, display read only config
|
|
||||||
if (config) {
|
|
||||||
return (
|
|
||||||
<ExpandableList label="Bee Desktop Settings" defaultOpen>
|
|
||||||
<ExpandableListItemInput label="Bee API" value={config['api-addr']} locked />
|
|
||||||
<ExpandableListItemInput label="Bee Debug API" value={config['debug-api-addr']} locked />
|
|
||||||
<ExpandableListItemInput label="CORS" value={config['cors-allowed-origins']} locked />
|
|
||||||
<ExpandableListItemInput label="Data DIR" value={config['data-dir']} locked />
|
|
||||||
<ExpandableListItemInput label="ENS resolver URL" value={config['resolver-options']} locked />
|
|
||||||
{config['swap-endpoint'] && (
|
|
||||||
<ExpandableListItemInput label="SWAP endpoint" value={config['swap-endpoint']} locked />
|
|
||||||
)}
|
|
||||||
</ExpandableList>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ExpandableList label="API Settings" defaultOpen>
|
<>
|
||||||
<ExpandableListItemInput label="Bee API" value={apiUrl} onConfirm={setApiUrl} locked={lockedApiSettings} />
|
<ExpandableList label="API Settings" defaultOpen>
|
||||||
<ExpandableListItemInput
|
<ExpandableListItemInput
|
||||||
label="Bee Debug API"
|
label="Bee API"
|
||||||
value={apiDebugUrl}
|
value={apiUrl}
|
||||||
onConfirm={setDebugApiUrl}
|
onConfirm={setApiUrl}
|
||||||
locked={lockedApiSettings}
|
locked={lockedApiSettings || isBeeDesktop}
|
||||||
/>
|
/>
|
||||||
</ExpandableList>
|
<ExpandableListItemInput
|
||||||
|
label="Bee Debug API"
|
||||||
|
value={apiDebugUrl}
|
||||||
|
onConfirm={setDebugApiUrl}
|
||||||
|
locked={lockedApiSettings || isBeeDesktop}
|
||||||
|
/>
|
||||||
|
<ExpandableListItemInput
|
||||||
|
label="Blockchain RPC URL"
|
||||||
|
value={providerUrl}
|
||||||
|
helperText="Changing the value will restart your bee node."
|
||||||
|
confirmLabel="Save and restart"
|
||||||
|
onConfirm={value => {
|
||||||
|
setAndPersistJsonRpcProvider(value)
|
||||||
|
.then(() => {
|
||||||
|
refresh()
|
||||||
|
enqueueSnackbar('Settings changed, restarting bee node...', { variant: 'success' })
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.log(error) //eslint-disable-line
|
||||||
|
enqueueSnackbar(`Failed to change RPC endpoint. Error: ${error}`, { variant: 'success' })
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ExpandableList>
|
||||||
|
{isBeeDesktop && (
|
||||||
|
<ExpandableList label="Desktop Settings" defaultOpen>
|
||||||
|
<ExpandableListItemInput label="CORS" value={cors ?? '-'} locked />
|
||||||
|
<ExpandableListItemInput label="Data DIR" value={dataDir ?? '-'} locked />
|
||||||
|
<ExpandableListItemInput label="ENS resolver URL" value={ensResolver ?? '-'} locked />
|
||||||
|
</ExpandableList>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { PostageBatchOptions } from '@ethersphere/bee-js'
|
||||||
import { Box, Grid, Typography } from '@material-ui/core'
|
import { Box, Grid, Typography } from '@material-ui/core'
|
||||||
import BigNumber from 'bignumber.js'
|
import BigNumber from 'bignumber.js'
|
||||||
import { Form, Formik, FormikHelpers } from 'formik'
|
import { Form, Formik, FormikHelpers } from 'formik'
|
||||||
@@ -102,13 +103,14 @@ export function PostageStampCreation({ onFinished }: Props): ReactElement {
|
|||||||
|
|
||||||
const amount = BigInt(values.amount)
|
const amount = BigInt(values.amount)
|
||||||
const depth = Number.parseInt(values.depth)
|
const depth = Number.parseInt(values.depth)
|
||||||
const options = values.label ? { label: values.label } : undefined
|
const options: PostageBatchOptions = { waitForUsable: false, label: values.label || undefined }
|
||||||
const batchId = await beeDebugApi.createPostageBatch(amount.toString(), depth, options)
|
const batchId = await beeDebugApi.createPostageBatch(amount.toString(), depth, options)
|
||||||
await waitUntilStampExists(batchId, beeDebugApi)
|
await waitUntilStampExists(batchId, beeDebugApi)
|
||||||
actions.resetForm()
|
actions.resetForm()
|
||||||
await refresh()
|
await refresh()
|
||||||
onFinished()
|
onFinished()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error(e) // eslint-disable-line
|
||||||
enqueueSnackbar(`Error: ${(e as Error).message}`, { variant: 'error' })
|
enqueueSnackbar(`Error: ${(e as Error).message}`, { variant: 'error' })
|
||||||
actions.setSubmitting(false)
|
actions.setSubmitting(false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { Box, Grid, Typography } from '@material-ui/core'
|
||||||
|
import { ReactElement, useContext } from 'react'
|
||||||
|
import { useNavigate } from 'react-router'
|
||||||
|
import Check from 'remixicon-react/CheckLineIcon'
|
||||||
|
import ExpandableListItem from '../../components/ExpandableListItem'
|
||||||
|
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
|
||||||
|
import { HistoryHeader } from '../../components/HistoryHeader'
|
||||||
|
import { Loading } from '../../components/Loading'
|
||||||
|
import { SwarmButton } from '../../components/SwarmButton'
|
||||||
|
import { SwarmDivider } from '../../components/SwarmDivider'
|
||||||
|
import { Context } from '../../providers/Bee'
|
||||||
|
import { Context as BalanceProvider } from '../../providers/WalletBalance'
|
||||||
|
import { TopUpProgressIndicator } from './TopUpProgressIndicator'
|
||||||
|
|
||||||
|
const MINIMUM_XDAI = '0.5'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
header: string
|
||||||
|
title: string
|
||||||
|
p: ReactElement
|
||||||
|
next: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Index({ header, title, p, next }: Props): ReactElement {
|
||||||
|
const { nodeAddresses } = useContext(Context)
|
||||||
|
const { balance } = useContext(BalanceProvider)
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
|
if (!balance || !nodeAddresses) {
|
||||||
|
return <Loading />
|
||||||
|
}
|
||||||
|
|
||||||
|
const disabled = balance.dai.toDecimal.lt(MINIMUM_XDAI)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<HistoryHeader>{header}</HistoryHeader>
|
||||||
|
<Box mb={4}>
|
||||||
|
<TopUpProgressIndicator index={0} />
|
||||||
|
</Box>
|
||||||
|
<Box mb={2}>
|
||||||
|
<Typography style={{ fontWeight: 'bold' }}>{title}</Typography>
|
||||||
|
</Box>
|
||||||
|
<Box mb={4}>{p}</Box>
|
||||||
|
<SwarmDivider mb={4} />
|
||||||
|
<Box mb={0.25}>
|
||||||
|
<ExpandableListItemKey label="Funding wallet address" value={nodeAddresses.ethereum} expanded />
|
||||||
|
</Box>
|
||||||
|
<Box mb={4}>
|
||||||
|
<ExpandableListItem label="xDAI balance" value={balance.dai.toSignificantDigits(4)} />
|
||||||
|
</Box>
|
||||||
|
<Grid container direction="row" justifyContent="space-between">
|
||||||
|
<SwarmButton iconType={Check} onClick={() => navigate(next)} disabled={disabled}>
|
||||||
|
Proceed
|
||||||
|
</SwarmButton>
|
||||||
|
{disabled ? (
|
||||||
|
<Typography>Please deposit at least {MINIMUM_XDAI} xDAI to the address above in order to proceed.</Typography>
|
||||||
|
) : null}
|
||||||
|
</Grid>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Typography } from '@material-ui/core'
|
import { Typography } from '@material-ui/core'
|
||||||
import { ReactElement } from 'react'
|
import { ReactElement } from 'react'
|
||||||
import Index from '.'
|
import Balance from './Balance'
|
||||||
import { ROUTES } from '../../routes'
|
import { ROUTES } from '../../routes'
|
||||||
|
|
||||||
export function BankCardTopUpIndex(): ReactElement {
|
export function BankCardTopUpIndex(): ReactElement {
|
||||||
return (
|
return (
|
||||||
<Index
|
<Balance
|
||||||
header={'Top-up with bank card'}
|
header={'Top-up with bank card'}
|
||||||
title={'Use a bank card to buy xDAI to the funding wallet address below'}
|
title={'Use a bank card to buy xDAI to the funding wallet address below'}
|
||||||
p={
|
p={
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import { Typography } from '@material-ui/core'
|
import { Typography } from '@material-ui/core'
|
||||||
import { ReactElement } from 'react'
|
import { ReactElement } from 'react'
|
||||||
import Index from '.'
|
import Balance from './Balance'
|
||||||
import { ROUTES } from '../../routes'
|
import { ROUTES } from '../../routes'
|
||||||
|
|
||||||
export function CryptoTopUpIndex(): ReactElement {
|
export function CryptoTopUpIndex(): ReactElement {
|
||||||
return (
|
return (
|
||||||
<Index
|
<Balance
|
||||||
header={'Top-up with cryptocurrencies'}
|
header={'Top-up with cryptocurrencies'}
|
||||||
title={'Send xDAI to the funding wallet below'}
|
title={'Send xDAI to the funding wallet below'}
|
||||||
p={
|
p={
|
||||||
|
|||||||
@@ -12,18 +12,18 @@ import { ProgressIndicator } from '../../components/ProgressIndicator'
|
|||||||
import { SwarmButton } from '../../components/SwarmButton'
|
import { SwarmButton } from '../../components/SwarmButton'
|
||||||
import { SwarmDivider } from '../../components/SwarmDivider'
|
import { SwarmDivider } from '../../components/SwarmDivider'
|
||||||
import { Context as BeeContext } from '../../providers/Bee'
|
import { Context as BeeContext } from '../../providers/Bee'
|
||||||
import { Context as TopUpContext } from '../../providers/TopUp'
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
|
import { Context as BalanceProvider } from '../../providers/WalletBalance'
|
||||||
import { ROUTES } from '../../routes'
|
import { ROUTES } from '../../routes'
|
||||||
import { sleepMs } from '../../utils'
|
import { sleepMs } from '../../utils'
|
||||||
import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop'
|
import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop'
|
||||||
import { ResolvedWallet } from '../../utils/wallet'
|
import { ResolvedWallet } from '../../utils/wallet'
|
||||||
import { useIsBeeDesktop } from '../../hooks/apiHooks'
|
|
||||||
import { BeeModes } from '@ethersphere/bee-js'
|
import { BeeModes } from '@ethersphere/bee-js'
|
||||||
|
|
||||||
export function GiftCardFund(): ReactElement {
|
export function GiftCardFund(): ReactElement {
|
||||||
const { isBeeDesktop } = useIsBeeDesktop()
|
const { nodeAddresses, nodeInfo } = useContext(BeeContext)
|
||||||
const { nodeAddresses, balance, nodeInfo } = useContext(BeeContext)
|
const { isBeeDesktop, provider, providerUrl } = useContext(SettingsContext)
|
||||||
const { provider, providerUrl } = useContext(TopUpContext)
|
const { balance } = useContext(BalanceProvider)
|
||||||
|
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [wallet, setWallet] = useState<ResolvedWallet | null>(null)
|
const [wallet, setWallet] = useState<ResolvedWallet | null>(null)
|
||||||
@@ -48,10 +48,6 @@ export function GiftCardFund(): ReactElement {
|
|||||||
const canUpgradeToLightNode = isBeeDesktop && nodeInfo?.beeMode === BeeModes.ULTRA_LIGHT
|
const canUpgradeToLightNode = isBeeDesktop && nodeInfo?.beeMode === BeeModes.ULTRA_LIGHT
|
||||||
|
|
||||||
async function restart() {
|
async function restart() {
|
||||||
if (!providerUrl) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sleepMs(5_000)
|
await sleepMs(5_000)
|
||||||
await upgradeToLightNode(providerUrl)
|
await upgradeToLightNode(providerUrl)
|
||||||
@@ -59,6 +55,7 @@ export function GiftCardFund(): ReactElement {
|
|||||||
enqueueSnackbar('Upgraded to light node', { variant: 'success' })
|
enqueueSnackbar('Upgraded to light node', { variant: 'success' })
|
||||||
navigate(ROUTES.RESTART_LIGHT)
|
navigate(ROUTES.RESTART_LIGHT)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error) // eslint-disable-line
|
||||||
enqueueSnackbar(`Failed to upgrade: ${error}`, { variant: 'error' })
|
enqueueSnackbar(`Failed to upgrade: ${error}`, { variant: 'error' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -76,6 +73,7 @@ export function GiftCardFund(): ReactElement {
|
|||||||
|
|
||||||
if (canUpgradeToLightNode) await restart()
|
if (canUpgradeToLightNode) await restart()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error) // eslint-disable-line
|
||||||
enqueueSnackbar(`Failed to fund: ${error}`, { variant: 'error' })
|
enqueueSnackbar(`Failed to fund: ${error}`, { variant: 'error' })
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useSnackbar } from 'notistack'
|
|||||||
import { ReactElement, useContext, useState } from 'react'
|
import { ReactElement, useContext, useState } from 'react'
|
||||||
import ArrowRight from 'remixicon-react/ArrowRightLineIcon'
|
import ArrowRight from 'remixicon-react/ArrowRightLineIcon'
|
||||||
import { useNavigate } from 'react-router'
|
import { useNavigate } from 'react-router'
|
||||||
import { Context as TopUpContext } from '../../providers/TopUp'
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
import { HistoryHeader } from '../../components/HistoryHeader'
|
import { HistoryHeader } from '../../components/HistoryHeader'
|
||||||
import { ProgressIndicator } from '../../components/ProgressIndicator'
|
import { ProgressIndicator } from '../../components/ProgressIndicator'
|
||||||
import { SwarmButton } from '../../components/SwarmButton'
|
import { SwarmButton } from '../../components/SwarmButton'
|
||||||
@@ -16,7 +16,7 @@ import { ROUTES } from '../../routes'
|
|||||||
import { Rpc } from '../../utils/rpc'
|
import { Rpc } from '../../utils/rpc'
|
||||||
|
|
||||||
export function GiftCardTopUpIndex(): ReactElement {
|
export function GiftCardTopUpIndex(): ReactElement {
|
||||||
const { provider } = useContext(TopUpContext)
|
const { provider } = useContext(SettingsContext)
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [giftCode, setGiftCode] = useState('')
|
const [giftCode, setGiftCode] = useState('')
|
||||||
|
|
||||||
@@ -38,6 +38,7 @@ export function GiftCardTopUpIndex(): ReactElement {
|
|||||||
enqueueSnackbar('Successfully verified gift wallet', { variant: 'success' })
|
enqueueSnackbar('Successfully verified gift wallet', { variant: 'success' })
|
||||||
navigate(ROUTES.TOP_UP_GIFT_CODE_FUND.replace(':privateKeyString', giftCode))
|
navigate(ROUTES.TOP_UP_GIFT_CODE_FUND.replace(':privateKeyString', giftCode))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error) // eslint-disable-line
|
||||||
enqueueSnackbar(`Gift wallet could not be verified: ${error}`, { variant: 'error' })
|
enqueueSnackbar(`Gift wallet could not be verified: ${error}`, { variant: 'error' })
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ import { Loading } from '../../components/Loading'
|
|||||||
import { SwarmButton } from '../../components/SwarmButton'
|
import { SwarmButton } from '../../components/SwarmButton'
|
||||||
import { SwarmDivider } from '../../components/SwarmDivider'
|
import { SwarmDivider } from '../../components/SwarmDivider'
|
||||||
import { SwarmTextInput } from '../../components/SwarmTextInput'
|
import { SwarmTextInput } from '../../components/SwarmTextInput'
|
||||||
import { useIsBeeDesktop } from '../../hooks/apiHooks'
|
|
||||||
import { BzzToken } from '../../models/BzzToken'
|
import { BzzToken } from '../../models/BzzToken'
|
||||||
import { DaiToken } from '../../models/DaiToken'
|
import { DaiToken } from '../../models/DaiToken'
|
||||||
|
import { Context as BalanceProvider } from '../../providers/WalletBalance'
|
||||||
import { Context as BeeContext } from '../../providers/Bee'
|
import { Context as BeeContext } from '../../providers/Bee'
|
||||||
import { Context as TopUpContext } from '../../providers/TopUp'
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
import { ROUTES } from '../../routes'
|
import { ROUTES } from '../../routes'
|
||||||
import { sleepMs } from '../../utils'
|
import { sleepMs } from '../../utils'
|
||||||
import { getBzzPriceAsDai, performSwap, restartBeeNode, upgradeToLightNode } from '../../utils/desktop'
|
import { getBzzPriceAsDai, performSwap, restartBeeNode, upgradeToLightNode } from '../../utils/desktop'
|
||||||
@@ -37,9 +37,9 @@ export function Swap({ header }: Props): ReactElement {
|
|||||||
const [userInputSwap, setUserInputSwap] = useState<string | null>(null)
|
const [userInputSwap, setUserInputSwap] = useState<string | null>(null)
|
||||||
const [price, setPrice] = useState(DaiToken.fromDecimal('0.6', 18))
|
const [price, setPrice] = useState(DaiToken.fromDecimal('0.6', 18))
|
||||||
|
|
||||||
const { providerUrl } = useContext(TopUpContext)
|
const { providerUrl, isBeeDesktop } = useContext(SettingsContext)
|
||||||
const { balance, nodeAddresses, nodeInfo } = useContext(BeeContext)
|
const { nodeAddresses, nodeInfo } = useContext(BeeContext)
|
||||||
const { isBeeDesktop } = useIsBeeDesktop()
|
const { balance } = useContext(BalanceProvider)
|
||||||
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { enqueueSnackbar } = useSnackbar()
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
@@ -78,10 +78,6 @@ export function Swap({ header }: Props): ReactElement {
|
|||||||
const canUpgradeToLightNode = isBeeDesktop && nodeInfo?.beeMode === BeeModes.ULTRA_LIGHT
|
const canUpgradeToLightNode = isBeeDesktop && nodeInfo?.beeMode === BeeModes.ULTRA_LIGHT
|
||||||
|
|
||||||
async function restart() {
|
async function restart() {
|
||||||
if (!providerUrl) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await sleepMs(5_000)
|
await sleepMs(5_000)
|
||||||
await upgradeToLightNode(providerUrl)
|
await upgradeToLightNode(providerUrl)
|
||||||
@@ -89,12 +85,13 @@ export function Swap({ header }: Props): ReactElement {
|
|||||||
enqueueSnackbar('Upgraded to light node', { variant: 'success' })
|
enqueueSnackbar('Upgraded to light node', { variant: 'success' })
|
||||||
navigate(ROUTES.RESTART_LIGHT)
|
navigate(ROUTES.RESTART_LIGHT)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error) // eslint-disable-line
|
||||||
enqueueSnackbar(`Failed to upgrade: ${error}`, { variant: 'error' })
|
enqueueSnackbar(`Failed to upgrade: ${error}`, { variant: 'error' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onSwap() {
|
async function onSwap() {
|
||||||
if (hasSwapped || !providerUrl) {
|
if (hasSwapped) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
@@ -105,6 +102,7 @@ export function Swap({ header }: Props): ReactElement {
|
|||||||
|
|
||||||
if (canUpgradeToLightNode) await restart()
|
if (canUpgradeToLightNode) await restart()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.error(error) // eslint-disable-line
|
||||||
enqueueSnackbar(`Failed to swap: ${error}`, { variant: 'error' })
|
enqueueSnackbar(`Failed to swap: ${error}`, { variant: 'error' })
|
||||||
} finally {
|
} finally {
|
||||||
balance?.refresh()
|
balance?.refresh()
|
||||||
|
|||||||
@@ -1,59 +1,122 @@
|
|||||||
import { Box, Grid, Typography } from '@material-ui/core'
|
import { Box, createStyles, Grid, makeStyles, Typography } from '@material-ui/core'
|
||||||
import { ReactElement, useContext } from 'react'
|
import { ReactElement, useContext, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router'
|
|
||||||
import Check from 'remixicon-react/CheckLineIcon'
|
import Check from 'remixicon-react/CheckLineIcon'
|
||||||
import ExpandableListItem from '../../components/ExpandableListItem'
|
import Download from 'remixicon-react/DownloadLineIcon'
|
||||||
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
|
import BankCard from 'remixicon-react/BankCard2LineIcon'
|
||||||
|
import MoneyDollarCircle from 'remixicon-react/MoneyDollarCircleLineIcon'
|
||||||
|
import Gift from 'remixicon-react/GiftLineIcon'
|
||||||
|
import { useNavigate } from 'react-router'
|
||||||
|
import ExpandableListItemActions from '../../components/ExpandableListItemActions'
|
||||||
import { HistoryHeader } from '../../components/HistoryHeader'
|
import { HistoryHeader } from '../../components/HistoryHeader'
|
||||||
import { Loading } from '../../components/Loading'
|
|
||||||
import { SwarmButton } from '../../components/SwarmButton'
|
import { SwarmButton } from '../../components/SwarmButton'
|
||||||
import { SwarmDivider } from '../../components/SwarmDivider'
|
import { ROUTES } from '../../routes'
|
||||||
import { Context } from '../../providers/Bee'
|
import { CheckState, Context as BeeContext } from '../../providers/Bee'
|
||||||
import { TopUpProgressIndicator } from './TopUpProgressIndicator'
|
import { Context as SettingsContext } from '../../providers/Settings'
|
||||||
|
import { Context as BalanceProvider } from '../../providers/WalletBalance'
|
||||||
|
import { BeeModes } from '@ethersphere/bee-js'
|
||||||
|
import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop'
|
||||||
|
import { Loading } from '../../components/Loading'
|
||||||
|
import { useSnackbar } from 'notistack'
|
||||||
|
import TroubleshootConnectionCard from '../../components/TroubleshootConnectionCard'
|
||||||
|
|
||||||
const MINIMUM_XDAI = '0.5'
|
const useStyles = makeStyles(() =>
|
||||||
|
createStyles({
|
||||||
|
checkWrapper: {
|
||||||
|
background: 'rgba(0, 230, 118, 0.25)',
|
||||||
|
borderRadius: 99999,
|
||||||
|
width: '180px',
|
||||||
|
height: '180px',
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
interface Props {
|
const MINIMUM_XDAI = '0.05'
|
||||||
header: string
|
const MINIMUM_XBZZ = '0.1'
|
||||||
title: string
|
|
||||||
p: ReactElement
|
|
||||||
next: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Index({ header, title, p, next }: Props): ReactElement {
|
export default function TopUp(): ReactElement {
|
||||||
const { nodeAddresses, balance } = useContext(Context)
|
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
|
const styles = useStyles()
|
||||||
|
const { isBeeDesktop } = useContext(SettingsContext)
|
||||||
|
const { nodeInfo, status } = useContext(BeeContext)
|
||||||
|
const { balance } = useContext(BalanceProvider)
|
||||||
|
const { providerUrl } = useContext(SettingsContext)
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const { enqueueSnackbar } = useSnackbar()
|
||||||
|
|
||||||
if (!balance || !nodeAddresses) {
|
const canUpgradeToLightNode =
|
||||||
|
isBeeDesktop &&
|
||||||
|
nodeInfo?.beeMode === BeeModes.ULTRA_LIGHT &&
|
||||||
|
balance?.dai.toDecimal.gte(MINIMUM_XDAI) &&
|
||||||
|
balance?.bzz.toDecimal.gte(MINIMUM_XBZZ)
|
||||||
|
|
||||||
|
async function restart() {
|
||||||
|
setLoading(true)
|
||||||
|
try {
|
||||||
|
await upgradeToLightNode(providerUrl)
|
||||||
|
await restartBeeNode()
|
||||||
|
enqueueSnackbar('Upgraded to light node', { variant: 'success' })
|
||||||
|
navigate(ROUTES.RESTART_LIGHT)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error) // eslint-disable-line
|
||||||
|
enqueueSnackbar(`Failed to upgrade: ${error}`, { variant: 'error' })
|
||||||
|
}
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status.all === CheckState.ERROR) return <TroubleshootConnectionCard />
|
||||||
|
|
||||||
|
if (!balance) {
|
||||||
return <Loading />
|
return <Loading />
|
||||||
}
|
}
|
||||||
|
|
||||||
const disabled = balance.dai.toDecimal.lt(MINIMUM_XDAI)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<HistoryHeader>{header}</HistoryHeader>
|
<HistoryHeader>Account</HistoryHeader>
|
||||||
<Box mb={4}>
|
<Grid container direction="column" alignItems="center">
|
||||||
<TopUpProgressIndicator index={0} />
|
<Box mb={6}>
|
||||||
</Box>
|
<div className={styles.checkWrapper}>
|
||||||
<Box mb={2}>
|
<Download size={100} color="#ededed" />
|
||||||
<Typography style={{ fontWeight: 'bold' }}>{title}</Typography>
|
</div>
|
||||||
</Box>
|
</Box>
|
||||||
<Box mb={4}>{p}</Box>
|
<Box mb={1}>
|
||||||
<SwarmDivider mb={4} />
|
<Typography style={{ fontWeight: 'bold' }}>Transfer funds to your Swarm account</Typography>
|
||||||
<Box mb={0.25}>
|
</Box>
|
||||||
<ExpandableListItemKey label="Funding wallet address" value={nodeAddresses.ethereum} expanded />
|
<Box mb={4}>
|
||||||
</Box>
|
<Typography align="center">Top up your account with xBZZ and xDAI.</Typography>
|
||||||
<Box mb={4}>
|
<Typography align="center">
|
||||||
<ExpandableListItem label="xDAI balance" value={balance.dai.toSignificantDigits(4)} />
|
If you're not familiar with cryptocurrencies, you can start with a bank card.
|
||||||
</Box>
|
</Typography>
|
||||||
<Grid container direction="row" justifyContent="space-between">
|
</Box>
|
||||||
<SwarmButton iconType={Check} onClick={() => navigate(next)} disabled={disabled}>
|
<ExpandableListItemActions>
|
||||||
Proceed
|
<SwarmButton iconType={Gift} onClick={() => navigate(ROUTES.TOP_UP_GIFT_CODE)}>
|
||||||
</SwarmButton>
|
Use a gift code
|
||||||
{disabled ? (
|
</SwarmButton>
|
||||||
<Typography>Please deposit at least {MINIMUM_XDAI} xDAI to the address above in order to proceed.</Typography>
|
<SwarmButton iconType={MoneyDollarCircle} onClick={() => navigate(ROUTES.TOP_UP_CRYPTO)}>
|
||||||
) : null}
|
Use xDAI
|
||||||
|
</SwarmButton>
|
||||||
|
<SwarmButton iconType={BankCard} onClick={() => navigate(ROUTES.TOP_UP_BANK_CARD)}>
|
||||||
|
Get started with bank card
|
||||||
|
</SwarmButton>
|
||||||
|
</ExpandableListItemActions>
|
||||||
|
{canUpgradeToLightNode && (
|
||||||
|
<>
|
||||||
|
<Box mt={8} mb={2}>
|
||||||
|
<Typography align="center">
|
||||||
|
It seems that you have enough balance to upgrade your bee node to light node. By upgrading you will gain
|
||||||
|
access to file upload and faster downloads.
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
<ExpandableListItemActions>
|
||||||
|
<SwarmButton iconType={Check} onClick={restart} disabled={loading} loading={loading}>
|
||||||
|
Upgrade now
|
||||||
|
</SwarmButton>
|
||||||
|
<div />
|
||||||
|
</ExpandableListItemActions>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,9 +15,7 @@ import PackageJson from '../../package.json'
|
|||||||
import { useLatestBeeRelease } from '../hooks/apiHooks'
|
import { useLatestBeeRelease } from '../hooks/apiHooks'
|
||||||
import { Token } from '../models/Token'
|
import { Token } from '../models/Token'
|
||||||
import type { Balance, ChequebookBalance, Settlements } from '../types'
|
import type { Balance, ChequebookBalance, Settlements } from '../types'
|
||||||
import { WalletAddress } from '../utils/wallet'
|
|
||||||
import { Context as SettingsContext } from './Settings'
|
import { Context as SettingsContext } from './Settings'
|
||||||
import { Context as TopUpContext } from './TopUp'
|
|
||||||
|
|
||||||
const REFRESH_WHEN_OK = 30_000
|
const REFRESH_WHEN_OK = 30_000
|
||||||
const REFRESH_WHEN_ERROR = 5_000
|
const REFRESH_WHEN_ERROR = 5_000
|
||||||
@@ -46,7 +44,6 @@ interface Status {
|
|||||||
|
|
||||||
interface ContextInterface {
|
interface ContextInterface {
|
||||||
status: Status
|
status: Status
|
||||||
balance: WalletAddress | null
|
|
||||||
latestPublishedVersion?: string
|
latestPublishedVersion?: string
|
||||||
latestUserVersion?: string
|
latestUserVersion?: string
|
||||||
latestUserVersionExact?: string
|
latestUserVersionExact?: string
|
||||||
@@ -65,6 +62,7 @@ interface ContextInterface {
|
|||||||
peerCheques: LastChequesResponse | null
|
peerCheques: LastChequesResponse | null
|
||||||
settlements: Settlements | null
|
settlements: Settlements | null
|
||||||
chainState: ChainState | null
|
chainState: ChainState | null
|
||||||
|
chainId: number | null
|
||||||
latestBeeRelease: LatestBeeRelease | null
|
latestBeeRelease: LatestBeeRelease | null
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
lastUpdate: number | null
|
lastUpdate: number | null
|
||||||
@@ -83,7 +81,6 @@ const initialValues: ContextInterface = {
|
|||||||
topology: { isEnabled: false, checkState: CheckState.ERROR },
|
topology: { isEnabled: false, checkState: CheckState.ERROR },
|
||||||
chequebook: { isEnabled: false, checkState: CheckState.ERROR },
|
chequebook: { isEnabled: false, checkState: CheckState.ERROR },
|
||||||
},
|
},
|
||||||
balance: null,
|
|
||||||
latestPublishedVersion: undefined,
|
latestPublishedVersion: undefined,
|
||||||
latestUserVersion: undefined,
|
latestUserVersion: undefined,
|
||||||
latestUserVersionExact: undefined,
|
latestUserVersionExact: undefined,
|
||||||
@@ -102,6 +99,7 @@ const initialValues: ContextInterface = {
|
|||||||
peerCheques: null,
|
peerCheques: null,
|
||||||
settlements: null,
|
settlements: null,
|
||||||
chainState: null,
|
chainState: null,
|
||||||
|
chainId: null,
|
||||||
latestBeeRelease: null,
|
latestBeeRelease: null,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
lastUpdate: null,
|
lastUpdate: null,
|
||||||
@@ -190,7 +188,6 @@ let isRefreshing = false
|
|||||||
|
|
||||||
export function Provider({ children }: Props): ReactElement {
|
export function Provider({ children }: Props): ReactElement {
|
||||||
const { beeApi, beeDebugApi } = useContext(SettingsContext)
|
const { beeApi, beeDebugApi } = useContext(SettingsContext)
|
||||||
const { provider } = useContext(TopUpContext)
|
|
||||||
const [apiHealth, setApiHealth] = useState<boolean>(false)
|
const [apiHealth, setApiHealth] = useState<boolean>(false)
|
||||||
const [debugApiHealth, setDebugApiHealth] = useState<Health | null>(null)
|
const [debugApiHealth, setDebugApiHealth] = useState<Health | null>(null)
|
||||||
const [nodeAddresses, setNodeAddresses] = useState<NodeAddresses | null>(null)
|
const [nodeAddresses, setNodeAddresses] = useState<NodeAddresses | null>(null)
|
||||||
@@ -203,7 +200,7 @@ export function Provider({ children }: Props): ReactElement {
|
|||||||
const [peerCheques, setPeerCheques] = useState<LastChequesResponse | null>(null)
|
const [peerCheques, setPeerCheques] = useState<LastChequesResponse | null>(null)
|
||||||
const [settlements, setSettlements] = useState<Settlements | null>(null)
|
const [settlements, setSettlements] = useState<Settlements | null>(null)
|
||||||
const [chainState, setChainState] = useState<ChainState | null>(null)
|
const [chainState, setChainState] = useState<ChainState | null>(null)
|
||||||
const [walletAddress, setWalletAddress] = useState<WalletAddress | null>(initialValues.balance)
|
const [chainId, setChainId] = useState<number | null>(null)
|
||||||
|
|
||||||
const { latestBeeRelease } = useLatestBeeRelease()
|
const { latestBeeRelease } = useLatestBeeRelease()
|
||||||
|
|
||||||
@@ -242,18 +239,6 @@ export function Provider({ children }: Props): ReactElement {
|
|||||||
if (beeDebugApi !== null) refresh()
|
if (beeDebugApi !== null) refresh()
|
||||||
}, [beeDebugApi]) // eslint-disable-line react-hooks/exhaustive-deps
|
}, [beeDebugApi]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (nodeAddresses?.ethereum && provider) {
|
|
||||||
WalletAddress.make(nodeAddresses.ethereum, provider).then(setWalletAddress)
|
|
||||||
}
|
|
||||||
}, [nodeAddresses, provider])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const interval = setInterval(() => walletAddress?.refresh().then(setWalletAddress), REFRESH_WHEN_OK)
|
|
||||||
|
|
||||||
return () => clearInterval(interval)
|
|
||||||
}, [walletAddress])
|
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
// Don't want to refresh when already refreshing
|
// Don't want to refresh when already refreshing
|
||||||
if (isRefreshing) return
|
if (isRefreshing) return
|
||||||
@@ -356,6 +341,12 @@ export function Provider({ children }: Props): ReactElement {
|
|||||||
.then(setChainState)
|
.then(setChainState)
|
||||||
.catch(() => setChainState(null)),
|
.catch(() => setChainState(null)),
|
||||||
|
|
||||||
|
// Wallet
|
||||||
|
beeDebugApi
|
||||||
|
.getWalletBalance({ timeout: TIMEOUT })
|
||||||
|
.then(({ chainID }) => setChainId(chainID))
|
||||||
|
.catch(() => setChainId(null)),
|
||||||
|
|
||||||
// Chequebook balance
|
// Chequebook balance
|
||||||
chequeBalanceWrapper()
|
chequeBalanceWrapper()
|
||||||
.then(setChequebookBalance)
|
.then(setChequebookBalance)
|
||||||
@@ -421,7 +412,6 @@ export function Provider({ children }: Props): ReactElement {
|
|||||||
<Context.Provider
|
<Context.Provider
|
||||||
value={{
|
value={{
|
||||||
status,
|
status,
|
||||||
balance: walletAddress,
|
|
||||||
latestUserVersion,
|
latestUserVersion,
|
||||||
latestUserVersionExact,
|
latestUserVersionExact,
|
||||||
latestPublishedVersion,
|
latestPublishedVersion,
|
||||||
@@ -446,6 +436,7 @@ export function Provider({ children }: Props): ReactElement {
|
|||||||
peerCheques,
|
peerCheques,
|
||||||
settlements,
|
settlements,
|
||||||
chainState,
|
chainState,
|
||||||
|
chainId,
|
||||||
latestBeeRelease,
|
latestBeeRelease,
|
||||||
isLoading,
|
isLoading,
|
||||||
lastUpdate,
|
lastUpdate,
|
||||||
|
|||||||
@@ -1,32 +1,52 @@
|
|||||||
import { Bee, BeeDebug } from '@ethersphere/bee-js'
|
import { Bee, BeeDebug } from '@ethersphere/bee-js'
|
||||||
import { createContext, ReactChild, ReactElement, useEffect, useState } from 'react'
|
import { providers } from 'ethers'
|
||||||
import { config } from '../config'
|
import { createContext, ReactNode, ReactElement, useEffect, useState } from 'react'
|
||||||
import { BeeConfig, useGetBeeConfig } from '../hooks/apiHooks'
|
import { config as appConfig } from '../config'
|
||||||
|
import { useGetBeeConfig } from '../hooks/apiHooks'
|
||||||
|
import { restartBeeNode, setJsonRpcInDesktop } from '../utils/desktop'
|
||||||
|
|
||||||
|
const LocalStorageKeys = {
|
||||||
|
providerUrl: 'json-rpc-provider',
|
||||||
|
}
|
||||||
|
|
||||||
|
const providerUrl = localStorage.getItem('json-rpc-provider') || appConfig.DEFAULT_RPC_URL
|
||||||
|
|
||||||
interface ContextInterface {
|
interface ContextInterface {
|
||||||
apiUrl: string
|
apiUrl: string
|
||||||
apiDebugUrl: string
|
apiDebugUrl: string
|
||||||
beeApi: Bee | null
|
beeApi: Bee | null
|
||||||
beeDebugApi: BeeDebug | null
|
beeDebugApi: BeeDebug | null
|
||||||
setApiUrl: (url: string) => void
|
|
||||||
setDebugApiUrl: (url: string) => void
|
|
||||||
lockedApiSettings: boolean
|
lockedApiSettings: boolean
|
||||||
desktopApiKey: string
|
desktopApiKey: string
|
||||||
config: BeeConfig | null
|
providerUrl: string
|
||||||
|
provider: providers.JsonRpcProvider
|
||||||
|
cors: string | null
|
||||||
|
dataDir: string | null
|
||||||
|
ensResolver: string | null
|
||||||
|
setApiUrl: (url: string) => void
|
||||||
|
setDebugApiUrl: (url: string) => void
|
||||||
|
setAndPersistJsonRpcProvider: (url: string) => Promise<void>
|
||||||
|
isBeeDesktop: boolean
|
||||||
isLoading: boolean
|
isLoading: boolean
|
||||||
error: Error | null
|
error: Error | null
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialValues: ContextInterface = {
|
const initialValues: ContextInterface = {
|
||||||
apiUrl: config.BEE_API_HOST,
|
apiUrl: appConfig.BEE_API_HOST,
|
||||||
apiDebugUrl: config.BEE_DEBUG_API_HOST,
|
apiDebugUrl: appConfig.BEE_DEBUG_API_HOST,
|
||||||
beeApi: null,
|
beeApi: null,
|
||||||
beeDebugApi: null,
|
beeDebugApi: null,
|
||||||
setApiUrl: () => {}, // eslint-disable-line
|
setApiUrl: () => {}, // eslint-disable-line
|
||||||
setDebugApiUrl: () => {}, // eslint-disable-line
|
setDebugApiUrl: () => {}, // eslint-disable-line
|
||||||
lockedApiSettings: false,
|
lockedApiSettings: false,
|
||||||
desktopApiKey: '',
|
desktopApiKey: '',
|
||||||
config: null,
|
setAndPersistJsonRpcProvider: async () => {}, // eslint-disable-line
|
||||||
|
providerUrl,
|
||||||
|
provider: new providers.JsonRpcProvider(providerUrl),
|
||||||
|
cors: null,
|
||||||
|
dataDir: null,
|
||||||
|
ensResolver: null,
|
||||||
|
isBeeDesktop: false,
|
||||||
isLoading: true,
|
isLoading: true,
|
||||||
error: null,
|
error: null,
|
||||||
}
|
}
|
||||||
@@ -35,10 +55,11 @@ export const Context = createContext<ContextInterface>(initialValues)
|
|||||||
export const Consumer = Context.Consumer
|
export const Consumer = Context.Consumer
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: ReactChild
|
children: ReactNode
|
||||||
beeApiUrl?: string
|
beeApiUrl?: string
|
||||||
beeDebugApiUrl?: string
|
beeDebugApiUrl?: string
|
||||||
lockedApiSettings?: boolean
|
lockedApiSettings?: boolean
|
||||||
|
isBeeDesktop?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Provider({
|
export function Provider({
|
||||||
@@ -46,15 +67,34 @@ export function Provider({
|
|||||||
beeApiUrl,
|
beeApiUrl,
|
||||||
beeDebugApiUrl,
|
beeDebugApiUrl,
|
||||||
lockedApiSettings: extLockedApiSettings,
|
lockedApiSettings: extLockedApiSettings,
|
||||||
|
isBeeDesktop: extIsBeeDesktop,
|
||||||
}: Props): ReactElement {
|
}: Props): ReactElement {
|
||||||
const [apiUrl, setApiUrl] = useState<string>(initialValues.apiUrl)
|
const [apiUrl, setApiUrl] = useState<string>(initialValues.apiUrl)
|
||||||
const [apiDebugUrl, setDebugApiUrl] = useState<string>(initialValues.apiDebugUrl)
|
const [apiDebugUrl, setDebugApiUrl] = useState<string>(initialValues.apiDebugUrl)
|
||||||
const [beeApi, setBeeApi] = useState<Bee | null>(null)
|
const [beeApi, setBeeApi] = useState<Bee | null>(null)
|
||||||
const [beeDebugApi, setBeeDebugApi] = useState<BeeDebug | null>(null)
|
const [beeDebugApi, setBeeDebugApi] = useState<BeeDebug | null>(null)
|
||||||
const [lockedApiSettings] = useState<boolean>(Boolean(extLockedApiSettings))
|
|
||||||
const [desktopApiKey, setDesktopApiKey] = useState<string>(initialValues.desktopApiKey)
|
const [desktopApiKey, setDesktopApiKey] = useState<string>(initialValues.desktopApiKey)
|
||||||
|
const [providerUrl, setProviderUrl] = useState(initialValues.providerUrl)
|
||||||
|
const [provider, setProvider] = useState(initialValues.provider)
|
||||||
const { config, isLoading, error } = useGetBeeConfig()
|
const { config, isLoading, error } = useGetBeeConfig()
|
||||||
|
|
||||||
|
const isBeeDesktop = Boolean(extIsBeeDesktop ?? appConfig.BEE_DESKTOP_ENABLED)
|
||||||
|
|
||||||
|
async function setAndPersistJsonRpcProvider(providerUrl: string) {
|
||||||
|
try {
|
||||||
|
localStorage.setItem(LocalStorageKeys.providerUrl, providerUrl)
|
||||||
|
setProviderUrl(providerUrl)
|
||||||
|
setProvider(new providers.JsonRpcProvider(providerUrl))
|
||||||
|
|
||||||
|
if (isBeeDesktop) {
|
||||||
|
await setJsonRpcInDesktop(providerUrl)
|
||||||
|
await restartBeeNode()
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error) // eslint-disable-line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function makeHttpUrl(string: string): string {
|
function makeHttpUrl(string: string): string {
|
||||||
if (!string.startsWith('http')) {
|
if (!string.startsWith('http')) {
|
||||||
return `http://${string}`
|
return `http://${string}`
|
||||||
@@ -104,9 +144,15 @@ export function Provider({
|
|||||||
beeDebugApi,
|
beeDebugApi,
|
||||||
setApiUrl,
|
setApiUrl,
|
||||||
setDebugApiUrl,
|
setDebugApiUrl,
|
||||||
lockedApiSettings,
|
lockedApiSettings: Boolean(extLockedApiSettings),
|
||||||
desktopApiKey,
|
desktopApiKey,
|
||||||
config,
|
provider,
|
||||||
|
providerUrl,
|
||||||
|
cors: config?.['cors-allowed-origins'] ?? null,
|
||||||
|
dataDir: config?.['data-dir'] ?? null,
|
||||||
|
ensResolver: config?.['resolver-options'] ?? null,
|
||||||
|
setAndPersistJsonRpcProvider,
|
||||||
|
isBeeDesktop,
|
||||||
isLoading,
|
isLoading,
|
||||||
error,
|
error,
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -1,29 +1,20 @@
|
|||||||
import { providers, Wallet } from 'ethers'
|
import { Wallet } from 'ethers'
|
||||||
import { createContext, ReactElement, useEffect, useState } from 'react'
|
import { createContext, ReactElement, useContext, useEffect, useState } from 'react'
|
||||||
import { setJsonRpcInDesktop } from '../utils/desktop'
|
import { Context as SettingsContext } from './Settings'
|
||||||
|
|
||||||
const LocalStorageKeys = {
|
const LocalStorageKeys = {
|
||||||
providerUrl: 'json-rpc-provider',
|
|
||||||
depositWallet: 'deposit-wallet',
|
depositWallet: 'deposit-wallet',
|
||||||
giftWallets: 'gift-wallets',
|
giftWallets: 'gift-wallets',
|
||||||
invitation: 'invitation',
|
invitation: 'invitation',
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ContextInterface {
|
interface ContextInterface {
|
||||||
providerUrl: string | null
|
|
||||||
provider: providers.JsonRpcProvider | null
|
|
||||||
giftWallets: Wallet[]
|
giftWallets: Wallet[]
|
||||||
setProviderUrl: (providerUrl: string) => void
|
|
||||||
addGiftWallet: (wallet: Wallet) => void
|
addGiftWallet: (wallet: Wallet) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
const providerUrl = localStorage.getItem('json-rpc-provider') || null
|
|
||||||
|
|
||||||
const initialValues: ContextInterface = {
|
const initialValues: ContextInterface = {
|
||||||
providerUrl,
|
|
||||||
provider: providerUrl ? new providers.JsonRpcProvider(providerUrl) : null,
|
|
||||||
giftWallets: [],
|
giftWallets: [],
|
||||||
setProviderUrl: () => {}, // eslint-disable-line
|
|
||||||
addGiftWallet: () => {}, // eslint-disable-line
|
addGiftWallet: () => {}, // eslint-disable-line
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,9 +26,8 @@ interface Props {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function Provider({ children }: Props): ReactElement {
|
export function Provider({ children }: Props): ReactElement {
|
||||||
const [providerUrl, setProviderUrl] = useState(initialValues.providerUrl)
|
|
||||||
const [provider, setProvider] = useState(initialValues.provider)
|
|
||||||
const [giftWallets, setGiftWallets] = useState(initialValues.giftWallets)
|
const [giftWallets, setGiftWallets] = useState(initialValues.giftWallets)
|
||||||
|
const { provider } = useContext(SettingsContext)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (provider === null) return
|
if (provider === null) return
|
||||||
@@ -49,14 +39,6 @@ export function Provider({ children }: Props): ReactElement {
|
|||||||
}
|
}
|
||||||
}, [provider])
|
}, [provider])
|
||||||
|
|
||||||
function setAndPersistJsonRpcProvider(providerUrl: string) {
|
|
||||||
localStorage.setItem(LocalStorageKeys.providerUrl, providerUrl)
|
|
||||||
setProviderUrl(providerUrl)
|
|
||||||
setProvider(new providers.JsonRpcProvider(providerUrl))
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
setJsonRpcInDesktop(providerUrl).catch(console.error)
|
|
||||||
}
|
|
||||||
|
|
||||||
function addGiftWallet(wallet: Wallet) {
|
function addGiftWallet(wallet: Wallet) {
|
||||||
const newArray = [...giftWallets, wallet]
|
const newArray = [...giftWallets, wallet]
|
||||||
localStorage.setItem(LocalStorageKeys.giftWallets, JSON.stringify(newArray.map(x => x.privateKey)))
|
localStorage.setItem(LocalStorageKeys.giftWallets, JSON.stringify(newArray.map(x => x.privateKey)))
|
||||||
@@ -66,10 +48,7 @@ export function Provider({ children }: Props): ReactElement {
|
|||||||
return (
|
return (
|
||||||
<Context.Provider
|
<Context.Provider
|
||||||
value={{
|
value={{
|
||||||
providerUrl,
|
|
||||||
provider,
|
|
||||||
giftWallets,
|
giftWallets,
|
||||||
setProviderUrl: setAndPersistJsonRpcProvider,
|
|
||||||
addGiftWallet,
|
addGiftWallet,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
import { createContext, ReactChild, ReactElement, useContext, useEffect, useState } from 'react'
|
||||||
|
import { Context as SettingsContext } from './Settings'
|
||||||
|
import { Context as BeeContext } from './Bee'
|
||||||
|
import { WalletAddress } from '../utils/wallet'
|
||||||
|
|
||||||
|
interface ContextInterface {
|
||||||
|
balance: WalletAddress | null
|
||||||
|
error: Error | null
|
||||||
|
isLoading: boolean
|
||||||
|
lastUpdate: number | null
|
||||||
|
start: (frequency?: number) => void
|
||||||
|
stop: () => void
|
||||||
|
refresh: () => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialValues: ContextInterface = {
|
||||||
|
balance: null,
|
||||||
|
error: null,
|
||||||
|
isLoading: false,
|
||||||
|
lastUpdate: null,
|
||||||
|
start: () => {}, // eslint-disable-line
|
||||||
|
stop: () => {}, // eslint-disable-line
|
||||||
|
refresh: () => Promise.reject(),
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Context = createContext<ContextInterface>(initialValues)
|
||||||
|
export const Consumer = Context.Consumer
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children: ReactChild
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Provider({ children }: Props): ReactElement {
|
||||||
|
const { provider } = useContext(SettingsContext)
|
||||||
|
const { nodeAddresses } = useContext(BeeContext)
|
||||||
|
const [balance, setBalance] = useState<WalletAddress | null>(initialValues.balance)
|
||||||
|
const [error, setError] = useState<Error | null>(initialValues.error)
|
||||||
|
const [isLoading, setIsLoading] = useState<boolean>(initialValues.isLoading)
|
||||||
|
const [lastUpdate, setLastUpdate] = useState<number | null>(initialValues.lastUpdate)
|
||||||
|
const [frequency, setFrequency] = useState<number | null>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (nodeAddresses?.ethereum && provider) {
|
||||||
|
WalletAddress.make(nodeAddresses.ethereum, provider).then(setBalance)
|
||||||
|
} else {
|
||||||
|
setBalance(null)
|
||||||
|
}
|
||||||
|
}, [nodeAddresses, provider])
|
||||||
|
|
||||||
|
const refresh = async () => {
|
||||||
|
// Don't want to refresh when already refreshing
|
||||||
|
if (isLoading) return
|
||||||
|
|
||||||
|
if (!balance) return
|
||||||
|
|
||||||
|
try {
|
||||||
|
setIsLoading(true)
|
||||||
|
|
||||||
|
setBalance(await balance.refresh())
|
||||||
|
setLastUpdate(Date.now())
|
||||||
|
} catch (e) {
|
||||||
|
setError(e as Error)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const start = (freq = 30000) => setFrequency(freq)
|
||||||
|
const stop = () => setFrequency(null)
|
||||||
|
|
||||||
|
// Start the update loop
|
||||||
|
useEffect(() => {
|
||||||
|
refresh()
|
||||||
|
|
||||||
|
// Start autorefresh only if the frequency is set
|
||||||
|
if (frequency) {
|
||||||
|
const interval = setInterval(refresh, frequency)
|
||||||
|
|
||||||
|
return () => clearInterval(interval)
|
||||||
|
}
|
||||||
|
}, [frequency]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Context.Provider value={{ balance, error, isLoading, lastUpdate, start, stop, refresh }}>
|
||||||
|
{children}
|
||||||
|
</Context.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { ReactElement } from 'react'
|
import { ReactElement, useContext } from 'react'
|
||||||
import { Route, Routes } from 'react-router-dom'
|
import { Route, Routes } from 'react-router-dom'
|
||||||
import { AccountChequebook } from './pages/account/chequebook/AccountChequebook'
|
import { AccountChequebook } from './pages/account/chequebook/AccountChequebook'
|
||||||
import { AccountFeeds } from './pages/account/feeds/AccountFeeds'
|
import { AccountFeeds } from './pages/account/feeds/AccountFeeds'
|
||||||
@@ -14,8 +14,7 @@ import { UploadLander } from './pages/files/UploadLander'
|
|||||||
import GiftCards from './pages/gift-code'
|
import GiftCards from './pages/gift-code'
|
||||||
import Info from './pages/info'
|
import Info from './pages/info'
|
||||||
import LightModeRestart from './pages/restart/LightModeRestart'
|
import LightModeRestart from './pages/restart/LightModeRestart'
|
||||||
import Wallet from './pages/rpc'
|
import TopUp from './pages/top-up'
|
||||||
import Confirmation from './pages/rpc/Confirmation'
|
|
||||||
import Settings from './pages/settings'
|
import Settings from './pages/settings'
|
||||||
import { CreatePostageStampPage } from './pages/stamps/CreatePostageStampPage'
|
import { CreatePostageStampPage } from './pages/stamps/CreatePostageStampPage'
|
||||||
import Status from './pages/status'
|
import Status from './pages/status'
|
||||||
@@ -24,6 +23,7 @@ import { CryptoTopUpIndex } from './pages/top-up/CryptoTopUpIndex'
|
|||||||
import { GiftCardFund } from './pages/top-up/GiftCardFund'
|
import { GiftCardFund } from './pages/top-up/GiftCardFund'
|
||||||
import { GiftCardTopUpIndex } from './pages/top-up/GiftCardTopUpIndex'
|
import { GiftCardTopUpIndex } from './pages/top-up/GiftCardTopUpIndex'
|
||||||
import { Swap } from './pages/top-up/Swap'
|
import { Swap } from './pages/top-up/Swap'
|
||||||
|
import { Context as SettingsContext } from './providers/Settings'
|
||||||
|
|
||||||
export enum ROUTES {
|
export enum ROUTES {
|
||||||
INFO = '/',
|
INFO = '/',
|
||||||
@@ -34,8 +34,7 @@ export enum ROUTES {
|
|||||||
HASH = '/files/hash/:hash',
|
HASH = '/files/hash/:hash',
|
||||||
SETTINGS = '/settings',
|
SETTINGS = '/settings',
|
||||||
STATUS = '/status',
|
STATUS = '/status',
|
||||||
WALLET = '/account/wallet/top-up',
|
TOP_UP = '/account/wallet/top-up',
|
||||||
CONFIRMATION = '/account/wallet/top-up/confirmation',
|
|
||||||
TOP_UP_CRYPTO = '/account/wallet/top-up/crypto',
|
TOP_UP_CRYPTO = '/account/wallet/top-up/crypto',
|
||||||
TOP_UP_CRYPTO_SWAP = '/account/wallet/top-up/crypto/swap',
|
TOP_UP_CRYPTO_SWAP = '/account/wallet/top-up/crypto/swap',
|
||||||
TOP_UP_BANK_CARD = '/account/wallet/top-up/bank-card',
|
TOP_UP_BANK_CARD = '/account/wallet/top-up/bank-card',
|
||||||
@@ -61,34 +60,37 @@ export const ACCOUNT_TABS = [
|
|||||||
ROUTES.ACCOUNT_FEEDS,
|
ROUTES.ACCOUNT_FEEDS,
|
||||||
]
|
]
|
||||||
|
|
||||||
const BaseRouter = (): ReactElement => (
|
const BaseRouter = (): ReactElement => {
|
||||||
<Routes>
|
const { isBeeDesktop } = useContext(SettingsContext)
|
||||||
<Route path={ROUTES.UPLOAD_IN_PROGRESS} element={<Upload />} />
|
|
||||||
<Route path={ROUTES.UPLOAD} element={<UploadLander />} />
|
return (
|
||||||
<Route path={ROUTES.DOWNLOAD} element={<Download />} />
|
<Routes>
|
||||||
<Route path={ROUTES.HASH} element={<Share />} />
|
<Route path={ROUTES.UPLOAD_IN_PROGRESS} element={<Upload />} />
|
||||||
<Route path={ROUTES.SETTINGS} element={<Settings />} />
|
<Route path={ROUTES.UPLOAD} element={<UploadLander />} />
|
||||||
<Route path={ROUTES.STATUS} element={<Status />} />
|
<Route path={ROUTES.DOWNLOAD} element={<Download />} />
|
||||||
<Route path={ROUTES.INFO} element={<Info />} />
|
<Route path={ROUTES.HASH} element={<Share />} />
|
||||||
<Route path={ROUTES.WALLET} element={<Wallet />} />
|
<Route path={ROUTES.SETTINGS} element={<Settings />} />
|
||||||
<Route path={ROUTES.CONFIRMATION} element={<Confirmation />} />
|
<Route path={ROUTES.STATUS} element={<Status />} />
|
||||||
<Route path={ROUTES.TOP_UP_CRYPTO} element={<CryptoTopUpIndex />} />
|
<Route path={ROUTES.INFO} element={<Info />} />
|
||||||
<Route path={ROUTES.TOP_UP_CRYPTO_SWAP} element={<Swap header="Top-up with cryptocurrencies" />} />
|
<Route path={ROUTES.TOP_UP} element={<TopUp />} />
|
||||||
<Route path={ROUTES.TOP_UP_BANK_CARD} element={<BankCardTopUpIndex />} />
|
<Route path={ROUTES.TOP_UP_CRYPTO} element={<CryptoTopUpIndex />} />
|
||||||
<Route path={ROUTES.TOP_UP_BANK_CARD_SWAP} element={<Swap header="Top-up with bank card" />} />
|
<Route path={ROUTES.TOP_UP_CRYPTO_SWAP} element={<Swap header="Top-up with cryptocurrencies" />} />
|
||||||
<Route path={ROUTES.TOP_UP_GIFT_CODE} element={<GiftCardTopUpIndex />} />
|
<Route path={ROUTES.TOP_UP_BANK_CARD} element={<BankCardTopUpIndex />} />
|
||||||
<Route path={ROUTES.TOP_UP_GIFT_CODE_FUND} element={<GiftCardFund />} />
|
<Route path={ROUTES.TOP_UP_BANK_CARD_SWAP} element={<Swap header="Top-up with bank card" />} />
|
||||||
<Route path={ROUTES.RESTART_LIGHT} element={<LightModeRestart />} />
|
<Route path={ROUTES.TOP_UP_GIFT_CODE} element={<GiftCardTopUpIndex />} />
|
||||||
<Route path={ROUTES.ACCOUNT_WALLET} element={<AccountWallet />} />
|
<Route path={ROUTES.TOP_UP_GIFT_CODE_FUND} element={<GiftCardFund />} />
|
||||||
<Route path={ROUTES.ACCOUNT_CHEQUEBOOK} element={<AccountChequebook />} />
|
<Route path={ROUTES.RESTART_LIGHT} element={<LightModeRestart />} />
|
||||||
<Route path={ROUTES.ACCOUNT_STAMPS} element={<AccountStamps />} />
|
<Route path={ROUTES.ACCOUNT_WALLET} element={<AccountWallet />} />
|
||||||
<Route path={ROUTES.ACCOUNT_STAMPS_NEW} element={<CreatePostageStampPage />} />
|
<Route path={ROUTES.ACCOUNT_CHEQUEBOOK} element={<AccountChequebook />} />
|
||||||
<Route path={ROUTES.ACCOUNT_FEEDS} element={<AccountFeeds />} />
|
<Route path={ROUTES.ACCOUNT_STAMPS} element={<AccountStamps />} />
|
||||||
<Route path={ROUTES.ACCOUNT_FEEDS_NEW} element={<CreateNewFeed />} />
|
<Route path={ROUTES.ACCOUNT_STAMPS_NEW} element={<CreatePostageStampPage />} />
|
||||||
<Route path={ROUTES.ACCOUNT_FEEDS_UPDATE} element={<UpdateFeed />} />
|
<Route path={ROUTES.ACCOUNT_FEEDS} element={<AccountFeeds />} />
|
||||||
<Route path={ROUTES.ACCOUNT_FEEDS_VIEW} element={<FeedSubpage />} />
|
<Route path={ROUTES.ACCOUNT_FEEDS_NEW} element={<CreateNewFeed />} />
|
||||||
<Route path={ROUTES.ACCOUNT_INVITATIONS} element={<GiftCards />} />
|
<Route path={ROUTES.ACCOUNT_FEEDS_UPDATE} element={<UpdateFeed />} />
|
||||||
</Routes>
|
<Route path={ROUTES.ACCOUNT_FEEDS_VIEW} element={<FeedSubpage />} />
|
||||||
)
|
{isBeeDesktop && <Route path={ROUTES.ACCOUNT_INVITATIONS} element={<GiftCards />} />}
|
||||||
|
</Routes>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default BaseRouter
|
export default BaseRouter
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
const chains = [
|
||||||
|
{
|
||||||
|
name: 'Ethereum Mainnet',
|
||||||
|
chainId: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Ropsten Testnet',
|
||||||
|
chainId: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Rinkeby Testnet',
|
||||||
|
chainId: 4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Görli Testnet',
|
||||||
|
chainId: 5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Kovan Testnet',
|
||||||
|
chainId: 42,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Gnosis Chain',
|
||||||
|
chainId: 100,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
export function chainIdToName(chainId: number): string {
|
||||||
|
return chains.find(record => record.chainId === chainId)?.name || 'Unknown'
|
||||||
|
}
|
||||||
@@ -18,15 +18,26 @@ export function detectIndexHtml(files: FilePath[]): DetectedIndex | false {
|
|||||||
return { indexPath: exactMatch }
|
return { indexPath: exactMatch }
|
||||||
}
|
}
|
||||||
|
|
||||||
const prefix = paths[0].split('/')[0] + '/'
|
const sortedPaths = paths.sort((a, b) => a.localeCompare(b))
|
||||||
|
const firstSegments = sortedPaths[0].split('/')
|
||||||
|
const lastSegments = sortedPaths[sortedPaths.length - 1].split('/')
|
||||||
|
let matchingSegments = 0
|
||||||
|
|
||||||
const allStartWithSamePrefix = paths.every(x => x.startsWith(prefix))
|
for (; matchingSegments < firstSegments.length; matchingSegments++) {
|
||||||
|
if (firstSegments[matchingSegments] !== lastSegments[matchingSegments]) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const commonPrefix = firstSegments.slice(0, matchingSegments).join('/') + '/'
|
||||||
|
|
||||||
|
const allStartWithSamePrefix = paths.every(x => x.startsWith(commonPrefix))
|
||||||
|
|
||||||
if (allStartWithSamePrefix) {
|
if (allStartWithSamePrefix) {
|
||||||
const match = paths.find(x => indexHtmls.map(y => prefix + y).includes(x))
|
const match = paths.find(x => indexHtmls.map(y => commonPrefix + y).includes(x))
|
||||||
|
|
||||||
if (match) {
|
if (match) {
|
||||||
return { indexPath: match, commonPrefix: prefix }
|
return { indexPath: match, commonPrefix }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +99,11 @@ export function getPath(file: FilePath): string {
|
|||||||
* Utility function that is needed to have correct directory structure as webkitRelativePath is read only
|
* Utility function that is needed to have correct directory structure as webkitRelativePath is read only
|
||||||
*/
|
*/
|
||||||
export function packageFile(file: FilePath, pathOverwrite?: string): FilePath {
|
export function packageFile(file: FilePath, pathOverwrite?: string): FilePath {
|
||||||
const path = pathOverwrite || getPath(file)
|
let path = pathOverwrite || getPath(file)
|
||||||
|
|
||||||
|
if (!path.startsWith('/') && path.includes('/')) {
|
||||||
|
path = `/${path}`
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
path: path,
|
path: path,
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export async function updateFeed(
|
|||||||
const wallet = await getWalletFromIdentity(identity, password)
|
const wallet = await getWalletFromIdentity(identity, password)
|
||||||
|
|
||||||
if (!identity.feedHash) {
|
if (!identity.feedHash) {
|
||||||
identity.feedHash = await beeApi.createFeedManifest(stamp, 'sequence', '00'.repeat(32), wallet.address)
|
identity.feedHash = (await beeApi.createFeedManifest(stamp, 'sequence', '00'.repeat(32), wallet.address)).reference
|
||||||
}
|
}
|
||||||
|
|
||||||
const writer = beeApi.makeFeedWriter('sequence', '00'.repeat(32), wallet.privateKey)
|
const writer = beeApi.makeFeedWriter('sequence', '00'.repeat(32), wallet.privateKey)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ export function getJson<T extends Record<string, any>>(url: string): Promise<T>
|
|||||||
return sendRequest(url, 'GET') as Promise<T>
|
return sendRequest(url, 'GET') as Promise<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function postJson(url: string, data?: Record<string, any>): Promise<Record<string, unknown>> {
|
export function postJson<T extends Record<string, any>>(url: string, data?: T): Promise<T> {
|
||||||
return sendRequest(url, 'POST', data)
|
return sendRequest(url, 'POST', data) as Promise<T>
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendRequest(
|
export async function sendRequest(
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
import { config } from '../config'
|
|
||||||
import * as Sentry from '@sentry/react'
|
|
||||||
import packageJson from '../../package.json'
|
|
||||||
import { BrowserTracing } from '@sentry/tracing'
|
|
||||||
import { getBeeDesktopLogs, getBeeLogs } from './desktop'
|
|
||||||
|
|
||||||
export async function initSentry(): Promise<void> {
|
|
||||||
let tunnelAvailable
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await fetch(`${config.BEE_DESKTOP_URL}/sentry`, { method: 'OPTIONS' })
|
|
||||||
|
|
||||||
if (result.status === 204) {
|
|
||||||
tunnelAvailable = true
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// There was an error, so tunnel is not available
|
|
||||||
tunnelAvailable = false
|
|
||||||
}
|
|
||||||
|
|
||||||
Sentry.init({
|
|
||||||
dsn: config.SENTRY_KEY,
|
|
||||||
release: packageJson.version,
|
|
||||||
environment: config.SENTRY_ENVIRONMENT,
|
|
||||||
tunnel: tunnelAvailable ? `${config.BEE_DESKTOP_URL}/sentry` : undefined,
|
|
||||||
integrations: [new BrowserTracing({ tracingOrigins: [config.BEE_DESKTOP_URL] })],
|
|
||||||
tracesSampleRate: 0.4,
|
|
||||||
beforeSend: async (event, hint) => {
|
|
||||||
hint.attachments = []
|
|
||||||
|
|
||||||
try {
|
|
||||||
// This will fail if we are not running in Bee Desktop, but that is alright
|
|
||||||
hint.attachments.push({ filename: 'bee-desktop.log', data: await getBeeDesktopLogs() })
|
|
||||||
// eslint-disable-next-line no-empty
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// This will fail if we are not running in Bee Desktop, but that is alright
|
|
||||||
hint.attachments.push({ filename: 'bee.log', data: await getBeeLogs() })
|
|
||||||
// eslint-disable-next-line no-empty
|
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
return event
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
After Width: | Height: | Size: 114 KiB |
@@ -0,0 +1,40 @@
|
|||||||
|
6857a7050f3b698675d85a6d019305b6090f95f0
|
||||||
|
fcf895e4df26acdce571c2333bbd0730ef29f891
|
||||||
|
68dc5591812e60b17dde51615ed0881ea5fcfd9f
|
||||||
|
7adb999f68d64fe05b5274eee058521aa23b2aef
|
||||||
|
becc4e5197099bdf152f1bf3bf9d1bb50e007a0c
|
||||||
|
3c5ba0f875ff345ee48a269d7b53eeda12fd5601
|
||||||
|
4dc6123048234084c599959142cf415172450715
|
||||||
|
864c785d0ddf4e24924de4ac165bd4e74c7e36b3
|
||||||
|
85df5c1f7994dfdc171db8ee17f6144ca18c0009
|
||||||
|
9bbc1aa874ec49a9ac933faf4b05ab33b132bfbc
|
||||||
|
0269aa60a6c456b9206e3b04c174603f869d2f14
|
||||||
|
60f7b18bfa0f07c210e91d385d92e19140aeb51d
|
||||||
|
21daec4b7ad73922169d8efddd0e0174fb90d013
|
||||||
|
ed3d75ae2c0d5295841b5f96d278222cb4abc0f2
|
||||||
|
10b8898cfaf7208884ea7c042cc456fc92c9b819
|
||||||
|
38f2cdd53aa2d46423c7d9ee3f55ecdb3d69044d
|
||||||
|
b3f3265c9d97e80260bb4a9c9b17c4a5bcf643a9
|
||||||
|
545bc39d80151cb23d0c98ce618f0a4adc120ac5
|
||||||
|
b4de4b6a2437e99534384cf6810feb500ee478f6
|
||||||
|
a0d37a09c84ca3b58a493dd27ba36f43e8ee4fad
|
||||||
|
2bd0ec3f8a3852fb960160dad51e5b4078426944
|
||||||
|
57c8e004de3cff1974ff285677a3a386bd38a317
|
||||||
|
bb6f33f3f12cfdc68ff2bb9f91406d40cee3c807
|
||||||
|
a0e2045d7b3f5a84c2fc0262b37cba5b93d66bd0
|
||||||
|
6456741eac9cdae9ced1cc2ce7d4972a3329bd39
|
||||||
|
d8a7a7875ce0b15d1e1a50e705c3118280363ec0
|
||||||
|
64c723249a47c0ff663000a762d95fb58f13fdea
|
||||||
|
0327e8529be0d96f86c841cc7839e15dca15d2bd
|
||||||
|
edcb3f24ba8c74359660bf2c488df0ed414072d6
|
||||||
|
d654a02bf4271e9633548a6777c1788a98eded87
|
||||||
|
bc6fa2c3c155a940262386571081420402b1b923
|
||||||
|
338d8167dd48f810aa9573bb53d2bf632331f989
|
||||||
|
81176b5e809c1b29aaf717cd3b43ee871c8f21c1
|
||||||
|
6853345f0d4fd39365c4d9de58d258779e89eb7c
|
||||||
|
b68aa42ba7a343eedd595ee197c6381457162b63
|
||||||
|
ecebaf8124aa6caff3542c25d80ed7cf5f64584f
|
||||||
|
501804d75a17f77799e09834101626c1c681237a
|
||||||
|
ebba852da0af9fab804a79c592c2bdfed286c26b
|
||||||
|
5ddabc3dcc3ab672e7b0a01f4afad5239109eba0
|
||||||
|
6e89b48babada48b6e6e3dafb6c280b6089ba841
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
89d0aa0693f8fa7fd56ea9821a20576b8dc0b70c
|
||||||
|
233f235852c31d31d25c41a95d276457d75c5d2c
|
||||||
|
4a6dbff20f95a99676b0423c945e532c1d27ce10
|
||||||
|
e83f5f472255e5e47a94bec9ddc5a0b10787230f
|
||||||
|
eb8f5d96cc60019a328a6fd70230d68e41ec5f8f
|
||||||
|
95d3b8187e99d5eb9fb4110602ed0986cdcf7e9c
|
||||||
|
2f57c850d44481baf3c91aac9a6aa17a6f870368
|
||||||
|
4af29bd9177509376e20e79f3a4ff41475e89ce1
|
||||||
|
b0424b5cc80fa80d7eb59faca0538bea7d4028ac
|
||||||
|
242bc01b9b54a13e0f60259deb66a4ccf428a679
|
||||||
|
34e3ca767691317ae7d021967f0576bd4eb0baa2
|
||||||
|
0bf5c07d4e807ca46c5fb4334381bc77163c1f9d
|
||||||
|
df4d25cb88c7177b2afddc1c652753a2b78ff7b9
|
||||||
|
b82c0ba16886648e7f21d0b9b24b33080574671f
|
||||||
|
92456c3bbffd461845f6600cf4357df7968b88e0
|
||||||
|
ddbf58f422d7c3dfb0a5fb4ded9cd9d9e99da4be
|
||||||
|
41c531777fd80868dffcff554de1d77b44dfab7e
|
||||||
|
02540a73ce034777a18fe9ed9c76855f6fdbfb63
|
||||||
|
0e6707e80215d5871203a1ca3048915eebec653b
|
||||||
|
a00398936467504d5b3ea8bb59ab0d1259ca83bd
|
||||||
|
1fb82cbec72739f7e366c9c4ca4ba75a3ffb20fe
|
||||||
|
2335340c6ceeb6b7e5f91d659ba5aa1c0b47892d
|
||||||
|
287b993cd5480a2267f7dbbb11f69777f6742b1e
|
||||||
|
9462fa394fac136bed96b6274f999afd0256ce82
|
||||||
|
33b3404926bb97848ea4f7a5d6f772251da7a608
|
||||||
|
bfa50de6375939c17ed4ec29c8e812c4e9be60ce
|
||||||
|
b2a9542b7bb6674f4aab36c30b16ff54c222bef1
|
||||||
|
40746c1c87e7ea175df5f1680a0eceda0239868b
|
||||||
|
73d56b02bfe537480cbfe59fde9ec859ed7fcd56
|
||||||
|
33f8ceb9133e50a67d8fbab76c7f986ee8593ee7
|
||||||
|
a520751396cfeadc99ea708e270080ad6170d5ea
|
||||||
|
3ac084cb847b17b753142900a99fcd1f441084ab
|
||||||
|
1a16765601210a635baad6aadaf6c9f1f2304d92
|
||||||
|
0e7ac2503779b3969e1a153ac06a9271b5af9a4d
|
||||||
|
c053d311c71f4461e2d56a7cf799b4841977b623
|
||||||
|
b63bef742d705306e32e726738257b83bcd92ddb
|
||||||
|
854e6e1731ccfc22327a6c5bd7ce78e394ef0325
|
||||||
|
15f7b431a7d48391a71b79ca0e1b567eb7ff5f5c
|
||||||
|
585e99c7cedfac1190ae449e5546a7da2fe0ff49
|
||||||
|
0aae3e5db6057a2796c59bdefcd6ae44b880e5ce
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
fafe57711ee6f0fe89ea64ab7e5b6f9a34eebc1a
|
||||||
|
3f3a2a728f45e00a17ee7255b1f4fc4d9adc83c0
|
||||||
|
815051cf4ac9235a0df2806bf2eabc668fa29a01
|
||||||
|
cdac5f6018279816ee7287794239de83dc6312af
|
||||||
|
401666cac4f1e34132176ba6565eb84899aa168f
|
||||||
|
5f2812e35ca870b9e85b5ac60b47e0d80aa2d905
|
||||||
|
ada7f51c064702ac4c1f33d1ced6dd882f2f4971
|
||||||
|
e5b8944c6ee08170205a47049626d995bc850151
|
||||||
|
cae308ccfc3ddc3c08f1da7bd6348c954c7c7cbe
|
||||||
|
73be5df1891a0e6e374936f7f1fd93104033fecb
|
||||||
|
60a8b7cb61e058722956e39d28f8b1efe5a0e309
|
||||||
|
b2b6d5e138d41738ff9057b74aec1965fc030b30
|
||||||
|
29ba55c0334e2fb60599ce99ed35b8adc65c92d6
|
||||||
|
1ade4db1e922d6609216902a65fc72535f5570d5
|
||||||
|
00749b46dc4d83d3c835ea51c387c2ba9f009ec4
|
||||||
|
f5623179b8dd80eeb5dba80e70b304dc2debb585
|
||||||
|
8ab93bde7091a75a66be77c20ee929dd37ea41f1
|
||||||
|
e8d7ee0b5bb27154a855d95e1af3e4709e1a58f9
|
||||||
|
5642d952fa67e0e89f1bc874c0e6593417289e78
|
||||||
|
e3b584b47a28b8b664c78f536e76ae9b1d3079bf
|
||||||
|
7d7c97d348eae903189c7c0efad090064d3b771e
|
||||||
|
ae1502dd53c908bb3658e41f4d4da3dbb11cb772
|
||||||
|
f4ef4a02759cb82cf90defab3ac948a65e874ee9
|
||||||
|
8c797b38d4594ac2c03d069e853a586d02c6b368
|
||||||
|
56f8a1fafd0c073440f71d8eeba3268689d78b98
|
||||||
|
dc1a0e8caf5934babf92802f3a753933400b37d9
|
||||||
|
da065c57205f39a943e3d0b10001fbf730feb552
|
||||||
|
2926b2389769c14d77fc1b0f0ff1faec0a26323f
|
||||||
|
7d439a063b726875e81683494affd5517f666649
|
||||||
|
4ba360418d2d1d5f003b932747a258494c4301e8
|
||||||
|
5d46dbc2f72781502eed0722c609ddd3ad2dcfa2
|
||||||
|
7b7d15425d0b7de25e2f27ee5e402e0c22c22038
|
||||||
|
e97a4765286e7c83d309676983c78da95754ff83
|
||||||
|
8ed57bead42d0d22ebe0fcb322a0aa94ace3bb66
|
||||||
|
352c383d10f97d17d4ea6d6ff55be5ec14e5a010
|
||||||
|
b5025ec9e14721a9034b6f9f24932734b4fb822a
|
||||||
|
a0d946a3a729d91ee0740b9d937ac7686cf7a553
|
||||||
|
ef44430604978c460c7e33e56c723e9d2976aff6
|
||||||
|
507dfe5656d5a3a139b7c0feb16b323557cb0a17
|
||||||
|
6dfa530095471d8af68dda90a25829c3fb01857d
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
0c5aee4c907c91a32cb63f34de20b584e57bc63b
|
||||||
|
1e58a64ee85233d042508363b83940c68fac51c2
|
||||||
|
caf8ff6f60dc0892415450cbc574799da9abbedc
|
||||||
|
74686af7ec63bb15dc6fb0af6134a2744ea8787e
|
||||||
|
6400780e4f6e4b33628bede4045249dfc9c69540
|
||||||
|
5acb7f4c35c88d65557a397a9adde4ecdc18ae14
|
||||||
|
f320cf7bff3fc91b237be60e3426a627afc89aa4
|
||||||
|
2076ca826e4e1c794954b20df36d3aef5a8e460a
|
||||||
|
bfe5bd2506752fe2e2bb9bbd4ba6ba32b602c7d9
|
||||||
|
007b6defb01b0de6e833dacd209752117c17f674
|
||||||
|
298e5755c41c583d7693a78a89a7473df8739b06
|
||||||
|
166fdcad59469adf63120b0dce19ce6c5f4761eb
|
||||||
|
5ba1049456f61cbd1438641495f704b502aefa2e
|
||||||
|
12b37c1ad7fc52442a40dc37ab15320db2367538
|
||||||
|
7ac458cfe550a48a0a2915012a109175125be704
|
||||||
|
a5b423222aefbfdd109edcbf24aaf1645fdca253
|
||||||
|
d19b935739188a8fd26ed8b5a10bbda9886ec6c0
|
||||||
|
f8bedd5e9fd7e12edb08ff9276cfd4c0ea9a6352
|
||||||
|
ae5a66da162be82eb892341cd647c2e5fcc6ae72
|
||||||
|
5db642841bb38be39c83fc35b0131a7cd0f38a0e
|
||||||
|
9cc4e3bec3365e21ce94d794a0892d695dc2a1c3
|
||||||
|
32ccd4cda5736a6834b5f6940e3493fa85f0a1f9
|
||||||
|
520c3eb1cd25302266b352bfd01004aab89aadd3
|
||||||
|
28bb5f995b7d834d74e499405bee30d65c0b9c5d
|
||||||
|
1de4ceb425708e62a65ab658e1c94baa30a6fc8b
|
||||||
|
904d403ec242b5d95c7fff31c0b581db1b434b19
|
||||||
|
b0b684d18d8145bb5284978430b9143b1c09d33f
|
||||||
|
d88afdcb176af85c165a9135b199cb78bfa3c04c
|
||||||
|
bfe09cab5fdd069c95cb5133414e4f029f3e5803
|
||||||
|
d89230f068a21d067cfc4087d1306a53896a9788
|
||||||
|
d6e72f6fe922c04e32cf236d642177d031b57f23
|
||||||
|
1049c6078ad9896ac914bab2425a05f865da4164
|
||||||
|
f5d9a7ec1dff1cd241c8303f84024900ee5ea7ce
|
||||||
|
c6c883d338bfac9709763c69f8f2e5b61c24bc0d
|
||||||
|
242574cb1c99e6731e4a792855920ffe63cd5c1c
|
||||||
|
41167719d37928de14c78b243ca371cfa893b07c
|
||||||
|
738f590d0b3cb71e3a03369068c6192fe1e16167
|
||||||
|
1268b7be1e0d6239a896be8adaa08b512bb65c67
|
||||||
|
136eb76f4a68a67940d6deed22e37098baab216a
|
||||||
|
fb5a4fc20e23aab9024bb621af1417bd9dbc29af
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"files": {
|
||||||
|
"main.css": "/static/css/main.073c9b0a.css",
|
||||||
|
"main.js": "/static/js/main.0215976b.js",
|
||||||
|
"static/js/787.28cb0dcd.chunk.js": "/static/js/787.28cb0dcd.chunk.js",
|
||||||
|
"static/media/logo.svg": "/static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg",
|
||||||
|
"index.html": "/index.html",
|
||||||
|
"main.073c9b0a.css.map": "/static/css/main.073c9b0a.css.map",
|
||||||
|
"main.0215976b.js.map": "/static/js/main.0215976b.js.map",
|
||||||
|
"787.28cb0dcd.chunk.js.map": "/static/js/787.28cb0dcd.chunk.js.map"
|
||||||
|
},
|
||||||
|
"entrypoints": [
|
||||||
|
"static/css/main.073c9b0a.css",
|
||||||
|
"static/js/main.0215976b.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1 @@
|
|||||||
|
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>React App</title><script defer="defer" src="/static/js/main.0215976b.js"></script><link href="/static/css/main.073c9b0a.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||||
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"short_name": "React App",
|
||||||
|
"name": "Create React App Sample",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "64x64 32x32 24x24 16x16",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "logo512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
# https://www.robotstxt.org/robotstxt.html
|
||||||
|
User-agent: *
|
||||||
|
Disallow:
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;margin:0}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}.App{text-align:center}.App-logo{height:40vmin;pointer-events:none}@media (prefers-reduced-motion:no-preference){.App-logo{-webkit-animation:App-logo-spin 20s linear infinite;animation:App-logo-spin 20s linear infinite}}.App-header{align-items:center;background-color:#282c34;color:#fff;display:flex;flex-direction:column;font-size:calc(10px + 2vmin);justify-content:center;min-height:100vh}.App-link{color:#61dafb}@-webkit-keyframes App-logo-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}@keyframes App-logo-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}}
|
||||||
|
/*# sourceMappingURL=main.073c9b0a.css.map*/
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"static/css/main.073c9b0a.css","mappings":"AAAA,KAKE,kCAAmC,CACnC,iCAAkC,CAJlC,mIAEY,CAHZ,QAMF,CAEA,KACE,uEAEF,CCZA,KACE,iBACF,CAEA,UACE,aAAc,CACd,mBACF,CAEA,8CACE,UACE,mDAA4C,CAA5C,2CACF,CACF,CAEA,YAKE,kBAAmB,CAJnB,wBAAyB,CAOzB,UAAY,CALZ,YAAa,CACb,qBAAsB,CAGtB,4BAA6B,CAD7B,sBAAuB,CAJvB,gBAOF,CAEA,UACE,aACF,CAEA,iCACE,GACE,8BAAuB,CAAvB,sBACF,CACA,GACE,+BAAyB,CAAzB,uBACF,CACF,CAPA,yBACE,GACE,8BAAuB,CAAvB,sBACF,CACA,GACE,+BAAyB,CAAzB,uBACF,CACF","sources":["index.css","App.css"],"sourcesContent":["body {\n margin: 0;\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\n sans-serif;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\ncode {\n font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\n monospace;\n}\n",".App {\n text-align: center;\n}\n\n.App-logo {\n height: 40vmin;\n pointer-events: none;\n}\n\n@media (prefers-reduced-motion: no-preference) {\n .App-logo {\n animation: App-logo-spin infinite 20s linear;\n }\n}\n\n.App-header {\n background-color: #282c34;\n min-height: 100vh;\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n font-size: calc(10px + 2vmin);\n color: white;\n}\n\n.App-link {\n color: #61dafb;\n}\n\n@keyframes App-logo-spin {\n from {\n transform: rotate(0deg);\n }\n to {\n transform: rotate(360deg);\n }\n}\n"],"names":[],"sourceRoot":""}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
"use strict";(self.webpackChunkmy_app=self.webpackChunkmy_app||[]).push([[787],{787:function(e,t,n){n.r(t),n.d(t,{getCLS:function(){return y},getFCP:function(){return g},getFID:function(){return C},getLCP:function(){return P},getTTFB:function(){return D}});var i,r,a,o,u=function(e,t){return{name:e,value:void 0===t?-1:t,delta:0,entries:[],id:"v2-".concat(Date.now(),"-").concat(Math.floor(8999999999999*Math.random())+1e12)}},c=function(e,t){try{if(PerformanceObserver.supportedEntryTypes.includes(e)){if("first-input"===e&&!("PerformanceEventTiming"in self))return;var n=new PerformanceObserver((function(e){return e.getEntries().map(t)}));return n.observe({type:e,buffered:!0}),n}}catch(e){}},f=function(e,t){var n=function n(i){"pagehide"!==i.type&&"hidden"!==document.visibilityState||(e(i),t&&(removeEventListener("visibilitychange",n,!0),removeEventListener("pagehide",n,!0)))};addEventListener("visibilitychange",n,!0),addEventListener("pagehide",n,!0)},s=function(e){addEventListener("pageshow",(function(t){t.persisted&&e(t)}),!0)},m=function(e,t,n){var i;return function(r){t.value>=0&&(r||n)&&(t.delta=t.value-(i||0),(t.delta||void 0===i)&&(i=t.value,e(t)))}},v=-1,p=function(){return"hidden"===document.visibilityState?0:1/0},d=function(){f((function(e){var t=e.timeStamp;v=t}),!0)},l=function(){return v<0&&(v=p(),d(),s((function(){setTimeout((function(){v=p(),d()}),0)}))),{get firstHiddenTime(){return v}}},g=function(e,t){var n,i=l(),r=u("FCP"),a=function(e){"first-contentful-paint"===e.name&&(f&&f.disconnect(),e.startTime<i.firstHiddenTime&&(r.value=e.startTime,r.entries.push(e),n(!0)))},o=window.performance&&performance.getEntriesByName&&performance.getEntriesByName("first-contentful-paint")[0],f=o?null:c("paint",a);(o||f)&&(n=m(e,r,t),o&&a(o),s((function(i){r=u("FCP"),n=m(e,r,t),requestAnimationFrame((function(){requestAnimationFrame((function(){r.value=performance.now()-i.timeStamp,n(!0)}))}))})))},h=!1,T=-1,y=function(e,t){h||(g((function(e){T=e.value})),h=!0);var n,i=function(t){T>-1&&e(t)},r=u("CLS",0),a=0,o=[],v=function(e){if(!e.hadRecentInput){var t=o[0],i=o[o.length-1];a&&e.startTime-i.startTime<1e3&&e.startTime-t.startTime<5e3?(a+=e.value,o.push(e)):(a=e.value,o=[e]),a>r.value&&(r.value=a,r.entries=o,n())}},p=c("layout-shift",v);p&&(n=m(i,r,t),f((function(){p.takeRecords().map(v),n(!0)})),s((function(){a=0,T=-1,r=u("CLS",0),n=m(i,r,t)})))},E={passive:!0,capture:!0},w=new Date,L=function(e,t){i||(i=t,r=e,a=new Date,F(removeEventListener),S())},S=function(){if(r>=0&&r<a-w){var e={entryType:"first-input",name:i.type,target:i.target,cancelable:i.cancelable,startTime:i.timeStamp,processingStart:i.timeStamp+r};o.forEach((function(t){t(e)})),o=[]}},b=function(e){if(e.cancelable){var t=(e.timeStamp>1e12?new Date:performance.now())-e.timeStamp;"pointerdown"==e.type?function(e,t){var n=function(){L(e,t),r()},i=function(){r()},r=function(){removeEventListener("pointerup",n,E),removeEventListener("pointercancel",i,E)};addEventListener("pointerup",n,E),addEventListener("pointercancel",i,E)}(t,e):L(t,e)}},F=function(e){["mousedown","keydown","touchstart","pointerdown"].forEach((function(t){return e(t,b,E)}))},C=function(e,t){var n,a=l(),v=u("FID"),p=function(e){e.startTime<a.firstHiddenTime&&(v.value=e.processingStart-e.startTime,v.entries.push(e),n(!0))},d=c("first-input",p);n=m(e,v,t),d&&f((function(){d.takeRecords().map(p),d.disconnect()}),!0),d&&s((function(){var a;v=u("FID"),n=m(e,v,t),o=[],r=-1,i=null,F(addEventListener),a=p,o.push(a),S()}))},k={},P=function(e,t){var n,i=l(),r=u("LCP"),a=function(e){var t=e.startTime;t<i.firstHiddenTime&&(r.value=t,r.entries.push(e),n())},o=c("largest-contentful-paint",a);if(o){n=m(e,r,t);var v=function(){k[r.id]||(o.takeRecords().map(a),o.disconnect(),k[r.id]=!0,n(!0))};["keydown","click"].forEach((function(e){addEventListener(e,v,{once:!0,capture:!0})})),f(v,!0),s((function(i){r=u("LCP"),n=m(e,r,t),requestAnimationFrame((function(){requestAnimationFrame((function(){r.value=performance.now()-i.timeStamp,k[r.id]=!0,n(!0)}))}))}))}},D=function(e){var t,n=u("TTFB");t=function(){try{var t=performance.getEntriesByType("navigation")[0]||function(){var e=performance.timing,t={entryType:"navigation",startTime:0};for(var n in e)"navigationStart"!==n&&"toJSON"!==n&&(t[n]=Math.max(e[n]-e.navigationStart,0));return t}();if(n.value=n.delta=t.responseStart,n.value<0||n.value>performance.now())return;n.entries=[t],e(n)}catch(e){}},"complete"===document.readyState?setTimeout(t,0):addEventListener("load",(function(){return setTimeout(t,0)}))}}}]);
|
||||||
|
//# sourceMappingURL=787.28cb0dcd.chunk.js.map
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* react-dom.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* react-jsx-runtime.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* react.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @license React
|
||||||
|
* scheduler.production.min.js
|
||||||
|
*
|
||||||
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the MIT license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree.
|
||||||
|
*/
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
||||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body></body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
🙅𝖆👾\都都𝖇🙅𝖆𝖈\𝖇—🙇\𝖆💁🙇東都東🙇\💁👾𝖆𝖆𝖆𝖇—東東京🙇𝖆𝖈💁🙇🙅🙅
|
||||||
|
都𝖆🙅\👾𝖈𝖈東🙇\都東𝖇𝖆東都\👾👾東東都👾👾\💁\𝖇𝖈𝖆🙇東都都\都👾💁🙇𝖈
|
||||||
|
👾\🙅東東京東都🙅—𝖇京京🙇💁—👾𝖇𝖇𝖇都👾東𝖆京👾京👾🙇都\𝖆—東東—京👾京東
|
||||||
|
—🙅都—𝖈𝖇—\\𝖆都💁💁🙇𝖈𝖈🙅—都\🙇𝖇💁𝖈𝖇\🙇𝖆💁👾京𝖆都都🙅—𝖈𝖇都𝖇
|
||||||
|
𝖇都\𝖈都👾💁𝖈🙇京👾京—𝖆𝖆🙅京𝖈\𝖇—京🙅💁𝖈—都\京都東東𝖆𝖇𝖇🙅\👾京🙅
|
||||||
|
東🙅\都𝖆—👾💁𝖈都🙅東—𝖆🙇𝖆🙇𝖈🙅京東𝖇𝖇🙅都𝖈東👾東🙇🙇🙇👾𝖆🙇👾—東—東
|
||||||
|
𝖆京都東🙅都𝖈🙇都\東🙅🙅👾𝖇𝖇👾𝖇👾👾👾京\—𝖇—🙅\𝖆𝖆京𝖆💁🙅京東𝖆🙇—💁
|
||||||
|
都東東🙇都👾𝖈𝖈𝖆𝖈👾東都💁京—𝖈都東🙇𝖈—🙇𝖈都\\東京🙅𝖇—𝖈🙇東𝖆𝖈東京👾
|
||||||
|
—𝖇—💁👾都👾𝖇—𝖈東京𝖇\都𝖆\\👾都🙅𝖈—𝖈京𝖈都👾👾💁𝖇都🙅💁東京👾都東東
|
||||||
|
👾—東🙇東🙇東𝖇💁💁東京—\京𝖇東——𝖆京京🙅💁🙇💁東🙅𝖆𝖈東🙅東𝖇💁👾東\\𝖆
|
||||||
|
都🙅💁—🙅𝖈🙇𝖈👾𝖆🙇👾𝖆\東𝖆🙇𝖆𝖆🙇都🙇都京𝖈🙅—都👾👾𝖇💁🙇🙅\\🙅𝖇🙅👾
|
||||||
|
—\京東𝖇東京𝖈𝖆\京東👾🙅𝖆💁東𝖇都𝖈—東都🙇🙇—𝖈💁👾\💁—🙅💁\𝖇京𝖈🙅💁
|
||||||
|
\🙇𝖇💁都🙇京\🙇𝖈🙅都—𝖆🙇𝖇𝖈𝖇𝖇🙅🙅𝖇👾\東—🙅🙇💁京𝖇東東\𝖇💁𝖈都𝖆👾
|
||||||
|
東🙇\👾𝖇👾💁都𝖆\🙇👾𝖈👾👾東𝖆京👾東都💁🙇💁———京都京東京💁👾𝖈京💁🙇🙅𝖈
|
||||||
|
𝖈\𝖆👾東👾𝖇—京東京👾京💁東京🙅都京👾🙅𝖈京—東🙇東🙅都𝖇𝖇京都𝖇—\𝖇都🙅𝖆
|
||||||
|
𝖈👾—都東👾\—🙅𝖈\—👾🙅𝖆👾—🙇👾東都🙅—💁——京𝖇—𝖆🙇🙅👾💁𝖆都🙇🙅\𝖆
|
||||||
|
𝖇—東\𝖇𝖈𝖇𝖆都👾🙇京都🙇🙇\🙅—\🙇👾𝖇東—👾🙅𝖈🙇—東💁𝖈𝖈都都東🙅\🙇\
|
||||||
|
🙅都京京👾—👾🙅𝖈👾𝖈都東💁🙅東🙅👾🙇𝖇𝖇東𝖈𝖇𝖇👾—京𝖈東𝖈🙅京𝖇\🙅𝖇🙇—\
|
||||||
|
京💁—\\💁💁京—🙇👾💁𝖆👾🙇𝖇𝖈🙇京𝖆🙇𝖈🙇𝖇🙅𝖇𝖈𝖈都𝖈🙇𝖆𝖈—𝖈都𝖇京都💁
|
||||||
|
🙇\𝖈—𝖆東京都🙇\\—京𝖇𝖈𝖈🙇👾東🙅🙅—𝖆—京𝖈東\都𝖆🙅𝖆—𝖇🙇都—🙅東👾
|
||||||
|
\𝖈🙅🙇東\👾京—東👾𝖈𝖈💁𝖈💁𝖈𝖆—\\都東𝖆💁東𝖆𝖆都都𝖈🙇𝖇𝖆東都🙅—都𝖆
|
||||||
|
\🙇🙅𝖇🙅🙅𝖇👾—都\𝖇👾🙅京🙅𝖇💁𝖇都🙇\🙇🙅京都𝖈\—𝖆𝖇💁🙅🙅𝖈🙅都🙇\東
|
||||||
|
𝖆𝖈𝖈𝖆\𝖇都𝖆都\𝖈京𝖆𝖈𝖇𝖇𝖇💁👾🙅👾都👾\𝖈𝖇𝖈\\東👾都𝖈𝖇🙇👾京💁京🙅
|
||||||
|
𝖈東都🙅🙇——\👾𝖈𝖈東\—💁𝖇𝖈🙇👾—都🙅𝖈—𝖆💁🙅💁東👾𝖇𝖇𝖈都𝖆東𝖈𝖈京🙅
|
||||||
|
京🙇💁👾🙅👾𝖆𝖆𝖆都\\🙇🙇𝖈都👾都👾東𝖈🙇💁—🙇👾——𝖈𝖇🙅京—👾都💁🙇🙇𝖈\
|
||||||
|
𝖇都👾💁東京💁💁🙇🙇京🙅都👾👾👾𝖇𝖆𝖈—𝖆𝖆\𝖈💁—🙇🙅都𝖆𝖇𝖆—🙇𝖇𝖇東🙅\\
|
||||||
|
\東𝖈🙇𝖈𝖆𝖆𝖇💁👾—東𝖆—\𝖈\\\東\🙇🙇🙅𝖈都—𝖆👾—𝖇—🙇\𝖇都京𝖈東🙇
|
||||||
|
🙇京\——𝖆💁🙅—東💁𝖇𝖆𝖆𝖆𝖈🙇東🙇💁京💁𝖇𝖇—都都𝖇💁🙅\🙅𝖆東👾京京𝖆東\
|
||||||
|
👾東𝖆𝖇東—東都💁都𝖆\東𝖆京👾都🙅\𝖈𝖇—都👾都𝖇\東💁🙇💁—🙅都𝖆𝖆𝖆東\—
|
||||||
|
京🙇京京京東—💁\💁🙇𝖇京💁💁都都👾\京𝖈🙅京京京👾🙅👾👾京——👾京𝖈𝖇🙅𝖆𝖆👾
|
||||||
|
𝖈\東𝖆\🙇𝖇𝖆💁𝖈𝖈\🙇𝖈👾京𝖈𝖈𝖆🙅🙇🙅都🙅京𝖇東👾🙇🙇🙇💁🙇京京🙇—👾都👾
|
||||||
|
𝖆京𝖈都京👾—💁𝖆東𝖆\𝖆💁京👾\都💁都💁🙅東🙇𝖈京京🙇都🙅𝖆𝖈———💁👾🙅🙅𝖇
|
||||||
|
🙇💁𝖆𝖇𝖈𝖇👾都都京—𝖆💁🙇—\京都𝖆𝖇🙇\東東\🙇👾—𝖈𝖈🙅京東𝖆𝖇💁💁𝖇🙅𝖆
|
||||||
|
👾💁𝖆—👾🙅🙅𝖇\𝖈—🙅\🙇𝖈🙅𝖇東🙇👾👾𝖈👾𝖈\京東\\京𝖈🙅🙅🙇𝖇京𝖈𝖈\都
|
||||||
|
東𝖇💁\🙅—🙇東𝖆\💁💁𝖆𝖈💁—東京\\🙅\𝖈👾👾京京\👾\東𝖇𝖇𝖇👾—𝖇𝖆𝖈東
|
||||||
|
🙅東𝖈\👾𝖈💁𝖈𝖆——都\都—\🙇𝖇𝖈👾𝖇京京💁都𝖈🙇👾𝖈京𝖈🙅—都💁\🙇都👾京
|
||||||
|
🙇🙇𝖇𝖆💁🙇🙅👾都👾\👾𝖈💁💁💁—👾——𝖆\💁💁都京東𝖇京𝖆—🙇京\👾𝖇𝖈𝖆💁京
|
||||||
|
—🙅都東京—東🙅—𝖈\京🙇𝖇🙇𝖈💁𝖆𝖇👾𝖇🙇𝖇𝖇👾🙇𝖈🙇💁都👾💁東🙇東𝖆都𝖆京𝖇
|
||||||
|
𝖆東🙇東—𝖈𝖆—京都💁𝖈💁𝖆🙇東🙅京都都👾👾🙇\都🙅\𝖆\🙇—𝖆🙇—🙇京京𝖈京都
|
||||||
|
東𝖆東🙇🙇𝖇🙇💁🙅👾𝖇💁東💁🙇🙅💁𝖈💁𝖆𝖈京💁🙇𝖆東都🙇都🙅𝖆—🙇𝖇🙇東京💁\🙅
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Document</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Hello Swarm</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,129 @@
|
|||||||
|
/* http://meyerweb.com/eric/tools/css/reset/
|
||||||
|
v2.0 | 20110126
|
||||||
|
License: none (public domain)
|
||||||
|
*/
|
||||||
|
|
||||||
|
html,
|
||||||
|
body,
|
||||||
|
div,
|
||||||
|
span,
|
||||||
|
applet,
|
||||||
|
object,
|
||||||
|
iframe,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
p,
|
||||||
|
blockquote,
|
||||||
|
pre,
|
||||||
|
a,
|
||||||
|
abbr,
|
||||||
|
acronym,
|
||||||
|
address,
|
||||||
|
big,
|
||||||
|
cite,
|
||||||
|
code,
|
||||||
|
del,
|
||||||
|
dfn,
|
||||||
|
em,
|
||||||
|
img,
|
||||||
|
ins,
|
||||||
|
kbd,
|
||||||
|
q,
|
||||||
|
s,
|
||||||
|
samp,
|
||||||
|
small,
|
||||||
|
strike,
|
||||||
|
strong,
|
||||||
|
sub,
|
||||||
|
sup,
|
||||||
|
tt,
|
||||||
|
var,
|
||||||
|
b,
|
||||||
|
u,
|
||||||
|
i,
|
||||||
|
center,
|
||||||
|
dl,
|
||||||
|
dt,
|
||||||
|
dd,
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
li,
|
||||||
|
fieldset,
|
||||||
|
form,
|
||||||
|
label,
|
||||||
|
legend,
|
||||||
|
table,
|
||||||
|
caption,
|
||||||
|
tbody,
|
||||||
|
tfoot,
|
||||||
|
thead,
|
||||||
|
tr,
|
||||||
|
th,
|
||||||
|
td,
|
||||||
|
article,
|
||||||
|
aside,
|
||||||
|
canvas,
|
||||||
|
details,
|
||||||
|
embed,
|
||||||
|
figure,
|
||||||
|
figcaption,
|
||||||
|
footer,
|
||||||
|
header,
|
||||||
|
hgroup,
|
||||||
|
menu,
|
||||||
|
nav,
|
||||||
|
output,
|
||||||
|
ruby,
|
||||||
|
section,
|
||||||
|
summary,
|
||||||
|
time,
|
||||||
|
mark,
|
||||||
|
audio,
|
||||||
|
video {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
font-size: 100%;
|
||||||
|
font: inherit;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
/* HTML5 display-role reset for older browsers */
|
||||||
|
article,
|
||||||
|
aside,
|
||||||
|
details,
|
||||||
|
figcaption,
|
||||||
|
figure,
|
||||||
|
footer,
|
||||||
|
header,
|
||||||
|
hgroup,
|
||||||
|
menu,
|
||||||
|
nav,
|
||||||
|
section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
blockquote,
|
||||||
|
q {
|
||||||
|
quotes: none;
|
||||||
|
}
|
||||||
|
blockquote:before,
|
||||||
|
blockquote:after,
|
||||||
|
q:before,
|
||||||
|
q:after {
|
||||||
|
content: '';
|
||||||
|
content: none;
|
||||||
|
}
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
}
|
||||||
|
After Width: | Height: | Size: 380 KiB |
@@ -0,0 +1,40 @@
|
|||||||
|
336e98b027786ddbc75a7547a656257c11be32b2
|
||||||
|
ce319a535a6ddb677b6c7fae34712f83c83b93bc
|
||||||
|
e4333ee6a9debbfdd9f7c356823e4fd1b57b1483
|
||||||
|
c621227028f71042c3a06c2ef643964e5406053b
|
||||||
|
ba9f12ce05fa560db7e11c71193dc32c91028105
|
||||||
|
14238b8036ffb9784b302febdc620a21d7106a5c
|
||||||
|
0353c02dd103750ef8f4abf40f44d0d39ea671a3
|
||||||
|
22c1c57af8e0acebd934a3f589c0c50c10c3ff62
|
||||||
|
c8767d4a3ed86809eccb3a8a22b6e1c852f3304d
|
||||||
|
a96eb113125d74c3eed07c613e3963ed3176e9fc
|
||||||
|
3a065ad47daf1787d3180ec57da5d4505eacb7e6
|
||||||
|
eab477e8bbd6b00021be7fd130a711da404ea618
|
||||||
|
580ed3679dc4b54ba276780d511aa28c78468b22
|
||||||
|
c08d260a8517784ddc0f307c72c32002d43a2d77
|
||||||
|
1d4b02795cbdfa272708569e9555bb36235f4ed4
|
||||||
|
27ce7eccea98ccd5155e84365fe76c135754bac4
|
||||||
|
23f761cf8c7498ea0132af6d60d0398e5a51262f
|
||||||
|
79f6f7f1764807619743fa040b3daffe5ff35d2c
|
||||||
|
604b8fbb44ed9f6bd3c1c10ea6af0292c324bc5e
|
||||||
|
7bbdfc627fcf0de8dca782e3a9b19f58cbe0dbd2
|
||||||
|
c2eba4de403cdab30ab9e8ea37cb658b1b718ad1
|
||||||
|
44a5ecb1be4c2f15db635e2b7402ecc7241195f9
|
||||||
|
faaddaaf72566599562259927e8e4d12f4d9b678
|
||||||
|
653b01bd828cfc9ff53f6bad9170dc68ac42e27c
|
||||||
|
d8af098efa267ac46eccaeb6a4f4880fedba86a2
|
||||||
|
c3247313d96c56270ad97a0a46c5794dd0ac24f1
|
||||||
|
33156f08843da9654b64dc96178ff10b01c32463
|
||||||
|
0954bf8ba75246150bf365b02db71bc37d5f22db
|
||||||
|
a35900cf088b2fd24776cc70bc45279c807fc342
|
||||||
|
873e711026f74bf3eb77fa2c1910793315d9ff46
|
||||||
|
afc4b4cbd2553d8bf38ee811f1ff9b56185a6ea5
|
||||||
|
50a33df8d719d6cf3889d38f97e951a78374d509
|
||||||
|
5b3512cb132d939d06252cffe1546fc33dccd137
|
||||||
|
9a072613d3169e88612e22cd333cb00ee4591d89
|
||||||
|
f193d645f223ae6862f9a0a4fa8b40ffe9bcdbde
|
||||||
|
e7a7f7f9c12fb4f6bdf6a7ed232ca75653b4fe88
|
||||||
|
5cacba67f68e338e6674b4e6e684c3d5c8f3328d
|
||||||
|
0a11ddb543d050555ae41a1c778f392e8c7a49ea
|
||||||
|
1914cd178a06057847984b534b5d615f087b7178
|
||||||
|
8a5444ac835ca19972df95bcf5d2aaa6eabf49ff
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
🙅𝖆👾\都都𝖇🙅𝖆𝖈\𝖇—🙇\𝖆💁🙇東都東🙇\💁👾𝖆𝖆𝖆𝖇—東東京🙇𝖆𝖈💁🙇🙅🙅
|
||||||
|
都𝖆🙅\👾𝖈𝖈東🙇\都東𝖇𝖆東都\👾👾東東都👾👾\💁\𝖇𝖈𝖆🙇東都都\都👾💁🙇𝖈
|
||||||
|
👾\🙅東東京東都🙅—𝖇京京🙇💁—👾𝖇𝖇𝖇都👾東𝖆京👾京👾🙇都\𝖆—東東—京👾京東
|
||||||
|
—🙅都—𝖈𝖇—\\𝖆都💁💁🙇𝖈𝖈🙅—都\🙇𝖇💁𝖈𝖇\🙇𝖆💁👾京𝖆都都🙅—𝖈𝖇都𝖇
|
||||||
|
𝖇都\𝖈都👾💁𝖈🙇京👾京—𝖆𝖆🙅京𝖈\𝖇—京🙅💁𝖈—都\京都東東𝖆𝖇𝖇🙅\👾京🙅
|
||||||
|
東🙅\都𝖆—👾💁𝖈都🙅東—𝖆🙇𝖆🙇𝖈🙅京東𝖇𝖇🙅都𝖈東👾東🙇🙇🙇👾𝖆🙇👾—東—東
|
||||||
|
𝖆京都東🙅都𝖈🙇都\東🙅🙅👾𝖇𝖇👾𝖇👾👾👾京\—𝖇—🙅\𝖆𝖆京𝖆💁🙅京東𝖆🙇—💁
|
||||||
|
都東東🙇都👾𝖈𝖈𝖆𝖈👾東都💁京—𝖈都東🙇𝖈—🙇𝖈都\\東京🙅𝖇—𝖈🙇東𝖆𝖈東京👾
|
||||||
|
—𝖇—💁👾都👾𝖇—𝖈東京𝖇\都𝖆\\👾都🙅𝖈—𝖈京𝖈都👾👾💁𝖇都🙅💁東京👾都東東
|
||||||
|
👾—東🙇東🙇東𝖇💁💁東京—\京𝖇東——𝖆京京🙅💁🙇💁東🙅𝖆𝖈東🙅東𝖇💁👾東\\𝖆
|
||||||
|
都🙅💁—🙅𝖈🙇𝖈👾𝖆🙇👾𝖆\東𝖆🙇𝖆𝖆🙇都🙇都京𝖈🙅—都👾👾𝖇💁🙇🙅\\🙅𝖇🙅👾
|
||||||
|
—\京東𝖇東京𝖈𝖆\京東👾🙅𝖆💁東𝖇都𝖈—東都🙇🙇—𝖈💁👾\💁—🙅💁\𝖇京𝖈🙅💁
|
||||||
|
\🙇𝖇💁都🙇京\🙇𝖈🙅都—𝖆🙇𝖇𝖈𝖇𝖇🙅🙅𝖇👾\東—🙅🙇💁京𝖇東東\𝖇💁𝖈都𝖆👾
|
||||||
|
東🙇\👾𝖇👾💁都𝖆\🙇👾𝖈👾👾東𝖆京👾東都💁🙇💁———京都京東京💁👾𝖈京💁🙇🙅𝖈
|
||||||
|
𝖈\𝖆👾東👾𝖇—京東京👾京💁東京🙅都京👾🙅𝖈京—東🙇東🙅都𝖇𝖇京都𝖇—\𝖇都🙅𝖆
|
||||||
|
𝖈👾—都東👾\—🙅𝖈\—👾🙅𝖆👾—🙇👾東都🙅—💁——京𝖇—𝖆🙇🙅👾💁𝖆都🙇🙅\𝖆
|
||||||
|
𝖇—東\𝖇𝖈𝖇𝖆都👾🙇京都🙇🙇\🙅—\🙇👾𝖇東—👾🙅𝖈🙇—東💁𝖈𝖈都都東🙅\🙇\
|
||||||
|
🙅都京京👾—👾🙅𝖈👾𝖈都東💁🙅東🙅👾🙇𝖇𝖇東𝖈𝖇𝖇👾—京𝖈東𝖈🙅京𝖇\🙅𝖇🙇—\
|
||||||
|
京💁—\\💁💁京—🙇👾💁𝖆👾🙇𝖇𝖈🙇京𝖆🙇𝖈🙇𝖇🙅𝖇𝖈𝖈都𝖈🙇𝖆𝖈—𝖈都𝖇京都💁
|
||||||
|
🙇\𝖈—𝖆東京都🙇\\—京𝖇𝖈𝖈🙇👾東🙅🙅—𝖆—京𝖈東\都𝖆🙅𝖆—𝖇🙇都—🙅東👾
|
||||||
|
\𝖈🙅🙇東\👾京—東👾𝖈𝖈💁𝖈💁𝖈𝖆—\\都東𝖆💁東𝖆𝖆都都𝖈🙇𝖇𝖆東都🙅—都𝖆
|
||||||
|
\🙇🙅𝖇🙅🙅𝖇👾—都\𝖇👾🙅京🙅𝖇💁𝖇都🙇\🙇🙅京都𝖈\—𝖆𝖇💁🙅🙅𝖈🙅都🙇\東
|
||||||
|
𝖆𝖈𝖈𝖆\𝖇都𝖆都\𝖈京𝖆𝖈𝖇𝖇𝖇💁👾🙅👾都👾\𝖈𝖇𝖈\\東👾都𝖈𝖇🙇👾京💁京🙅
|
||||||
|
𝖈東都🙅🙇——\👾𝖈𝖈東\—💁𝖇𝖈🙇👾—都🙅𝖈—𝖆💁🙅💁東👾𝖇𝖇𝖈都𝖆東𝖈𝖈京🙅
|
||||||
|
京🙇💁👾🙅👾𝖆𝖆𝖆都\\🙇🙇𝖈都👾都👾東𝖈🙇💁—🙇👾——𝖈𝖇🙅京—👾都💁🙇🙇𝖈\
|
||||||
|
𝖇都👾💁東京💁💁🙇🙇京🙅都👾👾👾𝖇𝖆𝖈—𝖆𝖆\𝖈💁—🙇🙅都𝖆𝖇𝖆—🙇𝖇𝖇東🙅\\
|
||||||
|
\東𝖈🙇𝖈𝖆𝖆𝖇💁👾—東𝖆—\𝖈\\\東\🙇🙇🙅𝖈都—𝖆👾—𝖇—🙇\𝖇都京𝖈東🙇
|
||||||
|
🙇京\——𝖆💁🙅—東💁𝖇𝖆𝖆𝖆𝖈🙇東🙇💁京💁𝖇𝖇—都都𝖇💁🙅\🙅𝖆東👾京京𝖆東\
|
||||||
|
👾東𝖆𝖇東—東都💁都𝖆\東𝖆京👾都🙅\𝖈𝖇—都👾都𝖇\東💁🙇💁—🙅都𝖆𝖆𝖆東\—
|
||||||
|
京🙇京京京東—💁\💁🙇𝖇京💁💁都都👾\京𝖈🙅京京京👾🙅👾👾京——👾京𝖈𝖇🙅𝖆𝖆👾
|
||||||
|
𝖈\東𝖆\🙇𝖇𝖆💁𝖈𝖈\🙇𝖈👾京𝖈𝖈𝖆🙅🙇🙅都🙅京𝖇東👾🙇🙇🙇💁🙇京京🙇—👾都👾
|
||||||
|
𝖆京𝖈都京👾—💁𝖆東𝖆\𝖆💁京👾\都💁都💁🙅東🙇𝖈京京🙇都🙅𝖆𝖈———💁👾🙅🙅𝖇
|
||||||
|
🙇💁𝖆𝖇𝖈𝖇👾都都京—𝖆💁🙇—\京都𝖆𝖇🙇\東東\🙇👾—𝖈𝖈🙅京東𝖆𝖇💁💁𝖇🙅𝖆
|
||||||
|
👾💁𝖆—👾🙅🙅𝖇\𝖈—🙅\🙇𝖈🙅𝖇東🙇👾👾𝖈👾𝖈\京東\\京𝖈🙅🙅🙇𝖇京𝖈𝖈\都
|
||||||
|
東𝖇💁\🙅—🙇東𝖆\💁💁𝖆𝖈💁—東京\\🙅\𝖈👾👾京京\👾\東𝖇𝖇𝖇👾—𝖇𝖆𝖈東
|
||||||
|
🙅東𝖈\👾𝖈💁𝖈𝖆——都\都—\🙇𝖇𝖈👾𝖇京京💁都𝖈🙇👾𝖈京𝖈🙅—都💁\🙇都👾京
|
||||||
|
🙇🙇𝖇𝖆💁🙇🙅👾都👾\👾𝖈💁💁💁—👾——𝖆\💁💁都京東𝖇京𝖆—🙇京\👾𝖇𝖈𝖆💁京
|
||||||
|
—🙅都東京—東🙅—𝖈\京🙇𝖇🙇𝖈💁𝖆𝖇👾𝖇🙇𝖇𝖇👾🙇𝖈🙇💁都👾💁東🙇東𝖆都𝖆京𝖇
|
||||||
|
𝖆東🙇東—𝖈𝖆—京都💁𝖈💁𝖆🙇東🙅京都都👾👾🙇\都🙅\𝖆\🙇—𝖆🙇—🙇京京𝖈京都
|
||||||
|
東𝖆東🙇🙇𝖇🙇💁🙅👾𝖇💁東💁🙇🙅💁𝖈💁𝖆𝖈京💁🙇𝖆東都🙇都🙅𝖆—🙇𝖇🙇東京💁\🙅
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
const axios = require('axios')
|
||||||
|
const puppeteer = require('puppeteer')
|
||||||
|
const { Assert, Click, sleep, Wait } = require('./library')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
* @returns {Promise<string>} Swarm hash
|
||||||
|
*/
|
||||||
|
async function selectStampAndUpload(page) {
|
||||||
|
await Click.elementWithText(page, 'button', 'Add Postage Stamp')
|
||||||
|
// select the first available stamp
|
||||||
|
await Click.elementWithClass(page, 'div', '.MuiSelect-select')
|
||||||
|
await Click.elementWithClass(page, 'li', '.MuiListItem-button')
|
||||||
|
await Wait.forEnabledStateXPath(page, 'button', 'Proceed With Selected Stamp')
|
||||||
|
// seems necessary, even though button is enabled by the previous step, it is only highlighted and not clicked
|
||||||
|
await sleep(500)
|
||||||
|
await Click.elementWithText(page, 'button', 'Proceed With Selected Stamp')
|
||||||
|
await Click.elementWithText(page, 'button', 'Upload To Your Node')
|
||||||
|
// check if the upload was successful
|
||||||
|
await Assert.elementWithTextExists(page, 'button', 'Download')
|
||||||
|
await Assert.elementWithTextExists(page, 'button', 'Update Feed')
|
||||||
|
|
||||||
|
// get the swarm hash
|
||||||
|
return page.url().split('/').pop()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function assertUploadedContentAtPath(swarmHash, path, contentType) {
|
||||||
|
const response = await axios.get(`http://localhost:1633/bzz/${swarmHash}/${encodeURI(path)}`)
|
||||||
|
|
||||||
|
if (response.status !== 200) {
|
||||||
|
throw new Error(`Expected 200 OK, got ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.headers['content-type'] !== contentType) {
|
||||||
|
throw new Error(`Expected content-type ${contentType}, got ${response.headers['content-type']}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = response
|
||||||
|
|
||||||
|
if (data.length === 0) {
|
||||||
|
throw new Error(`Expected non-empty data, got ${data}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { selectStampAndUpload, assertUploadedContentAtPath }
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/* eslint-disable no-console */
|
||||||
|
const handler = require('serve-handler')
|
||||||
|
const http = require('http')
|
||||||
|
const puppeteer = require('puppeteer')
|
||||||
|
const { testFolderUpload } = require('./test-case/FolderUpload')
|
||||||
|
const { testImageFileUpload } = require('./test-case/ImageFileUpload')
|
||||||
|
const { testTextFileUpload } = require('./test-case/TextFileUpload')
|
||||||
|
const { testWebsiteUpload } = require('./test-case/WebsiteUpload')
|
||||||
|
const { testReactWebsiteUpload } = require('./test-case/ReactWebsiteUpload')
|
||||||
|
const { testUnicodeFileUpload } = require('./test-case/UnicodeFileUpload')
|
||||||
|
const { testUnicodeWebsiteUpload } = require('./test-case/UnicodeWebsiteUpload')
|
||||||
|
|
||||||
|
const VIEWPORT = { width: 1366, height: 768 }
|
||||||
|
|
||||||
|
const testCases = [
|
||||||
|
testUnicodeFileUpload,
|
||||||
|
testUnicodeWebsiteUpload,
|
||||||
|
testTextFileUpload,
|
||||||
|
testImageFileUpload,
|
||||||
|
testFolderUpload,
|
||||||
|
testWebsiteUpload,
|
||||||
|
testReactWebsiteUpload,
|
||||||
|
]
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const server = prepareServer()
|
||||||
|
const { browser, page } = await preparePage()
|
||||||
|
const beforeAll = Date.now()
|
||||||
|
for (const testCase of testCases) {
|
||||||
|
const before = Date.now()
|
||||||
|
console.log('\x1b[34m…\x1b[0m', 'Running', testCase.name)
|
||||||
|
await testCase(page)
|
||||||
|
const delta = Date.now() - before
|
||||||
|
console.log('\x1b[32m✔\x1b[0m', testCase.name, 'passed in', delta, 'ms')
|
||||||
|
}
|
||||||
|
const delta = Date.now() - beforeAll
|
||||||
|
console.log('\x1b[32m✔✔✔\x1b[0m', 'All', testCases.length, 'tests passed in', delta, 'ms')
|
||||||
|
await page.close()
|
||||||
|
await browser.close()
|
||||||
|
server.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {Promise<{ browser: puppeteer.Browser, page: puppeteer.Page }>}
|
||||||
|
*/
|
||||||
|
async function preparePage() {
|
||||||
|
const browser = await puppeteer.launch({
|
||||||
|
defaultViewport: null,
|
||||||
|
headless: false,
|
||||||
|
args: [`--window-size=${VIEWPORT.width},${VIEWPORT.height}`],
|
||||||
|
})
|
||||||
|
const page = await browser.newPage()
|
||||||
|
await page.goto('http://localhost:8080' || process.env.PORT, { waitUntil: 'networkidle0' })
|
||||||
|
|
||||||
|
return { browser, page }
|
||||||
|
}
|
||||||
|
|
||||||
|
function prepareServer() {
|
||||||
|
const serverConfig = {
|
||||||
|
public: 'build',
|
||||||
|
trailingSlash: false,
|
||||||
|
rewrites: [{ source: '**', destination: '/index.html' }],
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = http.createServer((request, response) => {
|
||||||
|
return handler(request, response, serverConfig)
|
||||||
|
})
|
||||||
|
server.listen(8080)
|
||||||
|
|
||||||
|
return server
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
@@ -0,0 +1,152 @@
|
|||||||
|
const puppeteer = require('puppeteer')
|
||||||
|
|
||||||
|
const SLEEP_MS = 500
|
||||||
|
const SLEEP_ITERATIONS = 20
|
||||||
|
|
||||||
|
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
* @param {string} selector XPath selector
|
||||||
|
*/
|
||||||
|
async function waitForElementXPath(page, selector) {
|
||||||
|
for (let i = 0; i < SLEEP_ITERATIONS; i++) {
|
||||||
|
const [element] = await page.$x(selector)
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
await sleep(SLEEP_MS)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
* @param {string} selector CSS selector
|
||||||
|
*/
|
||||||
|
async function waitForElementCss(page, selector) {
|
||||||
|
for (let i = 0; i < SLEEP_ITERATIONS; i++) {
|
||||||
|
const element = await page.$(selector)
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
await sleep(SLEEP_MS)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
* @param {string} elementType HTML tag name, e.g. `a`, `button`, `div`,
|
||||||
|
* @param {string} text e.g. `"Submit"`
|
||||||
|
*/
|
||||||
|
function waitForEnabledStateXPath(page, elementType, text) {
|
||||||
|
return waitForElementXPath(page, `//${elementType}[contains(., '${text}')][not(@disabled)]`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
* @param {string} elementType HTML tag name, e.g. `a`, `button`, `div`,
|
||||||
|
* @param {string} text e.g. `"Submit"`
|
||||||
|
*/
|
||||||
|
async function clickElementWithText(page, elementType, text) {
|
||||||
|
const element = await waitForElementXPath(page, `//${elementType}[contains(., '${text}')]`)
|
||||||
|
|
||||||
|
if (!element) {
|
||||||
|
throw Error(`clickElementWithText: Could not find <${elementType}> containing "${text}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
await element.click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
* @param {string} elementType HTML tag name, e.g. `a`, `button`, `div`,
|
||||||
|
* @param {string} text e.g. `"Submit"`
|
||||||
|
* @param {string} filePath e.g. `"test-data/text.txt"`
|
||||||
|
*/
|
||||||
|
async function clickElementWithTextAndUpload(page, elementType, text, filePath) {
|
||||||
|
const [fileChooser] = await Promise.all([page.waitForFileChooser(), clickElementWithText(page, elementType, text)])
|
||||||
|
await fileChooser.accept([filePath])
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
* @param {string} elementType HTML tag name, e.g. `a`, `button`, `div`,
|
||||||
|
* @param {string} cssClass CSS class with the dot, e.g. '.MuiSelect-select'
|
||||||
|
*/
|
||||||
|
async function clickElementWithClass(page, elementType, cssClass) {
|
||||||
|
const element = await waitForElementCss(page, `${elementType}${cssClass}`)
|
||||||
|
|
||||||
|
if (!element) {
|
||||||
|
throw Error(`clickElementWithClass: Could not find <${elementType}> with class ${cssClass}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
await element.click()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
* @param {string} elementType HTML tag name, e.g. `a`, `button`, `div`,
|
||||||
|
* @param {string} text e.g. `"Submit"`
|
||||||
|
*/
|
||||||
|
async function assertElementWithTextExists(page, elementType, text) {
|
||||||
|
const element = await waitForElementXPath(page, `//${elementType}[contains(., '${text}')]`)
|
||||||
|
|
||||||
|
if (!element) {
|
||||||
|
throw Error(`assertElementWithTextExists: Could not find <${elementType}> containing "${text}"`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
* @param {string} elementType HTML tag name, e.g. `a`, `button`, `div`,
|
||||||
|
* @param {string} attribute HTML attribute, e.g. `alt` or `id`
|
||||||
|
* @param {string} value Expected value of the attribute
|
||||||
|
* @param {string} property Property to return from the queried HTML element
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
async function queryElementPropertyByAttribute(page, elementType, attribute, value, property) {
|
||||||
|
const element = await waitForElementXPath(page, `//${elementType}[contains(@${attribute}, "${value}")]`)
|
||||||
|
|
||||||
|
return page.evaluate((element, property) => element[property], element, property)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
* @param {string} alt `<img>` `alt` to look for
|
||||||
|
*/
|
||||||
|
function queryImgSrcByAlt(page, alt) {
|
||||||
|
return queryElementPropertyByAttribute(page, 'img', 'alt', alt, 'src')
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
sleep,
|
||||||
|
Assert: {
|
||||||
|
elementWithTextExists: assertElementWithTextExists,
|
||||||
|
},
|
||||||
|
Click: {
|
||||||
|
elementWithText: clickElementWithText,
|
||||||
|
elementWithClass: clickElementWithClass,
|
||||||
|
elementWithTextAndUpload: clickElementWithTextAndUpload,
|
||||||
|
},
|
||||||
|
Query: {
|
||||||
|
elementPropertyByAttribute: queryElementPropertyByAttribute,
|
||||||
|
imgSrcByAlt: queryImgSrcByAlt,
|
||||||
|
},
|
||||||
|
Wait: {
|
||||||
|
forElementCss: waitForElementCss,
|
||||||
|
forElementXPath: waitForElementXPath,
|
||||||
|
forEnabledStateXPath: waitForEnabledStateXPath,
|
||||||
|
},
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
const puppeteer = require('puppeteer')
|
||||||
|
const { selectStampAndUpload } = require('../helpers')
|
||||||
|
const { Assert, Click } = require('../library')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
*/
|
||||||
|
async function testFolderUpload(page) {
|
||||||
|
await Click.elementWithText(page, 'a', 'Files')
|
||||||
|
await Click.elementWithTextAndUpload(page, 'button', 'Add Folder', 'test-data/test-folder')
|
||||||
|
await assertUploadPreview(page)
|
||||||
|
await selectStampAndUpload(page)
|
||||||
|
await assertDownloadPreview(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
*/
|
||||||
|
async function assertDownloadPreview(page) {
|
||||||
|
await assertUploadPreview(page)
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Swarm Hash: 89ef8f3e[…]504e5d1c')
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Folder Name: test-folder')
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Kind: Folder')
|
||||||
|
await Assert.elementWithTextExists(page, 'h6', '4 items')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
*/
|
||||||
|
async function assertUploadPreview(page) {
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Folder Name: test-folder')
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Kind: Folder')
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Size: 6.56 kB')
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { testFolderUpload }
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
const puppeteer = require('puppeteer')
|
||||||
|
const { Assert, Click } = require('../library')
|
||||||
|
const { selectStampAndUpload } = require('../helpers')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
*/
|
||||||
|
async function testImageFileUpload(page) {
|
||||||
|
await Click.elementWithText(page, 'a', 'Files')
|
||||||
|
await Click.elementWithTextAndUpload(page, 'button', 'Add File', 'test-data/1337x1337.jpg')
|
||||||
|
await assertUploadPreview(page)
|
||||||
|
await selectStampAndUpload(page)
|
||||||
|
await assertDownloadPreview(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
*/
|
||||||
|
async function assertUploadPreview(page) {
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Filename: 1337x1337.jpg')
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Kind: image/jpeg')
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Size: 116.88 kB')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
*/
|
||||||
|
async function assertDownloadPreview(page) {
|
||||||
|
await assertUploadPreview(page)
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Swarm Hash: 5de1d879[…]3427432d')
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { testImageFileUpload }
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
const puppeteer = require('puppeteer')
|
||||||
|
const { selectStampAndUpload, assertUploadedContentAtPath } = require('../helpers')
|
||||||
|
const { Click } = require('../library')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
*/
|
||||||
|
async function testReactWebsiteUpload(page) {
|
||||||
|
await Click.elementWithText(page, 'a', 'Files')
|
||||||
|
await Click.elementWithTextAndUpload(page, 'button', 'Add Website', 'test-data/test-react-website')
|
||||||
|
const swarmHash = await selectStampAndUpload(page)
|
||||||
|
await assertUploadedContentAtPath(swarmHash, 'index.html', 'text/html; charset=utf-8')
|
||||||
|
await assertUploadedContentAtPath(swarmHash, 'asset-manifest.json', 'application/json')
|
||||||
|
await assertUploadedContentAtPath(swarmHash, 'static/css/main.073c9b0a.css', 'text/css; charset=utf-8')
|
||||||
|
await assertUploadedContentAtPath(swarmHash, 'static/css/main.073c9b0a.css.map', '')
|
||||||
|
await assertUploadedContentAtPath(swarmHash, 'static/js/787.28cb0dcd.chunk.js', 'application/javascript')
|
||||||
|
await assertUploadedContentAtPath(swarmHash, 'static/js/787.28cb0dcd.chunk.js.map', '')
|
||||||
|
await assertUploadedContentAtPath(
|
||||||
|
swarmHash,
|
||||||
|
'static/media/logo.6ce24c58023cc2f8fd88fe9d219db6c6.svg',
|
||||||
|
'image/svg+xml',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { testReactWebsiteUpload }
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
const puppeteer = require('puppeteer')
|
||||||
|
const { selectStampAndUpload } = require('../helpers')
|
||||||
|
const { Assert, Click } = require('../library')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
*/
|
||||||
|
async function testTextFileUpload(page) {
|
||||||
|
await Click.elementWithText(page, 'a', 'Files')
|
||||||
|
await Click.elementWithTextAndUpload(page, 'button', 'Add File', 'test-data/text.txt')
|
||||||
|
await assertUploadPreview(page)
|
||||||
|
await selectStampAndUpload(page)
|
||||||
|
await assertDownloadPreview(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
*/
|
||||||
|
async function assertUploadPreview(page) {
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Filename: text.txt')
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Kind: text/plain')
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Size: 1.64 kB')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
*/
|
||||||
|
async function assertDownloadPreview(page) {
|
||||||
|
assertUploadPreview(page)
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Swarm Hash: da0773a9[…]5f7a1b54')
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { testTextFileUpload }
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
const puppeteer = require('puppeteer')
|
||||||
|
const { selectStampAndUpload } = require('../helpers')
|
||||||
|
const { Assert, Click } = require('../library')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
*/
|
||||||
|
async function testUnicodeFileUpload(page) {
|
||||||
|
await Click.elementWithText(page, 'a', 'Files')
|
||||||
|
await Click.elementWithTextAndUpload(page, 'button', 'Add File', 'test-data/—𝖆𝖆🙇\\𝖈𝖈.txt')
|
||||||
|
await assertUploadPreview(page)
|
||||||
|
await selectStampAndUpload(page)
|
||||||
|
await assertDownloadPreview(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
*/
|
||||||
|
async function assertUploadPreview(page) {
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Filename: —𝖆𝖆🙇\\𝖈𝖈.txt')
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Kind: text/plain')
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Size: 5.51 kB')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
*/
|
||||||
|
async function assertDownloadPreview(page) {
|
||||||
|
assertUploadPreview(page)
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Swarm Hash: 30fbf1d2[…]ba400e86')
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { testUnicodeFileUpload }
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
const puppeteer = require('puppeteer')
|
||||||
|
const { selectStampAndUpload, assertUploadedContentAtPath } = require('../helpers')
|
||||||
|
const { Click } = require('../library')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
*/
|
||||||
|
async function testUnicodeWebsiteUpload(page) {
|
||||||
|
await Click.elementWithText(page, 'a', 'Files')
|
||||||
|
await Click.elementWithTextAndUpload(page, 'button', 'Add Website', 'test-data/test-unicode-website—𝖆𝖆🙇\\𝖈𝖈')
|
||||||
|
const swarmHash = await selectStampAndUpload(page)
|
||||||
|
await assertUploadedContentAtPath(swarmHash, 'index.html', 'text/html; charset=utf-8')
|
||||||
|
await assertUploadedContentAtPath(swarmHash, '—𝖆𝖆🙇/𝖈𝖈.txt', 'text/plain; charset=utf-8')
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { testUnicodeWebsiteUpload }
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
const puppeteer = require('puppeteer')
|
||||||
|
const { selectStampAndUpload } = require('../helpers')
|
||||||
|
const { Assert, Click } = require('../library')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
*/
|
||||||
|
async function testWebsiteUpload(page) {
|
||||||
|
await Click.elementWithText(page, 'a', 'Files')
|
||||||
|
await Click.elementWithTextAndUpload(page, 'button', 'Add Website', 'test-data/test-website')
|
||||||
|
await assertUploadPreview(page)
|
||||||
|
await selectStampAndUpload(page)
|
||||||
|
await assertDownloadPreview(page)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
*/
|
||||||
|
async function assertUploadPreview(page) {
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Folder Name: test-website')
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Kind: Website')
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Size: 390.10 kB')
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {puppeteer.Page} page Puppeteer Page object returned by `browser.newPage()`
|
||||||
|
*/
|
||||||
|
async function assertDownloadPreview(page) {
|
||||||
|
await assertUploadPreview(page)
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Swarm Hash: b9a6d15d[…]d0d48b81')
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Folder Name: test-website')
|
||||||
|
await Assert.elementWithTextExists(page, 'p', 'Kind: Website')
|
||||||
|
await Assert.elementWithTextExists(page, 'h6', '3 items')
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { testWebsiteUpload }
|
||||||
|
Before Width: | Height: | Size: 97 KiB After Width: | Height: | Size: 71 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 115 KiB After Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 108 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 135 KiB After Width: | Height: | Size: 115 KiB |
|
Before Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 61 KiB |
@@ -35,18 +35,9 @@ module.exports = () => {
|
|||||||
use: ['style-loader', 'css-loader'],
|
use: ['style-loader', 'css-loader'],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(png|jp(e*)g|svg|gif)$/,
|
test: /\.(jpe?g|png|ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/,
|
||||||
loader: 'file-loader',
|
use: ['base64-inline-loader'],
|
||||||
options: {
|
type: 'javascript/auto'
|
||||||
name: 'assets/[name].[ext]',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(ttf)$/,
|
|
||||||
loader: 'file-loader',
|
|
||||||
options: {
|
|
||||||
name: 'assets/fonts/[name].[ext]',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.(ts|js|tsx|jsx)$/,
|
test: /\.(ts|js|tsx|jsx)$/,
|
||||||
|
|||||||