← 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>