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:
commit
e69a4d5833
10
README.md
10
README.md
@ -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:
|
||||||
|
|
||||||
|
@ -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",
|
||||||
|
@ -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 {
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
|
@ -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 {
|
||||||
|
@ -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]} />
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
)
|
)
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user