Skip to content

Commit

Permalink
add another step
Browse files Browse the repository at this point in the history
  • Loading branch information
kentcdodds committed Aug 28, 2023
1 parent e4ad1cd commit 1da0287
Show file tree
Hide file tree
Showing 320 changed files with 5,005 additions and 52 deletions.
4 changes: 2 additions & 2 deletions exercises/06.honeypot/01.problem.basic/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ export default function App() {
<div className="font-light">epic</div>
<div className="font-bold">notes</div>
</Link>
<Link className="underline" to="/users/kody/notes/d27a197e/edit">
Edit Kody's first note
<Link className="underline" to="/signup">
Signup
</Link>
</nav>
</header>
Expand Down
4 changes: 2 additions & 2 deletions exercises/06.honeypot/01.solution.basic/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ export default function App() {
<div className="font-light">epic</div>
<div className="font-bold">notes</div>
</Link>
<Link className="underline" to="/users/kody/notes/d27a197e/edit">
Edit Kody's first note
<Link className="underline" to="/signup">
Signup
</Link>
</nav>
</header>
Expand Down
14 changes: 3 additions & 11 deletions exercises/06.honeypot/02.problem.util/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ export const links: LinksFunction = () => {
}

export async function loader() {
// 🐨 get the honeypot props from the honeypot object and add them to this
// object.
return json({ username: os.userInfo().username, ENV: getEnv() })
}

Expand All @@ -54,7 +52,7 @@ function Document({ children }: { children: React.ReactNode }) {
)
}

function App() {
export default function App() {
const data = useLoaderData<typeof loader>()
return (
<Document>
Expand All @@ -64,8 +62,8 @@ function App() {
<div className="font-light">epic</div>
<div className="font-bold">notes</div>
</Link>
<Link className="underline" to="/users/kody/notes/d27a197e/edit">
Edit Kody's first note
<Link className="underline" to="/signup">
Signup
</Link>
</nav>
</header>
Expand All @@ -91,12 +89,6 @@ function App() {
)
}

export default function AppWithProviders() {
// 💰 you'll need this const data = useLoaderData<typeof loader>()
// 🐨 render the HoneypotProvider here and pass the honeypot props
return <App />
}

export const meta: MetaFunction = () => {
return [
{ title: 'Epic Notes' },
Expand Down
24 changes: 4 additions & 20 deletions exercises/06.honeypot/02.solution.util/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,12 @@ import {
useLoaderData,
type MetaFunction,
} from '@remix-run/react'
import { HoneypotProvider } from 'remix-utils/honeypot/react'
import faviconAssetUrl from './assets/favicon.svg'
import { GeneralErrorBoundary } from './components/error-boundary.tsx'
import { KCDShop } from './kcdshop.tsx'
import fontStylestylesheetUrl from './styles/font.css'
import tailwindStylesheetUrl from './styles/tailwind.css'
import { getEnv } from './utils/env.server.ts'
import { honeypot } from './utils/honeypot.server.ts'

export const links: LinksFunction = () => {
return [
Expand All @@ -31,12 +29,7 @@ export const links: LinksFunction = () => {
}

export async function loader() {
const honeyProps = honeypot.getInputProps()
return json({
username: os.userInfo().username,
ENV: getEnv(),
honeyProps,
})
return json({ username: os.userInfo().username, ENV: getEnv() })
}

function Document({ children }: { children: React.ReactNode }) {
Expand All @@ -59,7 +52,7 @@ function Document({ children }: { children: React.ReactNode }) {
)
}

function App() {
export default function App() {
const data = useLoaderData<typeof loader>()
return (
<Document>
Expand All @@ -69,8 +62,8 @@ function App() {
<div className="font-light">epic</div>
<div className="font-bold">notes</div>
</Link>
<Link className="underline" to="/users/kody/notes/d27a197e/edit">
Edit Kody's first note
<Link className="underline" to="/signup">
Signup
</Link>
</nav>
</header>
Expand All @@ -96,15 +89,6 @@ function App() {
)
}

export default function AppWithProviders() {
const data = useLoaderData<typeof loader>()
return (
<HoneypotProvider {...data.honeyProps}>
<App />
</HoneypotProvider>
)
}

export const meta: MetaFunction = () => {
return [
{ title: 'Epic Notes' },
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Honeypot } from 'remix-utils/honeypot/server'

export const honeypot = new Honeypot({
validFromFieldName: process.env.TESTING ? undefined : null,
validFromFieldName: null,
})
1 change: 1 addition & 0 deletions exercises/06.honeypot/03.problem.provider/README.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Remix Utils
115 changes: 115 additions & 0 deletions exercises/06.honeypot/03.problem.provider/app/root.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import os from 'node:os'
import { cssBundleHref } from '@remix-run/css-bundle'
import { json, type LinksFunction } from '@remix-run/node'
import {
Link,
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
useLoaderData,
type MetaFunction,
} from '@remix-run/react'
import faviconAssetUrl from './assets/favicon.svg'
import { GeneralErrorBoundary } from './components/error-boundary.tsx'
import { KCDShop } from './kcdshop.tsx'
import fontStylestylesheetUrl from './styles/font.css'
import tailwindStylesheetUrl from './styles/tailwind.css'
import { getEnv } from './utils/env.server.ts'

export const links: LinksFunction = () => {
return [
{ rel: 'icon', type: 'image/svg+xml', href: faviconAssetUrl },
{ rel: 'stylesheet', href: fontStylestylesheetUrl },
{ rel: 'stylesheet', href: tailwindStylesheetUrl },
cssBundleHref ? { rel: 'stylesheet', href: cssBundleHref } : null,
].filter(Boolean)
}

export async function loader() {
// 🐨 get the honeypot props from the honeypot object and add them to this
// object.
return json({ username: os.userInfo().username, ENV: getEnv() })
}

function Document({ children }: { children: React.ReactNode }) {
return (
<html lang="en" className="h-full overflow-x-hidden">
<head>
<Meta />
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Links />
</head>
<body className="flex h-full flex-col justify-between bg-background text-foreground">
{children}
<ScrollRestoration />
<Scripts />
<KCDShop />
<LiveReload />
</body>
</html>
)
}

function App() {
const data = useLoaderData<typeof loader>()
return (
<Document>
<header className="container mx-auto py-6">
<nav className="flex justify-between">
<Link to="/">
<div className="font-light">epic</div>
<div className="font-bold">notes</div>
</Link>
<Link className="underline" to="/signup">
Signup
</Link>
</nav>
</header>

<div className="flex-1">
<Outlet />
</div>

<div className="container mx-auto flex justify-between">
<Link to="/">
<div className="font-light">epic</div>
<div className="font-bold">notes</div>
</Link>
<p>Built with ♥️ by {data.username}</p>
</div>
<div className="h-5" />
<script
dangerouslySetInnerHTML={{
__html: `window.ENV = ${JSON.stringify(data.ENV)}`,
}}
/>
</Document>
)
}

export default function AppWithProviders() {
// 💰 you'll need this const data = useLoaderData<typeof loader>()
// 🐨 render the HoneypotProvider here and pass the honeypot props
return <App />
}

export const meta: MetaFunction = () => {
return [
{ title: 'Epic Notes' },
{ name: 'description', content: `Your own captain's log` },
]
}

export function ErrorBoundary() {
return (
<Document>
<div className="flex-1">
<GeneralErrorBoundary />
</div>
</Document>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {
redirect,
type DataFunctionArgs,
type MetaFunction,
} from '@remix-run/node'
import { Form } from '@remix-run/react'
import { HoneypotInputs } from 'remix-utils/honeypot/react'
import { SpamError } from 'remix-utils/honeypot/server'
import { Button } from '#app/components/ui/button.tsx'
import { Input } from '#app/components/ui/input.tsx'
import { Label } from '#app/components/ui/label.tsx'
import { honeypot } from '#app/utils/honeypot.server.ts'

export async function action({ request }: DataFunctionArgs) {
const formData = await request.formData()
try {
honeypot.check(formData)
} catch (error) {
if (error instanceof SpamError) {
throw new Response('Form not submitted properly', { status: 400 })
}
throw error
}
// we'll implement signup later
return redirect('/')
}

export default function SignupRoute() {
return (
<div className="container flex min-h-full flex-col justify-center pb-32 pt-20">
<div className="mx-auto w-full max-w-lg">
<div className="flex flex-col gap-3 text-center">
<h1 className="text-h1">Welcome aboard!</h1>
<p className="text-body-md text-muted-foreground">
Please enter your details.
</p>
</div>
<Form
method="POST"
className="mx-auto flex min-w-[368px] max-w-sm flex-col gap-4"
>
<HoneypotInputs />
<div>
<Label htmlFor="email-input">Email</Label>
<Input autoFocus id="email-input" name="email" type="email" />
</div>
<Button className="w-full" type="submit">
Create an account
</Button>
</Form>
</div>
</div>
)
}

export const meta: MetaFunction = () => {
return [{ title: 'Setup Epic Notes Account' }]
}
48 changes: 48 additions & 0 deletions exercises/06.honeypot/03.problem.provider/app/utils/env.server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { z } from 'zod'

const schema = z.object({
NODE_ENV: z.enum(['production', 'development', 'test'] as const),
})

declare global {
namespace NodeJS {
interface ProcessEnv extends z.infer<typeof schema> {}
}
}

export function init() {
const parsed = schema.safeParse(process.env)

if (parsed.success === false) {
console.error(
'❌ Invalid environment variables:',
parsed.error.flatten().fieldErrors,
)

throw new Error('Invalid envirmonment variables')
}
}

/**
* This is used in both `entry.server.ts` and `root.tsx` to ensure that
* the environment variables are set and globally available before the app is
* started.
*
* NOTE: Do *not* add any environment variables in here that you do not wish to
* be included in the client.
* @returns all public ENV variables
*/
export function getEnv() {
return {
MODE: process.env.NODE_ENV,
}
}

type ENV = ReturnType<typeof getEnv>

declare global {
var ENV: ENV
interface Window {
ENV: ENV
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Honeypot } from 'remix-utils/honeypot/server'

export const honeypot = new Honeypot({
// 🐨 set this to process.env.TESTING ? undefined : null so it's disabled during tests
validFromFieldName: null,
})
Loading

0 comments on commit 1da0287

Please sign in to comment.