Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d7867ff475 | |||
| b9c008f019 | |||
| a7bd94af82 | |||
| 1be5cbda6d | |||
| 4c48657fca | |||
| 72488fd5a3 | |||
| 896f6e48d9 | |||
| f53e9664da | |||
| ff5b832017 | |||
| 9f0ab1323b | |||
| c9384ff23e | |||
| f8390d7eac | |||
| 408b565935 | |||
| f82444f212 | |||
| fd11f0166d | |||
| 186d0352cf | |||
| f01477ea70 | |||
| 6bfe97be5d | |||
| feeca008ac | |||
| cba21bb2e0 | |||
| 318592653c | |||
| 786d624e18 | |||
| 33fff93cac | |||
| 498294e227 |
@@ -14,6 +14,7 @@
|
||||
"crypto*",
|
||||
"stream*",
|
||||
"env-paths",
|
||||
"open"
|
||||
"open",
|
||||
"base64-inline-loader"
|
||||
]
|
||||
}
|
||||
@@ -19,6 +19,10 @@ jobs:
|
||||
- run: npm publish --access public
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
||||
- id: cleanVersion
|
||||
run: |
|
||||
version="${{ github.event.release.release.tag_name }}"
|
||||
echo "::set-output name=value::${version/v}"
|
||||
- name: Create Sentry release
|
||||
uses: getsentry/action-release@v1
|
||||
env:
|
||||
@@ -27,4 +31,4 @@ jobs:
|
||||
SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }}
|
||||
with:
|
||||
sourcemaps: ./build/static/js
|
||||
version: ${{ github.ref }}
|
||||
version: ${{ steps.cleanVersion.outputs.value }}
|
||||
|
||||
@@ -1,5 +1,37 @@
|
||||
# Changelog
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* don't link to latest release ([#477](https://github.com/ethersphere/bee-dashboard/issues/477)) ([498294e](https://github.com/ethersphere/bee-dashboard/commit/498294e227baa52c59adecf9c4cfd205061ddf75))
|
||||
* enable desktop update notifications on all platforms ([#476](https://github.com/ethersphere/bee-dashboard/issues/476)) ([33fff93](https://github.com/ethersphere/bee-dashboard/commit/33fff93cac31ec54b02f9c7d0c90c13c8d3763c7))
|
||||
|
||||
## [0.18.1](https://github.com/ethersphere/bee-dashboard/compare/v0.18.0...v0.18.1) (2022-07-05)
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
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
|
||||
[official Discord](https://discord.gg/GU22h2utj6) and by keeping an eye on the
|
||||
[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
|
||||
|
||||
@@ -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.
|
||||
|
||||
#### 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
|
||||
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 run desktop # This will inject the API key to the Dashboard
|
||||
```
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@ethersphere/bee-dashboard",
|
||||
"version": "0.18.1",
|
||||
"version": "0.19.0",
|
||||
"description": "An app which helps users to setup their Bee node and do actions like cash out cheques",
|
||||
"keywords": [
|
||||
"bee",
|
||||
@@ -26,7 +26,7 @@
|
||||
"url": "https://github.com/ethersphere/bee-dashboard.git"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ethersphere/bee-js": "^4.1.1",
|
||||
"@ethersphere/bee-js": "^5.0.0",
|
||||
"@ethersphere/manifest-js": "1.2.1",
|
||||
"@ethersphere/swarm-cid": "^0.1.0",
|
||||
"@material-ui/core": "4.12.3",
|
||||
@@ -50,9 +50,9 @@
|
||||
"notistack": "1.0.10",
|
||||
"opener": "1.5.2",
|
||||
"qrcode.react": "1.0.1",
|
||||
"react": ">= 17.0.2",
|
||||
"react": ">=17.0.0 || >=18.0.0",
|
||||
"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-router": "6.2.1",
|
||||
"react-router-dom": "6.2.1",
|
||||
@@ -92,6 +92,7 @@
|
||||
"babel-loader": "8.1.0",
|
||||
"babel-plugin-syntax-dynamic-import": "6.18.0",
|
||||
"babel-plugin-tsconfig-paths": "1.0.2",
|
||||
"base64-inline-loader": "^2.0.1",
|
||||
"cors": "^2.8.5",
|
||||
"depcheck": "^1.4.3",
|
||||
"env-paths": "^3.0.0",
|
||||
@@ -110,7 +111,9 @@
|
||||
"file-loader": "6.2.0",
|
||||
"open": "^8.4.0",
|
||||
"prettier": "2.4.1",
|
||||
"puppeteer": "^15.4.0",
|
||||
"react-scripts": "^5.0.1",
|
||||
"rimraf": "^3.0.2",
|
||||
"ts-node": "^10.8.1",
|
||||
"typescript": "4.7.3",
|
||||
"web-vitals": "2.1.2",
|
||||
@@ -126,9 +129,10 @@
|
||||
"start": "react-scripts start",
|
||||
"desktop": "node ./desktop.mjs",
|
||||
"build": "react-scripts build",
|
||||
"build:component": "webpack --mode=production",
|
||||
"build:component": "rimraf ./lib && webpack --mode=production",
|
||||
"compile:types": "tsc --project tsconfig.lib.json --emitDeclarationOnly --declaration",
|
||||
"test": "react-scripts test",
|
||||
"test:ui": "node ui-test/index.js",
|
||||
"serve": "node ./serve.js",
|
||||
"depcheck": "depcheck .",
|
||||
"lint": "eslint --fix \"src/**/*.ts\" \"src/**/*.tsx\" && prettier --write \"src/**/*.ts\" \"src/**/*.tsx\"",
|
||||
@@ -154,7 +158,7 @@
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0",
|
||||
"node": ">=14.0.0",
|
||||
"npm": ">=6.9.0",
|
||||
"bee": ">=0.6.0"
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ import { Provider as PlatformProvider } from './providers/Platform'
|
||||
import { Provider as SettingsProvider } from './providers/Settings'
|
||||
import { Provider as StampsProvider } from './providers/Stamps'
|
||||
import { Provider as TopUpProvider } from './providers/TopUp'
|
||||
import { Provider as BalanceProvider } from './providers/WalletBalance'
|
||||
import BaseRouter from './routes'
|
||||
import { theme } from './theme'
|
||||
import { config } from './config'
|
||||
@@ -23,6 +24,7 @@ interface Props {
|
||||
beeApiUrl?: string
|
||||
beeDebugApiUrl?: string
|
||||
lockedApiSettings?: boolean
|
||||
isBeeDesktop?: boolean
|
||||
}
|
||||
|
||||
if (config.SENTRY_KEY) {
|
||||
@@ -30,31 +32,38 @@ if (config.SENTRY_KEY) {
|
||||
initSentry().catch(e => console.error(e))
|
||||
}
|
||||
|
||||
const App = ({ beeApiUrl, beeDebugApiUrl, lockedApiSettings }: Props): ReactElement => {
|
||||
const App = ({ beeApiUrl, beeDebugApiUrl, lockedApiSettings, isBeeDesktop }: Props): ReactElement => {
|
||||
const mainApp = (
|
||||
<div className="App">
|
||||
<ThemeProvider theme={theme}>
|
||||
<SettingsProvider beeApiUrl={beeApiUrl} beeDebugApiUrl={beeDebugApiUrl} lockedApiSettings={lockedApiSettings}>
|
||||
<SettingsProvider
|
||||
beeApiUrl={beeApiUrl}
|
||||
beeDebugApiUrl={beeDebugApiUrl}
|
||||
lockedApiSettings={lockedApiSettings}
|
||||
isBeeDesktop={isBeeDesktop}
|
||||
>
|
||||
<TopUpProvider>
|
||||
<BeeProvider>
|
||||
<StampsProvider>
|
||||
<FileProvider>
|
||||
<FeedsProvider>
|
||||
<PlatformProvider>
|
||||
<SnackbarProvider anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}>
|
||||
<Router>
|
||||
<>
|
||||
<CssBaseline />
|
||||
<Dashboard>
|
||||
<BaseRouter />
|
||||
</Dashboard>
|
||||
</>
|
||||
</Router>
|
||||
</SnackbarProvider>
|
||||
</PlatformProvider>
|
||||
</FeedsProvider>
|
||||
</FileProvider>
|
||||
</StampsProvider>
|
||||
<BalanceProvider>
|
||||
<StampsProvider>
|
||||
<FileProvider>
|
||||
<FeedsProvider>
|
||||
<PlatformProvider>
|
||||
<SnackbarProvider preventDuplicate anchorOrigin={{ horizontal: 'right', vertical: 'bottom' }}>
|
||||
<Router>
|
||||
<>
|
||||
<CssBaseline />
|
||||
<Dashboard>
|
||||
<BaseRouter />
|
||||
</Dashboard>
|
||||
</>
|
||||
</Router>
|
||||
</SnackbarProvider>
|
||||
</PlatformProvider>
|
||||
</FeedsProvider>
|
||||
</FileProvider>
|
||||
</StampsProvider>
|
||||
</BalanceProvider>
|
||||
</BeeProvider>
|
||||
</TopUpProvider>
|
||||
</SettingsProvider>
|
||||
|
||||
@@ -48,6 +48,7 @@ export default function CheckoutModal({ peerId, uncashedAmount }: Props): ReactE
|
||||
)
|
||||
})
|
||||
.catch((e: Error) => {
|
||||
console.error(e) // eslint-disable-line
|
||||
enqueueSnackbar(<span>Error: {e.message}</span>, { variant: 'error' })
|
||||
})
|
||||
.finally(() => {
|
||||
|
||||
@@ -9,11 +9,10 @@ import HomeIcon from 'remixicon-react/Home3LineIcon'
|
||||
import SettingsIcon from 'remixicon-react/Settings2LineIcon'
|
||||
import AccountIcon from 'remixicon-react/Wallet3LineIcon'
|
||||
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 DesktopLogo from '../assets/desktop-logo.svg'
|
||||
import { config } from '../config'
|
||||
import { useIsBeeDesktop } from '../hooks/apiHooks'
|
||||
import { ROUTES } from '../routes'
|
||||
import Feedback from './Feedback'
|
||||
import SideBarItem from './SideBarItem'
|
||||
@@ -68,8 +67,7 @@ const useStyles = makeStyles((theme: Theme) =>
|
||||
|
||||
export default function SideBar(): ReactElement {
|
||||
const classes = useStyles()
|
||||
const { isBeeDesktop } = useIsBeeDesktop()
|
||||
const { providerUrl } = useContext(TopUpContext)
|
||||
const { isBeeDesktop } = useContext(SettingsContext)
|
||||
const { nodeInfo } = useContext(BeeContext)
|
||||
|
||||
const navBarItems = [
|
||||
@@ -86,7 +84,7 @@ export default function SideBar(): ReactElement {
|
||||
},
|
||||
{
|
||||
label: 'Account',
|
||||
path: providerUrl === null ? ROUTES.WALLET : ROUTES.ACCOUNT_WALLET,
|
||||
path: ROUTES.ACCOUNT_WALLET,
|
||||
icon: AccountIcon,
|
||||
pathMatcherSubstring: '/account/',
|
||||
},
|
||||
|
||||
@@ -55,6 +55,7 @@ export default function WithdrawDepositModal({
|
||||
setOpen(false)
|
||||
enqueueSnackbar(`${successMessage} Transaction ${transactionHash}`, { variant: 'success' })
|
||||
} catch (e) {
|
||||
console.error(e) // eslint-disable-line
|
||||
enqueueSnackbar(`${errorMessage} Error: ${(e as Error).message}`, { variant: 'error' })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ class Config {
|
||||
public readonly BEE_DOCS_HOST: string
|
||||
public readonly BEE_DISCORD_HOST: string
|
||||
public readonly GITHUB_REPO_URL: string
|
||||
public readonly BEE_DESKTOP_ENABLED: boolean
|
||||
public readonly BEE_DESKTOP_URL: string
|
||||
public readonly SENTRY_KEY: string | undefined
|
||||
public readonly SENTRY_ENVIRONMENT: string | undefined
|
||||
public readonly DEFAULT_RPC_URL: string
|
||||
|
||||
constructor() {
|
||||
this.BEE_API_HOST = sessionStorage.getItem('api_host') ?? process.env.REACT_APP_BEE_HOST ?? 'http://localhost:1633'
|
||||
@@ -20,7 +22,9 @@ class Config {
|
||||
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.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.DEFAULT_RPC_URL = process.env.REACT_APP_DEFAULT_RPC_URL ?? 'https://xdai.fairdatasociety.org'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ interface AddressInfo {
|
||||
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()
|
||||
app.use(cors())
|
||||
|
||||
@@ -26,48 +26,28 @@ export function mockServer(data: Record<string | number | symbol, string>): Prom
|
||||
}
|
||||
|
||||
let serverCorrect: Server
|
||||
let serverWrong: Server
|
||||
|
||||
let serverCorrectURL: string
|
||||
let serverWrongURL: string
|
||||
|
||||
beforeAll(async () => {
|
||||
serverCorrect = await mockServer({ name: 'bee-desktop' })
|
||||
serverCorrect = await mockServer({ autoUpdateEnabled: true, version: '0.1.0' })
|
||||
const portServerCorrect = (serverCorrect.address() as AddressInfo).port
|
||||
serverCorrectURL = `http://localhost:${portServerCorrect}`
|
||||
|
||||
serverWrong = await mockServer({ foo: 'bar' })
|
||||
const portServerWrong = (serverWrong.address() as AddressInfo).port
|
||||
serverWrongURL = `http://localhost:${portServerWrong}`
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await new Promise(resolve => serverCorrect.close(resolve))
|
||||
await new Promise(resolve => serverWrong.close(resolve))
|
||||
})
|
||||
|
||||
describe('useIsBeeDesktop', () => {
|
||||
it('should fail when connected to wrong server', async () => {
|
||||
const { result, waitFor } = renderHook(() => useIsBeeDesktop({ BEE_DESKTOP_URL: serverWrongURL }))
|
||||
|
||||
expect(result.current.isLoading).toBe(true)
|
||||
expect(result.current.isBeeDesktop).toBe(false)
|
||||
it('should not have error when connected to bee-desktop', async () => {
|
||||
const { result, waitFor } = renderHook(() => useIsBeeDesktop(true, { BEE_DESKTOP_URL: serverCorrectURL }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(result.current.isLoading).toBe(false)
|
||||
})
|
||||
expect(result.current.isBeeDesktop).toBe(false)
|
||||
})
|
||||
|
||||
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)
|
||||
expect(result.current.desktopAutoUpdateEnabled).toBe(true)
|
||||
expect(result.current.beeDesktopVersion).toBe('0.1.0')
|
||||
expect(result.current.error).toBe(null)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -11,7 +11,7 @@ export interface LatestBeeReleaseHook {
|
||||
}
|
||||
|
||||
export interface IsBeeDesktopHook {
|
||||
isBeeDesktop: boolean
|
||||
error: Error | null
|
||||
isLoading: boolean
|
||||
beeDesktopVersion: string
|
||||
desktopAutoUpdateEnabled: boolean
|
||||
@@ -30,31 +30,34 @@ interface Config {
|
||||
*
|
||||
* @returns isBeeDesktop true if this is run within bee-desktop
|
||||
*/
|
||||
export const useIsBeeDesktop = (conf: Config = config): IsBeeDesktopHook => {
|
||||
const [isBeeDesktop, setIsBeeDesktop] = useState<boolean>(false)
|
||||
export const useIsBeeDesktop = (isBeeDesktop = false, conf: Config = config): IsBeeDesktopHook => {
|
||||
const [desktopAutoUpdateEnabled, setDesktopAutoUpdateEnabled] = useState<boolean>(true)
|
||||
const [beeDesktopVersion, setBeeDesktopVersion] = useState<string>('')
|
||||
const [isLoading, setLoading] = useState<boolean>(true)
|
||||
const [error, setError] = useState<Error | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
axios
|
||||
.get(`${conf.BEE_DESKTOP_URL}/info`)
|
||||
.then(res => {
|
||||
if (res.data?.name === 'bee-desktop') {
|
||||
setIsBeeDesktop(true)
|
||||
if (!isBeeDesktop) {
|
||||
setLoading(false)
|
||||
setError(null)
|
||||
} else {
|
||||
axios
|
||||
.get(`${conf.BEE_DESKTOP_URL}/info`)
|
||||
.then(res => {
|
||||
setBeeDesktopVersion(res.data?.version)
|
||||
setDesktopAutoUpdateEnabled(res.data?.autoUpdateEnabled)
|
||||
} else setIsBeeDesktop(false)
|
||||
})
|
||||
.catch(() => {
|
||||
setIsBeeDesktop(false)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}, [conf])
|
||||
setError(null)
|
||||
})
|
||||
.catch(e => {
|
||||
setError(e)
|
||||
})
|
||||
.finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
}, [conf, isBeeDesktop])
|
||||
|
||||
return { isBeeDesktop, isLoading, beeDesktopVersion, desktopAutoUpdateEnabled }
|
||||
return { error, isLoading, beeDesktopVersion, desktopAutoUpdateEnabled }
|
||||
}
|
||||
|
||||
async function checkNewVersion(conf: Config): Promise<string> {
|
||||
@@ -70,7 +73,7 @@ async function checkNewVersion(conf: Config): Promise<string> {
|
||||
}
|
||||
|
||||
export function useNewBeeDesktopVersion(isBeeDesktop: boolean, conf: Config = config): NewDesktopVersionHook {
|
||||
const [newBeeDesktopVersion, setNewNewBeeDesktopVersion] = useState<string>('')
|
||||
const [newBeeDesktopVersion, setNewBeeDesktopVersion] = useState<string>('')
|
||||
|
||||
useEffect(() => {
|
||||
if (!isBeeDesktop) {
|
||||
@@ -79,7 +82,7 @@ export function useNewBeeDesktopVersion(isBeeDesktop: boolean, conf: Config = co
|
||||
|
||||
checkNewVersion(conf).then(version => {
|
||||
if (version !== '') {
|
||||
setNewNewBeeDesktopVersion(version)
|
||||
setNewBeeDesktopVersion(version)
|
||||
}
|
||||
})
|
||||
}, [isBeeDesktop, conf])
|
||||
|
||||
@@ -5,11 +5,12 @@ import { useSnackbar } from 'notistack'
|
||||
import CloseIcon from 'remixicon-react/CloseCircleLineIcon'
|
||||
import ErrorBoundary from '../components/ErrorBoundary'
|
||||
import SideBar from '../components/SideBar'
|
||||
import { Context } from '../providers/Bee'
|
||||
import { Context as BeeContext } from '../providers/Bee'
|
||||
import { Context as SettingsContext } from '../providers/Settings'
|
||||
import config from '../config'
|
||||
import * as Sentry from '@sentry/react'
|
||||
import ItsBroken from './ItsBroken'
|
||||
import { useIsBeeDesktop, useNewBeeDesktopVersion } from '../hooks/apiHooks'
|
||||
import { useNewBeeDesktopVersion } from '../hooks/apiHooks'
|
||||
import { BEE_DESKTOP_LATEST_RELEASE_PAGE } from '../utils/desktop'
|
||||
|
||||
const useStyles = makeStyles((theme: Theme) =>
|
||||
@@ -28,13 +29,54 @@ interface Props {
|
||||
const Dashboard = (props: Props): ReactElement => {
|
||||
const classes = useStyles()
|
||||
|
||||
const { isLoading } = useContext(Context)
|
||||
const { isBeeDesktop, desktopAutoUpdateEnabled } = useIsBeeDesktop()
|
||||
const { isLoading, isLatestBeeVersion, latestBeeRelease, latestBeeVersionUrl, latestUserVersion } =
|
||||
useContext(BeeContext)
|
||||
const { isBeeDesktop } = useContext(SettingsContext)
|
||||
const { newBeeDesktopVersion } = useNewBeeDesktopVersion(isBeeDesktop)
|
||||
const { enqueueSnackbar, closeSnackbar } = useSnackbar()
|
||||
|
||||
// New version of Bee client notification
|
||||
useEffect(() => {
|
||||
if (!desktopAutoUpdateEnabled && newBeeDesktopVersion !== '') {
|
||||
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(() => {
|
||||
if (newBeeDesktopVersion !== '') {
|
||||
enqueueSnackbar(`There is new Swarm Dashboard version ${newBeeDesktopVersion}!`, {
|
||||
variant: 'warning',
|
||||
preventDuplicate: true,
|
||||
@@ -61,7 +103,7 @@ const Dashboard = (props: Props): ReactElement => {
|
||||
),
|
||||
})
|
||||
}
|
||||
}, [enqueueSnackbar, closeSnackbar, newBeeDesktopVersion, desktopAutoUpdateEnabled])
|
||||
}, [enqueueSnackbar, closeSnackbar, newBeeDesktopVersion])
|
||||
|
||||
const content = (
|
||||
<>
|
||||
|
||||
@@ -10,13 +10,18 @@ import ExpandableListItemActions from '../../../components/ExpandableListItemAct
|
||||
import ExpandableListItemKey from '../../../components/ExpandableListItemKey'
|
||||
import { Loading } from '../../../components/Loading'
|
||||
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 { AccountNavigation } from '../AccountNavigation'
|
||||
import { Header } from '../Header'
|
||||
|
||||
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()
|
||||
|
||||
@@ -29,9 +34,11 @@ export function AccountWallet(): ReactElement {
|
||||
}
|
||||
|
||||
function onDeposit() {
|
||||
navigate(ROUTES.CONFIRMATION)
|
||||
navigate(ROUTES.TOP_UP)
|
||||
}
|
||||
|
||||
if (status.all === CheckState.ERROR) return <TroubleshootConnectionCard />
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
@@ -65,9 +72,11 @@ export function AccountWallet(): ReactElement {
|
||||
<SwarmButton onClick={onCheckTransactions} iconType={Link}>
|
||||
Check transactions on Blockscout
|
||||
</SwarmButton>
|
||||
<SwarmButton onClick={onInvite} iconType={Gift}>
|
||||
Invite to Swarm...
|
||||
</SwarmButton>
|
||||
{isBeeDesktop && (
|
||||
<SwarmButton onClick={onInvite} iconType={Gift}>
|
||||
Invite to Swarm...
|
||||
</SwarmButton>
|
||||
)}
|
||||
</ExpandableListItemActions>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -75,6 +75,7 @@ export function Download(): ReactElement {
|
||||
if (message.includes('Not Found: Not Found')) {
|
||||
message = 'The specified hash was not found.'
|
||||
}
|
||||
console.error(error) // eslint-disable-line
|
||||
enqueueSnackbar(<span>Error: {message || 'Unknown'}</span>, { variant: 'error' })
|
||||
} finally {
|
||||
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 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) {
|
||||
const idx = detectIndexHtml(files)
|
||||
|
||||
@@ -147,6 +147,7 @@ export function Upload(): ReactElement {
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
console.error(e) // eslint-disable-line
|
||||
enqueueSnackbar(`Error uploading: ${e.message}`, { variant: 'error' })
|
||||
setUploading(false)
|
||||
})
|
||||
|
||||
@@ -11,8 +11,9 @@ import ExpandableListItemKey from '../../components/ExpandableListItemKey'
|
||||
import { HistoryHeader } from '../../components/HistoryHeader'
|
||||
import { Loading } from '../../components/Loading'
|
||||
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 SettingsContext } from '../../providers/Settings'
|
||||
import { createGiftWallet } from '../../utils/desktop'
|
||||
import { ResolvedWallet } from '../../utils/wallet'
|
||||
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)
|
||||
|
||||
export default function Index(): ReactElement {
|
||||
const { giftWallets, addGiftWallet, provider } = useContext(TopUpContext)
|
||||
const { balance } = useContext(BeeContext)
|
||||
const { giftWallets, addGiftWallet } = useContext(TopUpContext)
|
||||
const { provider } = useContext(SettingsContext)
|
||||
const { balance } = useContext(BalanceProvider)
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [balances, setBalances] = useState<ResolvedWallet[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
async function mapGiftWallets() {
|
||||
if (!provider) return
|
||||
const results = []
|
||||
for (const giftWallet of giftWallets) {
|
||||
results.push(await ResolvedWallet.make(giftWallet, provider))
|
||||
@@ -52,6 +53,7 @@ export default function Index(): ReactElement {
|
||||
await createGiftWallet(wallet.address)
|
||||
enqueueSnackbar('Succesfully funded gift wallet', { variant: 'success' })
|
||||
} catch (error) {
|
||||
console.error(error) // eslint-disable-line
|
||||
enqueueSnackbar(`Failed to fund gift wallet: ${error}`, { variant: 'error' })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
|
||||
@@ -5,6 +5,8 @@ import ExchangeFunds from 'remixicon-react/ExchangeFundsLineIcon'
|
||||
import Upload from 'remixicon-react/UploadLineIcon'
|
||||
|
||||
import { Context as BeeContext } from '../../providers/Bee'
|
||||
import { Context as SettingsContext } from '../../providers/Settings'
|
||||
import { Context as BalanceProvider } from '../../providers/WalletBalance'
|
||||
import Card from '../../components/Card'
|
||||
import Map from '../../components/Map'
|
||||
import ExpandableListItem from '../../components/ExpandableListItem'
|
||||
@@ -13,6 +15,7 @@ import { ROUTES } from '../../routes'
|
||||
import { useIsBeeDesktop, useNewBeeDesktopVersion } from '../../hooks/apiHooks'
|
||||
import { BEE_DESKTOP_LATEST_RELEASE_PAGE } from '../../utils/desktop'
|
||||
import NodeInfoCard from './NodeInfoCard'
|
||||
import { chainIdToName } from '../../utils/chain'
|
||||
|
||||
export default function Status(): ReactElement {
|
||||
const {
|
||||
@@ -22,13 +25,24 @@ export default function Status(): ReactElement {
|
||||
latestBeeVersionUrl,
|
||||
topology,
|
||||
nodeInfo,
|
||||
balance,
|
||||
chequebookBalance,
|
||||
chainId,
|
||||
} = useContext(BeeContext)
|
||||
const { isBeeDesktop, beeDesktopVersion } = useIsBeeDesktop()
|
||||
const { isBeeDesktop } = useContext(SettingsContext)
|
||||
const { balance, error } = useContext(BalanceProvider)
|
||||
const { beeDesktopVersion } = useIsBeeDesktop()
|
||||
const { newBeeDesktopVersion } = useNewBeeDesktopVersion(isBeeDesktop)
|
||||
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 (
|
||||
<div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'stretch', alignContent: 'stretch' }}>
|
||||
@@ -42,7 +56,7 @@ export default function Status(): ReactElement {
|
||||
onClick: () => navigate(ROUTES.ACCOUNT_WALLET),
|
||||
}}
|
||||
icon={<Wallet />}
|
||||
title={`${balance?.bzz.toSignificantDigits(4)} xBZZ | ${balance?.dai.toSignificantDigits(4)} xDAI`}
|
||||
title={balanceText}
|
||||
subtitle="Current wallet balance."
|
||||
status="ok"
|
||||
/>
|
||||
@@ -51,7 +65,7 @@ export default function Status(): ReactElement {
|
||||
buttonProps={{
|
||||
iconType: Wallet,
|
||||
children: 'Setup wallet',
|
||||
onClick: () => navigate(ROUTES.WALLET),
|
||||
onClick: () => navigate(ROUTES.TOP_UP),
|
||||
}}
|
||||
icon={<Upload />}
|
||||
title="Your wallet is not setup."
|
||||
@@ -85,7 +99,7 @@ export default function Status(): ReactElement {
|
||||
icon={<ExchangeFunds />}
|
||||
title={
|
||||
chequebookBalance?.availableBalance
|
||||
? `${chequebookBalance.availableBalance.toFixedDecimal(4)} xBZZ`
|
||||
? `${chequebookBalance.availableBalance.toSignificantDigits(4)} xBZZ`
|
||||
: 'No available balance.'
|
||||
}
|
||||
subtitle="Chequebook not setup."
|
||||
@@ -112,6 +126,7 @@ export default function Status(): ReactElement {
|
||||
variant="outlined"
|
||||
href={BEE_DESKTOP_LATEST_RELEASE_PAGE}
|
||||
target="_blank"
|
||||
disabled={newBeeDesktopVersion === ''}
|
||||
style={{ height: '26px' }}
|
||||
>
|
||||
{newBeeDesktopVersion === '' ? 'latest' : 'update'}
|
||||
@@ -133,6 +148,7 @@ export default function Status(): ReactElement {
|
||||
size="small"
|
||||
variant="outlined"
|
||||
href={latestBeeVersionUrl}
|
||||
disabled={isLatestBeeVersion}
|
||||
target="_blank"
|
||||
style={{ height: '26px' }}
|
||||
>
|
||||
@@ -143,6 +159,7 @@ export default function Status(): ReactElement {
|
||||
}
|
||||
/>
|
||||
<ExpandableListItem label="Mode" value={nodeInfo?.beeMode} />
|
||||
{chainId && <ExpandableListItem label="Blockchain network" value={chainIdToName(chainId)} />}
|
||||
</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 ExpandableList from '../../components/ExpandableList'
|
||||
import ExpandableListItemInput from '../../components/ExpandableListItemInput'
|
||||
import { Context as BeeContext } from '../../providers/Bee'
|
||||
import { Context as SettingsContext } from '../../providers/Settings'
|
||||
import { useSnackbar } from 'notistack'
|
||||
|
||||
export default function SettingsPage(): ReactElement {
|
||||
const { apiUrl, apiDebugUrl, setApiUrl, setDebugApiUrl, lockedApiSettings, config, isLoading } =
|
||||
useContext(SettingsContext)
|
||||
const {
|
||||
apiUrl,
|
||||
apiDebugUrl,
|
||||
setApiUrl,
|
||||
setDebugApiUrl,
|
||||
lockedApiSettings,
|
||||
cors,
|
||||
dataDir,
|
||||
ensResolver,
|
||||
providerUrl,
|
||||
isLoading,
|
||||
isBeeDesktop,
|
||||
setAndPersistJsonRpcProvider,
|
||||
} = useContext(SettingsContext)
|
||||
const { refresh } = useContext(BeeContext)
|
||||
const { enqueueSnackbar } = useSnackbar()
|
||||
|
||||
if (isLoading) {
|
||||
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 (
|
||||
<ExpandableList label="API Settings" defaultOpen>
|
||||
<ExpandableListItemInput label="Bee API" value={apiUrl} onConfirm={setApiUrl} locked={lockedApiSettings} />
|
||||
<ExpandableListItemInput
|
||||
label="Bee Debug API"
|
||||
value={apiDebugUrl}
|
||||
onConfirm={setDebugApiUrl}
|
||||
locked={lockedApiSettings}
|
||||
/>
|
||||
</ExpandableList>
|
||||
<>
|
||||
<ExpandableList label="API Settings" defaultOpen>
|
||||
<ExpandableListItemInput
|
||||
label="Bee API"
|
||||
value={apiUrl}
|
||||
onConfirm={setApiUrl}
|
||||
locked={lockedApiSettings || isBeeDesktop}
|
||||
/>
|
||||
<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 BigNumber from 'bignumber.js'
|
||||
import { Form, Formik, FormikHelpers } from 'formik'
|
||||
@@ -102,13 +103,14 @@ export function PostageStampCreation({ onFinished }: Props): ReactElement {
|
||||
|
||||
const amount = BigInt(values.amount)
|
||||
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)
|
||||
await waitUntilStampExists(batchId, beeDebugApi)
|
||||
actions.resetForm()
|
||||
await refresh()
|
||||
onFinished()
|
||||
} catch (e) {
|
||||
console.error(e) // eslint-disable-line
|
||||
enqueueSnackbar(`Error: ${(e as Error).message}`, { variant: 'error' })
|
||||
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 { ReactElement } from 'react'
|
||||
import Index from '.'
|
||||
import Balance from './Balance'
|
||||
import { ROUTES } from '../../routes'
|
||||
|
||||
export function BankCardTopUpIndex(): ReactElement {
|
||||
return (
|
||||
<Index
|
||||
<Balance
|
||||
header={'Top-up with bank card'}
|
||||
title={'Use a bank card to buy xDAI to the funding wallet address below'}
|
||||
p={
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { Typography } from '@material-ui/core'
|
||||
import { ReactElement } from 'react'
|
||||
import Index from '.'
|
||||
import Balance from './Balance'
|
||||
import { ROUTES } from '../../routes'
|
||||
|
||||
export function CryptoTopUpIndex(): ReactElement {
|
||||
return (
|
||||
<Index
|
||||
<Balance
|
||||
header={'Top-up with cryptocurrencies'}
|
||||
title={'Send xDAI to the funding wallet below'}
|
||||
p={
|
||||
|
||||
@@ -12,18 +12,18 @@ import { ProgressIndicator } from '../../components/ProgressIndicator'
|
||||
import { SwarmButton } from '../../components/SwarmButton'
|
||||
import { SwarmDivider } from '../../components/SwarmDivider'
|
||||
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 { sleepMs } from '../../utils'
|
||||
import { restartBeeNode, upgradeToLightNode } from '../../utils/desktop'
|
||||
import { ResolvedWallet } from '../../utils/wallet'
|
||||
import { useIsBeeDesktop } from '../../hooks/apiHooks'
|
||||
import { BeeModes } from '@ethersphere/bee-js'
|
||||
|
||||
export function GiftCardFund(): ReactElement {
|
||||
const { isBeeDesktop } = useIsBeeDesktop()
|
||||
const { nodeAddresses, balance, nodeInfo } = useContext(BeeContext)
|
||||
const { provider, providerUrl } = useContext(TopUpContext)
|
||||
const { nodeAddresses, nodeInfo } = useContext(BeeContext)
|
||||
const { isBeeDesktop, provider, providerUrl } = useContext(SettingsContext)
|
||||
const { balance } = useContext(BalanceProvider)
|
||||
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [wallet, setWallet] = useState<ResolvedWallet | null>(null)
|
||||
@@ -48,10 +48,6 @@ export function GiftCardFund(): ReactElement {
|
||||
const canUpgradeToLightNode = isBeeDesktop && nodeInfo?.beeMode === BeeModes.ULTRA_LIGHT
|
||||
|
||||
async function restart() {
|
||||
if (!providerUrl) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await sleepMs(5_000)
|
||||
await upgradeToLightNode(providerUrl)
|
||||
@@ -59,6 +55,7 @@ export function GiftCardFund(): ReactElement {
|
||||
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' })
|
||||
}
|
||||
}
|
||||
@@ -76,6 +73,7 @@ export function GiftCardFund(): ReactElement {
|
||||
|
||||
if (canUpgradeToLightNode) await restart()
|
||||
} catch (error) {
|
||||
console.error(error) // eslint-disable-line
|
||||
enqueueSnackbar(`Failed to fund: ${error}`, { variant: 'error' })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useSnackbar } from 'notistack'
|
||||
import { ReactElement, useContext, useState } from 'react'
|
||||
import ArrowRight from 'remixicon-react/ArrowRightLineIcon'
|
||||
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 { ProgressIndicator } from '../../components/ProgressIndicator'
|
||||
import { SwarmButton } from '../../components/SwarmButton'
|
||||
@@ -16,7 +16,7 @@ import { ROUTES } from '../../routes'
|
||||
import { Rpc } from '../../utils/rpc'
|
||||
|
||||
export function GiftCardTopUpIndex(): ReactElement {
|
||||
const { provider } = useContext(TopUpContext)
|
||||
const { provider } = useContext(SettingsContext)
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [giftCode, setGiftCode] = useState('')
|
||||
|
||||
@@ -38,6 +38,7 @@ export function GiftCardTopUpIndex(): ReactElement {
|
||||
enqueueSnackbar('Successfully verified gift wallet', { variant: 'success' })
|
||||
navigate(ROUTES.TOP_UP_GIFT_CODE_FUND.replace(':privateKeyString', giftCode))
|
||||
} catch (error) {
|
||||
console.error(error) // eslint-disable-line
|
||||
enqueueSnackbar(`Gift wallet could not be verified: ${error}`, { variant: 'error' })
|
||||
} finally {
|
||||
setLoading(false)
|
||||
|
||||
@@ -14,11 +14,11 @@ import { Loading } from '../../components/Loading'
|
||||
import { SwarmButton } from '../../components/SwarmButton'
|
||||
import { SwarmDivider } from '../../components/SwarmDivider'
|
||||
import { SwarmTextInput } from '../../components/SwarmTextInput'
|
||||
import { useIsBeeDesktop } from '../../hooks/apiHooks'
|
||||
import { BzzToken } from '../../models/BzzToken'
|
||||
import { DaiToken } from '../../models/DaiToken'
|
||||
import { Context as BalanceProvider } from '../../providers/WalletBalance'
|
||||
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 { sleepMs } from '../../utils'
|
||||
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 [price, setPrice] = useState(DaiToken.fromDecimal('0.6', 18))
|
||||
|
||||
const { providerUrl } = useContext(TopUpContext)
|
||||
const { balance, nodeAddresses, nodeInfo } = useContext(BeeContext)
|
||||
const { isBeeDesktop } = useIsBeeDesktop()
|
||||
const { providerUrl, isBeeDesktop } = useContext(SettingsContext)
|
||||
const { nodeAddresses, nodeInfo } = useContext(BeeContext)
|
||||
const { balance } = useContext(BalanceProvider)
|
||||
|
||||
const navigate = useNavigate()
|
||||
const { enqueueSnackbar } = useSnackbar()
|
||||
@@ -78,10 +78,6 @@ export function Swap({ header }: Props): ReactElement {
|
||||
const canUpgradeToLightNode = isBeeDesktop && nodeInfo?.beeMode === BeeModes.ULTRA_LIGHT
|
||||
|
||||
async function restart() {
|
||||
if (!providerUrl) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await sleepMs(5_000)
|
||||
await upgradeToLightNode(providerUrl)
|
||||
@@ -89,12 +85,13 @@ export function Swap({ header }: Props): ReactElement {
|
||||
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' })
|
||||
}
|
||||
}
|
||||
|
||||
async function onSwap() {
|
||||
if (hasSwapped || !providerUrl) {
|
||||
if (hasSwapped) {
|
||||
return
|
||||
}
|
||||
setLoading(true)
|
||||
@@ -105,6 +102,7 @@ export function Swap({ header }: Props): ReactElement {
|
||||
|
||||
if (canUpgradeToLightNode) await restart()
|
||||
} catch (error) {
|
||||
console.error(error) // eslint-disable-line
|
||||
enqueueSnackbar(`Failed to swap: ${error}`, { variant: 'error' })
|
||||
} finally {
|
||||
balance?.refresh()
|
||||
|
||||
@@ -1,59 +1,122 @@
|
||||
import { Box, Grid, Typography } from '@material-ui/core'
|
||||
import { ReactElement, useContext } from 'react'
|
||||
import { useNavigate } from 'react-router'
|
||||
import { Box, createStyles, Grid, makeStyles, Typography } from '@material-ui/core'
|
||||
import { ReactElement, useContext, useState } from 'react'
|
||||
import Check from 'remixicon-react/CheckLineIcon'
|
||||
import ExpandableListItem from '../../components/ExpandableListItem'
|
||||
import ExpandableListItemKey from '../../components/ExpandableListItemKey'
|
||||
import Download from 'remixicon-react/DownloadLineIcon'
|
||||
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 { Loading } from '../../components/Loading'
|
||||
import { SwarmButton } from '../../components/SwarmButton'
|
||||
import { SwarmDivider } from '../../components/SwarmDivider'
|
||||
import { Context } from '../../providers/Bee'
|
||||
import { TopUpProgressIndicator } from './TopUpProgressIndicator'
|
||||
import { ROUTES } from '../../routes'
|
||||
import { CheckState, Context as BeeContext } from '../../providers/Bee'
|
||||
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 {
|
||||
header: string
|
||||
title: string
|
||||
p: ReactElement
|
||||
next: string
|
||||
}
|
||||
const MINIMUM_XDAI = '0.05'
|
||||
const MINIMUM_XBZZ = '0.1'
|
||||
|
||||
export default function Index({ header, title, p, next }: Props): ReactElement {
|
||||
const { nodeAddresses, balance } = useContext(Context)
|
||||
export default function TopUp(): ReactElement {
|
||||
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 />
|
||||
}
|
||||
|
||||
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}
|
||||
<HistoryHeader>Account</HistoryHeader>
|
||||
<Grid container direction="column" alignItems="center">
|
||||
<Box mb={6}>
|
||||
<div className={styles.checkWrapper}>
|
||||
<Download size={100} color="#ededed" />
|
||||
</div>
|
||||
</Box>
|
||||
<Box mb={1}>
|
||||
<Typography style={{ fontWeight: 'bold' }}>Transfer funds to your Swarm account</Typography>
|
||||
</Box>
|
||||
<Box mb={4}>
|
||||
<Typography align="center">Top up your account with xBZZ and xDAI.</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 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>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -15,9 +15,7 @@ import PackageJson from '../../package.json'
|
||||
import { useLatestBeeRelease } from '../hooks/apiHooks'
|
||||
import { Token } from '../models/Token'
|
||||
import type { Balance, ChequebookBalance, Settlements } from '../types'
|
||||
import { WalletAddress } from '../utils/wallet'
|
||||
import { Context as SettingsContext } from './Settings'
|
||||
import { Context as TopUpContext } from './TopUp'
|
||||
|
||||
const REFRESH_WHEN_OK = 30_000
|
||||
const REFRESH_WHEN_ERROR = 5_000
|
||||
@@ -46,7 +44,6 @@ interface Status {
|
||||
|
||||
interface ContextInterface {
|
||||
status: Status
|
||||
balance: WalletAddress | null
|
||||
latestPublishedVersion?: string
|
||||
latestUserVersion?: string
|
||||
latestUserVersionExact?: string
|
||||
@@ -65,6 +62,7 @@ interface ContextInterface {
|
||||
peerCheques: LastChequesResponse | null
|
||||
settlements: Settlements | null
|
||||
chainState: ChainState | null
|
||||
chainId: number | null
|
||||
latestBeeRelease: LatestBeeRelease | null
|
||||
isLoading: boolean
|
||||
lastUpdate: number | null
|
||||
@@ -83,7 +81,6 @@ const initialValues: ContextInterface = {
|
||||
topology: { isEnabled: false, checkState: CheckState.ERROR },
|
||||
chequebook: { isEnabled: false, checkState: CheckState.ERROR },
|
||||
},
|
||||
balance: null,
|
||||
latestPublishedVersion: undefined,
|
||||
latestUserVersion: undefined,
|
||||
latestUserVersionExact: undefined,
|
||||
@@ -102,6 +99,7 @@ const initialValues: ContextInterface = {
|
||||
peerCheques: null,
|
||||
settlements: null,
|
||||
chainState: null,
|
||||
chainId: null,
|
||||
latestBeeRelease: null,
|
||||
isLoading: true,
|
||||
lastUpdate: null,
|
||||
@@ -190,7 +188,6 @@ let isRefreshing = false
|
||||
|
||||
export function Provider({ children }: Props): ReactElement {
|
||||
const { beeApi, beeDebugApi } = useContext(SettingsContext)
|
||||
const { provider } = useContext(TopUpContext)
|
||||
const [apiHealth, setApiHealth] = useState<boolean>(false)
|
||||
const [debugApiHealth, setDebugApiHealth] = useState<Health | 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 [settlements, setSettlements] = useState<Settlements | 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()
|
||||
|
||||
@@ -242,18 +239,6 @@ export function Provider({ children }: Props): ReactElement {
|
||||
if (beeDebugApi !== null) refresh()
|
||||
}, [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 () => {
|
||||
// Don't want to refresh when already refreshing
|
||||
if (isRefreshing) return
|
||||
@@ -356,6 +341,12 @@ export function Provider({ children }: Props): ReactElement {
|
||||
.then(setChainState)
|
||||
.catch(() => setChainState(null)),
|
||||
|
||||
// Wallet
|
||||
beeDebugApi
|
||||
.getWalletBalance({ timeout: TIMEOUT })
|
||||
.then(({ chainID }) => setChainId(chainID))
|
||||
.catch(() => setChainId(null)),
|
||||
|
||||
// Chequebook balance
|
||||
chequeBalanceWrapper()
|
||||
.then(setChequebookBalance)
|
||||
@@ -421,7 +412,6 @@ export function Provider({ children }: Props): ReactElement {
|
||||
<Context.Provider
|
||||
value={{
|
||||
status,
|
||||
balance: walletAddress,
|
||||
latestUserVersion,
|
||||
latestUserVersionExact,
|
||||
latestPublishedVersion,
|
||||
@@ -446,6 +436,7 @@ export function Provider({ children }: Props): ReactElement {
|
||||
peerCheques,
|
||||
settlements,
|
||||
chainState,
|
||||
chainId,
|
||||
latestBeeRelease,
|
||||
isLoading,
|
||||
lastUpdate,
|
||||
|
||||
@@ -1,32 +1,52 @@
|
||||
import { Bee, BeeDebug } from '@ethersphere/bee-js'
|
||||
import { createContext, ReactChild, ReactElement, useEffect, useState } from 'react'
|
||||
import { config } from '../config'
|
||||
import { BeeConfig, useGetBeeConfig } from '../hooks/apiHooks'
|
||||
import { providers } from 'ethers'
|
||||
import { createContext, ReactNode, ReactElement, useEffect, useState } from 'react'
|
||||
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 {
|
||||
apiUrl: string
|
||||
apiDebugUrl: string
|
||||
beeApi: Bee | null
|
||||
beeDebugApi: BeeDebug | null
|
||||
setApiUrl: (url: string) => void
|
||||
setDebugApiUrl: (url: string) => void
|
||||
lockedApiSettings: boolean
|
||||
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
|
||||
error: Error | null
|
||||
}
|
||||
|
||||
const initialValues: ContextInterface = {
|
||||
apiUrl: config.BEE_API_HOST,
|
||||
apiDebugUrl: config.BEE_DEBUG_API_HOST,
|
||||
apiUrl: appConfig.BEE_API_HOST,
|
||||
apiDebugUrl: appConfig.BEE_DEBUG_API_HOST,
|
||||
beeApi: null,
|
||||
beeDebugApi: null,
|
||||
setApiUrl: () => {}, // eslint-disable-line
|
||||
setDebugApiUrl: () => {}, // eslint-disable-line
|
||||
lockedApiSettings: false,
|
||||
desktopApiKey: '',
|
||||
config: null,
|
||||
setAndPersistJsonRpcProvider: async () => {}, // eslint-disable-line
|
||||
providerUrl,
|
||||
provider: new providers.JsonRpcProvider(providerUrl),
|
||||
cors: null,
|
||||
dataDir: null,
|
||||
ensResolver: null,
|
||||
isBeeDesktop: false,
|
||||
isLoading: true,
|
||||
error: null,
|
||||
}
|
||||
@@ -35,10 +55,11 @@ export const Context = createContext<ContextInterface>(initialValues)
|
||||
export const Consumer = Context.Consumer
|
||||
|
||||
interface Props {
|
||||
children: ReactChild
|
||||
children: ReactNode
|
||||
beeApiUrl?: string
|
||||
beeDebugApiUrl?: string
|
||||
lockedApiSettings?: boolean
|
||||
isBeeDesktop?: boolean
|
||||
}
|
||||
|
||||
export function Provider({
|
||||
@@ -46,15 +67,34 @@ export function Provider({
|
||||
beeApiUrl,
|
||||
beeDebugApiUrl,
|
||||
lockedApiSettings: extLockedApiSettings,
|
||||
isBeeDesktop: extIsBeeDesktop,
|
||||
}: Props): ReactElement {
|
||||
const [apiUrl, setApiUrl] = useState<string>(initialValues.apiUrl)
|
||||
const [apiDebugUrl, setDebugApiUrl] = useState<string>(initialValues.apiDebugUrl)
|
||||
const [beeApi, setBeeApi] = useState<Bee | null>(null)
|
||||
const [beeDebugApi, setBeeDebugApi] = useState<BeeDebug | null>(null)
|
||||
const [lockedApiSettings] = useState<boolean>(Boolean(extLockedApiSettings))
|
||||
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 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 {
|
||||
if (!string.startsWith('http')) {
|
||||
return `http://${string}`
|
||||
@@ -104,9 +144,15 @@ export function Provider({
|
||||
beeDebugApi,
|
||||
setApiUrl,
|
||||
setDebugApiUrl,
|
||||
lockedApiSettings,
|
||||
lockedApiSettings: Boolean(extLockedApiSettings),
|
||||
desktopApiKey,
|
||||
config,
|
||||
provider,
|
||||
providerUrl,
|
||||
cors: config?.['cors-allowed-origins'] ?? null,
|
||||
dataDir: config?.['data-dir'] ?? null,
|
||||
ensResolver: config?.['resolver-options'] ?? null,
|
||||
setAndPersistJsonRpcProvider,
|
||||
isBeeDesktop,
|
||||
isLoading,
|
||||
error,
|
||||
}}
|
||||
|
||||
@@ -1,29 +1,20 @@
|
||||
import { providers, Wallet } from 'ethers'
|
||||
import { createContext, ReactElement, useEffect, useState } from 'react'
|
||||
import { setJsonRpcInDesktop } from '../utils/desktop'
|
||||
import { Wallet } from 'ethers'
|
||||
import { createContext, ReactElement, useContext, useEffect, useState } from 'react'
|
||||
import { Context as SettingsContext } from './Settings'
|
||||
|
||||
const LocalStorageKeys = {
|
||||
providerUrl: 'json-rpc-provider',
|
||||
depositWallet: 'deposit-wallet',
|
||||
giftWallets: 'gift-wallets',
|
||||
invitation: 'invitation',
|
||||
}
|
||||
|
||||
interface ContextInterface {
|
||||
providerUrl: string | null
|
||||
provider: providers.JsonRpcProvider | null
|
||||
giftWallets: Wallet[]
|
||||
setProviderUrl: (providerUrl: string) => void
|
||||
addGiftWallet: (wallet: Wallet) => void
|
||||
}
|
||||
|
||||
const providerUrl = localStorage.getItem('json-rpc-provider') || null
|
||||
|
||||
const initialValues: ContextInterface = {
|
||||
providerUrl,
|
||||
provider: providerUrl ? new providers.JsonRpcProvider(providerUrl) : null,
|
||||
giftWallets: [],
|
||||
setProviderUrl: () => {}, // eslint-disable-line
|
||||
addGiftWallet: () => {}, // eslint-disable-line
|
||||
}
|
||||
|
||||
@@ -35,9 +26,8 @@ interface Props {
|
||||
}
|
||||
|
||||
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 { provider } = useContext(SettingsContext)
|
||||
|
||||
useEffect(() => {
|
||||
if (provider === null) return
|
||||
@@ -49,14 +39,6 @@ export function Provider({ children }: Props): ReactElement {
|
||||
}
|
||||
}, [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) {
|
||||
const newArray = [...giftWallets, wallet]
|
||||
localStorage.setItem(LocalStorageKeys.giftWallets, JSON.stringify(newArray.map(x => x.privateKey)))
|
||||
@@ -66,10 +48,7 @@ export function Provider({ children }: Props): ReactElement {
|
||||
return (
|
||||
<Context.Provider
|
||||
value={{
|
||||
providerUrl,
|
||||
provider,
|
||||
giftWallets,
|
||||
setProviderUrl: setAndPersistJsonRpcProvider,
|
||||
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 { AccountChequebook } from './pages/account/chequebook/AccountChequebook'
|
||||
import { AccountFeeds } from './pages/account/feeds/AccountFeeds'
|
||||
@@ -14,8 +14,7 @@ import { UploadLander } from './pages/files/UploadLander'
|
||||
import GiftCards from './pages/gift-code'
|
||||
import Info from './pages/info'
|
||||
import LightModeRestart from './pages/restart/LightModeRestart'
|
||||
import Wallet from './pages/rpc'
|
||||
import Confirmation from './pages/rpc/Confirmation'
|
||||
import TopUp from './pages/top-up'
|
||||
import Settings from './pages/settings'
|
||||
import { CreatePostageStampPage } from './pages/stamps/CreatePostageStampPage'
|
||||
import Status from './pages/status'
|
||||
@@ -24,6 +23,7 @@ import { CryptoTopUpIndex } from './pages/top-up/CryptoTopUpIndex'
|
||||
import { GiftCardFund } from './pages/top-up/GiftCardFund'
|
||||
import { GiftCardTopUpIndex } from './pages/top-up/GiftCardTopUpIndex'
|
||||
import { Swap } from './pages/top-up/Swap'
|
||||
import { Context as SettingsContext } from './providers/Settings'
|
||||
|
||||
export enum ROUTES {
|
||||
INFO = '/',
|
||||
@@ -34,8 +34,7 @@ export enum ROUTES {
|
||||
HASH = '/files/hash/:hash',
|
||||
SETTINGS = '/settings',
|
||||
STATUS = '/status',
|
||||
WALLET = '/account/wallet/top-up',
|
||||
CONFIRMATION = '/account/wallet/top-up/confirmation',
|
||||
TOP_UP = '/account/wallet/top-up',
|
||||
TOP_UP_CRYPTO = '/account/wallet/top-up/crypto',
|
||||
TOP_UP_CRYPTO_SWAP = '/account/wallet/top-up/crypto/swap',
|
||||
TOP_UP_BANK_CARD = '/account/wallet/top-up/bank-card',
|
||||
@@ -61,34 +60,37 @@ export const ACCOUNT_TABS = [
|
||||
ROUTES.ACCOUNT_FEEDS,
|
||||
]
|
||||
|
||||
const BaseRouter = (): ReactElement => (
|
||||
<Routes>
|
||||
<Route path={ROUTES.UPLOAD_IN_PROGRESS} element={<Upload />} />
|
||||
<Route path={ROUTES.UPLOAD} element={<UploadLander />} />
|
||||
<Route path={ROUTES.DOWNLOAD} element={<Download />} />
|
||||
<Route path={ROUTES.HASH} element={<Share />} />
|
||||
<Route path={ROUTES.SETTINGS} element={<Settings />} />
|
||||
<Route path={ROUTES.STATUS} element={<Status />} />
|
||||
<Route path={ROUTES.INFO} element={<Info />} />
|
||||
<Route path={ROUTES.WALLET} element={<Wallet />} />
|
||||
<Route path={ROUTES.CONFIRMATION} element={<Confirmation />} />
|
||||
<Route path={ROUTES.TOP_UP_CRYPTO} element={<CryptoTopUpIndex />} />
|
||||
<Route path={ROUTES.TOP_UP_CRYPTO_SWAP} element={<Swap header="Top-up with cryptocurrencies" />} />
|
||||
<Route path={ROUTES.TOP_UP_BANK_CARD} element={<BankCardTopUpIndex />} />
|
||||
<Route path={ROUTES.TOP_UP_BANK_CARD_SWAP} element={<Swap header="Top-up with bank card" />} />
|
||||
<Route path={ROUTES.TOP_UP_GIFT_CODE} element={<GiftCardTopUpIndex />} />
|
||||
<Route path={ROUTES.TOP_UP_GIFT_CODE_FUND} element={<GiftCardFund />} />
|
||||
<Route path={ROUTES.RESTART_LIGHT} element={<LightModeRestart />} />
|
||||
<Route path={ROUTES.ACCOUNT_WALLET} element={<AccountWallet />} />
|
||||
<Route path={ROUTES.ACCOUNT_CHEQUEBOOK} element={<AccountChequebook />} />
|
||||
<Route path={ROUTES.ACCOUNT_STAMPS} element={<AccountStamps />} />
|
||||
<Route path={ROUTES.ACCOUNT_STAMPS_NEW} element={<CreatePostageStampPage />} />
|
||||
<Route path={ROUTES.ACCOUNT_FEEDS} element={<AccountFeeds />} />
|
||||
<Route path={ROUTES.ACCOUNT_FEEDS_NEW} element={<CreateNewFeed />} />
|
||||
<Route path={ROUTES.ACCOUNT_FEEDS_UPDATE} element={<UpdateFeed />} />
|
||||
<Route path={ROUTES.ACCOUNT_FEEDS_VIEW} element={<FeedSubpage />} />
|
||||
<Route path={ROUTES.ACCOUNT_INVITATIONS} element={<GiftCards />} />
|
||||
</Routes>
|
||||
)
|
||||
const BaseRouter = (): ReactElement => {
|
||||
const { isBeeDesktop } = useContext(SettingsContext)
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route path={ROUTES.UPLOAD_IN_PROGRESS} element={<Upload />} />
|
||||
<Route path={ROUTES.UPLOAD} element={<UploadLander />} />
|
||||
<Route path={ROUTES.DOWNLOAD} element={<Download />} />
|
||||
<Route path={ROUTES.HASH} element={<Share />} />
|
||||
<Route path={ROUTES.SETTINGS} element={<Settings />} />
|
||||
<Route path={ROUTES.STATUS} element={<Status />} />
|
||||
<Route path={ROUTES.INFO} element={<Info />} />
|
||||
<Route path={ROUTES.TOP_UP} element={<TopUp />} />
|
||||
<Route path={ROUTES.TOP_UP_CRYPTO} element={<CryptoTopUpIndex />} />
|
||||
<Route path={ROUTES.TOP_UP_CRYPTO_SWAP} element={<Swap header="Top-up with cryptocurrencies" />} />
|
||||
<Route path={ROUTES.TOP_UP_BANK_CARD} element={<BankCardTopUpIndex />} />
|
||||
<Route path={ROUTES.TOP_UP_BANK_CARD_SWAP} element={<Swap header="Top-up with bank card" />} />
|
||||
<Route path={ROUTES.TOP_UP_GIFT_CODE} element={<GiftCardTopUpIndex />} />
|
||||
<Route path={ROUTES.TOP_UP_GIFT_CODE_FUND} element={<GiftCardFund />} />
|
||||
<Route path={ROUTES.RESTART_LIGHT} element={<LightModeRestart />} />
|
||||
<Route path={ROUTES.ACCOUNT_WALLET} element={<AccountWallet />} />
|
||||
<Route path={ROUTES.ACCOUNT_CHEQUEBOOK} element={<AccountChequebook />} />
|
||||
<Route path={ROUTES.ACCOUNT_STAMPS} element={<AccountStamps />} />
|
||||
<Route path={ROUTES.ACCOUNT_STAMPS_NEW} element={<CreatePostageStampPage />} />
|
||||
<Route path={ROUTES.ACCOUNT_FEEDS} element={<AccountFeeds />} />
|
||||
<Route path={ROUTES.ACCOUNT_FEEDS_NEW} element={<CreateNewFeed />} />
|
||||
<Route path={ROUTES.ACCOUNT_FEEDS_UPDATE} element={<UpdateFeed />} />
|
||||
<Route path={ROUTES.ACCOUNT_FEEDS_VIEW} element={<FeedSubpage />} />
|
||||
{isBeeDesktop && <Route path={ROUTES.ACCOUNT_INVITATIONS} element={<GiftCards />} />}
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
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
|
||||
*/
|
||||
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 {
|
||||
path: path,
|
||||
|
||||
@@ -88,7 +88,7 @@ export async function updateFeed(
|
||||
const wallet = await getWalletFromIdentity(identity, password)
|
||||
|
||||
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)
|
||||
|
||||
@@ -6,8 +6,8 @@ export function getJson<T extends Record<string, any>>(url: string): Promise<T>
|
||||
return sendRequest(url, 'GET') as Promise<T>
|
||||
}
|
||||
|
||||
export function postJson(url: string, data?: Record<string, any>): Promise<Record<string, unknown>> {
|
||||
return sendRequest(url, 'POST', data)
|
||||
export function postJson<T extends Record<string, any>>(url: string, data?: T): Promise<T> {
|
||||
return sendRequest(url, 'POST', data) as Promise<T>
|
||||
}
|
||||
|
||||
export async function sendRequest(
|
||||
|
||||
|
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'],
|
||||
},
|
||||
{
|
||||
test: /\.(png|jp(e*)g|svg|gif)$/,
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: 'assets/[name].[ext]',
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.(ttf)$/,
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: 'assets/fonts/[name].[ext]',
|
||||
},
|
||||
test: /\.(jpe?g|png|ttf|eot|svg|woff(2)?)(\?[a-z0-9=&.]+)?$/,
|
||||
use: ['base64-inline-loader'],
|
||||
type: 'javascript/auto'
|
||||
},
|
||||
{
|
||||
test: /\.(ts|js|tsx|jsx)$/,
|
||||
|
||||