Screen forms
Store settings, a profile, or feature switches need a form, but they aren’t one record of a model. A screen can have a form instead of a component. Azu renders it like a resource form and saves through your validator.
Where the values live is up to you. load reads them, and save writes them.
Generate one
Section titled “Generate one”node ace azu:screen StoreSettings --formThat writes the screen and a validator in app/validators/store_settings.ts. Fill in load and save. The demo keeps its settings in a key and value table, through its own Setting model:
import { defineScreen, field } from '@mezielabs/azu'import { DateTime } from 'luxon'import { manageSettings } from '#abilities/main'import Setting from '#models/setting'import { storeSettingsValidator } from '#validators/store_settings'
/** * The store's settings (FR-23): a form Azu renders, for admins only * (manageSettings). `load` and `save` go through the app's own Setting * model; Azu never touches the settings table. */export default defineScreen({ label: 'Store settings', group: 'Settings', icon: 'settings', ability: manageSettings, form: { fields: [ field.text('storeName'), field.text('supportEmail').help('Shown to customers on receipts and the contact page'), field.select('currency').options({ NGN: 'Nigerian naira', GHS: 'Ghanaian cedi', KES: 'Kenyan shilling', ZAR: 'South African rand', USD: 'US dollar', GBP: 'British pound', }), field.decimal('vatRate').label('VAT rate (%)').min(0).max(100).step('0.01'), field.boolean('maintenanceMode').help('Customers see a notice instead of the shop'), field.richText('checkoutAnnouncement').help('Shown above the basket at checkout'), field.datetime('lastChangedAt').label('Last changed').readOnly(), ], validator: storeSettingsValidator, load: () => Setting.values([ 'storeName', 'supportEmail', 'currency', 'vatRate', 'maintenanceMode', 'checkoutAnnouncement', 'lastChangedAt', ]), save: (input) => Setting.store({ ...input, lastChangedAt: DateTime.now() }), },})And its validator:
import vine from '@vinejs/vine'import { CURRENCIES } from '#validators/coupon'import { decimalString, labelled, maxDecimal, twoPlaces } from '#validators/messages'
/** * The Store settings screen's validator (FR-23). Azu hands it only the * form's writable fields; "last changed" is set by the screen's `save`. */export const storeSettingsValidator = labelled( vine.create({ storeName: vine.string().trim().minLength(2).maxLength(80), supportEmail: vine.string().trim().email().maxLength(254), currency: vine.enum(CURRENCIES), // Stored as text, so kept in one format: "7.5" is saved as "7.50" vatRate: decimalString(0) .use(maxDecimal({ max: 100 })) .transform(twoPlaces), maintenanceMode: vine.boolean(), checkoutAnnouncement: vine.string().trim().maxLength(2000).nullable().optional(), }), { storeName: 'Store name', supportEmail: 'Support email', currency: 'Currency', vatRate: 'VAT rate', maintenanceMode: 'Maintenance mode', checkoutAnnouncement: 'Checkout announcement', }, { email: '{{ field }} must be an email address' })formorcomponent, not both. Nodatabeside a form either: its values come fromload. Either mistake is a type error and stops the app from booting.- Field names are checked against what
loadreturns. A misspelled field failstsc, as on resources. - Supported fields: text, textarea, number, decimal, boolean, select, date, datetime, and rich text. File, image, and relation fields aren’t supported on screens yet.
- The screen’s
abilitycovers the form, for opening it and for saving it. So fields have no.ability()here..readOnly()works as on resources.
Saving
Section titled “Saving”Saving works like a resource form.
- Only the form’s writable fields are read from the request.
- The validator checks them. Read-only fields reach the validator with
load’s values. savegets the validator’s output and nothing else.
Rich text is sanitised, and datetimes are read in the panel’s timezone.
A valid save comes back to the screen with a toast: “Store settings saved”, or your successMessage. Invalid input shows the errors under each field, with the input kept.
The page
Section titled “The page”It’s the resource form’s layout: the same widths, required markers, and limits from the validator. It has the sticky save bar with one “Save changes” button, and the “Leave without saving?” question. See Forms.