diff --git a/friends/assets/js/app.js b/friends/assets/js/app.js index 9457ec5..00e577f 100644 --- a/friends/assets/js/app.js +++ b/friends/assets/js/app.js @@ -64,7 +64,7 @@ window.selectMapResult = function (latlon, display) { }; window.selectRelation = function (id, name) { - var e = new Event('eventSender'); + var e = new Event('selectRelation'); e.data = { id: id, name: name @@ -72,18 +72,56 @@ window.selectRelation = function (id, name) { window.dispatchEvent(e); } +window.deleteRelation = function (id) { + var e = new Event('deleteRelation'); + e.data = { + id: id + } + + window.dispatchEvent(e); +} + +window.relationType = function (rel_id, type) { + var e = new Event('relationType'); + e.data = { + rel_id: rel_id, + type: type + } + window.dispatchEvent(e); +} + + Hooks.NewRelation = { mounted() { var list_el = document.querySelector("div#relationships"); - window.addEventListener("eventSender", e => { - this.pushEvent("select_relation", e.data, function (reply) { + window.addEventListener("selectRelation", e => { + this.pushEvent("phx:select_relation", e.data, function (reply) { console.log(reply); }) }) + } +} + +Hooks.RelationshipCard = { + mounted() { + console.log("Mounted card for relationship " + this.el.getAttribute("relationship-id")); + + window.addEventListener("deleteRelation", e => { + this.pushEvent("phx:delete_relation", e.data, function (reply) { + console.log(reply); + }) + }) + + window.addEventListener("relationType", e => { + this.pushEvent("phx:relation_type", e.data, function (reply) { + document.querySelector("#type-selector-" + e.data.rel_id).hidden = true; + }) + }) } } + Hooks.showMapbox = { initMap() { mapboxgl.accessToken = 'pk.eyJ1IjoicnlhbnBhbmR5YSIsImEiOiJja3psM2tlcDA1MXl1Mm9uZmo5bGxpNzdxIn0.TwBKpTTypcD5fWFc8XRyHg'; diff --git a/friends/lib/friends/relationship.ex b/friends/lib/friends/relationship.ex index 1eee859..3b8a76a 100644 --- a/friends/lib/friends/relationship.ex +++ b/friends/lib/friends/relationship.ex @@ -14,7 +14,7 @@ defmodule Friends.Relationship do has_many(:events, Friends.Event) end - @attrs [:friend_id, :relation_id] + @attrs [:friend_id, :relation_id, :type] def types(index) do types() |> elem(index) @@ -24,11 +24,11 @@ defmodule Friends.Relationship do # Tuple: name of the type, associated color, and what that person "is" to the other { {:self, :hidden, :self}, - {:acquaintances, :info, nil}, + {:acquaintances, :info, :known}, {:family, :primary, :relative}, {:friends, :secondary, :friend}, {:partners, :info, :partner}, - {:dating, :success, :date}, + {:dating, :success, :dating}, {:engaged, :success, :fiancé}, {:married, :success, :spouse}, {:divorced, :error, :ex}, @@ -37,6 +37,16 @@ defmodule Friends.Relationship do } end + def type_index(type) do + types() + |> Tuple.to_list() + |> Enum.find_index( + &(&1 + |> elem(0) + |> to_string() == type) + ) + end + def get_type(rel) do rel.type |> types |> elem(0) end @@ -57,9 +67,27 @@ defmodule Friends.Relationship do ) end + def validate_type(%{changes: %{type: type}} = changeset) do + if type |> is_integer() and type >= 0 and type < types() |> Tuple.to_list() |> length do + changeset + else + changeset |> Ecto.Changeset.add_error(:type, "Invalid type") + end + end + + def validate_type(changeset), do: changeset + + def update(rel, params \\ %{}) do + rel + |> changeset(params) + |> Map.put(:action, :update) + |> @repo.update!() + end + def changeset(struct, params \\ %{}) do struct |> Ecto.Changeset.cast(params, @attrs) + |> validate_type |> Ecto.Changeset.unique_constraint( [:friend_id, :relation_id], name: :relationships_friend_id_relation_id_index @@ -122,8 +150,29 @@ defmodule Friends.Relationship do end end + def get_by_id(id) do + case @repo.one( + from(r in Relationship, + where: r.id == ^id + ) + ) do + nil -> nil + rel -> rel |> load_preloads() + end + end + + def delete(rel) do + rel |> Friends.Repo.delete!() + end + def delete(a, b) do - get(a, b) |> Friends.Repo.delete!() + get(a, b) |> delete + end + + def change_type(rel, type) do + rel + |> changeset(%{type: type}) + |> update() end def get_by_slugs([slug1, slug2]) do @@ -141,13 +190,19 @@ defmodule Friends.Relationship do end def age(relationship) do - relationship.events - |> Enum.map(fn event -> - Date.diff(Date.utc_today(), event.date) - end) - |> Enum.sort() - |> List.last() - |> div(365) + case relationship.events do + [] -> + nil + + e -> + e + |> Enum.map(fn event -> + Date.diff(Date.utc_today(), event.date) + end) + |> Enum.sort() + |> List.last() + |> div(365) + end end def load_events( diff --git a/friends/lib/friends_web.ex b/friends/lib/friends_web.ex index ccfbcbd..d1999b4 100644 --- a/friends/lib/friends_web.ex +++ b/friends/lib/friends_web.ex @@ -24,6 +24,10 @@ defmodule FriendsWeb do import Plug.Conn import FriendsWeb.Gettext alias FriendsWeb.Router.Helpers, as: Routes + + alias Friends.{Friend, Relationship} + alias Friends.Accounts.User + import Helpers end end @@ -48,6 +52,15 @@ defmodule FriendsWeb do use Phoenix.LiveView, layout: {FriendsWeb.LayoutView, "live.html"} + alias FriendsWeb.Router.Helpers, as: Routes + + import Helpers + import Helpers.Names + import FriendsWeb.LiveHelpers + import FriendsWeb.Components + + alias Friends.{Friend, Relationship, Places} + unquote(view_helpers()) end end @@ -55,6 +68,14 @@ defmodule FriendsWeb do def live_component do quote do use Phoenix.LiveComponent + import Helpers + import FriendsWeb.LiveHelpers + alias Friends.{Friend, Relationship, Places} + alias FriendsWeb.Components.{Autocomplete, Map, Cards} + alias FriendsWeb.Components + alias FriendsWeb.Router.Helpers, as: Routes + alias FriendsWeb.LiveViews + alias Phoenix.LiveView.JS unquote(view_helpers()) end @@ -63,117 +84,8 @@ defmodule FriendsWeb do def component do quote do use Phoenix.Component - - unquote(view_helpers()) - end - end - - def router do - quote do - use Phoenix.Router - import Phoenix.Component - import Plug.Conn - import Phoenix.Controller - import Phoenix.LiveView.Router - end - end - - def channel do - quote do - use Phoenix.Channel - import FriendsWeb.Gettext - end - end - - defp view_helpers do - quote do - # Use all HTML functionality (forms, tags, etc) - use Phoenix.HTML - - # Import LiveView and .heex helpers (live_render, live_patch, <.form>, etc) - import Phoenix.LiveView.Helpers - - # Import basic rendering functionality (render, render_layout, etc) - import Phoenix.View - - import FriendsWeb.ErrorHelpers - import FriendsWeb.Gettext - alias FriendsWeb.Router.Helpers, as: Routes - end - end - - @doc """ - When used, dispatch to the appropriate controller/view/etc. - """ - defmacro __using__(which) when is_atom(which) do - apply(__MODULE__, which, []) - end -end -defmodule FriendsWeb do - @moduledoc """ - The entrypoint for defining your web interface, such - as controllers, views, channels and so on. - - This can be used in your application as: - - use FriendsWeb, :controller - use FriendsWeb, :view - - The definitions below will be executed for every view, - controller, etc, so keep them short and clean, focused - on imports, uses and aliases. - - Do NOT define functions inside the quoted expressions - below. Instead, define any helper function in modules - and import those modules here. - """ - - def controller do - quote do - use Phoenix.Controller, namespace: FriendsWeb - - import Plug.Conn - import FriendsWeb.Gettext - alias FriendsWeb.Router.Helpers, as: Routes - end - end - - def view do - quote do - use Phoenix.View, - root: "lib/friends_web/templates", - namespace: FriendsWeb - - # Import convenience functions from controllers - import Phoenix.Controller, - only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1] - - import Phoenix.Component - # Include shared imports and aliases for views - unquote(view_helpers()) - end - end - - def live_view do - quote do - use Phoenix.LiveView, - layout: {FriendsWeb.LayoutView, "live.html"} - - unquote(view_helpers()) - end - end - - def live_component do - quote do - use Phoenix.LiveComponent - - unquote(view_helpers()) - end - end - - def component do - quote do - use Phoenix.Component + import Helpers + import FriendsWeb.LiveHelpers unquote(view_helpers()) end diff --git a/friends/lib/friends_web/controllers/friends_controller.ex b/friends/lib/friends_web/controllers/friends_controller.ex index 8ed8cdf..7e530ab 100644 --- a/friends/lib/friends_web/controllers/friends_controller.ex +++ b/friends/lib/friends_web/controllers/friends_controller.ex @@ -1,8 +1,5 @@ defmodule FriendsWeb.FriendsController do use FriendsWeb, :controller - alias Friends.{Friend, Relationship} - alias Friends.Accounts.User - import Helpers def index(conn, _params) do conn diff --git a/friends/lib/friends_web/controllers/relationships_controller.ex b/friends/lib/friends_web/controllers/relationships_controller.ex new file mode 100644 index 0000000..7f857c2 --- /dev/null +++ b/friends/lib/friends_web/controllers/relationships_controller.ex @@ -0,0 +1,9 @@ +defmodule FriendsWeb.RelationshipsController do + use FriendsWeb, :controller + + def delete(id) do + rel = Relationship.get_by_id(id) + + IO.inspect("Deleting #{rel}") + end +end diff --git a/friends/lib/friends_web/live/components/cards.ex b/friends/lib/friends_web/live/components/cards.ex index 865b891..b301727 100644 --- a/friends/lib/friends_web/live/components/cards.ex +++ b/friends/lib/friends_web/live/components/cards.ex @@ -1,25 +1,43 @@ defmodule FriendsWeb.Components.Cards do use FriendsWeb, :live_component + import Helpers.Names + def relationship_card(assigns) do ~H""" -
-
{@relation.id}
+
+ + + <%= if @editable do %> + + + <% end %> + +
{@relation.id}
-

- <.link navigate={Routes.friends_show_path(FriendsWeb.Endpoint, :overview, @relation.slug)} class=""><%=@relation.name%> - <%= if @relationship |> Friends.Relationship.get_relation do %> -
Friends.Relationship.get_color}"}><%= @relationship |> Friends.Relationship.get_relation %>
- <% end %> -

-

If a dog chews shoes whose shoes does he choose?

+
+

<.link navigate={Routes.friends_show_path(FriendsWeb.Endpoint, :overview, @relation.slug)} class="no-underline font-bold hover:underline"><%=@relation.name%>

+ <.link patch={Routes.relationship_show_path(FriendsWeb.Endpoint, :overview, @friend.slug, @relation.slug)}>(details) + +
+
- - <%= if @editable do %> - Delete - <% end %> - +
+ + """ end + + def confirm_dialog(id) do + JS.show(to: "#warning-relation-#{id}") + end end diff --git a/friends/lib/friends_web/live/components/components.ex b/friends/lib/friends_web/live/components/components.ex index 8aba494..7a5e5c7 100644 --- a/friends/lib/friends_web/live/components/components.ex +++ b/friends/lib/friends_web/live/components/components.ex @@ -1,11 +1,5 @@ -defmodule FriendsWeb.FriendsLive.Components do +defmodule FriendsWeb.Components do use FriendsWeb, :live_component - use Phoenix.HTML - import Helpers - import FriendsWeb.LiveHelpers - alias Friends.Friend - alias FriendsWeb.Components.{Autocomplete, Map, Cards} - alias Phoenix.LiveView.JS def header(assigns) do ~H""" @@ -44,233 +38,73 @@ defmodule FriendsWeb.FriendsLive.Components do """ end - def show_page(:main, assigns), do: show_page(:overview, %{assigns | live_action: :overview}) - - def show_page(:overview, assigns) do + def relationship_details(assigns) do ~H""" - <%= if @address_latlon != "null" do %> - +
+ - def show_page(:timeline, assigns) do - ~H""" -
- <%= for event <- @friend |> Friends.Friend.get_events do %> -
    -
  • - <%= event.name %> | - <%= event.date |> format_date %> -
  • -
- <% end %> - <%= if @friend |> Friends.Friend.get_events |> Enum.empty? do %> -
No events on record yet.
- <% end %> + since + + <%= if @relationship |> Relationship.age do %> + <%= + @relationship |> Relationship.age + %> + <% else %> +
Relationship.get_color}"} data-tip="add milestone dates on the detail page."> + (no date) +
+ <% end %>
""" end - def show_page(:relationships, assigns) do + def relationship_type_selector(assigns) do ~H""" -
- <%= for relation <- @relationships do %> - <% relationship = relation(@friend, relation) %> - - <% end %> - <%= if @relationships |> Enum.empty? do %> -
No relationships on record yet.
- <% end %> -
+ + """ + end + + def relationship_type_function(id, type) do + """ + relationType("#{id}", "#{type}") """ end ### - def edit_page(:welcome, assigns) do - top = ~H""" -

Welcome!

-

Before we get started, we just need some basic info about you:

- <%= edit_page(:overview, assigns) %> - """ - end - - def edit_page(:overview, assigns) do - ~H""" - <.form - for={@changeset} - let={f} - action={@action} - phx_change= "validate" - phx_submit= "save" - > - <%= hidden_input f, :id, value: @friend.id %> -
- <%= text_input f, :name, placeholder: "Full Name", - class: "m-0 p-0 pb-2 pl-2 input input-bordered border-dashed", - style: "color: var(--tw-prose-headings); - font-weight: 800; - font-size: 2.25em; - min-width: 50%; - text-indent: 4px; - line-height: 1.1111111;", - value: @friend.name, - phx_debounce: :blur %> -
<%= error_tag f, :name %>
-
- - -
    -
  • - Email: -
    - <%= text_input f, :email, class: "input input-primary input-sm md:input-md input-disabled", phx_debounce: "blur", value: @friend.email %> -
    <%= error_tag f, :email %>
    -
    -
  • - <%= if @live_action != :welcome do %> -
  • - Nickname: -
    <%= text_input f, :nickname, class: "input input-primary input-sm md:input-md", phx_debounce: "blur", value: @friend.nickname %>
    -
  • - <% end %> -
  • - Birthday: -
    - <%= date_input f, :born, class: "input input-primary input-sm md:input-md", phx_debounce: "blur", value: @friend.born %> -
    <%= error_tag f, :born %>
    -
    -
  • -
  • - Phone: -
    - <%= text_input f, :phone, class: "input input-primary input-sm md:input-md", phx_debounce: "blur", value: @friend.phone |> FriendsWeb.LiveHelpers.display_phone(@changeset) %> -
    <%= error_tag f, :phone %>
    -
    -
  • -
  • - Address: -
    - <%= text_input f, :search_query, value: @search_query, - class: "input input-primary input-sm md:input-md", - phx_debounce: "500", - phx_change: :address_search, - phx_click: JS.show(to: "#search-results"), - phx_blur: JS.hide(to: "#search-results"), - autocomplete: "name" %> - <%= hidden_input f, :address_latlon, value: @address_latlon, - id: "address-latlon", autocomplete: "latlon", - phx_change: "validate" - %> -
    - -
  • -
-
- <%= if @live_action != :welcome do %> -
- <.link patch={Routes.friends_show_path(FriendsWeb.Endpoint, :overview, @friend.slug)} class="btn btn-block btn-outline">back -
- <% end %> -
- <%= if @changeset.valid? do %> - <%= submit "Save", phx_disable_with: "Saving...", class: "btn btn-block" %> - <% else %> - <%= submit "Save", class: "btn btn-block btn-disabled" %> - <% end %> -
- <%= if @live_action != :welcome and @current_user.profile.id == @friend.id do %> -
- <.link href={Routes.user_settings_path(FriendsWeb.Endpoint, :edit)} class="btn btn-block btn-error">Delete -
- <% end %> -
- - """ - end - - def edit_page(:relationships, assigns) do - ~H""" - <%= show_page(:relationships, assigns) %> - - <.form for={@changeset} let={f}> -
    -
  • - Type a name: -
    - <%= text_input f, :search_query, value: @search_query, - class: "input input-primary input-sm md:input-md", - phx_debounce: "500", - phx_change: :relation_search, - phx_click: JS.show(to: "#search-results"), - phx_blur: JS.hide(to: "#search-results"), - autocomplete: "name" %> - <%= hidden_input f, :relation_id, value: @relation_id, - id: "relation-id", autocomplete: "relation-id", - phx_change: "validate" - %> - -
    -
  • -
-
- <%= if @live_action != :welcome do %> -
- <.link patch={Routes.friends_show_path(FriendsWeb.Endpoint, :overview, @friend.slug)} class="btn btn-block btn-outline">back -
- <% end %> - -
- - """ - end - - def edit_page(:timeline, assigns) do - ~H""" - """ - end end diff --git a/friends/lib/friends_web/live/edit.ex b/friends/lib/friends_web/live/edit.ex index e962678..ba74a4d 100644 --- a/friends/lib/friends_web/live/edit.ex +++ b/friends/lib/friends_web/live/edit.ex @@ -1,11 +1,5 @@ defmodule FriendsWeb.FriendsLive.Edit do use FriendsWeb, :live_view - import FriendsWeb.LiveHelpers - import FriendsWeb.FriendsLive.Components - import Helpers - import Helpers.Names - - alias Friends.{Friend, Places} # No slug means it's a new profile form def mount(%{}, token, socket) do @@ -33,6 +27,7 @@ defmodule FriendsWeb.FriendsLive.Edit do if(live_action) do {:ok, socket + |> assign(:mode, :edit) |> assign(:live_action, live_action) |> assign_current_user(token |> Map.get("user_token")) |> assign(:friend, friend) @@ -62,6 +57,7 @@ defmodule FriendsWeb.FriendsLive.Edit do {:noreply, socket + |> assign(:mode, :edit) |> assign_friend(friend) |> assign(:action, Routes.friends_path(socket, :update)) |> assign(:live_action, live_action) @@ -85,6 +81,7 @@ defmodule FriendsWeb.FriendsLive.Edit do {:noreply, socket + |> assign(:mode, :edit) |> assign_friend(friend) |> assign(:relationships, friend |> relations) |> assign(:live_action, live_action) @@ -92,7 +89,10 @@ defmodule FriendsWeb.FriendsLive.Edit do |> assign(:relation_id, nil) |> assign(:search_results, nil) |> assign(:editable, editable) - |> title(friend.name <> " - " <> (live_action |> titlecase))} + |> title(friend.name <> " - " <> (live_action |> titlecase)) + |> push_navigate( + to: Routes.friends_show_path(FriendsWeb.Endpoint, :relationships, friend.slug) + )} end # Timeline edit @@ -107,6 +107,7 @@ defmodule FriendsWeb.FriendsLive.Edit do {:noreply, socket + |> assign(:mode, :edit) |> assign_friend(friend) |> assign(:live_action, live_action) |> assign(:search_query, nil) @@ -237,46 +238,6 @@ defmodule FriendsWeb.FriendsLive.Edit do end end - def handle_event( - "relation_search", - %{"friend" => %{"search_query" => query}}, - %{assigns: %{friend: friend}} = socket - ) do - if query == "" do - {:noreply, socket |> assign(:search_results, nil)} - else - results = - (Friend.Search.autocomplete(query, friend) ++ - [Friend.new(%{name: query})]) - |> Enum.map(&Friend.Search.parse_result/1) - - {:noreply, - socket - |> assign(:search_results, results) - |> assign(:search_query, query) - |> assign(:select_fxn, "selectRelation")} - end - end - - def handle_event( - "select_relation", - %{"id" => rel_id, "name" => rel_name}, - %{assigns: %{friend: friend, relationships: relationships}} = socket - ) do - new_rel = - case rel_id do - "new" -> Friend.create(%{name: rel_name}) - _num -> Friend.get_by_id(rel_id |> String.to_integer()) - end - - [updated_friend, updated_relation] = friend |> Friend.create_relationship(new_rel) - - {:noreply, - socket - |> assign_friend(updated_friend) - |> assign(:relationships, updated_friend |> relations)} - end - def handle_event(_event, _unsigned_params, socket) do {:noreply, socket} end diff --git a/friends/lib/friends_web/live/edit.html.heex b/friends/lib/friends_web/live/edit.html.heex index 585d692..3c36fc2 100644 --- a/friends/lib/friends_web/live/edit.html.heex +++ b/friends/lib/friends_web/live/edit.html.heex @@ -1,6 +1,6 @@
<%= edit_menu(assigns) %> - <%= edit_page(@live_action, assigns) %> + <%= apply(FriendsWeb.LiveViews.Edit, @live_action, [assigns]) %>
diff --git a/friends/lib/friends_web/live/friend.ex b/friends/lib/friends_web/live/friend.ex index 7080d10..2f8b6b7 100644 --- a/friends/lib/friends_web/live/friend.ex +++ b/friends/lib/friends_web/live/friend.ex @@ -1,11 +1,5 @@ defmodule FriendsWeb.FriendsLive.Friend do use FriendsWeb, :live_view - alias FriendsWeb.Router.Helpers, as: Routes - alias Friends.Friend - - import FriendsWeb.LiveHelpers - import Helpers - import Helpers.Names # Initialize variables on first load def mount(%{}, token, socket) do diff --git a/friends/lib/friends_web/live/live_views/edit.ex b/friends/lib/friends_web/live/live_views/edit.ex new file mode 100644 index 0000000..aceee13 --- /dev/null +++ b/friends/lib/friends_web/live/live_views/edit.ex @@ -0,0 +1,121 @@ +defmodule FriendsWeb.LiveViews.Edit do + use FriendsWeb, :live_component + + def welcome(assigns) do + top = ~H""" +

Welcome!

+

Before we get started, we just need some basic info about you:

+ <%= overview(assigns) %> + """ + end + + def overview(assigns) do + ~H""" + <.form + for={@changeset} + let={f} + action={@action} + phx_change= "validate" + phx_submit= "save" + > + <%= hidden_input f, :id, value: @friend.id %> +
+ <%= text_input f, :name, placeholder: "Full Name", + class: "m-0 p-0 pb-2 pl-2 input input-bordered border-dashed", + style: "color: var(--tw-prose-headings); + font-weight: 800; + font-size: 2.25em; + min-width: 50%; + text-indent: 4px; + line-height: 1.1111111;", + value: @friend.name, + phx_debounce: :blur %> +
<%= error_tag f, :name %>
+
+ + +
    +
  • + Email: +
    + <%= text_input f, :email, class: "input input-primary input-sm md:input-md input-disabled", phx_debounce: "blur", value: @friend.email %> +
    <%= error_tag f, :email %>
    +
    +
  • + <%= if @live_action != :welcome do %> +
  • + Nickname: +
    <%= text_input f, :nickname, class: "input input-primary input-sm md:input-md", phx_debounce: "blur", value: @friend.nickname %>
    +
  • + <% end %> +
  • + Birthday: +
    + <%= date_input f, :born, class: "input input-primary input-sm md:input-md", phx_debounce: "blur", value: @friend.born %> +
    <%= error_tag f, :born %>
    +
    +
  • +
  • + Phone: +
    + <%= text_input f, :phone, class: "input input-primary input-sm md:input-md", phx_debounce: "blur", value: @friend.phone |> FriendsWeb.LiveHelpers.display_phone(@changeset) %> +
    <%= error_tag f, :phone %>
    +
    +
  • +
  • + Address: +
    + <%= text_input f, :search_query, value: @search_query, + class: "input input-primary input-sm md:input-md", + phx_debounce: "500", + phx_change: :address_search, + phx_click: JS.show(to: "#search-results"), + phx_blur: JS.hide(to: "#search-results"), + autocomplete: "name" %> + <%= hidden_input f, :address_latlon, value: @address_latlon, + id: "address-latlon", autocomplete: "latlon", + phx_change: "validate" + %> +
    + +
  • +
+
+ <%= if @live_action != :welcome do %> +
+ <.link patch={Routes.friends_show_path(FriendsWeb.Endpoint, :overview, @friend.slug)} class="btn btn-block btn-outline">back +
+ <% end %> +
+ <%= if @changeset.valid? do %> + <%= submit "Save", phx_disable_with: "Saving...", class: "btn btn-block" %> + <% else %> + <%= submit "Save", class: "btn btn-block btn-disabled" %> + <% end %> +
+ <%= if @live_action != :welcome and @current_user.profile.id == @friend.id do %> +
+ <.link href={Routes.user_settings_path(FriendsWeb.Endpoint, :edit)} class="btn btn-block btn-error">Delete +
+ <% end %> +
+ + """ + end + + def relationships(assigns) do + # Just for illustration; this will never run + # as it's redirected via FriendsWeb.FriendsLive.Edit's + # handle_params function + FriendsWeb.LiveViews.Show.relationships(assigns) + end + + def timeline(assigns) do + ~H""" + """ + end +end diff --git a/friends/lib/friends_web/live/live_views/show.ex b/friends/lib/friends_web/live/live_views/show.ex new file mode 100644 index 0000000..7eb68f8 --- /dev/null +++ b/friends/lib/friends_web/live/live_views/show.ex @@ -0,0 +1,115 @@ +defmodule FriendsWeb.LiveViews.Show do + use FriendsWeb, :live_component + alias FriendsWeb.Components.Cards + + def main(assigns), do: overview(%{assigns | live_action: :overview}) + + def overview(assigns) do + ~H""" + <%= if @address_latlon != "null" do %> + + <% end %> +
    +
  • + Nickname: +
    + <%= if is_nil(@friend.nickname) do %> + none + <% else %> + <%= @friend.nickname %> + <% end %> +
    +
  • +
  • + Birthday: +
    <%= @friend.born |> Calendar.strftime("%B %d, %Y") %> +
    + (<%= @friend |> Friend.age %> years old) +
    +
  • +
  • + Email: +
    <%= @friend.email %>
    +
  • +
  • + Phone: +
    <%= @friend.phone %>
    +
  • +
  • + Address: + <%= if @address_latlon == "null" do %> + none + <% else %> +
    <%= @address %>
    + <% end %> + +
  • +
+ """ + end + + def relationships(assigns) do + ~H""" +
+ <%= for relation <- @relationships do %> + <% relationship = relation(@friend, relation) %> + + <% end %> + <%= if @relationships |> Enum.empty? do %> +
No relationships on record yet.
+ <% end %> +
+ <%= if @editable do %> + <.form for={@changeset} let={f} class="border-t-4"> +
    +
  • + Type a name: +
    + <%= text_input f, :search_query, value: @search_query, + class: "input input-primary input-sm md:input-md", + phx_debounce: "500", + phx_change: :relation_search, + phx_click: JS.show(to: "#search-results"), + phx_blur: JS.hide(to: "#search-results"), + autocomplete: "name" %> + <%= hidden_input f, :relation_id, value: @relation_id, + id: "relation-id", autocomplete: "relation-id", + phx_change: "validate" + %> + +
    +
  • +
+ + <% end %> + """ + end + + def timeline(assigns) do + ~H""" +
+ <%= for event <- @friend |> Friends.Friend.get_events do %> +
    +
  • + <%= event.name %> | + <%= event.date |> format_date %> +
  • +
+ <% end %> + <%= if @friend |> Friends.Friend.get_events |> Enum.empty? do %> +
No events on record yet.
+ <% end %> +
+ """ + end +end diff --git a/friends/lib/friends_web/live/show.ex b/friends/lib/friends_web/live/show.ex index ee17540..feec7f4 100644 --- a/friends/lib/friends_web/live/show.ex +++ b/friends/lib/friends_web/live/show.ex @@ -1,10 +1,7 @@ defmodule FriendsWeb.FriendsLive.Show do use FriendsWeb, :live_view - import FriendsWeb.LiveHelpers - import FriendsWeb.FriendsLive.Components - alias Friends.Friend - def mount(%{"slug" => slug} = _attrs, token, socket) do + def mount(%{"slug" => slug}, token, socket) do live_action = socket.assigns.live_action || false friend = Friend.get_by_slug(slug) @@ -15,6 +12,7 @@ defmodule FriendsWeb.FriendsLive.Show do if(live_action) do {:ok, socket + |> assign(:mode, :show) |> assign(:live_action, live_action) |> assign_current_user(token |> Map.get("user_token")) |> assign(:friend, friend) @@ -30,35 +28,119 @@ defmodule FriendsWeb.FriendsLive.Show do end def handle_params( - %{"slug" => slug} = attrs, + %{"slug" => slug}, _url, - %{assigns: %{friend: friend, live_action: :overview}} = socket + %{assigns: %{live_action: :overview}} = socket ) do friend = Friend.get_by_slug(slug) - editable = friend |> Friend.can_be_edited_by(socket.assigns[:current_user]) live_action = socket.assigns.live_action {:noreply, socket - |> assign_friend(friend) + |> assign(:mode, :show) |> title(friend.name <> " - " <> (live_action |> titlecase)) - |> assign(:editable, editable)} + |> assign_friend(friend)} end def handle_params( - %{"slug" => slug} = attrs, - _url, - %{assigns: %{friend: friend, live_action: :relationships}} = socket + %{"slug" => slug} = params, + url, + %{assigns: %{live_action: :relationships}} = socket ) do + live_action = socket.assigns.live_action + friend = Friend.get_by_slug(slug) editable = friend |> Friend.can_be_edited_by(socket.assigns[:current_user]) - live_action = socket.assigns.live_action {:noreply, socket + |> assign(:mode, :edit) |> assign_friend(friend) - |> assign(:relationships, friend |> Helpers.relations()) - |> title(friend.name <> " - " <> (live_action |> titlecase)) - |> assign(:editable, editable)} + |> assign(:relationships, friend |> relations) + |> assign(:live_action, live_action) + |> assign(:search_query, nil) + |> assign(:relation_id, nil) + |> assign(:search_results, nil) + |> assign(:editable, editable) + |> title(friend.name <> " - " <> (live_action |> titlecase))} + end + + def handle_event( + "relation_search", + %{"friend" => %{"search_query" => query}}, + %{assigns: %{friend: friend}} = socket + ) do + if query == "" do + {:noreply, socket |> assign(:search_results, nil)} + else + results = + (Friend.Search.autocomplete(query, friend) ++ + [Friend.new(%{name: query})]) + |> Enum.map(&Friend.Search.parse_result/1) + + {:noreply, + socket + |> assign(:search_results, results) + |> assign(:search_query, query) + |> assign(:select_fxn, "selectRelation")} + end + end + + def handle_event( + "phx:select_relation", + %{"id" => rel_id, "name" => rel_name}, + %{assigns: %{friend: friend, relationships: relationships}} = socket + ) do + new_rel = + case rel_id do + "new" -> Friend.create(%{name: rel_name}) + _num -> Friend.get_by_id(rel_id |> String.to_integer()) + end + + [updated_friend, updated_relation] = friend |> Friend.create_relationship(new_rel) + + {:noreply, + socket + |> assign_friend(updated_friend) + |> assign(:relationships, updated_friend |> relations)} + end + + def handle_event( + "phx:delete_relation", + %{"id" => rel_id}, + %{assigns: %{friend: friend, relationships: relationships}} = socket + ) do + rel = Relationship.get_by_id(rel_id) + + IO.inspect("Deleting #{rel.id}") + + rel |> Relationship.delete() + + updated_friend = Friend.get_by_id(friend.id) + + {:noreply, + socket + |> assign_friend(updated_friend) + |> assign(:relationships, updated_friend |> relations)} + end + + def handle_event( + "phx:relation_type", + %{"rel_id" => rel_id, "type" => type}, + %{assigns: %{friend: friend, relationships: relationships}} = socket + ) do + rel = Relationship.get_by_id(rel_id) + + IO.inspect("Changing type of relationship #{rel.id} to #{type}") + + rel |> Relationship.change_type(type |> Relationship.type_index()) + + updated_friend = Friend.get_by_id(friend.id) + + {:noreply, + socket + |> assign_friend(updated_friend) + |> assign(:relationships, updated_friend |> relations) + |> push_navigate(to: Routes.friends_show_path(socket, :relationships, friend.slug))} end end diff --git a/friends/lib/friends_web/live/show.html.heex b/friends/lib/friends_web/live/show.html.heex index 9f4d06c..14e41e6 100644 --- a/friends/lib/friends_web/live/show.html.heex +++ b/friends/lib/friends_web/live/show.html.heex @@ -2,9 +2,9 @@
<%= menu(assigns) %> <%= header(assigns) %> - <%= show_page(@live_action, assigns) %> + <%= apply(FriendsWeb.LiveViews.Show, @live_action, [assigns]) %> - <%= if @editable do %> + <%= if @editable and @mode != :edit do %>
<.link navigate={Routes.friends_edit_path(FriendsWeb.Endpoint, @live_action, @friend.slug)} class="btn btn-block md:btn-wide text-white"><%=@live_action |> get_edit_text%>
diff --git a/friends/lib/friends_web/router.ex b/friends/lib/friends_web/router.ex index c9ef46e..94b0a6e 100644 --- a/friends/lib/friends_web/router.ex +++ b/friends/lib/friends_web/router.ex @@ -105,6 +105,11 @@ defmodule FriendsWeb.Router do live "/:slug/relationships", FriendsLive.Show, :relationships end + scope "/relationship", FriendsWeb do + pipe_through [:browser] + live "/:slug1/:slug2", RelationshipLive.Show, :overview + end + # Edit modes (require being logged in and having a profile) scope "/edit/", FriendsWeb do pipe_through [:browser, :require_authenticated_user, :capture_profile] @@ -116,4 +121,11 @@ defmodule FriendsWeb.Router do live "/:slug/timeline", FriendsLive.Edit, :timeline live "/:slug/relationships", FriendsLive.Edit, :relationships end + + # API + scope "/api", FriendsWeb do + pipe_through :api + + delete "/relationship/:id", RelationshipsController, :delete + end end diff --git a/friends/lib/friends_web/templates/layout/root.html.heex b/friends/lib/friends_web/templates/layout/root.html.heex index dcee7bc..5b9e953 100644 --- a/friends/lib/friends_web/templates/layout/root.html.heex +++ b/friends/lib/friends_web/templates/layout/root.html.heex @@ -1,5 +1,5 @@ - + diff --git a/friends/lib/friends_web/views/live_helpers.ex b/friends/lib/friends_web/views/live_helpers.ex index ff7e795..00e5ec6 100644 --- a/friends/lib/friends_web/views/live_helpers.ex +++ b/friends/lib/friends_web/views/live_helpers.ex @@ -1,6 +1,6 @@ defmodule FriendsWeb.LiveHelpers do - use FriendsWeb, :live_component alias Friends.Friend + use Phoenix.LiveComponent def titlecase(atom) do atom |> to_string |> :string.titlecase() @@ -83,6 +83,7 @@ defmodule FriendsWeb.LiveHelpers do def assign_friend(socket, friend) do socket |> assign(:friend, friend) + |> assign(:editable, friend |> Friend.can_be_edited_by(socket.assigns[:current_user])) |> assign(:changeset, friend |> Friend.changeset()) end @@ -90,6 +91,7 @@ defmodule FriendsWeb.LiveHelpers do def assign_friend(socket, friend, changeset) do socket |> assign(:friend, friend) + |> assign(:editable, friend |> Friend.can_be_edited_by(socket.assigns[:current_user])) |> assign(:changeset, changeset) end end