Skip to content

Kody is live

Watch the launch video — what Kody is, and why it exists.

← Public packages

@kentcdodds/package-app-kit

Design tokens, PWA install/update, About/version, cache helpers, and optional realtime notes sync for Kody package apps.

starter-remix/app/controllers/notes.tsx

87 lines · 2.6 KB · TypeScript
/** @jsxRuntime automatic */
/** @jsxImportSource remix/ui */
import type { Controller } from 'remix/router'
import * as s from 'remix/data-schema'
import * as f from 'remix/data-schema/form-data'
import { redirect } from 'remix/response/redirect'
import { addNote, deleteNote, listNotes } from '../data/notes.ts'
import { render } from '../ui/render.tsx'
import { AppShell } from '../ui/layout.tsx'
import { ErrorBanner } from '../ui/error-banner.tsx'
import { NotesDemo } from '../ui/notes-demo.tsx'
import { routes } from '../routes.ts'

const addSchema = f.object({
	intent: f.field(s.string()),
	text: f.field(s.string()),
})

const deleteSchema = f.object({
	intent: f.field(s.string()),
	id: f.field(s.string()),
})

function wantsJson(context: { request: Request }) {
	// Client enhance sends Accept: application/json. Browser document POSTs send text/html…
	const accept = (context.request.headers.get('accept') || '').toLowerCase()
	return accept.startsWith('application/json')
}

export default {
	actions: {
		async index(context) {
			const notes = await listNotes(context)
			return render(
				context,
				<AppShell page="notes" heading="Notes">
					<NotesDemo notes={notes} actionHref={routes.notes.action.href()} />
				</AppShell>,
				{ page: 'notes', title: 'Notes · __APP_TITLE__' },
			)
		},

		async action(context) {
			const form = context.get(FormData)
			const intent = String(form?.get?.('intent') ?? '')
			const json = wantsJson(context)

			if (intent === 'delete') {
				const parsed = s.parseSafe(deleteSchema, form)
				const id = parsed.success ? String(parsed.value.id || '').trim() : ''
				const result = id ? await deleteNote(context, id) : { ok: false }
				if (json) return Response.json({ ok: Boolean(result?.ok), id })
				return redirect(routes.notes.index.href(), 303)
			}

			const parsed = s.parseSafe(addSchema, form)
			const text = parsed.success ? String(parsed.value.text || '').trim() : ''
			if (!text) {
				if (json) {
					return Response.json(
						{ ok: false, error: 'A note needs some text.' },
						{ status: 400 },
					)
				}
				return render(
					context,
					<AppShell page="notes" heading="Notes">
						<ErrorBanner
							title="Could not add note"
							message="A note needs some text."
							hint={
								<a class="pak-btn" href={routes.notes.index.href()}>
									Back
								</a>
							}
						/>
					</AppShell>,
					{ status: 400, page: 'notes', title: 'Notes · __APP_TITLE__' },
				)
			}

			const note = await addNote(context, text)
			if (json) return Response.json({ ok: true, note })
			return redirect(routes.notes.index.href(), 303)
		},
	},
} satisfies Controller<typeof routes.notes>