11
.babelrc
|
@ -1,11 +1,10 @@
|
|||
{
|
||||
"presets": [
|
||||
"@babel/preset-env",
|
||||
"@babel/preset-react",
|
||||
"@babel/preset-flow"
|
||||
],
|
||||
"presets": ["@babel/preset-env", "@babel/preset-react", "@babel/preset-flow"],
|
||||
"plugins": [
|
||||
"@babel/plugin-transform-regenerator",
|
||||
"@babel/plugin-proposal-class-properties",
|
||||
"@babel/plugin-proposal-object-rest-spread"
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
"@babel/plugin-proposal-optional-chaining",
|
||||
"@babel/plugin-syntax-dynamic-import"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
version: 2
|
||||
|
||||
jobs:
|
||||
test:
|
||||
docker:
|
||||
- image: electronuserland/builder:wine-chrome
|
||||
steps:
|
||||
- checkout
|
||||
- run: apt-get -y update
|
||||
- run: apt install -y software-properties-common
|
||||
- run: add-apt-repository ppa:jonathonf/ffmpeg-4
|
||||
- run: apt-get -y update
|
||||
- run: apt-get -y install libusb-1.0-0-dev graphicsmagick libudev-dev
|
||||
- run: apt-get -y install tmux xvfb libxtst6 libxss1 libgtk2.0-0 libnss3 libasound2 libgconf-2-4 ffmpeg frei0r-plugins
|
||||
# TODO: Just a quick try
|
||||
- run: rm yarn.lock
|
||||
- run: yarn install
|
||||
- run:
|
||||
name: Run Webpack
|
||||
command: yarn dev
|
||||
background: true
|
||||
- run:
|
||||
name: Run Mock RPC API
|
||||
command: yarn e2e:serve
|
||||
background: true
|
||||
- run: yarn wait-on http://localhost:8080 && yarn wait-on http://localhost:18232
|
||||
- run:
|
||||
command: Xvfb :44 -auth /tmp/xvfb.auth -ac -screen 0 1024x768x24 -listen tcp
|
||||
background: true
|
||||
- run:
|
||||
command: ffmpeg -y -f x11grab -video_size 1024x768 -i :44 -codec:v libx264 -r 12 /tmp/e2e-record.mp4
|
||||
background: true
|
||||
- run: DISPLAY=:44 yarn e2e:run
|
||||
- run: |
|
||||
kill -s SIGINT $(pgrep ffmpeg)
|
||||
sleep 10
|
||||
kill -s SIGTERM $(pgrep Xvfb)
|
||||
- store_artifacts:
|
||||
path: /tmp/e2e-record.mp4
|
||||
destination: e2e-record.mp4
|
||||
workflows:
|
||||
version: 2
|
||||
test:
|
||||
jobs:
|
||||
- test:
|
||||
filters:
|
||||
branches:
|
||||
only: feature/e2e
|
|
@ -0,0 +1 @@
|
|||
ZEC_PRICE_API_KEY=
|
55
.eslintrc
|
@ -1,15 +1,13 @@
|
|||
{
|
||||
"parser": "babel-eslint",
|
||||
"extends": [
|
||||
"airbnb",
|
||||
"plugin:flowtype/recommended"
|
||||
],
|
||||
"extends": ["airbnb", "plugin:flowtype/recommended"],
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"mocha": true
|
||||
"mocha": true,
|
||||
"jest/globals": true
|
||||
},
|
||||
"plugins": ["flowtype"],
|
||||
"plugins": ["flowtype", "jest"],
|
||||
"settings": {
|
||||
"flowtype": {
|
||||
"onlyFilesWithFlowAnnotation": true
|
||||
|
@ -17,39 +15,34 @@
|
|||
},
|
||||
"rules": {
|
||||
"jsx-quotes": ["error", "prefer-single"],
|
||||
"import/prefer-default-export": [
|
||||
"off"
|
||||
],
|
||||
"react/jsx-filename-extension": [
|
||||
1,
|
||||
{ "extensions": [".js"] }
|
||||
],
|
||||
"import/prefer-default-export": ["off"],
|
||||
"react/jsx-filename-extension": [1, { "extensions": [".js"] }],
|
||||
"jsx-a11y/anchor-is-valid": [
|
||||
"error",
|
||||
{
|
||||
"components": [
|
||||
"Link"
|
||||
],
|
||||
"specialLink": [
|
||||
"to",
|
||||
"hrefLeft",
|
||||
"hrefRight"
|
||||
],
|
||||
"aspects": [
|
||||
"noHref",
|
||||
"invalidHref",
|
||||
"preferButton"
|
||||
]
|
||||
"components": ["Link"],
|
||||
"specialLink": ["to", "hrefLeft", "hrefRight"],
|
||||
"aspects": ["noHref", "invalidHref", "preferButton"]
|
||||
}
|
||||
],
|
||||
"jsx-a11y/no-autofocus": [ 0, {
|
||||
"jsx-a11y/no-autofocus": [
|
||||
0,
|
||||
{
|
||||
"ignoreNonDOM": true
|
||||
}],
|
||||
"max-len": ["error", {
|
||||
}
|
||||
],
|
||||
"max-len": [
|
||||
"error",
|
||||
{
|
||||
"code": 100,
|
||||
"tabWidth": 2,
|
||||
"ignoreUrls": true,
|
||||
"ignoreComments": true,
|
||||
"ignoreStrings": true,
|
||||
"ignorePattern": "<p[^>]*>.*?</p>"
|
||||
}]
|
||||
"ignorePattern": "<p[^>]*>.*?</p>",
|
||||
"ignoreTrailingComments": true
|
||||
}
|
||||
],
|
||||
"consistent-return": 0
|
||||
}
|
||||
}
|
|
@ -1,11 +1,15 @@
|
|||
[ignore]
|
||||
.*/node_modules/polished/.*
|
||||
./__tests__
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
flow-typed
|
||||
|
||||
[lints]
|
||||
|
||||
[options]
|
||||
esproposal.optional_chaining=enable
|
||||
|
||||
[strict]
|
|
@ -3,3 +3,8 @@ node_modules
|
|||
dist
|
||||
.DS_Store
|
||||
flow-coverage
|
||||
build
|
||||
.docz
|
||||
coverage
|
||||
flow-typed
|
||||
.env
|
|
@ -0,0 +1,75 @@
|
|||
# Contributor Covenant Code of Conduct
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, gender identity and expression, level of
|
||||
experience, nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
|
||||
- Using welcoming and inclusive language
|
||||
- Being respectful of differing viewpoints and experiences
|
||||
- Gracefully accepting constructive criticism
|
||||
- Focusing on what is best for the community
|
||||
- Showing empathy towards other community members
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
- The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
- Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
- Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, or to ban temporarily or permanently any
|
||||
contributor for other behaviors that they deem inappropriate, threatening,
|
||||
offensive, or harmful.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at kent+coc@doddsfamily.us. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an
|
||||
incident. Further details of specific enforcement policies may be posted
|
||||
separately.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
|
||||
|
||||
[homepage]: http://contributor-covenant.org
|
||||
[version]: http://contributor-covenant.org/version/1/4/
|
|
@ -1,6 +1,6 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2018 Zcash Foundation
|
||||
Copyright (c) 2019 Zcash Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
72
NODE.md
|
@ -1,24 +1,26 @@
|
|||
# Zcash Node Setup
|
||||
|
||||
## Installation
|
||||
The original `zcashd` daemon built by the Zcash Company, is built entirely in for Linux systems. If you're running a Linux distro, you should look [here](https://github.com/zcash/zcash) for instructions on how to install it.
|
||||
|
||||
Download the attached files, verify checksum of the archive and extract
|
||||
For macOS users, please follow [this guide](https://github.com/kozyilmaz/zcash-apple) to build the binaries locally on your machine and then install it. After downloading the tarball and the hash, use the steps below to kickstart your node and connect it to the network.
|
||||
|
||||
## Installing
|
||||
|
||||
Download the attached files from Zcash Apple repository, verify the checksum of the archive and then extract its contents:
|
||||
|
||||
```bash
|
||||
shasum -a 256 -c zcash-macos-v2.0.1a.tar.bz2.hash
|
||||
tar -xvf zcash-macos-v2.0.1a.tar.bz2
|
||||
```
|
||||
|
||||
## How to use
|
||||
## Running
|
||||
|
||||
When launching Zcash on MacOS for the first time, certain initialization steps should be completed.
|
||||
Please run the commands below once for the first time
|
||||
When launching Zcash on macOS for the first time, certain initialization steps should be completed. Please run the commands below once for the first time
|
||||
|
||||
```
|
||||
cd zcash-macos-v2.0.1a/usr/local/bin
|
||||
./zcash-fetch-params
|
||||
./zcash-init
|
||||
./zcashd
|
||||
```
|
||||
|
||||
You can just run Zcash by launching the daemon afterwards:
|
||||
|
@ -27,4 +29,60 @@ You can just run Zcash by launching the daemon afterwards:
|
|||
./zcashd
|
||||
```
|
||||
|
||||
You can refer to NODE_COMMANDS for basic usage
|
||||
## Basic Commands
|
||||
|
||||
Along with `zcashd`, the daemon provides the `zcash-cli` utility which allows you to run RPC commands to your node, and receive the output data.
|
||||
|
||||
Some useful commands can be found below:
|
||||
|
||||
### General
|
||||
|
||||
> List information for a command
|
||||
```bash
|
||||
./zcash-cli help <command>
|
||||
```
|
||||
|
||||
> List all shielded addresses
|
||||
```bash
|
||||
./zcash-cli z_listaddresses
|
||||
```
|
||||
|
||||
> Create new shielded address
|
||||
```bash
|
||||
./zcash-cli z_getnewaddress
|
||||
```
|
||||
|
||||
> Get total balance
|
||||
```bash
|
||||
./zcash-cli z_gettotalbalance
|
||||
```
|
||||
|
||||
### Sending Funds
|
||||
```bash
|
||||
export ZADDR='zcNeXiyD3JkhKTrU38xM9C6HQGy9aP5qqVFH25qFzQGnmdwYZ2Dr53Jy7iRp64D4CzkMZdmKagN6mmtu3jVKHuZ8xZp8fw3'
|
||||
export FRIEND='zcfZJW3qLHpSc7q7W1SXRGdVjgM6Q6kRwdkz1DHW5sP2EqcMHf5RCp3Frpf2qnb81j9K6upzRN4HoVxfboVwLTRaZ7bKn8b'
|
||||
```
|
||||
|
||||
> Send from shielded address to shielded address (with memo and fee)
|
||||
```bash
|
||||
./zcash-cli z_sendmany "$ZADDR" "[{\"address\": \"$FRIEND\", \"amount\": 0.05, \"memo\": \"9876543210\"}]" 1 0.002
|
||||
```
|
||||
|
||||
> Get send result
|
||||
```bash
|
||||
./zcash-cli z_getoperationresult [\"$OPID\"]
|
||||
```
|
||||
|
||||
> List amounts received by shielded address
|
||||
```bash
|
||||
./zcash-cli z_listreceivedbyaddress "$ZADDR"
|
||||
```
|
||||
|
||||
## Helpful Resources
|
||||
|
||||
* [Zcash Documentation (ReadTheDocs)](https://zcash.readthedocs.io/en/latest/rtd_pages/user_guide.html)
|
||||
* [Zcash Apple Daemon](https://github.com/kozyilmaz/zcash-apple)
|
||||
* [Zcash Community Forum](https://forum.zcashcommunity.com/)
|
||||
* [Zcash Foundation](https://z.cash.foundation/)
|
||||
* [Zcash Daemon](https://github.com/zcash/zcash)
|
||||
* [Zcash Payment API Docs](https://github.com/zcash/zcash/blob/master/doc/payment-api.md)
|
|
@ -1,59 +0,0 @@
|
|||
|
||||
# for detailed information use the guides below
|
||||
https://github.com/zcash/zcash/wiki/1.0-User-Guide
|
||||
https://github.com/zcash/zcash/blob/master/doc/payment-api.md
|
||||
https://en.bitcoin.it/wiki/Original_Bitcoin_client/API_calls_list
|
||||
|
||||
# list info for a command
|
||||
$ ./zcash-cli help <command>
|
||||
|
||||
|
||||
# list all accounts (therefore all t-addr's)
|
||||
$ ./zcash-cli listreceivedbyaddress 0 true
|
||||
|
||||
# list of t-addr's for default account (again all t-addr's)
|
||||
$ ./zcash-cli getaddressesbyaccount ""
|
||||
|
||||
# list all unspent transaction outputs (t-addr UTXO's)
|
||||
$ ./zcash-cli listunspent
|
||||
|
||||
# create new t-addr's
|
||||
$ ./zcash-cli getnewaddress
|
||||
|
||||
|
||||
|
||||
# list all z-addr's
|
||||
$ ./zcash-cli z_listaddresses
|
||||
|
||||
# create new z-addr's
|
||||
$ ./zcash-cli z_getnewaddress
|
||||
|
||||
# get total balance
|
||||
$ ./zcash-cli z_gettotalbalance
|
||||
|
||||
|
||||
|
||||
# send funds
|
||||
$ export TADDR='t1TGVDzsEK2qbG1N8FJQFSAzV1bWWHMGGCS'
|
||||
$ export ZADDR='zcNeXiyD3JkhKTrU38xM9C6HQGy9aP5qqVFH25qFzQGnmdwYZ2Dr53Jy7iRp64D4CzkMZdmKagN6mmtu3jVKHuZ8xZp8fw3'
|
||||
$ export FRIEND='zcfZJW3qLHpSc7q7W1SXRGdVjgM6Q6kRwdkz1DHW5sP2EqcMHf5RCp3Frpf2qnb81j9K6upzRN4HoVxfboVwLTRaZ7bKn8b'
|
||||
|
||||
# send from t-addr to z-addr (with memo and fee)
|
||||
$ ./zcash-cli z_sendmany "$TADDR" "[{\"address\": \"$ZADDR\", \"amount\": 0.1, \"memo\": \"0123456789\"}]" 1 0.002
|
||||
|
||||
# send from t-addr to t-addr (with fee)
|
||||
$ ./zcash-cli z_sendmany "$TADDR1" "[{\"address\": \"$TADDR2\", \"amount\": 0.09}]" 1 0.002
|
||||
|
||||
# send from z-addr to z-addr (with memo and fee)
|
||||
$ ./zcash-cli z_sendmany "$ZADDR" "[{\"address\": \"$FRIEND\", \"amount\": 0.05, \"memo\": \"9876543210\"}]" 1 0.002
|
||||
|
||||
# get send result
|
||||
$ ./zcash-cli z_getoperationresult [\"$OPID\"]
|
||||
|
||||
|
||||
# list amounts received by z-addr
|
||||
$ ./zcash-cli z_listreceivedbyaddress "$ZADDR"
|
||||
|
||||
# list balance both for t-addr and z-addr
|
||||
$ ./zcash-cli z_getbalance "$TADDR"
|
||||
|
11
README.md
|
@ -12,6 +12,7 @@ Reference Wallet for the Zcash Network
|
|||
- [Babel](http://babeljs.io/): ES7/JSX transpilling
|
||||
- [ESLint](http://eslint.org/): code rules and linting
|
||||
- [React Router](https://github.com/reactjs/react-router): routing solution for react
|
||||
- [Styled Components](https://www.styled-components.com/): visual primitives for theming applications
|
||||
|
||||
## Installation
|
||||
|
||||
|
@ -21,12 +22,18 @@ yarn install
|
|||
|
||||
## Development
|
||||
|
||||
To run the application on port 8080
|
||||
To run the application you simply need to run
|
||||
|
||||
```bash
|
||||
yarn dev
|
||||
yarn start
|
||||
```
|
||||
|
||||
This will kickstart the webpack development server and serve the app on port 8080, as well as launch the Electron wrapper for the application, which houses the `zcashd` daemon process.
|
||||
|
||||
## Buiding & Distribution
|
||||
|
||||
TBD
|
||||
|
||||
## License
|
||||
|
||||
© Zcash Foundation 2018
|
|
@ -0,0 +1,63 @@
|
|||
// @flow
|
||||
|
||||
import configureStore from 'redux-mock-store';
|
||||
|
||||
import {
|
||||
LOAD_WALLET_SUMMARY,
|
||||
LOAD_WALLET_SUMMARY_SUCCESS,
|
||||
LOAD_WALLET_SUMMARY_ERROR,
|
||||
loadWalletSummary,
|
||||
loadWalletSummarySuccess,
|
||||
loadWalletSummaryError,
|
||||
} from '../../app/redux/modules/wallet';
|
||||
|
||||
const store = configureStore()();
|
||||
|
||||
describe('WalletSummary Actions', () => {
|
||||
beforeEach(() => store.clearActions());
|
||||
|
||||
test('should create an action to load wallet summary', () => {
|
||||
store.dispatch(loadWalletSummary());
|
||||
|
||||
expect(store.getActions()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: LOAD_WALLET_SUMMARY,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('should create an action to load wallet summary', () => {
|
||||
const payload = {
|
||||
total: 5000,
|
||||
transparent: 5000,
|
||||
shielded: 5000,
|
||||
addresses: [],
|
||||
transactions: [],
|
||||
zecPrice: 50,
|
||||
};
|
||||
|
||||
store.dispatch(loadWalletSummarySuccess(payload));
|
||||
|
||||
expect(store.getActions()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: LOAD_WALLET_SUMMARY_SUCCESS,
|
||||
payload,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
test('should create an action to load wallet summary with error', () => {
|
||||
const payload = {
|
||||
error: 'Something went wrong!',
|
||||
};
|
||||
|
||||
store.dispatch(loadWalletSummaryError(payload));
|
||||
|
||||
expect(store.getActions()[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: LOAD_WALLET_SUMMARY_ERROR,
|
||||
payload,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,23 @@
|
|||
// @flow
|
||||
import { getApp } from '../setup/utils';
|
||||
|
||||
const app = getApp();
|
||||
|
||||
beforeAll(async () => {
|
||||
await app.start();
|
||||
await app.client.waitUntilWindowLoaded();
|
||||
await app.client.waitUntilTextExists('#sidebar', 'Console');
|
||||
});
|
||||
afterAll(() => app.stop());
|
||||
|
||||
describe('Console', () => {
|
||||
test('should load "Console Page"', async () => {
|
||||
await app.client.element('#sidebar a:nth-child(6)').click();
|
||||
|
||||
expect(app.client.getText('#header p:first-child')).resolves.toEqual('Console');
|
||||
|
||||
expect(app.client.element('#console-wrapper img').getAttribute('src')).resolves.toEqual(
|
||||
expect.stringContaining('/assets/console_zcash.png'),
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,157 @@
|
|||
// @flow
|
||||
import { getApp } from '../setup/utils';
|
||||
|
||||
const app = getApp();
|
||||
|
||||
beforeEach(async () => {
|
||||
await app.start();
|
||||
await app.client.waitUntilWindowLoaded();
|
||||
await app.client.waitUntilTextExists('#sidebar', 'Send');
|
||||
await app.client.element('#sidebar a:nth-child(2)').click();
|
||||
});
|
||||
afterEach(() => app.stop());
|
||||
|
||||
describe('Send', () => {
|
||||
test('should load "Send Page"', async () => {
|
||||
expect(app.client.element('#send-wrapper').isVisible()).resolves.toEqual(true);
|
||||
});
|
||||
|
||||
test('should show Additional Options click', async () => {
|
||||
expect(app.client.element('#send-wrapper #send-fee-wrapper').isVisible()).resolves.toEqual(
|
||||
false,
|
||||
);
|
||||
|
||||
await app.client.element('#send-show-additional-options-button').click();
|
||||
|
||||
expect(app.client.element('#send-wrapper #send-fee-wrapper').isVisible()).resolves.toEqual(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test('should disable send button if required fields are empty', async () => {
|
||||
expect(app.client.element('#send-submit-button').getAttribute('disabled')).resolves.toEqual(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test('should enable send button if required fields are filled', async () => {
|
||||
await app.client.element('#sidebar a:nth-child(1)').click();
|
||||
await app.client.element('#sidebar a:nth-child(2)').click();
|
||||
await app.client.element('#send-wrapper #select-component').click();
|
||||
await app.client
|
||||
.element('#send-wrapper #select-component #t3Pnbg7XjP7FGPBUuz75H65aczphHgkpoJW')
|
||||
.click();
|
||||
await app.client.element('#send-wrapper input[name=amount]').setValue('0.484');
|
||||
await app.client
|
||||
.element('#send-wrapper input[name=to]')
|
||||
.setValue('t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1');
|
||||
|
||||
expect(
|
||||
expect(app.client.element('#send-submit-button').getAttribute('disabled')).resolves.toEqual(
|
||||
false,
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('should show confirm transaction modal', async () => {
|
||||
await app.client.element('#sidebar a:nth-child(1)').click();
|
||||
await app.client.element('#sidebar a:nth-child(2)').click();
|
||||
|
||||
await app.client.element('#send-wrapper #select-component').click();
|
||||
await app.client
|
||||
.element('#send-wrapper #select-component #t3Pnbg7XjP7FGPBUuz75H65aczphHgkpoJW')
|
||||
.click();
|
||||
await app.client.element('#send-wrapper input[name=amount]').setValue('0.484');
|
||||
await app.client
|
||||
.element('#send-wrapper input[name=to]')
|
||||
.setValue('tmMEBdrnRRRMKSUUC9SWdSova7V8NmHBqET');
|
||||
|
||||
await app.client.element('#send-submit-button').click();
|
||||
|
||||
expect(app.client.element('#send-confirm-transaction-modal').isVisible()).resolves.toEqual(
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
test('should display a load indicator while the transaction is processed', async () => {
|
||||
await app.client.element('#sidebar a:nth-child(1)').click();
|
||||
await app.client.element('#sidebar a:nth-child(2)').click();
|
||||
|
||||
await app.client.element('#send-wrapper #select-component').click();
|
||||
await app.client
|
||||
.element('#send-wrapper #select-component #t3Pnbg7XjP7FGPBUuz75H65aczphHgkpoJW')
|
||||
.click();
|
||||
await app.client.element('#send-wrapper input[name=amount]').setValue('0.484');
|
||||
await app.client
|
||||
.element('#send-wrapper input[name=to]')
|
||||
.setValue('tmMEBdrnRRRMKSUUC9SWdSova7V8NmHBqET');
|
||||
|
||||
await app.client.element('#send-submit-button').click();
|
||||
await app.client.element('#confirm-modal-button').click();
|
||||
expect(app.client.getAttribute('#send-confirm-transaction-modal img', 'src')).resolves.toEqual(
|
||||
expect.stringContaining('/assets/sync_icon.png'),
|
||||
);
|
||||
expect(app.client.getText('#send-confirm-transaction-modal p')).resolves.toEqual(
|
||||
'Processing transaction...',
|
||||
);
|
||||
expect(app.client.element('#confirm-modal-button').isVisible()).resolves.toEqual(false);
|
||||
});
|
||||
|
||||
test('should show an error in invalid transaction', async () => {
|
||||
expect(app.client.element('#send-error-text').isVisible()).resolves.toEqual(false);
|
||||
|
||||
await app.client.element('#sidebar a:nth-child(1)').click();
|
||||
await app.client.element('#sidebar a:nth-child(2)').click();
|
||||
await app.client.element('#send-wrapper #select-component').click();
|
||||
await app.client
|
||||
.element('#send-wrapper #select-component #t3Pnbg7XjP7FGPBUuz75H65aczphHgkpoJW')
|
||||
.click();
|
||||
await app.client.element('#send-wrapper input[name=amount]').setValue('-500');
|
||||
await app.client
|
||||
.element('#send-wrapper input[name=to]')
|
||||
.setValue('tmMEBdrnRRRMKSUUC9SWdSova7V8NmHBqET');
|
||||
|
||||
await app.client.element('#send-submit-button').click();
|
||||
await app.client.element('#confirm-modal-button').click();
|
||||
|
||||
expect(
|
||||
app.client
|
||||
.element('#send-error-message')
|
||||
.waitForVisible()
|
||||
.isVisible(),
|
||||
).resolves.toEqual(true);
|
||||
});
|
||||
|
||||
test('should show a success screen after transaction and show a transaction item', async () => {
|
||||
expect(app.client.element('#send-success-wrapper').isVisible()).resolves.toEqual(false);
|
||||
|
||||
await app.client.element('#sidebar a:nth-child(1)').click();
|
||||
await app.client.element('#sidebar a:nth-child(2)').click();
|
||||
await app.client.element('#send-wrapper #select-component').click();
|
||||
await app.client
|
||||
.element('#send-wrapper #select-component #t3Pnbg7XjP7FGPBUuz75H65aczphHgkpoJW')
|
||||
.click();
|
||||
await app.client.element('#send-wrapper input[name=amount]').setValue('0.484');
|
||||
await app.client
|
||||
.element('#send-wrapper input[name=to]')
|
||||
.setValue('t3Pnbg7XjP7FGasduz75HadsdzphHgkadW');
|
||||
|
||||
await app.client.element('#send-submit-button').click();
|
||||
await app.client.element('#confirm-modal-button').click();
|
||||
|
||||
await app.client
|
||||
.waitForVisible('#send-success-wrapper')
|
||||
.element('#send-confirm-transaction-modal button')
|
||||
.click();
|
||||
await app.client.element('#sidebar a:nth-child(1)').click();
|
||||
|
||||
await app.client.waitUntilTextExists('#transaction-item-operation-id-1', 'Send');
|
||||
|
||||
expect(await app.client.element('#transaction-item-operation-id-1 img').isVisible()).toEqual(
|
||||
true,
|
||||
);
|
||||
expect(
|
||||
await app.client.element('#transaction-item-operation-id-1 img').getAttribute('src'),
|
||||
).toEndWith('/assets/transaction_sent_icon.svg');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,98 @@
|
|||
// @flow
|
||||
|
||||
import { getApp } from '../setup/utils';
|
||||
|
||||
const app = getApp();
|
||||
|
||||
beforeAll(async () => {
|
||||
await app.start();
|
||||
await app.client.waitUntilWindowLoaded();
|
||||
await app.client.waitUntilTextExists('#sidebar', 'Dashboard');
|
||||
});
|
||||
afterAll(() => app.stop());
|
||||
|
||||
describe('Sidebar', () => {
|
||||
test('should see the active dashboard route', async () => {
|
||||
await app.client.element('#sidebar a:nth-child(1)').click();
|
||||
|
||||
expect(await app.client.getUrl()).toEndWith('/');
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(1)').getHTML()).toEqual(
|
||||
expect.stringContaining('Dashboard'),
|
||||
);
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(1) img').getAttribute('src')).toEqual(
|
||||
expect.stringContaining('/assets/dashboard_icon_active.svg'),
|
||||
);
|
||||
});
|
||||
|
||||
test('should see the active send route', async () => {
|
||||
await app.client.element('#sidebar a:nth-child(2)').click();
|
||||
|
||||
expect(await app.client.getUrl()).toEndWith('/send');
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(2)').getHTML()).toEqual(
|
||||
expect.stringContaining('Send'),
|
||||
);
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(2) img').getAttribute('src')).toEqual(
|
||||
expect.stringContaining('/assets/send_icon_active.svg'),
|
||||
);
|
||||
});
|
||||
|
||||
test('should see the active receive route', async () => {
|
||||
await app.client.element('#sidebar a:nth-child(3)').click();
|
||||
|
||||
expect(await app.client.getUrl()).toEndWith('/receive');
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(3)').getHTML()).toEqual(
|
||||
expect.stringContaining('Receive'),
|
||||
);
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(3) img').getAttribute('src')).toEqual(
|
||||
expect.stringContaining('/assets/receive_icon_active.svg'),
|
||||
);
|
||||
});
|
||||
|
||||
test('should see the active transactions route', async () => {
|
||||
await app.client.element('#sidebar a:nth-child(4)').click();
|
||||
|
||||
expect(await app.client.getUrl()).toEndWith('/transactions');
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(4)').getHTML()).toEqual(
|
||||
expect.stringContaining('Transactions'),
|
||||
);
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(4) img').getAttribute('src')).toEqual(
|
||||
expect.stringContaining('/assets/transactions_icon_active.svg'),
|
||||
);
|
||||
});
|
||||
|
||||
test('should see the active settings route', async () => {
|
||||
await app.client.element('#sidebar a:nth-child(5)').click();
|
||||
|
||||
expect(await app.client.getUrl()).toEndWith('/settings');
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(5)').getHTML()).toEqual(
|
||||
expect.stringContaining('Settings'),
|
||||
);
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(5) img').getAttribute('src')).toEqual(
|
||||
expect.stringContaining('/assets/settings_icon_active.svg'),
|
||||
);
|
||||
});
|
||||
|
||||
test('should see the active console route', async () => {
|
||||
await app.client.element('#sidebar a:nth-child(6)').click();
|
||||
|
||||
expect(await app.client.getUrl()).toEndWith('/console');
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(6)').getHTML()).toEqual(
|
||||
expect.stringContaining('Console'),
|
||||
);
|
||||
|
||||
expect(await app.client.element('#sidebar a:nth-child(6) img').getAttribute('src')).toEqual(
|
||||
expect.stringContaining('/assets/console_icon_active.svg'),
|
||||
);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,31 @@
|
|||
// @flow
|
||||
|
||||
import { getApp } from '../setup/utils';
|
||||
|
||||
const app = getApp();
|
||||
|
||||
beforeAll(async () => {
|
||||
await app.start();
|
||||
await app.client.waitUntilWindowLoaded();
|
||||
});
|
||||
afterAll(() => app.stop());
|
||||
|
||||
describe('Startup', () => {
|
||||
test('should open the window', () => expect(app.client.getWindowCount()).resolves.toEqual(1));
|
||||
|
||||
test('should have the right title', () => {
|
||||
expect(app.client.getTitle()).resolves.toEqual('ZEC Wallet');
|
||||
});
|
||||
|
||||
test('should show the text "ZEC Wallet Starting" in loading screen', async () => expect(app.client.element('#loading-screen:first-child p').getHTML()).resolves.toEqual(
|
||||
expect.stringContaining('ZEC Wallet Starting'),
|
||||
));
|
||||
|
||||
test('should show the zcash logo in loading screen', () => expect(app.client.getAttribute('#loading-screen:first-child img', 'src')).resolves.toEqual(
|
||||
expect.stringContaining('/assets/zcash-simple-icon.svg'),
|
||||
));
|
||||
|
||||
test('should show the loading circle in loading screen', () => {
|
||||
expect(app.client.element('#loading-screen svg').isExisting()).resolves.toEqual(true);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
// @flow
|
||||
import { getApp } from '../setup/utils';
|
||||
|
||||
const app = getApp();
|
||||
|
||||
beforeAll(async () => {
|
||||
await app.start();
|
||||
await app.client.waitUntilWindowLoaded();
|
||||
await app.client.waitUntilTextExists('#sidebar', 'Dashboard');
|
||||
});
|
||||
|
||||
afterAll(() => app.stop());
|
||||
|
||||
describe('Status Pill', () => {
|
||||
test('should show status pill in the header', async () => expect(
|
||||
app.client.waitUntilTextExists('#status-pill', '50.00%').getText('#status-pill'),
|
||||
).resolves.toEqual(expect.stringContaining('50.00%')));
|
||||
});
|
|
@ -0,0 +1,80 @@
|
|||
// @flow
|
||||
import walletSummaryReducer, {
|
||||
LOAD_WALLET_SUMMARY,
|
||||
LOAD_WALLET_SUMMARY_SUCCESS,
|
||||
LOAD_WALLET_SUMMARY_ERROR,
|
||||
} from '../../app/redux/modules/wallet';
|
||||
|
||||
describe('WalletSummary Reducer', () => {
|
||||
test('should return the valid initial state', () => {
|
||||
const initialState = {
|
||||
total: 0,
|
||||
shielded: 0,
|
||||
transparent: 0,
|
||||
error: null,
|
||||
isLoading: false,
|
||||
dollarValue: 0,
|
||||
};
|
||||
const action = {
|
||||
type: 'UNKNOWN_ACTION',
|
||||
payload: {},
|
||||
};
|
||||
|
||||
expect(walletSummaryReducer(undefined, action)).toEqual(initialState);
|
||||
});
|
||||
|
||||
test('should load the wallet summary', () => {
|
||||
const action = {
|
||||
type: LOAD_WALLET_SUMMARY,
|
||||
payload: {},
|
||||
};
|
||||
const expectedState = {
|
||||
total: 0,
|
||||
shielded: 0,
|
||||
transparent: 0,
|
||||
error: null,
|
||||
isLoading: true,
|
||||
dollarValue: 0,
|
||||
};
|
||||
|
||||
expect(walletSummaryReducer(undefined, action)).toEqual(expectedState);
|
||||
});
|
||||
|
||||
test('should load the wallet summary with success', () => {
|
||||
const action = {
|
||||
type: LOAD_WALLET_SUMMARY_SUCCESS,
|
||||
payload: {
|
||||
total: 1000,
|
||||
transparent: 1000,
|
||||
shielded: 1000,
|
||||
},
|
||||
};
|
||||
const expectedState = {
|
||||
...action.payload,
|
||||
error: null,
|
||||
isLoading: false,
|
||||
dollarValue: 0,
|
||||
};
|
||||
|
||||
expect(walletSummaryReducer(undefined, action)).toEqual(expectedState);
|
||||
});
|
||||
|
||||
test('should load the wallet summary with error', () => {
|
||||
const action = {
|
||||
type: LOAD_WALLET_SUMMARY_ERROR,
|
||||
payload: {
|
||||
error: 'Something went wrong',
|
||||
},
|
||||
};
|
||||
const expectedState = {
|
||||
total: 0,
|
||||
shielded: 0,
|
||||
transparent: 0,
|
||||
error: action.payload.error,
|
||||
isLoading: false,
|
||||
dollarValue: 0,
|
||||
};
|
||||
|
||||
expect(walletSummaryReducer(undefined, action)).toEqual(expectedState);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
// @flow
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
require('jest-extended');
|
||||
|
||||
// $FlowFixMe
|
||||
jest.DEFAULT_TIMEOUT_INTERVAL = 120000;
|
||||
jest.setTimeout(120000);
|
|
@ -0,0 +1,101 @@
|
|||
// @flow
|
||||
// eslint-disable-next-line import/no-extraneous-dependencies
|
||||
import 'babel-polyfill';
|
||||
|
||||
import createTestServer from 'create-test-server';
|
||||
|
||||
const transactions = [];
|
||||
|
||||
const sleep = (time: number) => new Promise(resolve => setTimeout(resolve, time));
|
||||
|
||||
createTestServer({
|
||||
httpPort: '18232',
|
||||
}).then(async (server) => {
|
||||
console.log('[MOCK RPC API]', server.url);
|
||||
|
||||
server.get('/', (req, res) => {
|
||||
res.send('Zcash RPC');
|
||||
});
|
||||
|
||||
server.post('/', async (req, res) => {
|
||||
const { method } = req.body;
|
||||
|
||||
switch (method) {
|
||||
case 'getinfo':
|
||||
sleep(1500).then(() => res.send({ result: { version: 1.0 } }));
|
||||
break;
|
||||
case 'getblockchaininfo':
|
||||
return res.send({ result: { verificationprogress: 0.5 } });
|
||||
case 'z_gettotalbalance':
|
||||
return res.send({
|
||||
result: { transparent: 2.5, private: 3.5, total: 6 },
|
||||
});
|
||||
case 'z_listaddresses':
|
||||
return res.send({
|
||||
result: ['zs1z7rejlpsa98s2rrrfkwmaxu53e4ue0ulcrw0h4x5g8jl04tak0d3mm47vdtahatqrlkngh9sly'],
|
||||
});
|
||||
case 'getaddressesbyaccount':
|
||||
return res.send({
|
||||
result: ['t3Pnbg7XjP7FGPBUuz75H65aczphHgkpoJW'],
|
||||
});
|
||||
case 'listtransactions':
|
||||
return res.send({
|
||||
result: transactions,
|
||||
});
|
||||
case 'z_sendmany':
|
||||
// eslint-disable-next-line
|
||||
sleep(2000).then(() => {
|
||||
const [, [obj], amount, fee] = req.body.params;
|
||||
|
||||
if ((obj.address[0] === 'z' || obj.address[0] === 't') && amount > 0) {
|
||||
transactions.push({
|
||||
account: '',
|
||||
address: obj.address,
|
||||
category: 'send',
|
||||
amount: obj.amount,
|
||||
vout: 0,
|
||||
fee,
|
||||
confirmations: 10,
|
||||
blockhash: 20,
|
||||
blockindex: 10,
|
||||
txid: `operation-id-${transactions.length + 1}`,
|
||||
time: Date.now(),
|
||||
timereceived: Date.now(),
|
||||
comment: '',
|
||||
otheraccount: '',
|
||||
size: 10,
|
||||
});
|
||||
|
||||
return res.send({ result: 'operation-id-1' });
|
||||
}
|
||||
|
||||
return res.status(500).send({ error: { message: 'Invalid address!' } });
|
||||
});
|
||||
break;
|
||||
case 'z_validateaddress':
|
||||
// eslint-disable-next-line
|
||||
const [zAd] = req.body.params;
|
||||
|
||||
if (zAd[0] === 'z' || zAd[0] === 't') {
|
||||
return res.send({ result: { isvalid: true } });
|
||||
}
|
||||
|
||||
return res.send({ result: { isvalid: false } });
|
||||
case 'validateaddress':
|
||||
// eslint-disable-next-line
|
||||
const [tAd] = req.body.params;
|
||||
|
||||
if (tAd[0] === 'z' || tAd[0] === 't') {
|
||||
return res.send({ result: { isvalid: true } });
|
||||
}
|
||||
|
||||
return res.send({ result: { isvalid: false } });
|
||||
case 'z_getoperationstatus':
|
||||
return res.send({
|
||||
result: [{ id: 'operation-id-1', status: 'success', result: { txid: 'txid-1' } }],
|
||||
});
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
// @flow
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
import electron from 'electron';
|
||||
import { Application } from 'spectron';
|
||||
|
||||
export const TIMEOUT = 20000;
|
||||
|
||||
export const getApp = () => new Application({
|
||||
path: electron,
|
||||
args: ['.'],
|
||||
startTimeout: TIMEOUT,
|
||||
waitTimeout: TIMEOUT,
|
||||
quitTimeout: TIMEOUT,
|
||||
env: {
|
||||
NODE_ENV: 'test',
|
||||
},
|
||||
});
|
|
@ -1,15 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import uuidv4 from 'uuid/v4';
|
||||
import { ADD_TODO } from '../constants/actions';
|
||||
import { getTimestamp } from '../utils/timestamp';
|
||||
|
||||
export const addTodo = (text: string) => ({
|
||||
type: ADD_TODO,
|
||||
payload: {
|
||||
text,
|
||||
id: uuidv4(),
|
||||
editing: false,
|
||||
createdAt: getTimestamp(),
|
||||
},
|
||||
});
|
|
@ -1,8 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import { CANCEL_UPDATE_TODO } from '../constants/actions';
|
||||
|
||||
export const cancelUpdateTodo = (id: string) => ({
|
||||
type: CANCEL_UPDATE_TODO,
|
||||
payload: { id },
|
||||
});
|
|
@ -1,8 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import { DELETE_TODO } from '../constants/actions';
|
||||
|
||||
export const deleteTodo = (id: string) => ({
|
||||
type: DELETE_TODO,
|
||||
payload: { id },
|
||||
});
|
|
@ -1,8 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import { TOGGLE_EDIT_TODO } from '../constants/actions';
|
||||
|
||||
export const toggleEdit = (id: string) => ({
|
||||
type: TOGGLE_EDIT_TODO,
|
||||
payload: { id },
|
||||
});
|
|
@ -1,11 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import { UPDATE_TODO } from '../constants/actions';
|
||||
|
||||
export const updateTodo = (id: string, text: string) => ({
|
||||
type: UPDATE_TODO,
|
||||
payload: {
|
||||
text,
|
||||
id,
|
||||
},
|
||||
});
|
21
app/app.js
|
@ -1,17 +1,26 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import React, { Fragment } from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import configureStore from './store/configure';
|
||||
import Router from './router';
|
||||
import { ConnectedRouter } from 'connected-react-router';
|
||||
import { ThemeProvider } from 'styled-components';
|
||||
|
||||
import { configureStore, history } from './redux/create';
|
||||
import { Router } from './router/container';
|
||||
import theme, { GlobalStyle } from './theme';
|
||||
|
||||
const store = configureStore({});
|
||||
|
||||
export default () => (
|
||||
<BrowserRouter>
|
||||
<ThemeProvider theme={theme}>
|
||||
<Fragment>
|
||||
<GlobalStyle />
|
||||
<Provider store={store}>
|
||||
<ConnectedRouter history={history}>
|
||||
{/* $FlowFixMe */}
|
||||
<Router />
|
||||
</ConnectedRouter>
|
||||
</Provider>
|
||||
</BrowserRouter>
|
||||
</Fragment>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="white" d="M0 7.33l2.829-2.83 9.175 9.339 9.167-9.339 2.829 2.83-11.996 12.17z"/></svg>
|
After Width: | Height: | Size: 181 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="white" d="M0 16.67l2.829 2.83 9.175-9.339 9.167 9.339 2.829-2.83-11.996-12.17z"/></svg>
|
After Width: | Height: | Size: 182 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" enable-background="new 0 0 24 24" height="24px" id="Layer_1" version="1.1" viewBox="0 0 24 24" width="24px" xml:space="preserve"><path xmlns="http://www.w3.org/2000/svg" fill="#777777" d="M22.245,4.015c0.313,0.313,0.313,0.826,0,1.139l-6.276,6.27c-0.313,0.312-0.313,0.826,0,1.14l6.273,6.272 c0.313,0.313,0.313,0.826,0,1.14l-2.285,2.277c-0.314,0.312-0.828,0.312-1.142,0l-6.271-6.271c-0.313-0.313-0.828-0.313-1.141,0 l-6.276,6.267c-0.313,0.313-0.828,0.313-1.141,0l-2.282-2.28c-0.313-0.313-0.313-0.826,0-1.14l6.278-6.269 c0.313-0.312,0.313-0.826,0-1.14L1.709,5.147c-0.314-0.313-0.314-0.827,0-1.14l2.284-2.278C4.308,1.417,4.821,1.417,5.135,1.73 L11.405,8c0.314,0.314,0.828,0.314,1.141,0.001l6.276-6.267c0.312-0.312,0.826-0.312,1.141,0L22.245,4.015z"/></svg>
|
After Width: | Height: | Size: 839 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22.891 10.875"><defs><style>.a{fill:#3b3b3f;stroke:#3b3b3f;stroke-width:0.5px;}</style></defs><g transform="translate(0.283 0.286)"><g transform="translate(-0.001 0.004)"><path class="a" d="M2.269,5.239c1.185,1.183,2.35,2.345,3.513,3.508a.938.938,0,1,1-1.307,1.342c-.786-.777-1.564-1.561-2.346-2.342q-.89-.89-1.779-1.78A.943.943,0,0,1,.345,4.5Q2.4,2.446,4.449.395A.938.938,0,0,1,5.441.115a.891.891,0,0,1,.638.7.941.941,0,0,1-.307.917q-1.654,1.649-3.3,3.3C2.408,5.091,2.349,5.156,2.269,5.239Z" transform="translate(0.001 -0.067)"/><path class="a" d="M297.98,5.214c-1.2-1.2-2.36-2.357-3.522-3.512a1.015,1.015,0,0,1-.336-.674.944.944,0,0,1,.536-.9.932.932,0,0,1,1.071.195c.592.582,1.177,1.173,1.765,1.76l2.344,2.344a.959.959,0,0,1,.01,1.534q-2.027,2.028-4.054,4.057a.948.948,0,0,1-1.053.283.933.933,0,0,1-.307-1.576q1.655-1.664,3.319-3.319C297.815,5.341,297.888,5.29,297.98,5.214Z" transform="translate(-277.88 -0.034)"/><path class="a" d="M142.53,9.378a3.646,3.646,0,0,1,.149-.453q1.983-4.169,3.976-8.332a.939.939,0,1,1,1.7.8q-1.995,4.19-4,8.375a.93.93,0,0,1-1.1.533A1,1,0,0,1,142.53,9.378Z" transform="translate(-134.629 -0.004)"/></g></g></svg>
|
After Width: | Height: | Size: 1.2 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22.891 10.875"><defs><style>.a{fill:#f4b728;stroke:#3b3b3f;stroke-width:0.5px;}</style></defs><g transform="translate(0.283 0.286)"><g transform="translate(-0.001 0.004)"><path class="a" d="M2.269,5.239c1.185,1.183,2.35,2.345,3.513,3.508a.938.938,0,1,1-1.307,1.342c-.786-.777-1.564-1.561-2.346-2.342q-.89-.89-1.779-1.78A.943.943,0,0,1,.345,4.5Q2.4,2.446,4.449.395A.938.938,0,0,1,5.441.115a.891.891,0,0,1,.638.7.941.941,0,0,1-.307.917q-1.654,1.649-3.3,3.3C2.408,5.091,2.349,5.156,2.269,5.239Z" transform="translate(0.001 -0.067)"/><path class="a" d="M297.98,5.214c-1.2-1.2-2.36-2.357-3.522-3.512a1.015,1.015,0,0,1-.336-.674.944.944,0,0,1,.536-.9.932.932,0,0,1,1.071.195c.592.582,1.177,1.173,1.765,1.76l2.344,2.344a.959.959,0,0,1,.01,1.534q-2.027,2.028-4.054,4.057a.948.948,0,0,1-1.053.283.933.933,0,0,1-.307-1.576q1.655-1.664,3.319-3.319C297.815,5.341,297.888,5.29,297.98,5.214Z" transform="translate(-277.88 -0.034)"/><path class="a" d="M142.53,9.378a3.646,3.646,0,0,1,.149-.453q1.983-4.169,3.976-8.332a.939.939,0,1,1,1.7.8q-1.995,4.19-4,8.375a.93.93,0,0,1-1.1.533A1,1,0,0,1,142.53,9.378Z" transform="translate(-134.629 -0.004)"/></g></g></svg>
|
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 273 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 19.091"><defs><style>.a{fill:#3b3b3f;}</style></defs><rect class="a" width="9.091" height="9.091" rx="2"/><rect class="a" width="20" height="8.182" rx="2" transform="translate(0 10.909)"/><rect class="a" width="9.091" height="9.091" rx="2" transform="translate(10.909)"/></svg>
|
After Width: | Height: | Size: 333 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 19.091"><defs><style>.a{fill:#f4b728;}</style></defs><rect class="a" width="9.091" height="9.091" rx="2"/><rect class="a" width="20" height="8.182" rx="2" transform="translate(0 10.909)"/><rect class="a" width="9.091" height="9.091" rx="2" transform="translate(10.909)"/></svg>
|
After Width: | Height: | Size: 333 B |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 33 KiB |
After Width: | Height: | Size: 4.6 KiB |
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" ?>
|
||||
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
|
||||
<svg enable-background="new 0 0 256 256" height="256px" id="Layer_1" version="1.1" viewBox="0 0 256 256" width="256px" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<circle fill="white" cx="57.6" cy="128" r="20"/>
|
||||
<circle fill="white" cx="128" cy="128" r="20"/>
|
||||
<circle fill="white" cx="198.4" cy="128" r="20"/>
|
||||
</svg>
|
After Width: | Height: | Size: 502 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.28 19.854"><defs><style>.a{fill:#3b3b3f;}</style></defs><g transform="translate(0)"><path class="a" d="M6.612,39.045h0a.9.9,0,0,1-.9.9h-3.9v9.638H5.959c.5,0,.675.176.675.682,0,.768,0,1.536,0,2.3,0,.424.191.619.617.62q2.387,0,4.774,0c.436,0,.627-.2.628-.638,0-.784,0-1.568,0-2.352,0-.429.186-.616.616-.616q1.94,0,3.88,0h.3V39.956H13.569a.9.9,0,0,1-.9-.9v-.022a.9.9,0,0,1,.9-.9c1.227,0,3.415,0,4.657,0a.948.948,0,0,1,1.053,1.066q0,7.972,0,15.944A1.048,1.048,0,0,1,18.23,56.2H1.041A.931.931,0,0,1,0,55.152q0-8.007,0-16.015a.912.912,0,0,1,.951-1c1.293-.006,3.529,0,4.769.006A.9.9,0,0,1,6.612,39.045Z" transform="translate(0.002 -36.345)"/><path class="a" d="M112.724,5.946l.016-.016a1.125,1.125,0,0,1,1.6.005l1.14,1.154.06-.024V1.222A1.221,1.221,0,0,1,116.756,0h0a1.222,1.222,0,0,1,1.222,1.222V7.057l1.143-1.144a1.125,1.125,0,0,1,1.6,0l.04.041a1.125,1.125,0,0,1,0,1.586l-3.4,3.4a.86.86,0,0,1-1.216,0l-3.409-3.41A1.125,1.125,0,0,1,112.724,5.946Z" transform="translate(-107.108)"/></g></svg>
|
After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.28 19.854"><defs><style>.a{fill:#f4b728;}</style></defs><g transform="translate(0)"><path class="a" d="M6.612,39.045h0a.9.9,0,0,1-.9.9h-3.9v9.638H5.959c.5,0,.675.176.675.682,0,.768,0,1.536,0,2.3,0,.424.191.619.617.62q2.387,0,4.774,0c.436,0,.627-.2.628-.638,0-.784,0-1.568,0-2.352,0-.429.186-.616.616-.616q1.94,0,3.88,0h.3V39.956H13.569a.9.9,0,0,1-.9-.9v-.022a.9.9,0,0,1,.9-.9c1.227,0,3.415,0,4.657,0a.948.948,0,0,1,1.053,1.066q0,7.972,0,15.944A1.048,1.048,0,0,1,18.23,56.2H1.041A.931.931,0,0,1,0,55.152q0-8.007,0-16.015a.912.912,0,0,1,.951-1c1.293-.006,3.529,0,4.769.006A.9.9,0,0,1,6.612,39.045Z" transform="translate(0.002 -36.345)"/><path class="a" d="M112.724,5.946l.016-.016a1.125,1.125,0,0,1,1.6.005l1.14,1.154.06-.024V1.222A1.221,1.221,0,0,1,116.756,0h0a1.222,1.222,0,0,1,1.222,1.222V7.057l1.143-1.144a1.125,1.125,0,0,1,1.6,0l.04.041a1.125,1.125,0,0,1,0,1.586l-3.4,3.4a.86.86,0,0,1-1.216,0l-3.409-3.41A1.125,1.125,0,0,1,112.724,5.946Z" transform="translate(-107.108)"/></g></svg>
|
After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.28 19.283"><defs><style>.a{fill:#3b3b3f;}</style></defs><g transform="translate(0 0)"><path class="a" d="M7.454,19.286c-.055-.021-.111-.038-.163-.063a.657.657,0,0,1-.4-.613c-.005-1.236,0-2.472,0-3.708a.213.213,0,0,1,.049-.12q1.437-1.766,2.878-3.528l6.229-7.635a.277.277,0,0,0,.051-.085.5.5,0,0,0-.042.029L9.744,9.019q-2.5,2.166-5,4.333a.12.12,0,0,1-.148.015Q2.513,12.512.432,11.661a.683.683,0,0,1-.086-1.232L3.87,8.4,18.218.118a.666.666,0,0,1,.751,0,.663.663,0,0,1,.294.717q-.356,2.122-.708,4.245Q18.2,7.241,17.838,9.4q-.428,2.568-.858,5.136-.23,1.38-.458,2.763a.686.686,0,0,1-.969.55q-2.369-.966-4.734-1.937a.138.138,0,0,0-.193.048q-1.254,1.535-2.511,3.066a.778.778,0,0,1-.424.264C7.612,19.286,7.533,19.286,7.454,19.286Z" transform="translate(0.003 -0.005)"/></g></svg>
|
After Width: | Height: | Size: 826 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 19.28 19.283"><defs><style>.a{fill:#f4b728;}</style></defs><g transform="translate(0 0)"><path class="a" d="M7.454,19.286c-.055-.021-.111-.038-.163-.063a.657.657,0,0,1-.4-.613c-.005-1.236,0-2.472,0-3.708a.213.213,0,0,1,.049-.12q1.437-1.766,2.878-3.528l6.229-7.635a.277.277,0,0,0,.051-.085.5.5,0,0,0-.042.029L9.744,9.019q-2.5,2.166-5,4.333a.12.12,0,0,1-.148.015Q2.513,12.512.432,11.661a.683.683,0,0,1-.086-1.232L3.87,8.4,18.218.118a.666.666,0,0,1,.751,0,.663.663,0,0,1,.294.717q-.356,2.122-.708,4.245Q18.2,7.241,17.838,9.4q-.428,2.568-.858,5.136-.23,1.38-.458,2.763a.686.686,0,0,1-.969.55q-2.369-.966-4.734-1.937a.138.138,0,0,0-.193.048q-1.254,1.535-2.511,3.066a.778.778,0,0,1-.424.264C7.612,19.286,7.533,19.286,7.454,19.286Z" transform="translate(0.003 -0.005)"/></g></svg>
|
After Width: | Height: | Size: 826 B |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20.327 20.328"><defs><style>.a{fill:#3b3b3f;fill-rule:evenodd;}</style></defs><path class="a" d="M20.311,9.58a.868.868,0,0,0-.725-.865c-.067-.017-.134-.017-.2-.029-.58-.1-1.159-.2-1.763-.312-.218-.571-.434-1.136-.658-1.713.1-.134.186-.265.283-.39.253-.32.5-.65.772-.953A1.017,1.017,0,0,0,18,3.707c-.146-.143-.271-.306-.408-.457-.577-.638-.859-.679-1.588-.23-.484.3-.961.6-1.471.921A8.953,8.953,0,0,0,12,2.771c-.047-.245-.093-.463-.131-.685-.07-.425-.137-.851-.213-1.276a.874.874,0,0,0-.827-.778,6.587,6.587,0,0,0-1.291,0A.861.861,0,0,0,8.708.8c-.041.248-.067.5-.131.737a6.365,6.365,0,0,1-.242,1.194c-.749.315-1.436.6-2.153.9-.131-.087-.283-.184-.431-.285-.373-.262-.734-.539-1.113-.787A.881.881,0,0,0,3.4,2.658c-.268.245-.527.5-.772.772a.863.863,0,0,0-.09,1.229c.236.361.5.7.746,1.058.125.184.312.338.323.51-.312.752-.6,1.439-.891,2.141-.111.023-.218.047-.329.064-.536.093-1.078.172-1.611.277a.855.855,0,0,0-.76.836,12.027,12.027,0,0,0,0,1.3.828.828,0,0,0,.647.787c.151.047.312.052.466.087a11.385,11.385,0,0,1,1.626.329c.23.609.446,1.177.667,1.765-.189.236-.37.466-.554.7-.227.283-.466.559-.679.854A.888.888,0,0,0,2.2,16.493a10.568,10.568,0,0,0,.816.915.842.842,0,0,0,1.116.157c.181-.1.35-.21.524-.318l1.2-.743c.437.224.83.452,1.241.635s.842.32,1.261.478c.038.186.073.341.1.5.087.492.157.988.262,1.477a.809.809,0,0,0,.848.728c.431.017.865.015,1.3-.006a.781.781,0,0,0,.749-.591c.061-.192.079-.4.125-.6a13.164,13.164,0,0,1,.28-1.492l2.141-.88c.192.131.4.268.6.411.335.236.661.484,1,.708a.863.863,0,0,0,1.232-.082,10.682,10.682,0,0,0,.772-.769.9.9,0,0,0,.093-1.238c-.277-.417-.58-.813-.865-1.224-.1-.146-.184-.306-.245-.408.3-.728.583-1.4.9-2.15.274-.05.565-.1.854-.154.335-.058.676-.1,1.008-.166a.891.891,0,0,0,.813-.935C20.329,10.352,20.332,9.964,20.311,9.58ZM10.188,14a3.779,3.779,0,0,1-3.8-3.825,3.812,3.812,0,1,1,7.624.023A3.769,3.769,0,0,1,10.188,14Z" transform="translate(0 -0.001)"/></svg>
|
After Width: | Height: | Size: 1.9 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20.327 20.328"><defs><style>.a{fill:#f4b728;fill-rule:evenodd;}</style></defs><path class="a" d="M20.311,9.58a.868.868,0,0,0-.725-.865c-.067-.017-.134-.017-.2-.029-.58-.1-1.159-.2-1.763-.312-.218-.571-.434-1.136-.658-1.713.1-.134.186-.265.283-.39.253-.32.5-.65.772-.953A1.017,1.017,0,0,0,18,3.707c-.146-.143-.271-.306-.408-.457-.577-.638-.859-.679-1.588-.23-.484.3-.961.6-1.471.921A8.953,8.953,0,0,0,12,2.771c-.047-.245-.093-.463-.131-.685-.07-.425-.137-.851-.213-1.276a.874.874,0,0,0-.827-.778,6.587,6.587,0,0,0-1.291,0A.861.861,0,0,0,8.708.8c-.041.248-.067.5-.131.737a6.365,6.365,0,0,1-.242,1.194c-.749.315-1.436.6-2.153.9-.131-.087-.283-.184-.431-.285-.373-.262-.734-.539-1.113-.787A.881.881,0,0,0,3.4,2.658c-.268.245-.527.5-.772.772a.863.863,0,0,0-.09,1.229c.236.361.5.7.746,1.058.125.184.312.338.323.51-.312.752-.6,1.439-.891,2.141-.111.023-.218.047-.329.064-.536.093-1.078.172-1.611.277a.855.855,0,0,0-.76.836,12.027,12.027,0,0,0,0,1.3.828.828,0,0,0,.647.787c.151.047.312.052.466.087a11.385,11.385,0,0,1,1.626.329c.23.609.446,1.177.667,1.765-.189.236-.37.466-.554.7-.227.283-.466.559-.679.854A.888.888,0,0,0,2.2,16.493a10.568,10.568,0,0,0,.816.915.842.842,0,0,0,1.116.157c.181-.1.35-.21.524-.318l1.2-.743c.437.224.83.452,1.241.635s.842.32,1.261.478c.038.186.073.341.1.5.087.492.157.988.262,1.477a.809.809,0,0,0,.848.728c.431.017.865.015,1.3-.006a.781.781,0,0,0,.749-.591c.061-.192.079-.4.125-.6a13.164,13.164,0,0,1,.28-1.492l2.141-.88c.192.131.4.268.6.411.335.236.661.484,1,.708a.863.863,0,0,0,1.232-.082,10.682,10.682,0,0,0,.772-.769.9.9,0,0,0,.093-1.238c-.277-.417-.58-.813-.865-1.224-.1-.146-.184-.306-.245-.408.3-.728.583-1.4.9-2.15.274-.05.565-.1.854-.154.335-.058.676-.1,1.008-.166a.891.891,0,0,0,.813-.935C20.329,10.352,20.332,9.964,20.311,9.58ZM10.188,14a3.779,3.779,0,0,1-3.8-3.825,3.812,3.812,0,1,1,7.624.023A3.769,3.769,0,0,1,10.188,14Z" transform="translate(0 -0.001)"/></svg>
|
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 3.4 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18.342 18.853"><defs><style>.a{fill:#6aeac0;}</style></defs><g transform="translate(34.55 55.562) rotate(180)"><g transform="translate(22.05 41.044)"><path class="a" d="M2.822,2.39h.817l3.889-.011c.69,0,1.381,0,2.071,0a.836.836,0,0,1,.1,1.668,1.591,1.591,0,0,1-.187.007q-4.277,0-8.55,0a1.1,1.1,0,0,1-.511-.093A.811.811,0,0,1,.228,2.67Q1.426,1.444,2.642.233A.8.8,0,0,1,3.777.24a.8.8,0,0,1,.015,1.127c-.295.31-.6.612-.907.918a.4.4,0,0,1-.1.056A.407.407,0,0,1,2.822,2.39Z" transform="translate(6.505 0) rotate(90)"/><path class="a" d="M2.822,1.664h.817l3.889.011c.69,0,1.381,0,2.071,0A.836.836,0,0,0,9.7.011,1.591,1.591,0,0,0,9.517,0Q5.24,0,.967,0A1.1,1.1,0,0,0,.455.093.811.811,0,0,0,.228,1.385q1.2,1.226,2.415,2.437A.808.808,0,0,0,3.792,2.687c-.295-.31-.6-.612-.907-.918a.4.4,0,0,0-.1-.056A.407.407,0,0,0,2.822,1.664Z" transform="translate(4.054 0) rotate(90)"/></g><g transform="translate(16.208 36.709)"><g transform="translate(0 0)"><path class="a" d="M9.568,0c.192.018.384.033.572.054a8.642,8.642,0,0,1,3.97,1.44,9.218,9.218,0,0,1,4.013,5.978,9.494,9.494,0,0,1-.923,6.481.923.923,0,0,0-.047.1.812.812,0,0,1-.919.5.847.847,0,0,1-.666-.85.325.325,0,0,1,.029-.13c.159-.38.322-.76.478-1.143a7.694,7.694,0,0,0,.59-3.456,7.687,7.687,0,0,0-2.4-5.236,7.093,7.093,0,0,0-4.071-1.979A7.2,7.2,0,0,0,4.249,3.579a7.447,7.447,0,0,0-2.5,4.614,7.65,7.65,0,0,0,1.925,6.492,7.156,7.156,0,0,0,4.429,2.377,7.222,7.222,0,0,0,5.453-1.39.387.387,0,0,0,.058-.051.8.8,0,0,1,.9-.17.871.871,0,0,1,.5.8.711.711,0,0,1-.275.572,8.448,8.448,0,0,1-4.48,1.954,8.725,8.725,0,0,1-6.846-2A9.249,9.249,0,0,1,.182,11.337a9.4,9.4,0,0,1-.127-3A9.343,9.343,0,0,1,2.36,3.108,8.962,8.962,0,0,1,7.234.214a8.521,8.521,0,0,1,1.419-.2A1.044,1.044,0,0,0,8.758,0C9.029,0,9.3,0,9.568,0Z" transform="translate(0.01)"/></g></g></g></svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18.342 18.853"><defs><style>.a{fill:#ff6c6c;}</style></defs><g transform="translate(-16.208 -36.709)"><g transform="translate(22.05 41.044)"><path class="a" d="M2.822,2.39h.817l3.889-.011c.69,0,1.381,0,2.071,0a.836.836,0,0,1,.1,1.668,1.591,1.591,0,0,1-.187.007q-4.277,0-8.55,0a1.1,1.1,0,0,1-.511-.093A.811.811,0,0,1,.228,2.67Q1.426,1.444,2.642.233A.8.8,0,0,1,3.777.24a.8.8,0,0,1,.015,1.127c-.295.31-.6.612-.907.918a.4.4,0,0,1-.1.056A.407.407,0,0,1,2.822,2.39Z" transform="translate(6.505 0) rotate(90)"/><path class="a" d="M2.822,1.664h.817l3.889.011c.69,0,1.381,0,2.071,0A.836.836,0,0,0,9.7.011,1.591,1.591,0,0,0,9.517,0Q5.24,0,.967,0A1.1,1.1,0,0,0,.455.093.811.811,0,0,0,.228,1.385q1.2,1.226,2.415,2.437A.808.808,0,0,0,3.792,2.687c-.295-.31-.6-.612-.907-.918a.4.4,0,0,0-.1-.056A.407.407,0,0,0,2.822,1.664Z" transform="translate(4.054 0) rotate(90)"/></g><g transform="translate(16.208 36.709)"><g transform="translate(0 0)"><path class="a" d="M9.568,0c.192.018.384.033.572.054a8.642,8.642,0,0,1,3.97,1.44,9.218,9.218,0,0,1,4.013,5.978,9.494,9.494,0,0,1-.923,6.481.923.923,0,0,0-.047.1.812.812,0,0,1-.919.5.847.847,0,0,1-.666-.85.325.325,0,0,1,.029-.13c.159-.38.322-.76.478-1.143a7.694,7.694,0,0,0,.59-3.456,7.687,7.687,0,0,0-2.4-5.236,7.093,7.093,0,0,0-4.071-1.979A7.2,7.2,0,0,0,4.249,3.579a7.447,7.447,0,0,0-2.5,4.614,7.65,7.65,0,0,0,1.925,6.492,7.156,7.156,0,0,0,4.429,2.377,7.222,7.222,0,0,0,5.453-1.39.387.387,0,0,0,.058-.051.8.8,0,0,1,.9-.17.871.871,0,0,1,.5.8.711.711,0,0,1-.275.572,8.448,8.448,0,0,1-4.48,1.954,8.725,8.725,0,0,1-6.846-2A9.249,9.249,0,0,1,.182,11.337a9.4,9.4,0,0,1-.127-3A9.343,9.343,0,0,1,2.36,3.108,8.962,8.962,0,0,1,7.234.214a8.521,8.521,0,0,1,1.419-.2A1.044,1.044,0,0,0,8.758,0C9.029,0,9.3,0,9.568,0Z" transform="translate(0.01)"/></g></g></g></svg>
|
After Width: | Height: | Size: 1.8 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20.82 21.4"><defs><style>.a{fill:#3b3b3f;}</style></defs><g transform="translate(0)"><path class="a" d="M10.862,0c.218.021.435.037.649.062A9.81,9.81,0,0,1,16.017,1.7a10.464,10.464,0,0,1,4.555,6.786,10.777,10.777,0,0,1-1.047,7.356,1.048,1.048,0,0,0-.053.119.921.921,0,0,1-1.043.567.961.961,0,0,1-.756-.965.369.369,0,0,1,.033-.148c.181-.431.366-.863.542-1.3a8.733,8.733,0,0,0,.67-3.923,8.725,8.725,0,0,0-2.723-5.944A8.051,8.051,0,0,0,11.573,2,8.176,8.176,0,0,0,4.824,4.062,8.452,8.452,0,0,0,1.99,9.3a8.683,8.683,0,0,0,2.185,7.369,8.123,8.123,0,0,0,5.028,2.7,8.2,8.2,0,0,0,6.19-1.577.44.44,0,0,0,.066-.058.909.909,0,0,1,1.027-.193.988.988,0,0,1,.571.908.807.807,0,0,1-.312.649,9.59,9.59,0,0,1-5.085,2.218,9.9,9.9,0,0,1-7.771-2.271,10.5,10.5,0,0,1-3.68-6.174A10.675,10.675,0,0,1,.064,9.46,10.6,10.6,0,0,1,2.68,3.528,10.173,10.173,0,0,1,8.213.242,9.672,9.672,0,0,1,9.823.021,1.185,1.185,0,0,0,9.942,0C10.25,0,10.554,0,10.862,0Z" transform="translate(0.01)"/><path class="a" d="M14.3,17.106h.9l4.28-.012c.76,0,1.52,0,2.28,0a.92.92,0,0,1,.115,1.836,1.751,1.751,0,0,1-.205.008q-4.707,0-9.41,0a1.214,1.214,0,0,1-.563-.1.893.893,0,0,1-.251-1.421q1.318-1.349,2.658-2.682a.889.889,0,0,1,1.265,1.249c-.324.341-.661.674-1,1.01a.436.436,0,0,1-.115.062A.448.448,0,0,1,14.3,17.106Z" transform="translate(-6.589 -8.53)"/><path class="a" d="M19.609,29.83h-.14q-3.654.006-7.3.008a.932.932,0,0,1-.928-.641A.915.915,0,0,1,12.1,28c1.109-.008,2.218,0,3.331,0h6.334a.908.908,0,0,1,.949.858.881.881,0,0,1-.283.694c-.32.32-.641.645-.961.969l-1.631,1.643A.892.892,0,0,1,18.7,32.3a.877.877,0,0,1-.164-1.339c.32-.341.657-.67.99-1a1.143,1.143,0,0,1,.111-.086A.094.094,0,0,1,19.609,29.83Z" transform="translate(-6.592 -16.498)"/></g></svg>
|
After Width: | Height: | Size: 1.7 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20.82 21.4"><defs><style>.a{fill:#f4b728;}</style></defs><path class="a" d="M10.862,0c.218.021.435.037.649.062A9.81,9.81,0,0,1,16.017,1.7a10.464,10.464,0,0,1,4.555,6.786,10.777,10.777,0,0,1-1.047,7.356,1.048,1.048,0,0,0-.053.119.921.921,0,0,1-1.043.567.961.961,0,0,1-.756-.965.369.369,0,0,1,.033-.148c.181-.431.366-.863.542-1.3a8.733,8.733,0,0,0,.67-3.923,8.725,8.725,0,0,0-2.723-5.944A8.051,8.051,0,0,0,11.573,2,8.176,8.176,0,0,0,4.824,4.062,8.452,8.452,0,0,0,1.99,9.3a8.683,8.683,0,0,0,2.185,7.369,8.123,8.123,0,0,0,5.028,2.7,8.2,8.2,0,0,0,6.19-1.577.44.44,0,0,0,.066-.058.909.909,0,0,1,1.027-.193.988.988,0,0,1,.571.908.807.807,0,0,1-.312.649,9.59,9.59,0,0,1-5.085,2.218,9.9,9.9,0,0,1-7.771-2.271,10.5,10.5,0,0,1-3.68-6.174A10.675,10.675,0,0,1,.064,9.46,10.6,10.6,0,0,1,2.68,3.528,10.173,10.173,0,0,1,8.213.242,9.672,9.672,0,0,1,9.823.021,1.185,1.185,0,0,0,9.942,0C10.25,0,10.554,0,10.862,0Z" transform="translate(0.01)"/><path class="a" d="M14.3,17.106h.9l4.28-.012c.76,0,1.52,0,2.28,0a.92.92,0,0,1,.115,1.836,1.751,1.751,0,0,1-.205.008q-4.707,0-9.41,0a1.214,1.214,0,0,1-.563-.1.893.893,0,0,1-.251-1.421q1.318-1.349,2.658-2.682a.889.889,0,0,1,1.265,1.249c-.324.341-.661.674-1,1.01a.436.436,0,0,1-.115.062A.448.448,0,0,1,14.3,17.106Z" transform="translate(-6.589 -8.53)"/><path class="a" d="M19.609,29.83h-.14q-3.654.006-7.3.008a.932.932,0,0,1-.928-.641A.915.915,0,0,1,12.1,28c1.109-.008,2.218,0,3.331,0h6.334a.908.908,0,0,1,.949.858.881.881,0,0,1-.283.694c-.32.32-.641.645-.961.969l-1.631,1.643A.892.892,0,0,1,18.7,32.3a.877.877,0,0,1-.164-1.339c.32-.341.657-.67.99-1a1.143,1.143,0,0,1,.111-.086A.094.094,0,0,1,19.609,29.83Z" transform="translate(-6.592 -16.498)"/></svg>
|
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 151 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="19.818" height="35.161" viewBox="0 0 19.818 35.161"><defs><style>.a{fill:#FCD639;}</style></defs><path class="a" d="M541.425,662.318v4.555h-7.678v5.678H545.5l-11.751,16v4.261h7.678v4.665h4.563v-4.665h7.577v-5.666H541.788l11.777-16v-4.273h-7.577v-4.555Z" transform="translate(-533.747 -662.318)"/></svg>
|
After Width: | Height: | Size: 349 B |
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: Home
|
||||
route: /
|
||||
---
|
||||
|
||||
# UI Components
|
||||
|
||||
Use the sidebar menu to browse the styleguide of React components available for the ZEC Wallet application.
|
|
@ -0,0 +1,126 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Link } from 'react-router-dom';
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
/* eslint-disable max-len */
|
||||
// $FlowFixMe
|
||||
import { darken } from 'polished';
|
||||
import type { ElementProps } from 'react';
|
||||
|
||||
const DefaultButton = styled.button`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 10px 30px;
|
||||
font-family: ${props => props.theme.fontFamily};
|
||||
font-weight: ${props => props.theme.fontWeight.bold};
|
||||
font-size: ${props => `${props.theme.fontSize.regular}em`};
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
min-width: 100px;
|
||||
border-radius: 100px;
|
||||
transition: background-color 0.1s ${props => props.theme.colors.transitionEase};
|
||||
`;
|
||||
|
||||
const Primary = styled(DefaultButton)`
|
||||
background-color: ${props => props.theme.colors.primary};
|
||||
color: ${props => props.theme.colors.secondary};
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
background-color: ${props => darken(0.1, props.theme.colors.primary(props))};
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: ${props => props.theme.colors.buttonBorderColor};
|
||||
cursor: not-allowed;
|
||||
opacity: 0.8;
|
||||
}
|
||||
`;
|
||||
|
||||
const Secondary = styled(DefaultButton)`
|
||||
background-color: transparent;
|
||||
color: ${props => props.theme.colors.secondary};
|
||||
border: 2px solid ${props => props.theme.colors.buttonBorderColor};
|
||||
|
||||
&:hover {
|
||||
border-color: ${props => props.theme.colors.primary};
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: Transparent;
|
||||
cursor: not-allowed;
|
||||
color: ${props => props.theme.colors.buttonBorderColor};
|
||||
|
||||
&:hover {
|
||||
border-color: ${props => props.theme.colors.buttonBorderColor};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const Icon = styled.img`
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
margin-right: 10px;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
...ElementProps<'button'>,
|
||||
label: string,
|
||||
onClick?: () => void,
|
||||
to?: ?string,
|
||||
variant?: 'primary' | 'secondary',
|
||||
disabled?: boolean,
|
||||
icon?: string | null,
|
||||
className?: string,
|
||||
isLoading?: boolean,
|
||||
};
|
||||
|
||||
export const Button = ({
|
||||
onClick,
|
||||
label,
|
||||
to,
|
||||
variant,
|
||||
disabled,
|
||||
icon,
|
||||
className,
|
||||
isLoading,
|
||||
id,
|
||||
}: Props) => {
|
||||
if (to && onClick) throw new Error('Should define either "to" or "onClick"');
|
||||
|
||||
const props = {
|
||||
onClick,
|
||||
disabled: disabled || isLoading,
|
||||
icon: null,
|
||||
className,
|
||||
id,
|
||||
};
|
||||
const buttonLabel = isLoading ? 'Loading...' : label;
|
||||
|
||||
const component = variant === 'primary' ? (
|
||||
<Primary {...props}>
|
||||
{icon ? <Icon src={icon} /> : null}
|
||||
{buttonLabel}
|
||||
</Primary>
|
||||
) : (
|
||||
<Secondary {...props}>
|
||||
{icon ? <Icon src={icon} /> : null}
|
||||
{buttonLabel}
|
||||
</Secondary>
|
||||
);
|
||||
|
||||
return to ? <Link to={String(to)}>{component}</Link> : component;
|
||||
};
|
||||
|
||||
Button.defaultProps = {
|
||||
to: '',
|
||||
icon: null,
|
||||
variant: 'primary',
|
||||
onClick: () => {},
|
||||
disabled: false,
|
||||
className: '',
|
||||
isLoading: false,
|
||||
};
|
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
name: Button
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from 'docz'
|
||||
|
||||
import { Button } from './button.js'
|
||||
import { DoczWrapper } from '../theme.js'
|
||||
|
||||
# Button
|
||||
|
||||
<PropsTable of={Button} />
|
||||
|
||||
## Primary
|
||||
|
||||
<Playground>
|
||||
<div style={{ backgroundColor: '#000', padding: '20px' }}>
|
||||
<DoczWrapper>
|
||||
{() => (
|
||||
<Button
|
||||
label='Click me!'
|
||||
onClick={() => alert('Clicked')}
|
||||
/>
|
||||
)}
|
||||
</DoczWrapper>
|
||||
</div>
|
||||
</Playground>
|
||||
|
||||
## Secondary
|
||||
|
||||
<Playground>
|
||||
<div style={{ backgroundColor: '#000', padding: '20px' }}>
|
||||
<DoczWrapper>
|
||||
{() => (
|
||||
<Button
|
||||
label='Click me!'
|
||||
onClick={() => alert('Clicked')}
|
||||
variant='secondary'
|
||||
/>
|
||||
)}
|
||||
</DoczWrapper>
|
||||
</div>
|
||||
</Playground>
|
||||
|
||||
## Primary Disabled
|
||||
|
||||
<Playground>
|
||||
<div style={{ backgroundColor: '#000', padding: '20px' }}>
|
||||
<DoczWrapper>
|
||||
{() => (
|
||||
<Button
|
||||
label='Cannot click'
|
||||
disabled
|
||||
/>
|
||||
)}
|
||||
</DoczWrapper>
|
||||
</div>
|
||||
</Playground>
|
||||
|
||||
## Secondary Disabled
|
||||
|
||||
<Playground>
|
||||
<div style={{ backgroundColor: '#000', padding: '20px' }}>
|
||||
<DoczWrapper>
|
||||
{() => (
|
||||
<Button
|
||||
label='Cannot click'
|
||||
disabled
|
||||
variant='secondary'
|
||||
/>
|
||||
)}
|
||||
</DoczWrapper>
|
||||
</div>
|
||||
</Playground>
|
||||
|
||||
## Link Button
|
||||
|
||||
<Playground>
|
||||
<div style={{ backgroundColor: '#000', padding: '20px' }}>
|
||||
<DoczWrapper>
|
||||
{() => (
|
||||
<Button
|
||||
label='Click me!'
|
||||
isLink
|
||||
to='/my-route'
|
||||
/>
|
||||
)}
|
||||
</DoczWrapper>
|
||||
</div>
|
||||
</Playground>
|
|
@ -0,0 +1,50 @@
|
|||
// @flow
|
||||
import React, { PureComponent } from 'react';
|
||||
|
||||
import { Button } from './button';
|
||||
|
||||
type Props = {
|
||||
text: string,
|
||||
className?: string,
|
||||
};
|
||||
|
||||
type State = { copied: boolean };
|
||||
|
||||
export class Clipboard extends PureComponent<Props, State> {
|
||||
static defaultProps = {
|
||||
className: '',
|
||||
};
|
||||
|
||||
state = {
|
||||
copied: false,
|
||||
};
|
||||
|
||||
handleClick = () => {
|
||||
const { text } = this.props;
|
||||
|
||||
const el = document.createElement('textarea');
|
||||
el.value = text;
|
||||
|
||||
if (document.body) document.body.appendChild(el);
|
||||
|
||||
el.select();
|
||||
document.execCommand('copy');
|
||||
if (document.body) document.body.removeChild(el);
|
||||
|
||||
this.setState({ copied: true });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { className } = this.props;
|
||||
const { copied } = this.state;
|
||||
|
||||
return (
|
||||
<Button
|
||||
label={copied ? 'Copied!' : 'Copy!'}
|
||||
className={className}
|
||||
onClick={this.handleClick}
|
||||
disabled={copied}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import type { Node, ElementProps } from 'react';
|
||||
|
||||
const Flex = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: ${props => props.alignItems};
|
||||
justify-content: ${props => props.justifyContent};
|
||||
${props => props.width && `width: ${props.width};`}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
...ElementProps<'div'>,
|
||||
alignItems?: string,
|
||||
justifyContent?: string,
|
||||
className?: string,
|
||||
children: Node,
|
||||
width?: string,
|
||||
};
|
||||
|
||||
export const ColumnComponent = ({ children, ...props }: Props) => (
|
||||
<Flex {...props}>{React.Children.map(children, ch => ch)}</Flex>
|
||||
);
|
||||
|
||||
ColumnComponent.defaultProps = {
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'flex-start',
|
||||
className: '',
|
||||
width: '',
|
||||
};
|
|
@ -0,0 +1,119 @@
|
|||
// @flow
|
||||
import React, { type Element } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { TextComponent } from './text';
|
||||
import { Button } from './button';
|
||||
import { Divider } from './divider';
|
||||
import { ModalComponent } from './modal';
|
||||
|
||||
import CloseIcon from '../assets/images/close_icon.svg';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
width: ${props => `${props.width}px`};
|
||||
background-color: ${props => props.theme.colors.background};
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0px 0px 30px 0px black;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const CloseIconWrapper = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
position: absolute;
|
||||
`;
|
||||
|
||||
const TitleWrapper = styled.div`
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
`;
|
||||
|
||||
const CloseIconImg = styled.img`
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-top: 12px;
|
||||
margin-right: 12px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const Btn = styled(Button)`
|
||||
width: 95%;
|
||||
margin-bottom: 10px;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
renderTrigger: (() => void) => Element<*>,
|
||||
title: string,
|
||||
onConfirm: () => void,
|
||||
onClose?: () => void,
|
||||
showButtons?: boolean,
|
||||
width?: number,
|
||||
isLoading?: boolean,
|
||||
children: (() => void) => Element<*>,
|
||||
};
|
||||
|
||||
export const ConfirmDialogComponent = ({
|
||||
children,
|
||||
title,
|
||||
onConfirm,
|
||||
onClose,
|
||||
renderTrigger,
|
||||
showButtons,
|
||||
isLoading,
|
||||
width,
|
||||
}: Props) => {
|
||||
const handleClose = toggle => () => {
|
||||
toggle();
|
||||
if (onClose) onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<ModalComponent
|
||||
id='confirm-dialog-modal-wrapper'
|
||||
renderTrigger={renderTrigger}
|
||||
closeOnBackdropClick={false}
|
||||
closeOnEsc={false}
|
||||
>
|
||||
{toggle => (
|
||||
<Wrapper width={width}>
|
||||
<CloseIconWrapper>
|
||||
<CloseIconImg src={CloseIcon} onClick={handleClose(toggle)} />
|
||||
</CloseIconWrapper>
|
||||
<TitleWrapper>
|
||||
<TextComponent value={title} align='center' />
|
||||
</TitleWrapper>
|
||||
<Divider opacity={0.3} />
|
||||
{children(handleClose(toggle))}
|
||||
{showButtons && (
|
||||
<>
|
||||
<Btn
|
||||
id='confirm-modal-button'
|
||||
label='Confirm'
|
||||
onClick={onConfirm}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
<Btn
|
||||
label='Cancel'
|
||||
onClick={handleClose(toggle)}
|
||||
variant='secondary'
|
||||
disabled={isLoading}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Wrapper>
|
||||
)}
|
||||
</ModalComponent>
|
||||
);
|
||||
};
|
||||
|
||||
ConfirmDialogComponent.defaultProps = {
|
||||
showButtons: true,
|
||||
width: 460,
|
||||
isLoading: false,
|
||||
onClose: () => {},
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
---
|
||||
name: Confirm Dialog
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from 'docz'
|
||||
|
||||
import { Button } from './button.js'
|
||||
import { ConfirmDialogComponent } from './confirm-dialog.js'
|
||||
import { DoczWrapper } from '../theme.js'
|
||||
|
||||
# Confirm Dialog
|
||||
|
||||
## Properties
|
||||
|
||||
<PropsTable of={ConfirmDialogComponent} />
|
||||
|
||||
## Basic Usage
|
||||
|
||||
<Playground>
|
||||
<DoczWrapper>
|
||||
{() => (
|
||||
<ConfirmDialogComponent
|
||||
title="Confirm example"
|
||||
onConfirm={() => alert('Confirm')}
|
||||
renderTrigger={toggle => <button onClick={toggle}> Open! </button>}
|
||||
>
|
||||
{toggle => <div>Confirm content</div>}
|
||||
</ConfirmDialogComponent>
|
||||
)}
|
||||
</DoczWrapper>
|
||||
</Playground>
|
|
@ -0,0 +1,11 @@
|
|||
// @flow
|
||||
import styled from 'styled-components';
|
||||
|
||||
export const Divider = styled.div`
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background-color: ${props => props.color || props.theme.colors.text};
|
||||
opacity: ${props => props.opacity || 1};
|
||||
margin-bottom: ${props => props.marginBottom || 0};
|
||||
margin-top: ${props => props.marginTop || 0};
|
||||
`;
|
|
@ -0,0 +1,136 @@
|
|||
// @flow
|
||||
import React, { type Node, Component } from 'react';
|
||||
import styled from 'styled-components';
|
||||
/* eslint-disable import/no-extraneous-dependencies */
|
||||
// $FlowFixMe
|
||||
import { darken } from 'polished';
|
||||
import Popover from 'react-popover';
|
||||
import ClickOutside from 'react-click-outside';
|
||||
|
||||
import { TextComponent } from './text';
|
||||
|
||||
import truncateAddress from '../utils/truncateAddress';
|
||||
|
||||
/* eslint-disable max-len */
|
||||
const MenuWrapper = styled.div`
|
||||
background-image: ${props => `linear-gradient(to right, ${darken(
|
||||
0.05,
|
||||
props.theme.colors.activeItem,
|
||||
)}, ${props.theme.colors.activeItem})`};
|
||||
border-radius: ${props => props.theme.boxBorderRadius};
|
||||
margin-left: -10px;
|
||||
max-width: 400px;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const MenuItem = styled.button`
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
border-bottom-style: solid;
|
||||
border-bottom-color: ${props => props.theme.colors.text};
|
||||
border-bottom-width: 1px;
|
||||
padding: 15px;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: default;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const OptionItem = styled(MenuItem)`
|
||||
&:hover {
|
||||
background-color: #F9D114;
|
||||
}
|
||||
`;
|
||||
|
||||
const Option = styled(TextComponent)`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const PopoverWithStyle = styled(Popover)`
|
||||
& > .Popover-tip {
|
||||
fill: ${props => props.theme.colors.activeItem};
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
renderTrigger: (toggleVisibility: () => void, isOpen: boolean) => Node,
|
||||
options: Array<{ label: string, onClick: () => void }>,
|
||||
label?: string | null,
|
||||
truncate?: boolean,
|
||||
};
|
||||
|
||||
type State = {
|
||||
isOpen: boolean,
|
||||
};
|
||||
|
||||
export class DropdownComponent extends Component<Props, State> {
|
||||
state = {
|
||||
isOpen: false,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
label: null,
|
||||
truncate: false,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isOpen } = this.state;
|
||||
const {
|
||||
label, options, truncate, renderTrigger,
|
||||
} = this.props;
|
||||
|
||||
const body = [
|
||||
<ClickOutside
|
||||
onClickOutside={() => this.setState(() => ({ isOpen: false }))}
|
||||
>
|
||||
<MenuWrapper>
|
||||
{label && (
|
||||
<MenuItem disabled isGroupLabel>
|
||||
<TextComponent value={label} isBold />
|
||||
</MenuItem>
|
||||
)}
|
||||
{options.map(({ label: optionLabel, onClick }) => (
|
||||
<OptionItem onClick={onClick} key={optionLabel}>
|
||||
<Option value={truncate ? truncateAddress(optionLabel) : optionLabel} />
|
||||
</OptionItem>
|
||||
))}
|
||||
</MenuWrapper>
|
||||
</ClickOutside>,
|
||||
];
|
||||
|
||||
return (
|
||||
<PopoverWithStyle
|
||||
isOpen={isOpen}
|
||||
preferPlace='below'
|
||||
enterExitTransitionDurationMs={0}
|
||||
tipSize={7}
|
||||
body={body}
|
||||
>
|
||||
{renderTrigger(
|
||||
() => this.setState(state => ({ isOpen: !state.isOpen })),
|
||||
isOpen,
|
||||
)}
|
||||
</PopoverWithStyle>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
---
|
||||
name: Dropdown
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from 'docz'
|
||||
|
||||
import { Button } from './button.js'
|
||||
import { DropdownComponent } from './dropdown.js'
|
||||
import { DoczWrapper } from '../theme.js'
|
||||
|
||||
# Dropdown
|
||||
|
||||
## Properties
|
||||
|
||||
<PropsTable of={DropdownComponent} />
|
||||
|
||||
## Basic Usage
|
||||
|
||||
<Playground>
|
||||
<DoczWrapper>
|
||||
{() => (
|
||||
<div style={{ height: '500px' }}>
|
||||
<DropdownComponent
|
||||
label='Addresses'
|
||||
renderTrigger={toggleVisibility => (
|
||||
<Button
|
||||
label='Show Dropdown'
|
||||
onClick={toggleVisibility}
|
||||
/>
|
||||
)}
|
||||
options={[
|
||||
{ label: 'asbh1yeasbdh23848asdasd', onClick: console.log },
|
||||
{ label: 'urtyruhjr374hbfdjdhuh', onClick: console.log },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</DoczWrapper>
|
||||
</Playground>
|
|
@ -0,0 +1,19 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { TextComponent } from './text';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 30px 0;
|
||||
`;
|
||||
|
||||
export const EmptyTransactionsComponent = () => (
|
||||
<Wrapper>
|
||||
<TextComponent value='No transactions!' />
|
||||
</Wrapper>
|
||||
);
|
|
@ -0,0 +1,75 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { ZcashLogo } from './zcash-logo';
|
||||
import { TextComponent } from './text';
|
||||
import { Divider } from './divider';
|
||||
import { RowComponent } from './row';
|
||||
import { StatusPill } from './status-pill';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
height: ${props => props.theme.headerHeight};
|
||||
width: 100vw;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
background-color: ${props => props.theme.colors.background};
|
||||
`;
|
||||
|
||||
const LogoWrapper = styled.div`
|
||||
height: ${props => props.theme.headerHeight};
|
||||
width: ${props => props.theme.sidebarWidth};
|
||||
background-image: linear-gradient(
|
||||
to right,
|
||||
${props => props.theme.colors.sidebarLogoGradientBegin},
|
||||
${props => props.theme.colors.sidebarLogoGradientEnd}
|
||||
);
|
||||
margin-bottom: 20px;
|
||||
`;
|
||||
|
||||
const TitleWrapper = styled.div`
|
||||
width: ${props => `calc(100% - ${props.theme.sidebarWidth})`};
|
||||
height: ${props => props.theme.headerHeight};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
padding-top: 10px;
|
||||
padding-left: ${props => props.theme.layoutPaddingLeft};
|
||||
padding-right: ${props => props.theme.layoutPaddingRight};
|
||||
`;
|
||||
|
||||
const TitleRow = styled(RowComponent)`
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const Title = styled(TextComponent)`
|
||||
font-size: ${props => `${props.theme.fontSize.large}em`};
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
text-transform: capitalize;
|
||||
letter-spacing: 0.25px;
|
||||
font-weight: ${props => props.theme.fontWeight.bold};
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
title: string,
|
||||
};
|
||||
|
||||
export const HeaderComponent = ({ title }: Props) => (
|
||||
<Wrapper id='header'>
|
||||
<LogoWrapper>
|
||||
<ZcashLogo />
|
||||
</LogoWrapper>
|
||||
<TitleWrapper>
|
||||
<TitleRow alignItems='center' justifyContent='space-around'>
|
||||
<Title value={title} />
|
||||
<StatusPill />
|
||||
</TitleRow>
|
||||
<Divider opacity={0.1} />
|
||||
</TitleWrapper>
|
||||
</Wrapper>
|
||||
);
|
|
@ -0,0 +1,9 @@
|
|||
// @flow
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { TextComponent } from './text';
|
||||
|
||||
export const InputLabelComponent = styled(TextComponent)`
|
||||
margin: ${props => props.marginTop || '20px'} 0 8.5px 0;
|
||||
font-weight: ${props => props.theme.fontWeight.bold};
|
||||
`;
|
|
@ -0,0 +1,96 @@
|
|||
// @flow
|
||||
import React, { type Element } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import theme from '../theme';
|
||||
|
||||
const getDefaultStyles = t => styled[t]`
|
||||
border-radius: ${props => props.theme.boxBorderRadius};
|
||||
border: none;
|
||||
background-color: ${props => props.bgColor || props.theme.colors.inputBackground};
|
||||
color: ${props => props.theme.colors.text};
|
||||
padding: 15px;
|
||||
padding-right: ${props => (props.withRightElement ? '85px' : '15px')};
|
||||
width: 100%;
|
||||
outline: none;
|
||||
font-family: ${props => props.theme.fontFamily};
|
||||
|
||||
::placeholder {
|
||||
opacity: 0.5;
|
||||
}
|
||||
`;
|
||||
|
||||
const Wrapper = styled.div`
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const RightElement = styled.div`
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
`;
|
||||
|
||||
const Input = getDefaultStyles('input');
|
||||
const Textarea = getDefaultStyles('textarea');
|
||||
|
||||
type Props = {
|
||||
inputType?: 'input' | 'textarea',
|
||||
value: string,
|
||||
onChange?: string => void,
|
||||
onFocus?: (SyntheticFocusEvent<HTMLInputElement>) => void,
|
||||
rows?: number,
|
||||
disabled?: boolean,
|
||||
type?: string,
|
||||
step?: number,
|
||||
name?: string,
|
||||
renderRight?: () => Element<*> | null,
|
||||
bgColor?: string,
|
||||
};
|
||||
|
||||
export const InputComponent = ({
|
||||
inputType,
|
||||
bgColor,
|
||||
onChange = () => {},
|
||||
renderRight = () => null,
|
||||
...props
|
||||
}: Props) => {
|
||||
const rightElement = renderRight();
|
||||
|
||||
const inputTypes = {
|
||||
input: () => (
|
||||
<Input
|
||||
onChange={evt => onChange(evt.target.value)}
|
||||
withRightElement={Boolean(rightElement)}
|
||||
bgColor={bgColor}
|
||||
{...props}
|
||||
/>
|
||||
),
|
||||
textarea: () => (
|
||||
<Textarea onChange={evt => onChange(evt.target.value)} bgColor={bgColor} {...props} />
|
||||
),
|
||||
};
|
||||
|
||||
if (!Object.keys(inputTypes).find(key => key === inputType)) {
|
||||
throw new Error(`Invalid input type: ${String(inputType)}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
{inputTypes[inputType || 'input']()}
|
||||
{rightElement && <RightElement>{rightElement}</RightElement>}
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
InputComponent.defaultProps = {
|
||||
inputType: 'input',
|
||||
rows: 4,
|
||||
disabled: false,
|
||||
type: 'text',
|
||||
name: '',
|
||||
renderRight: () => null,
|
||||
onChange: () => {},
|
||||
onFocus: () => {},
|
||||
step: 1,
|
||||
bgColor: theme.colors.inputBackground,
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
name: Input
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from 'docz'
|
||||
|
||||
import { InputComponent } from './input.js'
|
||||
import { DoczWrapper } from '../theme.js'
|
||||
|
||||
# Input
|
||||
|
||||
## Properties
|
||||
|
||||
<PropsTable of={InputComponent} />
|
||||
|
||||
## Text Input
|
||||
|
||||
<Playground>
|
||||
<DoczWrapper>
|
||||
{() => (
|
||||
<InputComponent
|
||||
inputType='input'
|
||||
value='Hello World!'
|
||||
onChange={console.log}
|
||||
/>
|
||||
)}
|
||||
</DoczWrapper>
|
||||
</Playground>
|
||||
|
||||
## Textarea
|
||||
|
||||
<Playground>
|
||||
<DoczWrapper>
|
||||
{() => (
|
||||
<InputComponent
|
||||
inputType='textarea'
|
||||
value='I am Zcash Electron Wallet'
|
||||
onChange={console.log}
|
||||
rows={10}
|
||||
/>
|
||||
)}
|
||||
</DoczWrapper>
|
||||
</Playground>
|
|
@ -0,0 +1,25 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Layout = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: ${props => `calc(100% - ${props.theme.sidebarWidth})`};
|
||||
height: ${props => `calc(100vh - ${props.theme.headerHeight})`};
|
||||
background-color: ${props => props.theme.colors.background};
|
||||
padding-left: ${props => props.theme.layoutPaddingLeft};
|
||||
padding-right: ${props => props.theme.layoutPaddingRight};
|
||||
overflow: auto;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
chidren: any, // eslint-disable-line
|
||||
};
|
||||
|
||||
export const LayoutComponent = (props: Props) => {
|
||||
// $FlowFixMe
|
||||
const { children } = props; // eslint-disable-line
|
||||
|
||||
return <Layout id='layout'>{children}</Layout>;
|
||||
};
|
|
@ -0,0 +1,95 @@
|
|||
// @flow
|
||||
import React, { PureComponent } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Transition, animated } from 'react-spring';
|
||||
|
||||
import CircleProgressComponent from 'react-circle';
|
||||
import { TextComponent } from './text';
|
||||
|
||||
import zcashLogo from '../assets/images/zcash-simple-icon.svg';
|
||||
|
||||
import theme from '../theme';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: ${props => props.theme.colors.cardBackgroundColor};
|
||||
`;
|
||||
|
||||
const CircleWrapper = styled.div`
|
||||
width: 125px;
|
||||
height: 125px;
|
||||
position: relative;
|
||||
margin-bottom: 25px;
|
||||
`;
|
||||
|
||||
const Logo = styled.img`
|
||||
z-index: 10;
|
||||
position: absolute;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
top: calc(50% - 25px);
|
||||
left: calc(50% - 25px);
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
progress: number,
|
||||
};
|
||||
|
||||
type State = {
|
||||
start: boolean,
|
||||
};
|
||||
|
||||
const TIME_DELAY_ANIM = 100;
|
||||
|
||||
export class LoadingScreen extends PureComponent<Props, State> {
|
||||
state = { start: false };
|
||||
|
||||
componentDidMount() {
|
||||
setTimeout(() => {
|
||||
this.setState(() => ({ start: true }));
|
||||
}, TIME_DELAY_ANIM);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { start } = this.state;
|
||||
const { progress } = this.props;
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Transition
|
||||
native
|
||||
items={start}
|
||||
enter={[{ height: 'auto', opacity: 1 }]}
|
||||
leave={{ height: 0, opacity: 0 }}
|
||||
from={{
|
||||
position: 'absolute',
|
||||
overflow: 'hidden',
|
||||
height: 0,
|
||||
opacity: 0,
|
||||
}}
|
||||
>
|
||||
{() => props => (
|
||||
<animated.div style={props} id='loading-screen'>
|
||||
<CircleWrapper>
|
||||
<Logo src={zcashLogo} alt='Zcash logo' />
|
||||
<CircleProgressComponent
|
||||
progress={progress}
|
||||
responsive
|
||||
showPercentage={false}
|
||||
progressColor={theme.colors.activeItem}
|
||||
bgColor={theme.colors.inactiveItem}
|
||||
/>
|
||||
</CircleWrapper>
|
||||
<TextComponent value='ZEC Wallet Starting' />
|
||||
</animated.div>
|
||||
)}
|
||||
</Transition>
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
// @flow
|
||||
import React, { PureComponent, Fragment, type Element } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const ModalWrapper = styled.div`
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
position: fixed;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
`;
|
||||
|
||||
const ChildrenWrapper = styled.div`
|
||||
z-index: 90;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
renderTrigger: (() => void) => Element<*>,
|
||||
children: (() => void) => Element<*>,
|
||||
closeOnBackdropClick?: boolean,
|
||||
closeOnEsc?: boolean,
|
||||
};
|
||||
|
||||
type State = {
|
||||
isVisible: boolean,
|
||||
};
|
||||
|
||||
const modalRoot = document.getElementById('modal-root');
|
||||
|
||||
export class ModalComponent extends PureComponent<Props, State> {
|
||||
element = document.createElement('div');
|
||||
|
||||
static defaultProps = {
|
||||
closeOnBackdropClick: true,
|
||||
closeOnEsc: true,
|
||||
};
|
||||
|
||||
state = {
|
||||
isVisible: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
const { closeOnEsc } = this.props;
|
||||
|
||||
if (closeOnEsc) {
|
||||
window.addEventListener('keydown', this.handleEscPress);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
const { closeOnEsc } = this.props;
|
||||
|
||||
if (closeOnEsc) {
|
||||
window.removeEventListener('keydown', this.handleEscPress);
|
||||
}
|
||||
}
|
||||
|
||||
handleEscPress = (event: Object) => {
|
||||
const { isVisible } = this.state;
|
||||
|
||||
if (event.key === 'Escape' && isVisible) {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
|
||||
open = () => {
|
||||
this.setState(
|
||||
() => ({ isVisible: true }),
|
||||
() => {
|
||||
if (modalRoot) modalRoot.appendChild(this.element);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
close = () => {
|
||||
this.setState(
|
||||
() => ({ isVisible: false }),
|
||||
() => {
|
||||
if (modalRoot) modalRoot.removeChild(this.element);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { renderTrigger, children, closeOnBackdropClick } = this.props;
|
||||
const { isVisible } = this.state;
|
||||
const toggleVisibility = isVisible ? this.close : this.open;
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
{renderTrigger(toggleVisibility)}
|
||||
{isVisible
|
||||
? createPortal(
|
||||
<ModalWrapper
|
||||
id='modal-portal-wrapper'
|
||||
onClick={(event) => {
|
||||
if (
|
||||
closeOnBackdropClick
|
||||
&& event.target.id === 'modal-portal-wrapper'
|
||||
) this.close();
|
||||
}}
|
||||
>
|
||||
<ChildrenWrapper>{children(toggleVisibility)}</ChildrenWrapper>
|
||||
</ModalWrapper>,
|
||||
this.element,
|
||||
)
|
||||
: null}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
name: Modal
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from 'docz'
|
||||
|
||||
import { ModalComponent } from './modal.js'
|
||||
|
||||
# Modal
|
||||
|
||||
## Properties
|
||||
|
||||
<PropsTable of={ModalComponent} />
|
||||
|
||||
## Basic usage
|
||||
|
||||
<Playground>
|
||||
<ModalComponent
|
||||
renderTrigger={toggleVisibility => (
|
||||
<button type="button" onClick={toggleVisibility}>
|
||||
Open Modal
|
||||
</button>
|
||||
)}
|
||||
>
|
||||
{toggleVisibility => (
|
||||
<div style={{ padding: '50px', backgroundColor: 'white' }}>
|
||||
Modal Content
|
||||
<button type="button" onClick={toggleVisibility}>
|
||||
Close Modal
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</ModalComponent>
|
||||
</Playground>
|
||||
|
||||
## Don't close with ESC or backdrop click
|
||||
|
||||
<Playground>
|
||||
<ModalComponent
|
||||
closeOnEsc={false}
|
||||
closeOnBackdropClick={false}
|
||||
renderTrigger={toggleVisibility => (
|
||||
<button type="button" onClick={toggleVisibility}>
|
||||
Open Modal
|
||||
</button>
|
||||
)}
|
||||
>
|
||||
{toggleVisibility => (
|
||||
<div style={{ padding: '50px', backgroundColor: 'white' }}>
|
||||
Modal Content
|
||||
<button type="button" onClick={toggleVisibility}>
|
||||
Close Modal
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</ModalComponent>
|
||||
</Playground>
|
|
@ -0,0 +1,17 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import QR from 'qrcode.react';
|
||||
|
||||
type Props = {
|
||||
value: string,
|
||||
size?: number,
|
||||
};
|
||||
|
||||
export const QRCode = ({ value, size }: Props) => (
|
||||
<QR value={value} size={size} />
|
||||
);
|
||||
|
||||
QRCode.defaultProps = {
|
||||
size: 128,
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
name: QRCode
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from 'docz'
|
||||
|
||||
import { QRCode } from './qrcode.js'
|
||||
|
||||
# QRCode
|
||||
|
||||
## Properties
|
||||
|
||||
<PropsTable of={QRCode} />
|
||||
|
||||
## Basic usage
|
||||
|
||||
<Playground>
|
||||
<QRCode value='https://z.cash.foundation' />
|
||||
</Playground>
|
||||
|
||||
## Custom size
|
||||
|
||||
<Playground>
|
||||
<QRCode
|
||||
value='https://z.cash.foundation'
|
||||
size={500}
|
||||
/>
|
||||
</Playground>
|
|
@ -0,0 +1,30 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import type { Node, ElementProps } from 'react';
|
||||
|
||||
const Flex = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: ${props => props.alignItems};
|
||||
justify-content: ${props => props.justifyContent};
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
...ElementProps<'div'>,
|
||||
alignItems?: string,
|
||||
justifyContent?: string,
|
||||
className?: string,
|
||||
children: Node,
|
||||
};
|
||||
|
||||
export const RowComponent = ({ children, ...props }: Props) => (
|
||||
<Flex {...props}>{React.Children.map(children, ch => ch)}</Flex>
|
||||
);
|
||||
|
||||
RowComponent.defaultProps = {
|
||||
alignItems: 'flex-start',
|
||||
justifyContent: 'flex-start',
|
||||
className: '',
|
||||
};
|
|
@ -0,0 +1,192 @@
|
|||
// @flow
|
||||
import React, { PureComponent } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { TextComponent } from './text';
|
||||
import ChevronUp from '../assets/images/chevron-up.svg';
|
||||
import ChevronDown from '../assets/images/chevron-down.svg';
|
||||
|
||||
import theme from '../theme';
|
||||
|
||||
/* eslint-disable max-len */
|
||||
const SelectWrapper = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
border-radius: ${props => props.theme.boxBorderRadius};
|
||||
border: none;
|
||||
background-color: ${props => props.bgColor || props.theme.colors.inputBackground};
|
||||
color: ${props => props.theme.colors.text};
|
||||
width: 100%;
|
||||
outline: none;
|
||||
font-family: ${props => props.theme.fontFamily};
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
|
||||
${props => props.isOpen
|
||||
&& `border-${props.placement}-left-radius: 0; border-${props.placement}-right-radius: 0;`}
|
||||
`;
|
||||
/* eslint-enable max-len */
|
||||
|
||||
const ValueWrapper = styled.div`
|
||||
width: 95%;
|
||||
padding: 13px;
|
||||
opacity: ${props => (props.hasValue ? '1' : '0.2')};
|
||||
text-transform: capitalize;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
`;
|
||||
|
||||
const SelectMenuButtonWrapper = styled.button`
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
width: 50px;
|
||||
height: 100%;
|
||||
padding: 13px;
|
||||
border-left: ${props => `1px solid ${props.theme.colors.background}`};
|
||||
`;
|
||||
/* eslint-disable max-len */
|
||||
const SelectMenuButton = styled.button`
|
||||
padding: 3px 7px;
|
||||
outline: none;
|
||||
background-color: transparent;
|
||||
border: 1px solid ${props => (props.isOpen ? props.theme.colors.primary : '#29292D')};
|
||||
border-radius: 100%;
|
||||
`;
|
||||
/* eslint-enable max-len */
|
||||
|
||||
const Icon = styled.img`
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
`;
|
||||
|
||||
const OptionsWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
${props => `${props.placement}: ${`-${props.optionsAmount * 40}px`}`};
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
const Option = styled.button`
|
||||
border: none;
|
||||
background: none;
|
||||
height: 40px;
|
||||
background-color: #5d5d5d;
|
||||
cursor: pointer;
|
||||
z-index: 99;
|
||||
text-transform: capitalize;
|
||||
padding: 5px 10px;
|
||||
border-bottom: 1px solid #4e4b4b;
|
||||
|
||||
&:hover {
|
||||
background-color: ${props => props.theme.colors.background};
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-bottom-left-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border-bottom: 1px solid transparent;
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
value: string,
|
||||
options: { value: string, label: string }[],
|
||||
placeholder?: string,
|
||||
onChange: string => void,
|
||||
placement?: 'top' | 'bottom',
|
||||
bgColor?: string,
|
||||
};
|
||||
type State = {
|
||||
isOpen: boolean,
|
||||
};
|
||||
|
||||
export class SelectComponent extends PureComponent<Props, State> {
|
||||
state = {
|
||||
isOpen: false,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
placeholder: '',
|
||||
placement: 'bottom',
|
||||
bgColor: theme.colors.inputBackground,
|
||||
};
|
||||
|
||||
onSelect = (value: string) => {
|
||||
const { onChange } = this.props;
|
||||
|
||||
this.setState(() => ({ isOpen: false }), () => onChange(value));
|
||||
};
|
||||
|
||||
handleClickOutside = (event: Object) => {
|
||||
const { isOpen } = this.state;
|
||||
if (isOpen && event.target.id !== 'select-options-wrapper') this.setState(() => ({ isOpen: false }));
|
||||
};
|
||||
|
||||
getSelectedLabel = (value: string) => {
|
||||
const { options } = this.props;
|
||||
const option = options.find(opt => opt.value === value);
|
||||
|
||||
if (option) return option.label;
|
||||
};
|
||||
|
||||
getSelectIcon = () => {
|
||||
const { placement } = this.props;
|
||||
const { isOpen } = this.state;
|
||||
|
||||
if (placement === 'top') {
|
||||
return !isOpen ? ChevronUp : ChevronDown;
|
||||
}
|
||||
|
||||
return !isOpen ? ChevronDown : ChevronUp;
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
value, options, placeholder, placement, bgColor,
|
||||
} = this.props;
|
||||
const { isOpen } = this.state;
|
||||
|
||||
return (
|
||||
<SelectWrapper
|
||||
id='select-component'
|
||||
isOpen={isOpen}
|
||||
placement={placement}
|
||||
onClick={() => this.setState(() => ({ isOpen: !isOpen }))}
|
||||
bgColor={bgColor}
|
||||
>
|
||||
<ValueWrapper hasValue={Boolean(value)}>
|
||||
{this.getSelectedLabel(value) || placeholder}
|
||||
</ValueWrapper>
|
||||
<SelectMenuButtonWrapper>
|
||||
<SelectMenuButton isOpen={isOpen}>
|
||||
<Icon src={this.getSelectIcon()} />
|
||||
</SelectMenuButton>
|
||||
</SelectMenuButtonWrapper>
|
||||
{isOpen && (
|
||||
<OptionsWrapper
|
||||
id='select-options-wrapper'
|
||||
optionsAmount={options.length}
|
||||
placement={placement}
|
||||
>
|
||||
{options.map(({ label, value: optionValue }) => (
|
||||
<Option
|
||||
id={optionValue}
|
||||
key={label + optionValue}
|
||||
onClick={() => this.onSelect(optionValue)}
|
||||
bgColor={bgColor}
|
||||
>
|
||||
<TextComponent value={label} />
|
||||
</Option>
|
||||
))}
|
||||
</OptionsWrapper>
|
||||
)}
|
||||
</SelectWrapper>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Link, type Location } from 'react-router-dom';
|
||||
import { MENU_OPTIONS } from '../constants/sidebar';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: ${props => props.theme.sidebarWidth};
|
||||
height: ${props => `calc(100vh - ${props.theme.headerHeight})`};
|
||||
font-family: ${props => props.theme.fontFamily}
|
||||
background-color: ${props => props.theme.colors.sidebarBg};
|
||||
padding-top: 15px;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const StyledLink = styled(Link)`
|
||||
color: ${props => (props.isActive
|
||||
? props.theme.colors.sidebarItemActive
|
||||
: props.theme.colors.sidebarItem)};
|
||||
font-size: ${props => `${props.theme.fontSize.regular}em`};
|
||||
text-decoration: none;
|
||||
font-weight: ${props => props.theme.fontWeight.bold};
|
||||
background-color: ${props => (props.isActive
|
||||
? `${props.theme.colors.sidebarHoveredItem}`
|
||||
: 'transparent')};
|
||||
letter-spacing: 0.25px;
|
||||
padding: 25px 20px;
|
||||
height: 35px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
outline: none;
|
||||
border-right: ${props => (props.isActive
|
||||
? `3px solid ${props.theme.colors.sidebarItemActive}`
|
||||
: 'none')};
|
||||
cursor: pointer;
|
||||
transition: all 0.03s ${props => props.theme.colors.transitionEase};
|
||||
|
||||
&:hover {
|
||||
color: ${
|
||||
/* eslint-disable-next-line max-len */
|
||||
props => (props.isActive ? props.theme.colors.sidebarItemActive : '#ddd')
|
||||
}
|
||||
background-color: ${props => props.theme.colors.sidebarHoveredItem};
|
||||
}
|
||||
`;
|
||||
|
||||
const Icon = styled.img`
|
||||
width: 17px;
|
||||
height: 17px;
|
||||
margin-right: 13px;
|
||||
|
||||
${StyledLink}:hover & {
|
||||
filter: ${props => (props.isActive ? 'none' : 'brightness(300%)')};
|
||||
}
|
||||
`;
|
||||
|
||||
type MenuItem = {
|
||||
route: string,
|
||||
label: string,
|
||||
icon: (isActive: boolean) => string,
|
||||
};
|
||||
|
||||
type Props = {
|
||||
options?: MenuItem[],
|
||||
location: Location,
|
||||
};
|
||||
|
||||
export const SidebarComponent = ({ options, location }: Props) => (
|
||||
<Wrapper id='sidebar'>
|
||||
{(options || []).map((item) => {
|
||||
const isActive = location.pathname === item.route;
|
||||
|
||||
return (
|
||||
<StyledLink isActive={isActive} key={item.route} to={item.route}>
|
||||
<Icon
|
||||
isActive={isActive}
|
||||
src={item.icon(isActive)}
|
||||
alt={`${item.route}`}
|
||||
/>
|
||||
{item.label}
|
||||
</StyledLink>
|
||||
);
|
||||
})}
|
||||
</Wrapper>
|
||||
);
|
||||
|
||||
SidebarComponent.defaultProps = {
|
||||
options: MENU_OPTIONS,
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
---
|
||||
name: Sidebar
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from 'docz'
|
||||
|
||||
import { SidebarComponent } from './sidebar.js'
|
||||
import { DoczWrapper } from '../theme.js'
|
||||
|
||||
# Sidebar
|
||||
|
||||
## General
|
||||
|
||||
Sidebar component does not behave correctly inside Docz Styleguide as it is tighly coupled with the router.
|
||||
|
||||
## Properties
|
||||
|
||||
<PropsTable of={SidebarComponent} />
|
||||
|
||||
## Basic Usage
|
||||
|
||||
<Playground>
|
||||
<DoczWrapper>
|
||||
{() => <SidebarComponent location={{}} />}
|
||||
</DoczWrapper>
|
||||
</Playground>
|
|
@ -0,0 +1,115 @@
|
|||
// @flow
|
||||
import React, { Component } from 'react';
|
||||
import styled, { keyframes } from 'styled-components';
|
||||
import eres from 'eres';
|
||||
|
||||
import { TextComponent } from './text';
|
||||
|
||||
import rpc from '../../services/api';
|
||||
|
||||
import readyIcon from '../assets/images/green_check.png';
|
||||
import syncIcon from '../assets/images/sync_icon.png';
|
||||
import errorIcon from '../assets/images/error_icon.png';
|
||||
|
||||
const rotate = keyframes`
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
`;
|
||||
|
||||
const Wrapper = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
background-color: #000;
|
||||
border-radius: 27px;
|
||||
padding: 8px 16px;
|
||||
`;
|
||||
|
||||
const Icon = styled.img`
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
margin-right: 8px;
|
||||
animation: 2s linear infinite;
|
||||
animation-name: ${props => (props.animated ? rotate : 'none')};
|
||||
`;
|
||||
|
||||
const StatusPillLabel = styled(TextComponent)`
|
||||
color: ${props => props.theme.colors.statusPillLabel};
|
||||
font-weight: ${props => props.theme.fontWeight.bold};
|
||||
text-transform: uppercase;
|
||||
font-size: 10px;
|
||||
padding-top: 1px;
|
||||
user-select: none;
|
||||
`;
|
||||
|
||||
type Props = {};
|
||||
|
||||
type State = {
|
||||
type: string,
|
||||
icon: string,
|
||||
progress: number,
|
||||
isSyncing: boolean,
|
||||
};
|
||||
|
||||
export class StatusPill extends Component<Props, State> {
|
||||
timer: ?IntervalID = null;
|
||||
|
||||
state = {
|
||||
type: 'syncing',
|
||||
icon: syncIcon,
|
||||
progress: 0,
|
||||
isSyncing: true,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.timer = setInterval(() => {
|
||||
this.getBlockchainStatus();
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
getBlockchainStatus = async () => {
|
||||
const [blockchainErr, blockchaininfo] = await eres(rpc.getblockchaininfo());
|
||||
|
||||
const newProgress = blockchaininfo.verificationprogress * 100;
|
||||
|
||||
this.setState({
|
||||
progress: newProgress,
|
||||
...(newProgress > 99.99
|
||||
? {
|
||||
type: 'ready',
|
||||
icon: readyIcon,
|
||||
isSyncing: false,
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
|
||||
if (blockchainErr) {
|
||||
this.setState(() => ({ type: 'error', icon: errorIcon }));
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
type, icon, progress, isSyncing,
|
||||
} = this.state;
|
||||
const showPercent = isSyncing ? `(${progress.toFixed(2)}%)` : '';
|
||||
|
||||
return (
|
||||
<Wrapper id='status-pill'>
|
||||
<Icon src={icon} animated={isSyncing} />
|
||||
<StatusPillLabel value={`${type} ${showPercent}`} />
|
||||
</Wrapper>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
name: StatusPill
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from 'docz'
|
||||
|
||||
import { StatusPill } from './status-pill.js'
|
||||
import { DoczWrapper } from '../theme.js'
|
||||
|
||||
# StatusPill
|
||||
|
||||
## Properties
|
||||
|
||||
<PropsTable of={StatusPill} />
|
||||
|
||||
## Basic Usage
|
||||
|
||||
<Playground>
|
||||
<DoczWrapper>
|
||||
{() => <StatusPill percent={83.} />}
|
||||
</DoczWrapper>
|
||||
</Playground>
|
|
@ -0,0 +1,50 @@
|
|||
// @flow
|
||||
/* eslint-disable max-len */
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import type { ElementProps } from 'react';
|
||||
|
||||
import theme from '../theme';
|
||||
|
||||
const Text = styled.p`
|
||||
font-family: ${props => props.theme.fontFamily};
|
||||
font-size: ${props => props.size};
|
||||
color: ${props => props.color || props.theme.colors.text};
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-weight: ${props => (props.isBold ? props.theme.fontWeight.bold : props.theme.fontWeight.default)};
|
||||
text-align: ${props => props.align};
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
...ElementProps<'p'>,
|
||||
value: string,
|
||||
isBold?: boolean,
|
||||
color?: string,
|
||||
className?: string,
|
||||
size?: string | number,
|
||||
align?: string,
|
||||
};
|
||||
|
||||
export const TextComponent = ({
|
||||
value, isBold, color, className, size, align, id,
|
||||
}: Props) => (
|
||||
<Text
|
||||
id={id}
|
||||
className={className}
|
||||
isBold={isBold}
|
||||
color={color}
|
||||
size={`${String(size)}em`}
|
||||
align={align}
|
||||
>
|
||||
{value}
|
||||
</Text>
|
||||
);
|
||||
|
||||
TextComponent.defaultProps = {
|
||||
className: '',
|
||||
isBold: false,
|
||||
color: theme.colors.text,
|
||||
size: theme.fontSize.regular,
|
||||
align: 'left',
|
||||
};
|
|
@ -1,81 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import type { TodoType } from '../../types/todo';
|
||||
|
||||
type Props = {
|
||||
updateTodo: Function,
|
||||
todo: TodoType,
|
||||
cancelUpdateTodo: Function,
|
||||
};
|
||||
|
||||
type State = {
|
||||
value: string,
|
||||
};
|
||||
|
||||
export default class TodoEditInput extends Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
value: props.todo.text || '',
|
||||
};
|
||||
}
|
||||
|
||||
handleSubmit = (event: SyntheticInputEvent<HTMLInputElement>, id: string) => {
|
||||
const { value } = this.state;
|
||||
const { updateTodo } = this.props;
|
||||
const trimValue = value.trim();
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (trimValue !== '') {
|
||||
updateTodo(id, trimValue);
|
||||
this.setState({ value: '' });
|
||||
}
|
||||
}
|
||||
|
||||
handleCancel = (id: string) => {
|
||||
const { cancelUpdateTodo } = this.props;
|
||||
cancelUpdateTodo(id);
|
||||
}
|
||||
|
||||
handleInputChange = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
||||
const { target: { value } } = event;
|
||||
this.setState({ value });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { value } = this.state;
|
||||
const { todo } = this.props;
|
||||
|
||||
return (
|
||||
<div className='todo-item__view todo-item__view--edit'>
|
||||
<form
|
||||
className='todo-item__input'
|
||||
onSubmit={e => this.handleSubmit(e, todo.id)}
|
||||
>
|
||||
<input
|
||||
value={value}
|
||||
onChange={this.handleInputChange}
|
||||
className='todo-item__input-field'
|
||||
autoFocus
|
||||
/>
|
||||
<button
|
||||
type='submit'
|
||||
className='todo-item__input-button'
|
||||
>
|
||||
Update
|
||||
</button>
|
||||
</form>
|
||||
<button
|
||||
type='button'
|
||||
className='todo-item__input-cancel'
|
||||
onClick={() => this.handleCancel(todo.id)}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,58 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React, { Component } from 'react';
|
||||
|
||||
type Props = {
|
||||
addTodo: Function,
|
||||
};
|
||||
|
||||
type State = {
|
||||
value: string,
|
||||
};
|
||||
|
||||
export default class TodoInput extends Component<Props, State> {
|
||||
state = {
|
||||
value: '',
|
||||
};
|
||||
|
||||
handleSubmit = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
||||
const { value } = this.state;
|
||||
const { addTodo } = this.props;
|
||||
const trimValue = value.trim();
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (trimValue !== '') {
|
||||
addTodo(trimValue);
|
||||
this.setState({ value: '' });
|
||||
}
|
||||
}
|
||||
|
||||
handleInputChange = (event: SyntheticInputEvent<HTMLInputElement>) => {
|
||||
const { target: { value } } = event;
|
||||
this.setState({ value });
|
||||
}
|
||||
|
||||
render() {
|
||||
const { value } = this.state;
|
||||
|
||||
return (
|
||||
<form
|
||||
className='todo__input'
|
||||
onSubmit={this.handleSubmit}
|
||||
>
|
||||
<input
|
||||
value={value}
|
||||
onChange={this.handleInputChange}
|
||||
className='todo__input-field'
|
||||
/>
|
||||
<button
|
||||
type='submit'
|
||||
className='todo__input-button'
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import type { TodoType } from '../../types/todo';
|
||||
|
||||
type Props = {
|
||||
todo: TodoType,
|
||||
deleteTodo: Function,
|
||||
toggleEdit: Function,
|
||||
};
|
||||
|
||||
export default class TodoListItem extends PureComponent<Props> {
|
||||
handleDelete = (id: string) => {
|
||||
if (!id) return;
|
||||
|
||||
const { deleteTodo } = this.props;
|
||||
deleteTodo(id);
|
||||
}
|
||||
|
||||
handleEditToggle = (id: string) => {
|
||||
if (!id) return;
|
||||
|
||||
const { toggleEdit } = this.props;
|
||||
toggleEdit(id);
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
todo,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div className='todo-item__view todo-item__view--view'>
|
||||
<span className='todo-item__text'>
|
||||
{todo.text}
|
||||
</span>
|
||||
<div className='todo-item__buttons'>
|
||||
<button
|
||||
type='button'
|
||||
onClick={() => this.handleEditToggle(todo.id)}
|
||||
className='todo-item__button'
|
||||
>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 528.899 528.899'><path className='todo-item__svg' d='M328.883 89.125l107.59 107.589-272.34 272.34L56.604 361.465l272.279-272.34zm189.23-25.948l-47.981-47.981c-18.543-18.543-48.653-18.543-67.259 0l-45.961 45.961 107.59 107.59 53.611-53.611c14.382-14.383 14.382-37.577 0-51.959zM.3 512.69c-1.958 8.812 5.998 16.708 14.811 14.565l119.891-29.069L27.473 390.597.3 512.69z' /></svg>
|
||||
</button>
|
||||
<button
|
||||
type='button'
|
||||
onClick={() => this.handleDelete(todo.id)}
|
||||
className='todo-item__button'
|
||||
>
|
||||
<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 512 512'><path className='todo-item__svg' fill='#1D1D1B' d='M459.232 60.687h-71.955c-1.121-17.642-15.631-31.657-33.553-31.657H161.669c-17.921 0-32.441 14.015-33.553 31.657H64.579c-18.647 0-33.767 15.12-33.767 33.768v8.442c0 18.648 15.12 33.768 33.767 33.768h21.04v342.113c0 13.784 11.179 24.963 24.963 24.963h308.996c13.784 0 24.964-11.179 24.964-24.963V136.665h14.691c18.663 0 33.768-15.12 33.768-33.768v-8.442c-.001-18.648-15.105-33.768-33.769-33.768zM196.674 443.725c0 12.58-10.197 22.803-22.802 22.803-12.598 0-22.803-10.223-22.803-22.803v-284.9c0-12.597 10.205-22.802 22.803-22.802 12.605 0 22.802 10.206 22.802 22.802v284.9zm91.213 0c0 12.58-10.205 22.803-22.803 22.803s-22.803-10.223-22.803-22.803v-284.9c0-12.597 10.205-22.802 22.803-22.802s22.803 10.206 22.803 22.802v284.9zm91.212 0c0 12.58-10.205 22.803-22.803 22.803-12.613 0-22.803-10.223-22.803-22.803v-284.9c0-12.597 10.189-22.802 22.803-22.802 12.598 0 22.803 10.206 22.803 22.802v284.9z' /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
// @flow
|
||||
|
||||
import React, { PureComponent } from 'react';
|
||||
import TodoEditInput from './todo-edit-input';
|
||||
import TodoListItem from './todo-list-item';
|
||||
import type { TodoType } from '../../types/todo';
|
||||
|
||||
type Props = {
|
||||
todos: Array<TodoType>,
|
||||
deleteTodo: Function,
|
||||
toggleEdit: Function,
|
||||
updateTodo: Function,
|
||||
cancelUpdateTodo: Function,
|
||||
};
|
||||
|
||||
export default class TodoList extends PureComponent<Props> {
|
||||
renderTodoView = (todo: TodoType) => {
|
||||
const { deleteTodo, toggleEdit } = this.props;
|
||||
|
||||
return (
|
||||
<TodoListItem
|
||||
todo={todo}
|
||||
deleteTodo={deleteTodo}
|
||||
toggleEdit={toggleEdit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderEditView = (todo: TodoType) => {
|
||||
const { updateTodo, cancelUpdateTodo } = this.props;
|
||||
|
||||
return (
|
||||
<TodoEditInput
|
||||
todo={todo}
|
||||
updateTodo={updateTodo}
|
||||
cancelUpdateTodo={cancelUpdateTodo}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
renderList = () => {
|
||||
const { todos } = this.props;
|
||||
const sortTodosByTime = todos.sort((a, b) => b.createdAt - a.createdAt);
|
||||
|
||||
return (
|
||||
<ul className='todo__list'>
|
||||
{sortTodosByTime.map(todo => (
|
||||
<li
|
||||
key={todo.id}
|
||||
className='todo__list-item todo-item'
|
||||
>
|
||||
{todo.editing ? (
|
||||
this.renderEditView(todo)
|
||||
) : (
|
||||
this.renderTodoView(todo)
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
renderEmptyState = () => (
|
||||
<p className='todo__list todo__list--empty'>No todos right now</p>
|
||||
);
|
||||
|
||||
render() {
|
||||
const { todos } = this.props;
|
||||
const hasTodos = todos.length;
|
||||
|
||||
return hasTodos ? this.renderList() : this.renderEmptyState();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
// @flow
|
||||
import React, { Fragment } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { TransactionItemComponent, type Transaction } from './transaction-item';
|
||||
import { TextComponent } from './text';
|
||||
import { Divider } from './divider';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
margin-top: 20px;
|
||||
`;
|
||||
|
||||
const TransactionsWrapper = styled.div`
|
||||
border-radius: ${props => props.theme.boxBorderRadius};
|
||||
overflow: hidden;
|
||||
background-color: ${props => props.theme.colors.cardBackgroundColor};
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 20px;
|
||||
`;
|
||||
|
||||
const Day = styled(TextComponent)`
|
||||
text-transform: uppercase;
|
||||
color: ${props => props.theme.colors.transactionsDate};
|
||||
font-size: ${props => `${props.theme.fontSize.regular * 0.9}em`};
|
||||
font-weight: ${props => props.theme.fontWeight.bold};
|
||||
margin-bottom: 5px;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
transactionsDate: string,
|
||||
transactions: Transaction[],
|
||||
zecPrice: number,
|
||||
};
|
||||
|
||||
export const TransactionDailyComponent = ({
|
||||
transactionsDate,
|
||||
transactions,
|
||||
zecPrice,
|
||||
}: Props) => (
|
||||
<Wrapper>
|
||||
<Day value={transactionsDate} />
|
||||
<TransactionsWrapper>
|
||||
{transactions.map(
|
||||
({
|
||||
date, type, address, amount, transactionId,
|
||||
}, idx) => (
|
||||
<Fragment key={`${address}-${type}-${amount}-${date}`}>
|
||||
<TransactionItemComponent
|
||||
transactionId={transactionId}
|
||||
type={type}
|
||||
date={date}
|
||||
address={address || ''}
|
||||
amount={amount}
|
||||
zecPrice={zecPrice}
|
||||
/>
|
||||
{idx < transactions.length - 1 && <Divider />}
|
||||
</Fragment>
|
||||
),
|
||||
)}
|
||||
</TransactionsWrapper>
|
||||
</Wrapper>
|
||||
);
|
|
@ -0,0 +1,40 @@
|
|||
---
|
||||
name: Transaction Daily
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from 'docz'
|
||||
|
||||
import { TransactionDailyComponent } from './transaction-daily.js'
|
||||
import { DoczWrapper } from '../theme.js'
|
||||
|
||||
# Transaction Item
|
||||
|
||||
## Properties
|
||||
|
||||
<PropsTable of={TransactionDailyComponent} />
|
||||
|
||||
## Basic Usage
|
||||
|
||||
<Playground>
|
||||
<DoczWrapper>
|
||||
{() => (
|
||||
<TransactionDailyComponent
|
||||
transactionsDate={new Date().toISOString()}
|
||||
transactions={[
|
||||
{
|
||||
type: 'received',
|
||||
address: '123456789123456789123456789123456789',
|
||||
amount: 1.7891,
|
||||
date: new Date().toISOString(),
|
||||
},
|
||||
{
|
||||
type: 'sent',
|
||||
address: '123456789123456789123456789123456789',
|
||||
amount: 0.8458,
|
||||
date: new Date().toISOString(),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</DoczWrapper>
|
||||
</Playground>
|
|
@ -0,0 +1,205 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import dateFns from 'date-fns';
|
||||
|
||||
import SentIcon from '../assets/images/transaction_sent_icon.svg';
|
||||
import ReceivedIcon from '../assets/images/transaction_received_icon.svg';
|
||||
import CloseIcon from '../assets/images/close_icon.svg';
|
||||
|
||||
import { TextComponent } from './text';
|
||||
import { RowComponent } from './row';
|
||||
import { ColumnComponent } from './column';
|
||||
|
||||
import theme from '../theme';
|
||||
|
||||
import formatNumber from '../utils/formatNumber';
|
||||
import truncateAddress from '../utils/truncateAddress';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
width: 460px;
|
||||
background-color: ${props => props.theme.colors.background};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0px 0px 30px 0px black;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const TitleWrapper = styled.div`
|
||||
margin-top: 20px;
|
||||
margin-bottom: 30px;
|
||||
`;
|
||||
|
||||
const Icon = styled.img`
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin: 20px 0;
|
||||
`;
|
||||
|
||||
const CloseIconWrapper = styled.div`
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end;
|
||||
position: absolute;
|
||||
`;
|
||||
|
||||
const CloseIconImg = styled.img`
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-top: 12px;
|
||||
margin-right: 12px;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const InfoRow = styled(RowComponent)`
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
padding: 0 30px;
|
||||
|
||||
&first-child {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #1d1d1d;
|
||||
}
|
||||
`;
|
||||
|
||||
const DetailsWrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 30px;
|
||||
`;
|
||||
|
||||
const Divider = styled.div`
|
||||
width: 100%;
|
||||
background-color: #3a3a3a;
|
||||
height: 1px;
|
||||
opacity: 0.5;
|
||||
`;
|
||||
|
||||
const Label = styled(TextComponent)`
|
||||
font-weight: ${props => props.theme.fontWeight.bold};
|
||||
color: ${props => props.theme.colors.transactionsDetailsLabel};
|
||||
margin-bottom: 5px;
|
||||
letter-spacing: 0.25px;
|
||||
`;
|
||||
|
||||
const Ellipsis = styled(TextComponent)`
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
amount: number,
|
||||
type: 'send' | 'receive',
|
||||
zecPrice: number,
|
||||
date: string,
|
||||
transactionId: string,
|
||||
from: string,
|
||||
to: string,
|
||||
handleClose: () => void,
|
||||
};
|
||||
|
||||
export const TransactionDetailsComponent = ({
|
||||
amount,
|
||||
type,
|
||||
zecPrice,
|
||||
date,
|
||||
transactionId,
|
||||
from,
|
||||
to,
|
||||
handleClose,
|
||||
}: Props) => {
|
||||
const isReceived = type === 'receive';
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<CloseIconWrapper>
|
||||
<CloseIconImg
|
||||
src={CloseIcon}
|
||||
onClick={handleClose}
|
||||
/>
|
||||
</CloseIconWrapper>
|
||||
<TitleWrapper>
|
||||
<TextComponent
|
||||
value='Transaction Details'
|
||||
align='center'
|
||||
/>
|
||||
</TitleWrapper>
|
||||
<DetailsWrapper>
|
||||
<Icon
|
||||
src={isReceived ? ReceivedIcon : SentIcon}
|
||||
alt='Transaction Type Icon'
|
||||
/>
|
||||
<TextComponent
|
||||
isBold
|
||||
size={2.625}
|
||||
value={formatNumber({
|
||||
append: `${isReceived ? '+' : '-'}ZEC `,
|
||||
value: amount,
|
||||
})}
|
||||
color={
|
||||
isReceived
|
||||
? theme.colors.transactionReceived
|
||||
: theme.colors.transactionSent
|
||||
}
|
||||
/>
|
||||
<TextComponent
|
||||
value={formatNumber({
|
||||
append: `${isReceived ? '+' : '-'}USD `,
|
||||
value: amount * zecPrice,
|
||||
})}
|
||||
size={1.5}
|
||||
color={theme.colors.transactionsDetailsLabel}
|
||||
/>
|
||||
</DetailsWrapper>
|
||||
<InfoRow>
|
||||
<ColumnComponent>
|
||||
<Label value='DATE' />
|
||||
<TextComponent value={dateFns.format(date, 'MMMM D, YYYY HH:MMA')} />
|
||||
</ColumnComponent>
|
||||
<ColumnComponent>
|
||||
<TextComponent
|
||||
value='FEES'
|
||||
isBold
|
||||
color={theme.colors.transactionsDetailsLabel}
|
||||
/>
|
||||
<TextComponent
|
||||
value={formatNumber({ value: amount * 0.1, append: 'ZEC ' })}
|
||||
/>
|
||||
</ColumnComponent>
|
||||
</InfoRow>
|
||||
<Divider />
|
||||
<InfoRow>
|
||||
<ColumnComponent width='100%'>
|
||||
<Label value='TRANSACTION ID' />
|
||||
<Ellipsis value={transactionId} />
|
||||
</ColumnComponent>
|
||||
</InfoRow>
|
||||
<Divider />
|
||||
<InfoRow>
|
||||
<ColumnComponent width='100%'>
|
||||
<Label value='FROM' />
|
||||
<Ellipsis value={truncateAddress(from)} />
|
||||
</ColumnComponent>
|
||||
</InfoRow>
|
||||
<Divider />
|
||||
<InfoRow>
|
||||
<ColumnComponent width='100%'>
|
||||
<Label value='TO' />
|
||||
<Ellipsis value={truncateAddress(to)} />
|
||||
</ColumnComponent>
|
||||
</InfoRow>
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,32 @@
|
|||
---
|
||||
name: Transaction Details
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from 'docz'
|
||||
|
||||
import { TransactionDetailsComponent } from './transaction-details.js'
|
||||
import { DoczWrapper } from '../theme.js'
|
||||
|
||||
# Transaction Details
|
||||
|
||||
## Properties
|
||||
|
||||
<PropsTable of={TransactionDetailsComponent} />
|
||||
|
||||
## Basic Usage
|
||||
|
||||
<Playground>
|
||||
<DoczWrapper>
|
||||
{() => (
|
||||
<TransactionDetailsComponent
|
||||
type="receive"
|
||||
amount={2851}
|
||||
zecPrice={56}
|
||||
transactionId="asdh8233uhd89as283dhuashd23-493iskdfdnhdufhs"
|
||||
from="WKSJniasnxusiuhd898ns13umdoioj93287yhnjknxU"
|
||||
to="WKSJniasnxusiuhd898ns13umdoioj93287yhnjknxU"
|
||||
date={new Date().toISOString()}
|
||||
/>
|
||||
)}
|
||||
</DoczWrapper>
|
||||
</Playground>
|
|
@ -0,0 +1,147 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import dateFns from 'date-fns';
|
||||
|
||||
import SentIcon from '../assets/images/transaction_sent_icon.svg';
|
||||
import ReceivedIcon from '../assets/images/transaction_received_icon.svg';
|
||||
|
||||
import { RowComponent } from './row';
|
||||
import { ColumnComponent } from './column';
|
||||
import { TextComponent } from './text';
|
||||
import { ModalComponent } from './modal';
|
||||
import { TransactionDetailsComponent } from './transaction-details';
|
||||
|
||||
import theme from '../theme';
|
||||
|
||||
import formatNumber from '../utils/formatNumber';
|
||||
import truncateAddress from '../utils/truncateAddress';
|
||||
|
||||
const Wrapper = styled(RowComponent)`
|
||||
background-color: ${props => props.theme.colors.cardBackgroundColor};
|
||||
padding: 15px 17px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background-color: #0a0a0a;
|
||||
}
|
||||
`;
|
||||
|
||||
const Icon = styled.img`
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
`;
|
||||
|
||||
const TransactionTypeLabel = styled(TextComponent)`
|
||||
color: ${props => (props.isReceived
|
||||
? props.theme.colors.transactionReceived
|
||||
: props.theme.colors.transactionSent)};
|
||||
text-transform: capitalize;
|
||||
`;
|
||||
|
||||
const TransactionAddress = styled(TextComponent)`
|
||||
color: #a7a7a7;
|
||||
|
||||
${Wrapper}:hover & {
|
||||
color: #fff;
|
||||
}
|
||||
`;
|
||||
|
||||
const TransactionTime = styled(TextComponent)`
|
||||
color: ${props => props.theme.colors.inactiveItem};
|
||||
`;
|
||||
|
||||
const TransactionColumn = styled(ColumnComponent)`
|
||||
margin-left: 10px;
|
||||
margin-right: 80px;
|
||||
min-width: 60px;
|
||||
`;
|
||||
|
||||
export type Transaction = {
|
||||
type: 'send' | 'receive',
|
||||
date: string,
|
||||
address: string,
|
||||
amount: number,
|
||||
zecPrice: number,
|
||||
transactionId: string,
|
||||
};
|
||||
|
||||
export const TransactionItemComponent = ({
|
||||
type,
|
||||
date,
|
||||
address,
|
||||
amount,
|
||||
zecPrice,
|
||||
transactionId,
|
||||
}: Transaction) => {
|
||||
const isReceived = type === 'receive';
|
||||
const transactionTime = dateFns.format(new Date(date), 'HH:mm A');
|
||||
const transactionValueInZec = formatNumber({
|
||||
value: amount,
|
||||
append: `${isReceived ? '+' : '-'}ZEC `,
|
||||
});
|
||||
const transactionValueInUsd = formatNumber({
|
||||
value: amount * zecPrice,
|
||||
append: `${isReceived ? '+' : '-'}USD $`,
|
||||
});
|
||||
const transactionAddress = truncateAddress(address);
|
||||
|
||||
return (
|
||||
<ModalComponent
|
||||
renderTrigger={toggleVisibility => (
|
||||
<Wrapper
|
||||
id={`transaction-item-${transactionId}`}
|
||||
alignItems='center'
|
||||
justifyContent='space-between'
|
||||
onClick={toggleVisibility}
|
||||
>
|
||||
<RowComponent alignItems='center'>
|
||||
<RowComponent alignItems='center'>
|
||||
<Icon
|
||||
src={isReceived ? ReceivedIcon : SentIcon}
|
||||
alt='Transaction Type Icon'
|
||||
/>
|
||||
<TransactionColumn>
|
||||
<TransactionTypeLabel
|
||||
isReceived={isReceived}
|
||||
value={type}
|
||||
isBold
|
||||
/>
|
||||
<TransactionTime value={transactionTime} />
|
||||
</TransactionColumn>
|
||||
</RowComponent>
|
||||
<TransactionAddress value={transactionAddress} />
|
||||
</RowComponent>
|
||||
<ColumnComponent alignItems='flex-end'>
|
||||
<TextComponent
|
||||
isBold
|
||||
value={transactionValueInZec}
|
||||
color={
|
||||
isReceived
|
||||
? theme.colors.transactionReceived
|
||||
: theme.colors.transactionSent
|
||||
}
|
||||
/>
|
||||
<TextComponent
|
||||
value={transactionValueInUsd}
|
||||
color={theme.colors.inactiveItem}
|
||||
/>
|
||||
</ColumnComponent>
|
||||
</Wrapper>
|
||||
)}
|
||||
>
|
||||
{toggleVisibility => (
|
||||
<TransactionDetailsComponent
|
||||
amount={amount}
|
||||
date={date}
|
||||
from={address}
|
||||
to=''
|
||||
transactionId={transactionId}
|
||||
handleClose={toggleVisibility}
|
||||
type={type}
|
||||
zecPrice={zecPrice}
|
||||
/>
|
||||
)}
|
||||
</ModalComponent>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,44 @@
|
|||
---
|
||||
name: Transaction Item
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from 'docz'
|
||||
|
||||
import { TransactionItemComponent } from './transaction-item.js'
|
||||
import { DoczWrapper } from '../theme.js'
|
||||
|
||||
# Transaction Item
|
||||
|
||||
## Properties
|
||||
|
||||
<PropsTable of={TransactionItemComponent} />
|
||||
|
||||
## Sent
|
||||
|
||||
<Playground>
|
||||
<DoczWrapper>
|
||||
{() => (
|
||||
<TransactionItemComponent
|
||||
type='sent'
|
||||
address='123456789123456789123456789123456789'
|
||||
amount={0.8652}
|
||||
date={new Date().toISOString()}
|
||||
/>
|
||||
)}
|
||||
</DoczWrapper>
|
||||
</Playground>
|
||||
|
||||
## Received
|
||||
|
||||
<Playground>
|
||||
<DoczWrapper>
|
||||
{() => (
|
||||
<TransactionItemComponent
|
||||
type='received'
|
||||
address='123456789123456789123456789123456789'
|
||||
amount={1.7891}
|
||||
date={new Date().toISOString()}
|
||||
/>
|
||||
)}
|
||||
</DoczWrapper>
|
||||
</Playground>
|
|
@ -0,0 +1,154 @@
|
|||
// @flow
|
||||
import React, { PureComponent } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Transition, animated } from 'react-spring';
|
||||
|
||||
import { ColumnComponent } from './column';
|
||||
import { Button } from './button';
|
||||
import { QRCode } from './qrcode';
|
||||
|
||||
import truncateAddress from '../utils/truncateAddress';
|
||||
|
||||
import eyeIcon from '../assets/images/eye.png';
|
||||
|
||||
const AddressWrapper = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
background-color: #000;
|
||||
border-radius: 6px;
|
||||
padding: 7px 13px;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const Input = styled.input`
|
||||
border-radius: ${props => props.theme.boxBorderRadius};
|
||||
border: none;
|
||||
background-color: ${props => props.theme.colors.inputBackground};
|
||||
color: ${props => props.theme.colors.text};
|
||||
padding: 15px;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
font-family: ${props => props.theme.fontFamily};
|
||||
|
||||
::placeholder {
|
||||
opacity: 0.5;
|
||||
}
|
||||
`;
|
||||
|
||||
const Btn = styled(Button)`
|
||||
border-width: 1px;
|
||||
font-weight: ${props => props.theme.fontWeight.regular};
|
||||
border-color: ${props => (props.isVisible
|
||||
? props.theme.colors.primary : props.theme.colors.buttonBorderColor
|
||||
)};
|
||||
padding: 8px 10px;
|
||||
min-width: 260px;
|
||||
|
||||
img {
|
||||
height: 12px;
|
||||
width: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
const QRCodeWrapper = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
background-color: #000;
|
||||
border-radius: 6px;
|
||||
padding: 20px;
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const RevealsMain = styled.div`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: flex-start;
|
||||
height: ${props => (props.isVisible ? '178px' : 0)}
|
||||
transition: all 0.25s ease-in-out;
|
||||
|
||||
& > div {
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
address: string,
|
||||
isVisible?: boolean,
|
||||
};
|
||||
|
||||
type State = {
|
||||
isVisible: boolean,
|
||||
};
|
||||
|
||||
export class WalletAddress extends PureComponent<Props, State> {
|
||||
static defaultProps = {
|
||||
isVisible: false,
|
||||
}
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
|
||||
this.state = { isVisible: Boolean(props.isVisible) };
|
||||
}
|
||||
|
||||
show = () => this.setState(() => ({ isVisible: true }));
|
||||
|
||||
hide = () => this.setState(() => ({ isVisible: false }));
|
||||
|
||||
render() {
|
||||
const { address } = this.props;
|
||||
const { isVisible } = this.state;
|
||||
const toggleVisibility = isVisible ? this.hide : this.show;
|
||||
const buttonLabel = `${isVisible ? 'Hide' : 'Show'} details and QR Code`;
|
||||
|
||||
return (
|
||||
<ColumnComponent width='100%'>
|
||||
<AddressWrapper>
|
||||
<Input
|
||||
value={isVisible ? address : truncateAddress(address)}
|
||||
onChange={() => {}}
|
||||
onFocus={event => event.currentTarget.select()}
|
||||
/>
|
||||
<Btn
|
||||
icon={eyeIcon}
|
||||
label={buttonLabel}
|
||||
onClick={toggleVisibility}
|
||||
variant='secondary'
|
||||
isVisible={isVisible}
|
||||
/>
|
||||
</AddressWrapper>
|
||||
<RevealsMain isVisible={isVisible}>
|
||||
<Transition
|
||||
native
|
||||
items={isVisible}
|
||||
enter={[{
|
||||
height: 'auto',
|
||||
opacity: 1,
|
||||
}]}
|
||||
leave={{ height: 0, opacity: 0 }}
|
||||
from={{
|
||||
position: 'absolute',
|
||||
overflow: 'hidden',
|
||||
opacity: 0,
|
||||
height: 0,
|
||||
}}
|
||||
>
|
||||
{show => show && (props => (
|
||||
<animated.div style={props}>
|
||||
<QRCodeWrapper>
|
||||
<QRCode value={address} />
|
||||
</QRCodeWrapper>
|
||||
</animated.div>
|
||||
))}
|
||||
</Transition>
|
||||
</RevealsMain>
|
||||
</ColumnComponent>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
---
|
||||
name: Wallet Address
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from 'docz'
|
||||
|
||||
import { WalletAddress } from './wallet-address.js'
|
||||
import { DoczWrapper } from '../theme.js'
|
||||
|
||||
# Wallet Address
|
||||
|
||||
## Properties
|
||||
|
||||
<PropsTable of={WalletAddress} />
|
||||
|
||||
## Basic usage
|
||||
|
||||
<Playground>
|
||||
<DoczWrapper>
|
||||
{() => (
|
||||
<div style={{ width: '700px' }}>
|
||||
<WalletAddress
|
||||
address='t14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1'
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</DoczWrapper>
|
||||
</Playground>
|
|
@ -0,0 +1,110 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { TextComponent } from './text';
|
||||
import { RowComponent } from './row';
|
||||
|
||||
import formatNumber from '../utils/formatNumber';
|
||||
|
||||
import theme from '../theme';
|
||||
|
||||
const Wrapper = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: ${props => props.theme.colors.cardBackgroundColor};
|
||||
border-radius: ${props => props.theme.boxBorderRadius};
|
||||
padding: 37px 45px;
|
||||
min-height: 250px;
|
||||
position: relative;
|
||||
margin-top: ${props => props.theme.layoutContentPaddingTop};
|
||||
`;
|
||||
|
||||
const AllAddresses = styled(TextComponent)`
|
||||
margin-bottom: 2.5px;
|
||||
font-size: ${props => `${props.theme.fontSize.small}em`};
|
||||
`;
|
||||
|
||||
const ValueBox = styled.div`
|
||||
margin-bottom: 15px;
|
||||
margin-right: 25px;
|
||||
`;
|
||||
|
||||
const Label = styled(TextComponent)`
|
||||
margin-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
margin-left: -7.5px;
|
||||
`;
|
||||
|
||||
const USDValue = styled(TextComponent)`
|
||||
opacity: 0.5;
|
||||
font-weight: ${props => props.theme.fontWeight.light};
|
||||
`;
|
||||
|
||||
const ShieldedValue = styled(Label)`
|
||||
color: ${props => props.theme.colors.activeItem};
|
||||
`;
|
||||
|
||||
type Props = {
|
||||
total: number,
|
||||
shielded: number,
|
||||
transparent: number,
|
||||
zecPrice: number,
|
||||
};
|
||||
|
||||
export const WalletSummaryComponent = ({
|
||||
total,
|
||||
shielded,
|
||||
transparent,
|
||||
zecPrice,
|
||||
}: Props) => (
|
||||
<Wrapper>
|
||||
<AllAddresses
|
||||
value='ALL ADDRESSES'
|
||||
isBold
|
||||
/>
|
||||
<ValueBox>
|
||||
<TextComponent
|
||||
size={theme.fontSize.medium * 2.5}
|
||||
value={`ZEC ${formatNumber({ value: total })}`}
|
||||
isBold
|
||||
/>
|
||||
<USDValue
|
||||
value={`USD $${formatNumber({ value: total * zecPrice })}`}
|
||||
size={theme.fontSize.medium * 2}
|
||||
/>
|
||||
</ValueBox>
|
||||
<RowComponent>
|
||||
<ValueBox>
|
||||
<ShieldedValue
|
||||
value='● SHIELDED'
|
||||
isBold
|
||||
size={theme.fontSize.small}
|
||||
/>
|
||||
<TextComponent
|
||||
value={`ZEC ${formatNumber({ value: shielded })}`}
|
||||
isBold
|
||||
size={theme.fontSize.medium}
|
||||
/>
|
||||
<USDValue
|
||||
value={`USD $${formatNumber({ value: shielded * zecPrice })}`}
|
||||
/>
|
||||
</ValueBox>
|
||||
<ValueBox>
|
||||
<Label
|
||||
value='● TRANSPARENT'
|
||||
isBold
|
||||
size={theme.fontSize.small}
|
||||
/>
|
||||
<TextComponent
|
||||
value={`ZEC ${formatNumber({ value: transparent })}`}
|
||||
isBold
|
||||
size={theme.fontSize.medium}
|
||||
/>
|
||||
<USDValue
|
||||
value={`USD $${formatNumber({ value: transparent * zecPrice })}`}
|
||||
/>
|
||||
</ValueBox>
|
||||
</RowComponent>
|
||||
</Wrapper>
|
||||
);
|
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
name: Wallet Summary
|
||||
---
|
||||
|
||||
import { Playground, PropsTable } from 'docz'
|
||||
|
||||
import { WalletSummaryComponent } from './wallet-summary.js'
|
||||
import { DoczWrapper } from '../theme.js'
|
||||
|
||||
# Wallet Summary
|
||||
|
||||
## Properties
|
||||
|
||||
<PropsTable of={WalletSummaryComponent} />
|
||||
|
||||
## Basic usage
|
||||
|
||||
<Playground>
|
||||
<DoczWrapper>
|
||||
{() => (
|
||||
<div style={{ width: '700px' }}>
|
||||
<WalletSummaryComponent
|
||||
total={5000}
|
||||
shielded={2500}
|
||||
transparent={2500}
|
||||
dollarValue={56}
|
||||
addresses={[
|
||||
't14oHp2v54vfmdgQ3v3SNuQga8JKHTNi2a1', 't14oHp2v54vfAShh121t5uQga8JKHTNi2a1',
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</DoczWrapper>
|
||||
</Playground>
|
|
@ -0,0 +1,72 @@
|
|||
// @flow
|
||||
import React, { type ComponentType, Component } from 'react';
|
||||
|
||||
import { LoadingScreen } from './loading-screen';
|
||||
|
||||
import rpc from '../../services/api';
|
||||
|
||||
type Props = {};
|
||||
|
||||
type State = {
|
||||
isRunning: boolean,
|
||||
progress: number,
|
||||
};
|
||||
|
||||
/* eslint-disable max-len */
|
||||
export const withDaemonStatusCheck = <PassedProps: {}>(
|
||||
WrappedComponent: ComponentType<PassedProps>,
|
||||
): ComponentType<$Diff<PassedProps, Props>> => class extends Component<PassedProps, State> {
|
||||
timer: ?IntervalID = null;
|
||||
|
||||
state = {
|
||||
isRunning: false,
|
||||
progress: 0,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
this.runTest();
|
||||
|
||||
this.timer = setInterval(this.runTest, 2000);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
runTest = () => {
|
||||
rpc
|
||||
.getinfo()
|
||||
.then((response) => {
|
||||
if (response) {
|
||||
setTimeout(() => {
|
||||
this.setState(() => ({ isRunning: true }));
|
||||
}, 500);
|
||||
this.setState(() => ({ progress: 100 }));
|
||||
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.setState((state) => {
|
||||
const newProgress = state.progress > 70 ? state.progress + 2.5 : state.progress + 5;
|
||||
return { progress: newProgress > 95 ? 95 : newProgress };
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isRunning, progress } = this.state;
|
||||
|
||||
if (isRunning) {
|
||||
return <WrappedComponent {...this.props} {...this.state} />;
|
||||
}
|
||||
|
||||
return <LoadingScreen progress={progress} />;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
// @flow
|
||||
import React from 'react';
|
||||
|
||||
export const ZcashLogo = () => (
|
||||
<svg xmlns='http://www.w3.org/2000/svg' viewBox='-75 -10 175 175'>
|
||||
<defs>
|
||||
<style>{'.a{ fill:#040508; }'}</style>
|
||||
</defs>
|
||||
<path
|
||||
className='a'
|
||||
d='M541.425,662.318v4.555h-7.678v5.678H545.5l-11.751,16v4.261h7.678v4.665h4.563v-4.665h7.577v-5.666H541.788l11.777-16v-4.273h-7.577v-4.555Z'
|
||||
transform='translate(-533.747 -662.318)'
|
||||
/>
|
||||
</svg>
|
||||
);
|
|
@ -1,7 +0,0 @@
|
|||
// @flow
|
||||
|
||||
export const ADD_TODO = 'ADD_TODO';
|
||||
export const DELETE_TODO = 'DELETE_TODO';
|
||||
export const UPDATE_TODO = 'UPDATE_TODO';
|
||||
export const TOGGLE_EDIT_TODO = 'TOGGLE_EDIT_TODO';
|
||||
export const CANCEL_UPDATE_TODO = 'CANCEL_UPDATE_TODO';
|
|
@ -0,0 +1,8 @@
|
|||
// @flow
|
||||
|
||||
export default {
|
||||
LOW: 0.001,
|
||||
MEDIUM: 0.005,
|
||||
HIGH: 0.009,
|
||||
CUSTOM: 'custom',
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
// @flow
|
||||
|
||||
export const DASHBOARD_ROUTE = '/';
|
||||
export const CONSOLE_ROUTE = '/console';
|
||||
export const SEND_ROUTE = '/send';
|
||||
export const RECEIVE_ROUTE = '/receive';
|
||||
export const TRANSACTIONS_ROUTE = '/transactions';
|
||||
export const SETTINGS_ROUTE = '/settings';
|
|
@ -0,0 +1,57 @@
|
|||
// @flow
|
||||
import DashboardIcon from '../assets/images/dashboard_icon.svg';
|
||||
import DashboardIconActive from '../assets/images/dashboard_icon_active.svg';
|
||||
import ConsoleIcon from '../assets/images/console_icon.svg';
|
||||
import ConsoleIconActive from '../assets/images/console_icon_active.svg';
|
||||
import SendIcon from '../assets/images/send_icon.svg';
|
||||
import SendIconActive from '../assets/images/send_icon_active.svg';
|
||||
import ReceiveIcon from '../assets/images/receive_icon.svg';
|
||||
import ReceiveIconActive from '../assets/images/receive_icon_active.svg';
|
||||
import TransactionsIcon from '../assets/images/transactions_icon.svg';
|
||||
import TransactionsIconActive from '../assets/images/transactions_icon_active.svg';
|
||||
import SettingsIcon from '../assets/images/settings_icon.svg';
|
||||
import SettingsIconActive from '../assets/images/settings_icon_active.svg';
|
||||
|
||||
import {
|
||||
DASHBOARD_ROUTE,
|
||||
SEND_ROUTE,
|
||||
RECEIVE_ROUTE,
|
||||
SETTINGS_ROUTE,
|
||||
CONSOLE_ROUTE,
|
||||
TRANSACTIONS_ROUTE,
|
||||
} from './routes';
|
||||
|
||||
export const MENU_OPTIONS = [
|
||||
{
|
||||
label: 'Dashboard',
|
||||
route: DASHBOARD_ROUTE,
|
||||
// eslint-disable-next-line
|
||||
icon: (isActive: boolean) => isActive ? DashboardIconActive : DashboardIcon,
|
||||
},
|
||||
{
|
||||
label: 'Send',
|
||||
route: SEND_ROUTE,
|
||||
icon: (isActive: boolean) => (isActive ? SendIconActive : SendIcon),
|
||||
},
|
||||
{
|
||||
label: 'Receive',
|
||||
route: RECEIVE_ROUTE,
|
||||
icon: (isActive: boolean) => (isActive ? ReceiveIconActive : ReceiveIcon),
|
||||
},
|
||||
{
|
||||
label: 'Transactions',
|
||||
route: TRANSACTIONS_ROUTE,
|
||||
// eslint-disable-next-line
|
||||
icon: (isActive: boolean) => isActive ? TransactionsIconActive : TransactionsIcon,
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
route: SETTINGS_ROUTE,
|
||||
icon: (isActive: boolean) => (isActive ? SettingsIconActive : SettingsIcon),
|
||||
},
|
||||
{
|
||||
label: 'Console',
|
||||
route: CONSOLE_ROUTE,
|
||||
icon: (isActive: boolean) => (isActive ? ConsoleIconActive : ConsoleIcon),
|
||||
},
|
||||
];
|