feat: add application form configuration and migration for shared application form data
- Introduced a new configuration file for the application form with various fields and validation messages. - Created a migration script to set up the database schema for the application form, including necessary tables and default values. - Added a migration for supporting awards copy to enhance the pages with additional columns. - Implemented a new SPA component for the application form section, integrating form logic and UI elements.
This commit is contained in:
191
src/migrations/20260713_155500_shared_application_form.ts
Normal file
191
src/migrations/20260713_155500_shared_application_form.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
import { type MigrateDownArgs, type MigrateUpArgs, sql } from '@payloadcms/db-sqlite'
|
||||
|
||||
import { homeContent } from '@/spa/homeContent'
|
||||
|
||||
type MigrationDB = MigrateUpArgs['db']
|
||||
|
||||
const ident = (value: string) => `\`${value.replace(/`/g, '``')}\``
|
||||
const literal = (value: string) => `'${value.replace(/'/g, "''")}'`
|
||||
const snake = (value: string) => value.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`)
|
||||
|
||||
const sectionScalars = [
|
||||
['eyebrow', homeContent.application.eyebrow],
|
||||
['heading', homeContent.application.heading],
|
||||
['body', homeContent.application.body],
|
||||
['awardImageAlt', homeContent.application.awardImageAlt],
|
||||
['formEyebrow', homeContent.application.formEyebrow],
|
||||
['formTitle', homeContent.application.formTitle],
|
||||
['footnote', homeContent.application.footnote],
|
||||
['footnoteStrong', homeContent.application.footnoteStrong],
|
||||
] as const
|
||||
|
||||
const topLevelFormScalars = [
|
||||
['stepCompleteLabel', homeContent.form.stepCompleteLabel],
|
||||
['backLabel', homeContent.form.backLabel],
|
||||
['nextLabel', homeContent.form.nextLabel],
|
||||
['submitLabel', homeContent.form.submitLabel],
|
||||
['yesLabel', homeContent.form.yesLabel],
|
||||
['noLabel', homeContent.form.noLabel],
|
||||
['requiredSuffix', homeContent.form.requiredSuffix],
|
||||
] as const
|
||||
|
||||
const formGroups = {
|
||||
validation: homeContent.form.validation,
|
||||
typeStep: homeContent.form.typeStep,
|
||||
eligibility: homeContent.form.eligibility,
|
||||
selfContact: homeContent.form.selfContact,
|
||||
selfSummary: homeContent.form.selfSummary,
|
||||
nominationCompany: homeContent.form.nominationCompany,
|
||||
nominationContact: homeContent.form.nominationContact,
|
||||
nominationSummary: homeContent.form.nominationSummary,
|
||||
success: homeContent.form.success,
|
||||
} as const
|
||||
|
||||
const mainScalarDefinitions = [
|
||||
...sectionScalars.map(([name, value]) => [`section_${snake(name)}`, value] as const),
|
||||
...topLevelFormScalars.map(([name, value]) => [`form_${snake(name)}`, value] as const),
|
||||
...Object.entries(formGroups).flatMap(([group, values]) =>
|
||||
Object.entries(values).map(([name, value]) => [`form_${snake(group)}_${snake(name)}`, value] as const),
|
||||
),
|
||||
]
|
||||
|
||||
async function tableExists(db: MigrationDB, tableName: string) {
|
||||
const rows = (await db.all(
|
||||
sql.raw(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ${literal(tableName)}`),
|
||||
)) as Array<{ name: string }>
|
||||
return rows.length > 0
|
||||
}
|
||||
|
||||
async function columnExists(db: MigrationDB, tableName: string, columnName: string) {
|
||||
if (!(await tableExists(db, tableName))) return false
|
||||
const columns = (await db.all(sql.raw(`PRAGMA table_info(${ident(tableName)})`))) as Array<{ name: string }>
|
||||
return columns.some((column) => column.name === columnName)
|
||||
}
|
||||
|
||||
async function createArrayTable(
|
||||
db: MigrationDB,
|
||||
tableName: string,
|
||||
fields: Array<{ name: string; defaultValue?: string }>,
|
||||
) {
|
||||
const fieldDefinitions = fields
|
||||
.map((field) => `${ident(field.name)} text${field.defaultValue ? ` DEFAULT ${literal(field.defaultValue)}` : ''}`)
|
||||
.join(',\n')
|
||||
|
||||
await db.run(sql.raw(`
|
||||
CREATE TABLE IF NOT EXISTS ${ident(tableName)} (
|
||||
\`_order\` integer NOT NULL,
|
||||
\`_parent_id\` integer NOT NULL,
|
||||
\`id\` text PRIMARY KEY NOT NULL,
|
||||
${fieldDefinitions},
|
||||
FOREIGN KEY (\`_parent_id\`) REFERENCES \`application_form\`(\`id\`) ON UPDATE no action ON DELETE cascade
|
||||
);
|
||||
`))
|
||||
await db.run(sql.raw(`CREATE INDEX IF NOT EXISTS ${ident(`${tableName}_order_idx`)} ON ${ident(tableName)} (\`_order\`);`))
|
||||
await db.run(sql.raw(`CREATE INDEX IF NOT EXISTS ${ident(`${tableName}_parent_id_idx`)} ON ${ident(tableName)} (\`_parent_id\`);`))
|
||||
}
|
||||
|
||||
async function seedArrayFromPage(
|
||||
db: MigrationDB,
|
||||
target: string,
|
||||
source: string,
|
||||
columns: string[],
|
||||
defaults: ReadonlyArray<Record<string, unknown>>,
|
||||
) {
|
||||
const sourceExists = await tableExists(db, source)
|
||||
if (sourceExists) {
|
||||
const columnList = columns.map(ident).join(', ')
|
||||
await db.run(sql.raw(`
|
||||
INSERT OR IGNORE INTO ${ident(target)} (\`_order\`, \`_parent_id\`, \`id\`, ${columnList})
|
||||
SELECT source.\`_order\`, 1, lower(hex(randomblob(16))), ${columns.map((column) => `source.${ident(column)}`).join(', ')}
|
||||
FROM ${ident(source)} source
|
||||
INNER JOIN \`pages\` page ON page.\`id\` = source.\`_parent_id\`
|
||||
WHERE page.\`spa_path\` = '/' OR page.\`slug\` IN ('startseite', 'home')
|
||||
ORDER BY source.\`_order\`;
|
||||
`))
|
||||
}
|
||||
|
||||
const count = (await db.all(sql.raw(`SELECT COUNT(*) AS count FROM ${ident(target)} WHERE \`_parent_id\` = 1;`))) as Array<{ count: number }>
|
||||
if (Number(count[0]?.count || 0) > 0) return
|
||||
|
||||
for (const [index, item] of defaults.entries()) {
|
||||
const values = columns.map((column) => literal(String(item[column] ?? ''))).join(', ')
|
||||
await db.run(sql.raw(`
|
||||
INSERT INTO ${ident(target)} (\`_order\`, \`_parent_id\`, \`id\`, ${columns.map(ident).join(', ')})
|
||||
VALUES (${index + 1}, 1, lower(hex(randomblob(16))), ${values});
|
||||
`))
|
||||
}
|
||||
}
|
||||
|
||||
export async function up({ db }: MigrateUpArgs): Promise<void> {
|
||||
const scalarColumns = mainScalarDefinitions
|
||||
.map(([name, value]) => `${ident(name)} text DEFAULT ${literal(value)}`)
|
||||
.join(',\n')
|
||||
|
||||
await db.run(sql.raw(`
|
||||
CREATE TABLE IF NOT EXISTS \`application_form\` (
|
||||
\`id\` integer PRIMARY KEY NOT NULL,
|
||||
${scalarColumns},
|
||||
\`section_award_image_id\` integer,
|
||||
\`updated_at\` text,
|
||||
\`created_at\` text,
|
||||
FOREIGN KEY (\`section_award_image_id\`) REFERENCES \`media\`(\`id\`) ON UPDATE no action ON DELETE set null
|
||||
);
|
||||
`))
|
||||
await db.run(sql.raw('CREATE INDEX IF NOT EXISTS `application_form_section_section_award_image_idx` ON `application_form` (`section_award_image_id`);'))
|
||||
|
||||
await createArrayTable(db, 'application_form_benefits', [
|
||||
{ name: 'num' },
|
||||
{ name: 'label' },
|
||||
{ name: 'desc' },
|
||||
])
|
||||
await createArrayTable(db, 'application_form_self_steps', [
|
||||
{ name: 'label' },
|
||||
{ name: 'icon', defaultValue: 'trophy' },
|
||||
])
|
||||
await createArrayTable(db, 'application_form_nom_steps', [
|
||||
{ name: 'label' },
|
||||
{ name: 'icon', defaultValue: 'trophy' },
|
||||
])
|
||||
await createArrayTable(db, 'application_form_emp_opts', [
|
||||
{ name: 'value' },
|
||||
{ name: 'label' },
|
||||
])
|
||||
|
||||
await db.run(sql.raw(`
|
||||
INSERT OR IGNORE INTO \`application_form\` (\`id\`, \`updated_at\`, \`created_at\`)
|
||||
VALUES (1, strftime('%Y-%m-%dT%H:%M:%fZ', 'now'), strftime('%Y-%m-%dT%H:%M:%fZ', 'now'));
|
||||
`))
|
||||
|
||||
if (await columnExists(db, 'pages', 'home_application_eyebrow')) {
|
||||
const assignments = [
|
||||
...sectionScalars.map(([name]) => [`section_${snake(name)}`, `home_application_${snake(name)}`] as const),
|
||||
...topLevelFormScalars.map(([name]) => [`form_${snake(name)}`, `home_form_${snake(name)}`] as const),
|
||||
...Object.keys(formGroups).flatMap((group) =>
|
||||
Object.keys(formGroups[group as keyof typeof formGroups]).map((name) => [
|
||||
`form_${snake(group)}_${snake(name)}`,
|
||||
`home_form_${snake(group)}_${snake(name)}`,
|
||||
] as const),
|
||||
),
|
||||
]
|
||||
.filter(([, source]) => source !== 'home_application_award_image')
|
||||
.map(([target, source]) => `${ident(target)} = COALESCE((SELECT ${ident(source)} FROM \`pages\` WHERE \`spa_path\` = '/' OR \`slug\` IN ('startseite', 'home') ORDER BY CASE WHEN \`spa_path\` = '/' THEN 0 ELSE 1 END LIMIT 1), ${ident(target)})`)
|
||||
|
||||
if (await columnExists(db, 'pages', 'home_application_award_image_id')) {
|
||||
assignments.push("`section_award_image_id` = (SELECT `home_application_award_image_id` FROM `pages` WHERE `spa_path` = '/' OR `slug` IN ('startseite', 'home') ORDER BY CASE WHEN `spa_path` = '/' THEN 0 ELSE 1 END LIMIT 1)")
|
||||
}
|
||||
await db.run(sql.raw(`UPDATE \`application_form\` SET ${assignments.join(',\n')} WHERE \`id\` = 1;`))
|
||||
}
|
||||
|
||||
await seedArrayFromPage(db, 'application_form_benefits', 'pages_home_application_benefits', ['num', 'label', 'desc'], homeContent.application.benefits)
|
||||
await seedArrayFromPage(db, 'application_form_self_steps', 'home_self_steps', ['label', 'icon'], homeContent.form.selfSteps)
|
||||
await seedArrayFromPage(db, 'application_form_nom_steps', 'home_nom_steps', ['label', 'icon'], homeContent.form.nominationSteps)
|
||||
await seedArrayFromPage(db, 'application_form_emp_opts', 'home_emp_opts', ['value', 'label'], homeContent.form.employeeOptions)
|
||||
}
|
||||
|
||||
export async function down({ db }: MigrateDownArgs): Promise<void> {
|
||||
await db.run(sql.raw('DROP TABLE IF EXISTS `application_form_emp_opts`;'))
|
||||
await db.run(sql.raw('DROP TABLE IF EXISTS `application_form_nom_steps`;'))
|
||||
await db.run(sql.raw('DROP TABLE IF EXISTS `application_form_self_steps`;'))
|
||||
await db.run(sql.raw('DROP TABLE IF EXISTS `application_form_benefits`;'))
|
||||
await db.run(sql.raw('DROP TABLE IF EXISTS `application_form`;'))
|
||||
}
|
||||
Reference in New Issue
Block a user