diff options
| author | Yuval Adam <_@yuv.al> | 2025-06-27 10:41:40 +0200 |
|---|---|---|
| committer | Yuval Adam <_@yuv.al> | 2025-06-27 10:41:40 +0200 |
| commit | c896691084bfc786dc1780ce36fd71e6f7ab9d60 (patch) | |
| tree | 0e60f238bb42c24441dfbfbcab6f240c0d4d0d67 /src/components | |
| parent | 8403930448013787bd0a986ece086f20ef6d9a57 (diff) | |
Migrate from react to svelte
Diffstat (limited to 'src/components')
| -rw-r--r-- | src/components/Counter.jsx | 16 | ||||
| -rw-r--r-- | src/components/SimpleTest.tsx | 32 | ||||
| -rw-r--r-- | src/components/TaskManager.svelte | 183 | ||||
| -rw-r--r-- | src/components/TaskManager.tsx | 202 |
4 files changed, 183 insertions, 250 deletions
diff --git a/src/components/Counter.jsx b/src/components/Counter.jsx deleted file mode 100644 index 8661dc0..0000000 --- a/src/components/Counter.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import { useState } from 'react'; - -export default function Counter() { - const [count, setCount] = useState(0); - - return ( - <div className="flex justify-center"> - <button - className="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 transition-colors" - onClick={() => setCount(count + 1)} - > - Clicked {count} times - </button> - </div> - ); -} diff --git a/src/components/SimpleTest.tsx b/src/components/SimpleTest.tsx deleted file mode 100644 index cfabeab..0000000 --- a/src/components/SimpleTest.tsx +++ /dev/null @@ -1,32 +0,0 @@ -import { useState } from 'react'; - -export default function SimpleTest() { - const [count, setCount] = useState(0); - - return ( - <div className="max-w-2xl mx-auto p-6"> - <h1 className="text-3xl font-bold mb-6">Simple Test Component</h1> - <div className="space-y-4"> - <p>This is a simple React component to test if the infinite refresh is caused by the API calls.</p> - <div className="flex items-center space-x-4"> - <button - onClick={() => setCount(count - 1)} - className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600" - > - - - </button> - <span className="text-xl font-bold">{count}</span> - <button - onClick={() => setCount(count + 1)} - className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600" - > - + - </button> - </div> - <p className="text-gray-600"> - If this component works without refreshing, the issue is in the TaskManager API calls. - </p> - </div> - </div> - ); -}
\ No newline at end of file diff --git a/src/components/TaskManager.svelte b/src/components/TaskManager.svelte new file mode 100644 index 0000000..735a488 --- /dev/null +++ b/src/components/TaskManager.svelte @@ -0,0 +1,183 @@ +<script lang="ts"> + import { onMount } from 'svelte'; + + interface Task { + id: number; + title: string; + description: string; + completed: boolean; + created_at?: string; + updated_at?: string; + } + + let tasks: Task[] = []; + let newTask = { title: '', description: '' }; + let loading = true; + let error: string | null = null; + + async function fetchTasks() { + try { + error = null; + const response = await fetch('/api/tasks'); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data = await response.json(); + console.log('Fetched tasks:', data); + tasks = Array.isArray(data) ? data : []; + } catch (err) { + console.error('Failed to fetch tasks:', err); + error = err instanceof Error ? err.message : 'Failed to fetch tasks'; + tasks = []; + } finally { + loading = false; + } + } + + onMount(() => { + fetchTasks(); + }); + + async function createTask(e: Event) { + e.preventDefault(); + if (!newTask.title.trim()) return; + + try { + const response = await fetch('/api/tasks', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(newTask) + }); + + if (response.ok) { + newTask = { title: '', description: '' }; + fetchTasks(); + } + } catch (err) { + console.error('Failed to create task:', err); + } + } + + async function toggleTask(task: Task) { + try { + await fetch(`/api/tasks/${task.id}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ ...task, completed: !task.completed }) + }); + fetchTasks(); + } catch (err) { + console.error('Failed to update task:', err); + } + } + + async function deleteTask(id: number) { + try { + await fetch(`/api/tasks/${id}`, { method: 'DELETE' }); + fetchTasks(); + } catch (err) { + console.error('Failed to delete task:', err); + } + } +</script> + +{#if loading} + <div class="max-w-2xl mx-auto p-6"> + <h1 class="text-3xl font-bold mb-6">Task Manager</h1> + <div class="p-4 text-center">Loading tasks...</div> + </div> +{:else if error} + <div class="max-w-2xl mx-auto p-6"> + <h1 class="text-3xl font-bold mb-6">Task Manager</h1> + <div class="p-4 bg-red-50 border border-red-200 rounded-lg"> + <p class="text-red-700">Error: {error}</p> + <button + on:click={fetchTasks} + class="mt-2 px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600" + > + Retry + </button> + </div> + </div> +{:else} + <div class="max-w-2xl mx-auto p-6"> + <h1 class="text-3xl font-bold mb-6">Task Manager</h1> + + <form on:submit={createTask} class="mb-8 space-y-4"> + <div> + <input + type="text" + placeholder="Task title" + bind:value={newTask.title} + class="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" + /> + </div> + <div> + <textarea + placeholder="Task description (optional)" + bind:value={newTask.description} + class="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" + rows="3" + /> + </div> + <button + type="submit" + class="w-full bg-blue-500 text-white p-3 rounded-lg hover:bg-blue-600 transition-colors" + > + Add Task + </button> + </form> + + <div class="space-y-4"> + {#if tasks.length === 0} + <p class="text-gray-500 text-center">No tasks yet. Create your first task above!</p> + {:else} + {#each tasks as task (task.id)} + <div + class="p-4 border rounded-lg {task.completed ? 'bg-green-50 border-green-200' : 'bg-white border-gray-200'}" + > + <div class="flex items-start justify-between"> + <div class="flex-1"> + <h3 + class="font-semibold {task.completed ? 'line-through text-gray-500' : 'text-gray-900'}" + > + {task.title} + </h3> + {#if task.description} + <p + class="mt-1 {task.completed ? 'line-through text-gray-400' : 'text-gray-600'}" + > + {task.description} + </p> + {/if} + {#if task.created_at} + <p class="text-xs text-gray-400 mt-2"> + Created: {new Date(task.created_at).toLocaleString()} + </p> + {/if} + </div> + <div class="flex space-x-2 ml-4"> + <button + on:click={() => toggleTask(task)} + class="px-3 py-1 rounded text-sm {task.completed + ? 'bg-yellow-500 text-white hover:bg-yellow-600' + : 'bg-green-500 text-white hover:bg-green-600'}" + > + {task.completed ? 'Undo' : 'Complete'} + </button> + <button + on:click={() => deleteTask(task.id)} + class="px-3 py-1 bg-red-500 text-white rounded text-sm hover:bg-red-600" + > + Delete + </button> + </div> + </div> + </div> + {/each} + {/if} + </div> + </div> +{/if}
\ No newline at end of file diff --git a/src/components/TaskManager.tsx b/src/components/TaskManager.tsx deleted file mode 100644 index a10a5f9..0000000 --- a/src/components/TaskManager.tsx +++ /dev/null @@ -1,202 +0,0 @@ -import { useState, useEffect, useCallback } from 'react'; - -interface Task { - id: number; - title: string; - description: string; - completed: boolean; - created_at?: string; - updated_at?: string; -} - -export default function TaskManager() { - const [tasks, setTasks] = useState<Task[]>([]); - const [newTask, setNewTask] = useState({ title: '', description: '' }); - const [loading, setLoading] = useState(true); - const [error, setError] = useState<string | null>(null); - - const fetchTasks = useCallback(async () => { - try { - setError(null); - const response = await fetch('/api/tasks'); - - if (!response.ok) { - throw new Error(`HTTP error! status: ${response.status}`); - } - - const data = await response.json(); - console.log('Fetched tasks:', data); - setTasks(Array.isArray(data) ? data : []); - } catch (error) { - console.error('Failed to fetch tasks:', error); - setError(error instanceof Error ? error.message : 'Failed to fetch tasks'); - setTasks([]); - } finally { - setLoading(false); - } - }, []); - - useEffect(() => { - fetchTasks(); - }, [fetchTasks]); - - const createTask = async (e: React.FormEvent) => { - e.preventDefault(); - if (!newTask.title.trim()) return; - - try { - const response = await fetch('/api/tasks', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(newTask) - }); - - if (response.ok) { - setNewTask({ title: '', description: '' }); - fetchTasks(); - } - } catch (error) { - console.error('Failed to create task:', error); - } - }; - - const toggleTask = async (task: Task) => { - try { - await fetch(`/api/tasks/${task.id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ ...task, completed: !task.completed }) - }); - fetchTasks(); - } catch (error) { - console.error('Failed to update task:', error); - } - }; - - const deleteTask = async (id: number) => { - try { - await fetch(`/api/tasks/${id}`, { method: 'DELETE' }); - fetchTasks(); - } catch (error) { - console.error('Failed to delete task:', error); - } - }; - - if (loading) { - return ( - <div className="max-w-2xl mx-auto p-6"> - <h1 className="text-3xl font-bold mb-6">Task Manager</h1> - <div className="p-4 text-center">Loading tasks...</div> - </div> - ); - } - - if (error) { - return ( - <div className="max-w-2xl mx-auto p-6"> - <h1 className="text-3xl font-bold mb-6">Task Manager</h1> - <div className="p-4 bg-red-50 border border-red-200 rounded-lg"> - <p className="text-red-700">Error: {error}</p> - <button - onClick={fetchTasks} - className="mt-2 px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600" - > - Retry - </button> - </div> - </div> - ); - } - - return ( - <div className="max-w-2xl mx-auto p-6"> - <h1 className="text-3xl font-bold mb-6">Task Manager</h1> - - <form onSubmit={createTask} className="mb-8 space-y-4"> - <div> - <input - type="text" - placeholder="Task title" - value={newTask.title} - onChange={(e) => setNewTask({ ...newTask, title: e.target.value })} - className="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" - /> - </div> - <div> - <textarea - placeholder="Task description (optional)" - value={newTask.description} - onChange={(e) => setNewTask({ ...newTask, description: e.target.value })} - className="w-full p-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" - rows={3} - /> - </div> - <button - type="submit" - className="w-full bg-blue-500 text-white p-3 rounded-lg hover:bg-blue-600 transition-colors" - > - Add Task - </button> - </form> - - <div className="space-y-4"> - {tasks.length === 0 ? ( - <p className="text-gray-500 text-center">No tasks yet. Create your first task above!</p> - ) : ( - tasks.map((task) => ( - <div - key={task.id} - className={`p-4 border rounded-lg ${ - task.completed ? 'bg-green-50 border-green-200' : 'bg-white border-gray-200' - }`} - > - <div className="flex items-start justify-between"> - <div className="flex-1"> - <h3 - className={`font-semibold ${ - task.completed ? 'line-through text-gray-500' : 'text-gray-900' - }`} - > - {task.title} - </h3> - {task.description && ( - <p - className={`mt-1 ${ - task.completed ? 'line-through text-gray-400' : 'text-gray-600' - }`} - > - {task.description} - </p> - )} - {task.created_at && ( - <p className="text-xs text-gray-400 mt-2"> - Created: {new Date(task.created_at).toLocaleString()} - </p> - )} - </div> - <div className="flex space-x-2 ml-4"> - <button - onClick={() => toggleTask(task)} - className={`px-3 py-1 rounded text-sm ${ - task.completed - ? 'bg-yellow-500 text-white hover:bg-yellow-600' - : 'bg-green-500 text-white hover:bg-green-600' - }`} - > - {task.completed ? 'Undo' : 'Complete'} - </button> - <button - onClick={() => deleteTask(task.id)} - className="px-3 py-1 bg-red-500 text-white rounded text-sm hover:bg-red-600" - > - Delete - </button> - </div> - </div> - </div> - )) - )} - </div> - </div> - ); -}
\ No newline at end of file |
