summaryrefslogtreecommitdiff
path: root/src/components
diff options
context:
space:
mode:
authorYuval Adam <_@yuv.al>2025-06-27 10:41:40 +0200
committerYuval Adam <_@yuv.al>2025-06-27 10:41:40 +0200
commitc896691084bfc786dc1780ce36fd71e6f7ab9d60 (patch)
tree0e60f238bb42c24441dfbfbcab6f240c0d4d0d67 /src/components
parent8403930448013787bd0a986ece086f20ef6d9a57 (diff)
Migrate from react to svelte
Diffstat (limited to 'src/components')
-rw-r--r--src/components/Counter.jsx16
-rw-r--r--src/components/SimpleTest.tsx32
-rw-r--r--src/components/TaskManager.svelte183
-rw-r--r--src/components/TaskManager.tsx202
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