From f7acba23d4cfe8f75fc9fdb111abfebfff481a8f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 23:12:52 +0000 Subject: [PATCH 1/4] Initial plan From 6ee69a5ff7bf3d88a0eac8e0f410c101f1f2aac7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 23:21:57 +0000 Subject: [PATCH 2/4] Refactor db.ts (1085 LOC) into focused modules Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com> --- src/lib/db-core.ts | 176 ++++++ src/lib/db-indexeddb.ts | 105 ++++ src/lib/db-localstorage.ts | 36 ++ src/lib/db-namespaces.ts | 108 ++++ src/lib/db-schema.ts | 106 ++++ src/lib/db-snippets.ts | 585 +++++++++++++++++++ src/lib/db.ts | 1122 ++---------------------------------- 7 files changed, 1153 insertions(+), 1085 deletions(-) create mode 100644 src/lib/db-core.ts create mode 100644 src/lib/db-indexeddb.ts create mode 100644 src/lib/db-localstorage.ts create mode 100644 src/lib/db-namespaces.ts create mode 100644 src/lib/db-schema.ts create mode 100644 src/lib/db-snippets.ts diff --git a/src/lib/db-core.ts b/src/lib/db-core.ts new file mode 100644 index 0000000..16818d7 --- /dev/null +++ b/src/lib/db-core.ts @@ -0,0 +1,176 @@ +/** + * Core database initialization and management + */ + +import initSqlJs, { Database } from 'sql.js' +import { loadFromIndexedDB, saveToIndexedDB, openIndexedDB, deleteFromIndexedDB } from './db-indexeddb' +import { loadFromLocalStorage, saveToLocalStorage, deleteFromLocalStorage } from './db-localstorage' +import { validateSchema, createTables } from './db-schema' +import { getStorageConfig, FlaskStorageAdapter, loadStorageConfig } from './storage' + +let dbInstance: Database | null = null +let sqlInstance: any = null +let flaskAdapter: FlaskStorageAdapter | null = null +let configLoaded = false + +async function wipeAndRecreateDB(): Promise { + console.warn('Wiping corrupted database and creating fresh schema...') + + await saveToIndexedDB(new Uint8Array()) + saveToLocalStorage(new Uint8Array()) + + await deleteFromIndexedDB() + deleteFromLocalStorage() + + dbInstance = null +} + +export async function initDB(): Promise { + if (dbInstance) return dbInstance + + if (!sqlInstance) { + sqlInstance = await initSqlJs({ + locateFile: (file) => `https://sql.js.org/dist/${file}` + }) + } + + let loadedData: Uint8Array | null = null + let schemaValid = false + + loadedData = await loadFromIndexedDB() + + if (!loadedData) { + loadedData = loadFromLocalStorage() + } + + if (loadedData && loadedData.length > 0) { + try { + const testDb = new sqlInstance.Database(loadedData) + schemaValid = await validateSchema(testDb) + + if (schemaValid) { + dbInstance = testDb + } else { + console.warn('Schema validation failed, wiping database') + testDb.close() + await wipeAndRecreateDB() + dbInstance = new sqlInstance.Database() + } + } catch (error) { + console.error('Failed to load saved database, creating new one:', error) + await wipeAndRecreateDB() + dbInstance = new sqlInstance.Database() + } + } else { + dbInstance = new sqlInstance.Database() + } + + if (!dbInstance) { + throw new Error('Failed to initialize database') + } + + createTables(dbInstance) + await saveDB() + + return dbInstance +} + +export async function saveDB() { + if (!dbInstance) return + + try { + const data = dbInstance.export() + + const savedToIDB = await saveToIndexedDB(data) + + if (!savedToIDB) { + saveToLocalStorage(data) + } + } catch (error) { + console.error('Failed to save database:', error) + } +} + +export function getFlaskAdapter(): FlaskStorageAdapter | null { + if (!configLoaded) { + loadStorageConfig() + configLoaded = true + } + + const config = getStorageConfig() + if (config.backend === 'flask' && config.flaskUrl) { + try { + if (!flaskAdapter || flaskAdapter['baseUrl'] !== config.flaskUrl) { + flaskAdapter = new FlaskStorageAdapter(config.flaskUrl) + } + return flaskAdapter + } catch (error) { + console.warn('Failed to create Flask adapter:', error) + return null + } + } + return null +} + +export async function exportDatabase(): Promise { + const db = await initDB() + return db.export() +} + +export async function importDatabase(data: Uint8Array): Promise { + if (!sqlInstance) { + sqlInstance = await initSqlJs({ + locateFile: (file) => `https://sql.js.org/dist/${file}` + }) + } + + try { + dbInstance = new sqlInstance.Database(data) + await saveDB() + } catch (error) { + console.error('Failed to import database:', error) + throw error + } +} + +export async function getDatabaseStats(): Promise<{ + snippetCount: number + templateCount: number + storageType: 'indexeddb' | 'localstorage' | 'none' + databaseSize: number +}> { + const db = await initDB() + + const snippetResult = db.exec('SELECT COUNT(*) as count FROM snippets') + const templateResult = db.exec('SELECT COUNT(*) as count FROM snippet_templates') + + const snippetCount = snippetResult[0]?.values[0]?.[0] as number || 0 + const templateCount = templateResult[0]?.values[0]?.[0] as number || 0 + + const data = db.export() + const databaseSize = data.length + + const hasIDB = await openIndexedDB() + const storageType = hasIDB ? 'indexeddb' : (loadFromLocalStorage() ? 'localstorage' : 'none') + + return { + snippetCount, + templateCount, + storageType, + databaseSize + } +} + +export async function clearDatabase(): Promise { + const adapter = getFlaskAdapter() + if (adapter) { + await adapter.wipeDatabase() + return + } + + await deleteFromIndexedDB() + deleteFromLocalStorage() + + dbInstance = null + await initDB() +} diff --git a/src/lib/db-indexeddb.ts b/src/lib/db-indexeddb.ts new file mode 100644 index 0000000..5631db3 --- /dev/null +++ b/src/lib/db-indexeddb.ts @@ -0,0 +1,105 @@ +/** + * IndexedDB operations for database persistence + */ + +const DB_KEY = 'codesnippet-db' +const IDB_NAME = 'CodeSnippetDB' +const IDB_STORE = 'database' +const IDB_VERSION = 1 + +export async function openIndexedDB(): Promise { + if (typeof indexedDB === 'undefined') return null + + return new Promise((resolve) => { + try { + const request = indexedDB.open(IDB_NAME, IDB_VERSION) + + request.onerror = () => { + console.warn('IndexedDB not available, falling back to localStorage') + resolve(null) + } + + request.onupgradeneeded = (event) => { + const db = (event.target as IDBOpenDBRequest).result + if (!db.objectStoreNames.contains(IDB_STORE)) { + db.createObjectStore(IDB_STORE) + } + } + + request.onsuccess = (event) => { + resolve((event.target as IDBOpenDBRequest).result) + } + } catch (error) { + console.warn('IndexedDB error:', error) + resolve(null) + } + }) +} + +export async function loadFromIndexedDB(): Promise { + const db = await openIndexedDB() + if (!db) return null + + return new Promise((resolve) => { + try { + const transaction = db.transaction([IDB_STORE], 'readonly') + const store = transaction.objectStore(IDB_STORE) + const request = store.get(DB_KEY) + + request.onsuccess = () => { + const data = request.result + resolve(data ? new Uint8Array(data) : null) + } + + request.onerror = () => { + console.warn('Failed to load from IndexedDB') + resolve(null) + } + } catch (error) { + console.warn('IndexedDB read error:', error) + resolve(null) + } + }) +} + +export async function saveToIndexedDB(data: Uint8Array): Promise { + const db = await openIndexedDB() + if (!db) return false + + return new Promise((resolve) => { + try { + const transaction = db.transaction([IDB_STORE], 'readwrite') + const store = transaction.objectStore(IDB_STORE) + const request = store.put(data, DB_KEY) + + request.onsuccess = () => resolve(true) + request.onerror = () => { + console.warn('Failed to save to IndexedDB') + resolve(false) + } + } catch (error) { + console.warn('IndexedDB write error:', error) + resolve(false) + } + }) +} + +export async function deleteFromIndexedDB(): Promise { + const db = await openIndexedDB() + if (!db) return + + return new Promise((resolve) => { + try { + const transaction = db.transaction([IDB_STORE], 'readwrite') + const store = transaction.objectStore(IDB_STORE) + const request = store.delete(DB_KEY) + request.onsuccess = () => resolve() + request.onerror = () => resolve() + } catch (error) { + console.warn('Error clearing IndexedDB:', error) + resolve() + } + }) +} + +export { DB_KEY } diff --git a/src/lib/db-localstorage.ts b/src/lib/db-localstorage.ts new file mode 100644 index 0000000..d5d3b25 --- /dev/null +++ b/src/lib/db-localstorage.ts @@ -0,0 +1,36 @@ +/** + * LocalStorage operations for database persistence + */ + +import { DB_KEY } from './db-indexeddb' + +export function loadFromLocalStorage(): Uint8Array | null { + try { + const savedData = localStorage.getItem(DB_KEY) + if (savedData) { + return new Uint8Array(JSON.parse(savedData)) + } + } catch (error) { + console.warn('Failed to load from localStorage:', error) + } + return null +} + +export function saveToLocalStorage(data: Uint8Array): boolean { + try { + const dataArray = Array.from(data) + localStorage.setItem(DB_KEY, JSON.stringify(dataArray)) + return true + } catch (error) { + console.warn('Failed to save to localStorage (quota exceeded?):', error) + return false + } +} + +export function deleteFromLocalStorage(): void { + try { + localStorage.removeItem(DB_KEY) + } catch (error) { + console.warn('Error clearing localStorage:', error) + } +} diff --git a/src/lib/db-namespaces.ts b/src/lib/db-namespaces.ts new file mode 100644 index 0000000..292e220 --- /dev/null +++ b/src/lib/db-namespaces.ts @@ -0,0 +1,108 @@ +/** + * Namespace operations for organizing snippets + */ + +import type { Namespace } from './types' +import { initDB, saveDB, getFlaskAdapter } from './db-core' +import { mapRowToObject, mapRowsToObjects } from './db-mapper' + +export async function getAllNamespaces(): Promise { + const adapter = getFlaskAdapter() + if (adapter) { + return await adapter.getAllNamespaces() + } + + const db = await initDB() + const results = db.exec('SELECT * FROM namespaces ORDER BY isDefault DESC, name ASC') + + return mapRowsToObjects(results) +} + +export async function createNamespace(name: string): Promise { + const namespace: Namespace = { + id: Date.now().toString(), + name, + createdAt: Date.now(), + isDefault: false + } + + const adapter = getFlaskAdapter() + if (adapter) { + await adapter.createNamespace(namespace) + return namespace + } + + const db = await initDB() + + db.run( + `INSERT INTO namespaces (id, name, createdAt, isDefault) + VALUES (?, ?, ?, ?)`, + [namespace.id, namespace.name, namespace.createdAt, namespace.isDefault ? 1 : 0] + ) + + await saveDB() + return namespace +} + +export async function deleteNamespace(id: string): Promise { + const adapter = getFlaskAdapter() + if (adapter) { + return await adapter.deleteNamespace(id) + } + + const db = await initDB() + + const defaultNamespace = db.exec('SELECT id FROM namespaces WHERE isDefault = 1') + if (defaultNamespace.length === 0 || defaultNamespace[0].values.length === 0) { + throw new Error('Default namespace not found') + } + + const defaultId = defaultNamespace[0].values[0][0] as string + + const checkDefault = db.exec('SELECT isDefault FROM namespaces WHERE id = ?', [id]) + if (checkDefault.length > 0 && checkDefault[0].values[0]?.[0] === 1) { + throw new Error('Cannot delete default namespace') + } + + db.run('UPDATE snippets SET namespaceId = ? WHERE namespaceId = ?', [defaultId, id]) + + db.run('DELETE FROM namespaces WHERE id = ?', [id]) + + await saveDB() +} + +export async function ensureDefaultNamespace(): Promise { + const db = await initDB() + + const results = db.exec('SELECT COUNT(*) as count FROM namespaces WHERE isDefault = 1') + const count = results[0]?.values[0]?.[0] as number || 0 + + if (count === 0) { + const defaultNamespace: Namespace = { + id: 'default', + name: 'Default', + createdAt: Date.now(), + isDefault: true + } + + db.run( + `INSERT INTO namespaces (id, name, createdAt, isDefault) + VALUES (?, ?, ?, ?)`, + [defaultNamespace.id, defaultNamespace.name, defaultNamespace.createdAt, 1] + ) + + await saveDB() + } +} + +export async function getNamespaceById(id: string): Promise { + const db = await initDB() + const results = db.exec('SELECT * FROM namespaces WHERE id = ?', [id]) + + if (results.length === 0 || results[0].values.length === 0) return null + + const columns = results[0].columns + const row = results[0].values[0] + + return mapRowToObject(row, columns) +} diff --git a/src/lib/db-schema.ts b/src/lib/db-schema.ts new file mode 100644 index 0000000..835d8f4 --- /dev/null +++ b/src/lib/db-schema.ts @@ -0,0 +1,106 @@ +/** + * Database schema management and validation + */ + +import type { Database } from 'sql.js' + +export async function validateSchema(db: Database): Promise { + try { + const snippetsCheck = db.exec("PRAGMA table_info(snippets)") + if (snippetsCheck.length === 0) return true + + const columns = snippetsCheck[0].values.map(row => row[1] as string) + const requiredColumns = ['id', 'title', 'code', 'language', 'category', 'namespaceId', 'createdAt', 'updatedAt'] + + for (const col of requiredColumns) { + if (!columns.includes(col)) { + console.warn(`Schema validation failed: missing column '${col}'`) + return false + } + } + + const namespacesCheck = db.exec("PRAGMA table_info(namespaces)") + if (namespacesCheck.length === 0) { + console.warn('Schema validation failed: namespaces table missing') + return false + } + + return true + } catch (error) { + console.error('Schema validation error:', error) + return false + } +} + +export function createTables(db: Database): void { + db.run(` + CREATE TABLE IF NOT EXISTS namespaces ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + createdAt INTEGER NOT NULL, + isDefault INTEGER DEFAULT 0 + ) + `) + + db.run(` + CREATE TABLE IF NOT EXISTS snippets ( + id TEXT PRIMARY KEY, + title TEXT NOT NULL, + description TEXT, + code TEXT NOT NULL, + language TEXT NOT NULL, + category TEXT NOT NULL, + namespaceId TEXT, + hasPreview INTEGER DEFAULT 0, + functionName TEXT, + inputParameters TEXT, + createdAt INTEGER NOT NULL, + updatedAt INTEGER NOT NULL, + FOREIGN KEY (namespaceId) REFERENCES namespaces(id) + ) + `) + + db.run(` + CREATE TABLE IF NOT EXISTS snippet_templates ( + id TEXT PRIMARY KEY, + title TEXT NOT NULL, + description TEXT, + code TEXT NOT NULL, + language TEXT NOT NULL, + category TEXT NOT NULL, + hasPreview INTEGER DEFAULT 0, + functionName TEXT, + inputParameters TEXT + ) + `) +} + +export async function validateDatabaseSchema(db: Database): Promise<{ valid: boolean; issues: string[] }> { + try { + const issues: string[] = [] + + const snippetsCheck = db.exec("PRAGMA table_info(snippets)") + if (snippetsCheck.length === 0) { + issues.push('Snippets table missing') + return { valid: false, issues } + } + + const columns = snippetsCheck[0].values.map(row => row[1] as string) + const requiredColumns = ['id', 'title', 'code', 'language', 'category', 'namespaceId', 'createdAt', 'updatedAt'] + + for (const col of requiredColumns) { + if (!columns.includes(col)) { + issues.push(`Missing column '${col}' in snippets table`) + } + } + + const namespacesCheck = db.exec("SELECT name FROM sqlite_master WHERE type='table' AND name='namespaces'") + if (namespacesCheck.length === 0) { + issues.push('Namespaces table missing') + } + + return { valid: issues.length === 0, issues } + } catch (error) { + return { valid: false, issues: ['Failed to validate schema: ' + (error as Error).message] } + } +} diff --git a/src/lib/db-snippets.ts b/src/lib/db-snippets.ts new file mode 100644 index 0000000..c3707d7 --- /dev/null +++ b/src/lib/db-snippets.ts @@ -0,0 +1,585 @@ +/** + * Snippet CRUD operations and templates management + */ + +import type { Snippet, SnippetTemplate } from './types' +import { initDB, saveDB, getFlaskAdapter } from './db-core' +import { mapRowToObject, mapRowsToObjects } from './db-mapper' +import { ensureDefaultNamespace } from './db-namespaces' + +export async function getAllSnippets(): Promise { + const adapter = getFlaskAdapter() + if (adapter) { + return await adapter.getAllSnippets() + } + + const db = await initDB() + const results = db.exec('SELECT * FROM snippets ORDER BY updatedAt DESC') + + return mapRowsToObjects(results) +} + +export async function getSnippet(id: string): Promise { + const adapter = getFlaskAdapter() + if (adapter) { + return await adapter.getSnippet(id) + } + + const db = await initDB() + const results = db.exec('SELECT * FROM snippets WHERE id = ?', [id]) + + if (results.length === 0 || results[0].values.length === 0) return null + + const columns = results[0].columns + const row = results[0].values[0] + + return mapRowToObject(row, columns) +} + +export async function createSnippet(snippet: Snippet): Promise { + const adapter = getFlaskAdapter() + if (adapter) { + return await adapter.createSnippet(snippet) + } + + const db = await initDB() + + db.run( + `INSERT INTO snippets (id, title, description, code, language, category, namespaceId, hasPreview, functionName, inputParameters, createdAt, updatedAt) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + snippet.id, + snippet.title, + snippet.description, + snippet.code, + snippet.language, + snippet.category, + snippet.namespaceId || null, + snippet.hasPreview ? 1 : 0, + snippet.functionName || null, + snippet.inputParameters ? JSON.stringify(snippet.inputParameters) : null, + snippet.createdAt, + snippet.updatedAt + ] + ) + + await saveDB() +} + +export async function updateSnippet(snippet: Snippet): Promise { + const adapter = getFlaskAdapter() + if (adapter) { + return await adapter.updateSnippet(snippet) + } + + const db = await initDB() + + db.run( + `UPDATE snippets + SET title = ?, description = ?, code = ?, language = ?, category = ?, namespaceId = ?, hasPreview = ?, functionName = ?, inputParameters = ?, updatedAt = ? + WHERE id = ?`, + [ + snippet.title, + snippet.description, + snippet.code, + snippet.language, + snippet.category, + snippet.namespaceId || null, + snippet.hasPreview ? 1 : 0, + snippet.functionName || null, + snippet.inputParameters ? JSON.stringify(snippet.inputParameters) : null, + snippet.updatedAt, + snippet.id + ] + ) + + await saveDB() +} + +export async function deleteSnippet(id: string): Promise { + const adapter = getFlaskAdapter() + if (adapter) { + return await adapter.deleteSnippet(id) + } + + const db = await initDB() + + db.run('DELETE FROM snippets WHERE id = ?', [id]) + + await saveDB() +} + +export async function getSnippetsByNamespace(namespaceId: string): Promise { + const db = await initDB() + const results = db.exec('SELECT * FROM snippets WHERE namespaceId = ? OR (namespaceId IS NULL AND ? = (SELECT id FROM namespaces WHERE isDefault = 1)) ORDER BY updatedAt DESC', [namespaceId, namespaceId]) + + return mapRowsToObjects(results) +} + +export async function moveSnippetToNamespace(snippetId: string, targetNamespaceId: string): Promise { + const adapter = getFlaskAdapter() + if (adapter) { + const snippet = await adapter.getSnippet(snippetId) + if (snippet) { + snippet.namespaceId = targetNamespaceId + snippet.updatedAt = Date.now() + await adapter.updateSnippet(snippet) + } + return + } + + const db = await initDB() + + db.run( + 'UPDATE snippets SET namespaceId = ?, updatedAt = ? WHERE id = ?', + [targetNamespaceId, Date.now(), snippetId] + ) + + await saveDB() +} + +export async function bulkMoveSnippets(snippetIds: string[], targetNamespaceId: string): Promise { + const adapter = getFlaskAdapter() + if (adapter) { + await adapter.bulkMoveSnippets(snippetIds, targetNamespaceId) + return + } + + const db = await initDB() + const now = Date.now() + + for (const snippetId of snippetIds) { + db.run( + 'UPDATE snippets SET namespaceId = ?, updatedAt = ? WHERE id = ?', + [targetNamespaceId, now, snippetId] + ) + } + + await saveDB() +} + +export async function getAllTemplates(): Promise { + const db = await initDB() + const results = db.exec('SELECT * FROM snippet_templates') + + return mapRowsToObjects(results) +} + +export async function createTemplate(template: SnippetTemplate): Promise { + const db = await initDB() + + db.run( + `INSERT INTO snippet_templates (id, title, description, code, language, category, hasPreview, functionName, inputParameters) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + [ + template.id, + template.title, + template.description, + template.code, + template.language, + template.category, + template.hasPreview ? 1 : 0, + template.functionName || null, + template.inputParameters ? JSON.stringify(template.inputParameters) : null + ] + ) + + await saveDB() +} + +export async function syncTemplatesFromJSON(templates: SnippetTemplate[]): Promise { + const db = await initDB() + + const existingTemplates = db.exec('SELECT id FROM snippet_templates') + const existingIds = new Set( + existingTemplates[0]?.values.map(row => row[0] as string) || [] + ) + + let addedCount = 0 + for (const template of templates) { + if (!existingIds.has(template.id)) { + await createTemplate(template) + addedCount++ + } + } +} + +export async function seedDatabase(): Promise { + const db = await initDB() + + await ensureDefaultNamespace() + + const checkSnippets = db.exec('SELECT COUNT(*) as count FROM snippets') + const snippetCount = checkSnippets[0]?.values[0]?.[0] as number + + if (snippetCount > 0) { + return + } + + const now = Date.now() + + const seedSnippets: Snippet[] = [ + { + id: 'seed-1', + title: 'React Counter Hook', + description: 'Basic state management with useState', + code: `import { useState } from 'react' + +function Counter() { + const [count, setCount] = useState(0) + + return ( +
+

Count: {count}

+
+ + +
+
+ ) +} + +export default Counter`, + language: 'tsx', + category: 'React Hooks', + hasPreview: true, + functionName: 'Counter', + inputParameters: [], + createdAt: now, + updatedAt: now + }, + { + id: 'seed-2', + title: 'Todo List Component', + description: 'Complete todo list with add, toggle, and delete', + code: `import { useState } from 'react' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Card } from '@/components/ui/card' +import { Checkbox } from '@/components/ui/checkbox' +import { Trash2 } from '@phosphor-icons/react' + +function TodoList() { + const [todos, setTodos] = useState([ + { id: 1, text: 'Learn React', completed: false }, + { id: 2, text: 'Build a project', completed: false } + ]) + const [input, setInput] = useState('') + + const addTodo = () => { + if (input.trim()) { + setTodos([...todos, { id: Date.now(), text: input, completed: false }]) + setInput('') + } + } + + const toggleTodo = (id) => { + setTodos(todos.map(todo => + todo.id === id ? { ...todo, completed: !todo.completed } : todo + )) + } + + const deleteTodo = (id) => { + setTodos(todos.filter(todo => todo.id !== id)) + } + + return ( + +

My Todos

+
+ setInput(e.target.value)} + onKeyPress={(e) => e.key === 'Enter' && addTodo()} + placeholder="Add a new todo..." + /> + +
+
+ {todos.map(todo => ( +
+ toggleTodo(todo.id)} + /> + + {todo.text} + + +
+ ))} +
+
+ ) +} + +export default TodoList`, + language: 'tsx', + category: 'Components', + hasPreview: true, + functionName: 'TodoList', + inputParameters: [], + createdAt: now - 1000, + updatedAt: now - 1000 + }, + { + id: 'seed-3', + title: 'Fetch Data Hook', + description: 'Custom hook for API data fetching', + code: `import { useState, useEffect } from 'react' + +function useFetch(url) { + const [data, setData] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + + useEffect(() => { + const fetchData = async () => { + try { + setLoading(true) + const response = await fetch(url) + if (!response.ok) throw new Error('Network response was not ok') + const json = await response.json() + setData(json) + setError(null) + } catch (err) { + setError(err.message) + } finally { + setLoading(false) + } + } + + fetchData() + }, [url]) + + return { data, loading, error } +}`, + language: 'tsx', + category: 'React Hooks', + hasPreview: false, + createdAt: now - 2000, + updatedAt: now - 2000 + }, + { + id: 'seed-4', + title: 'Animated Card', + description: 'Card with hover animation using Framer Motion', + code: `import { motion } from 'framer-motion' +import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card' + +function AnimatedCard({ title = 'Animated Card', description = 'Hover to see the effect' }) { + return ( + + + + {title} + {description} + + +

This card has smooth animations on hover and tap!

+
+
+
+ ) +} + +export default AnimatedCard`, + language: 'tsx', + category: 'Components', + hasPreview: true, + functionName: 'AnimatedCard', + inputParameters: [ + { + name: 'title', + type: 'string', + defaultValue: 'Animated Card', + description: 'Card title' + }, + { + name: 'description', + type: 'string', + defaultValue: 'Hover to see the effect', + description: 'Card description' + } + ], + createdAt: now - 3000, + updatedAt: now - 3000 + }, + { + id: 'seed-5', + title: 'Form Validation', + description: 'Form with react-hook-form validation', + code: `import { useForm } from 'react-hook-form' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Card } from '@/components/ui/card' + +function ContactForm() { + const { register, handleSubmit, formState: { errors } } = useForm() + + const onSubmit = (data) => { + console.log('Form data:', data) + alert('Form submitted successfully!') + } + + return ( + +

Contact Form

+
+
+ + + {errors.name && ( +

{errors.name.message}

+ )} +
+ +
+ + + {errors.email && ( +

{errors.email.message}

+ )} +
+ + +
+
+ ) +} + +export default ContactForm`, + language: 'tsx', + category: 'Forms', + hasPreview: true, + functionName: 'ContactForm', + inputParameters: [], + createdAt: now - 4000, + updatedAt: now - 4000 + } + ] + + for (const snippet of seedSnippets) { + await createSnippet(snippet) + } + + const seedTemplates: SnippetTemplate[] = [ + { + id: 'template-1', + title: 'Basic React Component', + description: 'Simple functional component template', + code: `function MyComponent() { + return ( +
+

Hello World

+

This is a basic component.

+
+ ) +} + +export default MyComponent`, + language: 'tsx', + category: 'Templates', + hasPreview: true, + functionName: 'MyComponent', + inputParameters: [] + }, + { + id: 'template-2', + title: 'Component with Props', + description: 'Component template with configurable props', + code: `function Greeting({ name = 'World', message = 'Hello' }) { + return ( +
+

{message}, {name}!

+
+ ) +} + +export default Greeting`, + language: 'tsx', + category: 'Templates', + hasPreview: true, + functionName: 'Greeting', + inputParameters: [ + { + name: 'name', + type: 'string', + defaultValue: 'World', + description: 'Name to greet' + }, + { + name: 'message', + type: 'string', + defaultValue: 'Hello', + description: 'Greeting message' + } + ] + }, + { + id: 'template-3', + title: 'useState Hook Template', + description: 'Component with state management', + code: `import { useState } from 'react' +import { Button } from '@/components/ui/button' + +function StatefulComponent() { + const [value, setValue] = useState(0) + + return ( +
+

Value: {value}

+ +
+ ) +} + +export default StatefulComponent`, + language: 'tsx', + category: 'Templates', + hasPreview: true, + functionName: 'StatefulComponent', + inputParameters: [] + } + ] + + for (const template of seedTemplates) { + await createTemplate(template) + } +} diff --git a/src/lib/db.ts b/src/lib/db.ts index db4de79..7059bb4 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -1,1085 +1,37 @@ -import initSqlJs, { Database } from 'sql.js' -import type { Snippet, SnippetTemplate } from './types' -import { getStorageConfig, FlaskStorageAdapter, loadStorageConfig } from './storage' -import { mapRowToObject, mapRowsToObjects } from './db-mapper' - -let dbInstance: Database | null = null -let sqlInstance: any = null -let flaskAdapter: FlaskStorageAdapter | null = null -let configLoaded = false - -const DB_KEY = 'codesnippet-db' -const IDB_NAME = 'CodeSnippetDB' -const IDB_STORE = 'database' -const IDB_VERSION = 1 - -async function openIndexedDB(): Promise { - if (typeof indexedDB === 'undefined') return null - - return new Promise((resolve) => { - try { - const request = indexedDB.open(IDB_NAME, IDB_VERSION) - - request.onerror = () => { - console.warn('IndexedDB not available, falling back to localStorage') - resolve(null) - } - - request.onupgradeneeded = (event) => { - const db = (event.target as IDBOpenDBRequest).result - if (!db.objectStoreNames.contains(IDB_STORE)) { - db.createObjectStore(IDB_STORE) - } - } - - request.onsuccess = (event) => { - resolve((event.target as IDBOpenDBRequest).result) - } - } catch (error) { - console.warn('IndexedDB error:', error) - resolve(null) - } - }) -} - -async function loadFromIndexedDB(): Promise { - const db = await openIndexedDB() - if (!db) return null - - return new Promise((resolve) => { - try { - const transaction = db.transaction([IDB_STORE], 'readonly') - const store = transaction.objectStore(IDB_STORE) - const request = store.get(DB_KEY) - - request.onsuccess = () => { - const data = request.result - resolve(data ? new Uint8Array(data) : null) - } - - request.onerror = () => { - console.warn('Failed to load from IndexedDB') - resolve(null) - } - } catch (error) { - console.warn('IndexedDB read error:', error) - resolve(null) - } - }) -} - -async function saveToIndexedDB(data: Uint8Array): Promise { - const db = await openIndexedDB() - if (!db) return false - - return new Promise((resolve) => { - try { - const transaction = db.transaction([IDB_STORE], 'readwrite') - const store = transaction.objectStore(IDB_STORE) - const request = store.put(data, DB_KEY) - - request.onsuccess = () => resolve(true) - request.onerror = () => { - console.warn('Failed to save to IndexedDB') - resolve(false) - } - } catch (error) { - console.warn('IndexedDB write error:', error) - resolve(false) - } - }) -} - -function loadFromLocalStorage(): Uint8Array | null { - try { - const savedData = localStorage.getItem(DB_KEY) - if (savedData) { - return new Uint8Array(JSON.parse(savedData)) - } - } catch (error) { - console.warn('Failed to load from localStorage:', error) - } - return null -} - -function saveToLocalStorage(data: Uint8Array): boolean { - try { - const dataArray = Array.from(data) - localStorage.setItem(DB_KEY, JSON.stringify(dataArray)) - return true - } catch (error) { - console.warn('Failed to save to localStorage (quota exceeded?):', error) - return false - } -} - -async function validateSchema(db: Database): Promise { - try { - const snippetsCheck = db.exec("PRAGMA table_info(snippets)") - if (snippetsCheck.length === 0) return true - - const columns = snippetsCheck[0].values.map(row => row[1] as string) - const requiredColumns = ['id', 'title', 'code', 'language', 'category', 'namespaceId', 'createdAt', 'updatedAt'] - - for (const col of requiredColumns) { - if (!columns.includes(col)) { - console.warn(`Schema validation failed: missing column '${col}'`) - return false - } - } - - const namespacesCheck = db.exec("PRAGMA table_info(namespaces)") - if (namespacesCheck.length === 0) { - console.warn('Schema validation failed: namespaces table missing') - return false - } - - return true - } catch (error) { - console.error('Schema validation error:', error) - return false - } -} - -async function wipeAndRecreateDB(): Promise { - console.warn('Wiping corrupted database and creating fresh schema...') - - await saveToIndexedDB(new Uint8Array()) - saveToLocalStorage(new Uint8Array()) - - const idb = await openIndexedDB() - if (idb) { - try { - const transaction = idb.transaction([IDB_STORE], 'readwrite') - const store = transaction.objectStore(IDB_STORE) - await new Promise((resolve) => { - const request = store.delete(DB_KEY) - request.onsuccess = () => resolve() - request.onerror = () => resolve() - }) - } catch (error) { - console.warn('Error clearing IndexedDB:', error) - } - } - - try { - localStorage.removeItem(DB_KEY) - } catch (error) { - console.warn('Error clearing localStorage:', error) - } - - dbInstance = null -} - -export async function initDB(): Promise { - if (dbInstance) return dbInstance - - if (!sqlInstance) { - sqlInstance = await initSqlJs({ - locateFile: (file) => `https://sql.js.org/dist/${file}` - }) - } - - let loadedData: Uint8Array | null = null - let schemaValid = false - - loadedData = await loadFromIndexedDB() - - if (!loadedData) { - loadedData = loadFromLocalStorage() - } - - if (loadedData && loadedData.length > 0) { - try { - const testDb = new sqlInstance.Database(loadedData) - schemaValid = await validateSchema(testDb) - - if (schemaValid) { - dbInstance = testDb - } else { - console.warn('Schema validation failed, wiping database') - testDb.close() - await wipeAndRecreateDB() - dbInstance = new sqlInstance.Database() - } - } catch (error) { - console.error('Failed to load saved database, creating new one:', error) - await wipeAndRecreateDB() - dbInstance = new sqlInstance.Database() - } - } else { - dbInstance = new sqlInstance.Database() - } - - if (!dbInstance) { - throw new Error('Failed to initialize database') - } - - dbInstance.run(` - CREATE TABLE IF NOT EXISTS namespaces ( - id TEXT PRIMARY KEY, - name TEXT NOT NULL, - createdAt INTEGER NOT NULL, - isDefault INTEGER DEFAULT 0 - ) - `) - - dbInstance.run(` - CREATE TABLE IF NOT EXISTS snippets ( - id TEXT PRIMARY KEY, - title TEXT NOT NULL, - description TEXT, - code TEXT NOT NULL, - language TEXT NOT NULL, - category TEXT NOT NULL, - namespaceId TEXT, - hasPreview INTEGER DEFAULT 0, - functionName TEXT, - inputParameters TEXT, - createdAt INTEGER NOT NULL, - updatedAt INTEGER NOT NULL, - FOREIGN KEY (namespaceId) REFERENCES namespaces(id) - ) - `) - - dbInstance.run(` - CREATE TABLE IF NOT EXISTS snippet_templates ( - id TEXT PRIMARY KEY, - title TEXT NOT NULL, - description TEXT, - code TEXT NOT NULL, - language TEXT NOT NULL, - category TEXT NOT NULL, - hasPreview INTEGER DEFAULT 0, - functionName TEXT, - inputParameters TEXT - ) - `) - - await saveDB() - - return dbInstance -} - -export async function saveDB() { - if (!dbInstance) return - - try { - const data = dbInstance.export() - - const savedToIDB = await saveToIndexedDB(data) - - if (!savedToIDB) { - saveToLocalStorage(data) - } - } catch (error) { - console.error('Failed to save database:', error) - } -} - -function getFlaskAdapter(): FlaskStorageAdapter | null { - if (!configLoaded) { - loadStorageConfig() - configLoaded = true - } - - const config = getStorageConfig() - if (config.backend === 'flask' && config.flaskUrl) { - try { - if (!flaskAdapter || flaskAdapter['baseUrl'] !== config.flaskUrl) { - flaskAdapter = new FlaskStorageAdapter(config.flaskUrl) - } - return flaskAdapter - } catch (error) { - console.warn('Failed to create Flask adapter:', error) - return null - } - } - return null -} - -export async function getAllSnippets(): Promise { - const adapter = getFlaskAdapter() - if (adapter) { - return await adapter.getAllSnippets() - } - - const db = await initDB() - const results = db.exec('SELECT * FROM snippets ORDER BY updatedAt DESC') - - return mapRowsToObjects(results) -} - -export async function getSnippet(id: string): Promise { - const adapter = getFlaskAdapter() - if (adapter) { - return await adapter.getSnippet(id) - } - - const db = await initDB() - const results = db.exec('SELECT * FROM snippets WHERE id = ?', [id]) - - if (results.length === 0 || results[0].values.length === 0) return null - - const columns = results[0].columns - const row = results[0].values[0] - - return mapRowToObject(row, columns) -} - -export async function createSnippet(snippet: Snippet): Promise { - const adapter = getFlaskAdapter() - if (adapter) { - return await adapter.createSnippet(snippet) - } - - const db = await initDB() - - db.run( - `INSERT INTO snippets (id, title, description, code, language, category, namespaceId, hasPreview, functionName, inputParameters, createdAt, updatedAt) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - [ - snippet.id, - snippet.title, - snippet.description, - snippet.code, - snippet.language, - snippet.category, - snippet.namespaceId || null, - snippet.hasPreview ? 1 : 0, - snippet.functionName || null, - snippet.inputParameters ? JSON.stringify(snippet.inputParameters) : null, - snippet.createdAt, - snippet.updatedAt - ] - ) - - await saveDB() -} - -export async function updateSnippet(snippet: Snippet): Promise { - const adapter = getFlaskAdapter() - if (adapter) { - return await adapter.updateSnippet(snippet) - } - - const db = await initDB() - - db.run( - `UPDATE snippets - SET title = ?, description = ?, code = ?, language = ?, category = ?, namespaceId = ?, hasPreview = ?, functionName = ?, inputParameters = ?, updatedAt = ? - WHERE id = ?`, - [ - snippet.title, - snippet.description, - snippet.code, - snippet.language, - snippet.category, - snippet.namespaceId || null, - snippet.hasPreview ? 1 : 0, - snippet.functionName || null, - snippet.inputParameters ? JSON.stringify(snippet.inputParameters) : null, - snippet.updatedAt, - snippet.id - ] - ) - - await saveDB() -} - -export async function deleteSnippet(id: string): Promise { - const adapter = getFlaskAdapter() - if (adapter) { - return await adapter.deleteSnippet(id) - } - - const db = await initDB() - - db.run('DELETE FROM snippets WHERE id = ?', [id]) - - await saveDB() -} - -export async function getAllTemplates(): Promise { - const db = await initDB() - const results = db.exec('SELECT * FROM snippet_templates') - - return mapRowsToObjects(results) -} - -export async function createTemplate(template: SnippetTemplate): Promise { - const db = await initDB() - - db.run( - `INSERT INTO snippet_templates (id, title, description, code, language, category, hasPreview, functionName, inputParameters) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, - [ - template.id, - template.title, - template.description, - template.code, - template.language, - template.category, - template.hasPreview ? 1 : 0, - template.functionName || null, - template.inputParameters ? JSON.stringify(template.inputParameters) : null - ] - ) - - await saveDB() -} - -export async function seedDatabase(): Promise { - const db = await initDB() - - await ensureDefaultNamespace() - - const checkSnippets = db.exec('SELECT COUNT(*) as count FROM snippets') - const snippetCount = checkSnippets[0]?.values[0]?.[0] as number - - if (snippetCount > 0) { - return - } - - const now = Date.now() - - const seedSnippets: Snippet[] = [ - { - id: 'seed-1', - title: 'React Counter Hook', - description: 'Basic state management with useState', - code: `import { useState } from 'react' - -function Counter() { - const [count, setCount] = useState(0) - - return ( -
-

Count: {count}

-
- - -
-
- ) -} - -export default Counter`, - language: 'tsx', - category: 'React Hooks', - hasPreview: true, - functionName: 'Counter', - inputParameters: [], - createdAt: now, - updatedAt: now - }, - { - id: 'seed-2', - title: 'Todo List Component', - description: 'Complete todo list with add, toggle, and delete', - code: `import { useState } from 'react' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Card } from '@/components/ui/card' -import { Checkbox } from '@/components/ui/checkbox' -import { Trash2 } from '@phosphor-icons/react' - -function TodoList() { - const [todos, setTodos] = useState([ - { id: 1, text: 'Learn React', completed: false }, - { id: 2, text: 'Build a project', completed: false } - ]) - const [input, setInput] = useState('') - - const addTodo = () => { - if (input.trim()) { - setTodos([...todos, { id: Date.now(), text: input, completed: false }]) - setInput('') - } - } - - const toggleTodo = (id) => { - setTodos(todos.map(todo => - todo.id === id ? { ...todo, completed: !todo.completed } : todo - )) - } - - const deleteTodo = (id) => { - setTodos(todos.filter(todo => todo.id !== id)) - } - - return ( - -

My Todos

-
- setInput(e.target.value)} - onKeyPress={(e) => e.key === 'Enter' && addTodo()} - placeholder="Add a new todo..." - /> - -
-
- {todos.map(todo => ( -
- toggleTodo(todo.id)} - /> - - {todo.text} - - -
- ))} -
-
- ) -} - -export default TodoList`, - language: 'tsx', - category: 'Components', - hasPreview: true, - functionName: 'TodoList', - inputParameters: [], - createdAt: now - 1000, - updatedAt: now - 1000 - }, - { - id: 'seed-3', - title: 'Fetch Data Hook', - description: 'Custom hook for API data fetching', - code: `import { useState, useEffect } from 'react' - -function useFetch(url) { - const [data, setData] = useState(null) - const [loading, setLoading] = useState(true) - const [error, setError] = useState(null) - - useEffect(() => { - const fetchData = async () => { - try { - setLoading(true) - const response = await fetch(url) - if (!response.ok) throw new Error('Network response was not ok') - const json = await response.json() - setData(json) - setError(null) - } catch (err) { - setError(err.message) - } finally { - setLoading(false) - } - } - - fetchData() - }, [url]) - - return { data, loading, error } -}`, - language: 'tsx', - category: 'React Hooks', - hasPreview: false, - createdAt: now - 2000, - updatedAt: now - 2000 - }, - { - id: 'seed-4', - title: 'Animated Card', - description: 'Card with hover animation using Framer Motion', - code: `import { motion } from 'framer-motion' -import { Card, CardHeader, CardTitle, CardDescription, CardContent } from '@/components/ui/card' - -function AnimatedCard({ title = 'Animated Card', description = 'Hover to see the effect' }) { - return ( - - - - {title} - {description} - - -

This card has smooth animations on hover and tap!

-
-
-
- ) -} - -export default AnimatedCard`, - language: 'tsx', - category: 'Components', - hasPreview: true, - functionName: 'AnimatedCard', - inputParameters: [ - { - name: 'title', - type: 'string', - defaultValue: 'Animated Card', - description: 'Card title' - }, - { - name: 'description', - type: 'string', - defaultValue: 'Hover to see the effect', - description: 'Card description' - } - ], - createdAt: now - 3000, - updatedAt: now - 3000 - }, - { - id: 'seed-5', - title: 'Form Validation', - description: 'Form with react-hook-form validation', - code: `import { useForm } from 'react-hook-form' -import { Button } from '@/components/ui/button' -import { Input } from '@/components/ui/input' -import { Label } from '@/components/ui/label' -import { Card } from '@/components/ui/card' - -function ContactForm() { - const { register, handleSubmit, formState: { errors } } = useForm() - - const onSubmit = (data) => { - console.log('Form data:', data) - alert('Form submitted successfully!') - } - - return ( - -

Contact Form

-
-
- - - {errors.name && ( -

{errors.name.message}

- )} -
- -
- - - {errors.email && ( -

{errors.email.message}

- )} -
- - -
-
- ) -} - -export default ContactForm`, - language: 'tsx', - category: 'Forms', - hasPreview: true, - functionName: 'ContactForm', - inputParameters: [], - createdAt: now - 4000, - updatedAt: now - 4000 - } - ] - - for (const snippet of seedSnippets) { - await createSnippet(snippet) - } - - const seedTemplates: SnippetTemplate[] = [ - { - id: 'template-1', - title: 'Basic React Component', - description: 'Simple functional component template', - code: `function MyComponent() { - return ( -
-

Hello World

-

This is a basic component.

-
- ) -} - -export default MyComponent`, - language: 'tsx', - category: 'Templates', - hasPreview: true, - functionName: 'MyComponent', - inputParameters: [] - }, - { - id: 'template-2', - title: 'Component with Props', - description: 'Component template with configurable props', - code: `function Greeting({ name = 'World', message = 'Hello' }) { - return ( -
-

{message}, {name}!

-
- ) -} - -export default Greeting`, - language: 'tsx', - category: 'Templates', - hasPreview: true, - functionName: 'Greeting', - inputParameters: [ - { - name: 'name', - type: 'string', - defaultValue: 'World', - description: 'Name to greet' - }, - { - name: 'message', - type: 'string', - defaultValue: 'Hello', - description: 'Greeting message' - } - ] - }, - { - id: 'template-3', - title: 'useState Hook Template', - description: 'Component with state management', - code: `import { useState } from 'react' -import { Button } from '@/components/ui/button' - -function StatefulComponent() { - const [value, setValue] = useState(0) - - return ( -
-

Value: {value}

- -
- ) -} - -export default StatefulComponent`, - language: 'tsx', - category: 'Templates', - hasPreview: true, - functionName: 'StatefulComponent', - inputParameters: [] - } - ] - - for (const template of seedTemplates) { - await createTemplate(template) - } -} - -export async function exportDatabase(): Promise { - const db = await initDB() - return db.export() -} - -export async function importDatabase(data: Uint8Array): Promise { - if (!sqlInstance) { - sqlInstance = await initSqlJs({ - locateFile: (file) => `https://sql.js.org/dist/${file}` - }) - } - - try { - dbInstance = new sqlInstance.Database(data) - await saveDB() - } catch (error) { - console.error('Failed to import database:', error) - throw error - } -} - -export async function getDatabaseStats(): Promise<{ - snippetCount: number - templateCount: number - storageType: 'indexeddb' | 'localstorage' | 'none' - databaseSize: number -}> { - const db = await initDB() - - const snippetResult = db.exec('SELECT COUNT(*) as count FROM snippets') - const templateResult = db.exec('SELECT COUNT(*) as count FROM snippet_templates') - - const snippetCount = snippetResult[0]?.values[0]?.[0] as number || 0 - const templateCount = templateResult[0]?.values[0]?.[0] as number || 0 - - const data = db.export() - const databaseSize = data.length - - const hasIDB = await openIndexedDB() - const storageType = hasIDB ? 'indexeddb' : (localStorage.getItem(DB_KEY) ? 'localstorage' : 'none') - - return { - snippetCount, - templateCount, - storageType, - databaseSize - } -} - -export async function clearDatabase(): Promise { - const adapter = getFlaskAdapter() - if (adapter) { - await adapter.wipeDatabase() - return - } - - const db = await openIndexedDB() - if (db) { - try { - const transaction = db.transaction([IDB_STORE], 'readwrite') - const store = transaction.objectStore(IDB_STORE) - store.delete(DB_KEY) - } catch (error) { - console.warn('Failed to clear IndexedDB:', error) - } - } - - try { - localStorage.removeItem(DB_KEY) - } catch (error) { - console.warn('Failed to clear localStorage:', error) - } - - dbInstance = null - await initDB() -} - -export async function syncTemplatesFromJSON(templates: SnippetTemplate[]): Promise { - const db = await initDB() - - const existingTemplates = db.exec('SELECT id FROM snippet_templates') - const existingIds = new Set( - existingTemplates[0]?.values.map(row => row[0] as string) || [] - ) - - let addedCount = 0 - for (const template of templates) { - if (!existingIds.has(template.id)) { - await createTemplate(template) - addedCount++ - } - } -} - -export async function getAllNamespaces(): Promise { - const adapter = getFlaskAdapter() - if (adapter) { - return await adapter.getAllNamespaces() - } - - const db = await initDB() - const results = db.exec('SELECT * FROM namespaces ORDER BY isDefault DESC, name ASC') - - return mapRowsToObjects(results) -} - -export async function createNamespace(name: string): Promise { - const namespace: import('./types').Namespace = { - id: Date.now().toString(), - name, - createdAt: Date.now(), - isDefault: false - } - - const adapter = getFlaskAdapter() - if (adapter) { - await adapter.createNamespace(namespace) - return namespace - } - - const db = await initDB() - - db.run( - `INSERT INTO namespaces (id, name, createdAt, isDefault) - VALUES (?, ?, ?, ?)`, - [namespace.id, namespace.name, namespace.createdAt, namespace.isDefault ? 1 : 0] - ) - - await saveDB() - return namespace -} - -export async function deleteNamespace(id: string): Promise { - const adapter = getFlaskAdapter() - if (adapter) { - return await adapter.deleteNamespace(id) - } - - const db = await initDB() - - const defaultNamespace = db.exec('SELECT id FROM namespaces WHERE isDefault = 1') - if (defaultNamespace.length === 0 || defaultNamespace[0].values.length === 0) { - throw new Error('Default namespace not found') - } - - const defaultId = defaultNamespace[0].values[0][0] as string - - const checkDefault = db.exec('SELECT isDefault FROM namespaces WHERE id = ?', [id]) - if (checkDefault.length > 0 && checkDefault[0].values[0]?.[0] === 1) { - throw new Error('Cannot delete default namespace') - } - - db.run('UPDATE snippets SET namespaceId = ? WHERE namespaceId = ?', [defaultId, id]) - - db.run('DELETE FROM namespaces WHERE id = ?', [id]) - - await saveDB() -} - -export async function ensureDefaultNamespace(): Promise { - const db = await initDB() - - const results = db.exec('SELECT COUNT(*) as count FROM namespaces WHERE isDefault = 1') - const count = results[0]?.values[0]?.[0] as number || 0 - - if (count === 0) { - const defaultNamespace: import('./types').Namespace = { - id: 'default', - name: 'Default', - createdAt: Date.now(), - isDefault: true - } - - db.run( - `INSERT INTO namespaces (id, name, createdAt, isDefault) - VALUES (?, ?, ?, ?)`, - [defaultNamespace.id, defaultNamespace.name, defaultNamespace.createdAt, 1] - ) - - await saveDB() - } -} - -export async function getSnippetsByNamespace(namespaceId: string): Promise { - const db = await initDB() - const results = db.exec('SELECT * FROM snippets WHERE namespaceId = ? OR (namespaceId IS NULL AND ? = (SELECT id FROM namespaces WHERE isDefault = 1)) ORDER BY updatedAt DESC', [namespaceId, namespaceId]) - - return mapRowsToObjects(results) -} - -export async function getNamespaceById(id: string): Promise { - const db = await initDB() - const results = db.exec('SELECT * FROM namespaces WHERE id = ?', [id]) - - if (results.length === 0 || results[0].values.length === 0) return null - - const columns = results[0].columns - const row = results[0].values[0] - - return mapRowToObject(row, columns) -} - -export async function moveSnippetToNamespace(snippetId: string, targetNamespaceId: string): Promise { - const adapter = getFlaskAdapter() - if (adapter) { - const snippet = await adapter.getSnippet(snippetId) - if (snippet) { - snippet.namespaceId = targetNamespaceId - snippet.updatedAt = Date.now() - await adapter.updateSnippet(snippet) - } - return - } - - const db = await initDB() - - db.run( - 'UPDATE snippets SET namespaceId = ?, updatedAt = ? WHERE id = ?', - [targetNamespaceId, Date.now(), snippetId] - ) - - await saveDB() -} - -export async function bulkMoveSnippets(snippetIds: string[], targetNamespaceId: string): Promise { - const adapter = getFlaskAdapter() - if (adapter) { - await adapter.bulkMoveSnippets(snippetIds, targetNamespaceId) - return - } - - const db = await initDB() - const now = Date.now() - - for (const snippetId of snippetIds) { - db.run( - 'UPDATE snippets SET namespaceId = ?, updatedAt = ? WHERE id = ?', - [targetNamespaceId, now, snippetId] - ) - } - - await saveDB() -} - -export async function validateDatabaseSchema(): Promise<{ valid: boolean; issues: string[] }> { - try { - const db = await initDB() - const issues: string[] = [] - - const snippetsCheck = db.exec("PRAGMA table_info(snippets)") - if (snippetsCheck.length === 0) { - issues.push('Snippets table missing') - return { valid: false, issues } - } - - const columns = snippetsCheck[0].values.map(row => row[1] as string) - const requiredColumns = ['id', 'title', 'code', 'language', 'category', 'namespaceId', 'createdAt', 'updatedAt'] - - for (const col of requiredColumns) { - if (!columns.includes(col)) { - issues.push(`Missing column '${col}' in snippets table`) - } - } - - const namespacesCheck = db.exec("SELECT name FROM sqlite_master WHERE type='table' AND name='namespaces'") - if (namespacesCheck.length === 0) { - issues.push('Namespaces table missing') - } - - return { valid: issues.length === 0, issues } - } catch (error) { - return { valid: false, issues: ['Failed to validate schema: ' + (error as Error).message] } - } -} +/** + * Main database module - Re-exports from focused modules + * This file maintains backward compatibility while delegating to specialized modules + */ + +// Re-export core database functions +export { initDB, saveDB, exportDatabase, importDatabase, getDatabaseStats, clearDatabase } from './db-core' + +// Re-export snippet operations +export { + getAllSnippets, + getSnippet, + createSnippet, + updateSnippet, + deleteSnippet, + getSnippetsByNamespace, + moveSnippetToNamespace, + bulkMoveSnippets, + getAllTemplates, + createTemplate, + syncTemplatesFromJSON, + seedDatabase +} from './db-snippets' + +// Re-export namespace operations +export { + getAllNamespaces, + createNamespace, + deleteNamespace, + ensureDefaultNamespace, + getNamespaceById +} from './db-namespaces' + +// Re-export schema validation +export { validateDatabaseSchema } from './db-schema' + +// Note: saveDB is intentionally not exported as it's used internally by the modules From 35e69c36c321f351085514eaefc60aea12b9ae9b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 23:24:10 +0000 Subject: [PATCH 3/4] Refactor component-code-snippets.ts (1006 LOC) into category modules Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com> --- src/lib/component-code-snippets.ts | 1013 +--------------------------- src/lib/snippets/atoms.ts | 209 ++++++ src/lib/snippets/index.ts | 8 + src/lib/snippets/molecules.ts | 219 ++++++ src/lib/snippets/organisms.ts | 338 ++++++++++ src/lib/snippets/templates.ts | 256 +++++++ 6 files changed, 1038 insertions(+), 1005 deletions(-) create mode 100644 src/lib/snippets/atoms.ts create mode 100644 src/lib/snippets/index.ts create mode 100644 src/lib/snippets/molecules.ts create mode 100644 src/lib/snippets/organisms.ts create mode 100644 src/lib/snippets/templates.ts diff --git a/src/lib/component-code-snippets.ts b/src/lib/component-code-snippets.ts index 4fcb03b..b7bccd5 100644 --- a/src/lib/component-code-snippets.ts +++ b/src/lib/component-code-snippets.ts @@ -1,1006 +1,9 @@ -export const atomsCodeSnippets = { - buttonDefault: `interface ButtonProps { - children?: string - onClick?: () => void -} +/** + * Component code snippets - Main re-export module + * This file maintains backward compatibility while delegating to specialized modules + */ -function DefaultButton({ children = "Default", onClick }: ButtonProps) { - return -}`, - buttonSecondary: `interface ButtonProps { - children?: string - variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" | "link" - onClick?: () => void -} - -function CustomButton({ children = "Secondary", variant = "secondary", onClick }: ButtonProps) { - return -}`, - buttonDestructive: `interface DestructiveButtonProps { - children?: string - onClick?: () => void -} - -function DestructiveButton({ children = "Destructive", onClick }: DestructiveButtonProps) { - return -}`, - buttonOutline: `interface OutlineButtonProps { - children?: string - onClick?: () => void -} - -function OutlineButton({ children = "Outline", onClick }: OutlineButtonProps) { - return -}`, - buttonGhost: `interface GhostButtonProps { - children?: string - onClick?: () => void -} - -function GhostButton({ children = "Ghost", onClick }: GhostButtonProps) { - return -}`, - buttonLink: `interface LinkButtonProps { - children?: string - onClick?: () => void -} - -function LinkButton({ children = "Link", onClick }: LinkButtonProps) { - return -}`, - buttonSizes: `interface ButtonSizesProps { - smallText?: string - defaultText?: string - largeText?: string - onSmallClick?: () => void - onDefaultClick?: () => void - onLargeClick?: () => void - onIconClick?: () => void -} - -function ButtonSizes({ - smallText = "Small", - defaultText = "Default", - largeText = "Large", - onSmallClick, - onDefaultClick, - onLargeClick, - onIconClick -}: ButtonSizesProps) { - return ( - <> - - - - - - ) -}`, - buttonWithIcons: `interface IconButtonProps { - primaryText?: string - secondaryText?: string - onPrimaryClick?: () => void - onSecondaryClick?: () => void -} - -function IconButtons({ - primaryText = "Favorite", - secondaryText = "Add Item", - onPrimaryClick, - onSecondaryClick -}: IconButtonProps) { - return ( - <> - - - - ) -}`, - badgeVariants: `interface BadgeVariantsProps { - defaultText?: string - secondaryText?: string - destructiveText?: string - outlineText?: string -} - -function BadgeVariants({ - defaultText = "Default", - secondaryText = "Secondary", - destructiveText = "Destructive", - outlineText = "Outline" -}: BadgeVariantsProps) { - return ( - <> - {defaultText} - {secondaryText} - {destructiveText} - {outlineText} - - ) -}`, - badgeWithIcons: `interface IconBadgeProps { - completedText?: string - failedText?: string -} - -function IconBadges({ - completedText = "Completed", - failedText = "Failed" -}: IconBadgeProps) { - return ( - <> - - - {completedText} - - - - {failedText} - - - ) -}`, - inputBasic: `interface InputProps { - placeholder?: string - value?: string - onChange?: (e: React.ChangeEvent) => void -} - -function BasicInput({ placeholder = "Default input", value, onChange }: InputProps) { - return -}`, - inputWithIcon: `interface SearchInputProps { - placeholder?: string - value?: string - onChange?: (e: React.ChangeEvent) => void -} - -function SearchInput({ placeholder = "Search...", value, onChange }: SearchInputProps) { - return ( -
- - -
- ) -}`, - inputTypes: `interface TypedInputsProps { - textPlaceholder?: string - emailPlaceholder?: string - passwordPlaceholder?: string - numberPlaceholder?: string - onTextChange?: (e: React.ChangeEvent) => void - onEmailChange?: (e: React.ChangeEvent) => void - onPasswordChange?: (e: React.ChangeEvent) => void - onNumberChange?: (e: React.ChangeEvent) => void -} - -function TypedInputs({ - textPlaceholder = "Text input", - emailPlaceholder = "email@example.com", - passwordPlaceholder = "Password", - numberPlaceholder = "123", - onTextChange, - onEmailChange, - onPasswordChange, - onNumberChange -}: TypedInputsProps) { - return ( - <> - - - - - - ) -}`, -} - -export const moleculesCodeSnippets = { - formField: `interface FormFieldProps { - label?: string - placeholder?: string - helperText?: string - id?: string - value?: string - onChange?: (e: React.ChangeEvent) => void -} - -function EmailFormField({ - label = "Email Address", - placeholder = "john@example.com", - helperText = "We'll never share your email with anyone else.", - id = "email", - value, - onChange -}: FormFieldProps) { - return ( -
- -
- - -
-

- {helperText} -

-
- ) -}`, - searchBar: `interface SearchBarProps { - placeholder?: string - value?: string - onChange?: (e: React.ChangeEvent) => void -} - -function SearchBar({ placeholder = "Search...", value, onChange }: SearchBarProps) { - return ( -
- - -
- ) -}`, - searchBarWithButton: `interface SearchBarWithButtonProps { - placeholder?: string - buttonText?: string - value?: string - onChange?: (e: React.ChangeEvent) => void - onSearch?: () => void -} - -function SearchBarWithButton({ - placeholder = "Search...", - buttonText = "Search", - value, - onChange, - onSearch -}: SearchBarWithButtonProps) { - return ( -
-
- - -
- -
- ) -}`, - userCard: `interface UserCardProps { - name?: string - username?: string - bio?: string - avatarUrl?: string - avatarFallback?: string - buttonText?: string - onButtonClick?: () => void -} - -function UserCard({ - name = "Alex Morgan", - username = "@alexmorgan", - bio = "Product designer passionate about creating delightful user experiences.", - avatarUrl = "https://i.pravatar.cc/150?img=1", - avatarFallback = "AM", - buttonText = "Follow", - onButtonClick -}: UserCardProps) { - return ( - -
- - - {avatarFallback} - -
-

{name}

-

{username}

-

- {bio} -

-
- -
-
- ) -}`, - socialActions: `interface SocialActionsProps { - likeText?: string - commentText?: string - shareText?: string - onLike?: () => void - onComment?: () => void - onShare?: () => void -} - -function SocialActions({ - likeText = "Like", - commentText = "Comment", - shareText = "Share", - onLike, - onComment, - onShare -}: SocialActionsProps) { - return ( -
- - - -
- ) -}`, - statusIndicator: `interface StatusIndicatorProps { - statusText?: string - badgeText?: string - isActive?: boolean -} - -function StatusIndicator({ - statusText = "System Online", - badgeText = "Active", - isActive = true -}: StatusIndicatorProps) { - return ( -
-
-
- {statusText} -
- {badgeText} -
- ) -}`, - contentCard: `interface ContentCardProps { - title?: string - description?: string - date?: string - readTime?: string - tags?: string[] -} - -function ContentCard({ - title = "Building Scalable Design Systems", - description = "Learn how to create and maintain design systems that grow with your team.", - date = "Mar 15, 2024", - readTime = "5 min read", - tags = ["Design", "System"] -}: ContentCardProps) { - return ( - -
-
-
-

- {title} -

-

- {description} -

-
-
-
-
- - {date} -
- - {readTime} -
-
- {tags.map((tag) => ( - {tag} - ))} -
-
-
- ) -}`, -} - -export const organismsCodeSnippets = { - navigationBar: `interface NavigationBarProps { - brandName?: string - navItems?: Array<{ label: string; icon: React.ReactNode; onClick?: () => void }> - avatarUrl?: string - avatarFallback?: string - onNotificationClick?: () => void - onSettingsClick?: () => void -} - -function NavigationBar({ - brandName = "BrandName", - navItems = [ - { label: "Home", icon: }, - { label: "Analytics", icon: }, - { label: "Projects", icon: } - ], - avatarUrl = "https://i.pravatar.cc/150?img=3", - avatarFallback = "U", - onNotificationClick, - onSettingsClick -}: NavigationBarProps) { - return ( -
-
-
-

{brandName}

- -
-
- - - - - {avatarFallback} - -
-
-
- ) -}`, - dataTable: `interface Transaction { - id: string - status: string - statusVariant?: "default" | "secondary" | "destructive" | "outline" - description: string - date: string - amount: string - isNegative?: boolean -} - -interface DataTableProps { - title?: string - exportButtonText?: string - transactions?: Transaction[] - onExport?: () => void -} - -function DataTable({ - title = "Recent Transactions", - exportButtonText = "Export", - transactions = [ - { id: "1", status: "Completed", description: "Payment received", date: "Mar 15, 2024", amount: "$250.00" } - ], - onExport -}: DataTableProps) { - return ( - -
-
-

{title}

- -
-
- - - - Status - Transaction - Date - Amount - - - - {transactions.map((transaction) => ( - - - {transaction.status} - - {transaction.description} - {transaction.date} - {transaction.amount} - - ))} - -
-
- ) -}`, - form: `interface CreateAccountFormProps { - title?: string - description?: string - firstNameLabel?: string - lastNameLabel?: string - emailLabel?: string - firstNamePlaceholder?: string - lastNamePlaceholder?: string - emailPlaceholder?: string - cancelButtonText?: string - submitButtonText?: string - onCancel?: () => void - onSubmit?: (data: { firstName: string; lastName: string; email: string }) => void -} - -function CreateAccountForm({ - title = "Create Account", - description = "Fill in your details to get started", - firstNameLabel = "First Name", - lastNameLabel = "Last Name", - emailLabel = "Email", - firstNamePlaceholder = "John", - lastNamePlaceholder = "Doe", - emailPlaceholder = "john@example.com", - cancelButtonText = "Cancel", - submitButtonText = "Create Account", - onCancel, - onSubmit -}: CreateAccountFormProps) { - const [firstName, setFirstName] = useState("") - const [lastName, setLastName] = useState("") - const [email, setEmail] = useState("") - - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault() - onSubmit?.({ firstName, lastName, email }) - } - - return ( - -
-
-

{title}

-

- {description} -

-
- -
-
- - setFirstName(e.target.value)} - /> -
-
- - setLastName(e.target.value)} - /> -
-
-
- -
- - setEmail(e.target.value)} - /> -
-
- -
- - -
- -
- ) -}`, - taskList: `interface Task { - id: string - title: string - description: string - status: string - statusColor?: "accent" | "destructive" - badgeText: string - badgeVariant?: "default" | "secondary" | "destructive" | "outline" - icon: React.ReactNode -} - -interface TaskListProps { - title?: string - addButtonText?: string - tasks?: Task[] - onAddTask?: () => void -} - -function TaskList({ - title = "Project Tasks", - addButtonText = "Add Task", - tasks = [ - { - id: "1", - title: "Design system documentation", - description: "Complete the component library documentation", - status: "Completed", - statusColor: "accent" as const, - badgeText: "Design", - badgeVariant: "secondary" as const, - icon: - } - ], - onAddTask -}: TaskListProps) { - return ( - -
-
-

{title}

- -
-
-
- {tasks.map((task) => ( -
-
- {task.icon} -
-

{task.title}

-

- {task.description} -

-
- {task.badgeText} - {task.status} -
-
-
-
- ))} -
-
- ) -}`, - sidebarNavigation: `interface SidebarNavItem { - label: string - icon: React.ReactNode - variant?: "default" | "ghost" - onClick?: () => void -} - -interface SidebarNavigationProps { - title?: string - navItems?: SidebarNavItem[] - contentText?: string -} - -function SidebarNavigation({ - title = "Dashboard", - navItems = [ - { label: "Home", icon: , variant: "ghost" as const }, - { label: "Analytics", icon: , variant: "default" as const }, - { label: "Projects", icon: , variant: "ghost" as const } - ], - contentText = "Main content area" -}: SidebarNavigationProps) { - return ( -
- -
-

- {contentText} -

-
-
- ) -}`, -} - -export const templatesCodeSnippets = { - dashboardLayout: `interface StatCard { - label: string - value: string - trend?: string -} - -interface DashboardLayoutProps { - title?: string - navItems?: Array<{ label: string; icon: React.ReactNode; variant?: "default" | "ghost" }> - avatarUrl?: string - avatarFallback?: string - stats?: StatCard[] - onNotificationClick?: () => void -} - -function DashboardLayout({ - title = "Dashboard", - navItems = [ - { label: "Overview", icon: , variant: "default" as const }, - { label: "Analytics", icon: , variant: "ghost" as const } - ], - avatarUrl, - avatarFallback = "U", - stats = [ - { label: "Total Revenue", value: "$45,231", trend: "+20.1% from last month" } - ], - onNotificationClick -}: DashboardLayoutProps) { - return ( -
-
-
-

{title}

-
- - - {avatarUrl && } - {avatarFallback} - -
-
-
-
- -
-

Overview

-
- {stats.map((stat) => ( - -

{stat.label}

-

{stat.value}

- {stat.trend && ( -

{stat.trend}

- )} -
- ))} -
-
-
-
- ) -}`, - landingPage: `interface LandingPageProps { - brandName?: string - navItems?: string[] - badge?: string - headline?: string - description?: string - primaryCta?: string - secondaryCta?: string - onPrimaryCta?: () => void - onSecondaryCta?: () => void -} - -function LandingPage({ - brandName = "ProductName", - navItems = ["Features", "Sign Up"], - badge = "New Release", - headline = "Build Amazing Products Faster", - description = "The complete toolkit for modern product development.", - primaryCta = "Get Started", - secondaryCta, - onPrimaryCta, - onSecondaryCta -}: LandingPageProps) { - return ( -
-
-
-
-
-

{brandName}

-
-
- {navItems.map((item) => ( - - ))} -
-
-
-
- {badge} -

- {headline} -

-

- {description} -

-
- - {secondaryCta && ( - - )} -
-
-
- ) -}`, - ecommercePage: `interface EcommercePageProps { - storeName?: string - productBadge?: string - productName?: string - productPrice?: string - originalPrice?: string - onAddToCart?: () => void -} - -function EcommercePage({ - storeName = "Store", - productBadge = "New Arrival", - productName = "Premium Product", - productPrice = "$299.00", - originalPrice, - onAddToCart -}: EcommercePageProps) { - return ( -
-
-
-

{storeName}

- -
-
-
-
-
-
- {productBadge} -

{productName}

-
- {productPrice} - {originalPrice && ( - - {originalPrice} - - )} -
- -
-
-
-
- ) -}`, - blogArticle: `interface BlogArticleProps { - blogName?: string - tags?: string[] - title?: string - authorName?: string - authorAvatar?: string - authorFallback?: string - date?: string - readTime?: string - excerpt?: string -} - -function BlogArticle({ - blogName = "Blog", - tags = ["Design", "Tutorial"], - title = "Building a Comprehensive Component Library", - authorName = "Alex Writer", - authorAvatar, - authorFallback = "AW", - date = "March 15, 2024", - readTime = "10 min read", - excerpt = "Design systems have become an essential part of modern product development." -}: BlogArticleProps) { - return ( -
-
-

{blogName}

-
-
-
-
- {tags.map((tag, idx) => ( - - {tag} - - ))} -
-

- {title} -

-
- - {authorAvatar && } - {authorFallback} - -
-

{authorName}

-

- {date} · {readTime} -

-
-
-
-

- {excerpt} -

-
-
-
- ) -}`, -} +export { atomsCodeSnippets } from './snippets/atoms' +export { moleculesCodeSnippets } from './snippets/molecules' +export { organismsCodeSnippets } from './snippets/organisms' +export { templatesCodeSnippets } from './snippets/templates' diff --git a/src/lib/snippets/atoms.ts b/src/lib/snippets/atoms.ts new file mode 100644 index 0000000..3d33b94 --- /dev/null +++ b/src/lib/snippets/atoms.ts @@ -0,0 +1,209 @@ +/** + * Atoms - Basic UI building blocks code snippets + */ + +export const atomsCodeSnippets = { + buttonDefault: `interface ButtonProps { + children?: string + onClick?: () => void +} + +function DefaultButton({ children = "Default", onClick }: ButtonProps) { + return +}`, + buttonSecondary: `interface ButtonProps { + children?: string + variant?: "default" | "secondary" | "destructive" | "outline" | "ghost" | "link" + onClick?: () => void +} + +function CustomButton({ children = "Secondary", variant = "secondary", onClick }: ButtonProps) { + return +}`, + buttonDestructive: `interface DestructiveButtonProps { + children?: string + onClick?: () => void +} + +function DestructiveButton({ children = "Destructive", onClick }: DestructiveButtonProps) { + return +}`, + buttonOutline: `interface OutlineButtonProps { + children?: string + onClick?: () => void +} + +function OutlineButton({ children = "Outline", onClick }: OutlineButtonProps) { + return +}`, + buttonGhost: `interface GhostButtonProps { + children?: string + onClick?: () => void +} + +function GhostButton({ children = "Ghost", onClick }: GhostButtonProps) { + return +}`, + buttonLink: `interface LinkButtonProps { + children?: string + onClick?: () => void +} + +function LinkButton({ children = "Link", onClick }: LinkButtonProps) { + return +}`, + buttonSizes: `interface ButtonSizesProps { + smallText?: string + defaultText?: string + largeText?: string + onSmallClick?: () => void + onDefaultClick?: () => void + onLargeClick?: () => void + onIconClick?: () => void +} + +function ButtonSizes({ + smallText = "Small", + defaultText = "Default", + largeText = "Large", + onSmallClick, + onDefaultClick, + onLargeClick, + onIconClick +}: ButtonSizesProps) { + return ( + <> + + + + + + ) +}`, + buttonWithIcons: `interface IconButtonProps { + primaryText?: string + secondaryText?: string + onPrimaryClick?: () => void + onSecondaryClick?: () => void +} + +function IconButtons({ + primaryText = "Favorite", + secondaryText = "Add Item", + onPrimaryClick, + onSecondaryClick +}: IconButtonProps) { + return ( + <> + + + + ) +}`, + badgeVariants: `interface BadgeVariantsProps { + defaultText?: string + secondaryText?: string + destructiveText?: string + outlineText?: string +} + +function BadgeVariants({ + defaultText = "Default", + secondaryText = "Secondary", + destructiveText = "Destructive", + outlineText = "Outline" +}: BadgeVariantsProps) { + return ( + <> + {defaultText} + {secondaryText} + {destructiveText} + {outlineText} + + ) +}`, + badgeWithIcons: `interface IconBadgeProps { + completedText?: string + failedText?: string +} + +function IconBadges({ + completedText = "Completed", + failedText = "Failed" +}: IconBadgeProps) { + return ( + <> + + + {completedText} + + + + {failedText} + + + ) +}`, + inputBasic: `interface InputProps { + placeholder?: string + value?: string + onChange?: (e: React.ChangeEvent) => void +} + +function BasicInput({ placeholder = "Default input", value, onChange }: InputProps) { + return +}`, + inputWithIcon: `interface SearchInputProps { + placeholder?: string + value?: string + onChange?: (e: React.ChangeEvent) => void +} + +function SearchInput({ placeholder = "Search...", value, onChange }: SearchInputProps) { + return ( +
+ + +
+ ) +}`, + inputTypes: `interface TypedInputsProps { + textPlaceholder?: string + emailPlaceholder?: string + passwordPlaceholder?: string + numberPlaceholder?: string + onTextChange?: (e: React.ChangeEvent) => void + onEmailChange?: (e: React.ChangeEvent) => void + onPasswordChange?: (e: React.ChangeEvent) => void + onNumberChange?: (e: React.ChangeEvent) => void +} + +function TypedInputs({ + textPlaceholder = "Text input", + emailPlaceholder = "email@example.com", + passwordPlaceholder = "Password", + numberPlaceholder = "123", + onTextChange, + onEmailChange, + onPasswordChange, + onNumberChange +}: TypedInputsProps) { + return ( + <> + + + + + + ) +}`, +} + diff --git a/src/lib/snippets/index.ts b/src/lib/snippets/index.ts new file mode 100644 index 0000000..e29e75f --- /dev/null +++ b/src/lib/snippets/index.ts @@ -0,0 +1,8 @@ +/** + * Component code snippets - Re-exports from focused modules + */ + +export { atomsCodeSnippets } from './atoms' +export { moleculesCodeSnippets } from './molecules' +export { organismsCodeSnippets } from './organisms' +export { templatesCodeSnippets } from './templates' diff --git a/src/lib/snippets/molecules.ts b/src/lib/snippets/molecules.ts new file mode 100644 index 0000000..d01b800 --- /dev/null +++ b/src/lib/snippets/molecules.ts @@ -0,0 +1,219 @@ +/** + * Molecules - Composite UI components code snippets + */ + +export const moleculesCodeSnippets = { + formField: `interface FormFieldProps { + label?: string + placeholder?: string + helperText?: string + id?: string + value?: string + onChange?: (e: React.ChangeEvent) => void +} + +function EmailFormField({ + label = "Email Address", + placeholder = "john@example.com", + helperText = "We'll never share your email with anyone else.", + id = "email", + value, + onChange +}: FormFieldProps) { + return ( +
+ +
+ + +
+

+ {helperText} +

+
+ ) +}`, + searchBar: `interface SearchBarProps { + placeholder?: string + value?: string + onChange?: (e: React.ChangeEvent) => void +} + +function SearchBar({ placeholder = "Search...", value, onChange }: SearchBarProps) { + return ( +
+ + +
+ ) +}`, + searchBarWithButton: `interface SearchBarWithButtonProps { + placeholder?: string + buttonText?: string + value?: string + onChange?: (e: React.ChangeEvent) => void + onSearch?: () => void +} + +function SearchBarWithButton({ + placeholder = "Search...", + buttonText = "Search", + value, + onChange, + onSearch +}: SearchBarWithButtonProps) { + return ( +
+
+ + +
+ +
+ ) +}`, + userCard: `interface UserCardProps { + name?: string + username?: string + bio?: string + avatarUrl?: string + avatarFallback?: string + buttonText?: string + onButtonClick?: () => void +} + +function UserCard({ + name = "Alex Morgan", + username = "@alexmorgan", + bio = "Product designer passionate about creating delightful user experiences.", + avatarUrl = "https://i.pravatar.cc/150?img=1", + avatarFallback = "AM", + buttonText = "Follow", + onButtonClick +}: UserCardProps) { + return ( + +
+ + + {avatarFallback} + +
+

{name}

+

{username}

+

+ {bio} +

+
+ +
+
+ ) +}`, + socialActions: `interface SocialActionsProps { + likeText?: string + commentText?: string + shareText?: string + onLike?: () => void + onComment?: () => void + onShare?: () => void +} + +function SocialActions({ + likeText = "Like", + commentText = "Comment", + shareText = "Share", + onLike, + onComment, + onShare +}: SocialActionsProps) { + return ( +
+ + + +
+ ) +}`, + statusIndicator: `interface StatusIndicatorProps { + statusText?: string + badgeText?: string + isActive?: boolean +} + +function StatusIndicator({ + statusText = "System Online", + badgeText = "Active", + isActive = true +}: StatusIndicatorProps) { + return ( +
+
+
+ {statusText} +
+ {badgeText} +
+ ) +}`, + contentCard: `interface ContentCardProps { + title?: string + description?: string + date?: string + readTime?: string + tags?: string[] +} + +function ContentCard({ + title = "Building Scalable Design Systems", + description = "Learn how to create and maintain design systems that grow with your team.", + date = "Mar 15, 2024", + readTime = "5 min read", + tags = ["Design", "System"] +}: ContentCardProps) { + return ( + +
+
+
+

+ {title} +

+

+ {description} +

+
+
+
+
+ + {date} +
+ + {readTime} +
+
+ {tags.map((tag) => ( + {tag} + ))} +
+
+
+ ) +}`, +} + diff --git a/src/lib/snippets/organisms.ts b/src/lib/snippets/organisms.ts new file mode 100644 index 0000000..49af3e4 --- /dev/null +++ b/src/lib/snippets/organisms.ts @@ -0,0 +1,338 @@ +/** + * Organisms - Complex UI patterns code snippets + */ + +export const organismsCodeSnippets = { + navigationBar: `interface NavigationBarProps { + brandName?: string + navItems?: Array<{ label: string; icon: React.ReactNode; onClick?: () => void }> + avatarUrl?: string + avatarFallback?: string + onNotificationClick?: () => void + onSettingsClick?: () => void +} + +function NavigationBar({ + brandName = "BrandName", + navItems = [ + { label: "Home", icon: }, + { label: "Analytics", icon: }, + { label: "Projects", icon: } + ], + avatarUrl = "https://i.pravatar.cc/150?img=3", + avatarFallback = "U", + onNotificationClick, + onSettingsClick +}: NavigationBarProps) { + return ( +
+
+
+

{brandName}

+ +
+
+ + + + + {avatarFallback} + +
+
+
+ ) +}`, + dataTable: `interface Transaction { + id: string + status: string + statusVariant?: "default" | "secondary" | "destructive" | "outline" + description: string + date: string + amount: string + isNegative?: boolean +} + +interface DataTableProps { + title?: string + exportButtonText?: string + transactions?: Transaction[] + onExport?: () => void +} + +function DataTable({ + title = "Recent Transactions", + exportButtonText = "Export", + transactions = [ + { id: "1", status: "Completed", description: "Payment received", date: "Mar 15, 2024", amount: "$250.00" } + ], + onExport +}: DataTableProps) { + return ( + +
+
+

{title}

+ +
+
+ + + + Status + Transaction + Date + Amount + + + + {transactions.map((transaction) => ( + + + {transaction.status} + + {transaction.description} + {transaction.date} + {transaction.amount} + + ))} + +
+
+ ) +}`, + form: `interface CreateAccountFormProps { + title?: string + description?: string + firstNameLabel?: string + lastNameLabel?: string + emailLabel?: string + firstNamePlaceholder?: string + lastNamePlaceholder?: string + emailPlaceholder?: string + cancelButtonText?: string + submitButtonText?: string + onCancel?: () => void + onSubmit?: (data: { firstName: string; lastName: string; email: string }) => void +} + +function CreateAccountForm({ + title = "Create Account", + description = "Fill in your details to get started", + firstNameLabel = "First Name", + lastNameLabel = "Last Name", + emailLabel = "Email", + firstNamePlaceholder = "John", + lastNamePlaceholder = "Doe", + emailPlaceholder = "john@example.com", + cancelButtonText = "Cancel", + submitButtonText = "Create Account", + onCancel, + onSubmit +}: CreateAccountFormProps) { + const [firstName, setFirstName] = useState("") + const [lastName, setLastName] = useState("") + const [email, setEmail] = useState("") + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + onSubmit?.({ firstName, lastName, email }) + } + + return ( + +
+
+

{title}

+

+ {description} +

+
+ +
+
+ + setFirstName(e.target.value)} + /> +
+
+ + setLastName(e.target.value)} + /> +
+
+
+ +
+ + setEmail(e.target.value)} + /> +
+
+ +
+ + +
+ +
+ ) +}`, + taskList: `interface Task { + id: string + title: string + description: string + status: string + statusColor?: "accent" | "destructive" + badgeText: string + badgeVariant?: "default" | "secondary" | "destructive" | "outline" + icon: React.ReactNode +} + +interface TaskListProps { + title?: string + addButtonText?: string + tasks?: Task[] + onAddTask?: () => void +} + +function TaskList({ + title = "Project Tasks", + addButtonText = "Add Task", + tasks = [ + { + id: "1", + title: "Design system documentation", + description: "Complete the component library documentation", + status: "Completed", + statusColor: "accent" as const, + badgeText: "Design", + badgeVariant: "secondary" as const, + icon: + } + ], + onAddTask +}: TaskListProps) { + return ( + +
+
+

{title}

+ +
+
+
+ {tasks.map((task) => ( +
+
+ {task.icon} +
+

{task.title}

+

+ {task.description} +

+
+ {task.badgeText} + {task.status} +
+
+
+
+ ))} +
+
+ ) +}`, + sidebarNavigation: `interface SidebarNavItem { + label: string + icon: React.ReactNode + variant?: "default" | "ghost" + onClick?: () => void +} + +interface SidebarNavigationProps { + title?: string + navItems?: SidebarNavItem[] + contentText?: string +} + +function SidebarNavigation({ + title = "Dashboard", + navItems = [ + { label: "Home", icon: , variant: "ghost" as const }, + { label: "Analytics", icon: , variant: "default" as const }, + { label: "Projects", icon: , variant: "ghost" as const } + ], + contentText = "Main content area" +}: SidebarNavigationProps) { + return ( +
+ +
+

+ {contentText} +

+
+
+ ) +}`, +} + diff --git a/src/lib/snippets/templates.ts b/src/lib/snippets/templates.ts new file mode 100644 index 0000000..76ee6ea --- /dev/null +++ b/src/lib/snippets/templates.ts @@ -0,0 +1,256 @@ +/** + * Templates - Complete page layouts code snippets + */ + +export const templatesCodeSnippets = { + dashboardLayout: `interface StatCard { + label: string + value: string + trend?: string +} + +interface DashboardLayoutProps { + title?: string + navItems?: Array<{ label: string; icon: React.ReactNode; variant?: "default" | "ghost" }> + avatarUrl?: string + avatarFallback?: string + stats?: StatCard[] + onNotificationClick?: () => void +} + +function DashboardLayout({ + title = "Dashboard", + navItems = [ + { label: "Overview", icon: , variant: "default" as const }, + { label: "Analytics", icon: , variant: "ghost" as const } + ], + avatarUrl, + avatarFallback = "U", + stats = [ + { label: "Total Revenue", value: "$45,231", trend: "+20.1% from last month" } + ], + onNotificationClick +}: DashboardLayoutProps) { + return ( +
+
+
+

{title}

+
+ + + {avatarUrl && } + {avatarFallback} + +
+
+
+
+ +
+

Overview

+
+ {stats.map((stat) => ( + +

{stat.label}

+

{stat.value}

+ {stat.trend && ( +

{stat.trend}

+ )} +
+ ))} +
+
+
+
+ ) +}`, + landingPage: `interface LandingPageProps { + brandName?: string + navItems?: string[] + badge?: string + headline?: string + description?: string + primaryCta?: string + secondaryCta?: string + onPrimaryCta?: () => void + onSecondaryCta?: () => void +} + +function LandingPage({ + brandName = "ProductName", + navItems = ["Features", "Sign Up"], + badge = "New Release", + headline = "Build Amazing Products Faster", + description = "The complete toolkit for modern product development.", + primaryCta = "Get Started", + secondaryCta, + onPrimaryCta, + onSecondaryCta +}: LandingPageProps) { + return ( +
+
+
+
+
+

{brandName}

+
+
+ {navItems.map((item) => ( + + ))} +
+
+
+
+ {badge} +

+ {headline} +

+

+ {description} +

+
+ + {secondaryCta && ( + + )} +
+
+
+ ) +}`, + ecommercePage: `interface EcommercePageProps { + storeName?: string + productBadge?: string + productName?: string + productPrice?: string + originalPrice?: string + onAddToCart?: () => void +} + +function EcommercePage({ + storeName = "Store", + productBadge = "New Arrival", + productName = "Premium Product", + productPrice = "$299.00", + originalPrice, + onAddToCart +}: EcommercePageProps) { + return ( +
+
+
+

{storeName}

+ +
+
+
+
+
+
+ {productBadge} +

{productName}

+
+ {productPrice} + {originalPrice && ( + + {originalPrice} + + )} +
+ +
+
+
+
+ ) +}`, + blogArticle: `interface BlogArticleProps { + blogName?: string + tags?: string[] + title?: string + authorName?: string + authorAvatar?: string + authorFallback?: string + date?: string + readTime?: string + excerpt?: string +} + +function BlogArticle({ + blogName = "Blog", + tags = ["Design", "Tutorial"], + title = "Building a Comprehensive Component Library", + authorName = "Alex Writer", + authorAvatar, + authorFallback = "AW", + date = "March 15, 2024", + readTime = "10 min read", + excerpt = "Design systems have become an essential part of modern product development." +}: BlogArticleProps) { + return ( +
+
+

{blogName}

+
+
+
+
+ {tags.map((tag, idx) => ( + + {tag} + + ))} +
+

+ {title} +

+
+ + {authorAvatar && } + {authorFallback} + +
+

{authorName}

+

+ {date} · {readTime} +

+
+
+
+

+ {excerpt} +

+
+
+
+ ) +}`, +} From 34f76d6ee4d522d5faa6a4688534f5c5f2ab8ef0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 17 Jan 2026 23:28:35 +0000 Subject: [PATCH 4/4] Address code review feedback - extract shared constants Co-authored-by: johndoe6345789 <224850594+johndoe6345789@users.noreply.github.com> --- src/lib/db-constants.ts | 8 ++++++++ src/lib/db-core.ts | 4 +++- src/lib/db-indexeddb.ts | 7 +------ src/lib/db-localstorage.ts | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 src/lib/db-constants.ts diff --git a/src/lib/db-constants.ts b/src/lib/db-constants.ts new file mode 100644 index 0000000..eff5427 --- /dev/null +++ b/src/lib/db-constants.ts @@ -0,0 +1,8 @@ +/** + * Database constants shared across modules + */ + +export const DB_KEY = 'codesnippet-db' +export const IDB_NAME = 'CodeSnippetDB' +export const IDB_STORE = 'database' +export const IDB_VERSION = 1 diff --git a/src/lib/db-core.ts b/src/lib/db-core.ts index 16818d7..3583fbe 100644 --- a/src/lib/db-core.ts +++ b/src/lib/db-core.ts @@ -7,6 +7,7 @@ import { loadFromIndexedDB, saveToIndexedDB, openIndexedDB, deleteFromIndexedDB import { loadFromLocalStorage, saveToLocalStorage, deleteFromLocalStorage } from './db-localstorage' import { validateSchema, createTables } from './db-schema' import { getStorageConfig, FlaskStorageAdapter, loadStorageConfig } from './storage' +import { DB_KEY } from './db-constants' let dbInstance: Database | null = null let sqlInstance: any = null @@ -151,7 +152,8 @@ export async function getDatabaseStats(): Promise<{ const databaseSize = data.length const hasIDB = await openIndexedDB() - const storageType = hasIDB ? 'indexeddb' : (loadFromLocalStorage() ? 'localstorage' : 'none') + const hasLocalStorage = typeof localStorage !== 'undefined' && localStorage.getItem(DB_KEY) !== null + const storageType = hasIDB ? 'indexeddb' : (hasLocalStorage ? 'localstorage' : 'none') return { snippetCount, diff --git a/src/lib/db-indexeddb.ts b/src/lib/db-indexeddb.ts index 5631db3..197de78 100644 --- a/src/lib/db-indexeddb.ts +++ b/src/lib/db-indexeddb.ts @@ -2,10 +2,7 @@ * IndexedDB operations for database persistence */ -const DB_KEY = 'codesnippet-db' -const IDB_NAME = 'CodeSnippetDB' -const IDB_STORE = 'database' -const IDB_VERSION = 1 +import { DB_KEY, IDB_NAME, IDB_STORE, IDB_VERSION } from './db-constants' export async function openIndexedDB(): Promise { if (typeof indexedDB === 'undefined') return null @@ -101,5 +98,3 @@ export async function deleteFromIndexedDB(): Promise { } }) } - -export { DB_KEY } diff --git a/src/lib/db-localstorage.ts b/src/lib/db-localstorage.ts index d5d3b25..10d2020 100644 --- a/src/lib/db-localstorage.ts +++ b/src/lib/db-localstorage.ts @@ -2,7 +2,7 @@ * LocalStorage operations for database persistence */ -import { DB_KEY } from './db-indexeddb' +import { DB_KEY } from './db-constants' export function loadFromLocalStorage(): Uint8Array | null { try {