├── test ├── test_helper.exs ├── live_view_patterns_web │ └── controllers │ │ ├── page_controller_test.exs │ │ ├── error_json_test.exs │ │ └── error_html_test.exs └── support │ └── conn_case.ex ├── rel └── overlays │ └── bin │ ├── server.bat │ └── server ├── priv └── static │ ├── favicon.ico │ └── robots.txt ├── .formatter.exs ├── lib ├── live_view_patterns_web │ ├── remote_data.ex │ ├── components │ │ ├── layouts │ │ │ ├── root.html.heex │ │ │ ├── app.html.heex │ │ │ └── sidebar.html.heex │ │ ├── layouts.ex │ │ └── core_components.ex │ ├── router.ex │ ├── live │ │ ├── data_loading │ │ │ ├── async_requests_live │ │ │ │ └── user_component.ex │ │ │ ├── partial_async_requests_live.ex │ │ │ ├── partial_async_requests_live │ │ │ │ └── user_component.ex │ │ │ ├── partial_async_requests_live.html.heex │ │ │ ├── async_requests_live.ex │ │ │ └── async_requests_live.html.heex │ │ └── home_live.ex │ ├── endpoint.ex │ └── telemetry.ex ├── live_view_patterns │ ├── repo.ex │ ├── task_supervisor.ex │ ├── stats.ex │ ├── users.ex │ ├── schemas │ │ ├── user │ │ │ └── stats.ex │ │ └── user.ex │ ├── repo │ │ └── seeder.ex │ └── application.ex ├── live_view_patterns.ex └── live_view_patterns_web.ex ├── assets ├── css │ └── app.css ├── js │ └── app.js └── tailwind.config.js ├── config ├── test.exs ├── prod.exs ├── config.exs ├── dev.exs └── runtime.exs ├── README.md ├── fly.toml ├── .gitignore ├── .dockerignore ├── mix.exs ├── Dockerfile └── mix.lock /test/test_helper.exs: -------------------------------------------------------------------------------- 1 | ExUnit.start() 2 | -------------------------------------------------------------------------------- /rel/overlays/bin/server.bat: -------------------------------------------------------------------------------- 1 | set PHX_SERVER=true 2 | call "%~dp0\live_view_patterns" start 3 | -------------------------------------------------------------------------------- /priv/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bigardone/live_view_patterns/HEAD/priv/static/favicon.ico -------------------------------------------------------------------------------- /rel/overlays/bin/server: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | cd -P -- "$(dirname -- "$0")" 3 | PHX_SERVER=true exec ./live_view_patterns start 4 | -------------------------------------------------------------------------------- /.formatter.exs: -------------------------------------------------------------------------------- 1 | [ 2 | import_deps: [:phoenix], 3 | plugins: [Phoenix.LiveView.HTMLFormatter], 4 | inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"] 5 | ] 6 | -------------------------------------------------------------------------------- /lib/live_view_patterns_web/remote_data.ex: -------------------------------------------------------------------------------- 1 | defmodule LiveViewPatternsWeb.RemoteData do 2 | import ExUnion 3 | 4 | defunion(not_requested | requesting(ref) | success(data) | error(reason)) 5 | end 6 | -------------------------------------------------------------------------------- /lib/live_view_patterns/repo.ex: -------------------------------------------------------------------------------- 1 | defmodule LiveViewPatterns.Repo do 2 | use Ecto.Repo, otp_app: :live_view_patterns, adapter: Etso.Adapter 3 | 4 | use Scrivener, page_size: 10, max_page_size: 100 5 | end 6 | -------------------------------------------------------------------------------- /priv/static/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /lib/live_view_patterns/task_supervisor.ex: -------------------------------------------------------------------------------- 1 | defmodule LiveViewPatterns.TaskSupervisor do 2 | @spec async(fun()) :: reference() 3 | def async(fun) do 4 | __MODULE__ 5 | |> Task.Supervisor.async(fun) 6 | |> Map.get(:ref) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /assets/css/app.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&family=Source+Code+Pro:wght@200&display=swap'); 2 | 3 | @import "tailwindcss/base"; 4 | @import "tailwindcss/components"; 5 | @import "tailwindcss/utilities"; 6 | 7 | /* This file is for your main application CSS */ 8 | -------------------------------------------------------------------------------- /test/live_view_patterns_web/controllers/page_controller_test.exs: -------------------------------------------------------------------------------- 1 | defmodule LiveViewPatternsWeb.PageControllerTest do 2 | use LiveViewPatternsWeb.ConnCase 3 | 4 | test "GET /", %{conn: conn} do 5 | conn = get(conn, ~p"/") 6 | assert html_response(conn, 200) =~ "Peace of mind from prototype to production" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/live_view_patterns/stats.ex: -------------------------------------------------------------------------------- 1 | defmodule LiveViewPatterns.Stats do 2 | import Ecto.Query 3 | 4 | alias Ecto.UUID 5 | 6 | alias LiveViewPatterns.{ 7 | Repo, 8 | Schemas.User.Stats 9 | } 10 | 11 | @spec by_user_ids([UUID.t()]) :: [Stats.t()] 12 | def by_user_ids(user_ids) do 13 | Stats 14 | |> where([s], s.user_id in ^user_ids) 15 | |> Repo.all() 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/live_view_patterns_web/controllers/error_json_test.exs: -------------------------------------------------------------------------------- 1 | defmodule LiveViewPatternsWeb.ErrorJSONTest do 2 | use LiveViewPatternsWeb.ConnCase, async: true 3 | 4 | test "renders 404" do 5 | assert LiveViewPatternsWeb.ErrorJSON.render("404.json", %{}) == %{ 6 | errors: %{detail: "Not Found"} 7 | } 8 | end 9 | 10 | test "renders 500" do 11 | assert LiveViewPatternsWeb.ErrorJSON.render("500.json", %{}) == 12 | %{errors: %{detail: "Internal Server Error"}} 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /lib/live_view_patterns/users.ex: -------------------------------------------------------------------------------- 1 | defmodule LiveViewPatterns.Users do 2 | import Ecto.Query 3 | 4 | alias LiveViewPatterns.{ 5 | Repo, 6 | Schemas.User 7 | } 8 | 9 | @spec all(with_stats: boolean()) :: [User.t()] 10 | def all(opts \\ []) do 11 | with_stats = Keyword.get(opts, :with_stats, false) 12 | 13 | from(u in User) 14 | |> preload_stats(with_stats) 15 | |> Repo.all() 16 | end 17 | 18 | defp preload_stats(q, false), do: q 19 | defp preload_stats(q, true), do: preload(q, [:stats]) 20 | end 21 | -------------------------------------------------------------------------------- /config/test.exs: -------------------------------------------------------------------------------- 1 | import Config 2 | 3 | # We don't run a server during test. If one is required, 4 | # you can enable the server option below. 5 | config :live_view_patterns, LiveViewPatternsWeb.Endpoint, 6 | http: [ip: {127, 0, 0, 1}, port: 4002], 7 | secret_key_base: "wfhdibXT6nHp0i6M2a4AW/IvRCA5y1zYcvERf3Kb6cMw+3EDVwUU/hIq/TTxW5jr", 8 | server: false 9 | 10 | # Print only warnings and errors during test 11 | config :logger, level: :warning 12 | 13 | # Initialize plugs at runtime for faster test compilation 14 | config :phoenix, :plug_init_mode, :runtime 15 | -------------------------------------------------------------------------------- /test/live_view_patterns_web/controllers/error_html_test.exs: -------------------------------------------------------------------------------- 1 | defmodule LiveViewPatternsWeb.ErrorHTMLTest do 2 | use LiveViewPatternsWeb.ConnCase, async: true 3 | 4 | # Bring render_to_string/4 for testing custom views 5 | import Phoenix.Template 6 | 7 | test "renders 404.html" do 8 | assert render_to_string(LiveViewPatternsWeb.ErrorHTML, "404", "html", []) == "Not Found" 9 | end 10 | 11 | test "renders 500.html" do 12 | assert render_to_string(LiveViewPatternsWeb.ErrorHTML, "500", "html", []) == 13 | "Internal Server Error" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /lib/live_view_patterns/schemas/user/stats.ex: -------------------------------------------------------------------------------- 1 | defmodule LiveViewPatterns.Schemas.User.Stats do 2 | use LiveViewPatterns, :schema 3 | 4 | alias __MODULE__ 5 | alias LiveViewPatterns.Schemas.User 6 | 7 | schema "stats" do 8 | field(:finished_tasks, :integer) 9 | field(:tracked_time, :integer) 10 | 11 | belongs_to(:user, User) 12 | end 13 | 14 | @fields ~w( 15 | user_id 16 | finished_tasks 17 | tracked_time 18 | )a 19 | 20 | def changeset(stats \\ %Stats{}, params) do 21 | stats 22 | |> cast(params, @fields) 23 | |> validate_required(@fields) 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/live_view_patterns_web/components/layouts/root.html.heex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | <.live_title suffix=" · LiveViewPatters"> 8 | <%= assigns[:page_title] %> 9 | 10 | 11 |