1
0
mirror of https://github.com/kremalicious/blog.git synced 2024-12-23 01:30:01 +01:00

Merge pull request #68 from kremalicious/feature/web3-number-input

number input & modal tweaks
This commit is contained in:
Matthias Kretschmann 2018-10-13 19:22:16 +02:00 committed by GitHub
commit e69a4d5833
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 237 additions and 75 deletions

View File

@ -18,7 +18,7 @@
- [🎉 Features](#-features) - [🎉 Features](#-features)
- [🎆 EXIF extraction](#-exif-extraction) - [🎆 EXIF extraction](#-exif-extraction)
- [💰 Cryptocurrency donation via Web3/MetaMask](#-cryptocurrency-donation-via-web3-metamask) - [💰 Cryptocurrency donation via Web3/MetaMask](#-cryptocurrency-donation-via-web3metamask)
- [🕸 Related Posts](#-related-posts) - [🕸 Related Posts](#-related-posts)
- [🐝 Coinhive](#-coinhive) - [🐝 Coinhive](#-coinhive)
- [🏆 SEO component](#-seo-component) - [🏆 SEO component](#-seo-component)
@ -55,11 +55,11 @@ If you want to know how this works, have a look at the respective component unde
### 💰 Cryptocurrency donation via Web3/MetaMask ### 💰 Cryptocurrency donation via Web3/MetaMask
Lets visitors say thanks with Bitcoin or Ether. Includes full Web3 client for sending Ether via MetaMask or Mist. Lets visitors say thanks with Bitcoin or Ether. Uses [web3.js](https://github.com/ethereum/web3.js) for sending Ether transactions via MetaMask, Brave or Mist. Component listens to account & network changes and adapts accordingly.
As a fallback, QR codes are generated with [react-qr-svg](https://github.com/no23reason/react-qr-svg) from the addresses defined in [`config.js`](config.js). As a fallback, QR codes are generated with [react-qr-svg](https://github.com/no23reason/react-qr-svg) from the addresses defined in [`config.js`](config.js).
<img width="743" alt="screen shot 2018-10-11 at 21 01 37" src="https://user-images.githubusercontent.com/90316/46827443-e1187680-cd98-11e8-9daf-00a37c0ee13a.png"> <img width="1091" alt="screen shot 2018-10-13 at 18 40 56" src="https://user-images.githubusercontent.com/90316/46907751-907b5780-cf17-11e8-902d-6c520b388292.png" />
If you want to know how this works, have a look at the respective components under If you want to know how this works, have a look at the respective components under
@ -70,7 +70,7 @@ If you want to know how this works, have a look at the respective components und
Under each post a list of related posts is displayed which are based on the tags of the currently viewed post. Also allows loading more related posts in place. Under each post a list of related posts is displayed which are based on the tags of the currently viewed post. Also allows loading more related posts in place.
<img width="691" alt="screen shot 2018-10-11 at 21 03 03" src="https://user-images.githubusercontent.com/90316/46827531-14f39c00-cd99-11e8-84aa-0e851c32c89c.png"> <img width="691" alt="screen shot 2018-10-11 at 21 03 03" src="https://user-images.githubusercontent.com/90316/46827531-14f39c00-cd99-11e8-84aa-0e851c32c89c.png" />
If you want to know how this works, have a look at the respective component under If you want to know how this works, have a look at the respective component under
@ -80,7 +80,7 @@ If you want to know how this works, have a look at the respective component unde
Includes a component for mining Monero with JavaScript via [Coinhive](https://coinhive.com). Includes a component for mining Monero with JavaScript via [Coinhive](https://coinhive.com).
<img width="166" alt="screen shot 2018-10-11 at 21 09 49" src="https://user-images.githubusercontent.com/90316/46827858-03f75a80-cd9a-11e8-84f1-65b7d0027124.png"> <img width="166" alt="screen shot 2018-10-11 at 21 09 49" src="https://user-images.githubusercontent.com/90316/46827858-03f75a80-cd9a-11e8-84f1-65b7d0027124.png" />
Functionality is opt-in on a post basis. Simply add this to any post's frontmatter to activate it for this post: Functionality is opt-in on a post basis. Simply add this to any post's frontmatter to activate it for this post:

View File

@ -31,8 +31,8 @@
"dms2dec": "^1.1.0", "dms2dec": "^1.1.0",
"fast-exif": "^1.0.1", "fast-exif": "^1.0.1",
"fraction.js": "^4.0.9", "fraction.js": "^4.0.9",
"gatsby": "^2.0.21", "gatsby": "^2.0.22",
"gatsby-image": "^2.0.13", "gatsby-image": "^2.0.14",
"gatsby-plugin-catch-links": "^2.0.4", "gatsby-plugin-catch-links": "^2.0.4",
"gatsby-plugin-favicon": "^3.1.4", "gatsby-plugin-favicon": "^3.1.4",
"gatsby-plugin-feed": "^2.0.8", "gatsby-plugin-feed": "^2.0.8",
@ -78,7 +78,7 @@
"@babel/node": "^7.0.0", "@babel/node": "^7.0.0",
"@babel/preset-env": "^7.1.0", "@babel/preset-env": "^7.1.0",
"babel-eslint": "^10.0.1", "babel-eslint": "^10.0.1",
"eslint": "^5.6.1", "eslint": "^5.7.0",
"eslint-config-prettier": "^3.1.0", "eslint-config-prettier": "^3.1.0",
"eslint-loader": "^2.1.1", "eslint-loader": "^2.1.1",
"eslint-plugin-graphql": "^2.1.1", "eslint-plugin-graphql": "^2.1.1",

View File

@ -10,14 +10,15 @@
bottom: 0; bottom: 0;
z-index: 10; z-index: 10;
background: rgba($body-background-color, .9); background: rgba($body-background-color, .9);
backdrop-filter: blur(5px); // backdrop-filter: blur(5px);
animation: fadein .3s; animation: fadein .3s;
padding: $spacer / 2; padding: $spacer;
@media (min-width: $screen-sm) { @media (min-width: $screen-sm) {
display: flex; display: flex;
align-items: center; align-items: flex-start;
justify-content: center; justify-content: center;
padding-top: 6vh;
} }
} }
@ -42,20 +43,31 @@
cursor: pointer; cursor: pointer;
background: transparent; background: transparent;
border: 0; border: 0;
-webkit-appearance: none; appearance: none;
line-height: 1; line-height: 1;
font-size: $font-size-large; font-size: $font-size-h2;
padding: 3px; padding: 4px;
position: absolute; position: absolute;
top: 0; top: 0;
right: ($spacer/4); right: ($spacer/4);
color: $brand-grey; color: $brand-grey-light;
font-weight: 500; font-weight: 500;
&:hover,
&:focus {
color: $brand-grey;
}
} }
// Prevent background scrolling when modal is open
.isModalOpen { .isModalOpen {
// Prevent background scrolling when modal is open
overflow: hidden; overflow: hidden;
// more cross-browser backdrop-filter
body > div:first-child {
transition: filter .85s ease-out;
filter: blur(5px);
}
} }
.modal__title { .modal__title {

View File

@ -1,11 +1,85 @@
import React, { PureComponent } from 'react' import React, { PureComponent } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import Web3 from 'web3' import Web3 from 'web3'
import Input from '../atoms/Input'
import styles from './Web3Donation.module.scss' import styles from './Web3Donation.module.scss'
const ONE_SECOND = 1000 const ONE_SECOND = 1000
const ONE_MINUTE = ONE_SECOND * 60 const ONE_MINUTE = ONE_SECOND * 60
const InputGroup = ({
networkId,
selectedAccount,
amount,
onAmountChange,
handleWeb3Button
}) => (
<div className={styles.inputGroup}>
<div className={styles.input}>
<Input
type="number"
disabled={!(networkId === '1') || !selectedAccount}
value={amount}
onChange={onAmountChange}
min="0"
step="0.01"
/>
<div className={styles.currency}>
<span>ETH</span>
</div>
</div>
<button
className="btn btn-primary"
onClick={handleWeb3Button}
disabled={!(networkId === '1') || !selectedAccount}
>
Make it rain
</button>
</div>
)
InputGroup.propTypes = {
networkId: PropTypes.string,
selectedAccount: PropTypes.string,
amount: PropTypes.number,
onAmountChange: PropTypes.func,
handleWeb3Button: PropTypes.func
}
const Alerts = ({ accounts, networkId, error, transactionHash }) => {
if (error || accounts.length === 0) {
return (
<div className={styles.alert}>
{accounts.length === 0 &&
'Web3 detected, but no account. Are you logged into your MetaMask account?'}
{networkId !== '1' && 'Please connect to Main network'}
{error && error.message}
</div>
)
}
if (transactionHash) {
return (
<div className={styles.success}>
You are awesome, thanks!
<br />
<a href={`https://etherscan.io/tx/${transactionHash}`}>
See your transaction on etherscan.io.
</a>
</div>
)
}
return null
}
Alerts.propTypes = {
accounts: PropTypes.array,
networkId: PropTypes.string,
error: PropTypes.object,
transactionHash: PropTypes.string
}
export default class Web3Donation extends PureComponent { export default class Web3Donation extends PureComponent {
state = { state = {
web3Connected: false, web3Connected: false,
@ -13,6 +87,7 @@ export default class Web3Donation extends PureComponent {
networkId: null, networkId: null,
accounts: [], accounts: [],
selectedAccount: null, selectedAccount: null,
amount: 0.01,
receipt: null, receipt: null,
transactionHash: null, transactionHash: null,
loading: false, loading: false,
@ -120,7 +195,7 @@ export default class Web3Donation extends PureComponent {
{ {
from: this.state.selectedAccount, from: this.state.selectedAccount,
to: this.props.address, to: this.props.address,
value: '10000000000000000' value: this.state.amount * 1e18 // ETH -> Wei
}, },
(error, transactionHash) => { (error, transactionHash) => {
if (error) this.setState({ error, loading: false }) if (error) this.setState({ error, loading: false })
@ -130,57 +205,45 @@ export default class Web3Donation extends PureComponent {
) )
} }
onAmountChange = ({ target }) => {
this.setState({ amount: target.value })
}
render() { render() {
return ( return (
<div className={styles.web3}> <div className={styles.web3}>
<header>
<h4>web3</h4> <h4>web3</h4>
<p>Send a donation with MetaMask or Mist.</p> <p>Send Ether with MetaMask, Brave, or Mist.</p>
</header>
{this.state.web3Connected ? ( {this.state.web3Connected ? (
<div> <div className={styles.web3Row}>
{this.state.loading ? ( {this.state.loading ? (
'Hang on...' 'Hang on...'
) : ( ) : (
<button <InputGroup
className="btn btn-primary" networkId={this.state.networkId}
onClick={this.handleWeb3Button} selectedAccount={this.state.selectedAccount}
disabled={ amount={this.state.amount}
!(this.state.networkId === '1') || !this.state.selectedAccount onAmountChange={this.onAmountChange}
} handleWeb3Button={this.handleWeb3Button}
> />
Make it rain 0.01 Ξ
</button>
)} )}
{this.state.accounts.length === 0 && ( <Alerts
<div className={styles.alert}> accounts={this.state.accounts}
Web3 detected, but no account. Are you logged into your MetaMask networkId={this.state.networkId}
account? error={this.state.error}
</div> transactionHash={this.state.transactionHash}
)} />
{this.state.networkId !== '1' && (
<div className={styles.alert}>Please connect to Main network</div>
)}
{this.state.error && (
<div className={styles.alert}>{this.state.error.message}</div>
)}
{this.state.transactionHash && (
<div className={styles.success}>
You are awesome, thanks!
<br />
<a
href={`https://etherscan.io/tx/${this.state.transactionHash}`}
>
See your transaction on etherscan.io.
</a>
</div>
)}
</div> </div>
) : ( ) : (
<div className={styles.alert}>No Web3 capable browser detected.</div> <small>
No Web3 detected. Install <a href="https://metamask.io">MetaMask</a>
, <a href="https://brave.com">Brave</a>, or{' '}
<a href="https://github.com/ethereum/mist">Mist</a>.
</small>
)} )}
</div> </div>
) )

View File

@ -10,21 +10,87 @@
margin-bottom: $spacer; margin-bottom: $spacer;
padding-bottom: $spacer * 1.5; padding-bottom: $spacer * 1.5;
button { small {
color: darken($alert-info, 60%);
margin-top: -($spacer / 2);
display: block;
}
}
.web3Row {
min-height: 58px;
}
.inputGroup {
max-width: 17rem;
margin: auto; margin: auto;
position: relative;
@media (min-width: $screen-sm) {
display: flex;
max-width: 18rem;
} }
h4 { button {
font-size: $font-size-large; width: 100%;
margin-top: 0; border-top-left-radius: 0;
margin-bottom: $spacer / 4; border-top-right-radius: 0;
color: $brand-grey; border-color: lighten($brand-grey-light, 10%);
@media (min-width: $screen-sm) {
width: 50%;
border-top-right-radius: $border-radius;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left: 0;
}
}
}
.input {
position: relative;
@media (min-width: $screen-sm) {
width: 50%;
}
input {
text-align: center; text-align: center;
border: 1px solid lighten($brand-grey-light, 20%);
font-size: $font-size-large;
padding: $spacer / 3 $spacer / 3 $spacer / 3 $spacer * 1.7;
border-bottom: 0;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
@media (min-width: $screen-sm) {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
border-bottom-left-radius: $border-radius;
border-bottom: 1px solid lighten($brand-grey-light, 20%);
border-right: 0;
} }
p { &::-webkit-inner-spin-button {
color: $brand-grey-light; margin-left: -($spacer / 2);
} }
}
}
.currency {
position: absolute;
top: 1px;
bottom: 1px;
left: 1px;
font-size: $font-size-small;
padding: $spacer / 3;
color: $brand-grey-light;
background: $brand-light;
border-right: 1px solid rgba($brand-grey-light, .4);
border-top-left-radius: $border-radius;
border-bottom-left-radius: $border-radius;
display: flex;
align-items: center;
} }
.alert { .alert {

View File

@ -36,6 +36,11 @@ class ModalThanks extends PureComponent {
<div className={styles.modalThanks}> <div className={styles.modalThanks}>
<Web3Donation address={author.ether} /> <Web3Donation address={author.ether} />
<header>
<h4>Other wallets</h4>
<p>Send Bitcoin or Ether from any wallet.</p>
</header>
{Object.keys(author).map((address, i) => ( {Object.keys(author).map((address, i) => (
<div key={i} className={styles.coin}> <div key={i} className={styles.coin}>
<Qr title={address} address={author[address]} /> <Qr title={address} address={author[address]} />

View File

@ -6,6 +6,30 @@
justify-content: space-between; justify-content: space-between;
flex-wrap: wrap; flex-wrap: wrap;
} }
h4 {
text-align: center;
margin: 0;
margin-bottom: $spacer / 2;
color: $brand-grey;
text-transform: capitalize;
}
header {
width: 100%;
text-align: center;
margin-bottom: $spacer;
h4 {
font-size: $font-size-large;
margin-top: 0;
margin-bottom: $spacer / 6;
}
p {
color: $brand-grey-light;
}
}
} }
.coin { .coin {
@ -16,14 +40,6 @@
margin-top: 0; margin-top: 0;
} }
h4 {
font-size: $font-size-large;
margin-top: 0;
margin-bottom: $spacer / 2;
color: $brand-grey;
text-align: center;
}
> svg { > svg {
margin-bottom: $spacer / 2; margin-bottom: $spacer / 2;
} }

View File

@ -103,7 +103,7 @@ class RelatedPosts extends PureComponent {
className={`${styles.button} btn`} className={`${styles.button} btn`}
onClick={this.shufflePosts} onClick={this.shufflePosts}
> >
More Related Posts Refresh Related Posts
</button> </button>
</aside> </aside>
) )

View File

@ -43,7 +43,7 @@ pre {
border-radius: $border-radius; border-radius: $border-radius;
// make 'em scrollable // make 'em scrollable
overflow: scroll; overflow: auto;
-webkit-overflow-scrolling: touch; -webkit-overflow-scrolling: touch;
max-height: 300px; max-height: 300px;