{
  "id": "app-skeleton-form",
  "name": "CRUD Form Builder",
  "category": "software-builder",
  "tags": ["form", "crud", "localstorage", "data-entry", "builder"],
  "description": "CRUD form with localStorage persistence for add, edit, delete, and list operations.",
  "triggers": ["crud form", "form builder", "data entry", "contact form", "survey"],
  "defaultSize": { "w": 5, "h": 5 },
  "source": "const AppSkeletonForm = () => {\n  const STORAGE_KEY = 'titan_form_items';\n  const [items, setItems] = React.useState([]);\n  const [form, setForm] = React.useState({ name: '', email: '', role: 'User' });\n  const [editingId, setEditingId] = React.useState(null);\n  const [filter, setFilter] = React.useState('');\n\n  React.useEffect(() => {\n    try {\n      const raw = localStorage.getItem(STORAGE_KEY);\n      if (raw) setItems(JSON.parse(raw));\n    } catch (e) {\n      setItems([]);\n    }\n  }, []);\n\n  React.useEffect(() => {\n    localStorage.setItem(STORAGE_KEY, JSON.stringify(items));\n  }, [items]);\n\n  const resetForm = () => {\n    setForm({ name: '', email: '', role: 'User' });\n    setEditingId(null);\n  };\n\n  const handleSubmit = () => {\n    if (!form.name.trim() || !form.email.trim()) return;\n    if (editingId !== null) {\n      setItems(prev => prev.map(it => it.id === editingId ? { ...form, id: editingId } : it));\n    } else {\n      setItems(prev => [...prev, { ...form, id: Date.now() }]);\n    }\n    resetForm();\n  };\n\n  const handleEdit = (item) => {\n    setForm({ name: item.name, email: item.email, role: item.role });\n    setEditingId(item.id);\n  };\n\n  const handleDelete = (id) => {\n    setItems(prev => prev.filter(it => it.id !== id));\n    if (editingId === id) resetForm();\n  };\n\n  const filtered = items.filter(it =>\n    it.name.toLowerCase().includes(filter.toLowerCase()) ||\n    it.email.toLowerCase().includes(filter.toLowerCase())\n  );\n\n  const styles = {\n    container: {\n      fontFamily: 'system-ui, -apple-system, sans-serif',\n      padding: 24,\n      background: '#f8fafc',\n      minHeight: '100%',\n      color: '#1e293b'\n    },\n    header: {\n      fontSize: 20,\n      fontWeight: 700,\n      marginBottom: 16\n    },\n    row: {\n      display: 'flex',\n      gap: 12,\n      marginBottom: 12,\n      flexWrap: 'wrap'\n    },\n    input: {\n      padding: '10px 12px',\n      borderRadius: 8,\n      border: '1px solid #cbd5e1',\n      fontSize: 14,\n      outline: 'none',\n      flex: 1,\n      minWidth: 140\n    },\n    select: {\n      padding: '10px 12px',\n      borderRadius: 8,\n      border: '1px solid #cbd5e1',\n      fontSize: 14,\n      outline: 'none',\n      background: '#fff',\n      minWidth: 100\n    },\n    btn: {\n      padding: '10px 18px',\n      borderRadius: 8,\n      border: 'none',\n      background: '#2563eb',\n      color: '#fff',\n      fontWeight: 600,\n      cursor: 'pointer',\n      fontSize: 14\n    },\n    btnGhost: {\n      padding: '10px 18px',\n      borderRadius: 8,\n      border: '1px solid #cbd5e1',\n      background: '#fff',\n      color: '#475569',\n      fontWeight: 600,\n      cursor: 'pointer',\n      fontSize: 14\n    },\n    btnDanger: {\n      padding: '6px 12px',\n      borderRadius: 6,\n      border: 'none',\n      background: '#ef4444',\n      color: '#fff',\n      fontWeight: 600,\n      cursor: 'pointer',\n      fontSize: 12\n    },\n    btnSmall: {\n      padding: '6px 12px',\n      borderRadius: 6,\n      border: 'none',\n      background: '#2563eb',\n      color: '#fff',\n      fontWeight: 600,\n      cursor: 'pointer',\n      fontSize: 12\n    },\n    card: {\n      background: '#fff',\n      borderRadius: 12,\n      padding: 16,\n      boxShadow: '0 1px 3px rgba(0,0,0,0.08)',\n      border: '1px solid #e2e8f0',\n      marginBottom: 16\n    },\n    tableWrap: {\n      overflowX: 'auto'\n    },\n    table: {\n      width: '100%',\n      borderCollapse: 'collapse',\n      fontSize: 14\n    },\n    th: {\n      textAlign: 'left',\n      padding: '10px 8px',\n      borderBottom: '2px solid #e2e8f0',\n      color: '#64748b',\n      fontWeight: 600,\n      fontSize: 12,\n      textTransform: 'uppercase'\n    },\n    td: {\n      padding: '10px 8px',\n      borderBottom: '1px solid #f1f5f9'\n    },\n    badge: {\n      display: 'inline-block',\n      padding: '2px 8px',\n      borderRadius: 999,\n      fontSize: 11,\n      fontWeight: 600,\n      background: '#dbeafe',\n      color: '#1d4ed8'\n    },\n    empty: {\n      textAlign: 'center',\n      color: '#94a3b8',\n      padding: 24,\n      fontSize: 14\n    },\n    stats: {\n      display: 'flex',\n      gap: 16,\n      marginBottom: 16\n    },\n    stat: {\n      background: '#fff',\n      borderRadius: 10,\n      padding: '12px 16px',\n      minWidth: 100,\n      boxShadow: '0 1px 2px rgba(0,0,0,0.06)',\n      border: '1px solid #e2e8f0'\n    },\n    statValue: {\n      fontSize: 22,\n      fontWeight: 700,\n      color: '#0f172a'\n    },\n    statLabel: {\n      fontSize: 12,\n      color: '#64748b',\n      marginTop: 2\n    }\n  };\n\n  return (\n    <div style={styles.container}>\n      <div style={styles.header}>\n        \u270E CRUD Form Builder\n      </div>\n\n      <div style={styles.stats}>\n        <div style={styles.stat}>\n          <div style={styles.statValue}>{items.length}</div>\n          <div style={styles.statLabel}>Total Entries</div>\n        </div>\n        <div style={styles.stat}>\n          <div style={styles.statValue}>{items.filter(i => i.role === 'Admin').length}</div>\n          <div style={styles.statLabel}>Admins</div>\n        </div>\n      </div>\n\n      <div style={styles.card}>\n        <div style={{ fontWeight: 600, marginBottom: 12, fontSize: 14, color: '#475569' }}>\n          {editingId !== null ? 'Edit Entry' : 'Add New Entry'}\n        </div>\n        <div style={styles.row}>\n          <input\n            style={styles.input}\n            placeholder=\"Full name\"\n            value={form.name}\n            onChange={e => setForm({ ...form, name: e.target.value })}\n          />\n          <input\n            style={styles.input}\n            placeholder=\"Email address\"\n            value={form.email}\n            onChange={e => setForm({ ...form, email: e.target.value })}\n          />\n          <select\n            style={styles.select}\n            value={form.role}\n            onChange={e => setForm({ ...form, role: e.target.value })}\n          >\n            <option>User</option>\n            <option>Admin</option>\n            <option>Editor</option>\n          </select>\n        </div>\n        <div style={styles.row}>\n          <button style={styles.btn} onClick={handleSubmit}>\n            {editingId !== null ? 'Update Entry' : 'Add Entry'}\n          </button>\n          {editingId !== null && (\n            <button style={styles.btnGhost} onClick={resetForm}>Cancel</button>\n          )}\n        </div>\n      </div>\n\n      <div style={styles.card}>\n        <input\n          style={{ ...styles.input, width: '100%', marginBottom: 12, boxSizing: 'border-box' }}\n          placeholder=\"Search entries...\"\n          value={filter}\n          onChange={e => setFilter(e.target.value)}\n        />\n        {filtered.length === 0 ? (\n          <div style={styles.empty}>No entries yet. Add one above.</div>\n        ) : (\n          <div style={styles.tableWrap}>\n            <table style={styles.table}>\n              <thead>\n                <tr>\n                  <th style={styles.th}>Name</th>\n                  <th style={styles.th}>Email</th>\n                  <th style={styles.th}>Role</th>\n                  <th style={styles.th}></th>\n                </tr>\n              </thead>\n              <tbody>\n                {filtered.map(item => (\n                  <tr key={item.id}>\n                    <td style={styles.td}>{item.name}</td>\n                    <td style={styles.td}>{item.email}</td>\n                    <td style={styles.td}><span style={styles.badge}>{item.role}</span></td>\n                    <td style={styles.td}>\n                      <div style={{ display: 'flex', gap: 6 }}>\n                        <button style={styles.btnSmall} onClick={() => handleEdit(item)}>Edit</button>\n                        <button style={styles.btnDanger} onClick={() => handleDelete(item.id)}>Delete</button>\n                      </div>\n                    </td>\n                  </tr>\n                ))}\n              </tbody>\n            </table>\n          </div>\n        )}\n      </div>\n    </div>\n  );\n};\nrender(<AppSkeletonForm/>);",
  "placeholders": [{ "name": "REPLACE_WITH_STORAGE_KEY", "description": "localStorage key used to persist form entries", "default": "titan_form_items" }]
}
