Compare commits

...

69 Commits

Author SHA1 Message Date
James Prado 816ce3180f Translation Updates (#1323)
* Update account view routing

* Temporarily add unicode character to translated strings for testing

* Temporarily select add unicode to all untranslated strings

* Format changes

* Add all english translations for /account & /generate

* Add the rest of the english translations

* Add a few more missing translations

* Update en translations

* Get selectedLanguage from localstorage instead of redux sttate

* Update snapshots

* Add missing translation keys & Update translate functs & change variable prefix

* translate all markdown strings & remove old translation strings

* Update snapshot

* Add a few more translation strs

* Move raw strings being translated into json

* All translation keys are now Uppercase

* Fix up the last few translations

* Update snapshot

* Uppercase de translation strings

* Bring back shapeshift logo on swap

* Fix contracts tab translations

* Fix a few more translations

* Fix translations

* remove debugging stuff

* Update snapshots

* Use react.fragment as markdown root renderer

* Seperate markdown translations into their own function

* Clean up translation functions

* Clean up translation functions

* Update snapshot

* Fix some broken translation strings

* Add prettier ignore file
2018-03-21 22:50:25 -05:00
William O'Beirne 7521337bda Custom DPaths, Improvements, and Fix SingularDTV (#1351)
* Add dpath to select option display

* Re-enable custom path

* Make it a submittable form to behave better with HW wallets

* Adjust styles

* Widen regex to allow for SingularDTV dpath
2018-03-21 15:19:15 -05:00
greenkeeper[bot] f42b81ac8a fix(package): update electron-updater to version 2.21.1 (#1338) 2018-03-21 14:45:05 -05:00
greenkeeper[bot] aa08c1902d chore(package): update ts-jest to version 22.4.2 (#1339) 2018-03-21 14:08:54 -05:00
Michael - Blurpesec 8eeefe7e25 Fix offline broadcast link (#1350)
* Fixed offline broadcast link

* fix for link from root
2018-03-21 13:49:33 -05:00
William O'Beirne 5d41a7f774 Remove all LESS code (#1348)
* Checking in removal process.

* Final removal of all SCSS

* Remove unnecessary test

* tscheck
2018-03-20 15:08:57 -05:00
William O'Beirne 0b44919912 Update node-sass (#1346) 2018-03-20 11:00:49 -05:00
Danny Skubak 937cc3fde5 Add Jenkins Integration (#1295)
* add jenkins changes

* run prettier

* update icons

* update icons to use latest favicon
2018-03-19 10:42:42 -05:00
Danny Skubak 9be46bf8aa Update blockie package in webpack config (#1337) 2018-03-17 23:52:47 -05:00
Daniel Ternyak f5716514b1
chore(package): update node-sass to version 4.8.1 (#1315)
Closes #1310
2018-03-17 19:48:37 -05:00
Daniel Ternyak 8c0dd3f455
Use ethereum-blockies-base64 (#1326)
* use makeBlockie from ethereum-blockies-base64

* adjust type ethereum-blockies-base64 type declaration

* add ethereum-blockies-base64

* update ethereum-blockies-base64; remove redundent declaration file
2018-03-17 16:31:28 -05:00
Daniel Ternyak 825e467db7
fix(package): update query-string to version 6.0.0 (#1325)
Closes #1322
2018-03-17 00:52:04 -05:00
greenkeeper[bot] 9e723e609c chore(package): update css-loader to version 0.28.11 (#1331) 2018-03-17 00:14:58 -05:00
greenkeeper[bot] be9729c302 chore(package): update @types/react-router-dom to version 4.2.5 (#1328) 2018-03-17 00:14:14 -05:00
greenkeeper[bot] 9a5e2a8c2a chore(package): update electron-builder to version 20.5.1 (#1324) 2018-03-17 00:13:57 -05:00
greenkeeper[bot] 0976552a0b chore(package): update @types/lodash to version 4.14.105 (#1327) 2018-03-17 00:13:17 -05:00
greenkeeper[bot] bd4a63e591 chore(package): update @types/react-select to version 1.2.5 (#1329) 2018-03-17 00:12:56 -05:00
greenkeeper[bot] aeb1b74d8d chore(package): update webpack-subresource-integrity to version 1.1.0-rc.4 (#1330) 2018-03-17 00:12:38 -05:00
greenkeeper[bot] 433a3276bb chore(package): update electron to version 1.8.4 (#1335) 2018-03-17 00:12:20 -05:00
greenkeeper[bot] 4b6cba3211 chore(package): update enzyme-to-json to version 3.3.3 (#1332) 2018-03-17 00:12:03 -05:00
William O'Beirne db6b737cad Show Recent Txs on Check Tx Page (#1147)
* Save transactions to local storage.

* Checksum more things + reset hash on network change.

* Fix IHexTransaction type, grab from from tx object directly.

* Refactor storage of recent transactions to use redux storage and loading.

* Refactor types to a transactions types file.

* Initial crack at recent transactions tab on account

* Punctuation.

* Transaction Status responsive behavior.

* Refactor transaction helper function out to remove circular dependency.

* Fix typings

* Collapse subtabs to select list when too small.

* s/wallet/address

* Type select onChange

* Get fields from current state if web3 tx
2018-03-14 15:10:14 -05:00
greenkeeper[bot] 740b191542 chore(package): update tslint-config-prettier to version 1.10.0 (#1316) 2018-03-14 14:53:11 -05:00
greenkeeper[bot] 0bc24f5825 chore(package): update nodemon to version 1.17.2 (#1320) 2018-03-14 14:52:57 -05:00
greenkeeper[bot] 92e757ceb2 chore(package): update image-webpack-loader to version 4.2.0 (#1319) 2018-03-14 14:52:02 -05:00
William O'Beirne 4b8adc81ce Allow 0 ETH Transactions (#1307)
* Allow zero number

* Fail when request payment is zero value, or if you try to send token with zero value.

* Parseint instead of addition casting to catch empty string.
2018-03-14 14:51:37 -05:00
anticlimactic 16469e1a62 Show Disabled Send Button on /pushtx (#1302)
* Fixes #1291. Implemented a new boolean that allows you to toggle whether you are using a disabled send tx button that persists or a send tx button that remains invisible until a valid transaction is present.

* Fixed object shorthand precommit error.

* Cleanup boolean logic, remove redundant code, make comparision elements more obvious
2018-03-12 00:06:09 -05:00
Daniel Ternyak 3d8f678887
chore(package): update electron-builder to version 20.4.0 (#1299)
Closes #1271
2018-03-11 18:09:31 -05:00
Daniel Ternyak 8649655019
Add HackerOne to Announcement (#1218) 2018-03-11 17:25:24 -05:00
greenkeeper[bot] 9284fb56eb chore(package): update @types/react-select to version 1.2.4 (#1301) 2018-03-11 17:24:35 -05:00
greenkeeper[bot] 7905b0cf67 chore(package): update less-loader to version 4.1.0 (#1304) 2018-03-11 17:24:20 -05:00
greenkeeper[bot] 3f6681711f chore(package): update style-loader to version 0.20.3 (#1305) 2018-03-11 17:23:31 -05:00
greenkeeper[bot] 7b7728cf9d chore(package): update copy-webpack-plugin to version 4.5.1 (#1306) 2018-03-11 17:23:15 -05:00
greenkeeper[bot] 033407460b fix(package): update wallet-address-validator to version 0.1.3 (#1303) 2018-03-11 17:22:38 -05:00
James Prado 4788381641 Update Account SubTab Routing / Token Scanner CSS Updates (#1276)
* Redirect user to subtab after logging in

* Update token balances styles

* Adjust routing for readOnly wallets
2018-03-11 17:19:35 -05:00
William O'Beirne 56943232a0 Roll Back enzyme-to-json Revoked Version (#1308) 2018-03-09 18:34:08 -06:00
greenkeeper[bot] aa287f478a chore(package): update electron to version 1.8.3 (#1266) 2018-03-08 13:29:09 -06:00
James Prado 9cac0298a2 Improve accessibility (a11y) (#1267)
* Manage modal focus

* Add isOpen prop to CustomNodeModal

* Remove outline overrides

* Update outline style for inputs

* Fix modal focus management & Cleanup CustomNodeModal

* Add aria-label on modal close button

* Fix modal scroll to top

* Add aria-live property for notifications

* Add aria-busy to Spinner component

* Fix border styles for generatewallet password inputs

* Update token balances inputs

* Remove multiple h1's & Update styles

* Add alt text to all img elements

* Update swap link from bity to shapeshift

* Update aria-labels and alt text

* Only show keystore password input when required

* Revert "Only show keystore password input when required"

This reverts commit 7ec5de52da0982cd3131f365b142f6915638d831.

* address changes requested
2018-03-08 13:28:43 -06:00
greenkeeper[bot] 6e8d807b22 fix(package): update react-markdown to version 3.3.0 (#1272) 2018-03-08 12:06:06 -06:00
greenkeeper[bot] 80383a831e fix(package): update ethereumjs-tx to version 1.3.4 (#1283) 2018-03-08 12:05:29 -06:00
greenkeeper[bot] 5e97a9fe66 chore(package): update url-search-params-polyfill to version 3.0.0 (#1296) 2018-03-08 12:04:58 -06:00
greenkeeper[bot] 3af6607901 chore(package): update enzyme-to-json to version 3.3.2 (#1297) 2018-03-08 12:04:20 -06:00
aitrean cf9887f21f Outstanding tasks to Productionize Tx (#1194)
* Verify and complete all branching saga logic tests for transaction stack.

* Write reducer tests for refactored transaction stack.

* Add selector tests. Some files still need to be debugged.

* Add snapshot test for fields, additional seelector testing.

* Remove fields snapshots.

* Remove ABIs from the TestState json

* Use redux state instead of raw json in selector testing.

* Fix merge issues.

* Remove log

* Fix state values.

* Change test value to wei.

* Last touchup.

* Fix buffer shape, change Wei typo, use reasonable wei values.

* Last touch up.
2018-03-08 12:03:45 -06:00
HenryNguyen5 94b3f3403b Fix recommended gas price breaking on subtab switch (#1268) 2018-03-07 17:55:46 -06:00
HenryNguyen5 16e6677c0f Derive available tx metadata (#1257)
* Derive "from" and "unit" parameters from current transaction when possible

* Fix tsc errors

* Address changes
2018-03-07 17:42:16 -06:00
William O'Beirne 3bce82ba86 Fix favicon transparency. (#1289) 2018-03-07 17:37:59 -06:00
HenryNguyen5 c340246ca0 Enable no-implicit-any (#1263)
* Progress commit

* Update more types

* Fix more types

* Fix abi function types

* Fix lib types

* Fix rest of types

* Address wbobeirne changes

* Change origin and destination check
2018-03-07 17:36:05 -06:00
Daniel Ternyak 0e26f7af4c
Tag Beta 0.5.1 (#1287) 2018-03-07 10:55:29 -06:00
Daniel Ternyak 3278318fa6
Fix Coinbase Widget (#1286)
* Connect Promos; pass current wallet address to Coinbase

* Use ShapeShift as promo if no wallet instance when showing Coinbase

* use appstate for typing
2018-03-07 10:54:14 -06:00
greenkeeper[bot] b901c3e125 chore(package): update @types/jest to version 22.2.0 (#1261) 2018-03-05 23:52:43 -06:00
Daniel Ternyak 8acef8e54f
Revert hard-source-webpack-plugin updates (#1269)
* Revert "fix(package): update hard-source-webpack-plugin to version 0.6.4 (#1254)"

This reverts commit e45324e7f1.

* Revert "fix(package): update hard-source-webpack-plugin to version 0.6.1 (#1232)"

This reverts commit b016d35819.
2018-03-05 23:52:20 -06:00
HenryNguyen5 2d057eb0b2 Fix swap timer (#1265) 2018-03-05 22:51:31 -06:00
William O'Beirne a5be81e96d Change swap link icon to ShapeShift logo. Get rid of new banner. (#1264) 2018-03-05 21:22:45 -06:00
Daniel Ternyak 0d4bc5bbb1
Tag Beta Release 0.5.0 (#1255) 2018-03-05 14:36:20 -06:00
greenkeeper[bot] 446d7043c3 chore(package): update resolve-url-loader to version 2.3.0 (#1243) 2018-03-05 13:52:27 -06:00
greenkeeper[bot] 3bdf41cf59 chore(package): update electron-builder to version 20.2.1 (#1250) 2018-03-05 13:51:45 -06:00
Taylor Monahan 7941403782 Copy / messaging tweaks (#1253)
* replace "don't have a wallet" with "account" for more consistency

* Update Copyright info in disclaimer model

* fix prettier issues
2018-03-05 13:51:06 -06:00
greenkeeper[bot] 533255094b fix(package): update electron-updater to version 2.21.0 (#1251) 2018-03-05 13:49:42 -06:00
William O'Beirne d0379b19a0 Fix Disclaimer Open on ESC (#1245)
* Don’t listen to escape while modal is closed.

* Remove console
2018-03-05 13:00:54 -06:00
Daniel Ternyak e45324e7f1
fix(package): update hard-source-webpack-plugin to version 0.6.4 (#1254)
Closes #1252
2018-03-05 12:59:09 -06:00
William O'Beirne 3a36818412 Add Check TX Button to Transaction Notification (#1217)
* Reinstates v3 behavior on transaction send:

* Alert shows Check TX Status button

* Check TX page uses txHash query param

* LogOutPrompt now carries over search params and hash

* Remove console log

* Update test.
2018-03-05 12:58:53 -06:00
William O'Beirne e3194a649e Fix Blurry Modals (#1246) 2018-03-05 12:52:35 -06:00
greenkeeper[bot] b016d35819 fix(package): update hard-source-webpack-plugin to version 0.6.1 (#1232) 2018-03-03 13:29:42 -06:00
Daniel Ternyak dd83071ec7
chore(package): update url-loader to version 1.0.1 (#1240)
Closes #1237
2018-03-03 13:27:27 -06:00
greenkeeper[bot] 383df967a7 chore(package): update sass-loader to version 6.0.7 (#1236) 2018-03-03 13:27:10 -06:00
greenkeeper[bot] 611e511b33 fix(package): update moment to version 2.21.0 (#1235) 2018-03-03 13:26:36 -06:00
Luit Hollander a9aeacb775 Fix Repo Link (#1239) 2018-03-03 13:12:45 -06:00
William O'Beirne 6927aa0b55 Prevent Scrolling From Altering Amount Field (#1234)
* Prevent scrolling to adjust number input.

* Blur instead, less annoying.
2018-03-03 13:11:05 -06:00
HenryNguyen5 4dd0fc9785 Fix contract interact (#1233)
* reduce unintended side effects from empty units

* Fix contract interaction "to" address being empty

* Fix tsc errors
2018-03-02 15:57:17 -06:00
greenkeeper[bot] 86992e8198 chore(package): update ts-jest to version 22.4.1 (#1231) 2018-03-02 11:19:04 -06:00
357 changed files with 19986 additions and 19244 deletions

1
.prettierignore Normal file
View File

@ -0,0 +1 @@
common/translations

View File

@ -29,7 +29,7 @@ export function loadBityRatesSucceededSwap(
export type TLoadShapeshiftRatesSucceededSwap = typeof loadShapeshiftRatesSucceededSwap;
export function loadShapeshiftRatesSucceededSwap(
payload
payload: interfaces.LoadShapeshiftRatesSucceededSwapAction['payload']
): interfaces.LoadShapeshiftRatesSucceededSwapAction {
return {
type: TypeKeys.SWAP_LOAD_SHAPESHIFT_RATES_SUCCEEDED,

View File

@ -18,3 +18,18 @@ export function setTransactionData(
payload
};
}
export type TResetTransactionData = typeof resetTransactionData;
export function resetTransactionData(): interfaces.ResetTransactionDataAction {
return { type: TypeKeys.TRANSACTIONS_RESET_TRANSACTION_DATA };
}
export type TAddRecentTransaction = typeof addRecentTransaction;
export function addRecentTransaction(
payload: interfaces.AddRecentTransactionAction['payload']
): interfaces.AddRecentTransactionAction {
return {
type: TypeKeys.TRANSACTIONS_ADD_RECENT_TRANSACTION,
payload
};
}

View File

@ -1,5 +1,5 @@
import { TypeKeys } from './constants';
import { TransactionData, TransactionReceipt } from 'libs/nodes';
import { SavedTransaction, TransactionData, TransactionReceipt } from 'types/transactions';
export interface FetchTransactionDataAction {
type: TypeKeys.TRANSACTIONS_FETCH_TRANSACTION_DATA;
@ -16,5 +16,18 @@ export interface SetTransactionDataAction {
};
}
export interface ResetTransactionDataAction {
type: TypeKeys.TRANSACTIONS_RESET_TRANSACTION_DATA;
}
export interface AddRecentTransactionAction {
type: TypeKeys.TRANSACTIONS_ADD_RECENT_TRANSACTION;
payload: SavedTransaction;
}
/*** Union Type ***/
export type TransactionsAction = FetchTransactionDataAction | SetTransactionDataAction;
export type TransactionsAction =
| FetchTransactionDataAction
| SetTransactionDataAction
| ResetTransactionDataAction
| AddRecentTransactionAction;

View File

@ -1,5 +1,7 @@
export enum TypeKeys {
TRANSACTIONS_FETCH_TRANSACTION_DATA = 'TRANSACTIONS_FETCH_TRANSACTION_DATA',
TRANSACTIONS_SET_TRANSACTION_DATA = 'TRANSACTIONS_SET_TRANSACTION_DATA',
TRANSACTIONS_SET_TRANSACTION_ERROR = 'TRANSACTIONS_SET_TRANSACTION_ERROR'
TRANSACTIONS_SET_TRANSACTION_ERROR = 'TRANSACTIONS_SET_TRANSACTION_ERROR',
TRANSACTIONS_RESET_TRANSACTION_DATA = 'TRANSACTIONS_RESET_TRANSACTION_DATA',
TRANSACTIONS_ADD_RECENT_TRANSACTION = 'TRANSACTIONS_ADD_RECENT_TRANSACTION'
}

View File

@ -29,10 +29,14 @@ const repOptions = {
name: 'Augur'
};
export interface MappedRates {
[key: string]: any;
}
export function getAllRates() {
const mappedRates = {};
const mappedRates: MappedRates = {};
return _getAllRates().then(bityRates => {
bityRates.objects.forEach(each => {
bityRates.objects.forEach((each: any) => {
const pairName = each.pair;
const from = { id: pairName.substring(0, 3) };
const to = { id: pairName.substring(3, 6) };

View File

@ -1,4 +1,5 @@
import { checkHttpStatus, parseJSON } from './utils';
import { Omit } from 'react-redux';
const MAX_GAS_FAST = 250;
@ -21,15 +22,29 @@ export interface GasEstimates {
isDefault: boolean;
}
interface GasExpressResponse {
block_time: number;
blockNum: number;
fast: number;
fastest: number;
safeLow: number;
standard: number;
}
export function fetchGasEstimates(): Promise<GasEstimates> {
return fetch('https://dev.blockscale.net/api/gasexpress.json', {
mode: 'cors'
})
.then(checkHttpStatus)
.then(parseJSON)
.then((res: object) => {
.then((res: GasExpressResponse) => {
// Make sure it looks like a raw gas estimate, and it has valid values
const keys = ['safeLow', 'standard', 'fast', 'fastest'];
const keys: (keyof Omit<GasExpressResponse, 'block_time' | 'blockNum'>)[] = [
'safeLow',
'standard',
'fast',
'fastest'
];
keys.forEach(key => {
if (typeof res[key] !== 'number') {
throw new Error(

View File

@ -28,6 +28,44 @@ export const SHAPESHIFT_TOKEN_WHITELIST = [
];
export const SHAPESHIFT_WHITELIST = [...SHAPESHIFT_TOKEN_WHITELIST, 'ETH', 'ETC', 'BTC'];
interface IPairData {
limit: number;
maxLimit: number;
min: number;
minerFee: number;
pair: string;
rate: string;
}
interface IExtraPairData {
status: string;
image: string;
name: string;
}
interface IAvailablePairData {
[pairName: string]: IExtraPairData;
}
interface ShapeshiftMarketInfo {
rate: string;
limit: number;
pair: string;
maxLimit: number;
min: number;
minerFee: number;
}
interface TokenMap {
[pairName: string]: {
id: string;
rate: string;
limit: number;
min: number;
options: (IExtraPairData & { id: string })[];
};
}
class ShapeshiftService {
public whitelist = SHAPESHIFT_WHITELIST;
private url = SHAPESHIFT_BASE_URL;
@ -36,13 +74,18 @@ class ShapeshiftService {
'Content-Type': 'application/json'
};
public checkStatus(address) {
public checkStatus(address: string) {
return fetch(`${this.url}/txStat/${address}`)
.then(checkHttpStatus)
.then(parseJSON);
}
public sendAmount(withdrawal, originKind, destinationKind, destinationAmount) {
public sendAmount(
withdrawal: string,
originKind: string,
destinationKind: string,
destinationAmount: number
) {
const pair = `${originKind.toLowerCase()}_${destinationKind.toLowerCase()}`;
return fetch(`${this.url}/sendamount`, {
@ -81,7 +124,7 @@ class ShapeshiftService {
return mappedRates;
};
private getPairRates(marketInfo) {
private getPairRates(marketInfo: ShapeshiftMarketInfo[]) {
const filteredMarketInfo = marketInfo.filter(obj => {
const { pair } = obj;
const pairArr = pair.split('_');
@ -97,7 +140,7 @@ class ShapeshiftService {
return pairRates;
}
private async checkAvl(pairRates) {
private async checkAvl(pairRates: IPairData[]) {
const avlCoins = await this.getAvlCoins();
const mapAvl = pairRates.map(p => {
const { pair } = p;
@ -121,7 +164,8 @@ class ShapeshiftService {
};
}
});
return mapAvl;
const filered = mapAvl.filter(v => v);
return filered as (IPairData & IAvailablePairData)[];
}
private getAvlCoins() {
@ -130,7 +174,7 @@ class ShapeshiftService {
.then(parseJSON);
}
private getSinglePairRate(pair) {
private getSinglePairRate(pair: string) {
return fetch(`${this.url}/rate/${pair}`)
.then(checkHttpStatus)
.then(parseJSON);
@ -142,12 +186,12 @@ class ShapeshiftService {
.then(parseJSON);
}
private isWhitelisted(coin) {
private isWhitelisted(coin: string) {
return this.whitelist.includes(coin);
}
private mapMarketInfo(marketInfo) {
const tokenMap = {};
private mapMarketInfo(marketInfo: (IPairData & IAvailablePairData)[]) {
const tokenMap: TokenMap = {};
marketInfo.forEach(m => {
const originKind = m.pair.substring(0, 3);
const destinationKind = m.pair.substring(4, 7);

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 38 KiB

View File

@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="81px" height="120px" viewBox="0 0 81 120" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 48.2 (47327) - http://www.bohemiancoding.com/sketch -->
<title>Artboard</title>
<desc>Created with Sketch.</desc>
<defs>
<linearGradient x1="100.099854%" y1="-16.122731%" x2="44.1579795%" y2="77.6511455%" id="linearGradient-1">
<stop stop-color="#2B415B" offset="13.45%"></stop>
<stop stop-color="#3B5676" offset="37.62%"></stop>
<stop stop-color="#54769E" offset="69.23%"></stop>
<stop stop-color="#52749B" offset="79.01%"></stop>
<stop stop-color="#4D6C92" offset="86.14%"></stop>
<stop stop-color="#436082" offset="92.44%"></stop>
<stop stop-color="#364F6C" offset="98.22%"></stop>
<stop stop-color="#314863" offset="100%"></stop>
</linearGradient>
<linearGradient x1="99.7887646%" y1="61.4941149%" x2="-4.60729499%" y2="13.3460499%" id="linearGradient-2">
<stop stop-color="#54769E" offset="0%"></stop>
<stop stop-color="#53749C" offset="48.02%"></stop>
<stop stop-color="#4F6F95" offset="68.78%"></stop>
<stop stop-color="#486588" offset="84.23%"></stop>
<stop stop-color="#435F80" offset="90.95%"></stop>
</linearGradient>
<linearGradient x1="725.856983%" y1="50.0229457%" x2="-347.124022%" y2="50.0229457%" id="linearGradient-3">
<stop stop-color="#20344C" offset="25.39%"></stop>
<stop stop-color="#273D57" offset="40.72%"></stop>
<stop stop-color="#395373" offset="67.33%"></stop>
<stop stop-color="#54769E" offset="100%"></stop>
</linearGradient>
<linearGradient x1="-653.788268%" y1="50.0214562%" x2="455.494413%" y2="50.0214562%" id="linearGradient-4">
<stop stop-color="#54769E" offset="25.39%"></stop>
<stop stop-color="#4D6E93" offset="41.33%"></stop>
<stop stop-color="#3C5777" offset="68.97%"></stop>
<stop stop-color="#233850" offset="100%"></stop>
</linearGradient>
<linearGradient x1="50.0370086%" y1="-209.335792%" x2="49.9575834%" y2="235.965027%" id="linearGradient-5">
<stop stop-color="#54769E" offset="0.6545247%"></stop>
<stop stop-color="#507198" offset="19.93%"></stop>
<stop stop-color="#466488" offset="45.02%"></stop>
<stop stop-color="#354F6D" offset="73.18%"></stop>
<stop stop-color="#21354D" offset="100%"></stop>
</linearGradient>
<linearGradient x1="163.859644%" y1="-95.5767241%" x2="-53.1396713%" y2="173.049483%" id="linearGradient-6">
<stop stop-color="#54769E" offset="25.39%"></stop>
<stop stop-color="#4D6E93" offset="41.02%"></stop>
<stop stop-color="#3C5777" offset="68.13%"></stop>
<stop stop-color="#22364E" offset="100%"></stop>
</linearGradient>
<linearGradient x1="-44.1116757%" y1="-60.1463542%" x2="137.687762%" y2="146.957292%" id="linearGradient-7">
<stop stop-color="#54769E" offset="25.39%"></stop>
<stop stop-color="#4D6E93" offset="41.02%"></stop>
<stop stop-color="#3C5777" offset="68.13%"></stop>
<stop stop-color="#22364E" offset="100%"></stop>
</linearGradient>
<linearGradient x1="-1.43584837%" y1="31.6007047%" x2="127.701745%" y2="103.798097%" id="linearGradient-8">
<stop stop-color="#54769E" offset="26.64%"></stop>
<stop stop-color="#425E7F" offset="100%"></stop>
</linearGradient>
<linearGradient x1="-71.276129%" y1="-65.3137535%" x2="149.174581%" y2="96.3311507%" id="linearGradient-9">
<stop stop-color="#54769E" stop-opacity="0" offset="46.09%"></stop>
<stop stop-color="#52739A" stop-opacity="0.2156" offset="56.99%"></stop>
<stop stop-color="#4A698E" stop-opacity="0.4266" offset="67.64%"></stop>
<stop stop-color="#3D597B" stop-opacity="0.6356" offset="78.2%"></stop>
<stop stop-color="#2C435F" stop-opacity="0.8422" offset="88.63%"></stop>
<stop stop-color="#1B2E45" offset="96.61%"></stop>
</linearGradient>
<linearGradient x1="50.309375%" y1="295.997443%" x2="50.309375%" y2="-124.649242%" id="linearGradient-10">
<stop stop-color="#54769E" offset="25.39%"></stop>
<stop stop-color="#4D6E93" offset="41.02%"></stop>
<stop stop-color="#3C5777" offset="68.13%"></stop>
<stop stop-color="#22364E" offset="100%"></stop>
</linearGradient>
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Artboard" fill-rule="nonzero">
<g id="logo-shapeshift">
<polygon id="Shape" fill="#273C51" points="75.9045553 29.0113736 81 0.0349956255 59.7045553 8.88888889 36.3006508 8.88888889 15.0052061 0 20.1357918 29.0113736 15.3214751 45.1793526 19.8546638 48.0139983 0 65.3718285 0 81.67979 22.7010846 112.825897 44.1019523 119.965004 44.1370933 120 60.8642082 111.461067 60.8993492 111.426072 60.8993492 98.7926509 51.4112798 93.6132983 51.4112798 93.6132983 51.4112798 93.6132983 66.3110629 54.1032371 66.2056399 54.1032371 80.683731 45.1793526"></polygon>
<g id="Group">
<polygon id="Shape" fill="url(#linearGradient-1)" points="34.2584416 35.3372093 0 65.2325581 47.7233766 92.5465116 47.8987013 36.244186"></polygon>
<polygon id="Shape" fill="#466284" points="29.3844156 53.8255814 47.7584416 102.697674 47.8987013 56.5465116"></polygon>
<polygon id="Shape" fill="#354D6A" points="66.1675325 54 47.7584416 102.697674 47.8987013 56.5465116"></polygon>
<polygon id="Shape" fill="url(#linearGradient-2)" points="80.8246753 0.104651163 62.5558442 9.6627907 47.9337662 12.8372093 33.1714286 9.6627907 14.9727273 0.0697674419 21.2493506 28.9186047 15.2883117 45.1046512 36.187013 58.0116279 47.7935065 70.5697674 47.7935065 70.6744186 47.8636364 70.6046512 47.9337662 70.6744186 47.9337662 70.5697674 59.5402597 58.0116279 80.5090909 45.1046512 74.5480519 28.9534884"></polygon>
<polygon id="Shape" fill="url(#linearGradient-3)" points="80.8246753 0.104651163 75.7402597 28.9883721 80.5090909 45.1046512 74.5480519 28.9534884"></polygon>
<polygon id="Shape" fill="url(#linearGradient-4)" points="14.9727273 0.0697674419 20.0922078 28.9883721 15.2883117 45.1046512 21.2493506 28.9186047"></polygon>
<polygon id="Shape" fill="url(#linearGradient-5)" points="14.9727273 0.0697674419 36.2220779 8.93023256 59.5753247 8.93023256 80.8246753 0.104651163 62.5558442 9.6627907 47.9337662 12.8372093 33.1714286 9.6627907"></polygon>
<polygon id="Shape" fill="url(#linearGradient-6)" points="21.2493506 28.9186047 20.1974026 31.8139535 42.2532468 11.5813953"></polygon>
<polygon id="Shape" fill="url(#linearGradient-7)" points="74.5480519 28.9534884 53.6493506 11.5813953 75.5649351 31.6744186"></polygon>
<polygon id="Shape" fill="url(#linearGradient-8)" points="0 65.2325581 51.3 93.3837209 57.787013 109.534884 44.0415584 114.732558 22.6519481 112.534884 0 81.4883721"></polygon>
<polygon id="Shape" fill="#FFFFFF" points="42.4636364 88.5348837 22.7220779 112.465116 22.6519481 112.534884 44.0064935 119.651163 44.0415584 119.686047 60.7324675 111.174419 60.7675325 111.139535 60.7675325 98.5465116"></polygon>
<polygon id="Shape" fill="url(#linearGradient-9)" points="74.5480519 28.9534884 80.8246753 0.104651163 62.5558442 9.6627907 53.6493506 11.5813953"></polygon>
<polygon id="Shape" fill="#FFFFFF" points="47.8987013 70.6744186 42.112987 64.3604651 47.8987013 58.0116279 53.7194805 64.3604651"></polygon>
<polygon id="Shape" fill="url(#linearGradient-10)" points="47.3376623 12.6976744 47.9337662 12.8372093 48.4597403 12.7325581 47.8987013 49.5348837"></polygon>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.1 KiB

View File

@ -1,36 +0,0 @@
// Mixins
// --------------------------------------------------
// Utilities
@import "mixins/hide-text.less";
@import "mixins/opacity.less";
@import "mixins/image.less";
@import "mixins/labels.less";
@import "mixins/reset-filter.less";
@import "mixins/resize.less";
@import "mixins/responsive-visibility.less";
@import "mixins/size.less";
@import "mixins/tab-focus.less";
@import "mixins/reset-text.less";
@import "mixins/text-emphasis.less";
@import "mixins/text-overflow.less";
@import "mixins/vendor-prefixes.less";
// Components
@import "mixins/alerts.less";
@import "mixins/buttons.less";
@import "mixins/panels.less";
@import "mixins/pagination.less";
@import "mixins/list-group.less";
@import "mixins/nav-divider.less";
@import "mixins/forms.less";
@import "mixins/progress-bar.less";
@import "mixins/table-row.less";
// Skins
@import "mixins/background-variant.less";
@import "mixins/border-radius.less";
@import "mixins/gradients.less";
// Layout
@import "mixins/clearfix.less";
@import "mixins/center-block.less";
@import "mixins/nav-vertical-align.less";
@import "mixins/grid-framework.less";
@import "mixins/grid.less";

View File

@ -1,14 +0,0 @@
// Alerts
.alert-variant(@background; @border; @text-color) {
background-color: @background;
border-color: @border;
color: @text-color;
hr {
border-top-color: darken(@border, 5%);
}
.alert-link {
color: darken(@text-color, 10%);
}
}

View File

@ -1,9 +0,0 @@
// Contextual backgrounds
.bg-variant(@color) {
background-color: @color;
a&:hover,
a&:focus {
background-color: darken(@color, 10%);
}
}

View File

@ -1,21 +0,0 @@
// Single side border-radius
.border-top-radius(@radius) {
border-top-right-radius: @radius;
border-top-left-radius: @radius;
}
.border-right-radius(@radius) {
border-bottom-right-radius: @radius;
border-top-right-radius: @radius;
}
.border-bottom-radius(@radius) {
border-bottom-right-radius: @radius;
border-bottom-left-radius: @radius;
}
.border-left-radius(@radius) {
border-bottom-left-radius: @radius;
border-top-left-radius: @radius;
}

View File

@ -1,73 +0,0 @@
// Button variants
//
// Easily pump out default styles, as well as :hover, :focus, :active,
// and disabled options for all buttons
.button-variant(@color; @background; @border) {
color: @color;
background-color: @background;
border-color: @border;
transition: all ease 250ms;
&:focus,
&.focus {
color: @color;
background-color: darken(@background, 5%);
border-color: darken(@border, 5%);
transition: all ease 250ms;
}
&:hover {
color: @color;
background-color: darken(@background, 5%);
border-color: darken(@border, 5%);
transition: all ease 250ms;
}
&:active,
&.active,
.open > .dropdown-toggle& {
color: @color;
background-color: darken(@background, 5%);
border-color: darken(@border, 5%);
transition: all ease 250ms;
&:hover,
&:focus,
&.focus {
color: @color;
background-color: darken(@background, 15%);
border-color: darken(@border, 15%);
transition: all ease 250ms;
}
}
&:active,
&.active,
.open > .dropdown-toggle& {
background-image: none;
}
&.disabled,
&[disabled],
fieldset[disabled] & {
&,
&:hover,
&:focus,
&.focus,
&:active,
&.active {
background-color: @background;
border-color: @border;
}
}
.badge {
color: @background;
background-color: @color;
}
}
// Button sizes
.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {
padding: @padding-vertical @padding-horizontal;
font-size: @font-size;
line-height: @line-height;
border-radius: @border-radius;
}

View File

@ -1,7 +0,0 @@
// Center-align a block level element
.center-block() {
display: block;
margin-left: auto;
margin-right: auto;
}

View File

@ -1,22 +0,0 @@
// Clearfix
//
// For modern browsers
// 1. The space content is one way to avoid an Opera bug when the
// contenteditable attribute is included anywhere else in the document.
// Otherwise it causes space to appear at the top and bottom of elements
// that are clearfixed.
// 2. The use of `table` rather than `block` is only necessary if using
// `:before` to contain the top-margins of child elements.
//
// Source: http://nicolasgallagher.com/micro-clearfix-hack/
.clearfix() {
&:before,
&:after {
content: " "; // 1
display: table; // 2
}
&:after {
clear: both;
}
}

View File

@ -1,83 +0,0 @@
// Form validation states
//
// Used in forms.less to generate the form validation CSS for warnings, errors,
// and successes.
.form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) {
// Color the label and help text
.help-block,
.control-label,
.radio,
.checkbox,
.radio-inline,
.checkbox-inline,
&.radio label,
&.checkbox label,
&.radio-inline label,
&.checkbox-inline label {
color: @text-color;
}
// Set the border and box shadow on specific inputs to match
.form-control {
border-color: @border-color;
.box-shadow(inset 0 1px 1px rgba(0, 0, 0, .075)); // Redeclare so transitions work
&:focus {
border-color: darken(@border-color, 10%);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 3px rgba(@brand-primary, .5);
}
}
// Set validation states also for addons
.input-group-addon {
color: @text-color;
border-color: @border-color;
background-color: @background-color;
}
// Optional feedback icon
.form-control-feedback {
color: @text-color;
}
}
// Form control focus state
//
// Generate a customized focus state and for any input with the specified color,
// which defaults to the `@input-border-focus` variable.
//
// We highly encourage you to not customize the default value, but instead use
// this to tweak colors on an as-needed basis. This aesthetic change is based on
// WebKit's default styles, but applicable to a wider range of browsers. Its
// usability and accessibility should be taken into account with any change.
//
// Example usage: change the default blue border and shadow to white for better
// contrast against a dark gray background.
.form-control-focus(@color: @input-border-focus) {
@color-rgba: rgba(red(@color), green(@color), blue(@color), .6);
&:focus {
border-color: @color;
outline: 0;
.box-shadow(~"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}");
}
}
// Form control sizing
//
// Relative text size, padding, and border-radii changes for form controls. For
// horizontal sizing, wrap controls in the predefined grid classes. `<select>`
// element gets special love because it's special, and that's a fact!
.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {
height: @input-height;
padding: @padding-vertical @padding-horizontal;
font-size: @font-size;
line-height: @line-height;
border-radius: @border-radius;
select& {
height: @input-height;
line-height: @input-height;
}
textarea&,
select[multiple] & {
height: auto;
}
}

View File

@ -1,59 +0,0 @@
// Gradients
#gradient {
// Horizontal gradient, from left to right
//
// Creates two color stops, start and end, by specifying a color and position for each color stop.
// Color stops are not available in IE9 and below.
.horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {
background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+
background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12
background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+
background-repeat: repeat-x;
filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)", argb(@start-color), argb(@end-color))); // IE9 and down
}
// Vertical gradient, from top to bottom
//
// Creates two color stops, start and end, by specifying a color and position for each color stop.
// Color stops are not available in IE9 and below.
.vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {
background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+
background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12
background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+
background-repeat: repeat-x;
filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)", argb(@start-color), argb(@end-color))); // IE9 and down
}
.directional(@start-color: #555; @end-color: #333; @deg: 45deg) {
background-repeat: repeat-x;
background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+
background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12
background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+
}
.horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {
background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);
background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);
background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);
background-repeat: no-repeat;
filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)", argb(@start-color), argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback
}
.vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {
background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);
background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);
background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);
background-repeat: no-repeat;
filter: e(%("progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)", argb(@start-color), argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback
}
.radial(@inner-color: #555; @outer-color: #333) {
background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);
background-image: radial-gradient(circle, @inner-color, @outer-color);
background-repeat: no-repeat;
}
.striped(@color: rgba(255,255,255,.15); @angle: 45deg) {
background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);
background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);
}
}

View File

@ -1,96 +0,0 @@
// Framework grid generation
//
// Used only by Bootstrap to generate the correct number of grid classes given
// any value of `@grid-columns`.
.make-grid-columns() {
// Common styles for all sizes of grid columns, widths 1-12
.col(@index) { // initial
@item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}";
.col((@index + 1), @item);
}
.col(@index, @list) when (@index =< @grid-columns) { // general; "=<" isn't a typo
@item: ~".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}";
.col((@index + 1), ~"@{list}, @{item}");
}
.col(@index, @list) when (@index > @grid-columns) { // terminal
@{list} {
position: relative;
// Prevent columns from collapsing when empty
min-height: 1px;
// Inner gutter via padding
padding-left: (@grid-gutter-width / 2);
padding-right: (@grid-gutter-width / 2);
}
}
.col(1); // kickstart it
}
.float-grid-columns(@class) {
.col(@index) { // initial
@item: ~".col-@{class}-@{index}";
.col((@index + 1), @item);
}
.col(@index, @list) when (@index =< @grid-columns) { // general
@item: ~".col-@{class}-@{index}";
.col((@index + 1), ~"@{list}, @{item}");
}
.col(@index, @list) when (@index > @grid-columns) { // terminal
@{list} {
float: left;
}
}
.col(1); // kickstart it
}
.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {
.col-@{class}-@{index} {
width: percentage((@index / @grid-columns));
}
}
.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) {
.col-@{class}-push-@{index} {
left: percentage((@index / @grid-columns));
}
}
.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) {
.col-@{class}-push-0 {
left: auto;
}
}
.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) {
.col-@{class}-pull-@{index} {
right: percentage((@index / @grid-columns));
}
}
.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) {
.col-@{class}-pull-0 {
right: auto;
}
}
.calc-grid-column(@index, @class, @type) when (@type = offset) {
.col-@{class}-offset-@{index} {
margin-left: percentage((@index / @grid-columns));
}
}
// Basic looping in LESS
.loop-grid-columns(@index, @class, @type) when (@index >= 0) {
.calc-grid-column(@index, @class, @type);
// next iteration
.loop-grid-columns((@index - 1), @class, @type);
}
// Create grid for specific class
.make-grid(@class) {
.float-grid-columns(@class);
.loop-grid-columns(@grid-columns, @class, width);
.loop-grid-columns(@grid-columns, @class, pull);
.loop-grid-columns(@grid-columns, @class, push);
.loop-grid-columns(@grid-columns, @class, offset);
}

View File

@ -1,134 +0,0 @@
// Grid system
//
// Generate semantic grid columns with these mixins.
// Centered container element
.container-fixed(@gutter: @grid-gutter-width) {
margin-right: auto;
margin-left: auto;
padding-left: (@gutter / 2);
padding-right: (@gutter / 2);
&:extend(.clearfix all);
}
// Creates a wrapper for a series of columns
.make-row(@gutter: @grid-gutter-width) {
margin-left: (@gutter / -2);
margin-right: (@gutter / -2);
&:extend(.clearfix all);
}
// Generate the extra small columns
.make-xs-column(@columns; @gutter: @grid-gutter-width) {
position: relative;
float: left;
width: percentage((@columns / @grid-columns));
min-height: 1px;
padding-left: (@gutter / 2);
padding-right: (@gutter / 2);
}
.make-xs-column-offset(@columns) {
margin-left: percentage((@columns / @grid-columns));
}
.make-xs-column-push(@columns) {
left: percentage((@columns / @grid-columns));
}
.make-xs-column-pull(@columns) {
right: percentage((@columns / @grid-columns));
}
// Generate the small columns
.make-sm-column(@columns; @gutter: @grid-gutter-width) {
position: relative;
min-height: 1px;
padding-left: (@gutter / 2);
padding-right: (@gutter / 2);
@media (min-width: @screen-sm-min) {
float: left;
width: percentage((@columns / @grid-columns));
}
}
.make-sm-column-offset(@columns) {
@media (min-width: @screen-sm-min) {
margin-left: percentage((@columns / @grid-columns));
}
}
.make-sm-column-push(@columns) {
@media (min-width: @screen-sm-min) {
left: percentage((@columns / @grid-columns));
}
}
.make-sm-column-pull(@columns) {
@media (min-width: @screen-sm-min) {
right: percentage((@columns / @grid-columns));
}
}
// Generate the medium columns
.make-md-column(@columns; @gutter: @grid-gutter-width) {
position: relative;
min-height: 1px;
padding-left: (@gutter / 2);
padding-right: (@gutter / 2);
@media (min-width: @screen-md-min) {
float: left;
width: percentage((@columns / @grid-columns));
}
}
.make-md-column-offset(@columns) {
@media (min-width: @screen-md-min) {
margin-left: percentage((@columns / @grid-columns));
}
}
.make-md-column-push(@columns) {
@media (min-width: @screen-md-min) {
left: percentage((@columns / @grid-columns));
}
}
.make-md-column-pull(@columns) {
@media (min-width: @screen-md-min) {
right: percentage((@columns / @grid-columns));
}
}
// Generate the large columns
.make-lg-column(@columns; @gutter: @grid-gutter-width) {
position: relative;
min-height: 1px;
padding-left: (@gutter / 2);
padding-right: (@gutter / 2);
@media (min-width: @screen-lg-min) {
float: left;
width: percentage((@columns / @grid-columns));
}
}
.make-lg-column-offset(@columns) {
@media (min-width: @screen-lg-min) {
margin-left: percentage((@columns / @grid-columns));
}
}
.make-lg-column-push(@columns) {
@media (min-width: @screen-lg-min) {
left: percentage((@columns / @grid-columns));
}
}
.make-lg-column-pull(@columns) {
@media (min-width: @screen-lg-min) {
right: percentage((@columns / @grid-columns));
}
}

View File

@ -1,21 +0,0 @@
// CSS image replacement
//
// Heads up! v3 launched with only `.hide-text()`, but per our pattern for
// mixins being reused as classes with the same name, this doesn't hold up. As
// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`.
//
// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757
// Deprecated as of v3.0.1 (will be removed in v4)
.hide-text() {
font: ~"0/0" a;
color: transparent;
text-shadow: none;
background-color: transparent;
border: 0;
}
// New mixin to use as of v3.0.1
.text-hide() {
.hide-text();
}

View File

@ -1,25 +0,0 @@
// Image Mixins
// - Responsive image
// - Retina image
// Responsive image
//
// Keep images from scaling beyond the width of their parents.
.img-responsive(@display: block) {
display: @display;
max-width: 100%; // Part 1: Set a maximum relative to the parent
height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching
}
// Retina image
//
// Short retina mixin for setting background-image and -size. Note that the
// spelling of `min--moz-device-pixel-ratio` is intentional.
.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {
background-image: url("@{file-1x}");
@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and ( min--moz-device-pixel-ratio: 2), only screen and ( -o-min-device-pixel-ratio: 2/1), only screen and ( min-device-pixel-ratio: 2), only screen and ( min-resolution: 192dpi), only screen and ( min-resolution: 2dppx) {
background-image: url("@{file-2x}");
background-size: @width-1x @height-1x;
}
}

View File

@ -1,12 +0,0 @@
// Labels
.label-variant(@color) {
background-color: @color;
&[href] {
&:hover,
&:focus {
background-color: darken(@color, 10%);
}
}
}

View File

@ -1,30 +0,0 @@
// List Groups
.list-group-item-variant(@state; @background; @color) {
.list-group-item-@{state} {
color: @color;
background-color: @background;
a&,
button& {
color: @color;
.list-group-item-heading {
color: inherit;
}
&:hover,
&:focus {
color: @color;
background-color: darken(@background, 5%);
}
&.active,
&.active:hover,
&.active:focus {
color: #fff;
background-color: @color;
border-color: @color;
}
}
}
}

View File

@ -1,10 +0,0 @@
// Horizontal dividers
//
// Dividers (basically an hr) within dropdowns and nav lists
.nav-divider(@color: #e5e5e5) {
height: 1px;
margin: 2px 0;
overflow: hidden;
background-color: @color;
}

View File

@ -1,9 +0,0 @@
// Navbar vertical align
//
// Vertically center elements in the navbar.
// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.
.navbar-vertical-align(@element-height) {
margin-top: ((@navbar-height - @element-height) / 2);
margin-bottom: ((@navbar-height - @element-height) / 2);
}

View File

@ -1,8 +0,0 @@
// Opacity
.opacity(@opacity) {
opacity: @opacity;
// IE8 filter
@opacity-ie: (@opacity * 100);
filter: ~"alpha(opacity=@{opacity-ie})";
}

View File

@ -1,24 +0,0 @@
// Pagination
.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {
> li {
> a,
> span {
padding: @padding-vertical @padding-horizontal;
font-size: @font-size;
line-height: @line-height;
}
&:first-child {
> a,
> span {
.border-left-radius(@border-radius);
}
}
&:last-child {
> a,
> span {
.border-right-radius(@border-radius);
}
}
}
}

View File

@ -1,24 +0,0 @@
// Panels
.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) {
border-color: @border;
& > .panel-heading {
color: @heading-text-color;
background-color: @heading-bg-color;
border-color: @heading-border;
+ .panel-collapse > .panel-body {
border-top-color: @border;
}
.badge {
color: @heading-bg-color;
background-color: @heading-text-color;
}
}
& > .panel-footer {
+ .panel-collapse > .panel-body {
border-bottom-color: @border;
}
}
}

View File

@ -1,10 +0,0 @@
// Progress bars
.progress-bar-variant(@color) {
background-color: @color;
// Deprecated parent class requirement as of v3.2.0
.progress-striped & {
#gradient > .striped();
}
}

View File

@ -1,8 +0,0 @@
// Reset filters for IE
//
// When you need to remove a gradient background, do not forget to use this to reset
// the IE filter for IE9 and below.
.reset-filter() {
filter: e(%("progid:DXImageTransform.Microsoft.gradient(enabled = false)"));
}

View File

@ -1,18 +0,0 @@
.reset-text() {
font-family: @font-family-base;
// We deliberately do NOT reset font-size.
font-style: normal;
font-weight: normal;
letter-spacing: normal;
line-break: auto;
line-height: @line-height-base;
text-align: left; // Fallback for where `start` is not supported
text-align: start;
text-decoration: none;
text-shadow: none;
text-transform: none;
white-space: normal;
word-break: normal;
word-spacing: normal;
word-wrap: normal;
}

View File

@ -1,6 +0,0 @@
// Resize anything
.resizable(@direction) {
resize: @direction; // Options: horizontal, vertical, both
overflow: auto; // Per CSS3 UI, `resize` only applies when `overflow` isn't `visible`
}

View File

@ -1,21 +0,0 @@
// Responsive utilities
//
// More easily include all the states for responsive-utilities.less.
.responsive-visibility() {
display: block !important;
table& {
display: table !important;
}
tr& {
display: table-row !important;
}
th&,
td& {
display: table-cell !important;
}
}
.responsive-invisibility() {
display: none !important;
}

View File

@ -1,10 +0,0 @@
// Sizing shortcuts
.size(@width; @height) {
width: @width;
height: @height;
}
.square(@size) {
.size(@size; @size);
}

View File

@ -1,6 +0,0 @@
// WebKit-style focus
.tab-focus() {
outline: thin dotted;
outline-offset: 3px;
}

View File

@ -1,28 +0,0 @@
// Tables
.table-row-variant(@state; @background) {
// Exact selectors below required to override `.table-striped` and prevent
// inheritance to nested tables.
.table > thead > tr,
.table > tbody > tr,
.table > tfoot > tr {
> td.@{state},
> th.@{state},
&.@{state} > td,
&.@{state} > th {
background-color: @background;
}
}
// Hover states for `.table-hover`
// Note: this is not available for cells or rows within `thead` or `tfoot`.
.table-hover > tbody > tr {
> td.@{state}:hover,
> th.@{state}:hover,
&.@{state}:hover > td,
&:hover > .@{state},
&.@{state}:hover > th {
background-color: darken(@background, 5%);
}
}
}

View File

@ -1,9 +0,0 @@
// Typography
.text-emphasis-variant(@color) {
color: @color;
a&:hover,
a&:focus {
color: darken(@color, 5%);
}
}

View File

@ -1,8 +0,0 @@
// Text overflow
// Requires inline-block or block for proper styling
.text-overflow() {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

View File

@ -1,254 +0,0 @@
// Vendor Prefixes
//
// All vendor mixins are deprecated as of v3.2.0 due to the introduction of
// Autoprefixer in our Gruntfile. They will be removed in v4.
// - Animations
// - Backface visibility
// - Box shadow
// - Box sizing
// - Content columns
// - Hyphens
// - Placeholder text
// - Transformations
// - Transitions
// - User Select
// Animations
.animation(@animation) {
-webkit-animation: @animation;
-o-animation: @animation;
animation: @animation;
}
.animation-name(@name) {
-webkit-animation-name: @name;
animation-name: @name;
}
.animation-duration(@duration) {
-webkit-animation-duration: @duration;
animation-duration: @duration;
}
.animation-timing-function(@timing-function) {
-webkit-animation-timing-function: @timing-function;
animation-timing-function: @timing-function;
}
.animation-delay(@delay) {
-webkit-animation-delay: @delay;
animation-delay: @delay;
}
.animation-iteration-count(@iteration-count) {
-webkit-animation-iteration-count: @iteration-count;
animation-iteration-count: @iteration-count;
}
.animation-direction(@direction) {
-webkit-animation-direction: @direction;
animation-direction: @direction;
}
.animation-fill-mode(@fill-mode) {
-webkit-animation-fill-mode: @fill-mode;
animation-fill-mode: @fill-mode;
}
// Backface visibility
// Prevent browsers from flickering when using CSS 3D transforms.
// Default value is `visible`, but can be changed to `hidden`
.backface-visibility(@visibility) {
-webkit-backface-visibility: @visibility;
-moz-backface-visibility: @visibility;
backface-visibility: @visibility;
}
// Drop shadows
//
// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's
// supported browsers that have box shadow capabilities now support it.
.box-shadow(@shadow) {
-webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1
box-shadow: @shadow;
}
// Box sizing
.box-sizing(@boxmodel) {
-webkit-box-sizing: @boxmodel;
-moz-box-sizing: @boxmodel;
box-sizing: @boxmodel;
}
// CSS3 Content Columns
.content-columns(@column-count; @column-gap: @grid-gutter-width) {
-webkit-column-count: @column-count;
-moz-column-count: @column-count;
column-count: @column-count;
-webkit-column-gap: @column-gap;
-moz-column-gap: @column-gap;
column-gap: @column-gap;
}
// Optional hyphenation
.hyphens(@mode: auto) {
word-wrap: break-word;
-webkit-hyphens: @mode;
-moz-hyphens: @mode;
-ms-hyphens: @mode; // IE10+
-o-hyphens: @mode;
hyphens: @mode;
}
// Placeholder text
.placeholder(@color: @input-color-placeholder) {
// Firefox
&::-moz-placeholder {
color: @color;
opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526
}
&:-ms-input-placeholder {
color: @color;
}
// Internet Explorer 10+
&::-webkit-input-placeholder {
color: @color;
}
// Safari and Chrome
}
// Transformations
.scale(@ratio) {
-webkit-transform: scale(@ratio);
-ms-transform: scale(@ratio); // IE9 only
-o-transform: scale(@ratio);
transform: scale(@ratio);
}
.scale(@ratioX; @ratioY) {
-webkit-transform: scale(@ratioX, @ratioY);
-ms-transform: scale(@ratioX, @ratioY); // IE9 only
-o-transform: scale(@ratioX, @ratioY);
transform: scale(@ratioX, @ratioY);
}
.scaleX(@ratio) {
-webkit-transform: scaleX(@ratio);
-ms-transform: scaleX(@ratio); // IE9 only
-o-transform: scaleX(@ratio);
transform: scaleX(@ratio);
}
.scaleY(@ratio) {
-webkit-transform: scaleY(@ratio);
-ms-transform: scaleY(@ratio); // IE9 only
-o-transform: scaleY(@ratio);
transform: scaleY(@ratio);
}
.skew(@x; @y) {
-webkit-transform: skewX(@x) skewY(@y);
-ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+
-o-transform: skewX(@x) skewY(@y);
transform: skewX(@x) skewY(@y);
}
.translate(@x; @y) {
-webkit-transform: translate(@x, @y);
-ms-transform: translate(@x, @y); // IE9 only
-o-transform: translate(@x, @y);
transform: translate(@x, @y);
}
.translate3d(@x; @y; @z) {
-webkit-transform: translate3d(@x, @y, @z);
transform: translate3d(@x, @y, @z);
}
.rotate(@degrees) {
-webkit-transform: rotate(@degrees);
-ms-transform: rotate(@degrees); // IE9 only
-o-transform: rotate(@degrees);
transform: rotate(@degrees);
}
.rotateX(@degrees) {
-webkit-transform: rotateX(@degrees);
-ms-transform: rotateX(@degrees); // IE9 only
-o-transform: rotateX(@degrees);
transform: rotateX(@degrees);
}
.rotateY(@degrees) {
-webkit-transform: rotateY(@degrees);
-ms-transform: rotateY(@degrees); // IE9 only
-o-transform: rotateY(@degrees);
transform: rotateY(@degrees);
}
.perspective(@perspective) {
-webkit-perspective: @perspective;
-moz-perspective: @perspective;
perspective: @perspective;
}
.perspective-origin(@perspective) {
-webkit-perspective-origin: @perspective;
-moz-perspective-origin: @perspective;
perspective-origin: @perspective;
}
.transform-origin(@origin) {
-webkit-transform-origin: @origin;
-moz-transform-origin: @origin;
-ms-transform-origin: @origin; // IE9 only
transform-origin: @origin;
}
// Transitions
.transition(@transition) {
-webkit-transition: @transition;
-o-transition: @transition;
transition: @transition;
}
.transition-property(@transition-property) {
-webkit-transition-property: @transition-property;
transition-property: @transition-property;
}
.transition-delay(@transition-delay) {
-webkit-transition-delay: @transition-delay;
transition-delay: @transition-delay;
}
.transition-duration(@transition-duration) {
-webkit-transition-duration: @transition-duration;
transition-duration: @transition-duration;
}
.transition-timing-function(@timing-function) {
-webkit-transition-timing-function: @timing-function;
transition-timing-function: @timing-function;
}
.transition-transform(@transition) {
-webkit-transition: -webkit-transform @transition;
-moz-transition: -moz-transform @transition;
-o-transition: -o-transform @transition;
transition: transform @transition;
}
// User select
// For selecting text on the page
.user-select(@select) {
-webkit-user-select: @select;
-moz-user-select: @select;
-ms-user-select: @select; // IE10+
user-select: @select;
}

View File

@ -1,373 +0,0 @@
img {
max-width: 100%;
height: auto;
}
textarea {
resize: vertical;
}
// anouncement bars
.announcement {
padding: 3px 10px;
text-align: center;
font-weight: 300;
color: white;
display: block;
background-color: @brand-primary;
a {
text-decoration: underline;
color: white;
transition: 250ms all ease;
}
&:hover,
&:focus {
transition: 250ms all ease;
color: darken(white, 5%);
text-decoration: none;
background-color: darken(@brand-primary, 5%);
}
strong {
font-weight: 400;
}
}
.annoucement-warning {
background-color: @brand-danger;
&:hover,
&:focus {
background-color: darken(@brand-danger, 5%);
a,
a:hover {
text-decoration: none;
}
}
}
.annoucement-danger {
background-color: @brand-danger;
a:hover,
a:focus {
color: darken(white, 5%);
a,
a:hover {
text-decoration: none;
}
}
}
// address icenticons
.address-identicon-container {
padding-left: 0;
padding-top: @space-md;
text-align: left;
@media screen and (max-width: @grid-float-breakpoint) {
padding-left: @space-lg;
padding-right: @space-lg;
padding-top: 0;
}
}
.address-identicon-container-right {
padding-right: 0;
padding-top: @space-md;
text-align: right;
.addressIdenticon {
float: right;
}
@media screen and (max-width: @grid-float-breakpoint) {
padding-left: @space-lg;
padding-right: @space-lg;
padding-top: 0;
}
}
.address-identicon-container-offline {
padding: 0;
margin-left: -15px;
}
.addressIdenticon {
width: 4rem;
height: 4rem;
background-size: cover;
background-repeat: no-repeat;
border-radius: 50%;
box-shadow: inset rgba(255, 255, 255, 0.5) 0 2px 2px,
inset rgba(0, 0, 0, 0.6) 0 -1px 8px;
}
.addressIdenticon.med {
width: 3rem;
height: 3rem;
}
.addressIdenticon.small {
width: 2rem;
height: 2rem;
}
.addressIdenticon.inline {
display: inline-block;
}
.addressIdenticon.float {
float: left;
margin-right: @space-sm;
}
// helpers
.wrap {
word-wrap: break-word;
}
.bigger-on-mobile.form-control[readonly] {
height: 60px;
@media screen and (max-width: 767px) {
height: 100px;
}
}
// help page
#paneHelp {
h3 {
margin-top: 2em;
}
}
// monospace things
.mono,
.form-control,
#accountAddress,
#accountBalance,
#accountBalanceUsd,
#accountBalanceEur,
#accountBalanceBtc,
#accountBalancePopMB-0,
#accountBalancePopMB-2,
#accountAddressMainTbl-1 {
font-family: @font-family-monospace;
font-weight: normal;
letter-spacing: .02em;
}
// QR Code on Offline Transactions Page
.offline-qrcode {
margin-top: -150px;
max-width: 300px;
@media screen and (max-width: 942px) {
margin-top: -78px;
}
@media screen and (max-width: 769px) {
margin-top: 0;
}
}
// collapsable containers
.collapse-container {
h2,
h4 {
cursor: pointer;
}
.collapse-button {
float: left;
font-weight: 500;
user-select: none;
padding: 10px;
margin: -10px -35px;
font-size: 24px;
line-height: 1.6;
}
}
// help collapsable containers
.help .collapse-container {
margin: 40px 0;
.collapse-button {
margin: -10px -30px;
font-size: 20px;
line-height: 1;
}
}
// little x image next to custom tokens
.token-remove {
width: @font-size-small;
height: @font-size-small;
position: absolute;
left: 18px;
cursor: pointer;
}
.node-remove {
width: 16px;
height: 16px;
position: absolute;
right: 6px;
top: 8px;
}
.m-addresses td:first-child {
max-width: 50px;
min-width: 50px;
}
.m-addresses td:last-child {
text-align: right;
}
h2 a.isActive {
color: #333;
cursor: default;
&:hover,
&:active {
color: #333;
cursor: default;
}
}
.item {
margin: 6px 0;
}
.output.well {
padding: @space-sm @space;
margin-top: -@space/2;
p {
margin: @space-sm 0;
}
}
label small {
color: @gray-light;
}
.write-address {
.col-xs-1 {
padding: 0;
}
.col-xs-11 {
padding-right: 10px;
}
}
.write-boolean label {
display: block;
}
.decrypt-drtv {
padding: @space 0;
label.radio {
padding: 0 @space-lg;
}
}
.qr-code {
max-width: 17rem;
margin: auto;
}
.qr-pkey-container {
position: relative;
}
.qr-overlay {
background-color: black;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.contracts {
h4 + .dropdown,
h4 + .dropdown .btn {
margin-top: 0;
}
a.isActive {
color: @text-color;
}
.dropdown-toggle {
display: block;
text-align: left;
white-space: normal;
.caret {
position: absolute;
right: @space;
top: 1.25rem;
}
}
.dropdown-menu-left {
font-size: @font-size-small;
left: auto;
right: 0;
min-width: 37rem;
small {
float: right;
}
}
}
.view-wallet-content {
@media screen and (min-width: @grid-float-breakpoint) {
padding-left: @space*3;
}
h5 {
margin-bottom: @space-xs;
}
a.isActive {
color: @text-color;
}
}
#selectedTypeLedger ol {
padding-left: 1rem;
}
.loading-wrap {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: fadeOut(black, 30%);
z-index: 999;
text-align: center;
.loading {
position: absolute;
text-align: center;
top: 40%;
left: 50%;
transform: translate(-50%, -60%);
}
img {
border-radius: 50%;
}
h1 {
animation: opacity 2000ms infinite ease-in-out;
color: white;
}
@keyframes opacity {
from,
to {
opacity: 0;
}
50% {
opacity: 1;
}
}
}
.embedded-logo {
height: auto;
width: 100%;
max-width: 290px;
padding: .5rem 0;
}
.ens-response {
color: @gray;
}

View File

@ -1,28 +0,0 @@
#etherWalletPopUp {
padding: 10px;
position: relative;
min-width: 346px;
.back-icon {
position: absolute;
left: 5px;
top: 5px;
&:hover,
&:active {
outline: 0;
}
}
}
.quicksend-address {
font-family: @font-family-monospace;
max-width: 170px;
word-wrap: break-word;
font-size: 12px;
font-weight: 300;
display: block;
}
.chrome-tokens span {
display: inline-block;
}

View File

@ -1,7 +0,0 @@
@import 'etherwallet-variables.less';
// Core variables and mixins
@import 'bootstrap/mixins.less';
// Utility classes
@import 'etherwallet-custom.less';
@import 'etherwallet-ext-custom.less';
@import 'etherwallet-utilities.less';

View File

@ -1,92 +0,0 @@
/* Custom Utilities */
.text-navy {
color: @ether-navy;
}
.text-gray-lighter {
color: @gray-lighter;
}
.text-blue {
color: @ether-blue;
}
.text-white {
color: #fff;
}
.bg-navy {
background: @ether-navy;
}
.bg-gray-lighter {
background-color: @gray-lighter;
}
.bg-blue {
background-color: @ether-blue;
}
.bg-white {
background-color: #fff;
}
.text-serif {
font-family: @font-family-serif;
}
.text-sans-serif {
font-family: @font-family-sans-serif;
}
.pad-v-sm {
padding-top: .5em;
padding-bottom: .5em;
}
.pad-v-md {
padding-top: 1em;
padding-bottom: 1em;
}
.pad-v-lg {
padding-top: 1.5em;
padding-bottom: 1.5em;
}
.pad-v-xl {
padding-top: 2em;
padding-bottom: 2em;
}
.marg-v-sm {
margin-top: .5em;
margin-bottom: .5em;
}
.marg-v-md {
margin-top: 1em;
margin-bottom: 1em;
}
.marg-v-lg {
margin-top: 1.5em;
margin-bottom: 1.5em;
}
.marg-v-xl {
margin-top: 2em;
margin-bottom: 2em;
}
.img-fill {
width: 100%;
height: auto;
max-width: 100%;
}
.no-scroll {
height: 100%;
overflow: hidden;
}

View File

@ -1,362 +0,0 @@
// Colors
@ether-navy: #163151;
@ether-blue: #0e97c0;
@space-xs: 0.25rem;
@space-sm: 0.5rem;
@space-md: 0.75rem;
@space: 1rem;
@space-lg: 1.5rem;
@space-xl: 2rem;
@gray-base: #000;
@gray-darker: lighten(@gray-base, 13.5%);
@gray-dark: lighten(@gray-base, 20%);
@gray: #737373;
@gray-light: #9a9a9a;
@gray-lighter: #ececec;
@gray-lightest: #fafafa;
@brand-primary: @ether-blue;
@brand-success: #5dba5a;
@brand-info: @ether-navy;
@brand-warning: #ff9800;
@brand-danger: #ea4b40;
@body-bg: #fff;
@text-color: @gray-dark;
@link-color: @brand-primary;
@link-hover-color: darken(@link-color, 5%);
@link-hover-decoration: none;
@transition: 500ms all ease-in-out;
// Typography
@font-family-sans-serif: 'Lato', sans-serif;
@font-family-serif: Georgia, 'Times New Roman', Times, serif;
@font-family-monospace: 'Roboto Mono', Menlo, Monaco, Consolas, 'Courier New', monospace;
@font-family-base: @font-family-sans-serif;
@base: 15;
@font-size-pixels: @base+px;
@font-size-pixels-xl: @base+1px; // for xl screens
@font-size-pixels-sm: @base+px; // for small screens
@font-size-large-bump: 2.25rem; // 33.75
@font-size-large: 1.9rem; // 28.5
@font-size-medium-bump: 1.5rem; // 22.5
@font-size-medium: 1.3rem; // 19.5
@font-size-bump-more: 1.15rem; // 17.25
@font-size-bump: 1.07rem; // 16.05
@font-size-base: 1rem; // 15
@font-size-small: 0.92rem; // 13.8
@font-size-xs: 0.8rem; // 12
@font-size-h1: @font-size-large-bump;
@font-size-h2: @font-size-large;
@font-size-h3: @font-size-medium-bump;
@font-size-h4: @font-size-medium;
@font-size-h5: @font-size-bump-more;
@font-size-h6: @font-size-bump;
@line-height-base: 1.4;
@line-height-computed: 1.4;
@headings-font-family: inherit;
@headings-font-weight: 700;
@headings-line-height: 1.2;
@headings-color: inherit;
// Spacing
@padding-base-vertical: @space*.6;
@padding-base-horizontal: @space;
@padding-large-vertical: @space-md;
@padding-large-horizontal: @space-xl;
@padding-small-vertical: 0.1rem;
@padding-small-horizontal: @space-sm;
@padding-xs-vertical: @space-xs;
@padding-xs-horizontal: 0.2rem;
@line-height-large: 1.2;
@line-height-small: 1.5;
@border-radius: 2px;
@component-active-color: #fff;
@component-active-bg: @brand-primary;
@caret-width-base: @space-xs;
@caret-width-large: @space-xs;
// Tables
@table-cell-padding: @space-sm;
@table-condensed-cell-padding: @space-xs;
@table-bg: transparent;
@table-bg-accent: #f9f9f9;
@table-bg-hover: @gray-lightest;
@table-bg-active: @table-bg-hover;
@table-border-color: #ddd;
// Buttons
@btn-font-weight: normal;
@btn-default-color: #333;
@btn-default-bg: #ececec;
@btn-default-border: @gray-lighter;
@btn-primary-color: #fff;
@btn-primary-bg: @brand-primary;
@btn-primary-border: darken(@btn-primary-bg, 5%);
@btn-success-color: #fff;
@btn-success-bg: @brand-success;
@btn-success-border: darken(@btn-success-bg, 5%);
@btn-info-color: #fff;
@btn-info-bg: @brand-info;
@btn-info-border: darken(@btn-info-bg, 5%);
@btn-warning-color: #fff;
@btn-warning-bg: @brand-warning;
@btn-warning-border: darken(@btn-warning-bg, 5%);
@btn-danger-color: #fff;
@btn-danger-bg: @brand-danger;
@btn-danger-border: darken(@btn-danger-bg, 5%);
@btn-link-disabled-color: @gray-light;
// Forms
@input-bg: #fff;
@input-bg-disabled: @gray-lightest;
@input-color: @gray;
@input-border: @gray-lighter;
@input-border-focus: @brand-primary;
@input-color-placeholder: darken(@gray-lighter, 10%);
@input-height-base: 2.55rem;
@input-height-large: 4rem;
@input-height-small: 2rem;
@form-group-margin-bottom: @space-sm;
@legend-color: @gray-dark;
@legend-border-color: #e5e5e5;
@input-group-addon-bg: @gray-lighter;
@input-group-addon-border-color: @input-border;
@cursor-disabled: default;
@dropdown-bg: #fff;
@dropdown-border: rgba(0, 0, 0, 0.15);
@dropdown-fallback-border: @gray-lighter;
@dropdown-divider-bg: #e5e5e5;
@dropdown-link-color: @ether-navy;
@dropdown-link-hover-color: @ether-blue;
@dropdown-link-hover-bg: @gray-lightest;
@dropdown-link-active-color: @component-active-color;
@dropdown-link-active-bg: @component-active-bg;
@dropdown-link-disabled-color: @gray-light;
@dropdown-header-color: @gray-light;
@dropdown-caret-color: #000;
@zindex-navbar: 1000;
@zindex-dropdown: 1000;
@zindex-popover: 1060;
@zindex-tooltip: 1070;
@zindex-navbar-fixed: 1030;
@zindex-modal-background: 1040;
@zindex-modal: 1050;
@zindex-alerts: 1060;
@screen-xs: 32rem;
@screen-xs-min: @screen-xs;
@screen-sm: 51.2rem;
@screen-sm-min: @screen-sm;
@screen-md: 66.133333333rem;
@screen-md-min: @screen-md;
@screen-lg: 80rem;
@screen-lg-min: @screen-lg;
@screen-xl: 94rem;
@screen-xl-min: @screen-xl;
@screen-xs-max: (@screen-sm-min - 1);
@screen-sm-max: (@screen-md-min - 1);
@screen-md-max: (@screen-lg-min - 1);
@screen-lg-max: (@screen-xl-min - 1);
@grid-columns: 12;
@grid-gutter-width: 3rem;
@grid-float-breakpoint: @screen-sm-min;
@grid-float-breakpoint-max: (@grid-float-breakpoint - 1);
@cont-padding: 5%;
@cont-padding-lg: 7.5%;
@container-tablet: (@screen-sm + @grid-gutter-width);
@container-sm: @container-tablet;
@container-desktop: (@screen-md + @grid-gutter-width);
@container-md: @container-desktop;
@container-large-desktop: (@screen-lg + @grid-gutter-width);
@container-lg: @container-large-desktop;
@state-success-text: darken(@brand-success, 10%);
@state-success-bg: #dff0d8;
@state-success-border: darken(spin(@state-success-bg, -10), 5%);
@state-info-text: darken(@brand-info, 10%);
@state-info-bg: #d9edf7;
@state-info-border: darken(spin(@state-info-bg, -10), 7%);
@state-warning-text: darken(@brand-warning, 10%);
@state-warning-bg: #fcf8e3;
@state-warning-border: darken(spin(@state-warning-bg, -10), 5%);
@state-danger-text: darken(@brand-danger, 10%);
@state-danger-bg: #f2dede;
@state-danger-border: darken(spin(@state-danger-bg, -10), 5%);
@tooltip-max-width: 200px;
@tooltip-color: #fff;
@tooltip-bg: #000;
@tooltip-opacity: 0.9;
@tooltip-arrow-width: @space-sm;
@tooltip-arrow-color: @tooltip-bg;
@label-default-bg: @gray-light;
@label-primary-bg: @brand-primary;
@label-success-bg: @brand-success;
@label-info-bg: @brand-info;
@label-warning-bg: @brand-warning;
@label-danger-bg: @brand-danger;
@label-color: #fff;
@label-link-hover-color: #fff;
@modal-inner-padding: @space*1.5;
@modal-title-padding: @space;
@modal-title-line-height: @line-height-base;
@modal-content-bg: #fff;
@modal-content-border-color: rgba(0, 0, 0, 0.2);
@modal-content-fallback-border-color: #999;
@modal-backdrop-bg: #000;
@modal-backdrop-opacity: 0.5;
@modal-header-border-color: #e5e5e5;
@modal-footer-border-color: @modal-header-border-color;
@modal-lg: 70rem;
@modal-md: 50rem;
@modal-sm: 30rem;
@alert-border-radius: @border-radius;
@alert-link-font-weight: bold;
@alert-success-bg: @brand-success;
@alert-success-text: white;
@alert-success-border: @alert-success-bg;
@alert-info-bg: @brand-primary;
@alert-info-text: white;
@alert-info-border: @alert-info-bg;
@alert-warning-bg: @brand-warning;
@alert-warning-text: white;
@alert-warning-border: @alert-warning-bg;
@alert-danger-bg: @brand-danger;
@alert-danger-text: white;
@alert-danger-border: @alert-danger-bg;
@progress-bg: @gray-lightest;
@progress-bar-color: #fff;
@progress-border-radius: @border-radius;
@progress-bar-bg: @brand-primary;
@progress-bar-success-bg: @brand-success;
@progress-bar-warning-bg: @brand-warning;
@progress-bar-danger-bg: @brand-danger;
@progress-bar-info-bg: @brand-info;
@list-group-bg: #fff;
@list-group-border: #ddd;
@list-group-border-radius: @border-radius;
@list-group-hover-bg: @gray-lightest;
@list-group-active-color: @component-active-color;
@list-group-active-bg: @component-active-bg;
@list-group-active-border: @list-group-active-bg;
@list-group-active-text-color: lighten(@list-group-active-bg, 40%);
@list-group-disabled-color: @gray-light;
@list-group-disabled-bg: @gray-lighter;
@list-group-disabled-text-color: @list-group-disabled-color;
@list-group-link-color: #555;
@list-group-link-hover-color: @list-group-link-color;
@list-group-link-heading-color: #333;
@thumbnail-padding: 4px;
@thumbnail-bg: @body-bg;
@thumbnail-border: #ddd;
@thumbnail-border-radius: @border-radius;
@thumbnail-caption-color: @text-color;
@thumbnail-caption-padding: 9px;
@well-bg: @gray-lightest;
@well-border: darken(@well-bg, 7%);
@badge-font-weight: bold;
@badge-line-height: 1;
@badge-border-radius: 10px;
@close-font-weight: bold;
@close-color: #000;
@close-text-shadow: 0 1px 0 #fff;
@code-color: #c7254e;
@code-bg: #f9f2f4;
@kbd-color: #fff;
@kbd-bg: #333;
@pre-bg: @gray-lightest;
@pre-color: @gray-dark;
@pre-border-color: @gray-lighter;
@pre-scrollable-max-height: 340px;
@component-offset-horizontal: 180px;
@text-muted: @gray-light;
@abbr-border-color: @gray-light;
@headings-small-color: inherit;
@blockquote-small-color: @gray-light;
@blockquote-font-size: (@font-size-base * 1.25);
@blockquote-border-color: @gray-lighter;
@page-header-border-color: @gray-lighter;
@dl-horizontal-offset: @component-offset-horizontal;
@hr-border: @gray-lighter;

View File

@ -13,7 +13,7 @@ export const AddressField: React.SFC<Props> = ({ isReadOnly }) => (
withProps={({ currentTo, isValid, onChange, readOnly }) => (
<div className="input-group-wrapper">
<label className="input-group">
<div className="input-group-header">{translate('SEND_addr')}</div>
<div className="input-group-header">{translate('SEND_ADDR_SHORT')}</div>
<Input
className={`input-group-input ${isValid ? '' : 'invalid'}`}
type="text"

View File

@ -1,7 +1,7 @@
import React from 'react';
import { AmountFieldFactory } from './AmountFieldFactory';
import { UnitDropDown } from 'components';
import translate, { translateRaw } from 'translations';
import translate from 'translations';
import { Input } from 'components/ui';
interface Props {
@ -18,14 +18,14 @@ export const AmountField: React.SFC<Props> = ({
<AmountFieldFactory
withProps={({ currentValue: { raw }, isValid, onChange, readOnly }) => (
<div className="input-group-wrapper">
<label className="input-group input-group-inline-dropdown">
<div className="input-group-header">{translate('SEND_amount')}</div>
<label className="input-group input-group-inline">
<div className="input-group-header">{translate('SEND_AMOUNT_SHORT')}</div>
<Input
className={`input-group-input ${
isAmountValid(raw, customValidator, isValid) ? '' : 'invalid'
}`}
type="number"
placeholder={translateRaw('SEND_amount_short')}
placeholder={'1'}
value={raw}
readOnly={!!readOnly}
onChange={onChange}
@ -37,5 +37,8 @@ export const AmountField: React.SFC<Props> = ({
/>
);
const isAmountValid = (raw, customValidator, isValid) =>
customValidator ? customValidator(raw) : isValid;
const isAmountValid = (
raw: string,
customValidator: ((rawAmount: string) => boolean) | undefined,
isValid: boolean
) => (customValidator ? customValidator(raw) : isValid);

View File

@ -98,7 +98,7 @@ class AccountInfo extends React.Component<Props, State> {
const wallet = this.props.wallet as LedgerWallet | TrezorWallet;
return (
<div className="AccountInfo">
<h5 className="AccountInfo-section-header">{translate('sidebar_AccountAddr')}</h5>
<h5 className="AccountInfo-section-header">{translate('SIDEBAR_ACCOUNTADDR')}</h5>
<div className="AccountInfo-section AccountInfo-address-section">
<div className="AccountInfo-address-icon">
<Identicon address={address} size="100%" />
@ -134,7 +134,9 @@ class AccountInfo extends React.Component<Props, State> {
});
}}
>
{confirmAddr ? null : 'Display address on ' + wallet.getWalletType()}
{confirmAddr
? null
: translate('SIDEBAR_DISPLAY_ADDR', { $wallet: wallet.getWalletType() })}
</a>
{confirmAddr ? (
<span className="AccountInfo-address-confirm">
@ -145,7 +147,7 @@ class AccountInfo extends React.Component<Props, State> {
)}
<div className="AccountInfo-section">
<h5 className="AccountInfo-section-header">{translate('sidebar_AccountBal')}</h5>
<h5 className="AccountInfo-section-header">{translate('SIDEBAR_ACCOUNTBAL')}</h5>
<ul className="AccountInfo-list">
<li className="AccountInfo-list-item AccountInfo-balance">
<span
@ -182,7 +184,7 @@ class AccountInfo extends React.Component<Props, State> {
{(!!blockExplorer || !!tokenExplorer) && (
<div className="AccountInfo-section">
<h5 className="AccountInfo-section-header">{translate('sidebar_TransHistory')}</h5>
<h5 className="AccountInfo-section-header">{translate('SIDEBAR_TRANSHISTORY')}</h5>
<ul className="AccountInfo-list">
{!!blockExplorer && (
<li className="AccountInfo-list-item">

View File

@ -8,7 +8,6 @@ import { chain, flatMap } from 'lodash';
import { TokenBalance, getShownTokenBalances } from 'selectors/wallet';
import { Balance } from 'libs/wallet';
import './EquivalentValues.scss';
import { Wei } from 'libs/units';
import { AppState } from 'reducers';
import { getNetworkConfig, getOffline } from 'selectors/config';
import { connect } from 'react-redux';
@ -16,6 +15,7 @@ import btcIco from 'assets/images/bitcoin.png';
import ethIco from 'assets/images/ether.png';
import repIco from 'assets/images/augur.png';
import { NetworkConfig } from 'types/network';
import BN from 'bn.js';
interface AllValue {
symbol: string;
@ -51,6 +51,14 @@ interface DispatchProps {
fetchCCRates: TFetchCCRatesRequested;
}
interface FiatSymbols {
[key: string]: string;
}
interface Rates {
[rate: string]: number;
}
type Props = StateProps & DispatchProps;
class EquivalentValues extends React.Component<Props, State> {
@ -110,7 +118,7 @@ class EquivalentValues extends React.Component<Props, State> {
}
}
public selectOption = equivalentValues => {
public selectOption = (equivalentValues: Option) => {
this.setState({ equivalentValues });
};
@ -120,28 +128,38 @@ class EquivalentValues extends React.Component<Props, State> {
const isFetching =
!balance || balance.isPending || !tokenBalances || Object.keys(rates).length === 0;
const pairRates = this.generateValues(equivalentValues.label, equivalentValues.value);
const fiatSymbols = {
const fiatSymbols: FiatSymbols = {
USD: '$',
EUR: '€',
GBP: '£',
CHF: ' '
};
const coinAndTokenSymbols = {
const coinAndTokenSymbols: any = {
BTC: btcIco,
ETH: ethIco,
REP: repIco
};
interface ValueProps {
className: string;
rate: string;
value: BN | null;
symbol?: string;
icon?: string;
key?: number | string;
}
const Value = ({ className = '', rate, value, symbol = '', icon = '' }) => (
<div className={`EquivalentValues-values-currency ${className}`}>
<img src={icon} />
{!!symbol && <span className="EquivalentValues-values-currency-fiat-symbol">{symbol}</span>}
<span className="EquivalentValues-values-currency-label">{rate}</span>{' '}
const Value = (props: ValueProps) => (
<div className={`EquivalentValues-values-currency ${props.className}`}>
<img src={props.icon} />
{!!props.symbol && (
<span className="EquivalentValues-values-currency-fiat-symbol">{props.symbol}</span>
)}
<span className="EquivalentValues-values-currency-label">{props.rate}</span>{' '}
<span className="EquivalentValues-values-currency-value">
<UnitDisplay
unit={'ether'}
value={value}
displayShortBalance={rateSymbols.isFiat(rate) ? 2 : 3}
value={props.value}
displayShortBalance={rateSymbols.isFiat(props.rate) ? 2 : 3}
checkOffline={true}
/>
</span>
@ -151,13 +169,13 @@ class EquivalentValues extends React.Component<Props, State> {
return (
<div className="EquivalentValues">
<div className="EquivalentValues-header">
<h5 className="EquivalentValues-title">{translate('sidebar_Equiv')}</h5>
<h5 className="EquivalentValues-title">{translate('SIDEBAR_EQUIV')}</h5>
<Select
name="equivalentValues"
// TODO: Update type
value={equivalentValues as any}
options={options as any}
onChange={this.selectOption}
onChange={this.selectOption as any}
clearable={false}
searchable={false}
/>
@ -165,13 +183,11 @@ class EquivalentValues extends React.Component<Props, State> {
{isOffline ? (
<div className="EquivalentValues-offline well well-sm">
Equivalent values are unavailable offline
{translate('EQUIV_VALS_OFFLINE')}
</div>
) : network.isTestnet ? (
<div className="text-center">
<h5 style={{ color: 'red' }}>
On test network, equivalent values will not be displayed.
</h5>
<h5 style={{ color: 'red' }}>{translate('EQUIV_VALS_TESTNET')}</h5>
</div>
) : ratesError ? (
<h5>{ratesError}</h5>
@ -210,7 +226,7 @@ class EquivalentValues extends React.Component<Props, State> {
)}
</React.Fragment>
) : (
<p>Sorry, equivalent values are not supported for this unit.</p>
<p>{translate('EQUIV_VALS_UNSUPPORTED_UNIT')}</p>
)}
</div>
)}
@ -224,7 +240,7 @@ class EquivalentValues extends React.Component<Props, State> {
const allRates = Object.values(balance).map(
value => !!rates[value.symbol] && rates[value.symbol]
);
const allEquivalentValues = allRates.map((rateType, i) => {
const allEquivalentValues = allRates.map((rateType: any, i) => {
return {
symbol: Object.keys(rates)[i],
equivalentValues: [
@ -260,9 +276,9 @@ class EquivalentValues extends React.Component<Props, State> {
// return equivalent value (unit * rate * balance)
private handleValues(unit: string, balance: Balance['wei']) {
const { rates } = this.props;
const ratesObj = { ...rates[unit] };
const ratesObj: Rates = { ...rates[unit] };
return Object.keys(ratesObj).map(key => {
const value = (balance as Wei).muln(ratesObj[key]);
const value = balance!.muln(ratesObj[key]);
return { rate: key, value };
});
}

View File

@ -2,10 +2,14 @@ import React from 'react';
import CoinbaseLogo from 'assets/images/logo-coinbase.svg';
import { NewTabLink } from 'components/ui';
export const Coinbase: React.SFC = () => (
interface Props {
address: string;
}
export const Coinbase: React.SFC<Props> = ({ address }) => (
<NewTabLink
className="Promos-promo Promos--coinbase"
href="https://buy.coinbase.com?code=60c05061-3a76-57be-b1cd-a7afa97bcb8c&address=0xA7DeFf12461661212734dB35AdE9aE7d987D648c&crypto_currency=ETH&currency=USD"
href={`https://buy.coinbase.com?code=60c05061-3a76-57be-b1cd-a7afa97bcb8c&address=${address}&crypto_currency=ETH&currency=USD`}
>
<div className="Promos-promo-inner">
<div className="Promos-promo-text">
@ -13,7 +17,7 @@ export const Coinbase: React.SFC = () => (
<h5 key="2">Buy ETH with USD</h5>
</div>
<div className="Promos-promo-images">
<img src={CoinbaseLogo} />
<img src={CoinbaseLogo} alt="Coinbase logo" />
</div>
</div>
</NewTabLink>

View File

@ -11,8 +11,8 @@ export const HardwareWallets: React.SFC = () => (
<h6>Learn more about protecting your funds.</h6>
</div>
<div className="Promos-promo-images">
<img src={ledgerLogo} />
<img src={trezorLogo} />
<img src={ledgerLogo} alt="Ledger Logo" />
<img src={trezorLogo} alt="Trezor Logo" />
</div>
</div>
</HelpLink>

View File

@ -13,7 +13,7 @@ export const Shapeshift: React.SFC = () => (
</h5>
</div>
<div className="Promos-promo-images">
<img src={ShapeshiftLogo} />
<img src={ShapeshiftLogo} alt="Shapeshift Logo" />
</div>
</div>
</Link>

View File

@ -85,7 +85,6 @@
height: 12px;
border: 3px solid $gray-lightest;
border-radius: 100%;
outline: none;
opacity: 0.6;
&.is-active {
@ -96,7 +95,7 @@
// Per-promo customizations
&--shapeshift {
background-color: #263A52;
background-color: #263a52;
.Promos-promo-images {
max-width: 130px;

View File

@ -2,21 +2,29 @@ import React from 'react';
import { TransitionGroup, CSSTransition } from 'react-transition-group';
import { HardwareWallets, Coinbase, Shapeshift } from './PromoComponents';
import './Promos.scss';
import { connect } from 'react-redux';
import { AppState } from '../../reducers';
const promos = [HardwareWallets, Coinbase, Shapeshift];
const CarouselAnimation = ({ children, ...props }) => (
const CarouselAnimation = ({ children, ...props }: any) => (
<CSSTransition {...props} timeout={300} classNames="carousel">
{children}
</CSSTransition>
);
// Don't change Coinbase index
const promos = [HardwareWallets, Coinbase, Shapeshift];
interface State {
activePromo: number;
}
export default class Promos extends React.PureComponent<{}, State> {
interface StateProps {
wallet: AppState['wallet']['inst'];
}
class PromosClass extends React.PureComponent<StateProps, State> {
public timer: any = null;
public promos = [HardwareWallets, Coinbase, Shapeshift];
public state = {
activePromo: parseInt(String(Math.random() * promos.length), 10)
@ -30,13 +38,27 @@ export default class Promos extends React.PureComponent<{}, State> {
clearInterval(this.timer);
}
public getPromo() {
const { activePromo } = this.state;
const { wallet } = this.props;
if (activePromo === 1) {
if (wallet) {
return <Coinbase address={wallet.getAddressString()} />;
} else {
return <Shapeshift />;
}
} else {
return promos[activePromo];
}
}
public render() {
const { activePromo } = this.state;
return (
<div className="Promos">
<TransitionGroup className="Promos-promo-wrapper">
<CarouselAnimation key={Math.random()}>{promos[activePromo]}</CarouselAnimation>
<CarouselAnimation key={Math.random()}>{this.getPromo()}</CarouselAnimation>
</TransitionGroup>
<div className="Promos-nav">
{promos.map((_, index) => {
@ -64,3 +86,11 @@ export default class Promos extends React.PureComponent<{}, State> {
this.setState({ activePromo });
};
}
function mapStateToProps(state: AppState): StateProps {
return {
wallet: state.wallet.inst
};
}
export default connect(mapStateToProps, {})(PromosClass);

View File

@ -1,4 +1,4 @@
@import "common/sass/variables";
@import 'common/sass/variables';
.AddCustom {
&-field {
@ -11,23 +11,20 @@
&-buttons {
padding-top: 10px;
display: flex;
justify-content: center;
flex-wrap: wrap;
&-help {
text-align: center;
display: block;
font-size: 13px;
margin-bottom: 10px;
}
&-btn {
margin-right: 10px;
&.btn-primary {
width: 120px;
}
&.btn-default {
width: 110px;
}
padding: 0.5rem 1.5rem;
margin: 0.25rem 0.5rem;
}
}
}

View File

@ -1,7 +1,7 @@
import React from 'react';
import { HELP_ARTICLE } from 'config';
import { isPositiveIntegerOrZero, isValidETHAddress } from 'libs/validators';
import translate from 'translations';
import translate, { translateRaw } from 'translations';
import { HelpLink, Input } from 'components/ui';
import './AddCustomTokenForm.scss';
import { Token } from 'types/network';
@ -47,17 +47,17 @@ export default class AddCustomTokenForm extends React.PureComponent<Props, State
{
name: 'symbol',
value: symbol,
label: translate('TOKEN_Symbol')
label: translateRaw('TOKEN_SYMBOL')
},
{
name: 'address',
value: address,
label: translate('TOKEN_Addr')
label: translateRaw('TOKEN_ADDR')
},
{
name: 'decimal',
value: decimal,
label: translate('TOKEN_Dec')
label: translateRaw('TOKEN_DEC')
}
];
@ -66,11 +66,11 @@ export default class AddCustomTokenForm extends React.PureComponent<Props, State
{fields.map(field => {
return (
<label className="AddCustom-field form-group" key={field.name}>
<span className="AddCustom-field-label">{field.label}</span>
<div className="input-group-header">{field.label}</div>
<Input
className={`${
errors[field.name] ? 'invalid' : field.value ? 'valid' : ''
} AddCustom-field-input input-sm`}
} input-group-input-small`}
type="text"
name={field.name}
value={field.value}
@ -83,21 +83,21 @@ export default class AddCustomTokenForm extends React.PureComponent<Props, State
);
})}
<HelpLink article={HELP_ARTICLE.ADDING_NEW_TOKENS} className="AddCustom-buttons-help">
{translate('ADD_CUSTOM_TKN_HELP')}
</HelpLink>
<div className="AddCustom-buttons">
<HelpLink article={HELP_ARTICLE.ADDING_NEW_TOKENS} className="AddCustom-buttons-help">
{translate('Need help? Learn how to add custom tokens.')}
</HelpLink>
<button
className="AddCustom-buttons-btn btn btn-primary btn-sm"
disabled={!this.isValid()}
>
{translate('x_Save')}
</button>
<button
className="AddCustom-buttons-btn btn btn-sm btn-default"
onClick={this.props.toggleForm}
>
{translate('x_Cancel')}
{translate('ACTION_2')}
</button>
<button
className="AddCustom-buttons-btn btn btn-primary btn-sm"
disabled={!this.isValid()}
>
{translate('X_SAVE')}
</button>
</div>
</form>

View File

@ -15,19 +15,23 @@ interface Props {
onRemoveCustomToken(symbol: string): any;
}
interface TrackedTokens {
[symbol: string]: boolean;
}
interface State {
trackedTokens: { [symbol: string]: boolean };
showCustomTokenForm: boolean;
}
export default class TokenBalances extends React.PureComponent<Props, State> {
public state = {
public state: State = {
trackedTokens: {},
showCustomTokenForm: false
};
public componentWillReceiveProps(nextProps: Props) {
if (nextProps.tokenBalances !== this.props.tokenBalances) {
const trackedTokens = nextProps.tokenBalances.reduce((prev, t) => {
const trackedTokens = nextProps.tokenBalances.reduce<TrackedTokens>((prev, t) => {
prev[t.symbol] = !t.balance.isZero();
return prev;
}, {});
@ -46,11 +50,9 @@ export default class TokenBalances extends React.PureComponent<Props, State> {
bottom = (
<div className="TokenBalances-buttons">
<button className="btn btn-primary btn-block" onClick={this.handleSetWalletTokens}>
<span>{translate('x_Save')}</span>
<span>{translate('X_SAVE')}</span>
</button>
<p className="TokenBalances-buttons-help">
{translate('Missing tokens? You can add custom tokens next.')}
</p>
<p className="TokenBalances-buttons-help">{translate('PROMPT_ADD_CUSTOM_TKN')}</p>
</div>
);
} else if (showCustomTokenForm) {
@ -67,10 +69,10 @@ export default class TokenBalances extends React.PureComponent<Props, State> {
bottom = (
<div className="TokenBalances-buttons">
<button className="btn btn-default btn-xs" onClick={this.toggleShowCustomTokenForm}>
<span>{translate('SEND_custom')}</span>
</button>{' '}
<span>{translate('SEND_CUSTOM')}</span>
</button>
<button className="btn btn-default btn-xs" onClick={this.props.scanWalletForTokens}>
<span>Scan for New Tokens</span>
<span>{translate('SCAN_TOKENS')}</span>
</button>
</div>
);
@ -101,7 +103,7 @@ export default class TokenBalances extends React.PureComponent<Props, State> {
</tbody>
</table>
) : (
<div className="well well-sm text-center">No tokens found</div>
<div className="well well-sm text-center">{translate('SCAN_TOKENS_FAIL_NO_TOKENS')}</div>
)}
{bottom}
</div>

View File

@ -54,6 +54,7 @@ export default class TokenRow extends React.PureComponent<Props, State> {
{!!custom && (
<img
src={removeIcon}
alt="Remove"
className="TokenRow-symbol-remove"
title="Remove Token"
onClick={this.onRemove}

View File

@ -1,4 +1,4 @@
@import "common/sass/variables";
@import 'common/sass/variables';
.TokenBalances {
&-title {
@ -34,6 +34,12 @@
}
&-buttons {
display: flex;
flex-wrap: wrap;
justify-content: center;
& > &-btn {
margin: 0.25rem 0.5rem;
}
&-help {
padding-top: 10px;
text-align: center;

View File

@ -74,7 +74,7 @@ class TokenBalances extends React.Component<Props> {
className="TokenBalances-scan btn btn-primary btn-block"
onClick={this.scanWalletForTokens}
>
{translate('Scan for my Tokens')}
{translate('SCAN_TOKENS')}
</button>
);
} else {
@ -95,7 +95,7 @@ class TokenBalances extends React.Component<Props> {
return (
<section className="TokenBalances">
<h5 className="TokenBalances-title">{translate('sidebar_TokenBal')}</h5>
<h5 className="TokenBalances-title">{translate('SIDEBAR_TOKENBAL')}</h5>
{content}
</section>
);

View File

@ -1,5 +1,5 @@
@import "common/sass/variables";
@import "common/sass/mixins";
@import 'common/sass/variables';
@import 'common/sass/mixins';
.BetaAgreement {
@include cover-message;
@ -20,7 +20,6 @@
margin: 0 auto;
border: none;
padding: 0;
outline: none;
transition: $transition;
&.is-continue {

View File

@ -7,6 +7,7 @@ import { AppState } from 'reducers';
import './Body.scss';
import { getNetworkConfig } from 'selectors/config';
import { NetworkConfig } from 'types/network';
import translate from 'translations';
interface State {
showDetails: boolean;
@ -43,7 +44,7 @@ class BodyClass extends React.Component<StateProps, State> {
}`}
onClick={this.toggleDetails}
>
Details
{translate('ACTION_8')}
</button>
{showDetails && <Details />}
</div>

View File

@ -8,6 +8,8 @@ import { connect } from 'react-redux';
import { SerializedTransaction } from 'components/renderCbs';
import { AppState } from 'reducers';
import { getFrom, getUnit, isEtherTransaction } from 'selectors/transaction';
import { toChecksumAddress } from 'ethereumjs-util';
import translate from 'translations';
interface StateProps {
from: AppState['transaction']['meta']['from'];
@ -24,7 +26,9 @@ class AddressesClass extends Component<StateProps> {
return (
<SerializedTransaction
withSerializedTransaction={(_, { to, data }) => {
const toFormatted = isToken ? ERC20.transfer.decodeInput(data)._to : to;
const toFormatted = toChecksumAddress(
isToken ? ERC20.transfer.decodeInput(data)._to : to
);
return (
<div className="tx-modal-address">
<div className="tx-modal-address-from">
@ -32,7 +36,7 @@ class AddressesClass extends Component<StateProps> {
<Identicon className="tx-modal-address-from-icon" size={size} address={from} />
)}
<div className="tx-modal-address-from-content">
<h5 className="tx-modal-address-from-title">From </h5>
<h5 className="tx-modal-address-from-title">{translate('CONFIRM_TX_FROM')} </h5>
<h5 className="tx-modal-address-from-address small">{from}</h5>
</div>
</div>
@ -42,7 +46,9 @@ class AddressesClass extends Component<StateProps> {
<img src={arrow} alt="arrow" />
</div>
<div className="tx-modal-address-tkn-contract-content">
<p className="tx-modal-address-tkn-contract-title">via the {unit} contract</p>
<p className="tx-modal-address-tkn-contract-title">
{translate('CONFIRM_TX_VIA_CONTRACT', { unit })}
</p>
<a
className="small tx-modal-address-tkn-contract-link"
href={ETHAddressExplorer(to)}
@ -59,7 +65,7 @@ class AddressesClass extends Component<StateProps> {
address={toFormatted}
/>
<div className="tx-modal-address-to-content">
<h5 className="tx-modal-address-to-title">To </h5>
<h5 className="tx-modal-address-to-title">{translate('CONFIRM_TX_TO')} </h5>
<h5 className="small tx-modal-address-to-address">{toFormatted}</h5>
</div>
</div>

View File

@ -7,6 +7,7 @@ import { SerializedTxParams, getParamsFromSerializedTx } from 'selectors/transac
import { connect } from 'react-redux';
import { getNetworkConfig } from 'selectors/config';
import { NetworkConfig } from 'types/network';
import translate from 'translations';
interface StateProps extends SerializedTxParams, AllUSDValues {
network: NetworkConfig;
@ -32,7 +33,7 @@ class AmountsClass extends Component<StateProps> {
<table className="tx-modal-amount">
<tbody>
<tr className="tx-modal-amount-send">
<td>You'll Send</td>
<td>{translate('CONFIRM_TX_SENDING')}</td>
<td>
<UnitDisplay
value={currentValue}
@ -54,7 +55,7 @@ class AmountsClass extends Component<StateProps> {
)}
</tr>
<tr className="tx-modal-amount-fee">
<td>Transaction Fee</td>
<td>{translate('CONFIRM_TX_FEE')}</td>
<td>
<UnitDisplay
value={fee}
@ -77,7 +78,7 @@ class AmountsClass extends Component<StateProps> {
</tr>
{!isToken && (
<tr className="tx-modal-amount-total">
<td>Total</td>
<td>{translate('CONFIRM_TX_TOTAL')}</td>
<td>
<UnitDisplay
value={total}

View File

@ -11,9 +11,9 @@ import {
TBroadcastWeb3TransactionRequested
} from 'actions/transaction';
import { currentTransactionBroadcasting } from 'selectors/transaction';
import { translateRaw } from 'translations';
import './ConfirmationModalTemplate.scss';
import { AppState } from 'reducers';
import { translateRaw, translate } from 'translations';
interface DispatchProps {
broadcastLocalTransactionRequested: TBroadcastLocalTransactionRequested;
@ -73,7 +73,7 @@ class ConfirmationModalTemplateClass extends React.Component<Props, State> {
const { timeToRead } = this.state;
const buttonPrefix = timeToRead > 0 ? `(${timeToRead}) ` : '';
const defaultConfirmButton = {
text: buttonPrefix + translateRaw('SENDModal_Yes'),
text: buttonPrefix + translateRaw('ACTION_11'),
type: 'primary' as IButton['type'],
disabled: timeToRead > 0,
onClick: this.confirm
@ -85,7 +85,7 @@ class ConfirmationModalTemplateClass extends React.Component<Props, State> {
timeLeft: timeToRead,
timePrefix: buttonPrefix,
timeLocked: defaultConfirmButton.disabled,
defaultText: translateRaw('SENDModal_Yes'),
defaultText: translateRaw('ACTION_11'),
type: defaultConfirmButton.type
})
: defaultConfirmButton;
@ -93,7 +93,7 @@ class ConfirmationModalTemplateClass extends React.Component<Props, State> {
const buttons: IButton[] = [
confirmButton,
{
text: translateRaw('SENDModal_No'),
text: translate('ACTION_2'),
type: 'default',
onClick: onClose
}
@ -101,7 +101,7 @@ class ConfirmationModalTemplateClass extends React.Component<Props, State> {
return (
<Modal
title="Confirm Transaction"
title={translateRaw('CONFIRM_TX_MODAL_TITLE')}
buttons={buttons}
handleClose={onClose}
disableButtons={transactionBroadcasting}

View File

@ -9,7 +9,7 @@ export const DataField: React.SFC<{}> = () => (
withProps={({ data: { raw }, dataExists, onChange, readOnly }) => (
<div className="input-group-wrapper">
<label className="input-group">
<div className="input-group-header">{translate('OFFLINE_Step2_Label_6')}</div>
<div className="input-group-header">{translate('OFFLINE_STEP2_LABEL_6')}</div>
<Input
className={dataExists ? 'is-valid' : 'is-invalid'}
type="text"

View File

@ -1,26 +1,33 @@
import React from 'react';
import { translateRaw } from 'translations';
import { Link } from 'react-router-dom';
import translate from 'translations';
import { NewTabLink } from 'components/ui';
import { BlockExplorerConfig } from 'types/network';
export interface TransactionSucceededProps {
txHash: string;
blockExplorer: BlockExplorerConfig;
blockExplorer?: BlockExplorerConfig;
}
const TransactionSucceeded = ({ txHash, blockExplorer }: TransactionSucceededProps) => {
const txHashLink = blockExplorer.txUrl(txHash);
let verifyBtn: React.ReactElement<string> | undefined;
if (blockExplorer) {
verifyBtn = (
<NewTabLink className="btn btn-xs" href={blockExplorer.txUrl(txHash)}>
{translate('VERIFY_TX', { $block_explorer: blockExplorer.name })}
</NewTabLink>
);
}
return (
<div>
<p>{translateRaw('SUCCESS_3') + txHash}</p>
<a
className="btn btn-xs btn-info string"
href={txHashLink}
target="_blank"
rel="noopener noreferrer"
>
Verify Transaction
</a>
<p>
{translate('SUCCESS_3')} {txHash}
</p>
{verifyBtn}
<Link to={`/tx-status?txHash=${txHash}`} className="btn btn-xs">
{translate('NAV_CheckTxStatus')}
</Link>
</div>
);
};

View File

@ -3,6 +3,7 @@ import Modal, { IButton } from 'components/ui/Modal';
import { HelpLink } from 'components/ui';
import { HELP_ARTICLE } from 'config';
import './DisclaimerModal.scss';
import translate from 'translations';
interface Props {
isOpen: boolean;
@ -10,7 +11,9 @@ interface Props {
}
const DisclaimerModal: React.SFC<Props> = ({ isOpen, handleClose }) => {
const buttons: IButton[] = [{ text: 'Okay', type: 'default', onClick: handleClose }];
const buttons: IButton[] = [
{ text: translate('ACTION_10'), type: 'default', onClick: handleClose }
];
return (
<Modal isOpen={isOpen} title="Disclaimer" buttons={buttons} handleClose={handleClose}>
<p>
@ -41,7 +44,11 @@ const DisclaimerModal: React.SFC<Props> = ({ isOpen, handleClose }) => {
English and, because of this, the English version of our website is the official text.
</p>
<p>
<b>MIT License</b> Copyright © 2015-2017 MyCrypto LLC
<b>MIT License</b>
<br />
Copyright (c) 2015-2017 MyEtherWallet LLC
<br />
Copyright (c) 2018 MyCrypto, Inc.
</p>
<p>
Permission is hereby granted, free of charge, to any person obtaining a copy of this

View File

@ -1,5 +1,6 @@
import React from 'react';
import './PreFooter.scss';
import translate from 'translations';
interface Props {
openModal(): void;
@ -10,9 +11,8 @@ const PreFooter: React.SFC<Props> = ({ openModal }) => {
<section className="pre-footer">
<div className="container">
<p>
MyCrypto.com does not hold your keys for you. We cannot access accounts, recover keys,
reset passwords, nor reverse transactions. Protect your keys & always check that you are
on correct URL. <a onClick={openModal}>You are responsible for your security.</a>
{translate('PREFOOTER_WARNING')}{' '}
<a onClick={openModal}>{translate('PREFOOTER_SECURITY_WARNING')}</a>
</p>
</div>
</section>

View File

@ -14,6 +14,7 @@ import DisclaimerModal from './DisclaimerModal';
import { NewTabLink } from 'components/ui';
import OnboardModal from 'containers/OnboardModal';
import './index.scss';
import { translateRaw } from 'translations';
const SocialMediaLink = ({ link, text }: Link) => {
return (
@ -58,39 +59,39 @@ const PRODUCT_INFO: Link[] = [
{
link:
'https://chrome.google.com/webstore/detail/etheraddresslookup/pdknmigbbbhmllnmgdfalmedcmcefdfn',
text: 'Ether Address Lookup'
text: translateRaw('ETHER_ADDRESS_LOOKUP')
},
{
link:
'https://chrome.google.com/webstore/detail/ethersecuritylookup/bhhfhgpgmifehjdghlbbijjaimhmcgnf',
text: 'Ether Security Lookup'
text: translateRaw('ETHER_SECURITY_LOOKUP')
},
{
link: 'https://etherscamdb.info/',
text: 'EtherScamDB'
text: translateRaw('ETHERSCAMDB')
},
{
link: 'https://www.mycrypto.com/helpers.html',
text: 'Helpers & ENS Debugging'
text: translateRaw('FOOTER_HELP_AND_DEBUGGING')
},
{
link: 'mailto:press@mycrypto.com',
text: 'Press Inquiries'
text: translateRaw('FOOTER_PRESS')
}
];
const AFFILIATES: Link[] = [
{
link: ledgerReferralURL,
text: 'Buy a Ledger Wallet'
text: translateRaw('LEDGER_REFERAL_1')
},
{
link: trezorReferralURL,
text: 'Buy a TREZOR'
text: translateRaw('TREZOR_REFERAL')
},
{
link: ethercardReferralURL,
text: 'Get an ether.card'
text: translateRaw('ETHERCARD_REFERAL')
}
];
@ -165,51 +166,52 @@ export default class Footer extends React.PureComponent<Props, State> {
<div className="Footer-about-links">
<a href="https://mycrypto.com">MyCrypto.com</a>
<NewTabLink href={knowledgeBaseURL}>Help & Support</NewTabLink>
<NewTabLink href="https://about.mycrypto.com">Our Team</NewTabLink>
<NewTabLink href={knowledgeBaseURL}>{translateRaw('FOOTER_SUPPORT')}</NewTabLink>
<NewTabLink href="https://about.mycrypto.com">
{translateRaw('FOOTER_TEAM')}
</NewTabLink>
</div>
<p className="Footer-about-text">
MyCrypto is an open-source, client-side tool for generating Ether Wallets, handling
ERC-20 tokens, and interacting with the blockchain more easily. Developed by and for
the community since 2015, were focused on building awesome products that put the
power in peoples hands.
</p>
<p className="Footer-about-text">{translateRaw('FOOTER_ABOUT')}</p>
<div className="Footer-about-legal">
<div className="Footer-about-legal-text">
© {new Date().getFullYear()} MyCrypto, Inc.
</div>
<div className="Footer-about-legal-text">
<a onClick={this.toggleModal}>Disclaimer</a>
<a onClick={this.toggleModal}>{translateRaw('DISCLAIMER')}</a>
</div>
<div className="Footer-about-legal-text">v{VERSION}</div>
</div>
</div>
<div className="Footer-support Footer-section">
<h5 className="Footer-support-title">Support Us & Our Friends</h5>
<h5 className="Footer-support-title">{translateRaw('FOOTER_AFFILIATE_TITLE')}</h5>
<div className="Footer-support-affiliates">
{AFFILIATES.map(link => (
<NewTabLink key={link.text} href={link.link}>
{AFFILIATES.map((link, i) => (
<NewTabLink key={i} href={link.link}>
{link.text}
</NewTabLink>
))}
</div>
<div className="Footer-support-donate">
<div className="Footer-support-donate-currency">Donate ETH</div>
<div className="Footer-support-donate-currency">
{translateRaw('DONATE_CURRENCY', { $currency: 'ETH' })}
</div>
<div className="Footer-support-donate-address">{donationAddressMap.ETH}</div>
</div>
<div className="Footer-support-donate">
<div className="Footer-support-donate-currency">Donate BTC</div>
<div className="Footer-support-donate-currency">
{translateRaw('DONATE_CURRENCY', { $currency: 'BTC' })}
</div>
<div className="Footer-support-donate-address">{donationAddressMap.BTC}</div>
</div>
<div className="Footer-support-friends">
{FRIENDS.map(link => (
<NewTabLink key={link.text} href={link.link}>
{FRIENDS.map((link, i) => (
<NewTabLink key={i} href={link.link}>
{link.text}
</NewTabLink>
))}

View File

@ -17,14 +17,14 @@ export const GasLimitField: React.SFC<Props> = ({ customLabel, disabled }) => (
<div className="input-group-wrapper AdvancedGas-gas-price">
<label className="input-group">
<div className="input-group-header">
{customLabel ? customLabel : translate('TRANS_gas')}
{customLabel ? customLabel : translate('TRANS_GAS')}
<div className="flex-spacer" />
<InlineSpinner active={gasEstimationPending} text="Calculating" />
</div>
<Input
className={gasLimitValidator(raw) ? 'is-valid' : 'is-invalid'}
type="number"
placeholder="e.g. 21000"
placeholder="21000"
readOnly={!!readOnly}
value={raw}
onChange={onChange}

View File

@ -13,7 +13,7 @@ interface StateProps {
}
interface OwnProps {
withProps(props: CallBackProps);
withProps(props: CallBackProps): null | React.ReactElement<any>;
onChange(value: React.FormEvent<HTMLInputElement>): void;
}

View File

@ -41,9 +41,9 @@ export default class GenerateKeystoreModal extends React.Component<Props, State>
}
}
public componentWillReceiveProps(nextProps) {
public componentWillReceiveProps(nextProps: Props) {
if (nextProps.privateKey !== this.props.privateKey) {
this.setState({ privateKey: nextProps.privateKey });
this.setState({ privateKey: nextProps.privateKey || '' });
}
}
@ -55,14 +55,14 @@ export default class GenerateKeystoreModal extends React.Component<Props, State>
return (
<Modal
title={translate('Generate Keystore File')}
title={translateRaw('GENERATE_KEYSTORE_ACTION')}
isOpen={this.props.isOpen}
handleClose={this.handleClose}
>
<form className="GenKeystore" onSubmit={this.handleSubmit}>
<div className="input-group-wrapper GenKeystore-field">
<label className="input-group input-group-inline-dropdown">
<div className="input-group-header">Private Key</div>
<label className="input-group input-group-inline">
<div className="input-group-header">{translate('X_PRIVKEY2')}</div>
<TogglablePassword
name="privateKey"
value={privateKey}
@ -74,13 +74,15 @@ export default class GenerateKeystoreModal extends React.Component<Props, State>
</label>
</div>
<div className="input-group-wrapper GenKeystore-field">
<label className="input-group input-group-inline-dropdown">
<div className="input-group-header">Password</div>
<label className="input-group input-group-inline">
<div className="input-group-header">{translate('INPUT_PASSWORD_LABEL')}</div>
<TogglablePassword
name="password"
value={password}
onChange={this.handleInput}
placeholder={translateRaw('Minimum 9 characters')}
placeholder={translateRaw('INPUT_PASSWORD_PLACEHOLDER', {
$pass_length: MINIMUM_PASSWORD_LENGTH.toString()
})}
isValid={isPasswordValid}
/>
</label>
@ -91,24 +93,20 @@ export default class GenerateKeystoreModal extends React.Component<Props, State>
className="GenKeystore-button btn btn-primary btn-block"
disabled={!isPrivateKeyValid || !isPasswordValid}
>
{translate('Generate Keystore File')}
{translate('GENERATE_KEYSTORE_ACTION')}
</button>
) : hasError ? (
<p className="alert alert-danger">
Keystore generation failed or was invalid. In order to prevent loss of funds, we
cannot provide you with a keystore file that may be corrupted. Refresh the page or use
a different browser, and try again.
</p>
<p className="alert alert-danger">{translate('GENERATE_KEYSTORE_FAILED')}</p>
) : (
<a
onClick={this.handleClose}
href={keystoreFile.blob}
className="GenKeystore-button btn btn-success btn-block"
aria-label={translateRaw('x_Keystore')}
aria-describedby={translateRaw('x_KeystoreDesc')}
aria-label={translateRaw('X_KEYSTORE')}
aria-describedby={translateRaw('X_KEYSTOREDESC')}
download={keystoreFile.filename}
>
{translate('Download Keystore File')}
{translate('ACTION_12')}
</a>
)}
</form>

View File

@ -6,7 +6,7 @@ export const GenerateTransaction: React.SFC<{}> = () => (
<GenerateTransactionFactory
withProps={({ disabled, isWeb3Wallet, onClick }) => (
<button disabled={disabled} className="btn btn-info btn-block" onClick={onClick}>
{isWeb3Wallet ? translate('SEND_generate') : translate('DEP_signtx')}
{isWeb3Wallet ? translate('SEND_GENERATE') : translate('DEP_SIGNTX')}
</button>
)}
/>

View File

@ -0,0 +1,15 @@
.CustomNodeModal {
.flex-wrapper {
margin: 0px -8px;
> .input-group {
margin: 0px 8px;
> .input-group-input {
width: 100%;
}
}
}
input[type='checkbox'] {
margin-right: 1rem;
margin-bottom: 1rem;
}
}

View File

@ -1,6 +1,6 @@
import React from 'react';
import Modal, { IButton } from 'components/ui/Modal';
import translate from 'translations';
import translate, { translateRaw } from 'translations';
import { CustomNetworkConfig } from 'types/network';
import { CustomNodeConfig } from 'types/node';
import { TAddCustomNetwork, addCustomNetwork, AddCustomNodeAction } from 'actions/config';
@ -13,19 +13,13 @@ import {
} from 'selectors/config';
import { CustomNode } from 'libs/nodes';
import { Input } from 'components/ui';
import Dropdown from 'components/ui/Dropdown';
import './CustomNodeModal.scss';
const CUSTOM = 'custom';
interface InputProps {
name: string;
placeholder?: string;
type?: string;
autoComplete?: 'off';
onFocus?(): void;
onBlur?(): void;
}
const CUSTOM = { label: 'Custom', value: 'custom' };
interface OwnProps {
isOpen: boolean;
addCustomNode(payload: AddCustomNodeAction['payload']): void;
handleClose(): void;
}
@ -55,7 +49,7 @@ interface State {
type Props = OwnProps & StateProps & DispatchProps;
class CustomNodeModal extends React.Component<Props, State> {
public state: State = {
public INITIAL_STATE = {
name: '',
url: '',
network: Object.keys(this.props.staticNetworks)[0],
@ -66,9 +60,17 @@ class CustomNodeModal extends React.Component<Props, State> {
username: '',
password: ''
};
public state: State = this.INITIAL_STATE;
public componentDidUpdate(prevProps: Props) {
// Reset state when modal opens
if (!prevProps.isOpen && prevProps.isOpen !== this.props.isOpen) {
this.setState(this.INITIAL_STATE);
}
}
public render() {
const { customNetworks, handleClose, staticNetworks } = this.props;
const { customNetworks, handleClose, staticNetworks, isOpen } = this.props;
const { network } = this.state;
const isHttps = window.location.protocol.includes('https');
const invalids = this.getInvalids();
@ -82,164 +84,157 @@ class CustomNodeModal extends React.Component<Props, State> {
},
{
type: 'default',
text: translate('x_Cancel'),
text: translate('ACTION_2'),
onClick: handleClose
}
];
const conflictedNode = this.getConflictedNode();
const staticNetwrks = Object.keys(staticNetworks).map(net => {
return { label: net, value: net };
});
const customNetwrks = Object.entries(customNetworks).map(([id, net]) => {
return { label: net.name + ' (Custom)', value: id };
});
const options = [...staticNetwrks, ...customNetwrks, CUSTOM];
return (
<Modal
title={translate('NODE_Title')}
isOpen={true}
title={translateRaw('NODE_Title')}
isOpen={isOpen}
buttons={buttons}
handleClose={handleClose}
maxWidth={580}
>
<div>
{isHttps && <div className="alert alert-warning small">{translate('NODE_Warning')}</div>}
{isHttps && <div className="alert alert-warning small">{translate('NODE_WARNING')}</div>}
{conflictedNode && (
<div className="alert alert-warning small">
You already have a node called '{conflictedNode.name}' that matches this one, saving
this will overwrite it
{conflictedNode && (
<div className="alert alert-warning small">
{translate('CUSTOM_NODE_CONFLICT', { conflictedNode: conflictedNode.name })}
</div>
)}
<form className="CustomNodeModal">
<div className="flex-wrapper">
<label className="col-sm-9 input-group flex-grow-1">
<div className="input-group-header">{translate('CUSTOM_NODE_NAME')}</div>
<Input
className={`input-group-input ${this.state.name && invalids.name ? 'invalid' : ''}`}
type="text"
placeholder="My Node"
value={this.state.name}
onChange={e => this.setState({ name: e.currentTarget.value })}
/>
</label>
<label className="col-sm-3 input-group">
<div className="input-group-header">Network</div>
<Dropdown
className="input-group-dropdown"
value={network}
options={options}
clearable={false}
onChange={(e: { label: string; value: string }) =>
this.setState({ network: e.value })
}
/>
</label>
</div>
{network === CUSTOM.value && (
<div className="flex-wrapper">
<label className="col-sm-6 input-group input-group-inline">
<div className="input-group-header">{translate('CUSTOM_NETWORK_NAME')}</div>
<Input
className={`input-group-input ${
this.state.customNetworkId && invalids.customNetworkId ? 'invalid' : ''
}`}
type="text"
placeholder="My Custom Network"
value={this.state.customNetworkId}
onChange={e => this.setState({ customNetworkId: e.currentTarget.value })}
/>
</label>
<label className="col-sm-3 input-group input-group-inline">
<div className="input-group-header">{translate('CUSTOM_NETWORK_CURRENCY')}</div>
<Input
className={`input-group-input ${
this.state.customNetworkUnit && invalids.customNetworkUnit ? 'invalid' : ''
}`}
type="text"
placeholder="ETH"
value={this.state.customNetworkUnit}
onChange={e => this.setState({ customNetworkUnit: e.currentTarget.value })}
/>
</label>
<label className="col-sm-3 input-group input-group-inline">
<div className="input-group-header">{translate('CUSTOM_NETWORK_CHAIN_ID')}</div>
<Input
className={`input-group-input ${
this.state.customNetworkChainId && invalids.customNetworkChainId
? 'invalid'
: ''
}`}
type="text"
placeholder="1"
value={this.state.customNetworkChainId}
onChange={e => this.setState({ customNetworkChainId: e.currentTarget.value })}
/>
</label>
</div>
)}
<form>
<div className="row">
<div className="col-sm-7">
<label>{translate('NODE_Name')}</label>
{this.renderInput(
{
name: 'name',
placeholder: 'My Node'
},
invalids
)}
</div>
<div className="col-sm-5">
<label>Network</label>
<select
className="form-control"
name="network"
value={network}
onChange={this.handleChange}
>
{Object.keys(staticNetworks).map(net => (
<option key={net} value={net}>
{net}
</option>
))}
{Object.entries(customNetworks).map(([id, net]) => (
<option key={id} value={id}>
{net.name} (Custom)
</option>
))}
<option value={CUSTOM}>Custom...</option>
</select>
</div>
</div>
<label className="input-group input-group-inline">
<div className="input-group-header">{translate('CUSTOM_NETWORK_URL')}</div>
<Input
className={`input-group-input ${this.state.url && invalids.url ? 'invalid' : ''}`}
type="text"
placeholder="https://127.0.0.1:8545/"
value={this.state.url}
onChange={e => this.setState({ url: e.currentTarget.value })}
autoComplete="off"
/>
</label>
{network === CUSTOM && (
<div className="row">
<div className="col-sm-6">
<label className="is-required">Network Name</label>
{this.renderInput(
{
name: 'customNetworkId',
placeholder: 'My Custom Network'
},
invalids
)}
</div>
<div className="col-sm-3">
<label className="is-required">Currency</label>
{this.renderInput(
{
name: 'customNetworkUnit',
placeholder: 'ETH'
},
invalids
)}
</div>
<div className="col-sm-3">
<label>Chain ID</label>
{this.renderInput(
{
name: 'customNetworkChainId',
placeholder: 'e.g. 1'
},
invalids
)}
</div>
</div>
)}
<label>
<input
type="checkbox"
name="hasAuth"
checked={this.state.hasAuth}
onChange={() => this.setState({ hasAuth: !this.state.hasAuth })}
/>
<span>{translate('CUSTOM_NETWORK_HTTP_AUTH')}</span>
</label>
<div className="row">
<div className="col-sm-12">
<label>URL</label>
{this.renderInput(
{
name: 'url',
placeholder: 'e.g. https://127.0.0.1:8545/',
autoComplete: 'off'
},
invalids
)}
</div>
{this.state.hasAuth && (
<div className="flex-wrapper ">
<label className="col-sm-6 input-group input-group-inline">
<div className="input-group-header">{translate('INPUT_USERNAME_LABEL')}</div>
<Input
className={`input-group-input ${
this.state.username && invalids.username ? 'invalid' : ''
}`}
type="text"
value={this.state.username}
onChange={e => this.setState({ username: e.currentTarget.value })}
/>
</label>
<label className="col-sm-6 input-group input-group-inline">
<div className="input-group-header">{translate('INPUT_PASSWORD_LABEL')}</div>
<Input
className={`input-group-input ${
this.state.password && invalids.password ? 'invalid' : ''
}`}
type="password"
value={this.state.password}
onChange={e => this.setState({ password: e.currentTarget.value })}
/>
</label>
</div>
<div className="row">
<div className="col-sm-12">
<label>
<input
type="checkbox"
name="hasAuth"
checked={this.state.hasAuth}
onChange={this.handleCheckbox}
/>{' '}
<span>HTTP Basic Authentication</span>
</label>
</div>
</div>
{this.state.hasAuth && (
<div className="row">
<div className="col-sm-6">
<label className="is-required">Username</label>
{this.renderInput({ name: 'username' }, invalids)}
</div>
<div className="col-sm-6">
<label className="is-required">Password</label>
{this.renderInput(
{
name: 'password',
type: 'password'
},
invalids
)}
</div>
</div>
)}
</form>
</div>
)}
</form>
</Modal>
);
}
private renderInput(input: InputProps, invalids: { [key: string]: boolean }) {
return (
<Input
className={`${this.state[input.name] && invalids[input.name] ? 'invalid' : ''}`}
value={this.state[input.name]}
onChange={this.handleChange}
autoComplete="off"
{...input}
/>
);
}
private getInvalids(): { [key: string]: boolean } {
const {
url,
@ -278,7 +273,7 @@ class CustomNodeModal extends React.Component<Props, State> {
}
// If they have a custom network, make sure info is provided
if (network === CUSTOM) {
if (network === CUSTOM.value) {
if (!customNetworkId) {
invalids.customNetworkId = true;
}
@ -315,7 +310,7 @@ class CustomNodeModal extends React.Component<Props, State> {
const { network, url, name, username, password } = this.state;
const networkId =
network === CUSTOM
network === CUSTOM.value
? this.makeCustomNetworkId(this.makeCustomNetworkConfigFromState())
: network;
@ -348,20 +343,10 @@ class CustomNodeModal extends React.Component<Props, State> {
return customNodes[config.id];
}
private handleChange = (ev: React.FormEvent<HTMLInputElement | HTMLSelectElement>) => {
const { name, value } = ev.currentTarget;
this.setState({ [name as any]: value });
};
private handleCheckbox = (ev: React.FormEvent<HTMLInputElement>) => {
const { name } = ev.currentTarget;
this.setState({ [name as any]: !this.state[name as keyof State] });
};
private saveAndAdd = () => {
const node = this.makeCustomNodeConfigFromState();
if (this.state.network === CUSTOM) {
if (this.state.network === CUSTOM.value) {
const network = this.makeCustomNetworkConfigFromState();
this.props.addCustomNetwork({ config: network, id: node.network });

View File

@ -11,19 +11,19 @@ export interface TabLink {
const tabs: TabLink[] = [
{
name: 'Account View & Send',
name: 'NAV_VIEW',
to: '/account'
},
{
name: 'NAV_GenerateWallet',
name: 'NAV_GENERATEWALLET',
to: '/generate'
},
{
name: 'NAV_Swap',
name: 'NAV_SWAP',
to: '/swap'
},
{
name: 'NAV_Contracts',
name: 'NAV_CONTRACTS',
to: '/contracts'
},
{
@ -31,19 +31,19 @@ const tabs: TabLink[] = [
to: '/ens'
},
{
name: 'Sign & Verify Message',
name: 'NAV_SIGN',
to: '/sign-and-verify-message'
},
{
name: 'NAV_TxStatus',
name: 'NAV_TXSTATUS',
to: '/tx-status'
},
{
name: 'Broadcast Transaction',
name: 'NAV_BROADCAST',
to: '/pushTx'
},
{
name: 'NAV_Help',
name: 'NAV_HELP',
to: `${knowledgeBaseURL}`,
external: true
}

View File

@ -1,4 +1,4 @@
@import "common/sass/variables";
@import 'common/sass/variables';
.NavigationLink {
display: inline-block;
@ -14,7 +14,7 @@
min-height: 2.75rem;
&:after {
content: "";
content: '';
background: $brand-primary;
height: 2px;
width: 100%;
@ -45,13 +45,13 @@
}
}
#NAV_Swap a:before {
content:"";
#NAV_SWAP a:before {
content: '';
display: inline-block;
margin-top: -.1rem;
margin-top: -0.1rem;
width: 1.3rem;
height: 1.3rem;
background-image: url('~assets/images/swap.svg');
background-image: url('~assets/images/logo-shapeshift-no-text.svg');
background-position: center;
background-repeat: no-repeat;
background-size: contain;

View File

@ -192,12 +192,11 @@ class Header extends Component<Props, State> {
<Navigation color={!network.isCustom && network.color} />
{isAddingCustomNode && (
<CustomNodeModal
addCustomNode={this.addCustomNode}
handleClose={this.closeCustomNodeModal}
/>
)}
<CustomNodeModal
isOpen={isAddingCustomNode}
addCustomNode={this.addCustomNode}
handleClose={this.closeCustomNodeModal}
/>
</div>
);
}

View File

@ -4,6 +4,7 @@ import { withRouter, RouteComponentProps } from 'react-router-dom';
import Modal, { IButton } from 'components/ui/Modal';
import { AppState } from 'reducers';
import { resetWallet, TResetWallet } from 'actions/wallet';
import translate, { translateRaw } from 'translations';
interface Props extends RouteComponentProps<{}> {
// State
@ -18,7 +19,7 @@ interface State {
}
class LogOutPromptClass extends React.Component<Props, State> {
constructor(props) {
constructor(props: Props) {
super(props);
this.state = {
nextLocation: null,
@ -42,17 +43,17 @@ class LogOutPromptClass extends React.Component<Props, State> {
public render() {
const buttons: IButton[] = [
{ text: 'Log Out', type: 'primary', onClick: this.onConfirm },
{ text: 'Cancel', type: 'default', onClick: this.onCancel }
{ text: translate('ACTION_7'), type: 'primary', onClick: this.onConfirm },
{ text: translate('ACTION_2'), type: 'default', onClick: this.onCancel }
];
return (
<Modal
title="You are about to log out"
title={translateRaw('WALLET_LOGOUT_MODAL_TITLE')}
isOpen={this.state.openModal}
handleClose={this.onCancel}
buttons={buttons}
>
<p>Leaving this page will log you out. Are you sure you want to continue?</p>
<p>{translate('WALLET_LOGOUT_MODAL_DESC')}</p>
</Modal>
);
}
@ -62,7 +63,7 @@ class LogOutPromptClass extends React.Component<Props, State> {
};
private onConfirm = () => {
const { nextLocation } = this.state;
const { nextLocation: next } = this.state;
this.props.resetWallet();
this.setState(
{
@ -70,8 +71,8 @@ class LogOutPromptClass extends React.Component<Props, State> {
nextLocation: null
},
() => {
if (nextLocation) {
this.props.history.push(nextLocation.pathname);
if (next) {
this.props.history.push(`${next.pathname}${next.search}${next.hash}`);
}
}
);

View File

@ -35,7 +35,7 @@ class NonceField extends React.Component<Props> {
<div className="input-group-wrapper Nonce-label">
<label className="input-group">
<div className="input-group-header">
{translate('OFFLINE_Step2_Label_5')}
{translate('OFFLINE_STEP2_LABEL_5')}
<Help
size="x1"
link="https://support.mycrypto.com/transactions/what-is-nonce.html"
@ -44,7 +44,7 @@ class NonceField extends React.Component<Props> {
<Input
className={`Nonce-field-input ${!!value ? 'is-valid' : 'is-invalid'}`}
type="number"
placeholder="e.g. 7"
placeholder="7"
value={raw}
readOnly={readOnly}
onChange={onChange}

View File

@ -100,8 +100,8 @@ export default class PaperWallet extends React.Component<Props, {}> {
return (
<div style={styles.container}>
<img src={sidebarImg} style={styles.sidebar} />
<img src={ethLogo} style={styles.ethLogo} />
<img src={sidebarImg} style={styles.sidebar} alt="MyCrypto Logo" />
<img src={ethLogo} style={styles.ethLogo} alt="ETH Logo" />
<div style={styles.block}>
<div style={styles.box}>
@ -111,7 +111,7 @@ export default class PaperWallet extends React.Component<Props, {}> {
</div>
<div style={styles.block}>
<img src={notesBg} style={styles.box} />
<img src={notesBg} style={styles.box} aria-hidden={true} />
<p style={styles.blockText}>AMOUNT / NOTES</p>
</div>

View File

@ -1,8 +1,8 @@
import { PaperWallet } from 'components';
import React from 'react';
import { translateRaw } from 'translations';
import printElement from 'utils/printElement';
import { stripHexPrefix } from 'libs/values';
import translate, { translateRaw } from 'translations';
export const print = (address: string, privateKey: string) => () =>
address &&
@ -38,13 +38,13 @@ const PrintableWallet: React.SFC<Props> = ({ address, privateKey }) => {
<PaperWallet address={address} privateKey={pkey} />
<a
role="button"
aria-label={translateRaw('x_Print')}
aria-label={translateRaw('X_PRINT')}
aria-describedby="x_PrintDesc"
className="btn btn-lg btn-primary btn-block"
onClick={print(address, pkey)}
style={{ margin: '10px auto 0', maxWidth: '260px' }}
>
{translateRaw('x_Print')}
{translate('X_PRINT')}
</a>
</div>
);

View File

@ -5,16 +5,18 @@ import { ConfirmationModal } from 'components/ConfirmationModal';
export const SendButton: React.SFC<{
onlyTransactionParameters?: boolean;
toggleDisabled?: boolean;
customModal?: typeof ConfirmationModal;
}> = ({ onlyTransactionParameters, customModal }) => (
}> = ({ onlyTransactionParameters, toggleDisabled, customModal }) => (
<SendButtonFactory
onlyTransactionParameters={!!onlyTransactionParameters}
toggleDisabled={toggleDisabled}
Modal={customModal ? customModal : ConfirmationModal}
withProps={({ onClick }) => (
withProps={({ disabled, onClick }: { disabled: boolean; onClick(): void }) => (
<div className="row form-group">
<div className="col-xs-12">
<button className="btn btn-primary btn-block" onClick={onClick}>
{translate('SEND_trans')}
<button disabled={disabled} className="btn btn-primary btn-block" onClick={onClick}>
{translate('SEND_TRANS')}
</button>
</div>
</div>

View File

@ -20,7 +20,7 @@ export const OfflineBroadcast = connect((state: AppState) => ({ offline: getOffl
const BroadCast: React.SFC<{}> = () => (
<p>
To broadcast this transaction, paste the above into the{' '}
<Link to="pushTx">Broadcast Transaction tab</Link> or{' '}
<Link to="/pushTx">Broadcast Transaction tab</Link> or{' '}
<NewTabLink href="https://etherscan.io/pushTx">etherscan.io/pushTx</NewTabLink>
</p>
);

View File

@ -2,7 +2,6 @@ import React, { Component } from 'react';
import { getOffline } from 'selectors/config';
import { AppState } from 'reducers';
import { connect } from 'react-redux';
import { CallbackProps } from '../SendButtonFactory';
import { getCurrentTransactionStatus, currentTransactionBroadcasted } from 'selectors/transaction';
import { showNotification, TShowNotification } from 'actions/notifications';
import { ITransactionStatus } from 'reducers/transaction/broadcast';
@ -26,7 +25,7 @@ interface DispatchProps {
interface OwnProps {
Modal: typeof ConfirmationModal;
withProps(props: CallbackProps): React.ReactElement<any> | null;
withOnClick(onClick: { onClick(): void }): React.ReactElement<any> | null;
}
const INITIAL_STATE: State = {
@ -41,7 +40,7 @@ class OnlineSendClass extends Component<Props, State> {
public render() {
return !this.props.offline ? (
<React.Fragment>
{this.props.withProps({ onClick: this.openModal })}
{this.props.withOnClick({ onClick: this.openModal })}
<this.props.Modal isOpen={this.state.showModal} onClose={this.closeModal} />
</React.Fragment>
) : null;

View File

@ -1,7 +1,6 @@
import translate from 'translations';
import { getTransactionFields, makeTransaction } from 'libs/transaction';
import { OfflineBroadcast } from './OfflineBroadcast';
import { SerializedTransaction } from 'components/renderCbs';
import { OnlineSend } from './OnlineSend';
import { addHexPrefix } from 'ethereumjs-util';
import { getWalletType, IWalletType } from 'selectors/wallet';
@ -10,60 +9,94 @@ import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { ConfirmationModal } from 'components/ConfirmationModal';
import { TextArea } from 'components/ui';
import { getSerializedTransaction } from 'selectors/transaction';
export interface CallbackProps {
disabled: boolean;
onClick(): void;
}
interface StateProps {
walletType: IWalletType;
serializedTransaction: AppState['transaction']['sign']['local']['signedTransaction'];
}
interface OwnProps {
onlyTransactionParameters?: boolean;
toggleDisabled?: boolean;
Modal: typeof ConfirmationModal;
withProps(props: CallbackProps): React.ReactElement<any> | null;
}
const getStringifiedTx = (serializedTransaction: string) =>
const getStringifiedTx = (serializedTransaction: Buffer) =>
JSON.stringify(getTransactionFields(makeTransaction(serializedTransaction)), null, 2);
type Props = StateProps & OwnProps;
class SendButtonFactoryClass extends Component<Props> {
public render() {
const { onlyTransactionParameters } = this.props;
const {
onlyTransactionParameters,
serializedTransaction,
toggleDisabled,
walletType
} = this.props;
const columnSize = onlyTransactionParameters ? 12 : 6;
/* Left and right transaction comparision boxes, only displayed when a serialized transaction
exists in state */
// shows the json representation of the transaction
const leftTxCompare = serializedTransaction && (
<div className={`col-sm-${columnSize}`}>
<label>{walletType.isWeb3Wallet ? 'Transaction Parameters' : translate('SEND_RAW')}</label>
<TextArea value={getStringifiedTx(serializedTransaction)} rows={4} readOnly={true} />
</div>
);
// shows the serialized representation of the transaction
// "onlyTransactionParameters" used in broadcast tx so the same serialized tx isnt redundantly
// displayed
const rightTxCompare = serializedTransaction &&
!onlyTransactionParameters && (
<div className="col-sm-6">
<label>
{walletType.isWeb3Wallet
? 'Serialized Transaction Parameters'
: translate('SEND_SIGNED')}
</label>
<TextArea
value={addHexPrefix(serializedTransaction.toString('hex'))}
rows={4}
readOnly={true}
/>
</div>
);
const shouldDisplayOnlineSend = toggleDisabled || serializedTransaction;
return (
<SerializedTransaction
withSerializedTransaction={serializedTransaction => (
<React.Fragment>
<div className={`col-sm-${columnSize}`}>
<label>
{this.props.walletType.isWeb3Wallet
? 'Transaction Parameters'
: translate('SEND_raw')}
</label>
<TextArea value={getStringifiedTx(serializedTransaction)} rows={4} readOnly={true} />
</div>
{!onlyTransactionParameters && (
<div className="col-sm-6">
<label>
{this.props.walletType.isWeb3Wallet
? 'Serialized Transaction Parameters'
: translate('SEND_signed')}
</label>
<TextArea value={addHexPrefix(serializedTransaction)} rows={4} readOnly={true} />
</div>
)}
<OfflineBroadcast />
<OnlineSend withProps={this.props.withProps} Modal={this.props.Modal} />
</React.Fragment>
<>
{leftTxCompare}
{rightTxCompare}
<OfflineBroadcast />
{shouldDisplayOnlineSend && (
<OnlineSend
withOnClick={({ onClick }) =>
this.props.withProps({
disabled: !!(toggleDisabled && !serializedTransaction),
onClick
})
}
Modal={this.props.Modal}
/>
)}
/>
</>
);
}
}
export const SendButtonFactory = connect((state: AppState) => ({
walletType: getWalletType(state)
walletType: getWalletType(state),
serializedTransaction: getSerializedTransaction(state)
}))(SendButtonFactoryClass);

View File

@ -27,7 +27,7 @@ class SendEverythingClass extends Component<Props> {
!readOnly ? (
<span className="help-block">
<a onClick={this.onSendEverything}>
<span className="">{translate('SEND_TransferTotal')}</span>
<span>{translate('SEND_TRANSFERTOTAL')}</span>
</a>
</span>
) : null

View File

@ -3,6 +3,7 @@ import { connect } from 'react-redux';
import { AppState } from 'reducers';
import { signaturePending } from 'selectors/transaction';
import { Spinner } from 'components/ui';
import translate from 'translations';
interface StateProps {
isSignaturePending: boolean;
isHardwareWallet: boolean;
@ -15,7 +16,7 @@ class SigningStatusClass extends Component<StateProps> {
const HWWalletPrompt: React.SFC<{}> = _ =>
isHardwareWallet ? (
<p>
<b>Confirm transaction on hardware wallet</b>
<b>{translate('CONFIRM_HARDWARE_WALLET_TRANSACTION')}</b>
</p>
) : null;

View File

@ -4,6 +4,9 @@
margin-top: 15px;
&-tabs {
display: inline-block;
white-space: nowrap;
&-link {
display: inline-block;
padding: 8px;
@ -26,4 +29,8 @@
}
}
}
&-select {
margin-bottom: $space-md;
}
}

View File

@ -1,4 +1,5 @@
import React from 'react';
import Select, { Option } from 'react-select';
import { NavLink, RouteComponentProps } from 'react-router-dom';
import './SubTabs.scss';
@ -9,32 +10,135 @@ export interface Tab {
redirect?: string;
}
interface Props {
interface OwnProps {
tabs: Tab[];
match: RouteComponentProps<{}>['match'];
}
export default class SubTabs extends React.PureComponent<Props> {
type Props = OwnProps & RouteComponentProps<{}>;
interface State {
tabsWidth: number;
isCollapsed: boolean;
}
export default class SubTabs extends React.PureComponent<Props, State> {
public state: State = {
tabsWidth: 0,
isCollapsed: false
};
private containerEl: HTMLDivElement | null;
private tabsEl: HTMLDivElement | null;
public componentDidMount() {
this.measureTabsWidth();
window.addEventListener('resize', this.handleResize);
}
public componentWillUnmount() {
window.removeEventListener('resize', this.handleResize);
}
public componentWillReceiveProps(nextProps: Props) {
// When new tabs come in, we'll need to uncollapse so that they can
// be measured and collapsed again, if needed.
if (this.props.tabs !== nextProps.tabs) {
this.setState({ isCollapsed: false });
}
}
public componentDidUpdate(prevProps: Props) {
// New tabs === new measurements
if (this.props.tabs !== prevProps.tabs) {
this.measureTabsWidth();
}
}
public render() {
const { tabs, match } = this.props;
const currentPath = match.url;
const { isCollapsed } = this.state;
const basePath = match.url;
const currentPath = location.pathname;
let content: React.ReactElement<string>;
return (
<div className="SubTabs row">
<div className="SubTabs-tabs col-sm-12">
if (isCollapsed) {
const options = tabs.map(tab => ({
label: tab.name as string,
value: tab.path,
disabled: tab.disabled
}));
content = (
<div className="SubTabs-select">
<Select
options={options}
value={currentPath.split('/').pop()}
onChange={this.handleSelect}
searchable={false}
clearable={false}
/>
</div>
);
} else {
// All tabs visible navigation
content = (
<div className="SubTabs-tabs" ref={el => (this.tabsEl = el)}>
{tabs.map((t, i) => (
// Same as normal Link, but knows when it's active, and applies activeClassName
<NavLink
className={`SubTabs-tabs-link ${t.disabled ? 'is-disabled' : ''}`}
activeClassName="is-active"
to={currentPath + '/' + t.path}
key={i}
>
{t.name}
</NavLink>
<SubTabLink tab={t} basePath={basePath} className="SubTabs-tabs-link" key={i} />
))}
</div>
);
}
return (
<div className="SubTabs" ref={el => (this.containerEl = el)}>
{content}
</div>
);
}
private handleSelect = ({ value }: Option) => {
this.props.history.push(`${this.props.match.url}/${value}`);
};
// Tabs become a dropdown if they would wrap
private handleResize = () => {
if (!this.containerEl) {
return;
}
this.setState({
isCollapsed: this.state.tabsWidth >= this.containerEl.offsetWidth
});
};
// Store the tab width for future
private measureTabsWidth = () => {
if (this.tabsEl) {
this.setState({ tabsWidth: this.tabsEl.offsetWidth }, () => {
this.handleResize();
});
} else {
// Briefly show, measure, collapse again still not enough room
this.setState({ isCollapsed: false }, this.measureTabsWidth);
}
};
}
interface SubTabLinkProps {
tab: Tab;
basePath: string;
className: string;
onClick?(ev: React.MouseEvent<HTMLAnchorElement>): void;
}
const SubTabLink: React.SFC<SubTabLinkProps> = ({ tab, className, basePath, onClick }) => (
<NavLink
className={`${className} ${tab.disabled ? 'is-disabled' : ''}`}
activeClassName="is-active"
to={basePath + '/' + tab.path}
onClick={onClick}
>
{tab.name}
</NavLink>
);

View File

@ -1,6 +1,5 @@
import React from 'react';
import BN from 'bn.js';
import { translateRaw } from 'translations';
import { connect } from 'react-redux';
import {
inputGasPrice,
@ -10,7 +9,8 @@ import {
getNonceRequested,
TGetNonceRequested,
reset,
TReset
TReset,
ResetAction
} from 'actions/transaction';
import { fetchCCRatesRequested, TFetchCCRatesRequested } from 'actions/rates';
import { getNetworkConfig, getOffline } from 'selectors/config';
@ -21,6 +21,7 @@ import AdvancedGas, { AdvancedOptions } from './components/AdvancedGas';
import './TXMetaDataPanel.scss';
import { getGasPrice } from 'selectors/transaction';
import { NetworkConfig } from 'types/network';
import { translateRaw } from 'translations';
type SliderStates = 'simple' | 'advanced';
@ -44,6 +45,7 @@ interface DefaultProps {
}
interface OwnProps {
resetIncludeExcludeProperties?: ResetAction['payload'];
initialState?: SliderStates;
disableToggle?: boolean;
advancedGasOptions?: AdvancedOptions;
@ -69,7 +71,7 @@ class TXMetaDataPanel extends React.Component<Props, State> {
public componentDidMount() {
if (!this.props.offline) {
this.props.reset();
this.props.reset(this.props.resetIncludeExcludeProperties);
this.props.fetchCCRates([this.props.network.unit]);
this.props.getNonceRequested();
}
@ -112,8 +114,8 @@ class TXMetaDataPanel extends React.Component<Props, State> {
<div className="help-block">
<a className="Gas-toggle" onClick={this.toggleAdvanced}>
{showAdvanced
? `- ${translateRaw('Back to simple')}`
: `+ ${translateRaw('Advanced Settings')}`}
? `- ${translateRaw('TRANS_SIMPLE')}`
: `+ ${translateRaw('TRANS_ADVANCED')}`}
</a>
</div>
)}

View File

@ -1,5 +1,5 @@
import React from 'react';
import translate, { translateRaw } from 'translations';
import { translateRaw } from 'translations';
import FeeSummary from './FeeSummary';
import './AdvancedGas.scss';
import { TToggleAutoGasLimit, toggleAutoGasLimit } from 'actions/config';
@ -74,7 +74,7 @@ class AdvancedGas extends React.Component<Props, State> {
<div className="input-group-wrapper AdvancedGas-gas-price">
<label className="input-group">
<div className="input-group-header">
{translate('OFFLINE_Step2_Label_3')} (gwei)
{translateRaw('OFFLINE_STEP2_LABEL_3')} (gwei)
</div>
<Input
className={!!gasPrice.raw && !validGasPrice ? 'is-invalid' : ''}
@ -89,7 +89,7 @@ class AdvancedGas extends React.Component<Props, State> {
{gasLimitField && (
<div className="AdvancedGas-gas-limit">
<GasLimitField customLabel={translateRaw('OFFLINE_Step2_Label_4')} />
<GasLimitField customLabel={translateRaw('OFFLINE_STEP2_LABEL_4')} />
</div>
)}
{nonceField && (

View File

@ -1,6 +1,6 @@
import React from 'react';
import Slider from 'rc-slider';
import translate, { translateRaw } from 'translations';
import Slider, { createSliderWithTooltip } from 'rc-slider';
import translate from 'translations';
import FeeSummary from './FeeSummary';
import './SimpleGas.scss';
import { AppState } from 'reducers';
@ -16,12 +16,13 @@ import { getEstimates, getIsEstimating } from 'selectors/gas';
import { Wei, fromWei } from 'libs/units';
import { gasPriceDefaults } from 'config';
import { InlineSpinner } from 'components/ui/InlineSpinner';
const SliderWithTooltip = Slider.createSliderWithTooltip(Slider);
import { TInputGasPrice } from 'actions/transaction';
const SliderWithTooltip = createSliderWithTooltip(Slider);
interface OwnProps {
gasPrice: AppState['transaction']['fields']['gasPrice'];
inputGasPrice(rawGas: string);
setGasPrice(rawGas: string);
setGasPrice: TInputGasPrice;
inputGasPrice(rawGas: string): void;
}
interface StateProps {
@ -39,14 +40,22 @@ interface ActionProps {
type Props = OwnProps & StateProps & ActionProps;
interface State {
hasSetRecommendedGasPrice: boolean;
}
class SimpleGas extends React.Component<Props> {
public state: State = {
hasSetRecommendedGasPrice: false
};
public componentDidMount() {
this.fixGasPrice();
this.props.fetchGasEstimates();
}
public componentWillReceiveProps(nextProps: Props) {
if (!this.props.gasEstimates && nextProps.gasEstimates) {
if (!this.state.hasSetRecommendedGasPrice && nextProps.gasEstimates) {
this.setState({ hasSetRecommendedGasPrice: true });
this.props.setGasPrice(nextProps.gasEstimates.fast.toString());
}
}
@ -71,7 +80,7 @@ class SimpleGas extends React.Component<Props> {
<div className="SimpleGas row form-group">
<div className="SimpleGas-title">
<div className="flex-wrapper">
<label>{translateRaw('Transaction Fee')} </label>
<label>{translate('CONFIRM_TX_FEE')} </label>
<div className="flex-spacer" />
<InlineSpinner active={noncePending || gasLimitPending} text="Calculating" />
</div>
@ -99,8 +108,8 @@ class SimpleGas extends React.Component<Props> {
disabled={isGasEstimating}
/>
<div className="SimpleGas-slider-labels">
<span>{translate('Cheap')}</span>
<span>{translate('Fast')}</span>
<span>{translate('TX_FEE_SCALE_LEFT')}</span>
<span>{translate('TX_FEE_SCALE_RIGHT')}</span>
</div>
</div>
<FeeSummary
@ -120,21 +129,6 @@ class SimpleGas extends React.Component<Props> {
this.props.inputGasPrice(gasGwei.toString());
};
private fixGasPrice() {
const { gasPrice, gasEstimates } = this.props;
if (!gasEstimates) {
return;
}
// If the gas price is above or below our minimum, bring it in line
const gasPriceGwei = this.getGasPriceGwei(gasPrice.value);
if (gasPriceGwei < gasEstimates.safeLow) {
this.props.setGasPrice(gasEstimates.safeLow.toString());
} else if (gasPriceGwei > gasEstimates.fastest) {
this.props.setGasPrice(gasEstimates.fastest.toString());
}
}
private getGasPriceGwei(gasPriceValue: Wei) {
return parseFloat(fromWei(gasPriceValue, 'gwei'));
}

View File

@ -17,7 +17,7 @@ interface Props {
toggleAriaLabel?: string;
isValid?: boolean;
isVisible?: boolean;
validity?: 'valid' | 'invalid' | 'semivalid';
readOnly?: boolean;
// Textarea-only props
isTextareaWhenVisible?: boolean;
@ -55,24 +55,21 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
disabled,
ariaLabel,
toggleAriaLabel,
validity,
isTextareaWhenVisible,
isValid,
onChange,
onFocus,
onBlur,
handleToggleVisibility
handleToggleVisibility,
readOnly
} = this.props;
const { isVisible } = this.state;
const validClass = validity
? `is-${validity}`
: isValid === null || isValid === undefined ? '' : isValid ? 'is-valid' : 'is-invalid';
return (
<div className={`TogglablePassword input-group input-group-inline-dropdown ${className}`}>
<div className={`TogglablePassword input-group input-group-inline`}>
{isTextareaWhenVisible && isVisible ? (
<TextArea
className={validClass}
className={`${className} ${!isValid ? 'invalid' : ''}`}
value={value}
name={name}
disabled={disabled}
@ -83,6 +80,7 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
placeholder={placeholder}
rows={this.props.rows || 3}
aria-label={ariaLabel}
readOnly={readOnly}
/>
) : (
<Input
@ -90,12 +88,13 @@ export default class TogglablePassword extends React.PureComponent<Props, State>
name={name}
disabled={disabled}
type={isVisible ? 'text' : 'password'}
className={`${validClass}`}
className={`${className} ${!isValid ? 'invalid' : ''} border-rad-right-0`}
placeholder={placeholder}
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
aria-label={ariaLabel}
readOnly={readOnly}
/>
)}
<span

View File

@ -1,7 +1,7 @@
import React from 'react';
import translate from 'translations';
import { Identicon, UnitDisplay, NewTabLink, TextArea, Address } from 'components/ui';
import { TransactionData, TransactionReceipt } from 'libs/nodes';
import { TransactionData, TransactionReceipt } from 'types/transactions';
import { NetworkConfig } from 'types/network';
import './TransactionDataTable.scss';
@ -71,7 +71,7 @@ const TransactionDataTable: React.SFC<Props> = ({ data, receipt, network }) => {
const rows: TableRow[] = [
{
label: 'Status',
label: translate('TX_STATUS'),
data: (
<React.Fragment>
<strong className={`TxData-row-data-status is-${statusType}`}>{statusMsg}</strong>
@ -86,15 +86,15 @@ const TransactionDataTable: React.SFC<Props> = ({ data, receipt, network }) => {
)
},
{
label: translate('x_TxHash'),
label: translate('X_TXHASH'),
data: <MaybeLink href={explorer.tx}>{data.hash}</MaybeLink>
},
{
label: 'Block Number',
label: translate('TX_BLOCK_NUMB'),
data: receipt && <MaybeLink href={explorer.block}>{receipt.blockNumber}</MaybeLink>
},
{
label: translate('OFFLINE_Step1_Label_1'),
label: translate('OFFLINE_STEP1_LABEL_1'),
data: (
<MaybeLink href={explorer.from}>
<Identicon address={data.from} size="26px" />
@ -103,7 +103,7 @@ const TransactionDataTable: React.SFC<Props> = ({ data, receipt, network }) => {
)
},
{
label: translate('OFFLINE_Step2_Label_1'),
label: translate('OFFLINE_STEP2_LABEL_1'),
data: (
<MaybeLink href={explorer.to}>
<Identicon address={data.to} size="26px" />
@ -112,23 +112,23 @@ const TransactionDataTable: React.SFC<Props> = ({ data, receipt, network }) => {
)
},
{
label: translate('SEND_amount_short'),
label: translate('SEND_AMOUNT_SHORT'),
data: <UnitDisplay value={data.value} unit="ether" symbol={network.unit} />
},
{
label: translate('OFFLINE_Step2_Label_3'),
label: translate('OFFLINE_STEP2_LABEL_3'),
data: <UnitDisplay value={data.gasPrice} unit="gwei" symbol="Gwei" />
},
{
label: translate('OFFLINE_Step2_Label_4'),
label: translate('OFFLINE_STEP2_LABEL_4'),
data: <UnitDisplay value={data.gas} unit="wei" />
},
{
label: 'Gas Used',
label: translate('TX_GAS_USED'),
data: receipt && <UnitDisplay value={receipt.gasUsed} unit="wei" />
},
{
label: 'Transaction Fee',
label: translate('CONFIRM_TX_FEE'),
data: receipt && (
<UnitDisplay
value={receipt.gasUsed.mul(data.gasPrice)}
@ -138,7 +138,7 @@ const TransactionDataTable: React.SFC<Props> = ({ data, receipt, network }) => {
)
},
{
label: translate('New contract address'),
label: translate('NEW_CONTRACT_ADDR'),
data: receipt &&
receipt.contractAddress && (
<MaybeLink href={explorer.contract}>
@ -147,11 +147,11 @@ const TransactionDataTable: React.SFC<Props> = ({ data, receipt, network }) => {
)
},
{
label: translate('OFFLINE_Step2_Label_5'),
label: translate('OFFLINE_STEP2_LABEL_5'),
data: data.nonce
},
{
label: translate('TRANS_data'),
label: translate('TRANS_DATA'),
data: hasInputData ? <TextArea value={data.input} disabled={true} /> : null
}
];

View File

@ -9,6 +9,7 @@
&-data {
text-align: left;
overflow-x: auto;
}
&-loading {

View File

@ -8,7 +8,7 @@ import { Spinner } from 'components/ui';
import TransactionDataTable from './TransactionDataTable';
import { AppState } from 'reducers';
import { NetworkConfig } from 'types/network';
import { TransactionState } from 'reducers/transactions';
import { TransactionState } from 'types/transactions';
import './TransactionStatus.scss';
interface OwnProps {
@ -44,7 +44,6 @@ class TransactionStatus extends React.Component<Props> {
if (tx && tx.data) {
content = (
<React.Fragment>
<h2 className="TxStatus-title">Transaction Found</h2>
<div className="TxStatus-data">
<TransactionDataTable network={network} data={tx.data} receipt={tx.receipt} />
</div>
@ -53,13 +52,13 @@ class TransactionStatus extends React.Component<Props> {
} else if (tx && tx.error) {
content = (
<div className="TxStatus-error">
<h2 className="TxStatus-error-title">{translate('tx_notFound')}</h2>
<p className="TxStatus-error-desc">{translate('tx_notFound_1')}</p>
<h2 className="TxStatus-error-title">{translate('TX_NOTFOUND')}</h2>
<p className="TxStatus-error-desc">{translate('TX_NOTFOUND_1')}</p>
<ul className="TxStatus-error-list">
<li>Make sure you copied the Transaction Hash correctly</li>
<li>{translate('tx_notFound_2')}</li>
<li>{translate('tx_notFound_3')}</li>
<li>{translate('tx_notFound_4')}</li>
<li>{translate('TX_NOTFOUND_5')}</li>
<li>{translate('TX_NOTFOUND_2')}</li>
<li>{translate('TX_NOTFOUND_3')}</li>
<li>{translate('TX_NOTFOUND_4')}</li>
</ul>
</div>
);

View File

@ -1,22 +1,20 @@
import React from 'react';
import Markdown from 'react-markdown';
import { translateRaw } from 'translations';
interface Props {
translationKey: string;
source: string;
}
const Translate = ({ translationKey }: Props) => {
const source = translateRaw(translationKey);
const TranslateMarkdown = ({ source }: Props) => {
return (
<Markdown
escapeHtml={true}
unwrapDisallowed={true}
allowedTypes={['link', 'emphasis', 'strong', 'code', 'root', 'inlineCode']}
renderers={{ root: 'span' }}
renderers={{ root: React.Fragment }}
source={source}
/>
);
};
export default Translate;
export default TranslateMarkdown;

Some files were not shown because too many files have changed in this diff Show More