Adds auth
This commit is contained in:
parent
8218af529d
commit
a081840aa9
16 changed files with 1491 additions and 3951 deletions
|
@ -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",
|
||||
|
|
|
@ -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 }
|
||||
|
|
|
@ -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} />
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
})
|
||||
|
|
|
@ -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",
|
||||
|
|
86
packages/www/pages/_document.tsx
Normal file
86
packages/www/pages/_document.tsx
Normal 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
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
6
packages/www/pages/shhh.tsx
Normal file
6
packages/www/pages/shhh.tsx
Normal 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" />
|
||||
}
|
182
packages/www/pages/sponsorware.tsx
Normal file
182
packages/www/pages/sponsorware.tsx
Normal 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'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'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',
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
4
packages/www/styles/index.ts
Normal file
4
packages/www/styles/index.ts
Normal file
|
@ -0,0 +1,4 @@
|
|||
import styled, { css, getCssText, globalStyles, light, dark } from './stitches.config'
|
||||
|
||||
export default styled
|
||||
export { css, getCssText, globalStyles, light, dark }
|
145
packages/www/styles/stitches.config.ts
Normal file
145
packages/www/styles/stitches.config.ts
Normal 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 }
|
|
@ -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"]
|
||||
}
|
||||
|
|
27
packages/www/utils/gtag.ts
Normal file
27
packages/www/utils/gtag.ts
Normal 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,
|
||||
})
|
||||
}
|
||||
}
|
1
packages/www/worker/index.js
Normal file
1
packages/www/worker/index.js
Normal file
|
@ -0,0 +1 @@
|
|||
self.__WB_DISABLE_DEV_LOGS = true
|
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue