Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bump Epic-stack to latest w Conform v1 #147

Merged
merged 10 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/heat-stack.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:
- name: ⎔ Setup node
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20

- name: 📥 Download deps
uses: bahmutov/npm-install@v1
Expand All @@ -59,7 +59,7 @@ jobs:
- name: ⎔ Setup node
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20

- name: 📥 Download deps
uses: bahmutov/npm-install@v1
Expand All @@ -82,7 +82,7 @@ jobs:
- name: ⎔ Setup node
uses: actions/setup-node@v3
with:
node-version: 18
node-version: 20

- name: 📥 Download deps
uses: bahmutov/npm-install@v1
Expand All @@ -96,7 +96,7 @@ jobs:
run: npm run build:icons

- name: ⚡ Run vitest
run: npm run test app/utils/pyodide.test.ts -- --coverage
run: npm run test app/utils/pyodide.test.ts -- # --coverage

# playwright tests work great but slight jank/inconsistency passing, and not used yet, so disabling for now
# playwright:
Expand Down Expand Up @@ -158,7 +158,7 @@ jobs:

deploy:
name: 🚀 Deploy
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
needs: [lint, typecheck, vitest] #, playwright]
# only build/deploy main branch on pushes
if:
Expand Down
10 changes: 5 additions & 5 deletions heat-stack/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ cp .env.example .env

# make sure you're using node version 18
npm install -g nvm
nvm use 18
nvm use 20

# install and patch the environment
npm install
Expand All @@ -24,7 +24,7 @@ npm run dev
# create an environment file
cp .env.example .env

nvm use 18
nvm use 20
npm install
npm run dev
```
Expand Down Expand Up @@ -80,7 +80,7 @@ To re-create the patch for py file support in `/patch`, use these [instructions]
</div>

```sh
npx create-remix@latest --install --template epicweb-dev/epic-stack
npx create-epic-app@latest
```

[![The Epic Stack](https://github-production-user-asset-6210df.s3.amazonaws.com/1500684/246885449-1b00286c-aa3d-44b2-9ef2-04f694eb3592.png)](https://www.epicweb.dev/epic-stack)
Expand All @@ -91,9 +91,9 @@ npx create-remix@latest --install --template epicweb-dev/epic-stack

## Watch Kent's Introduction to The Epic Stack

[![screenshot of a YouTube video](https://github-production-user-asset-6210df.s3.amazonaws.com/1500684/242088051-6beafa78-41c6-47e1-b999-08d3d3e5cb57.png)](https://www.youtube.com/watch?v=yMK5SVRASxM)
[![Epic Stack Talk slide showing Flynn Rider with knives, the text "I've been around and I've got opinions" and Kent speaking in the corner](https://github-production-user-asset-6210df.s3.amazonaws.com/1500684/277818553-47158e68-4efc-43ae-a477-9d1670d4217d.png)](https://www.epicweb.dev/talks/the-epic-stack)

["The Epic Stack" by Kent C. Dodds at #RemixConf 2023 💿](https://www.youtube.com/watch?v=yMK5SVRASxM)
["The Epic Stack" by Kent C. Dodds](https://www.epicweb.dev/talks/the-epic-stack)

## Docs

Expand Down
2 changes: 1 addition & 1 deletion heat-stack/app/components/error-boundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function GeneralErrorBoundary({
? (statusHandlers?.[error.status] ?? defaultStatusHandler)({
error,
params,
})
})
: unexpectedErrorHandler(error)}
</div>
)
Expand Down
41 changes: 22 additions & 19 deletions heat-stack/app/components/forms.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useInputEvent } from '@conform-to/react'
import React, { useId, useRef } from 'react'
import { useInputControl } from '@conform-to/react'
import React, { useId } from 'react'
import { Checkbox, type CheckboxProps } from './ui/checkbox.tsx'
import { Input } from './ui/input.tsx'
import { Label } from './ui/label.tsx'
Expand All @@ -19,7 +19,7 @@ export function ErrorList({
return (
<ul id={id} className="flex flex-col gap-1">
{errorsToRender.map(e => (
<li key={e} className="text-foreground-destructive text-[10px]">
<li key={e} className="text-[10px] text-foreground-destructive">
{e}
</li>
))}
Expand Down Expand Up @@ -94,42 +94,45 @@ export function CheckboxField({
className,
}: {
labelProps: JSX.IntrinsicElements['label']
buttonProps: CheckboxProps
buttonProps: CheckboxProps & {
name: string
form: string
value?: string
}
errors?: ListOfErrors
className?: string
}) {
const { key, defaultChecked, ...checkboxProps } = buttonProps
const fallbackId = useId()
const buttonRef = useRef<HTMLButtonElement>(null)
// To emulate native events that Conform listen to:
// See https://conform.guide/integrations
const control = useInputEvent({
// Retrieve the checkbox element by name instead as Radix does not expose the internal checkbox element
// See https://github.com/radix-ui/primitives/discussions/874
ref: () =>
buttonRef.current?.form?.elements.namedItem(buttonProps.name ?? ''),
onFocus: () => buttonRef.current?.focus(),
const checkedValue = buttonProps.value ?? 'on'
const input = useInputControl({
key,
name: buttonProps.name,
formId: buttonProps.form,
initialValue: defaultChecked ? checkedValue : undefined,
})
const id = buttonProps.id ?? buttonProps.name ?? fallbackId
const id = buttonProps.id ?? fallbackId
const errorId = errors?.length ? `${id}-error` : undefined

return (
<div className={className}>
<div className="flex gap-2">
<Checkbox
{...checkboxProps}
id={id}
ref={buttonRef}
aria-invalid={errorId ? true : undefined}
aria-describedby={errorId}
{...buttonProps}
checked={input.value === checkedValue}
onCheckedChange={state => {
control.change(Boolean(state.valueOf()))
input.change(state.valueOf() ? checkedValue : '')
buttonProps.onCheckedChange?.(state)
}}
onFocus={event => {
control.focus()
input.focus()
buttonProps.onFocus?.(event)
}}
onBlur={event => {
control.blur()
input.blur()
buttonProps.onBlur?.(event)
}}
type="button"
Expand Down
3 changes: 1 addition & 2 deletions heat-stack/app/components/search-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,8 @@ export function SearchBar({
type="submit"
status={isSubmitting ? 'pending' : status}
className="flex w-full items-center justify-center"
size="sm"
>
<Icon name="magnifying-glass" size="sm" />
<Icon name="magnifying-glass" size="md" />
<span className="sr-only">Search</span>
</StatusButton>
</div>
Expand Down
28 changes: 11 additions & 17 deletions heat-stack/app/components/toaster.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
import { useEffect } from 'react'
import { Toaster, toast as showToast } from 'sonner'
import { toast as showToast } from 'sonner'
import { type Toast } from '#app/utils/toast.server.ts'

export function EpicToaster({ toast }: { toast?: Toast | null }) {
return (
<>
<Toaster closeButton position="top-center" />
{toast ? <ShowToast toast={toast} /> : null}
</>
)
}

function ShowToast({ toast }: { toast: Toast }) {
const { id, type, title, description } = toast
export function useToast(toast?: Toast | null) {
useEffect(() => {
setTimeout(() => {
showToast[type](title, { id, description })
}, 0)
}, [description, id, title, type])
return null
if (toast) {
setTimeout(() => {
showToast[toast.type](toast.title, {
id: toast.id,
description: toast.description,
})
}, 0)
}
}, [toast])
}
2 changes: 1 addition & 1 deletion heat-stack/app/components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as React from 'react'
import { cn } from '#app/utils/misc.tsx'

const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors outline-none focus-visible:ring-4 focus-within:ring-4 ring-ring ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
'inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors outline-none focus-visible:ring-2 focus-within:ring-2 ring-ring ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
Expand Down
26 changes: 26 additions & 0 deletions heat-stack/app/components/ui/sonner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Toaster as Sonner } from 'sonner'

type ToasterProps = React.ComponentProps<typeof Sonner>

const EpicToaster = ({ theme, ...props }: ToasterProps) => {
return (
<Sonner
theme={theme}
className="toaster group"
toastOptions={{
classNames: {
toast:
'group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg',
description: 'group-[.toast]:text-muted-foreground',
actionButton:
'group-[.toast]:bg-primary group-[.toast]:text-primary-foreground',
cancelButton:
'group-[.toast]:bg-muted group-[.toast]:text-muted-foreground',
},
}}
{...props}
/>
)
}

export { EpicToaster }
7 changes: 4 additions & 3 deletions heat-stack/app/entry.server.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { PassThrough } from 'stream'
import {
createReadableStreamFromReadable,
type DataFunctionArgs,
type LoaderFunctionArgs,
type ActionFunctionArgs,
type HandleDocumentRequestFunction,
} from '@remix-run/node'
import { RemixServer } from '@remix-run/react'
import * as Sentry from '@sentry/remix'
import isbot from 'isbot'
import { isbot } from 'isbot'
import { getInstanceInfo } from 'litefs-js'
import { renderToPipeableStream } from 'react-dom/server'
import { getEnv, init } from './utils/env.server.ts'
Expand Down Expand Up @@ -94,7 +95,7 @@ export async function handleDataRequest(response: Response) {

export function handleError(
error: unknown,
{ request }: DataFunctionArgs,
{ request }: LoaderFunctionArgs | ActionFunctionArgs,
): void {
if (error instanceof Error) {
Sentry.captureRemixServerException(error, 'remix.server', request)
Expand Down
Loading
Loading