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

put web3 donation on page, kick out modal

This commit is contained in:
Matthias Kretschmann 2019-11-17 23:56:29 +01:00
parent eb892807eb
commit e5adbb68c5
Signed by: m
GPG Key ID: 606EEEF3C479A91F
19 changed files with 161 additions and 317 deletions

View File

@ -5,6 +5,9 @@ if (typeof window.IntersectionObserver === 'undefined') {
import('intersection-observer') import('intersection-observer')
} }
import wrapPageElementWithLayout from './src/helpers/wrapPageElement'
export const wrapPageElement = wrapPageElementWithLayout
// Display a message when a service worker updates // Display a message when a service worker updates
// https://www.gatsbyjs.org/docs/add-offline-support-with-a-service-worker/#displaying-a-message-when-a-service-worker-updates // https://www.gatsbyjs.org/docs/add-offline-support-with-a-service-worker/#displaying-a-message-when-a-service-worker-updates
export const onServiceWorkerUpdateReady = () => { export const onServiceWorkerUpdateReady = () => {

2
gatsby-ssr.js Normal file
View File

@ -0,0 +1,2 @@
import wrapPageElementWithLayout from './src/helpers/wrapPageElement'
export const wrapPageElement = wrapPageElementWithLayout

View File

@ -1,124 +0,0 @@
@import 'variables';
.modal {
position: fixed;
overflow: auto;
-webkit-overflow-scrolling: touch;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 9;
background: rgba($body-background-color, 0.95);
backdrop-filter: blur(5px);
animation: fadein 0.3s;
padding: $spacer;
:global(.dark) & {
background: rgba($body-background-color--dark, 0.95);
}
@media (min-width: $screen-sm) {
display: flex;
align-items: flex-start;
justify-content: center;
padding-top: 6vh;
}
}
.modalContent {
outline: 0;
background: $body-background-color;
position: relative;
border-radius: $border-radius;
border: 1px solid rgba($brand-grey-light, 0.4);
box-shadow: 0 5px 30px rgba($brand-grey-light, 0.2);
padding: 0 $spacer / 2 $spacer / 2;
max-width: 100%;
:global(.dark) & {
background: $body-background-color--dark;
box-shadow: 0 5px 30px rgba(darken($brand-main, 20%), 0.5);
}
@media (min-width: $screen-md) {
max-width: $screen-sm;
padding: 0 $spacer $spacer;
}
}
.modalClose {
display: block;
cursor: pointer;
background: transparent;
border: 0;
appearance: none;
padding: 4px;
position: absolute;
top: $spacer / 4;
right: ($spacer/4);
outline: 0;
svg {
width: 24px;
height: 24px;
stroke: $brand-grey-light;
}
&:hover,
&:focus {
svg {
stroke: $brand-cyan;
}
}
}
.isModalOpen {
// Prevent background scrolling when modal is open
overflow: hidden;
// more cross-browser backdrop-filter
// body > div:first-child {
// transition: filter .85s ease-out;
// filter: blur(5px);
// }
}
.modalTitle {
font-size: $font-size-h4;
margin-top: $spacer / 2;
margin-bottom: $spacer / 2;
margin-left: -($spacer / 2);
margin-right: -($spacer / 2);
border-bottom: 1px solid rgba($brand-grey-light, 0.4);
padding: 0 $spacer;
padding-bottom: ($spacer/2);
@media (min-width: $screen-md) {
margin-left: -($spacer);
margin-right: -($spacer);
}
}
//
// Overlay/content animations
//
@keyframes fadein {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes fadeout {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}

View File

@ -1,23 +0,0 @@
import React from 'react'
import { render } from '@testing-library/react'
import Modal from './Modal'
import ReactModal from 'react-modal'
describe('Modal', () => {
it('renders without crashing', () => {
ReactModal.setAppElement(document.createElement('div'))
const { rerender } = render(
<Modal title="Hello" isOpen handleCloseModal={() => null}>
Hello
</Modal>
)
expect(document.querySelector('.ReactModalPortal')).toBeInTheDocument()
rerender(
<Modal isOpen={false} handleCloseModal={() => null}>
Hello
</Modal>
)
})
})

View File

@ -1,38 +0,0 @@
import React from 'react'
import ReactModal from 'react-modal'
import Icon from './Icon'
import styles from './Modal.module.scss'
if (process.env.NODE_ENV !== 'test') ReactModal.setAppElement('#___gatsby')
export default function Modal({
title,
isOpen,
handleCloseModal,
children,
...props
}: {
title?: string
isOpen?: boolean
handleCloseModal(): void
children: any
}) {
if (!isOpen) return null
return (
<ReactModal
overlayClassName={styles.modal}
className={styles.modalContent}
htmlOpenClassName={styles.isModalOpen}
shouldReturnFocusAfterClose={false}
isOpen={isOpen}
{...props}
>
{title && <h1 className={styles.modalTitle}>{title}</h1>}
{children}
<button className={styles.modalClose} onClick={handleCloseModal}>
<Icon name="X" />
</button>
</ReactModal>
)
}

View File

@ -5,10 +5,11 @@
} }
.code { .code {
margin: 0; margin: 0 auto;
position: relative; position: relative;
padding: 0; padding: 0;
padding-right: 2rem; padding-right: 2rem;
width: fit-content;
code { code {
padding: $spacer / 2; padding: $spacer / 2;

View File

@ -24,6 +24,10 @@
composes: alert; composes: alert;
color: darken($alert-error, 60%); color: darken($alert-error, 60%);
:global(.dark) & {
color: darken($alert-error, 40%);
}
&::after { &::after {
display: none; display: none;
} }

View File

@ -9,7 +9,7 @@ export const alertMessages = (
'Web3 detected, but no account. Are you logged into your MetaMask account?', 'Web3 detected, but no account. Are you logged into your MetaMask account?',
noCorrectNetwork: `Please connect to <strong>Main</strong> network. You are on <strong>${networkName}</strong> right now.`, noCorrectNetwork: `Please connect to <strong>Main</strong> network. You are on <strong>${networkName}</strong> right now.`,
noWeb3: noWeb3:
'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>.', 'No Web3 detected. Install <a href="https://metamask.io">MetaMask</a> or <a href="https://brave.com">Brave</a>.',
transaction: `<a href="https://etherscan.io/tx/${transactionHash}" target="_blank">See your transaction on etherscan.io.</a>`, transaction: `<a href="https://etherscan.io/tx/${transactionHash}" target="_blank">See your transaction on etherscan.io.</a>`,
waitingForUser: 'Waiting for your confirmation', waitingForUser: 'Waiting for your confirmation',
waitingConfirmation: 'Waiting for network confirmation, hang on', waitingConfirmation: 'Waiting for network confirmation, hang on',

View File

@ -1,14 +1,11 @@
@import 'variables'; @import 'variables';
@import 'mixins';
.web3 { .web3 {
@include divider;
width: 100%; width: 100%;
text-align: center; text-align: center;
margin-top: $spacer / 2; margin-top: $spacer / 2;
margin-bottom: $spacer; margin-bottom: $spacer;
padding-bottom: $spacer * 1.5; padding-bottom: $spacer;
small { small {
color: darken($alert-info, 60%); color: darken($alert-info, 60%);

View File

@ -88,7 +88,7 @@ export default class Web3Donation extends PureComponent<
initAccountsPoll() { initAccountsPoll() {
if (!this.interval) { if (!this.interval) {
this.interval = setInterval(this.fetchAccounts, ONE_SECOND) this.interval = setInterval(this.fetchAccounts, ONE_SECOND * 10)
} }
} }
@ -184,8 +184,8 @@ export default class Web3Donation extends PureComponent<
return ( return (
<div className={styles.web3}> <div className={styles.web3}>
<header> <header>
<h4>web3</h4> <h4>Web3 Wallet</h4>
<p>Send Ether with MetaMask, Brave, or Mist.</p> <p>Send Ether with MetaMask or Brave.</p>
</header> </header>
<div className={styles.web3Row}> <div className={styles.web3Row}>

View File

@ -1,20 +1,13 @@
import React, { useState } from 'react' import React from 'react'
import { Link } from 'gatsby'
import Container from '../atoms/Container' import Container from '../atoms/Container'
import Icon from '../atoms/Icon' import Icon from '../atoms/Icon'
import Vcard from '../molecules/Vcard' import Vcard from '../molecules/Vcard'
import ThemeSwitch from '../molecules/ThemeSwitch' import ThemeSwitch from '../molecules/ThemeSwitch'
import ModalThanks from './ModalThanks'
import styles from './Footer.module.scss'
import { useSiteMetadata } from '../../hooks/use-site-metadata' import { useSiteMetadata } from '../../hooks/use-site-metadata'
import styles from './Footer.module.scss'
function Copyright({ function Copyright() {
toggleModal,
showModal
}: {
toggleModal(): void
showModal: boolean
}) {
const { name, uri, bitcoin, github } = useSiteMetadata().author const { name, uri, bitcoin, github } = useSiteMetadata().author
const year = new Date().getFullYear() const year = new Date().getFullYear()
@ -30,33 +23,23 @@ function Copyright({
<Icon name="GitHub" /> <Icon name="GitHub" />
View source View source
</a> </a>
<button className={styles.btc} onClick={toggleModal}> <Link to="/thanks" className={styles.btc}>
<Icon name="Bitcoin" /> <Icon name="Bitcoin" />
<code>{bitcoin}</code> <code>{bitcoin}</code>
</button> </Link>
</p> </p>
{showModal && (
<ModalThanks isOpen={showModal} handleCloseModal={toggleModal} />
)}
</section> </section>
) )
} }
export default function Footer() { export default function Footer() {
const [showModal, setShowModal] = useState(false)
const toggleModal = () => {
setShowModal(!showModal)
}
return ( return (
<footer role="contentinfo" className={styles.footer}> <footer role="contentinfo" className={styles.footer}>
<Container> <Container>
<ThemeSwitch /> <ThemeSwitch />
<Vcard /> <Vcard />
<Copyright showModal={showModal} toggleModal={toggleModal} /> <Copyright />
</Container> </Container>
</footer> </footer>
) )

View File

@ -1,47 +0,0 @@
import React, { lazy, Suspense } from 'react'
import shortid from 'shortid'
import { Author } from '../../@types/Site'
import { useSiteMetadata } from '../../hooks/use-site-metadata'
import Qr from '../atoms/Qr'
import Modal from '../atoms/Modal'
import styles from './ModalThanks.module.scss'
const Web3Donation = lazy(() => import('../molecules/Web3Donation'))
const Coin = ({ address, author }: { address: string; author: Author }) => (
<div className={styles.coin}>
<Suspense fallback={<div>Loading...</div>}>
<Qr title={address} address={(author as any)[address]} />
</Suspense>
</div>
)
export default function ModalThanks(props: any) {
const { author } = useSiteMetadata()
const coins = Object.keys(author).filter(
key => key === 'bitcoin' || key === 'ether'
)
return (
<Modal
{...props}
contentLabel="Say thanks with Bitcoin or Ether"
title="Say thanks"
>
<div className={styles.modalThanks}>
<Suspense fallback={<div>Loading...</div>}>
<Web3Donation address={author.ether} />
</Suspense>
<header>
<h4>Any other wallets</h4>
<p>Send Bitcoin or Ether from any wallet.</p>
</header>
{coins.map((address: string) => (
<Coin key={shortid.generate()} address={address} author={author} />
))}
</div>
</Modal>
)
}

View File

@ -0,0 +1,8 @@
import React from 'react'
import Layout from '../components/Layout'
const wrapPageElement = ({ element, props }: { element: any; props: any }) => (
<Layout {...props}>{element}</Layout>
)
export default wrapPageElement

View File

@ -1,11 +1,18 @@
@import 'variables'; @import 'variables';
@import 'mixins';
.modalThanks { .buttonBack {
@media (min-width: $screen-sm) { svg {
display: flex; stroke: $brand-grey-light;
justify-content: space-between; display: inline-block;
flex-wrap: wrap; margin-bottom: -0.15rem;
} }
}
.thanks {
@include breakoutviewport;
min-height: 100vh;
h4 { h4 {
text-align: center; text-align: center;
@ -41,6 +48,22 @@
} }
} }
.title {
margin-top: 0;
margin-bottom: $spacer * 2;
font-size: $font-size-h2;
}
.coins {
width: 100%;
@media (min-width: $screen-sm) {
display: flex;
justify-content: space-between;
flex-wrap: wrap;
}
}
.coin { .coin {
margin-top: $spacer; margin-top: $spacer;
@ -49,3 +72,8 @@
margin-top: 0; margin-top: 0;
} }
} }
.loading {
width: 100%;
text-align: center;
}

69
src/pages/thanks.tsx Normal file
View File

@ -0,0 +1,69 @@
import React, { lazy, Suspense } from 'react'
import shortid from 'shortid'
import Helmet from 'react-helmet'
import { Author } from '../@types/Site'
import { useSiteMetadata } from '../hooks/use-site-metadata'
import Typekit from '../components/atoms/Typekit'
import Qr from '../components/atoms/Qr'
import Icon from '../components/atoms/Icon'
import styles from './thanks.module.scss'
const Web3Donation = lazy(() => import('../components/molecules/Web3Donation'))
const Coin = ({ address, author }: { address: string; author: Author }) => (
<div className={styles.coin}>
<Qr title={address} address={(author as any)[address]} />
</div>
)
const BackButton = () => (
<button
className={`link ${styles.buttonBack}`}
onClick={() => window.history.back()}
>
<Icon name="ChevronLeft" /> Go Back
</button>
)
export default function Thanks() {
const { author } = useSiteMetadata()
const coins = Object.keys(author).filter(
key => key === 'bitcoin' || key === 'ether'
)
return (
<>
<Helmet>
<title>Say thanks</title>
<meta name="robots" content="noindex,nofollow" />
</Helmet>
<article className={styles.thanks}>
<BackButton />
<header>
<h1 className={styles.title}>Say Thanks</h1>
</header>
<Suspense fallback={<div className={styles.loading}>Loading...</div>}>
<Web3Donation address={author.ether} />
<div className={styles.coins}>
<header>
<h4>Any other wallets</h4>
<p>Send Bitcoin or Ether from any wallet.</p>
</header>
{coins.map((address: string) => (
<Coin
key={shortid.generate()}
address={address}
author={author}
/>
))}
</div>
</Suspense>
</article>
</>
)
}

View File

@ -2,7 +2,6 @@ import React from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { Post } from '../@types/Post' import { Post } from '../@types/Post'
import SEO from '../components/atoms/SEO' import SEO from '../components/atoms/SEO'
import Layout from '../components/Layout'
import styles from './Page.module.scss' import styles from './Page.module.scss'
export default function Page({ export default function Page({
@ -23,10 +22,8 @@ export default function Page({
<Helmet title={title} /> <Helmet title={title} />
<SEO slug={location.pathname} postSEO post={post} /> <SEO slug={location.pathname} postSEO post={post} />
<Layout location={location}> <h1 className={styles.pageTitle}>{title}</h1>
<h1 className={styles.pageTitle}>{title}</h1> {section ? <section className={section}>{children}</section> : children}
{section ? <section className={section}>{children}</section> : children}
</Layout>
</> </>
) )
} }

View File

@ -1,5 +1,4 @@
import React, { useState } from 'react' import React, { useState } from 'react'
import ModalThanks from '../../components/organisms/ModalThanks'
import { useSiteMetadata } from '../../hooks/use-site-metadata' import { useSiteMetadata } from '../../hooks/use-site-metadata'
import styles from './PostActions.module.scss' import styles from './PostActions.module.scss'
import Icon from '../../components/atoms/Icon' import Icon from '../../components/atoms/Icon'
@ -38,13 +37,8 @@ export default function PostActions({
githubLink: string githubLink: string
}) { }) {
const { siteUrl } = useSiteMetadata() const { siteUrl } = useSiteMetadata()
const [showModal, setShowModal] = useState(false)
const urlTwitter = `https://twitter.com/intent/tweet?text=@kremalicious&url=${siteUrl}${slug}` const urlTwitter = `https://twitter.com/intent/tweet?text=@kremalicious&url=${siteUrl}${slug}`
const toggleModal = () => {
setShowModal(!showModal)
}
return ( return (
<aside className={styles.actions}> <aside className={styles.actions}>
<Action <Action
@ -55,17 +49,13 @@ export default function PostActions({
<Action <Action
title="Found something useful?" title="Found something useful?"
text="Say thanks with Bitcoin or Ether" text="Say thanks with Bitcoin or Ether"
onClick={toggleModal} url="/thanks"
/> />
<Action <Action
title="Edit on GitHub" title="Edit on GitHub"
text="Contribute to this post on GitHub" text="Contribute to this post on GitHub"
url={githubLink} url={githubLink}
/> />
{showModal && (
<ModalThanks isOpen={showModal} handleCloseModal={toggleModal} />
)}
</aside> </aside>
) )
} }

View File

@ -2,7 +2,6 @@ import React from 'react'
import { Helmet } from 'react-helmet' import { Helmet } from 'react-helmet'
import { graphql } from 'gatsby' import { graphql } from 'gatsby'
import { Post as PostMetadata } from '../../@types/Post' import { Post as PostMetadata } from '../../@types/Post'
import Layout from '../../components/Layout'
import Exif from '../../components/atoms/Exif' import Exif from '../../components/atoms/Exif'
import SEO from '../../components/atoms/SEO' import SEO from '../../components/atoms/SEO'
import RelatedPosts from '../../components/molecules/RelatedPosts' import RelatedPosts from '../../components/molecules/RelatedPosts'
@ -18,11 +17,9 @@ import { Image } from '../../components/atoms/Image'
export default function Post({ export default function Post({
data, data,
location,
pageContext: { next, prev } pageContext: { next, prev }
}: { }: {
data: { post: PostMetadata } data: { post: PostMetadata }
location: Location
pageContext: { pageContext: {
next: { title: string; slug: string } next: { title: string; slug: string }
prev: { title: string; slug: string } prev: { title: string; slug: string }
@ -40,36 +37,34 @@ export default function Post({
<SEO slug={slug} post={post} postSEO /> <SEO slug={slug} post={post} postSEO />
<Layout location={location}> <article className={styles.hentry}>
<article className={styles.hentry}> <header>
<header> <PostTitle type={type} linkurl={linkurl} title={title} />
<PostTitle type={type} linkurl={linkurl} title={title} /> {type === 'post' && <PostLead post={post} />}
{type === 'post' && <PostLead post={post} />} </header>
</header>
{type === 'photo' && <PostContent post={post} />} {type === 'photo' && <PostContent post={post} />}
{image && ( {image && (
<Image <Image
fluid={image.childImageSharp.fluid} fluid={image.childImageSharp.fluid}
alt={title} alt={title}
original={image.childImageSharp.original} original={image.childImageSharp.original}
/> />
)} )}
{type === 'photo' && image && image.fields && ( {type === 'photo' && image && image.fields && (
<Exif exif={image.fields.exif} /> <Exif exif={image.fields.exif} />
)} )}
{type !== 'photo' && <PostContent post={post} />} {type !== 'photo' && <PostContent post={post} />}
{type === 'link' && <PostLinkActions slug={slug} linkurl={linkurl} />} {type === 'link' && <PostLinkActions slug={slug} linkurl={linkurl} />}
<PostActions slug={slug} githubLink={githubLink} /> <PostActions slug={slug} githubLink={githubLink} />
<PostMeta post={post} /> <PostMeta post={post} />
</article> </article>
<RelatedPosts photos={type === 'photo'} tags={tags} /> <RelatedPosts photos={type === 'photo'} tags={tags} />
<PrevNext prev={prev} next={next} /> <PrevNext prev={prev} next={next} />
</Layout>
</> </>
) )
} }

View File

@ -1,7 +1,6 @@
import React from 'react' import React from 'react'
import { Link, graphql } from 'gatsby' import { Link, graphql } from 'gatsby'
import { Post } from '../@types/Post' import { Post } from '../@types/Post'
import Layout from '../components/Layout'
import Pagination from '../components/molecules/Pagination' import Pagination from '../components/molecules/Pagination'
import Featured from '../components/molecules/Featured' import Featured from '../components/molecules/Featured'
import PostTitle from './Post/PostTitle' import PostTitle from './Post/PostTitle'
@ -69,7 +68,7 @@ export default function Posts({
}) })
return ( return (
<Layout location={location}> <>
<SEO /> <SEO />
{location.pathname === '/' && <Featured />} {location.pathname === '/' && <Featured />}
{tag && ( {tag && (
@ -85,7 +84,7 @@ export default function Posts({
)} )}
{PostsList} {PostsList}
{numPages > 1 && <Pagination pageContext={pageContext} />} {numPages > 1 && <Pagination pageContext={pageContext} />}
</Layout> </>
) )
} }