diff --git a/lib/homeman/accounts.ex b/lib/homeman/accounts.ex
new file mode 100644
index 0000000..e6ae6a2
--- /dev/null
+++ b/lib/homeman/accounts.ex
@@ -0,0 +1,296 @@
+defmodule Homeman.Accounts do
+ @moduledoc """
+ The Accounts context.
+ """
+
+ import Ecto.Query, warn: false
+ alias Homeman.Repo
+
+ alias Homeman.Accounts.User
+
+ @doc """
+ Returns the list of users.
+
+ ## Examples
+
+ iex> list_users()
+ [%User{}, ...]
+
+ """
+ def list_users do
+ Repo.all(User)
+ end
+
+ @doc """
+ Gets a single user.
+
+ Raises `Ecto.NoResultsError` if the User does not exist.
+
+ ## Examples
+
+ iex> get_user!(123)
+ %User{}
+
+ iex> get_user!(456)
+ ** (Ecto.NoResultsError)
+
+ """
+ def get_user!(id), do: Repo.get!(User, id)
+
+ @doc """
+ Creates a user.
+
+ ## Examples
+
+ iex> create_user(%{field: value})
+ {:ok, %User{}}
+
+ iex> create_user(%{field: bad_value})
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def create_user(attrs \\ %{}) do
+ %User{}
+ |> User.changeset(attrs)
+ |> Repo.insert()
+ end
+
+ @doc """
+ Updates a user.
+
+ ## Examples
+
+ iex> update_user(user, %{field: new_value})
+ {:ok, %User{}}
+
+ iex> update_user(user, %{field: bad_value})
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def update_user(%User{} = user, attrs) do
+ user
+ |> User.changeset(attrs)
+ |> Repo.update()
+ end
+
+ @doc """
+ Deletes a user.
+
+ ## Examples
+
+ iex> delete_user(user)
+ {:ok, %User{}}
+
+ iex> delete_user(user)
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def delete_user(%User{} = user) do
+ Repo.delete(user)
+ end
+
+ @doc """
+ Returns an `%Ecto.Changeset{}` for tracking user changes.
+
+ ## Examples
+
+ iex> change_user(user)
+ %Ecto.Changeset{data: %User{}}
+
+ """
+ def change_user(%User{} = user, attrs \\ %{}) do
+ User.changeset(user, attrs)
+ end
+
+ alias Homeman.Accounts.Todo
+
+ @doc """
+ Returns the list of todos.
+
+ ## Examples
+
+ iex> list_todos()
+ [%Todo{}, ...]
+
+ """
+ def list_todos do
+ Repo.all(Todo)
+ end
+
+ @doc """
+ Gets a single todo.
+
+ Raises `Ecto.NoResultsError` if the Todo does not exist.
+
+ ## Examples
+
+ iex> get_todo!(123)
+ %Todo{}
+
+ iex> get_todo!(456)
+ ** (Ecto.NoResultsError)
+
+ """
+ def get_todo!(id), do: Repo.get!(Todo, id)
+
+ @doc """
+ Creates a todo.
+
+ ## Examples
+
+ iex> create_todo(%{field: value})
+ {:ok, %Todo{}}
+
+ iex> create_todo(%{field: bad_value})
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def create_todo(attrs \\ %{}) do
+ %Todo{}
+ |> Todo.changeset(attrs)
+ |> Repo.insert()
+ end
+
+ @doc """
+ Updates a todo.
+
+ ## Examples
+
+ iex> update_todo(todo, %{field: new_value})
+ {:ok, %Todo{}}
+
+ iex> update_todo(todo, %{field: bad_value})
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def update_todo(%Todo{} = todo, attrs) do
+ todo
+ |> Todo.changeset(attrs)
+ |> Repo.update()
+ end
+
+ @doc """
+ Deletes a todo.
+
+ ## Examples
+
+ iex> delete_todo(todo)
+ {:ok, %Todo{}}
+
+ iex> delete_todo(todo)
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def delete_todo(%Todo{} = todo) do
+ Repo.delete(todo)
+ end
+
+ @doc """
+ Returns an `%Ecto.Changeset{}` for tracking todo changes.
+
+ ## Examples
+
+ iex> change_todo(todo)
+ %Ecto.Changeset{data: %Todo{}}
+
+ """
+ def change_todo(%Todo{} = todo, attrs \\ %{}) do
+ Todo.changeset(todo, attrs)
+ end
+
+ alias Homeman.Accounts.Task
+
+ @doc """
+ Returns the list of tasks.
+
+ ## Examples
+
+ iex> list_tasks()
+ [%Task{}, ...]
+
+ """
+ def list_tasks do
+ Repo.all(Task)
+ end
+
+ @doc """
+ Gets a single task.
+
+ Raises `Ecto.NoResultsError` if the Task does not exist.
+
+ ## Examples
+
+ iex> get_task!(123)
+ %Task{}
+
+ iex> get_task!(456)
+ ** (Ecto.NoResultsError)
+
+ """
+ def get_task!(id), do: Repo.get!(Task, id)
+
+ @doc """
+ Creates a task.
+
+ ## Examples
+
+ iex> create_task(%{field: value})
+ {:ok, %Task{}}
+
+ iex> create_task(%{field: bad_value})
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def create_task(attrs \\ %{}) do
+ %Task{}
+ |> Task.changeset(attrs)
+ |> Repo.insert()
+ end
+
+ @doc """
+ Updates a task.
+
+ ## Examples
+
+ iex> update_task(task, %{field: new_value})
+ {:ok, %Task{}}
+
+ iex> update_task(task, %{field: bad_value})
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def update_task(%Task{} = task, attrs) do
+ task
+ |> Task.changeset(attrs)
+ |> Repo.update()
+ end
+
+ @doc """
+ Deletes a task.
+
+ ## Examples
+
+ iex> delete_task(task)
+ {:ok, %Task{}}
+
+ iex> delete_task(task)
+ {:error, %Ecto.Changeset{}}
+
+ """
+ def delete_task(%Task{} = task) do
+ Repo.delete(task)
+ end
+
+ @doc """
+ Returns an `%Ecto.Changeset{}` for tracking task changes.
+
+ ## Examples
+
+ iex> change_task(task)
+ %Ecto.Changeset{data: %Task{}}
+
+ """
+ def change_task(%Task{} = task, attrs \\ %{}) do
+ Task.changeset(task, attrs)
+ end
+end
diff --git a/lib/homeman/accounts/task.ex b/lib/homeman/accounts/task.ex
new file mode 100644
index 0000000..043dc3b
--- /dev/null
+++ b/lib/homeman/accounts/task.ex
@@ -0,0 +1,22 @@
+defmodule Homeman.Accounts.Task do
+ use Ecto.Schema
+ import Ecto.Changeset
+
+ @primary_key {:id, :binary_id, autogenerate: true}
+ @foreign_key_type :binary_id
+ schema "tasks" do
+ field :description, :string
+ field :phase, :string
+ field :emoji, :string
+ field :completed_at, :naive_datetime
+
+ timestamps(type: :utc_datetime)
+ end
+
+ @doc false
+ def changeset(task, attrs) do
+ task
+ |> cast(attrs, [:description, :emoji, :completed_at, :phase])
+ |> validate_required([:description, :emoji, :phase])
+ end
+end
diff --git a/lib/homeman/accounts/todo.ex b/lib/homeman/accounts/todo.ex
new file mode 100644
index 0000000..6e737d3
--- /dev/null
+++ b/lib/homeman/accounts/todo.ex
@@ -0,0 +1,22 @@
+defmodule Homeman.Accounts.Todo do
+ use Ecto.Schema
+ import Ecto.Changeset
+
+ @primary_key {:id, :binary_id, autogenerate: true}
+ @foreign_key_type :binary_id
+ schema "todos" do
+ field :description, :string
+ field :emoji, :string
+ field :completed_at, :naive_datetime
+ field :assignee_user_id, :binary_id
+
+ timestamps(type: :utc_datetime)
+ end
+
+ @doc false
+ def changeset(todo, attrs) do
+ todo
+ |> cast(attrs, [:description, :emoji, :completed_at])
+ |> validate_required([:description, :emoji, :completed_at])
+ end
+end
diff --git a/lib/homeman/accounts/user.ex b/lib/homeman/accounts/user.ex
new file mode 100644
index 0000000..6f038ef
--- /dev/null
+++ b/lib/homeman/accounts/user.ex
@@ -0,0 +1,21 @@
+defmodule Homeman.Accounts.User do
+ use Ecto.Schema
+ import Ecto.Changeset
+
+ @primary_key {:id, :binary_id, autogenerate: true}
+ @foreign_key_type :binary_id
+ schema "users" do
+ field :name, :string
+ field :color, :string
+ field :avatar_url, :string
+
+ timestamps(type: :utc_datetime)
+ end
+
+ @doc false
+ def changeset(user, attrs) do
+ user
+ |> cast(attrs, [:name, :avatar_url, :color])
+ |> validate_required([:name, :avatar_url, :color])
+ end
+end
diff --git a/lib/homeman_web/live/task_live/form_component.ex b/lib/homeman_web/live/task_live/form_component.ex
new file mode 100644
index 0000000..bd407b6
--- /dev/null
+++ b/lib/homeman_web/live/task_live/form_component.ex
@@ -0,0 +1,93 @@
+defmodule HomemanWeb.TaskLive.FormComponent do
+ use HomemanWeb, :live_component
+
+ alias Homeman.Accounts
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+ <.header>
+ <%= @title %>
+ <:subtitle>Use this form to manage task records in your database.
+
+
+ <.simple_form
+ for={@form}
+ id="task-form"
+ phx-target={@myself}
+ phx-change="validate"
+ phx-submit="save"
+ >
+ <.input field={@form[:description]} type="text" label="Description" />
+ <.input field={@form[:emoji]} type="text" label="Emoji" />
+ <.input field={@form[:completed_at]} type="datetime-local" label="Completed at" />
+ <.input field={@form[:phase]} type="text" label="Phase" />
+ <:actions>
+ <.button phx-disable-with="Saving...">Save Task
+
+
+
+ """
+ end
+
+ @impl true
+ def update(%{task: task} = assigns, socket) do
+ changeset = Accounts.change_task(task)
+
+ {:ok,
+ socket
+ |> assign(assigns)
+ |> assign_form(changeset)}
+ end
+
+ @impl true
+ def handle_event("validate", %{"task" => task_params}, socket) do
+ changeset =
+ socket.assigns.task
+ |> Accounts.change_task(task_params)
+ |> Map.put(:action, :validate)
+
+ {:noreply, assign_form(socket, changeset)}
+ end
+
+ def handle_event("save", %{"task" => task_params}, socket) do
+ save_task(socket, socket.assigns.action, task_params)
+ end
+
+ defp save_task(socket, :edit, task_params) do
+ case Accounts.update_task(socket.assigns.task, task_params) do
+ {:ok, task} ->
+ notify_parent({:saved, task})
+
+ {:noreply,
+ socket
+ |> put_flash(:info, "Task updated successfully")
+ |> push_patch(to: socket.assigns.patch)}
+
+ {:error, %Ecto.Changeset{} = changeset} ->
+ {:noreply, assign_form(socket, changeset)}
+ end
+ end
+
+ defp save_task(socket, :new, task_params) do
+ case Accounts.create_task(task_params) do
+ {:ok, task} ->
+ notify_parent({:saved, task})
+
+ {:noreply,
+ socket
+ |> put_flash(:info, "Task created successfully")
+ |> push_patch(to: socket.assigns.patch)}
+
+ {:error, %Ecto.Changeset{} = changeset} ->
+ {:noreply, assign_form(socket, changeset)}
+ end
+ end
+
+ defp assign_form(socket, %Ecto.Changeset{} = changeset) do
+ assign(socket, :form, to_form(changeset))
+ end
+
+ defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
+end
diff --git a/lib/homeman_web/live/task_live/index.ex b/lib/homeman_web/live/task_live/index.ex
new file mode 100644
index 0000000..1fb7908
--- /dev/null
+++ b/lib/homeman_web/live/task_live/index.ex
@@ -0,0 +1,47 @@
+defmodule HomemanWeb.TaskLive.Index do
+ use HomemanWeb, :live_view
+
+ alias Homeman.Accounts
+ alias Homeman.Accounts.Task
+
+ @impl true
+ def mount(_params, _session, socket) do
+ {:ok, stream(socket, :tasks, Accounts.list_tasks())}
+ end
+
+ @impl true
+ def handle_params(params, _url, socket) do
+ {:noreply, apply_action(socket, socket.assigns.live_action, params)}
+ end
+
+ defp apply_action(socket, :edit, %{"id" => id}) do
+ socket
+ |> assign(:page_title, "Edit Task")
+ |> assign(:task, Accounts.get_task!(id))
+ end
+
+ defp apply_action(socket, :new, _params) do
+ socket
+ |> assign(:page_title, "New Task")
+ |> assign(:task, %Task{})
+ end
+
+ defp apply_action(socket, :index, _params) do
+ socket
+ |> assign(:page_title, "Listing Tasks")
+ |> assign(:task, nil)
+ end
+
+ @impl true
+ def handle_info({HomemanWeb.TaskLive.FormComponent, {:saved, task}}, socket) do
+ {:noreply, stream_insert(socket, :tasks, task)}
+ end
+
+ @impl true
+ def handle_event("delete", %{"id" => id}, socket) do
+ task = Accounts.get_task!(id)
+ {:ok, _} = Accounts.delete_task(task)
+
+ {:noreply, stream_delete(socket, :tasks, task)}
+ end
+end
diff --git a/lib/homeman_web/live/task_live/index.html.heex b/lib/homeman_web/live/task_live/index.html.heex
new file mode 100644
index 0000000..3634cf0
--- /dev/null
+++ b/lib/homeman_web/live/task_live/index.html.heex
@@ -0,0 +1,44 @@
+<.header>
+ Listing Tasks
+ <:actions>
+ <.link patch={~p"/tasks/new"}>
+ <.button>New Task
+
+
+
+
+<.table
+ id="tasks"
+ rows={@streams.tasks}
+ row_click={fn {_id, task} -> JS.navigate(~p"/tasks/#{task}") end}
+>
+ <:col :let={{_id, task}} label="Description"><%= task.description %>
+ <:col :let={{_id, task}} label="Emoji"><%= task.emoji %>
+ <:col :let={{_id, task}} label="Completed at"><%= task.completed_at %>
+ <:col :let={{_id, task}} label="Phase"><%= task.phase %>
+ <:action :let={{_id, task}}>
+
+ <.link navigate={~p"/tasks/#{task}"}>Show
+
+ <.link patch={~p"/tasks/#{task}/edit"}>Edit
+
+ <:action :let={{id, task}}>
+ <.link
+ phx-click={JS.push("delete", value: %{id: task.id}) |> hide("##{id}")}
+ data-confirm="Are you sure?"
+ >
+ Delete
+
+
+
+
+<.modal :if={@live_action in [:new, :edit]} id="task-modal" show on_cancel={JS.patch(~p"/tasks")}>
+ <.live_component
+ module={HomemanWeb.TaskLive.FormComponent}
+ id={@task.id || :new}
+ title={@page_title}
+ action={@live_action}
+ task={@task}
+ patch={~p"/tasks"}
+ />
+
diff --git a/lib/homeman_web/live/task_live/show.ex b/lib/homeman_web/live/task_live/show.ex
new file mode 100644
index 0000000..7dda5f9
--- /dev/null
+++ b/lib/homeman_web/live/task_live/show.ex
@@ -0,0 +1,21 @@
+defmodule HomemanWeb.TaskLive.Show do
+ use HomemanWeb, :live_view
+
+ alias Homeman.Accounts
+
+ @impl true
+ def mount(_params, _session, socket) do
+ {:ok, socket}
+ end
+
+ @impl true
+ def handle_params(%{"id" => id}, _, socket) do
+ {:noreply,
+ socket
+ |> assign(:page_title, page_title(socket.assigns.live_action))
+ |> assign(:task, Accounts.get_task!(id))}
+ end
+
+ defp page_title(:show), do: "Show Task"
+ defp page_title(:edit), do: "Edit Task"
+end
diff --git a/lib/homeman_web/live/task_live/show.html.heex b/lib/homeman_web/live/task_live/show.html.heex
new file mode 100644
index 0000000..68cafb7
--- /dev/null
+++ b/lib/homeman_web/live/task_live/show.html.heex
@@ -0,0 +1,29 @@
+<.header>
+ Task <%= @task.id %>
+ <:subtitle>This is a task record from your database.
+ <:actions>
+ <.link patch={~p"/tasks/#{@task}/show/edit"} phx-click={JS.push_focus()}>
+ <.button>Edit task
+
+
+
+
+<.list>
+ <:item title="Description"><%= @task.description %>
+ <:item title="Emoji"><%= @task.emoji %>
+ <:item title="Completed at"><%= @task.completed_at %>
+ <:item title="Phase"><%= @task.phase %>
+
+
+<.back navigate={~p"/tasks"}>Back to tasks
+
+<.modal :if={@live_action == :edit} id="task-modal" show on_cancel={JS.patch(~p"/tasks/#{@task}")}>
+ <.live_component
+ module={HomemanWeb.TaskLive.FormComponent}
+ id={@task.id}
+ title={@page_title}
+ action={@live_action}
+ task={@task}
+ patch={~p"/tasks/#{@task}"}
+ />
+
diff --git a/lib/homeman_web/live/todo_live/form_component.ex b/lib/homeman_web/live/todo_live/form_component.ex
new file mode 100644
index 0000000..84560b7
--- /dev/null
+++ b/lib/homeman_web/live/todo_live/form_component.ex
@@ -0,0 +1,92 @@
+defmodule HomemanWeb.TodoLive.FormComponent do
+ use HomemanWeb, :live_component
+
+ alias Homeman.Accounts
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+ <.header>
+ <%= @title %>
+ <:subtitle>Use this form to manage todo records in your database.
+
+
+ <.simple_form
+ for={@form}
+ id="todo-form"
+ phx-target={@myself}
+ phx-change="validate"
+ phx-submit="save"
+ >
+ <.input field={@form[:description]} type="text" label="Description" />
+ <.input field={@form[:emoji]} type="text" label="Emoji" />
+ <.input field={@form[:completed_at]} type="datetime-local" label="Completed at" />
+ <:actions>
+ <.button phx-disable-with="Saving...">Save Todo
+
+
+
+ """
+ end
+
+ @impl true
+ def update(%{todo: todo} = assigns, socket) do
+ changeset = Accounts.change_todo(todo)
+
+ {:ok,
+ socket
+ |> assign(assigns)
+ |> assign_form(changeset)}
+ end
+
+ @impl true
+ def handle_event("validate", %{"todo" => todo_params}, socket) do
+ changeset =
+ socket.assigns.todo
+ |> Accounts.change_todo(todo_params)
+ |> Map.put(:action, :validate)
+
+ {:noreply, assign_form(socket, changeset)}
+ end
+
+ def handle_event("save", %{"todo" => todo_params}, socket) do
+ save_todo(socket, socket.assigns.action, todo_params)
+ end
+
+ defp save_todo(socket, :edit, todo_params) do
+ case Accounts.update_todo(socket.assigns.todo, todo_params) do
+ {:ok, todo} ->
+ notify_parent({:saved, todo})
+
+ {:noreply,
+ socket
+ |> put_flash(:info, "Todo updated successfully")
+ |> push_patch(to: socket.assigns.patch)}
+
+ {:error, %Ecto.Changeset{} = changeset} ->
+ {:noreply, assign_form(socket, changeset)}
+ end
+ end
+
+ defp save_todo(socket, :new, todo_params) do
+ case Accounts.create_todo(todo_params) do
+ {:ok, todo} ->
+ notify_parent({:saved, todo})
+
+ {:noreply,
+ socket
+ |> put_flash(:info, "Todo created successfully")
+ |> push_patch(to: socket.assigns.patch)}
+
+ {:error, %Ecto.Changeset{} = changeset} ->
+ {:noreply, assign_form(socket, changeset)}
+ end
+ end
+
+ defp assign_form(socket, %Ecto.Changeset{} = changeset) do
+ assign(socket, :form, to_form(changeset))
+ end
+
+ defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
+end
diff --git a/lib/homeman_web/live/todo_live/index.ex b/lib/homeman_web/live/todo_live/index.ex
new file mode 100644
index 0000000..78d664d
--- /dev/null
+++ b/lib/homeman_web/live/todo_live/index.ex
@@ -0,0 +1,47 @@
+defmodule HomemanWeb.TodoLive.Index do
+ use HomemanWeb, :live_view
+
+ alias Homeman.Accounts
+ alias Homeman.Accounts.Todo
+
+ @impl true
+ def mount(_params, _session, socket) do
+ {:ok, stream(socket, :todos, Accounts.list_todos())}
+ end
+
+ @impl true
+ def handle_params(params, _url, socket) do
+ {:noreply, apply_action(socket, socket.assigns.live_action, params)}
+ end
+
+ defp apply_action(socket, :edit, %{"id" => id}) do
+ socket
+ |> assign(:page_title, "Edit Todo")
+ |> assign(:todo, Accounts.get_todo!(id))
+ end
+
+ defp apply_action(socket, :new, _params) do
+ socket
+ |> assign(:page_title, "New Todo")
+ |> assign(:todo, %Todo{})
+ end
+
+ defp apply_action(socket, :index, _params) do
+ socket
+ |> assign(:page_title, "Listing Todos")
+ |> assign(:todo, nil)
+ end
+
+ @impl true
+ def handle_info({HomemanWeb.TodoLive.FormComponent, {:saved, todo}}, socket) do
+ {:noreply, stream_insert(socket, :todos, todo)}
+ end
+
+ @impl true
+ def handle_event("delete", %{"id" => id}, socket) do
+ todo = Accounts.get_todo!(id)
+ {:ok, _} = Accounts.delete_todo(todo)
+
+ {:noreply, stream_delete(socket, :todos, todo)}
+ end
+end
diff --git a/lib/homeman_web/live/todo_live/index.html.heex b/lib/homeman_web/live/todo_live/index.html.heex
new file mode 100644
index 0000000..fcbefc2
--- /dev/null
+++ b/lib/homeman_web/live/todo_live/index.html.heex
@@ -0,0 +1,43 @@
+<.header>
+ Listing Todos
+ <:actions>
+ <.link patch={~p"/todos/new"}>
+ <.button>New Todo
+
+
+
+
+<.table
+ id="todos"
+ rows={@streams.todos}
+ row_click={fn {_id, todo} -> JS.navigate(~p"/todos/#{todo}") end}
+>
+ <:col :let={{_id, todo}} label="Description"><%= todo.description %>
+ <:col :let={{_id, todo}} label="Emoji"><%= todo.emoji %>
+ <:col :let={{_id, todo}} label="Completed at"><%= todo.completed_at %>
+ <:action :let={{_id, todo}}>
+
+ <.link navigate={~p"/todos/#{todo}"}>Show
+
+ <.link patch={~p"/todos/#{todo}/edit"}>Edit
+
+ <:action :let={{id, todo}}>
+ <.link
+ phx-click={JS.push("delete", value: %{id: todo.id}) |> hide("##{id}")}
+ data-confirm="Are you sure?"
+ >
+ Delete
+
+
+
+
+<.modal :if={@live_action in [:new, :edit]} id="todo-modal" show on_cancel={JS.patch(~p"/todos")}>
+ <.live_component
+ module={HomemanWeb.TodoLive.FormComponent}
+ id={@todo.id || :new}
+ title={@page_title}
+ action={@live_action}
+ todo={@todo}
+ patch={~p"/todos"}
+ />
+
diff --git a/lib/homeman_web/live/todo_live/show.ex b/lib/homeman_web/live/todo_live/show.ex
new file mode 100644
index 0000000..1543ea8
--- /dev/null
+++ b/lib/homeman_web/live/todo_live/show.ex
@@ -0,0 +1,21 @@
+defmodule HomemanWeb.TodoLive.Show do
+ use HomemanWeb, :live_view
+
+ alias Homeman.Accounts
+
+ @impl true
+ def mount(_params, _session, socket) do
+ {:ok, socket}
+ end
+
+ @impl true
+ def handle_params(%{"id" => id}, _, socket) do
+ {:noreply,
+ socket
+ |> assign(:page_title, page_title(socket.assigns.live_action))
+ |> assign(:todo, Accounts.get_todo!(id))}
+ end
+
+ defp page_title(:show), do: "Show Todo"
+ defp page_title(:edit), do: "Edit Todo"
+end
diff --git a/lib/homeman_web/live/todo_live/show.html.heex b/lib/homeman_web/live/todo_live/show.html.heex
new file mode 100644
index 0000000..0724fd3
--- /dev/null
+++ b/lib/homeman_web/live/todo_live/show.html.heex
@@ -0,0 +1,28 @@
+<.header>
+ Todo <%= @todo.id %>
+ <:subtitle>This is a todo record from your database.
+ <:actions>
+ <.link patch={~p"/todos/#{@todo}/show/edit"} phx-click={JS.push_focus()}>
+ <.button>Edit todo
+
+
+
+
+<.list>
+ <:item title="Description"><%= @todo.description %>
+ <:item title="Emoji"><%= @todo.emoji %>
+ <:item title="Completed at"><%= @todo.completed_at %>
+
+
+<.back navigate={~p"/todos"}>Back to todos
+
+<.modal :if={@live_action == :edit} id="todo-modal" show on_cancel={JS.patch(~p"/todos/#{@todo}")}>
+ <.live_component
+ module={HomemanWeb.TodoLive.FormComponent}
+ id={@todo.id}
+ title={@page_title}
+ action={@live_action}
+ todo={@todo}
+ patch={~p"/todos/#{@todo}"}
+ />
+
diff --git a/lib/homeman_web/live/user_live/form_component.ex b/lib/homeman_web/live/user_live/form_component.ex
new file mode 100644
index 0000000..1f57b5a
--- /dev/null
+++ b/lib/homeman_web/live/user_live/form_component.ex
@@ -0,0 +1,92 @@
+defmodule HomemanWeb.UserLive.FormComponent do
+ use HomemanWeb, :live_component
+
+ alias Homeman.Accounts
+
+ @impl true
+ def render(assigns) do
+ ~H"""
+
+ <.header>
+ <%= @title %>
+ <:subtitle>Use this form to manage user records in your database.
+
+
+ <.simple_form
+ for={@form}
+ id="user-form"
+ phx-target={@myself}
+ phx-change="validate"
+ phx-submit="save"
+ >
+ <.input field={@form[:name]} type="text" label="Name" />
+ <.input field={@form[:avatar_url]} type="text" label="Avatar url" />
+ <.input field={@form[:color]} type="text" label="Color" />
+ <:actions>
+ <.button phx-disable-with="Saving...">Save User
+
+
+
+ """
+ end
+
+ @impl true
+ def update(%{user: user} = assigns, socket) do
+ changeset = Accounts.change_user(user)
+
+ {:ok,
+ socket
+ |> assign(assigns)
+ |> assign_form(changeset)}
+ end
+
+ @impl true
+ def handle_event("validate", %{"user" => user_params}, socket) do
+ changeset =
+ socket.assigns.user
+ |> Accounts.change_user(user_params)
+ |> Map.put(:action, :validate)
+
+ {:noreply, assign_form(socket, changeset)}
+ end
+
+ def handle_event("save", %{"user" => user_params}, socket) do
+ save_user(socket, socket.assigns.action, user_params)
+ end
+
+ defp save_user(socket, :edit, user_params) do
+ case Accounts.update_user(socket.assigns.user, user_params) do
+ {:ok, user} ->
+ notify_parent({:saved, user})
+
+ {:noreply,
+ socket
+ |> put_flash(:info, "User updated successfully")
+ |> push_patch(to: socket.assigns.patch)}
+
+ {:error, %Ecto.Changeset{} = changeset} ->
+ {:noreply, assign_form(socket, changeset)}
+ end
+ end
+
+ defp save_user(socket, :new, user_params) do
+ case Accounts.create_user(user_params) do
+ {:ok, user} ->
+ notify_parent({:saved, user})
+
+ {:noreply,
+ socket
+ |> put_flash(:info, "User created successfully")
+ |> push_patch(to: socket.assigns.patch)}
+
+ {:error, %Ecto.Changeset{} = changeset} ->
+ {:noreply, assign_form(socket, changeset)}
+ end
+ end
+
+ defp assign_form(socket, %Ecto.Changeset{} = changeset) do
+ assign(socket, :form, to_form(changeset))
+ end
+
+ defp notify_parent(msg), do: send(self(), {__MODULE__, msg})
+end
diff --git a/lib/homeman_web/live/user_live/index.ex b/lib/homeman_web/live/user_live/index.ex
new file mode 100644
index 0000000..2057dd0
--- /dev/null
+++ b/lib/homeman_web/live/user_live/index.ex
@@ -0,0 +1,47 @@
+defmodule HomemanWeb.UserLive.Index do
+ use HomemanWeb, :live_view
+
+ alias Homeman.Accounts
+ alias Homeman.Accounts.User
+
+ @impl true
+ def mount(_params, _session, socket) do
+ {:ok, stream(socket, :users, Accounts.list_users())}
+ end
+
+ @impl true
+ def handle_params(params, _url, socket) do
+ {:noreply, apply_action(socket, socket.assigns.live_action, params)}
+ end
+
+ defp apply_action(socket, :edit, %{"id" => id}) do
+ socket
+ |> assign(:page_title, "Edit User")
+ |> assign(:user, Accounts.get_user!(id))
+ end
+
+ defp apply_action(socket, :new, _params) do
+ socket
+ |> assign(:page_title, "New User")
+ |> assign(:user, %User{})
+ end
+
+ defp apply_action(socket, :index, _params) do
+ socket
+ |> assign(:page_title, "Listing Users")
+ |> assign(:user, nil)
+ end
+
+ @impl true
+ def handle_info({HomemanWeb.UserLive.FormComponent, {:saved, user}}, socket) do
+ {:noreply, stream_insert(socket, :users, user)}
+ end
+
+ @impl true
+ def handle_event("delete", %{"id" => id}, socket) do
+ user = Accounts.get_user!(id)
+ {:ok, _} = Accounts.delete_user(user)
+
+ {:noreply, stream_delete(socket, :users, user)}
+ end
+end
diff --git a/lib/homeman_web/live/user_live/index.html.heex b/lib/homeman_web/live/user_live/index.html.heex
new file mode 100644
index 0000000..e98a2d4
--- /dev/null
+++ b/lib/homeman_web/live/user_live/index.html.heex
@@ -0,0 +1,43 @@
+<.header>
+ Listing Users
+ <:actions>
+ <.link patch={~p"/users/new"}>
+ <.button>New User
+
+
+
+
+<.table
+ id="users"
+ rows={@streams.users}
+ row_click={fn {_id, user} -> JS.navigate(~p"/users/#{user}") end}
+>
+ <:col :let={{_id, user}} label="Name"><%= user.name %>
+ <:col :let={{_id, user}} label="Avatar url"><%= user.avatar_url %>
+ <:col :let={{_id, user}} label="Color"><%= user.color %>
+ <:action :let={{_id, user}}>
+
+ <.link navigate={~p"/users/#{user}"}>Show
+
+ <.link patch={~p"/users/#{user}/edit"}>Edit
+
+ <:action :let={{id, user}}>
+ <.link
+ phx-click={JS.push("delete", value: %{id: user.id}) |> hide("##{id}")}
+ data-confirm="Are you sure?"
+ >
+ Delete
+
+
+
+
+<.modal :if={@live_action in [:new, :edit]} id="user-modal" show on_cancel={JS.patch(~p"/users")}>
+ <.live_component
+ module={HomemanWeb.UserLive.FormComponent}
+ id={@user.id || :new}
+ title={@page_title}
+ action={@live_action}
+ user={@user}
+ patch={~p"/users"}
+ />
+
diff --git a/lib/homeman_web/live/user_live/show.ex b/lib/homeman_web/live/user_live/show.ex
new file mode 100644
index 0000000..f99759f
--- /dev/null
+++ b/lib/homeman_web/live/user_live/show.ex
@@ -0,0 +1,21 @@
+defmodule HomemanWeb.UserLive.Show do
+ use HomemanWeb, :live_view
+
+ alias Homeman.Accounts
+
+ @impl true
+ def mount(_params, _session, socket) do
+ {:ok, socket}
+ end
+
+ @impl true
+ def handle_params(%{"id" => id}, _, socket) do
+ {:noreply,
+ socket
+ |> assign(:page_title, page_title(socket.assigns.live_action))
+ |> assign(:user, Accounts.get_user!(id))}
+ end
+
+ defp page_title(:show), do: "Show User"
+ defp page_title(:edit), do: "Edit User"
+end
diff --git a/lib/homeman_web/live/user_live/show.html.heex b/lib/homeman_web/live/user_live/show.html.heex
new file mode 100644
index 0000000..9e5d0a7
--- /dev/null
+++ b/lib/homeman_web/live/user_live/show.html.heex
@@ -0,0 +1,28 @@
+<.header>
+ User <%= @user.id %>
+ <:subtitle>This is a user record from your database.
+ <:actions>
+ <.link patch={~p"/users/#{@user}/show/edit"} phx-click={JS.push_focus()}>
+ <.button>Edit user
+
+
+
+
+<.list>
+ <:item title="Name"><%= @user.name %>
+ <:item title="Avatar url"><%= @user.avatar_url %>
+ <:item title="Color"><%= @user.color %>
+
+
+<.back navigate={~p"/users"}>Back to users
+
+<.modal :if={@live_action == :edit} id="user-modal" show on_cancel={JS.patch(~p"/users/#{@user}")}>
+ <.live_component
+ module={HomemanWeb.UserLive.FormComponent}
+ id={@user.id}
+ title={@page_title}
+ action={@live_action}
+ user={@user}
+ patch={~p"/users/#{@user}"}
+ />
+
diff --git a/lib/homeman_web/router.ex b/lib/homeman_web/router.ex
index 23d5651..07d7ce8 100644
--- a/lib/homeman_web/router.ex
+++ b/lib/homeman_web/router.ex
@@ -18,6 +18,29 @@ defmodule HomemanWeb.Router do
pipe_through :browser
get "/", PageController, :home
+
+ live "/tasks", TaskLive.Index, :index
+ live "/tasks/new", TaskLive.Index, :new
+ live "/tasks/:id/edit", TaskLive.Index, :edit
+
+ live "/tasks/:id", TaskLive.Show, :show
+ live "/tasks/:id/show/edit", TaskLive.Show, :edit
+
+
+ live "/todos", TodoLive.Index, :index
+ live "/todos/new", TodoLive.Index, :new
+ live "/todos/:id/edit", TodoLive.Index, :edit
+
+ live "/todos/:id", TodoLive.Show, :show
+ live "/todos/:id/show/edit", TodoLive.Show, :edit
+
+
+ live "/users", UserLive.Index, :index
+ live "/users/new", UserLive.Index, :new
+ live "/users/:id/edit", UserLive.Index, :edit
+
+ live "/users/:id", UserLive.Show, :show
+ live "/users/:id/show/edit", UserLive.Show, :edit
end
# Other scopes may use custom stacks.
diff --git a/priv/repo/migrations/20240122053553_create_users.exs b/priv/repo/migrations/20240122053553_create_users.exs
new file mode 100644
index 0000000..72a145a
--- /dev/null
+++ b/priv/repo/migrations/20240122053553_create_users.exs
@@ -0,0 +1,14 @@
+defmodule Homeman.Repo.Migrations.CreateUsers do
+ use Ecto.Migration
+
+ def change do
+ create table(:users, primary_key: false) do
+ add :id, :binary_id, primary_key: true
+ add :name, :string
+ add :avatar_url, :string, null: true
+ add :color, :string
+
+ timestamps(type: :utc_datetime)
+ end
+ end
+end
diff --git a/priv/repo/migrations/20240122054006_create_todos.exs b/priv/repo/migrations/20240122054006_create_todos.exs
new file mode 100644
index 0000000..0b7a11a
--- /dev/null
+++ b/priv/repo/migrations/20240122054006_create_todos.exs
@@ -0,0 +1,17 @@
+defmodule Homeman.Repo.Migrations.CreateTodos do
+ use Ecto.Migration
+
+ def change do
+ create table(:todos, primary_key: false) do
+ add :id, :binary_id, primary_key: true
+ add :description, :string
+ add :emoji, :string, null: true
+ add :completed_at, :naive_datetime, null: true
+ add :assignee_user_id, references(:users, on_delete: :nothing, type: :binary_id), null: true
+
+ timestamps(type: :utc_datetime)
+ end
+
+ create index(:todos, [:assignee_user_id])
+ end
+end
diff --git a/priv/repo/migrations/20240122054053_create_tasks.exs b/priv/repo/migrations/20240122054053_create_tasks.exs
new file mode 100644
index 0000000..fe4b28c
--- /dev/null
+++ b/priv/repo/migrations/20240122054053_create_tasks.exs
@@ -0,0 +1,15 @@
+defmodule Homeman.Repo.Migrations.CreateTasks do
+ use Ecto.Migration
+
+ def change do
+ create table(:tasks, primary_key: false) do
+ add :id, :binary_id, primary_key: true
+ add :description, :string
+ add :emoji, :string, null: true
+ add :completed_at, :naive_datetime, null: true
+ add :phase, :string
+
+ timestamps(type: :utc_datetime)
+ end
+ end
+end
diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs
index babb004..6d6611e 100644
--- a/priv/repo/seeds.exs
+++ b/priv/repo/seeds.exs
@@ -9,3 +9,9 @@
#
# We recommend using the bang functions (`insert!`, `update!`
# and so on) as they will fail if something goes wrong.
+
+%{
+ name: "Daddy",
+ color: "#00aaff",
+ avatar_url: "/uploads/space.png",
+}
diff --git a/test/homeman/accounts_test.exs b/test/homeman/accounts_test.exs
new file mode 100644
index 0000000..a497bde
--- /dev/null
+++ b/test/homeman/accounts_test.exs
@@ -0,0 +1,181 @@
+defmodule Homeman.AccountsTest do
+ use Homeman.DataCase
+
+ alias Homeman.Accounts
+
+ describe "users" do
+ alias Homeman.Accounts.User
+
+ import Homeman.AccountsFixtures
+
+ @invalid_attrs %{name: nil, color: nil, avatar_url: nil}
+
+ test "list_users/0 returns all users" do
+ user = user_fixture()
+ assert Accounts.list_users() == [user]
+ end
+
+ test "get_user!/1 returns the user with given id" do
+ user = user_fixture()
+ assert Accounts.get_user!(user.id) == user
+ end
+
+ test "create_user/1 with valid data creates a user" do
+ valid_attrs = %{name: "some name", color: "some color", avatar_url: "some avatar_url"}
+
+ assert {:ok, %User{} = user} = Accounts.create_user(valid_attrs)
+ assert user.name == "some name"
+ assert user.color == "some color"
+ assert user.avatar_url == "some avatar_url"
+ end
+
+ test "create_user/1 with invalid data returns error changeset" do
+ assert {:error, %Ecto.Changeset{}} = Accounts.create_user(@invalid_attrs)
+ end
+
+ test "update_user/2 with valid data updates the user" do
+ user = user_fixture()
+ update_attrs = %{name: "some updated name", color: "some updated color", avatar_url: "some updated avatar_url"}
+
+ assert {:ok, %User{} = user} = Accounts.update_user(user, update_attrs)
+ assert user.name == "some updated name"
+ assert user.color == "some updated color"
+ assert user.avatar_url == "some updated avatar_url"
+ end
+
+ test "update_user/2 with invalid data returns error changeset" do
+ user = user_fixture()
+ assert {:error, %Ecto.Changeset{}} = Accounts.update_user(user, @invalid_attrs)
+ assert user == Accounts.get_user!(user.id)
+ end
+
+ test "delete_user/1 deletes the user" do
+ user = user_fixture()
+ assert {:ok, %User{}} = Accounts.delete_user(user)
+ assert_raise Ecto.NoResultsError, fn -> Accounts.get_user!(user.id) end
+ end
+
+ test "change_user/1 returns a user changeset" do
+ user = user_fixture()
+ assert %Ecto.Changeset{} = Accounts.change_user(user)
+ end
+ end
+
+ describe "todos" do
+ alias Homeman.Accounts.Todo
+
+ import Homeman.AccountsFixtures
+
+ @invalid_attrs %{description: nil, emoji: nil, completed_at: nil}
+
+ test "list_todos/0 returns all todos" do
+ todo = todo_fixture()
+ assert Accounts.list_todos() == [todo]
+ end
+
+ test "get_todo!/1 returns the todo with given id" do
+ todo = todo_fixture()
+ assert Accounts.get_todo!(todo.id) == todo
+ end
+
+ test "create_todo/1 with valid data creates a todo" do
+ valid_attrs = %{description: "some description", emoji: "some emoji", completed_at: ~N[2024-01-21 05:40:00]}
+
+ assert {:ok, %Todo{} = todo} = Accounts.create_todo(valid_attrs)
+ assert todo.description == "some description"
+ assert todo.emoji == "some emoji"
+ assert todo.completed_at == ~N[2024-01-21 05:40:00]
+ end
+
+ test "create_todo/1 with invalid data returns error changeset" do
+ assert {:error, %Ecto.Changeset{}} = Accounts.create_todo(@invalid_attrs)
+ end
+
+ test "update_todo/2 with valid data updates the todo" do
+ todo = todo_fixture()
+ update_attrs = %{description: "some updated description", emoji: "some updated emoji", completed_at: ~N[2024-01-22 05:40:00]}
+
+ assert {:ok, %Todo{} = todo} = Accounts.update_todo(todo, update_attrs)
+ assert todo.description == "some updated description"
+ assert todo.emoji == "some updated emoji"
+ assert todo.completed_at == ~N[2024-01-22 05:40:00]
+ end
+
+ test "update_todo/2 with invalid data returns error changeset" do
+ todo = todo_fixture()
+ assert {:error, %Ecto.Changeset{}} = Accounts.update_todo(todo, @invalid_attrs)
+ assert todo == Accounts.get_todo!(todo.id)
+ end
+
+ test "delete_todo/1 deletes the todo" do
+ todo = todo_fixture()
+ assert {:ok, %Todo{}} = Accounts.delete_todo(todo)
+ assert_raise Ecto.NoResultsError, fn -> Accounts.get_todo!(todo.id) end
+ end
+
+ test "change_todo/1 returns a todo changeset" do
+ todo = todo_fixture()
+ assert %Ecto.Changeset{} = Accounts.change_todo(todo)
+ end
+ end
+
+ describe "tasks" do
+ alias Homeman.Accounts.Task
+
+ import Homeman.AccountsFixtures
+
+ @invalid_attrs %{description: nil, phase: nil, emoji: nil, completed_at: nil}
+
+ test "list_tasks/0 returns all tasks" do
+ task = task_fixture()
+ assert Accounts.list_tasks() == [task]
+ end
+
+ test "get_task!/1 returns the task with given id" do
+ task = task_fixture()
+ assert Accounts.get_task!(task.id) == task
+ end
+
+ test "create_task/1 with valid data creates a task" do
+ valid_attrs = %{description: "some description", phase: "some phase", emoji: "some emoji", completed_at: ~N[2024-01-21 05:40:00]}
+
+ assert {:ok, %Task{} = task} = Accounts.create_task(valid_attrs)
+ assert task.description == "some description"
+ assert task.phase == "some phase"
+ assert task.emoji == "some emoji"
+ assert task.completed_at == ~N[2024-01-21 05:40:00]
+ end
+
+ test "create_task/1 with invalid data returns error changeset" do
+ assert {:error, %Ecto.Changeset{}} = Accounts.create_task(@invalid_attrs)
+ end
+
+ test "update_task/2 with valid data updates the task" do
+ task = task_fixture()
+ update_attrs = %{description: "some updated description", phase: "some updated phase", emoji: "some updated emoji", completed_at: ~N[2024-01-22 05:40:00]}
+
+ assert {:ok, %Task{} = task} = Accounts.update_task(task, update_attrs)
+ assert task.description == "some updated description"
+ assert task.phase == "some updated phase"
+ assert task.emoji == "some updated emoji"
+ assert task.completed_at == ~N[2024-01-22 05:40:00]
+ end
+
+ test "update_task/2 with invalid data returns error changeset" do
+ task = task_fixture()
+ assert {:error, %Ecto.Changeset{}} = Accounts.update_task(task, @invalid_attrs)
+ assert task == Accounts.get_task!(task.id)
+ end
+
+ test "delete_task/1 deletes the task" do
+ task = task_fixture()
+ assert {:ok, %Task{}} = Accounts.delete_task(task)
+ assert_raise Ecto.NoResultsError, fn -> Accounts.get_task!(task.id) end
+ end
+
+ test "change_task/1 returns a task changeset" do
+ task = task_fixture()
+ assert %Ecto.Changeset{} = Accounts.change_task(task)
+ end
+ end
+end
diff --git a/test/homeman_web/live/task_live_test.exs b/test/homeman_web/live/task_live_test.exs
new file mode 100644
index 0000000..34ad5e7
--- /dev/null
+++ b/test/homeman_web/live/task_live_test.exs
@@ -0,0 +1,113 @@
+defmodule HomemanWeb.TaskLiveTest do
+ use HomemanWeb.ConnCase
+
+ import Phoenix.LiveViewTest
+ import Homeman.AccountsFixtures
+
+ @create_attrs %{description: "some description", phase: "some phase", emoji: "some emoji", completed_at: "2024-01-21T05:40:00"}
+ @update_attrs %{description: "some updated description", phase: "some updated phase", emoji: "some updated emoji", completed_at: "2024-01-22T05:40:00"}
+ @invalid_attrs %{description: nil, phase: nil, emoji: nil, completed_at: nil}
+
+ defp create_task(_) do
+ task = task_fixture()
+ %{task: task}
+ end
+
+ describe "Index" do
+ setup [:create_task]
+
+ test "lists all tasks", %{conn: conn, task: task} do
+ {:ok, _index_live, html} = live(conn, ~p"/tasks")
+
+ assert html =~ "Listing Tasks"
+ assert html =~ task.description
+ end
+
+ test "saves new task", %{conn: conn} do
+ {:ok, index_live, _html} = live(conn, ~p"/tasks")
+
+ assert index_live |> element("a", "New Task") |> render_click() =~
+ "New Task"
+
+ assert_patch(index_live, ~p"/tasks/new")
+
+ assert index_live
+ |> form("#task-form", task: @invalid_attrs)
+ |> render_change() =~ "can't be blank"
+
+ assert index_live
+ |> form("#task-form", task: @create_attrs)
+ |> render_submit()
+
+ assert_patch(index_live, ~p"/tasks")
+
+ html = render(index_live)
+ assert html =~ "Task created successfully"
+ assert html =~ "some description"
+ end
+
+ test "updates task in listing", %{conn: conn, task: task} do
+ {:ok, index_live, _html} = live(conn, ~p"/tasks")
+
+ assert index_live |> element("#tasks-#{task.id} a", "Edit") |> render_click() =~
+ "Edit Task"
+
+ assert_patch(index_live, ~p"/tasks/#{task}/edit")
+
+ assert index_live
+ |> form("#task-form", task: @invalid_attrs)
+ |> render_change() =~ "can't be blank"
+
+ assert index_live
+ |> form("#task-form", task: @update_attrs)
+ |> render_submit()
+
+ assert_patch(index_live, ~p"/tasks")
+
+ html = render(index_live)
+ assert html =~ "Task updated successfully"
+ assert html =~ "some updated description"
+ end
+
+ test "deletes task in listing", %{conn: conn, task: task} do
+ {:ok, index_live, _html} = live(conn, ~p"/tasks")
+
+ assert index_live |> element("#tasks-#{task.id} a", "Delete") |> render_click()
+ refute has_element?(index_live, "#tasks-#{task.id}")
+ end
+ end
+
+ describe "Show" do
+ setup [:create_task]
+
+ test "displays task", %{conn: conn, task: task} do
+ {:ok, _show_live, html} = live(conn, ~p"/tasks/#{task}")
+
+ assert html =~ "Show Task"
+ assert html =~ task.description
+ end
+
+ test "updates task within modal", %{conn: conn, task: task} do
+ {:ok, show_live, _html} = live(conn, ~p"/tasks/#{task}")
+
+ assert show_live |> element("a", "Edit") |> render_click() =~
+ "Edit Task"
+
+ assert_patch(show_live, ~p"/tasks/#{task}/show/edit")
+
+ assert show_live
+ |> form("#task-form", task: @invalid_attrs)
+ |> render_change() =~ "can't be blank"
+
+ assert show_live
+ |> form("#task-form", task: @update_attrs)
+ |> render_submit()
+
+ assert_patch(show_live, ~p"/tasks/#{task}")
+
+ html = render(show_live)
+ assert html =~ "Task updated successfully"
+ assert html =~ "some updated description"
+ end
+ end
+end
diff --git a/test/homeman_web/live/todo_live_test.exs b/test/homeman_web/live/todo_live_test.exs
new file mode 100644
index 0000000..0ce5150
--- /dev/null
+++ b/test/homeman_web/live/todo_live_test.exs
@@ -0,0 +1,113 @@
+defmodule HomemanWeb.TodoLiveTest do
+ use HomemanWeb.ConnCase
+
+ import Phoenix.LiveViewTest
+ import Homeman.AccountsFixtures
+
+ @create_attrs %{description: "some description", emoji: "some emoji", completed_at: "2024-01-21T05:40:00"}
+ @update_attrs %{description: "some updated description", emoji: "some updated emoji", completed_at: "2024-01-22T05:40:00"}
+ @invalid_attrs %{description: nil, emoji: nil, completed_at: nil}
+
+ defp create_todo(_) do
+ todo = todo_fixture()
+ %{todo: todo}
+ end
+
+ describe "Index" do
+ setup [:create_todo]
+
+ test "lists all todos", %{conn: conn, todo: todo} do
+ {:ok, _index_live, html} = live(conn, ~p"/todos")
+
+ assert html =~ "Listing Todos"
+ assert html =~ todo.description
+ end
+
+ test "saves new todo", %{conn: conn} do
+ {:ok, index_live, _html} = live(conn, ~p"/todos")
+
+ assert index_live |> element("a", "New Todo") |> render_click() =~
+ "New Todo"
+
+ assert_patch(index_live, ~p"/todos/new")
+
+ assert index_live
+ |> form("#todo-form", todo: @invalid_attrs)
+ |> render_change() =~ "can't be blank"
+
+ assert index_live
+ |> form("#todo-form", todo: @create_attrs)
+ |> render_submit()
+
+ assert_patch(index_live, ~p"/todos")
+
+ html = render(index_live)
+ assert html =~ "Todo created successfully"
+ assert html =~ "some description"
+ end
+
+ test "updates todo in listing", %{conn: conn, todo: todo} do
+ {:ok, index_live, _html} = live(conn, ~p"/todos")
+
+ assert index_live |> element("#todos-#{todo.id} a", "Edit") |> render_click() =~
+ "Edit Todo"
+
+ assert_patch(index_live, ~p"/todos/#{todo}/edit")
+
+ assert index_live
+ |> form("#todo-form", todo: @invalid_attrs)
+ |> render_change() =~ "can't be blank"
+
+ assert index_live
+ |> form("#todo-form", todo: @update_attrs)
+ |> render_submit()
+
+ assert_patch(index_live, ~p"/todos")
+
+ html = render(index_live)
+ assert html =~ "Todo updated successfully"
+ assert html =~ "some updated description"
+ end
+
+ test "deletes todo in listing", %{conn: conn, todo: todo} do
+ {:ok, index_live, _html} = live(conn, ~p"/todos")
+
+ assert index_live |> element("#todos-#{todo.id} a", "Delete") |> render_click()
+ refute has_element?(index_live, "#todos-#{todo.id}")
+ end
+ end
+
+ describe "Show" do
+ setup [:create_todo]
+
+ test "displays todo", %{conn: conn, todo: todo} do
+ {:ok, _show_live, html} = live(conn, ~p"/todos/#{todo}")
+
+ assert html =~ "Show Todo"
+ assert html =~ todo.description
+ end
+
+ test "updates todo within modal", %{conn: conn, todo: todo} do
+ {:ok, show_live, _html} = live(conn, ~p"/todos/#{todo}")
+
+ assert show_live |> element("a", "Edit") |> render_click() =~
+ "Edit Todo"
+
+ assert_patch(show_live, ~p"/todos/#{todo}/show/edit")
+
+ assert show_live
+ |> form("#todo-form", todo: @invalid_attrs)
+ |> render_change() =~ "can't be blank"
+
+ assert show_live
+ |> form("#todo-form", todo: @update_attrs)
+ |> render_submit()
+
+ assert_patch(show_live, ~p"/todos/#{todo}")
+
+ html = render(show_live)
+ assert html =~ "Todo updated successfully"
+ assert html =~ "some updated description"
+ end
+ end
+end
diff --git a/test/homeman_web/live/user_live_test.exs b/test/homeman_web/live/user_live_test.exs
new file mode 100644
index 0000000..427c2ac
--- /dev/null
+++ b/test/homeman_web/live/user_live_test.exs
@@ -0,0 +1,113 @@
+defmodule HomemanWeb.UserLiveTest do
+ use HomemanWeb.ConnCase
+
+ import Phoenix.LiveViewTest
+ import Homeman.AccountsFixtures
+
+ @create_attrs %{name: "some name", color: "some color", avatar_url: "some avatar_url"}
+ @update_attrs %{name: "some updated name", color: "some updated color", avatar_url: "some updated avatar_url"}
+ @invalid_attrs %{name: nil, color: nil, avatar_url: nil}
+
+ defp create_user(_) do
+ user = user_fixture()
+ %{user: user}
+ end
+
+ describe "Index" do
+ setup [:create_user]
+
+ test "lists all users", %{conn: conn, user: user} do
+ {:ok, _index_live, html} = live(conn, ~p"/users")
+
+ assert html =~ "Listing Users"
+ assert html =~ user.name
+ end
+
+ test "saves new user", %{conn: conn} do
+ {:ok, index_live, _html} = live(conn, ~p"/users")
+
+ assert index_live |> element("a", "New User") |> render_click() =~
+ "New User"
+
+ assert_patch(index_live, ~p"/users/new")
+
+ assert index_live
+ |> form("#user-form", user: @invalid_attrs)
+ |> render_change() =~ "can't be blank"
+
+ assert index_live
+ |> form("#user-form", user: @create_attrs)
+ |> render_submit()
+
+ assert_patch(index_live, ~p"/users")
+
+ html = render(index_live)
+ assert html =~ "User created successfully"
+ assert html =~ "some name"
+ end
+
+ test "updates user in listing", %{conn: conn, user: user} do
+ {:ok, index_live, _html} = live(conn, ~p"/users")
+
+ assert index_live |> element("#users-#{user.id} a", "Edit") |> render_click() =~
+ "Edit User"
+
+ assert_patch(index_live, ~p"/users/#{user}/edit")
+
+ assert index_live
+ |> form("#user-form", user: @invalid_attrs)
+ |> render_change() =~ "can't be blank"
+
+ assert index_live
+ |> form("#user-form", user: @update_attrs)
+ |> render_submit()
+
+ assert_patch(index_live, ~p"/users")
+
+ html = render(index_live)
+ assert html =~ "User updated successfully"
+ assert html =~ "some updated name"
+ end
+
+ test "deletes user in listing", %{conn: conn, user: user} do
+ {:ok, index_live, _html} = live(conn, ~p"/users")
+
+ assert index_live |> element("#users-#{user.id} a", "Delete") |> render_click()
+ refute has_element?(index_live, "#users-#{user.id}")
+ end
+ end
+
+ describe "Show" do
+ setup [:create_user]
+
+ test "displays user", %{conn: conn, user: user} do
+ {:ok, _show_live, html} = live(conn, ~p"/users/#{user}")
+
+ assert html =~ "Show User"
+ assert html =~ user.name
+ end
+
+ test "updates user within modal", %{conn: conn, user: user} do
+ {:ok, show_live, _html} = live(conn, ~p"/users/#{user}")
+
+ assert show_live |> element("a", "Edit") |> render_click() =~
+ "Edit User"
+
+ assert_patch(show_live, ~p"/users/#{user}/show/edit")
+
+ assert show_live
+ |> form("#user-form", user: @invalid_attrs)
+ |> render_change() =~ "can't be blank"
+
+ assert show_live
+ |> form("#user-form", user: @update_attrs)
+ |> render_submit()
+
+ assert_patch(show_live, ~p"/users/#{user}")
+
+ html = render(show_live)
+ assert html =~ "User updated successfully"
+ assert html =~ "some updated name"
+ end
+ end
+end
diff --git a/test/support/fixtures/accounts_fixtures.ex b/test/support/fixtures/accounts_fixtures.ex
new file mode 100644
index 0000000..2d15fb7
--- /dev/null
+++ b/test/support/fixtures/accounts_fixtures.ex
@@ -0,0 +1,55 @@
+defmodule Homeman.AccountsFixtures do
+ @moduledoc """
+ This module defines test helpers for creating
+ entities via the `Homeman.Accounts` context.
+ """
+
+ @doc """
+ Generate a user.
+ """
+ def user_fixture(attrs \\ %{}) do
+ {:ok, user} =
+ attrs
+ |> Enum.into(%{
+ avatar_url: "some avatar_url",
+ color: "some color",
+ name: "some name"
+ })
+ |> Homeman.Accounts.create_user()
+
+ user
+ end
+
+ @doc """
+ Generate a todo.
+ """
+ def todo_fixture(attrs \\ %{}) do
+ {:ok, todo} =
+ attrs
+ |> Enum.into(%{
+ completed_at: ~N[2024-01-21 05:40:00],
+ description: "some description",
+ emoji: "some emoji"
+ })
+ |> Homeman.Accounts.create_todo()
+
+ todo
+ end
+
+ @doc """
+ Generate a task.
+ """
+ def task_fixture(attrs \\ %{}) do
+ {:ok, task} =
+ attrs
+ |> Enum.into(%{
+ completed_at: ~N[2024-01-21 05:40:00],
+ description: "some description",
+ emoji: "some emoji",
+ phase: "some phase"
+ })
+ |> Homeman.Accounts.create_task()
+
+ task
+ end
+end