Taller práctico
Configuremos las cuentas
- ¿Tienes Github?
- ¿Tienes Vercel?
- ¿Tienes Supabase?
Empezando
-
Vaya a Database.new y configure su instancia.
-
Vaya al editor SQL y haga lo siguiente:
- Crear tablas
create table todos ( id bigint generated by default as identity primary key, user_id uuid references auth.users not null, task text check (char_length(task) > 3), is_complete boolean default false, inserted_at timestamp with time zone default timezone('utc'::text, now()) not null );B. Crear políticas RLS
alter table todos enable row level security; create policy "Individuals can create todos." on todos for insert with check (auth.uid() = user_id); create policy "Individuals can view their own todos. " on todos for select using ((select auth.uid()) = user_id); create policy "Individuals can update their own todos." on todos for update using ((select auth.uid()) = user_id); create policy "Individuals can delete their own todos." on todos for delete using ((select auth.uid()) = user_id); -
Crea la siguiente aplicación
npx create-next-app -e with-supabase- Configurar env.local
NEXT_PUBLIC_SUPABASE_URL=<SUBSTITUTE_SUPABASE_URL>
NEXT_PUBLIC_SUPABASE_ANON_KEY=<SUBSTITUTE_SUPABASE_ANON_KEY>-
Cambie el nombre de /protected/page.tsx a /protected/_page.tsx
-
Cree un nuevo page.tsx en esa ubicación:
export default function Home() { return <div>Hello World</div> } -
Cree el componente. Lista de tareas pendientes.tsx
"use client" import { useEffect, useState } from "react" import { createClient } from "@/utils/supabase/client" interface Todo { id: number task: string is_complete: boolean inserted_at: string } export default function ToDoList() { const supabase = createClient() const [todos, setTodos] = useState<Todo[]>([]) const [loading, setLoading] = useState(true) const [error, setError] = useState<string | null>(null) // Fetch todos when component mounts useEffect(() => { fetchTodos() }, []) const fetchTodos = async () => { try { setLoading(true) const { data, error } = await supabase .from("todos") .select("*") .order("inserted_at", { ascending: false }) if (error) { throw error } setTodos(data || []) } catch (error) { setError(error instanceof Error ? error.message : "An error occurred") } finally { setLoading(false) } } const toggleTodoComplete = async (id: number, currentStatus: boolean) => { try { const { error } = await supabase .from("todos") .update({ is_complete: !currentStatus }) .eq("id", id) if (error) { throw error } // Update local state setTodos( todos.map((todo) => (todo.id === id ? { ...todo, is_complete: !currentStatus } : todo)), ) } catch (error) { setError(error instanceof Error ? error.message : "An error occurred") } } // Show loading state if (loading) { return ( <div className="flex justify-center items-center p-4"> <p>Loading...</p> </div> ) } // Show error state if (error) { return ( <div className="flex justify-center items-center p-4"> <p className="text-red-500">Error: {error}</p> </div> ) } return ( <div className="max-w-2xl mx-auto p-4"> <h2 className="text-2xl font-bold mb-4">Your ToDo List</h2> {todos.length === 0 ? ( <p className="text-gray-500">No todos yet!</p> ) : ( <ul className="space-y-2"> {todos.map((todo) => ( <li key={todo.id} className="flex items-center justify-between p-3 bg-white rounded shadow" > <div className="flex items-center space-x-3"> <input type="checkbox" checked={todo.is_complete} onChange={() => toggleTodoComplete(todo.id, todo.is_complete)} className="h-5 w-5 rounded border-gray-300" /> <span className={`${todo.is_complete ? "line-through text-gray-500" : ""}`}> {todo.task} </span> </div> <span className="text-sm text-gray-500"> {new Date(todo.inserted_at).toLocaleDateString()} </span> </li> ))} </ul> )} </div> ) } -
Cree AddToDo.tsx
"use client" import { useState, useEffect } from "react" import { createClient } from "@/utils/supabase/client" export default function AddToDo({ onAdd }: { onAdd?: () => void }) { const supabase = createClient() const [task, setTask] = useState("") const [loading, setLoading] = useState(false) const [error, setError] = useState<string | null>(null) const [user, setUser] = useState<any>(null) useEffect(() => { const checkUser = async () => { const { data: { user }, } = await supabase.auth.getUser() setUser(user) } checkUser() }, []) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() if (!user) { setError("Please login to add todos") return } if (task.length < 4) { setError("Task must be at least 4 characters long") return } try { setLoading(true) setError(null) const { error } = await supabase.from("todos").insert([{ task, user_id: user.id }]) if (error) throw error setTask("") if (onAdd) onAdd() } catch (err) { setError(err instanceof Error ? err.message : "Failed to add todo") } finally { setLoading(false) } } if (!user) { return ( <div className="max-w-2xl mx-auto p-4"> <p className="text-gray-600">Please login to add todos</p> </div> ) } return ( <form onSubmit={handleSubmit} className="max-w-2xl mx-auto p-4"> <div className="flex gap-2"> <input type="text" value={task} onChange={(e) => setTask(e.target.value)} placeholder="Add a new todo..." className="flex-1 px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" disabled={loading} /> <button type="submit" disabled={loading} className={`px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 ${ loading ? "opacity-50 cursor-not-allowed" : "" }`} > {loading ? "Adding..." : "Add Todo"} </button> </div> {error && <p className="mt-2 text-red-500 text-sm">{error}</p>} </form> ) } -
Crea un nuevo repositorio en GitHub
-
Crear proyecto en Vercel
-
Agregar variables de entorno en Vercel
-
Agregue URL de redireccionamiento
Más información
https://www.youtube.com/@odisealabs
https://youtu.be/T-qAtAKjqwc?si=1dHG5LjEIbh18Uby
https://supabase.com/docs/guides/getting-started/quickstarts/nextjs