snippets and account pages

main
Mike Fix 5 years ago
parent 5ebd355ef0
commit 31747420d1

@ -0,0 +1,277 @@
import React from 'react'
import { Elements, StripeProvider, CardElement, injectStripe } from 'react-stripe-elements'
import { useAsyncCallback } from '@dawnlabs/tacklebox'
import Button from './Button'
import Input from './Input'
import { useAuth } from './AuthContext'
import LoginButton from './LoginButton'
import { COLORS } from '../lib/constants'
const X = (
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 17 17">
<path
className="base"
fill="white"
d="M8.5,17 C3.80557963,17 0,13.1944204 0,8.5 C0,3.80557963 3.80557963,0 8.5,0 C13.1944204,0 17,3.80557963 17,8.5 C17,13.1944204 13.1944204,17 8.5,17 Z"
/>
<path
className="glyph"
fill={COLORS.BLACK}
d="M8.5,7.29791847 L6.12604076,4.92395924 C5.79409512,4.59201359 5.25590488,4.59201359 4.92395924,4.92395924 C4.59201359,5.25590488 4.59201359,5.79409512 4.92395924,6.12604076 L7.29791847,8.5 L4.92395924,10.8739592 C4.59201359,11.2059049 4.59201359,11.7440951 4.92395924,12.0760408 C5.25590488,12.4079864 5.79409512,12.4079864 6.12604076,12.0760408 L8.5,9.70208153 L10.8739592,12.0760408 C11.2059049,12.4079864 11.7440951,12.4079864 12.0760408,12.0760408 C12.4079864,11.7440951 12.4079864,11.2059049 12.0760408,10.8739592 L9.70208153,8.5 L12.0760408,6.12604076 C12.4079864,5.79409512 12.4079864,5.25590488 12.0760408,4.92395924 C11.7440951,4.59201359 11.2059049,4.59201359 10.8739592,4.92395924 L8.5,7.29791847 L8.5,7.29791847 Z"
/>
</svg>
)
function Billing(props) {
const user = useAuth()
const [submit, { error, loading, data: success }] = useAsyncCallback(async e => {
e.preventDefault()
const name = e.target.name.value.trim()
const res = await props.stripe.createToken({ name })
if (res.error) {
throw res.error.message
}
return {}
})
if (!user) {
return (
<div className="login">
<div>
<LoginButton />
</div>
<style jsx>
{`
.login {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
`}
</style>
</div>
)
}
return (
<div className="checkout">
{success ? (
<div className="column">
<h4>Thank you for supporting Carbon!</h4>
<p className="success">
However, Carbon Diamond is not quite ready yet.
<br />
Your card has <u>not</u> been charged or saved today.
<br />
We greatly appreciate your support, and will contact you when these premium features
launch!
</p>
<p className="success">
the Carbon Team{' '}
<span role="img" aria-label="Black and yellow hearts">
💛🖤
</span>
</p>
</div>
) : (
<div className="column">
<h4>
Upgrade to <span>Diamond</span>
<br />
<span className="tag">($5.00 / month)</span>
</h4>
<p>Please enter a credit or debit card:</p>
<form onSubmit={submit}>
<fieldset>
<CardElement
{...{
iconStyle: 'solid',
style: {
base: {
iconColor: COLORS.BLUE,
color: COLORS.BLUE,
fontWeight: 500,
fontFamily: 'Roboto, Open Sans, Segoe UI, sans-serif',
fontSize: '16px',
fontSmoothing: 'antialiased',
':-webkit-autofill': {
color: '#fce883'
},
'::placeholder': {
color: 'rgba(255, 255, 255, 0.7)'
}
},
invalid: {
iconColor: COLORS.RED,
color: COLORS.RED
}
}
}}
/>
<hr />
<Input placeholder="Cardholders's name..." name="name" required />
</fieldset>
<small>
(By clicking subscribe, you are accepting the{' '}
<a href="/terms">terms and conditions</a>)
</small>
<br />
<Button
display="inline-flex"
border
large
padding="8px 16px"
margin="8px 0 0"
type="submit"
color="rgba(255, 255, 255, 0.7)"
>
{loading ? 'Sending...' : 'Subscribe'}
</Button>
<div className={`error ${error ? 'visible' : ''}`} role="alert">
{X}
<span className="message">{error}</span>
</div>
</form>
</div>
)}
<style jsx>{`
.checkout {
position: relative;
font-size: 16px;
font-weight: 500;
border-radius: 4px;
padding: 1rem 1.5rem;
color: white;
background-color: black;
}
a {
text-decoration: underline;
}
p {
margin: 0 0 8px;
font-size: 12px;
font-weight: normal;
}
small {
font-size: 10px;
}
h4 {
font-size: 32px;
margin: 0 0 2rem;
}
.tag {
display: block;
font-weight: lighter;
color: rgba(255, 255, 255, 0.8);
font-size: 16px;
margin-top: 0.25rem;
}
hr {
border: 0;
height: 1px;
margin: 0.5rem 0 1rem;
background: ${COLORS.SECONDARY};
}
fieldset {
width: 100%;
margin: 0 0 2.5rem;
padding: 0.5rem 0.5rem 0.75rem;
border: 1px solid ${COLORS.SECONDARY};
border-radius: 4px;
}
fieldset :global(input) {
text-align: left;
font-size: 16px;
color: ${COLORS.BLUE};
}
fieldset :global(input::placeholder) {
opacity: 1;
color: rgba(255, 255, 255, 0.7);
}
fieldset :global(.StripeElement) {
width: 100%;
padding: 12px 16px 12px 0;
}
form:valid :global(button) {
color: ${COLORS.BLUE};
box-shadow: inset 0px 0px 0px 1px ${COLORS.BLUE};
}
.error {
display: inline-flex;
justify-content: flex-start;
align-items: center;
position: relative;
top: +3px;
opacity: 0;
margin-left: 1rem;
font-size: 12px;
transform: translateY(20px);
transition-property: opacity, transform;
transition-duration: 0.35s;
transition-timing-function: cubic-bezier(0.165, 0.84, 0.44, 1);
}
.error.visible {
opacity: 1;
transform: none;
}
.error svg {
margin-top: -1px;
}
.error .message {
margin-left: 8px;
font-size: inherit;
color: ${COLORS.RED};
}
.success {
font-size: 16px;
line-height: 1.5;
margin: 0 0 2rem;
}
`}</style>
</div>
)
}
const BillingWithStripe = injectStripe(Billing)
export default function() {
const [stripe, setStripe] = React.useState(null)
React.useEffect(() => {
setStripe(window.Stripe(process.env.STRIPE_PUBLIC_KEY))
}, [])
return (
<StripeProvider stripe={stripe}>
<Elements>
<BillingWithStripe />
</Elements>
</StripeProvider>
)
}

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
// import Link from 'next/link' import Link from 'next/link'
import firebase, { login, logout } from '../lib/client' import firebase, { login, logout } from '../lib/client'
import Button from './Button' import Button from './Button'
@ -8,14 +8,26 @@ import { useAuth } from './AuthContext'
function Drawer(props) { function Drawer(props) {
return ( return (
<Popout hidden={!props.isVisible} pointerLeft="14px" style={{ width: '180px', left: 0 }}> <Popout hidden={!props.isVisible} pointerLeft="14px" style={{ width: '160px', left: 0 }}>
<div className="flex"> <div className="flex">
{/* <Link href="/settings"> <Link href="/snippets">
<Button large center padding="8px 0"> <Button large center padding="0.5rem 0" style={{ borderBottom: '1px solid' }}>
Settings <img src="/static/svg/snippets.svg" alt="Snippets page" width="16px" /> Snippets{' '}
{/* FILES? */}
</Button> </Button>
</Link> */} </Link>
<Button large center padding="8px 0" onClick={logout}> <Link href="/account">
<Button large center padding="0.5rem 0" style={{ borderBottom: '1px solid' }}>
<img
src="/static/svg/person.svg"
alt="Account"
width="16px"
style={{ left: '-2px', marginRight: 'calc(1rem - 3px)' }}
/>{' '}
Account
</Button>
</Link>
<Button large center padding="0.5rem 0" onClick={logout}>
Sign Out Sign Out
</Button> </Button>
</div> </div>
@ -23,8 +35,13 @@ function Drawer(props) {
{` {`
.flex { .flex {
display: flex; display: flex;
flex-direction: column;
height: 100%; height: 100%;
} }
img {
position: relative;
margin-right: 1rem;
}
`} `}
</style> </style>
</Popout> </Popout>
@ -61,7 +78,7 @@ function LoginButton({ isVisible, toggleVisibility }) {
> >
<img <img
height={20} height={20}
src={user ? user.photoURL : '/static/github.svg'} src={user ? user.photoURL : '/static/svg/github.svg'}
alt={user ? user.displayName : 'GitHub'} alt={user ? user.displayName : 'GitHub'}
/> />
<span>{user ? user.displayName : 'Sign in/up'}</span> <span>{user ? user.displayName : 'Sign in/up'}</span>

@ -21,7 +21,7 @@ describe('background color', () => {
cy.visit('/') cy.visit('/')
openPicker() openPicker()
closePicker() closePicker()
cy.get(picker).should('not.be.visible') cy.get(picker).should('not.exist')
}) })
it('changes background color to dark red', () => { it('changes background color to dark red', () => {

@ -83,6 +83,21 @@ function getSnippet(uid = '', { host } = {}) {
}) })
} }
function listSnippets(page, headers) {
return client
.get(`/snippets`, {
params: {
page
},
headers
})
.then(res => res.data)
.catch(e => {
console.error(e)
throw e
})
}
function isNotDefaultSetting(v, k) { function isNotDefaultSetting(v, k) {
return v === DEFAULT_SETTINGS[k] || !Object.prototype.hasOwnProperty.call(DEFAULT_SETTINGS, k) return v === DEFAULT_SETTINGS[k] || !Object.prototype.hasOwnProperty.call(DEFAULT_SETTINGS, k)
} }
@ -131,6 +146,7 @@ const createSnippet = debounce(data => updateSnippet(null, data), ms('5s'), {
export default { export default {
snippet: { snippet: {
get: getSnippet, get: getSnippet,
list: listSnippets,
update: debounce(updateSnippet, ms('1s'), { leading: true, trailing: true }), update: debounce(updateSnippet, ms('1s'), { leading: true, trailing: true }),
create: createSnippet, create: createSnippet,
delete: id => deleteSnippet(id) delete: id => deleteSnippet(id)

@ -30,4 +30,12 @@ export function login(provider) {
.catch(console.error) .catch(console.error)
} }
export function loginGitHub() {
const provider = new firebase.auth.GithubAuthProvider()
provider.setCustomParameters({
allow_signup: 'true'
})
return login(provider)
}
export default firebase.apps.length ? firebase : null export default firebase.apps.length ? firebase : null

@ -28,7 +28,8 @@ const config = withOffline({
FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID, FIREBASE_PROJECT_ID: process.env.FIREBASE_PROJECT_ID,
FIREBASE_MESSAGING_SENDER_ID: process.env.FIREBASE_MESSAGING_SENDER_ID, FIREBASE_MESSAGING_SENDER_ID: process.env.FIREBASE_MESSAGING_SENDER_ID,
FIREBASE_FE_APP_ID: process.env.FIREBASE_FE_APP_ID, FIREBASE_FE_APP_ID: process.env.FIREBASE_FE_APP_ID,
FIREBASE_API_KEY: process.env.FIREBASE_API_KEY FIREBASE_API_KEY: process.env.FIREBASE_API_KEY,
STRIPE_PUBLIC_KEY: process.env.STRIPE_PUBLIC_KEY
} }
}) })

@ -26,17 +26,27 @@
"Service-Worker-Allowed": "/" "Service-Worker-Allowed": "/"
} }
}, },
{
"src": "/",
"continue": true,
"headers": {
"X-Frame-Options": "SAMEORIGIN"
}
},
{ {
"src": "^/(.*)/?", "src": "^/(.*)/?",
"continue": true, "continue": true,
"headers": { "headers": {
"X-Frame-Options": "SAMEORIGIN",
"X-XSS-Protection": "1; mode=block", "X-XSS-Protection": "1; mode=block",
"X-Content-Type-Options": "nosniff", "X-Content-Type-Options": "nosniff",
"Referrer-Policy": "no-referrer-when-downgrade", "Referrer-Policy": "no-referrer-when-downgrade",
"Feature-Policy": "geolocation 'self'; microphone 'self'; camera 'self'" "Feature-Policy": "geolocation 'self'; microphone 'self'; camera 'self'"
} }
}, },
{
"src": "/embed/",
"dest": "/embed.html"
},
{ "handle": "filesystem" } { "handle": "filesystem" }
], ],
"build": { "build": {
@ -45,7 +55,8 @@
"FIREBASE_PROJECT_ID": "@carbon-firebase-project-id", "FIREBASE_PROJECT_ID": "@carbon-firebase-project-id",
"FIREBASE_MESSAGING_SENDER_ID": "@carbon-firebase-messaging-sender-id", "FIREBASE_MESSAGING_SENDER_ID": "@carbon-firebase-messaging-sender-id",
"FIREBASE_FE_APP_ID": "@carbon-firebase-fe-app-id", "FIREBASE_FE_APP_ID": "@carbon-firebase-fe-app-id",
"FIREBASE_API_KEY": "@carbon-firebase-api-key" "FIREBASE_API_KEY": "@carbon-firebase-api-key",
"STRIPE_PUBLIC_KEY": "@carbon-stripe-public-key"
} }
}, },
"github": { "github": {

@ -26,6 +26,7 @@
"codemirror-mode-elixir": "^1.1.2", "codemirror-mode-elixir": "^1.1.2",
"codemirror-solidity": "^0.2.1", "codemirror-solidity": "^0.2.1",
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"date-fns": "^2.0.1",
"dom-to-image": "^2.6.0", "dom-to-image": "^2.6.0",
"downshift": "^3.2.12", "downshift": "^3.2.12",
"dropperx": "^1.0.1", "dropperx": "^1.0.1",
@ -51,6 +52,7 @@
"react-image-crop": "^6.0.16", "react-image-crop": "^6.0.16",
"react-is": "^16.9.0", "react-is": "^16.9.0",
"react-spinner": "^0.2.7", "react-spinner": "^0.2.7",
"react-stripe-elements": "^4.0.1",
"react-syntax-highlight": "^15.3.1", "react-syntax-highlight": "^15.3.1",
"tohash": "^1.0.2" "tohash": "^1.0.2"
}, },

@ -5,7 +5,9 @@ export default class extends Document {
render() { render() {
return ( return (
<html lang="en"> <html lang="en">
<Head /> <Head>
<script src="https://js.stripe.com/v3/" />
</Head>
<body> <body>
<Main /> <Main />
<NextScript /> <NextScript />

@ -24,7 +24,7 @@ export default () => (
<div className="mt2"> <div className="mt2">
<img <img
width="632px" width="632px"
src="/static/open-source-companies.svg" src="/static/svg/open-source-companies.svg"
alt="Companies that trust Carbon: Google, Airbnb, GitHub, Coinbase, and Microsoft" alt="Companies that trust Carbon: Google, Airbnb, GitHub, Coinbase, and Microsoft"
/> />
</div> </div>

@ -0,0 +1,258 @@
// Theirs
import React from 'react'
import dynamic from 'next/dynamic'
// Ours
import Button from '../components/Button'
import Page from '../components/Page'
import MenuButton from '../components/MenuButton'
import { MetaLinks } from '../components/Meta'
import { useAuth } from '../components/AuthContext'
import { loginGitHub, logout } from '../lib/client'
import { COLORS } from '../lib/constants'
const Billing = dynamic(() => import('../components/Billing'), {
loading: () => <div style={{ minHeight: '360px' }} />
})
function logoutThunk() {
return logout
}
const soon = <span title="Coming Soon"></span>
function Plan({ selectBilling }) {
const user = useAuth()
function handleSelectFree() {
if (!user) {
loginGitHub()
}
}
function handleSelectUpgrade() {
if (!user) {
return loginGitHub()
}
selectBilling()
}
return (
<div className="plan">
<table>
<thead>
<tr>
<td />
<td>
<h3>Free</h3>
</td>
<td>
<h3 style={{ color: COLORS.BLUE }}>Diamond</h3>
</td>
</tr>
</thead>
<tbody>
<tr>
<td>PNG/SVG Exports</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Full visual editor</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Custom backgrounds</td>
<td></td>
<td></td>
</tr>
<tr>
<td>GitHub Gist support</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Saved snippets</td>
<td>1000</td>
<td></td>
</tr>
<tr>
<td>Embed saved snippets</td>
<td></td>
<td></td>
</tr>
<tr>
<td>API Access {soon}</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Saved custom themes/presets {soon}</td>
<td></td>
<td></td>
</tr>
<tr>
<td>Twitter card unfurls {soon}</td>
<td></td>
<td></td>
</tr>
<tr>
<td></td>
<td>FREE FOREVER</td>
<td>$5.00 / month</td>
</tr>
<tr>
<td></td>
<td>
<Button
large
margin="0 auto"
center
border
padding="4px 8px"
color="white"
disabled={user && user.plan === 'free'}
onClick={handleSelectFree}
>
{user ? 'Current' : 'Get Started'}
</Button>
</td>
<td>
<Button
large
margin="0 auto"
center
border
padding="4px 8px"
color={COLORS.BLUE}
disabled={user && user.plan === 'diamond'}
onClick={handleSelectUpgrade}
>
Upgrade
</Button>
</td>
</tr>
</tbody>
</table>
<style jsx>
{`
table {
width: 100%;
}
td {
font-size: 14px;
padding: 0.5rem 0.5rem 0.5rem 1rem;
}
tr:nth-of-type(odd) {
background: ${COLORS.HOVER};
}
thead tr {
background: ${COLORS.BLACK};
}
tr td:not(:last-of-type) {
border-right: 1px solid white;
}
tr td:not(:nth-child(1)) {
text-align: center;
padding: 0.5rem 1rem;
}
h3 {
margin: 0;
}
table :global(button) {
text-transform: uppercase;
font-size: 16px;
}
`}
</style>
</div>
)
}
function Settings() {
const [selected, select] = React.useState('Plan')
const user = useAuth()
function selectMenu(name) {
return () => select(name)
}
return (
<div className="editor">
<div className="settings-bottom">
<div className="settings-menu">
<MenuButton name="Plan" select={selectMenu} selected={selected}></MenuButton>
<MenuButton name="Billing" select={selectMenu} selected={selected}></MenuButton>
{/* <MenuButton name="API Keys" select={selectMenu} selected={selected} /> */}
<MenuButton name="Sign Out" select={logoutThunk} selected={selected} noArrows />
</div>
<div className="content">
{selected === 'Plan' && <Plan selectBilling={selectMenu('Billing')} />}
{selected === 'Billing' && <Billing />}
</div>
</div>
{user && <img className="avatar" src={user.photoURL} alt={user.displayName} />}
<style jsx>
{`
.editor {
position: relative;
background: ${COLORS.BLACK};
border: 3px solid ${COLORS.SECONDARY};
border-radius: 8px;
width: auto;
}
.settings-container {
position: relative;
}
.settings-bottom {
display: flex;
border-radius: 8px;
overflow: hidden;
}
.settings-menu {
display: flex;
flex-direction: column;
flex: 0 0 96px;
background-color: ${COLORS.DARK_GRAY};
}
.content {
width: 580px;
border-left: 3px solid ${COLORS.SECONDARY};
}
.avatar {
position: absolute;
width: 64px;
height: 64px;
border-radius: 50%;
background: ${COLORS.BLACK};
border: 3px solid ${COLORS.SECONDARY};
top: -40px;
right: -40px;
}
`}
</style>
</div>
)
}
function SettingsPage() {
return (
<Page>
<MetaLinks />
<Settings />
</Page>
)
}
export default SettingsPage

@ -0,0 +1,213 @@
// Theirs
import React from 'react'
import Link from 'next/link'
import Router from 'next/router'
import formatDistanceToNow from 'date-fns/formatDistanceToNow'
import { useAsyncCallback } from '@dawnlabs/tacklebox'
import Button from '../components/Button'
import LoginButton from '../components/LoginButton'
import { useAuth } from '../components/AuthContext'
import { useAPI } from '../components/ApiContext'
import { MetaLinks } from '../components/Meta'
import Carbon from '../components/Carbon'
import { COLORS, DEFAULT_SETTINGS } from '../lib/constants'
// Ours
import Page from '../components/Page'
function correctTimestamp(n) {
if (n < 9e12) {
return n * 1000
}
return n
}
function Snippet(props) {
const config = { ...DEFAULT_SETTINGS, ...props, fontSize: '2px', windowControls: false }
return (
<Link prefetch={false} href={`/${props.id}`}>
<a href={`/${props.id}`}>
<div className="snippet">
<div className="column">
<div className="carbon-container">
<Carbon key={config.language} readOnly={true} config={config} loading={props.loading}>
{props.code}
</Carbon>
</div>
<div className="id">{props.title || props.id}</div>
<div className="meta">
Edited {formatDistanceToNow(correctTimestamp(props.updatedAt), { addSuffix: true })}
</div>
</div>
</div>
<style jsx>
{`
.snippet {
position: relative;
width: 266px;
height: 266px;
border: 1px solid ${COLORS.SECONDARY};
border-radius: 3px;
cursor: pointer !important;
overflow: hidden;
}
.snippet:hover {
background: ${COLORS.HOVER};
}
.snippet:hover:after {
position: absolute;
content: '↗';
display: flex;
align-items: center;
justify-content: center;
background: ${COLORS.HOVER};
opacity: 0.8;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
font-size: 48px;
z-index: 999;
}
.column {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
overflow: hidden;
}
.id {
position: absolute;
top: 0.25rem;
right: 0.125rem;
border-radius: 3px;
background: ${COLORS.SECONDARY};
color: ${COLORS.BLACK};
font-size: 10px;
padding: 0.125rem;
max-width: 80%;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
z-index: 200;
}
.carbon-container {
width: 100%;
height: 100%;
overflow: hidden;
color: ${COLORS.SECONDARY};
width: 266px;
}
.carbon-container :global(.CodeMirror__container .CodeMirror span) {
font-size: 2px !important;
}
.meta {
background: ${COLORS.SECONDARY};
color: ${COLORS.DARK_GRAY};
width: 100%;
font-size: 10px;
padding: 0.24rem;
bottom: 0px;
}
`}
</style>
</a>
</Link>
)
}
function ActionButton(props) {
return (
<Button
border
center
margin="0.5rem"
flex="0 0 266px"
color={COLORS.GRAY}
style={{ minHeight: 266 }}
{...props}
/>
)
}
function useOnMount() {
const [mounted, mount] = React.useState(false)
React.useEffect(() => {
mount(true)
}, [])
return mounted
}
function SnippetsPage() {
const user = useAuth()
const api = useAPI()
const [snippets, setSnippets] = React.useState([])
const [page, setPage] = React.useState(0)
const mounted = useOnMount()
const [loadMore, { loading, data: previousRes }] = useAsyncCallback(api.snippet.list)
React.useEffect(() => {
if (user) {
const authorization = user.ra
loadMore(page, { authorization }).then(newSnippets =>
setSnippets(curr => curr.concat(newSnippets))
)
}
}, [loadMore, page, user])
if (!user) {
return <LoginButton />
}
return (
<div className="container">
{snippets.map(snippet => (
<Snippet key={snippet.id} {...snippet} loading={!mounted} />
))}
{snippets.length && previousRes && previousRes.length < 10 ? null : (
<ActionButton
disabled={loading}
onClick={() => {
if (snippets.length) return setPage(p => p + 1)
Router.push('/')
}}
>
<h4>{loading ? 'Loading...' : !snippets.length ? 'Create snippet +' : 'Load more +'}</h4>
</ActionButton>
)}
<style jsx>
{`
.container {
max-width: 872px;
min-width: ${266 + 32 + 8}px;
border: 3px solid ${COLORS.SECONDARY};
border-radius: 8px;
padding: 0.5rem;
display: flex;
flex-wrap: wrap;
}
.container :global(.snippet) {
margin: 0.5rem;
}
`}
</style>
</div>
)
}
export default () => (
<Page enableHeroText={true}>
<MetaLinks />
<SnippetsPage />
</Page>
)

Before

Width:  |  Height:  |  Size: 882 B

After

Width:  |  Height:  |  Size: 882 B

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="white" d="M256 256c52.805 0 96-43.201 96-96s-43.195-96-96-96-96 43.201-96 96 43.195 96 96 96zm0 48c-63.598 0-192 32.402-192 96v48h384v-48c0-63.598-128.402-96-192-96z"/></svg>

After

Width:  |  Height:  |  Size: 248 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="white" d="M190.4 354.1L91.9 256l98.4-98.1-30-29.9L32 256l128.4 128 30-29.9zm131.2 0L420 256l-98.4-98.1 30-29.9L480 256 351.6 384l-30-29.9z"/><path fill="white" d="M155.6 276h40v-40h-40v40zm200.8-40h-40v40h40v-40zM236 276h40v-40h-40v40z"/></svg>

After

Width:  |  Height:  |  Size: 319 B

@ -2974,6 +2974,11 @@ date-fns@^1.27.2:
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw==
date-fns@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.0.1.tgz#c5f30e31d3294918e6b6a82753a4e719120e203d"
integrity sha512-C14oTzTZy8DH1Eq8N78owrCWvf3+cnJw88BTK/N3DYWVxDJuJzPaNdplzYxDYuuXXGvqBcO4Vy5SOrwAooXSWw==
date-now@^0.1.4: date-now@^0.1.4:
version "0.1.4" version "0.1.4"
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
@ -7126,6 +7131,13 @@ react-spinner@^0.2.7:
resolved "https://registry.yarnpkg.com/react-spinner/-/react-spinner-0.2.7.tgz#ea3ca3375dd7a54edbb5cc01d17496a2e2fc14db" resolved "https://registry.yarnpkg.com/react-spinner/-/react-spinner-0.2.7.tgz#ea3ca3375dd7a54edbb5cc01d17496a2e2fc14db"
integrity sha1-6jyjN13XpU7btcwB0XSWouL8FNs= integrity sha1-6jyjN13XpU7btcwB0XSWouL8FNs=
react-stripe-elements@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/react-stripe-elements/-/react-stripe-elements-4.0.1.tgz#89b52c909a80f17afc7907313c3b3c3f21fa125d"
integrity sha512-S+O2+hphs6ASz29l85nj6mpS7YWTa3NMwZTonIMt4+8xrfS/jET+0Xd3cNdJoGkHiCtLEId6UimqivONK+liOw==
dependencies:
prop-types "^15.5.10"
react-syntax-highlight@^15.3.1: react-syntax-highlight@^15.3.1:
version "15.3.1" version "15.3.1"
resolved "https://registry.yarnpkg.com/react-syntax-highlight/-/react-syntax-highlight-15.3.1.tgz#b44f6f77e2783e8f74c4b30b50d5a886cc35fc1f" resolved "https://registry.yarnpkg.com/react-syntax-highlight/-/react-syntax-highlight-15.3.1.tgz#b44f6f77e2783e8f74c4b30b50d5a886cc35fc1f"

Loading…
Cancel
Save