Asistent AI
La zi
{
"pages": [
{
"slug": "todo-list",
"title": "Todo List",
"description": "A page to create and view todo items.",
"components": [
{
"component_name": "Loading Bar",
"component_description": "A thin animated loading bar shown at the top of the page during SDK requests.",
"component_code": "<div id=\"loading-bar-container\" class=\"fixed top-0 left-0 w-full h-1 z-50 pointer-events-none hidden\">\n <div id=\"loading-bar\" class=\"h-full bg-blue-600 dark:bg-blue-400 animate-loading-bar\" style=\"width: 0%\"></div>\n</div>\n<style>\n @keyframes loading-progress {\n 0% { width: 0%; }\n 20% { width: 30%; }\n 40% { width: 55%; }\n 60% { width: 75%; }\n 80% { width: 90%; }\n 100% { width: 100%; }\n }\n .animate-loading-bar {\n animation: loading-progress 1.5s ease-out forwards;\n }\n @keyframes loading-fade-out {\n 0% { opacity: 1; }\n 100% { opacity: 0; }\n }\n .animate-loading-fade {\n animation: loading-fade-out 0.3s ease-out forwards;\n }\n</style>",
"component_script": "let _loadingCount = 0;\n\nfunction showLoading() {\n _loadingCount++;\n const container = document.getElementById('loading-bar-container');\n const bar = document.getElementById('loading-bar');\n if (container) {\n container.classList.remove('hidden');\n bar.classList.remove('animate-loading-fade');\n bar.style.width = '0%';\n bar.offsetHeight;\n bar.classList.add('animate-loading-bar');\n }\n}\n\nfunction hideLoading() {\n _loadingCount = Math.max(0, _loadingCount - 1);\n if (_loadingCount > 0) return;\n const container = document.getElementById('loading-bar-container');\n const bar = document.getElementById('loading-bar');\n if (container && bar) {\n bar.style.width = '100%';\n bar.classList.remove('animate-loading-bar');\n bar.classList.add('animate-loading-fade');\n setTimeout(() => {\n if (_loadingCount === 0) {\n container.classList.add('hidden');\n bar.classList.remove('animate-loading-fade');\n bar.style.width = '0%';\n }\n }, 300);\n }\n}"
},
{
"component_name": "Todo List",
"component_description": "Displays a list of todo items with styled cards, including mark as done, edit and delete functionality.",
"component_code": "<div class=\"mt-5 w-full max-w-xl mx-auto\">\n <h2 class=\"text-2xl font-bold text-gray-900 dark:text-gray-100 mb-4\">My Todos</h2>\n <div id=\"todo-list\" class=\"space-y-2\"></div>\n <p id=\"empty-state\" class=\"text-gray-400 dark:text-gray-500 text-center py-8 hidden\">No todos yet. Add one below!</p>\n</div>",
"component_script": "let _todosCache = [];\n\nfunction isDone(todo) {\n return todo.done === true || todo.done === 'true' || todo.done === 1;\n}\n\nfunction escapeAttr(str) {\n return String(str).replace(/&/g, '&').replace(/\"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>');\n}\n\nasync function fetchTodos() {\n showLoading();\n try {\n _todosCache = await sdk.getRecords('todos');\n renderTodos();\n } catch (error) {\n console.error(error);\n } finally {\n hideLoading();\n }\n}\n\nfunction renderTodos() {\n const listEl = document.getElementById('todo-list');\n const emptyEl = document.getElementById('empty-state');\n listEl.innerHTML = '';\n if (_todosCache.length === 0) {\n emptyEl.classList.remove('hidden');\n } else {\n emptyEl.classList.add('hidden');\n _todosCache.forEach(todo => {\n const done = isDone(todo);\n const nameClass = done ? 'line-through text-gray-400 dark:text-gray-500' : 'text-gray-900 dark:text-gray-100';\n const cardClass = done ? 'bg-gray-50 dark:bg-gray-800/50 border-gray-100 dark:border-gray-700/50' : 'bg-white dark:bg-gray-800 border-gray-200 dark:border-gray-700';\n const checkBtnClass = done\n ? 'bg-emerald-500 dark:bg-emerald-400 border-emerald-500 dark:border-emerald-400'\n : 'border-gray-300 dark:border-gray-500 hover:border-emerald-400 dark:hover:border-emerald-400';\n const checkSvg = done\n ? '<svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-4 w-4 text-white dark:text-gray-900\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"3\" d=\"M5 13l4 4L19 7\"/></svg>'\n : '';\n const safeName = escapeAttr(todo.name);\n const todoId = todo.id;\n\n const div = document.createElement('div');\n div.id = 'todo-item-' + todoId;\n div.className = 'flex items-center justify-between ' + cardClass + ' border rounded-lg px-4 py-3 shadow-sm hover:shadow-md transition-shadow';\n div.innerHTML = `\n <div id=\"todo-view-${todoId}\" class=\"flex items-center gap-3 w-full\">\n <button id=\"todo-check-${todoId}\" class=\"flex-shrink-0 w-6 h-6 rounded-full border-2 flex items-center justify-center transition-colors ${checkBtnClass}\" title=\"${done ? 'Mark as undone' : 'Mark as done'}\">\n ${checkSvg}\n </button>\n <span class=\"${nameClass} text-base flex-1 transition-colors\">${todo.name}</span>\n <div class=\"flex items-center gap-1\">\n <button id=\"todo-edit-btn-${todoId}\" class=\"text-blue-500 hover:text-blue-700 dark:text-blue-400 dark:hover:text-blue-300 transition-colors p-1 rounded hover:bg-blue-50 dark:hover:bg-blue-900/30\" title=\"Edit\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z\"/></svg>\n </button>\n <button id=\"todo-delete-btn-${todoId}\" class=\"text-rose-500 hover:text-rose-700 dark:text-rose-400 dark:hover:text-rose-300 transition-colors p-1 rounded hover:bg-rose-50 dark:hover:bg-rose-900/30\" title=\"Delete\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" class=\"h-5 w-5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\"><path stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16\"/></svg>\n </button>\n </div>\n </div>\n <div id=\"todo-edit-${todoId}\" class=\"hidden flex items-center gap-2 w-full\">\n <input id=\"todo-edit-input-${todoId}\" type=\"text\" value=\"${safeName}\" class=\"flex-1 px-3 py-1.5 rounded-md border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent text-sm\">\n <button id=\"todo-save-btn-${todoId}\" class=\"px-3 py-1.5 bg-emerald-600 hover:bg-emerald-700 dark:bg-emerald-500 dark:hover:bg-emerald-600 text-white text-sm font-medium rounded-md transition-colors\">Save</button>\n <button id=\"todo-cancel-btn-${todoId}\" class=\"px-3 py-1.5 bg-gray-200 hover:bg-gray-300 dark:bg-gray-600 dark:hover:bg-gray-500 text-gray-700 dark:text-gray-200 text-sm font-medium rounded-md transition-colors\">Cancel</button>\n </div>\n `;\n listEl.appendChild(div);\n\n document.getElementById('todo-check-' + todoId).addEventListener('click', function() {\n toggleDone(todoId);\n });\n document.getElementById('todo-edit-btn-' + todoId).addEventListener('click', function() {\n startEdit(todoId, todo.name);\n });\n document.getElementById('todo-delete-btn-' + todoId).addEventListener('click', function() {\n deleteTodo(todoId);\n });\n document.getElementById('todo-save-btn-' + todoId).addEventListener('click', function() {\n saveEdit(todoId);\n });\n document.getElementById('todo-cancel-btn-' + todoId).addEventListener('click', function() {\n cancelEdit(todoId);\n });\n document.getElementById('todo-edit-input-' + todoId).addEventListener('keydown', function(e) {\n handleEditKey(e, todoId);\n });\n });\n }\n}\n\nasync function toggleDone(id) {\n const todo = _todosCache.find(t => t.id === id);\n if (!todo) return;\n const newDone = !isDone(todo);\n showLoading();\n try {\n await sdk.updateRecord('todos', id, { done: newDone });\n fetchTodos();\n } catch (error) {\n console.error(error);\n hideLoading();\n }\n}\n\nfunction startEdit(id, currentName) {\n const viewEl = document.getElementById('todo-view-' + id);\n const editEl = document.getElementById('todo-edit-' + id);\n const inputEl = document.getElementById('todo-edit-input-' + id);\n viewEl.classList.add('hidden');\n editEl.classList.remove('hidden');\n inputEl.value = currentName;\n inputEl.focus();\n inputEl.select();\n}\n\nfunction cancelEdit(id) {\n const viewEl = document.getElementById('todo-view-' + id);\n const editEl = document.getElementById('todo-edit-' + id);\n viewEl.classList.remove('hidden');\n editEl.classList.add('hidden');\n}\n\nfunction handleEditKey(event, id) {\n if (event.key === 'Enter') {\n saveEdit(id);\n } else if (event.key === 'Escape') {\n cancelEdit(id);\n }\n}\n\nasync function saveEdit(id) {\n try {\n const inputEl = document.getElementById('todo-edit-input-' + id);\n const value = inputEl.value.trim();\n if (!value) return;\n showLoading();\n await sdk.updateRecord('todos', id, { name: value });\n fetchTodos();\n } catch (error) {\n console.error(error);\n hideLoading();\n }\n}\n\nasync function deleteTodo(id) {\n showLoading();\n try {\n await sdk.deleteRecord('todos', id);\n fetchTodos();\n } catch (error) {\n console.error(error);\n hideLoading();\n }\n}\n\nfetchTodos();\nsdk.on('todo:created', () => fetchTodos());"
},
{
"component_name": "Add Todo",
"component_description": "A styled form to add new todo items.",
"component_code": "<div class=\"w-full max-w-xl mx-auto mt-6\">\n <form id=\"add-todo-form\" class=\"flex gap-3\">\n <input id=\"todo-input\" type=\"text\" placeholder=\"What needs to be done?\" class=\"flex-1 px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500 dark:focus:ring-blue-400 focus:border-transparent transition-shadow\">\n <button type=\"submit\" class=\"px-6 py-3 bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white font-semibold rounded-lg shadow-sm hover:shadow-md transition-all active:scale-95\">Add</button>\n </form>\n</div>",
"component_script": "document.getElementById('add-todo-form').addEventListener('submit', async function(event) {\n event.preventDefault();\n try {\n const todoInput = document.getElementById('todo-input');\n const value = todoInput.value.trim();\n if (!value) return;\n showLoading();\n await sdk.createRecord('todos', { name: value, done: false });\n todoInput.value = '';\n sdk.emit('todo:created');\n } catch (error) {\n console.error(error);\n } finally {\n hideLoading();\n }\n});"
}
]
}
],
"tables": [
{
"slug": "todos",
"name": "Todos",
"description": "A table to store todo items.",
"fields": [
{
"slug": "name",
"name": "Name",
"type": "String"
},
{
"slug": "done",
"name": "Done",
"type": "Boolean"
}
]
}
]
}