Adds auth

This commit is contained in:
Steve Ruiz 2021-09-04 13:02:13 +01:00
parent 8218af529d
commit a081840aa9
16 changed files with 1491 additions and 3951 deletions

View file

@ -59,7 +59,7 @@
"@radix-ui/react-id": "^0.0.6",
"@radix-ui/react-radio-group": "^0.0.18",
"@radix-ui/react-tooltip": "^0.0.20",
"@stitches/react": "^0.2.3",
"@stitches/react": "^1.0.0",
"@tldraw/core": "^0.0.57",
"perfect-freehand": "^0.5.3",
"react-hotkeys-hook": "^3.4.0",

View file

@ -1,6 +1,6 @@
import { createCss, defaultThemeMap } from '@stitches/react'
import { createStitches, defaultThemeMap } from '@stitches/react'
const { styled, css, theme, getCssString } = createCss({
const { styled, css, createTheme, getCssText } = createStitches({
themeMap: {
...defaultThemeMap,
},
@ -99,7 +99,7 @@ const { styled, css, theme, getCssString } = createCss({
},
})
const dark = theme({
const dark = createTheme({
colors: {
brushFill: 'rgba(180, 180, 180, .05)',
brushStroke: 'rgba(180, 180, 180, .25)',
@ -136,4 +136,4 @@ const dark = theme({
export default styled
export { css, getCssString, dark }
export { css, getCssText, dark }

View file

@ -1,5 +1,9 @@
import { TLDraw } from '@tldraw/tldraw'
export default function Editor() {
return <TLDraw id="default" />
interface EditorProps {
id?: string
}
export default function Editor({ id = 'home' }: EditorProps) {
return <TLDraw id={id} />
}

View file

@ -1,6 +1,14 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const withTM = require('next-transpile-modules')(['@tldraw/tldraw'])
const { NODE_ENV } = process.env
const isProduction = NODE_ENV === 'production'
module.exports = withTM({
reactStrictMode: true,
pwa: {
disable: !isProduction,
dest: 'public',
},
})

View file

@ -20,7 +20,11 @@
"next": "11.1.2",
"react": "17.0.2",
"react-dom": "17.0.2",
"next-transpile-modules": "^8.0.0"
"next-auth": "3.29.0",
"next-transpile-modules": "^8.0.0",
"next-pwa": "^5.2.23",
"next-themes": "^0.0.15",
"@stitches/react": "^1.0.0"
},
"devDependencies": {
"@types/react": "^17.0.19",

View file

@ -0,0 +1,86 @@
import NextDocument, { Html, Head, Main, NextScript, DocumentContext } from 'next/document'
import { getCssText } from '../styles'
import { GA_TRACKING_ID } from '../utils/gtag'
class MyDocument extends NextDocument {
static async getInitialProps(ctx: DocumentContext): Promise<{
styles: JSX.Element
html: string
head?: JSX.Element[]
}> {
try {
const initialProps = await NextDocument.getInitialProps(ctx)
return {
...initialProps,
styles: (
<>
{initialProps.styles}
<style id="stitches" dangerouslySetInnerHTML={{ __html: getCssText() }} />
</>
),
}
} catch (e) {
console.error(e.message)
} finally {
null
}
}
render(): JSX.Element {
const APP_NAME = 'tldraw'
const APP_DESCRIPTION = 'A tiny little drawing app.'
const APP_URL = 'https://tldraw.com'
return (
<Html lang="en">
<Head>
<meta name="application-name" content={APP_NAME} />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black" />
<meta name="apple-mobile-web-app-title" content={APP_NAME} />
<meta name="description" content={APP_DESCRIPTION} />
<meta name="format-detection" content="telephone=no" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="theme-color" content="#fafafa" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:url" content={APP_URL} />
<meta name="twitter:title" content={APP_NAME} />
<meta name="twitter:description" content={APP_DESCRIPTION} />
<meta name="twitter:creator" content="@steveruizok" />
<meta property="og:type" content="website" />
<meta property="og:title" content={APP_NAME} />
<meta property="og:description" content={APP_DESCRIPTION} />
<meta property="og:site_name" content={APP_NAME} />
<meta property="og:url" content={APP_URL} />
<link rel="manifest" href="/manifest.json" />
<link rel="shortcut icon" href="/favicon.ico" />
<link rel="apple-touch-icon" sizes="180x180" href="/icons/apple-touch-icon.png" />
<script async src={`https://www.googletagmanager.com/gtag/js?id=${GA_TRACKING_ID}`} />
<script
dangerouslySetInnerHTML={{
__html: `
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', '${GA_TRACKING_ID}', {
page_path: window.location.pathname,
});
`,
}}
/>
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
}
export default MyDocument

View file

@ -1,6 +1,24 @@
import dynamic from 'next/dynamic'
const Editor = dynamic(() => import('../components/editor'), { ssr: false })
import { GetServerSideProps } from 'next'
import { getSession } from 'next-auth/client'
export default function Home() {
return <Editor />
const Editor = dynamic(() => import('components/editor'), { ssr: false })
export default function Shhh(): JSX.Element {
return <Editor id="home" />
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getSession(context)
if (!session?.user && process.env.NODE_ENV !== 'development') {
context.res.setHeader('Location', `/sponsorware`)
context.res.statusCode = 307
}
return {
props: {
session,
},
}
}

View file

@ -0,0 +1,6 @@
import dynamic from 'next/dynamic'
const Editor = dynamic(() => import('components/editor'), { ssr: false })
export default function Shhh(): JSX.Element {
return <Editor id="home" />
}

View file

@ -0,0 +1,182 @@
import styled from 'styles'
import { getSession, signin, signout, useSession } from 'next-auth/client'
import { GetServerSideProps } from 'next'
import React from 'react'
export default function Sponsorware(): JSX.Element {
const [session, loading] = useSession()
return (
<OuterContent>
<Content
size={{
'@sm': 'small',
}}
>
<h1>tldraw (is sponsorware)</h1>
<p>
Hey, thanks for visiting <a href="https://tldraw.com/">tldraw</a>, a tiny little drawing
app by <a href="https://twitter.com/steveruizok">steveruizok</a>.
</p>
<video autoPlay muted playsInline onClick={(e) => e.currentTarget.play()}>
<source src="images/hello.mp4" type="video/mp4" />
</video>
<p>This project is currently: </p>
<ul>
<li>in development</li>
<li>only available for my sponsors</li>
</ul>
<p>
If you&apos;d like to try it out,{' '}
<a
href="https://github.com/sponsors/steveruizok"
target="_blank"
rel="noopener noreferrer"
>
sponsor me on Github
</a>{' '}
(at any level) and sign in below.
</p>
<ButtonGroup>
{session ? (
<>
<Button onClick={() => signout()} variant={'secondary'}>
Sign Out
</Button>
<Detail>
Signed in as {session?.user?.name} ({session?.user?.email}), but it looks like
you&apos;re not yet a sponsor.
<br />
Something wrong? Try <a href="/">reloading the page</a> or DM me on{' '}
<a href="https://twitter.com/steveruizok">Twitter</a>.
</Detail>
</>
) : (
<>
<Button onClick={() => signin('github')} variant={'primary'}>
{loading ? 'Loading...' : 'Sign in With Github'}
</Button>
<Detail>Already a sponsor? Just sign in to visit the app.</Detail>
</>
)}
</ButtonGroup>
</Content>
</OuterContent>
)
}
export const getServerSideProps: GetServerSideProps = async (context) => {
const session = await getSession(context)
return {
props: {
session,
},
}
}
const OuterContent = styled('div', {
backgroundColor: '$canvas',
padding: '8px 8px 64px 8px',
margin: '0 auto',
overflow: 'scroll',
position: 'fixed',
display: 'flex',
justifyContent: 'center',
alignItems: 'flex-start',
top: 0,
left: 0,
right: 0,
bottom: 0,
width: '100%',
height: '100%',
})
const Content = styled('div', {
width: '720px',
padding: '8px 16px',
maxWidth: '100%',
backgroundColor: '$panel',
borderRadius: '4px',
boxShadow: '$12',
color: '$text',
fontSize: '$2',
fontFamily: '$body',
lineHeight: 1.5,
'& a': {
color: '$bounds',
backgroundColor: '$boundsBg',
padding: '2px 4px',
margin: '0 -3px',
borderRadius: '2px',
},
'& p': {
borderRadius: '8px',
},
'& video': {
maxWidth: '100%',
border: '1px solid $overlay',
borderRadius: '4px',
overflow: 'hidden',
margin: '16px 0',
},
'& iframe': {
border: 'none',
backgroundColor: 'none',
background: 'none',
},
variants: {
size: {
small: {
fontSize: '$3',
padding: '32px',
},
},
},
})
const ButtonGroup = styled('div', {
display: 'grid',
gap: '16px',
margin: '40px 0 32px 0',
})
const Detail = styled('p', {
fontSize: '$2',
textAlign: 'center',
})
const Button = styled('button', {
cursor: 'pointer',
width: '100%',
padding: '12px 0',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
font: '$ui',
fontSize: '$3',
color: '$panel',
border: 'none',
borderRadius: '4px',
variants: {
variant: {
primary: {
fontWeight: 'bold',
background: '$bounds',
color: '$panel',
boxShadow: '$4',
},
secondary: {
border: '1px solid $overlay',
background: 'transparent',
color: '$muted',
},
},
},
})

View file

@ -0,0 +1,4 @@
import styled, { css, getCssText, globalStyles, light, dark } from './stitches.config'
export default styled
export { css, getCssText, globalStyles, light, dark }

View file

@ -0,0 +1,145 @@
import { createStitches, defaultThemeMap } from '@stitches/react'
const { styled, css, globalCss, createTheme, getCssText } = createStitches({
themeMap: {
...defaultThemeMap,
},
theme: {
colors: {
codeHl: 'rgba(144, 144, 144, .15)',
brushFill: 'rgba(0,0,0,.05)',
brushStroke: 'rgba(0,0,0,.25)',
hint: 'rgba(216, 226, 249, 1.000)',
selected: 'rgba(66, 133, 244, 1.000)',
bounds: 'rgba(65, 132, 244, 1.000)',
boundsBg: 'rgba(65, 132, 244, 0.05)',
highlight: 'rgba(65, 132, 244, 0.15)',
overlay: 'rgba(0, 0, 0, 0.15)',
overlayContrast: 'rgba(255, 255, 255, 0.15)',
border: '#aaaaaa',
canvas: '#f8f9fa',
panel: '#fefefe',
inactive: '#cccccf',
hover: '#efefef',
text: '#333333',
tooltipBg: '#1d1d1d',
tooltipText: '#ffffff',
muted: '#777777',
input: '#f3f3f3',
inputBorder: '#dddddd',
warn: 'rgba(255, 100, 100, 1)',
lineError: 'rgba(255, 0, 0, .1)',
},
shadows: {
2: '0px 1px 1px rgba(0, 0, 0, 0.14)',
3: '0px 2px 3px rgba(0, 0, 0, 0.14)',
4: '0px 4px 5px -1px rgba(0, 0, 0, 0.14)',
8: '0px 12px 17px rgba(0, 0, 0, 0.14)',
12: '0px 12px 17px rgba(0, 0, 0, 0.14)',
24: '0px 24px 38px rgba(0, 0, 0, 0.14)',
key: '1px 1px rgba(0,0,0,1)',
},
space: {
0: '2px',
1: '3px',
2: '4px',
3: '8px',
4: '12px',
5: '16px',
},
fontSizes: {
0: '10px',
1: '12px',
2: '13px',
3: '16px',
4: '18px',
},
fonts: {
ui: '"Recursive", system-ui, sans-serif',
body: '"Recursive", system-ui, sans-serif',
mono: '"Recursive Mono", monospace',
},
fontWeights: {},
lineHeights: {},
letterSpacings: {},
sizes: {},
borderWidths: {
0: '$1',
},
borderStyles: {},
radii: {
0: '2px',
1: '4px',
2: '8px',
},
zIndices: {},
transitions: {},
},
media: {
sm: '(min-width: 640px)',
md: '(min-width: 768px)',
},
utils: {
zDash: () => (value: number) => {
return {
strokeDasharray: `calc(${value}px / var(--camera-zoom)) calc(${value}px / var(--camera-zoom))`,
}
},
zStrokeWidth: () => (value: number | number[]) => {
if (Array.isArray(value)) {
return {
strokeWidth: `calc(${value[0]}px / var(--camera-zoom))`,
}
}
return {
strokeWidth: `calc(${value}px / var(--camera-zoom))`,
}
},
},
})
const light = createTheme({})
const dark = createTheme({
colors: {
brushFill: 'rgba(180, 180, 180, .05)',
brushStroke: 'rgba(180, 180, 180, .25)',
hint: 'rgba(216, 226, 249, 1.000)',
selected: 'rgba(38, 150, 255, 1.000)',
bounds: 'rgba(38, 150, 255, 1.000)',
boundsBg: 'rgba(38, 150, 255, 0.05)',
highlight: 'rgba(38, 150, 255, 0.15)',
overlay: 'rgba(0, 0, 0, 0.15)',
overlayContrast: 'rgba(255, 255, 255, 0.15)',
border: '#202529',
canvas: '#343d45',
panel: '#49555f',
inactive: '#aaaaad',
hover: '#343d45',
text: '#f8f9fa',
muted: '#e0e2e6',
input: '#f3f3f3',
inputBorder: '#ddd',
tooltipBg: '#1d1d1d',
tooltipText: '#ffffff',
codeHl: 'rgba(144, 144, 144, .15)',
lineError: 'rgba(255, 0, 0, .1)',
},
shadows: {
2: '0px 1px 1px rgba(0, 0, 0, 0.24)',
3: '0px 2px 3px rgba(0, 0, 0, 0.24)',
4: '0px 4px 5px -1px rgba(0, 0, 0, 0.24)',
8: '0px 12px 17px rgba(0, 0, 0, 0.24)',
12: '0px 12px 17px rgba(0, 0, 0, 0.24)',
24: '0px 24px 38px rgba(0, 0, 0, 0.24)',
},
})
const globalStyles = globalCss({
'*': { boxSizing: 'border-box' },
})
export default styled
export { css, getCssText, globalStyles, light, dark }

View file

@ -1,11 +1,7 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
@ -16,14 +12,13 @@
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve"
"jsx": "preserve",
"baseUrl": ".",
"rootDir": ".",
"paths": {
"-*": ["./*"]
}
},
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}

View file

@ -0,0 +1,27 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
export const GA_TRACKING_ID = process.env.GA_MEASUREMENT_ID
type GTagEvent = {
action: string
category: string
label: string
value: number
}
export const pageview = (url: URL): void => {
if ('gtag' in window) {
;(window as any)?.gtag('config', GA_TRACKING_ID, {
page_path: url,
})
}
}
export const event = ({ action, category, label, value }: GTagEvent): void => {
if ('gtag' in window) {
;(window as any)?.gtag('event', action, {
event_category: category,
event_label: label,
value: value,
})
}
}

View file

@ -0,0 +1 @@
self.__WB_DISABLE_DEV_LOGS = true

File diff suppressed because it is too large Load diff

1025
yarn.lock

File diff suppressed because it is too large Load diff