2017-08-15 13:56:29 +00:00
# MyEtherWallet V4+ (ALPHA - VISIT [V3](https://github.com/kvhnuke/etherwallet) for the production site)
2017-04-12 05:06:09 +00:00
2017-11-07 23:59:27 +00:00
[![Greenkeeper badge ](https://badges.greenkeeper.io/MyEtherWallet/MyEtherWallet.svg )](https://greenkeeper.io/)
2017-04-12 05:06:09 +00:00
#### Run:
```bash
npm run dev # run app in dev mode
```
#### Build:
```bash
npm run build # build app
```
It generates app in `dist` folder.
#### Test:
```bash
npm run test # run tests with Jest
```
2017-09-20 00:47:46 +00:00
#### Dev (HTTPS):
1. Create your own SSL Certificate (Heroku has a [nice guide here ](https://devcenter.heroku.com/articles/ssl-certificate-self ))
2. Move the `.key` and `.crt` files into `webpack_config/server.*`
3. Run the following command:
```bash
npm run dev:https
```
2017-08-24 16:41:47 +00:00
#### Derivation Check:
##### The derivation checker utility assumes that you have:
1. Docker installed/available
2. [dternyak/eth-priv-to-addr ](https://hub.docker.com/r/dternyak/eth-priv-to-addr/ ) pulled from DockerHub
##### Docker setup instructions:
2017-10-31 06:25:22 +00:00
1. Install docker (on macOS, [Docker for Mac ](https://docs.docker.com/docker-for-mac/ ) is suggested)
2017-08-24 16:41:47 +00:00
2. `docker pull dternyak/eth-priv-to-addr`
2017-09-20 00:47:46 +00:00
2017-08-24 16:41:47 +00:00
##### Run Derivation Checker
```bash
npm run derivation-checker
```
2017-04-12 05:06:09 +00:00
## Folder structure:
```
2017-04-12 05:09:28 +00:00
│
2017-10-14 19:14:24 +00:00
├── common
2017-04-27 03:59:16 +00:00
│ ├── actions - application actions
2017-10-31 06:25:22 +00:00
│ ├── api - Services and XHR utils
2017-04-12 05:06:09 +00:00
│ ├── components - components according to "Redux philosophy"
│ ├── config - frontend config depending on REACT_WEBPACK_ENV
│ ├── containers - containers according to "Redux philosophy"
│ ├── reducers - application reducers
│ ├── routing - application routing
2017-10-14 19:14:24 +00:00
│ ├── index.tsx - entry
2017-04-12 05:06:09 +00:00
│ ├── index.html
2017-04-12 22:38:25 +00:00
├── static
2017-04-12 05:06:09 +00:00
├── webpack_config - Webpack configuration
├── jest_config - Jest configuration
2017-04-27 03:59:16 +00:00
```
2017-07-11 03:03:08 +00:00
2017-07-15 04:16:36 +00:00
## Style Guides and Philosophies
The following are guides for developers to follow for writing compliant code.
2017-07-27 17:05:09 +00:00
### Redux and Actions
2017-10-14 19:14:24 +00:00
Each reducer has one file in `reducers/[namespace].ts` that contains the reducer
and initial state, one file in `actions/[namespace].ts` that contains the action
2017-07-27 17:05:09 +00:00
creators and their return types, and optionally one file in
2017-10-14 19:14:24 +00:00
`sagas/[namespace].ts` that handles action side effects using
2017-07-27 17:05:09 +00:00
[`redux-saga` ](https://github.com/redux-saga/redux-saga ).
The files should be laid out as follows:
#### Reducer
* State should be explicitly defined and exported
2017-10-14 19:14:24 +00:00
* Initial state should match state typing, define every key
2017-07-27 17:05:09 +00:00
2017-10-14 19:14:24 +00:00
```ts
import { NamespaceAction } from "actions/[namespace]";
import { TypeKeys } from 'actions/[namespace]/constants';
2017-07-27 17:05:09 +00:00
2017-10-14 19:14:24 +00:00
export interface State { /* definition for state object */ };
2017-07-27 17:05:09 +00:00
export const INITIAL_STATE: State = { /* Initial state shape */ };
2017-10-14 19:14:24 +00:00
export function [namespace](
2017-07-27 17:05:09 +00:00
state: State = INITIAL_STATE,
action: NamespaceAction
): State {
switch (action.type) {
2017-10-14 19:14:24 +00:00
case TypeKeys.NAMESPACE_NAME_OF_ACTION:
2017-07-27 17:05:09 +00:00
return {
...state,
// Alterations to state
2017-10-14 19:14:24 +00:00
};
2017-07-27 17:05:09 +00:00
default:
return state;
}
}
```
#### Actions
2017-10-14 19:14:24 +00:00
* Define each action creator in `actionCreator.ts`
* Define each action object type in `actionTypes.ts`
* Export a union of all of the action types for use by the reducer
* Define each action type as a string enum in `constants.ts`
* Export `actionCreators` and `actionTypes` from module file `index.ts`
2017-07-27 17:05:09 +00:00
2017-10-14 19:14:24 +00:00
```
├── common
├── actions - application actions
├── [namespace] - action namespace
├── actionCreators.ts - action creators
├── actionTypes.ts - action interfaces / types
├── constants.ts - string enum
├── index.ts - exports all action creators and action object types
```
##### constants.ts
```ts
export enum TypeKeys {
NAMESPACE_NAME_OF_ACTION = 'NAMESPACE_NAME_OF_ACTION'
}
```
##### actionTypes.ts
```ts
2017-07-27 17:05:09 +00:00
/*** Name of action ** */
2017-10-14 19:14:24 +00:00
export interface NameOfActionAction {
type: TypeKeys.NAMESPACE_NAME_OF_ACTION,
2017-07-27 17:05:09 +00:00
/* Rest of the action object shape */
};
/*** Action Union ** */
export type NamespaceAction =
| ActionOneAction
| ActionTwoAction
| ActionThreeAction;
```
2017-10-14 19:14:24 +00:00
##### actionCreators.ts
```ts
import * as interfaces from './actionTypes';
import { TypeKeys } from './constants';
2017-07-27 17:05:09 +00:00
2017-10-14 19:14:24 +00:00
export interface TNameOfAction = typeof nameOfAction;
export function nameOfAction(): interfaces.NameOfActionAction {
return {
type: TypeKeys.NAMESPACE_NAME_OF_ACTION,
payload: {}
};
};
```
##### index.ts
```ts
export * from './actionCreators';
export * from './actionTypes';
```
### Higher Order Components
2017-07-27 17:05:09 +00:00
2017-10-14 19:14:24 +00:00
#### Typing Injected Props
2017-11-15 01:17:43 +00:00
Props made available through higher order components can be tricky to type. Normally, if a component requires a prop, you add it to the component's interface and it just works. However, working with injected props from [higher order components ](https://medium.com/@DanHomola/react-higher-order-components-in-typescript-made-simple-6f9b55691af1 ), you will be forced to supply all required props whenever you compose the component.
2017-07-27 17:05:09 +00:00
2017-10-24 01:46:40 +00:00
```ts
2017-10-14 19:14:24 +00:00
interface MyComponentProps {
name: string;
countryCode?: string;
2017-11-15 01:17:43 +00:00
routerLocation: { pathname: string };
2017-10-14 19:14:24 +00:00
}
...
class OtherComponent extends React.Component< {}, {}> {
render() {
return (
< MyComponent
name="foo"
countryCode="CA"
2017-11-15 01:17:43 +00:00
// Error: 'routerLocation' is missing!
2017-10-14 19:14:24 +00:00
/>
);
}
```
2017-11-15 01:17:43 +00:00
Instead of tacking the injected props on the MyComponentProps interface, put them in another interface called `InjectedProps` :
2017-10-14 19:14:24 +00:00
2017-10-24 01:46:40 +00:00
```ts
2017-10-14 19:14:24 +00:00
interface MyComponentProps {
name: string;
countryCode?: string;
}
2017-07-27 17:05:09 +00:00
2017-11-15 01:17:43 +00:00
interface InjectedProps {
routerLocation: { pathname: string };
2017-10-14 19:14:24 +00:00
}
```
2017-11-15 01:17:43 +00:00
Now add a [getter ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/get ) to cast `this.props` as the original props - `MyComponentProps` and the injected props - `InjectedProps` :
2017-10-14 19:14:24 +00:00
2017-10-24 01:46:40 +00:00
```ts
2017-10-14 19:14:24 +00:00
class MyComponent extends React.Component< MyComponentProps , { } > {
get injected() {
2017-11-15 01:17:43 +00:00
return this.props as MyComponentProps & InjectedProps;
2017-10-14 19:14:24 +00:00
}
render() {
2017-11-15 01:17:43 +00:00
const { name, countryCode, routerLocation } = this.props;
2017-10-14 19:14:24 +00:00
...
}
}
```
2017-07-27 17:05:09 +00:00
2017-10-24 01:46:40 +00:00
## Event Handlers
Event handlers such as `onChange` and `onClick` , should be properly typed. For example, if you have an event listener on an input element inside a form:
```ts
public onValueChange = (e: React.FormEvent< HTMLInputElement > ) => {
if (this.props.onChange) {
this.props.onChange(
e.currentTarget.value,
this.props.unit
);
}
};
```
Where you type the event as a `React.FormEvent` of type `HTML<TYPE>Element` .
## Class names
Dynamic class names should use the `classnames` module to simplify how they are created instead of using string template literals with expressions inside.
2017-07-15 04:16:36 +00:00
### Styling
Legacy styles are housed under `common/assets/styles` and written with LESS.
However, going forward, each styled component should create a a `.scss` file of
the same name in the same folder, and import it like so:
2017-10-14 19:14:24 +00:00
```ts
2017-07-15 04:16:36 +00:00
import React from "react";
import "./MyComponent.scss";
2017-10-14 19:14:24 +00:00
export default class MyComponent extends React.component< {}, {}> {
2017-07-15 04:16:36 +00:00
render() {
return (
< div className = "MyComponent" >
< div className = "MyComponent-child" > Hello!< / div >
< / div >
);
}
}
```
These style modules adhere to [SuitCSS naming convention ](https://github.com/suitcss/suit/blob/master/doc/naming-conventions.md ):
```scss
.MyComponent {
/* Styles */
& -child {
/* Styles */
& .is-hidden {
display: none;
}
}
}
```
All elements inside of a component should extend its parent class namespace, or
create a new namespace (Potentially breaking that out into its own component.)
Variables and mixins can be imported from the files in `common/styles` :
```scss
@import "sass/colors";
code {
color: $code-color;
}
```
#### Converting Styles
When working on a module that has styling in Less, try to do the following:
* Screenshot the component in question
* Create a new SCSS file in the same directory
* Remove styling from LESS file, convert it to the SCSS file (Mostly s/@/$)
* Convert class names to SuitCSS naming convention
* Convert any utility classes from `etherewallet-utilities.less` into mixins
* Convert as many element selectors to class name selectors as possible
* Convert as many `<br/>` tags or ` ` s to margins
* Ensure that there has been little to no deviation from screenshot
2017-09-25 02:55:23 +00:00
## Thanks & Support
< a href = "https://browserstack.com/" >
< img src = "https://i.imgur.com/Rib9y9E.png" align = "left" / >
< / a >
Cross browser testing and debugging provided by the very lovely team at BrowserStack.