project-nomad/admin/app/validators/common.ts
Chris Sherwood 5afc3a270a feat: Improve curated collections UX with persistent tier selection
- Add installed_tiers table to persist user's tier selection per category
- Change tier selection behavior: clicking a tier now highlights it locally,
  user must click "Submit" to confirm (previously clicked = immediate download)
- Remove "Recommended" badge and asterisk (*) from tier displays
- Highlight installed tier instead of recommended tier in CategoryCard
- Add "Click to choose" hint when no tier is installed
- Save installed tier when downloading from Content Explorer or Easy Setup
- Pass installed tier to modal as default selection

Database:
- New migration: create installed_tiers table (category_slug unique, tier_slug)
- New model: InstalledTier

Backend:
- ZimService.listCuratedCategories() now includes installedTierSlug
- New ZimService.saveInstalledTier() method
- New POST /api/zim/save-installed-tier endpoint

Frontend:
- TierSelectionModal: local selection state, "Close" → "Submit" button
- CategoryCard: highlight based on installedTierSlug, add "Click to choose"
- Content Explorer: save tier after download, refresh categories
- Easy Setup: save tiers on wizard completion

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-24 15:33:50 -08:00

46 lines
907 B
TypeScript

import vine from '@vinejs/vine'
export const remoteDownloadValidator = vine.compile(
vine.object({
url: vine
.string()
.url({
require_tld: false, // Allow local URLs
})
.trim(),
})
)
export const remoteDownloadValidatorOptional = vine.compile(
vine.object({
url: vine
.string()
.url({
require_tld: false, // Allow local URLs
})
.trim()
.optional(),
})
)
export const filenameParamValidator = vine.compile(
vine.object({
params: vine.object({
filename: vine.string().trim().minLength(1).maxLength(4096),
}),
})
)
export const downloadCollectionValidator = vine.compile(
vine.object({
slug: vine.string(),
})
)
export const saveInstalledTierValidator = vine.compile(
vine.object({
categorySlug: vine.string().trim().minLength(1),
tierSlug: vine.string().trim().minLength(1),
})
)