UNPKG

@bigdigital/kiosk-content-sdk

Version:

A Firebase-powered Content Management System SDK optimized for kiosks with offline support, template management, and real-time connection monitoring

186 lines (165 loc) 4.97 kB
import { z } from "zod"; export const userRoles = ["admin", "editor", "viewer"] as const; export const fieldTypeEnum = z.enum([ "text", "number", "boolean", "select", "rich-text", "image", "video", "audio", "date", "multi-select", "color" ]); export const normalizeGroupName = (name: string): string => { let words = name.split(/[\s\-_]+/); if (words.length === 1) { words = words[0] .replace(/([a-z0-9])([A-Z])/g, '$1 $2') .replace(/([a-zA-Z])(\d)/g, '$1 $2') .replace(/([a-z])([a-z]*)/gi, (match, first, rest) => `${first}${rest.toLowerCase()}`) .split(/\s+/); } words = words .filter(word => word.length > 0) .map(word => word.toLowerCase()); if (words.length === 0) return ''; return words[0] + words.slice(1) .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(''); }; // Base schemas and types export const validationSchema = z.object({ required: z.boolean().default(false), min: z.number().optional(), max: z.number().optional(), pattern: z.string().optional(), customError: z.string().optional() }).optional(); export const fieldSchema = z.object({ id: z.string(), name: z.string().min(1, "Field name is required"), label: z.string(), type: fieldTypeEnum, description: z.string().optional(), defaultValue: z.any().optional(), options: z.array(z.string()).optional(), validation: validationSchema, step: z.number().optional(), groupId: z.string().optional() }); export const groupSchema = z.object({ id: z.string(), name: z.string(), description: z.string().optional(), order: z.number(), fieldIds: z.array(z.string()), normalizedName: z.string().optional() }); export const templateSchema = z.object({ id: z.string(), name: z.string(), description: z.string(), type: z.string(), fields: z.array(fieldSchema), groups: z.record(z.string(), groupSchema), ungroupedFieldIds: z.array(z.string()), groupOrder: z.array(z.string()), createdBy: z.string(), createdAt: z.number(), updatedAt: z.number(), version: z.number().default(1), settings: z.object({ cacheStrategy: z.enum(["memory", "local", "none"]).default("memory"), syncInterval: z.number().optional(), offlineSupport: z.boolean().default(false), }).default({ cacheStrategy: "memory", offlineSupport: false }) }); export const templateValuesSchema = z.object({ groups: z.record(z.string(), z.record(z.string(), z.any())).default({}), ungrouped: z.record(z.string(), z.any()).default({}), _template: z.object({ id: z.string(), version: z.number() }).optional() }); // Content schema with data field const baseContentSchema = z.object({ id: z.string(), title: z.string(), description: z.string(), type: z.enum(["text", "image", "video", "template"]), mediaUrl: z.string().optional(), projectIds: z.array(z.string()).default([]), createdBy: z.string(), createdAt: z.number(), updatedAt: z.number(), templateId: z.string().optional(), templateValues: templateValuesSchema.optional(), templateVersion: z.number().optional(), version: z.number().default(1), status: z.enum(["published", "draft", "archived"]).default("draft"), metadata: z.record(z.string(), z.any()).default({}), data: z.record(z.string(), z.any()).optional() }); export const contentSchema = z.preprocess((data: any) => { if (typeof data !== 'object' || data === null) return data; const processed = { ...data }; if (processed.published === true) { processed.status = "published"; } delete processed.published; return processed; }, baseContentSchema); // Export types export type Content = z.infer<typeof baseContentSchema>; export type Field = z.infer<typeof fieldSchema>; export type Group = z.infer<typeof groupSchema>; export type Template = z.infer<typeof templateSchema>; export type FieldType = z.infer<typeof fieldTypeEnum>; export type TemplateValues = z.infer<typeof templateValuesSchema>; export interface KioskConfig { projectId: string; apiKey: string; authDomain?: string; offlineSupport?: boolean; cacheStrategy?: "memory" | "local" | "none"; syncInterval?: number; cacheMaxAge?: number; } export interface CacheOptions { ttl?: number; strategy?: "memory" | "local" | "none"; validateOnLoad?: boolean; } export interface SyncOptions { force?: boolean; onProgress?: (progress: number) => void; onError?: (error: Error) => void; retryAttempts?: number; } export interface GroupedTemplateContent { group: Group & { normalizedName: string }; fields: Field[]; values: Record<string, any>; } export interface TemplateContentStructure { groups: GroupedTemplateContent[]; ungroupedFields: { fields: Field[]; values: Record<string, any>; }; } export interface ContentHookResult { content: Content | null; template: Template | null; loading: boolean; error: Error | null; isOnline: boolean; refresh?: () => void; }