├── public ├── CNAME ├── favicon.png ├── robots.txt ├── manifest.json ├── sitemap.xml ├── README.md └── index.html ├── config.json.example ├── src ├── images │ ├── bg.png │ └── bg_original.png ├── fonts │ ├── charterc.otf │ ├── HelveticaNeue.ttf │ ├── OpenSans-Bold.ttf │ ├── EBGaramond-Bold.ttf │ ├── Audiowide-Regular.ttf │ └── OpenSans-Regular.ttf ├── index.css ├── lib │ ├── mnemonic │ │ ├── index.js │ │ ├── isInMnemonic.js │ │ └── languages │ │ │ └── english.json │ ├── toCapitalizeCase.js │ ├── copyToClipboard.js │ ├── lazyScroll.js │ ├── classNames.js │ ├── seedHelpers.js │ └── seedHelpers.test.js ├── utils │ ├── enzymeSetup.js │ └── gtag.js ├── components │ ├── title │ │ ├── index.js │ │ └── title.css │ ├── svg │ │ ├── icons.css │ │ └── Svg.js │ ├── faq │ │ ├── faq.css │ │ └── index.js │ ├── intro │ │ └── index.js │ ├── caption │ │ └── index.js │ ├── seedConstructor │ │ ├── seedConstructor.css │ │ └── index.js │ ├── button │ │ ├── index.js │ │ └── button.css │ ├── page │ │ ├── index.js │ │ └── page.css │ ├── copyButton │ │ └── index.js │ ├── footer │ │ ├── index.js │ │ └── footer.css │ ├── splittedSeed │ │ ├── index.js │ │ └── splittedSeed.css │ ├── seedContainer │ │ ├── index.test.js │ │ └── index.js │ ├── pagesSelect │ │ ├── index.js │ │ └── pagesSelect.css │ ├── print │ │ ├── print.css │ │ └── index.js │ └── restoreSeed │ │ ├── restoreSeed.css │ │ └── index.js ├── App.test.js ├── index.js ├── icons │ └── svg-sprite.svg ├── content.js ├── App.js ├── registerServiceWorker.js └── App.css ├── .gitignore ├── README.md ├── package.json └── LICENSE.GPL /public/CNAME: -------------------------------------------------------------------------------- 1 | paper-wallet.me -------------------------------------------------------------------------------- /config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "port": 8080 3 | } -------------------------------------------------------------------------------- /src/images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HashEx/paperwallet/HEAD/src/images/bg.png -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HashEx/paperwallet/HEAD/public/favicon.png -------------------------------------------------------------------------------- /src/fonts/charterc.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HashEx/paperwallet/HEAD/src/fonts/charterc.otf -------------------------------------------------------------------------------- /src/fonts/HelveticaNeue.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HashEx/paperwallet/HEAD/src/fonts/HelveticaNeue.ttf -------------------------------------------------------------------------------- /src/fonts/OpenSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HashEx/paperwallet/HEAD/src/fonts/OpenSans-Bold.ttf -------------------------------------------------------------------------------- /src/images/bg_original.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HashEx/paperwallet/HEAD/src/images/bg_original.png -------------------------------------------------------------------------------- /src/fonts/EBGaramond-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HashEx/paperwallet/HEAD/src/fonts/EBGaramond-Bold.ttf -------------------------------------------------------------------------------- /src/fonts/Audiowide-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HashEx/paperwallet/HEAD/src/fonts/Audiowide-Regular.ttf -------------------------------------------------------------------------------- /src/fonts/OpenSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HashEx/paperwallet/HEAD/src/fonts/OpenSans-Regular.ttf -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-Agent: * 2 | Disallow: /print 3 | Sitemap: https://paper-wallet.me/sitemap.xml 4 | Host: https://paper-wallet.me -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | padding: 0; 4 | font-family: sans-serif; 5 | } 6 | 7 | a { 8 | color: inherit; 9 | } 10 | -------------------------------------------------------------------------------- /src/lib/mnemonic/index.js: -------------------------------------------------------------------------------- 1 | import isInMnemonic, { mnemonicAsObject } from "./isInMnemonic"; 2 | 3 | export default { 4 | isInMnemonic, 5 | mnemonicAsObject 6 | } -------------------------------------------------------------------------------- /src/lib/toCapitalizeCase.js: -------------------------------------------------------------------------------- 1 | export default (string) => { 2 | return string.split(" ").map((word) => { 3 | return word.slice(0, 1).toUpperCase() + word.slice(1); 4 | }).join(" "); 5 | } -------------------------------------------------------------------------------- /src/utils/enzymeSetup.js: -------------------------------------------------------------------------------- 1 | import Enzyme from "enzyme"; 2 | import Adapter from "enzyme-adapter-react-16"; 3 | 4 | Enzyme.configure({adapter: new Adapter()}); 5 | 6 | export default Enzyme; -------------------------------------------------------------------------------- /src/utils/gtag.js: -------------------------------------------------------------------------------- 1 | export default (type, options) => { 2 | const { gtag } = window; 3 | if(typeof gtag !== 'undefined'){ 4 | gtag('event', type, options); 5 | }else{ 6 | console.log('Gtag is not defined'); 7 | } 8 | } -------------------------------------------------------------------------------- /src/components/title/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | import "./title.css"; 5 | 6 | const Title = () =>

paper wallet

; 7 | 8 | export default Title; -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Paper Wallet", 3 | "name": "Paper Wallet", 4 | "icons": [], 5 | "start_url": "./index.html", 6 | "display": "standalone", 7 | "theme_color": "#000000", 8 | "background_color": "#ffffff" 9 | } 10 | -------------------------------------------------------------------------------- /src/components/svg/icons.css: -------------------------------------------------------------------------------- 1 | .icon-arrow { 2 | width: 13px; 3 | height: 8px; 4 | } 5 | 6 | .icon-arrow-restore-seed { 7 | width: 18px; 8 | height: 10px; 9 | opacity: 0; 10 | } 11 | 12 | .icon-arrow-restore-seed--valid { 13 | fill: #379299; 14 | opacity: 1; 15 | } -------------------------------------------------------------------------------- /src/components/faq/faq.css: -------------------------------------------------------------------------------- 1 | .faq { 2 | padding: 30px 15px; 3 | color: rgba(0, 0, 0, 0.91); 4 | } 5 | 6 | .faq-item { 7 | padding-bottom: 10px; 8 | margin-bottom: 10px; 9 | } 10 | 11 | .faq-item__header { 12 | font-size: 26px; 13 | } 14 | 15 | .faq-item__text { 16 | line-height: 1.33; 17 | } -------------------------------------------------------------------------------- /src/lib/mnemonic/isInMnemonic.js: -------------------------------------------------------------------------------- 1 | import mnemonic from "./languages/english.json"; 2 | 3 | export const mnemonicAsObject = mnemonic.reduce((acc, item, index) => { 4 | acc[item] = { 5 | word: item, 6 | index 7 | }; 8 | return acc; 9 | }, {}); 10 | 11 | export default word => mnemonicAsObject[word] !== undefined; -------------------------------------------------------------------------------- /src/components/intro/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | import content from "../../content"; 5 | 6 | export default () => ( 7 |
8 | {content.intro} 9 | {" "} 10 | {content.readMore}. 11 |
12 | ); 13 | -------------------------------------------------------------------------------- /src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import Enzyme from "./utils/enzymeSetup"; 4 | 5 | import App from "./App"; 6 | 7 | import SeedContainer from "./components/seedContainer"; 8 | 9 | const { shallow } = Enzyme; 10 | 11 | it("renders without crashing", () => { 12 | const wrapper = shallow(); 13 | }); 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | yarn.lock 23 | 24 | config.json -------------------------------------------------------------------------------- /src/lib/copyToClipboard.js: -------------------------------------------------------------------------------- 1 | export default (text, cb) => { 2 | let tmp = document.createElement("input"); 3 | let focus = document.activeElement; 4 | tmp.value = text.trim(); 5 | document.body.appendChild(tmp); 6 | tmp.select(); 7 | document.execCommand('copy'); 8 | document.body.removeChild(tmp); 9 | focus.focus(); 10 | if(typeof cb === "function"){ 11 | cb(); 12 | } 13 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { hydrate, render } from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import registerServiceWorker from "./registerServiceWorker"; 6 | 7 | const rootElement = document.getElementById("root"); 8 | if (rootElement.hasChildNodes()) { 9 | hydrate(, rootElement); 10 | } else { 11 | render(, rootElement); 12 | } 13 | //registerServiceWorker(); -------------------------------------------------------------------------------- /src/icons/svg-sprite.svg: -------------------------------------------------------------------------------- 1 | BFDA07D1-DDB6-44C0-8D7F-BBE8726308A5 -------------------------------------------------------------------------------- /src/lib/lazyScroll.js: -------------------------------------------------------------------------------- 1 | export default () => { 2 | var duration = 1; 3 | var w = window.pageYOffset; 4 | var t = document.body.getBoundingClientRect().top; 5 | var start = null; 6 | requestAnimationFrame(step); 7 | function step(time) { 8 | if (start === null) start = time; 9 | var progress = time - start; 10 | var r = 11 | t < 0 12 | ? Math.max(w - progress / duration, w + t) 13 | : Math.min(w + progress / duration, w + t); 14 | 15 | window.scrollTo(0, r); 16 | 17 | if (r !== w + t) requestAnimationFrame(step); 18 | } 19 | }; -------------------------------------------------------------------------------- /src/components/caption/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import classNames from "../../lib/classNames"; 5 | 6 | const Caption = ({error, restore, children}) => { 7 | let classes = classNames("caption", { 8 | "caption--invalid": error, 9 | "caption--restore-seed": restore 10 | }); 11 | return

{error ? error : children}

12 | }; 13 | 14 | Caption.propTypes = { 15 | className: PropTypes.string, 16 | error: PropTypes.string, 17 | children: PropTypes.oneOfType([PropTypes.string, PropTypes.element]) 18 | } 19 | 20 | export default Caption; -------------------------------------------------------------------------------- /src/components/svg/Svg.js: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import "./icons.css"; 3 | 4 | const SVG = { 5 | "icon-arrow": (props) => ( 6 | 13 | 17 | 18 | ) 19 | } 20 | 21 | 22 | const Svg = ({name, className}) => { 23 | const Icon = SVG[name]; 24 | return 25 | } 26 | 27 | export default Svg; -------------------------------------------------------------------------------- /src/lib/classNames.js: -------------------------------------------------------------------------------- 1 | export default function classNames(){ 2 | const args = Array.prototype.slice.call(arguments); 3 | let classes = []; 4 | args.forEach((item) => { 5 | if(typeof item === "string" && item){ 6 | classes.push(item); 7 | } 8 | if(typeof item === "object" && item){ 9 | if(!Array.isArray(item)){ 10 | Object.keys(item).forEach(key => { 11 | if(item[key]){ 12 | classes.push(key); 13 | } 14 | }); 15 | }else if(Array.isArray(item) && item.length){ 16 | classes.push(classNames.apply(null, item)); 17 | } 18 | } 19 | }); 20 | classes = classes.filter((item, index) => classes.indexOf(item) === index).join(" "); 21 | return classes; 22 | } -------------------------------------------------------------------------------- /src/components/seedConstructor/seedConstructor.css: -------------------------------------------------------------------------------- 1 | .seed-constructor { 2 | background-color: #fff; 3 | } 4 | 5 | .splitter__container { 6 | text-align: center; 7 | position: relative; 8 | } 9 | 10 | /*@media(min-width: 750px) { 11 | .splitter__container { 12 | padding-bottom: 70px; 13 | } 14 | } 15 | 16 | @media(min-width: 1200px) { 17 | .splitter__container { 18 | padding-bottom: 81px; 19 | } 20 | }*/ 21 | 22 | .footer { 23 | padding: 15px; 24 | background-color: #5b636a; 25 | box-shadow: inset 0 0 33px 0 rgba(0, 0, 0, 0.03); 26 | color: #fff; 27 | font-family: OpenSans, sans-serif; 28 | font-size: 12px; 29 | } 30 | 31 | @media(min-width: 1600px) { 32 | .footer { 33 | padding: 15px 0; 34 | } 35 | } -------------------------------------------------------------------------------- /src/components/button/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import classNames from "../../lib/classNames"; 5 | 6 | import "./button.css"; 7 | 8 | class Button extends React.Component { 9 | render(){ 10 | const {className, children, color, ...props} = this.props; 11 | const classes = classNames("btn", className, { 12 | [`btn--${color}`]: color 13 | }); 14 | return ( 15 | 18 | ) 19 | } 20 | } 21 | 22 | Button.propTypes = { 23 | className: PropTypes.string, 24 | children: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), 25 | color: PropTypes.oneOf(["green", "white"]) 26 | } 27 | 28 | export default Button; -------------------------------------------------------------------------------- /src/components/title/title.css: -------------------------------------------------------------------------------- 1 | .title { 2 | margin-top: 0; 3 | text-align: right; 4 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 5 | font-size: 32px; 6 | font-weight: bold; 7 | font-style: normal; 8 | font-stretch: normal; 9 | color: #132029; 10 | position: relative; 11 | z-index: 1; 12 | line-height: 1.75; 13 | letter-spacing: -1.5px; 14 | padding: 0 24px; 15 | margin-bottom: -16px; 16 | } 17 | 18 | @media(min-width: 750px) { 19 | .title { 20 | font-size: 64px; 21 | line-height: 0.88; 22 | padding: 0 65px; 23 | margin-bottom: 0; 24 | letter-spacing: -3px; 25 | margin-bottom: -3px; 26 | } 27 | } 28 | 29 | @media(min-width: 1200px) { 30 | .title { 31 | padding: 0; 32 | } 33 | } 34 | 35 | .title a { 36 | text-decoration: none; 37 | color: inherit; 38 | } -------------------------------------------------------------------------------- /public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | https://paper-wallet.me/ 12 | 2018-10-01T12:45:15+00:00 13 | 1.00 14 | 15 | 16 | https://paper-wallet.me/faq 17 | 2018-10-01T12:45:15+00:00 18 | 0.80 19 | 20 | 21 | https://paper-wallet.me/recover 22 | 2018-10-01T12:45:15+00:00 23 | 0.80 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/components/page/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import CopyButton from "../copyButton"; 5 | 6 | import "./page.css"; 7 | 8 | const Header = ({children}) => ( 9 |
{children}
10 | ); 11 | 12 | const Content = ({children}) => ( 13 |
{children}
14 | ); 15 | 16 | const Page = ({ children, textToCopy }) => ( 17 |
18 |
19 | {children} 20 | {textToCopy && } 21 |
22 |
23 | ); 24 | 25 | Page.propTypes = { 26 | children: PropTypes.oneOfType([ 27 | PropTypes.string, 28 | PropTypes.element, 29 | PropTypes.arrayOf(PropTypes.element) 30 | ]).isRequired, 31 | textToCopy: PropTypes.string 32 | } 33 | 34 | Page.Header = Header; 35 | Page.Content = Content; 36 | 37 | export default Page; 38 | -------------------------------------------------------------------------------- /public/README.md: -------------------------------------------------------------------------------- 1 | # Paper Wallet 2 | 3 | Paper Wallet is the safest way to store your seed phrase and private keys. Our solution allows to split your seed phrase into several pages which you can print out and keep separately. We recommend locking the pages in separate safe deposit boxes in different locations. Paper Wallet supports redundant encryption algorithms, thus, letting you set not only the number of pages to store the seed on, but also the number of pages required to recover the seed. 4 | 5 | ## Licensing 6 | 7 | Paper Wallet licensed under GNU GPLv3. 8 | 9 | ## Quick Start Guide 10 | 11 | ### Prerequisites 12 | 13 | For building Paper Wallet, following packages are required (note, that this can vary from distribution to distribution): 14 | 15 | 1. `git` 16 | 1. `Node.js` 17 | 18 | ### Installation 19 | 20 | Clone the repository: 21 | 22 | ``` 23 | $ git clone https://github.com/HashEx/paperwallet.git 24 | ``` 25 | 26 | Install dependencies: 27 | 28 | ``` 29 | $ npm install 30 | ``` 31 | 32 | or 33 | 34 | ``` 35 | $ yarn install 36 | ``` 37 | 38 | Run Paper Wallet: 39 | 40 | ``` 41 | $ npm start 42 | ``` 43 | -------------------------------------------------------------------------------- /src/components/copyButton/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import copyToClipboard from "../../lib/copyToClipboard"; 5 | import classNames from "../../lib/classNames"; 6 | 7 | const initialCopyText = "copy"; 8 | 9 | class CopyButton extends React.Component{ 10 | constructor(props){ 11 | super(props); 12 | this.state = { 13 | copyText: initialCopyText 14 | } 15 | } 16 | onCopy = () => { 17 | this.setState({copyText: "copied!"}); 18 | setTimeout(() => { 19 | this.setState({copyText: initialCopyText}); 20 | }, 1000); 21 | }; 22 | onClick = e => { 23 | const {text} = this.props; 24 | e.preventDefault(); 25 | copyToClipboard(text, this.onCopy); 26 | }; 27 | render(){ 28 | const {className} = this.props; 29 | const classes = classNames("copy-btn", className); 30 | return ( 31 | 34 | ); 35 | } 36 | }; 37 | 38 | CopyButton.propTypes = { 39 | className: PropTypes.string, 40 | text: PropTypes.string.isRequired 41 | } 42 | 43 | 44 | export default CopyButton; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Paper Wallet 2 | 3 | Paper Wallet is the safest way to store your seed phrase and private keys. Our solution allows to split your seed phrase into several pages which you can print out and keep separately. We recommend locking the pages in separate safe deposit boxes in different locations. Paper Wallet supports redundant encryption algorithms, thus, letting you set not only the number of pages to store the seed on, but also the number of pages required to recover the seed. 4 | 5 | ## Licensing 6 | 7 | Paper Wallet licensed under GNU GPLv3. 8 | 9 | ## Quick Start Guide 10 | 11 | ### Prerequisites 12 | 13 | For building Paper Wallet, following packages are required (note, that this can vary from distribution to distribution): 14 | 15 | 1. `git` 16 | 1. `Node.js` 17 | 18 | ### Installation 19 | 20 | Clone the repository: 21 | 22 | ``` 23 | $ git clone https://github.com/HashEx/paperwallet.git 24 | ``` 25 | 26 | Install dependencies: 27 | 28 | ``` 29 | $ npm install 30 | ``` 31 | 32 | or 33 | 34 | ``` 35 | $ yarn install 36 | ``` 37 | 38 | Run Paper Wallet: 39 | 40 | ``` 41 | $ npm start 42 | ``` 43 | 44 | or 45 | 46 | ``` 47 | $ yarn start 48 | ``` 49 | -------------------------------------------------------------------------------- /src/components/footer/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | import content from "../../content"; 5 | 6 | import "./footer.css"; 7 | 8 | const Footer = () => ( 9 | 41 | ); 42 | 43 | export default Footer; 44 | -------------------------------------------------------------------------------- /src/components/button/button.css: -------------------------------------------------------------------------------- 1 | .btn { 2 | background-image: none; 3 | display: inline-block; 4 | text-align: center; 5 | border: none; 6 | border-radius: 1px; 7 | font-weight: bold; 8 | box-shadow: 2px 2px 2px 0 rgba(0, 0, 0, 0.17); 9 | line-height: 3.43; 10 | font-size: 14px; 11 | line-height: 29px; 12 | } 13 | 14 | @media(min-width: 750px) { 15 | .btn { 16 | font-size: 28px; 17 | height: 64px; 18 | line-height: 64px; 19 | line-height: 1.71; 20 | } 21 | } 22 | 23 | .btn--green { 24 | width: 136px; 25 | background-image: linear-gradient(to bottom, #74b2b6, #669b9f); 26 | color: #fff; 27 | } 28 | 29 | @media(min-width: 750px) { 30 | .btn--green { 31 | width: 270px; 32 | } 33 | } 34 | 35 | .btn--white { 36 | width: 80px; 37 | background: #e8f1f2; 38 | color: #626566; 39 | } 40 | 41 | @media(min-width: 750px) { 42 | .btn--white { 43 | width: 160px; 44 | } 45 | } 46 | 47 | .btn--white:hover { 48 | background-image: linear-gradient(#e7bebe, #e7bebe), linear-gradient(to bottom, #74b2b6, #669b9f); 49 | color: #61585a; 50 | } 51 | 52 | .btn--valid-seed { 53 | position: relative; 54 | top: -21px; 55 | } 56 | 57 | @media(min-width:750px) { 58 | .btn--valid-seed { 59 | top: -34px; 60 | } 61 | } 62 | 63 | .btn[disabled]{ 64 | opacity: 0.7; 65 | cursor: not-allowed; 66 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paper-wallet-react", 3 | "version": "1.0.0", 4 | "private": true, 5 | "homepage": "https://hashex.github.io/paperwallet", 6 | "dependencies": { 7 | "bip39": "^2.5.0", 8 | "react": "^16.4.1", 9 | "react-dom": "^16.4.1", 10 | "react-helmet": "^5.2.0", 11 | "react-router": "^4.3.1", 12 | "react-router-dom": "^4.3.1", 13 | "react-scripts": "^2.0.4", 14 | "react-select": "^2.0.0-beta.7" 15 | }, 16 | "scripts": { 17 | "start": "react-scripts start", 18 | "build": "react-scripts build", 19 | "test": "react-scripts test --env=jsdom", 20 | "eject": "react-scripts eject", 21 | "postbuild": "react-snap", 22 | "deploy": "npm run build && gh-pages -d build" 23 | }, 24 | "devDependencies": { 25 | "babel-cli": "^6.26.0", 26 | "babel-core": "^7.0.0-bridge.0", 27 | "babel-plugin-transform-class-properties": "^6.24.1", 28 | "babel-plugin-transform-ensure-ignore": "^0.1.0", 29 | "babel-preset-es2015": "^6.24.1", 30 | "babel-preset-react": "^6.24.1", 31 | "babel-preset-react-app": "^3.1.2", 32 | "babel-preset-stage-3": "^6.24.1", 33 | "cross-env": "^5.2.0", 34 | "enzyme": "^3.7.0", 35 | "enzyme-adapter-react-16": "^1.6.0", 36 | "gh-pages": "^2.0.1", 37 | "react-snap": "^1.19.0", 38 | "sinon": "^6.3.5" 39 | }, 40 | "browserslist": [ 41 | ">0.2%", 42 | "not dead", 43 | "not ie <= 11", 44 | "not op_mini all" 45 | ], 46 | "reactSnap": { 47 | "fixWebpackChunksIssue": false 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/components/footer/footer.css: -------------------------------------------------------------------------------- 1 | .footer { 2 | padding: 9px 18px 18px; 3 | box-sizing: border-box; 4 | position: absolute; 5 | left: 0; 6 | bottom: 0; 7 | width: 100%; 8 | } 9 | 10 | @media(min-width: 750px) { 11 | .footer { 12 | padding: 1px 18px 10px; 13 | position: absolute; 14 | left: 0; 15 | bottom: 0; 16 | width: 100%; 17 | height: 40px; 18 | } 19 | } 20 | 21 | .footer__logo { 22 | font-family: OpenSans, sans-serif; 23 | font-size: 12px; 24 | font-weight: 600; 25 | line-height: 1.67; 26 | color: #ffffff; 27 | } 28 | 29 | @media(min-width: 750px) { 30 | .footer__logo { 31 | float: left; 32 | padding-top: 8px; 33 | padding-right: 16px; 34 | } 35 | } 36 | 37 | .footer__menu { 38 | padding-left: 0; 39 | list-style-type: none; 40 | margin: 0; 41 | } 42 | 43 | @media(min-width: 750px) { 44 | .footer__menu { 45 | float: left; 46 | } 47 | } 48 | 49 | .footer__menu-item { 50 | text-align: left; 51 | } 52 | 53 | @media(min-width: 750px) { 54 | .footer__menu-item { 55 | display: inline-block; 56 | padding: 0 16px; 57 | } 58 | } 59 | 60 | .footer__menu-link { 61 | opacity: 0.71; 62 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 63 | font-size: 12px; 64 | font-weight: normal; 65 | line-height: 1.67; 66 | color: #ffffff; 67 | padding-top: 9px; 68 | display: block; 69 | -webkit-text-size-adjust: 100%; 70 | } 71 | 72 | .footer__copy { 73 | padding-top: 9px; 74 | color: white; 75 | opacity: 0.81; 76 | -webkit-text-size-adjust: 100%; 77 | } 78 | 79 | @media(min-width: 750px) { 80 | .footer__copy { 81 | float: right; 82 | padding-top: 10px; 83 | } 84 | } -------------------------------------------------------------------------------- /src/components/splittedSeed/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | 4 | import PagesSelect from "../pagesSelect"; 5 | import Page from "../page"; 6 | import Button from "../button"; 7 | 8 | import "./splittedSeed.css"; 9 | 10 | const Actions = ({ onPrint, onClear }) => ( 11 |
12 | 15 | 18 |
19 | ); 20 | 21 | Actions.propTypes = { 22 | onPrint: PropTypes.func.isRequired, 23 | onClear: PropTypes.func.isRequired 24 | } 25 | 26 | const Pages = ({ seed }) => ( 27 |
28 |
29 | {seed.map((text, i) => ( 30 | 31 | {i + 1} 32 | {text} 33 | 34 | ))} 35 |
36 |
37 | ); 38 | 39 | Pages.propTypes = { 40 | seed: PropTypes.arrayOf(PropTypes.string).isRequired 41 | } 42 | 43 | const SplittedSeed = props => { 44 | const { seed, onPrint, onClear, pages, onChangePageCount } = props; 45 | return ( 46 |
47 | 48 | 49 | 50 |
51 | ); 52 | }; 53 | 54 | SplittedSeed.propTypes = { 55 | onPrint: PropTypes.func.isRequired, 56 | onClear: PropTypes.func.isRequired, 57 | pages: PropTypes.number.isRequired, 58 | onChangePageCount: PropTypes.func.isRequired, 59 | seed: PropTypes.arrayOf(PropTypes.string).isRequired, 60 | } 61 | 62 | export default SplittedSeed; 63 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 22 | 23 | 24 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/components/seedContainer/index.test.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import sinon from "sinon"; 4 | 5 | import Enzyme from "../../utils/enzymeSetup"; 6 | 7 | import SeedContainer from "./"; 8 | 9 | const { shallow } = Enzyme; 10 | 11 | 12 | describe("", () => { 13 | describe("index route", () => { 14 | const props = { 15 | value: "", 16 | onChange: jest.fn(), 17 | onFocus: jest.fn(), 18 | onBlur: jest.fn() 19 | } 20 | let index = shallow(); 21 | let textarea = index.find("textarea"); 22 | it("should focus", () => { 23 | textarea.simulate("focus"); 24 | expect(props.onFocus).toHaveBeenCalled(); 25 | expect(index.state().isFocused).toBeTruthy(); 26 | }); 27 | 28 | it("should change value", () => { 29 | expect(index.state().value).toEqual(""); 30 | textarea.simulate("change", {target: {value: "text"}}); 31 | expect(props.onChange).toHaveBeenCalled(); 32 | expect(index.state().value).toEqual("text"); 33 | expect(index.find('textarea').getElement().props.value).toEqual("text"); 34 | expect(index.state().isFocused).toBeTruthy(); 35 | }); 36 | 37 | it("should blur", () => { 38 | textarea.simulate("blur"); 39 | expect(props.onBlur).toHaveBeenCalled(); 40 | expect(index.state().isFocused).toBeFalsy(); 41 | }); 42 | }); 43 | 44 | describe("recover route", () => { 45 | const props = { 46 | isRestore: true, 47 | value: "margin steak boil huge crush argue couch culture truth bar addict service valley pencil bring", 48 | onChange: jest.fn() 49 | }; 50 | let recover = shallow(); 51 | let textarea = recover.find("textarea"); 52 | it("should set value", () => { 53 | expect(textarea.getElement().props.value).toEqual(props.value); 54 | }); 55 | describe("