Lots of changes!
This commit is contained in:
parent
b85e3cc96c
commit
b9c7cb7b84
@ -64,7 +64,7 @@ window.selectMapResult = function (latlon, display) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
window.selectRelation = function (id, name) {
|
window.selectRelation = function (id, name) {
|
||||||
var e = new Event('eventSender');
|
var e = new Event('selectRelation');
|
||||||
e.data = {
|
e.data = {
|
||||||
id: id,
|
id: id,
|
||||||
name: name
|
name: name
|
||||||
@ -72,18 +72,56 @@ window.selectRelation = function (id, name) {
|
|||||||
window.dispatchEvent(e);
|
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 = {
|
Hooks.NewRelation = {
|
||||||
mounted() {
|
mounted() {
|
||||||
var list_el = document.querySelector("div#relationships");
|
var list_el = document.querySelector("div#relationships");
|
||||||
window.addEventListener("eventSender", e => {
|
window.addEventListener("selectRelation", e => {
|
||||||
this.pushEvent("select_relation", e.data, function (reply) {
|
this.pushEvent("phx:select_relation", e.data, function (reply) {
|
||||||
console.log(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 = {
|
Hooks.showMapbox = {
|
||||||
initMap() {
|
initMap() {
|
||||||
mapboxgl.accessToken = 'pk.eyJ1IjoicnlhbnBhbmR5YSIsImEiOiJja3psM2tlcDA1MXl1Mm9uZmo5bGxpNzdxIn0.TwBKpTTypcD5fWFc8XRyHg';
|
mapboxgl.accessToken = 'pk.eyJ1IjoicnlhbnBhbmR5YSIsImEiOiJja3psM2tlcDA1MXl1Mm9uZmo5bGxpNzdxIn0.TwBKpTTypcD5fWFc8XRyHg';
|
||||||
|
|||||||
@ -14,7 +14,7 @@ defmodule Friends.Relationship do
|
|||||||
has_many(:events, Friends.Event)
|
has_many(:events, Friends.Event)
|
||||||
end
|
end
|
||||||
|
|
||||||
@attrs [:friend_id, :relation_id]
|
@attrs [:friend_id, :relation_id, :type]
|
||||||
|
|
||||||
def types(index) do
|
def types(index) do
|
||||||
types() |> elem(index)
|
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
|
# Tuple: name of the type, associated color, and what that person "is" to the other
|
||||||
{
|
{
|
||||||
{:self, :hidden, :self},
|
{:self, :hidden, :self},
|
||||||
{:acquaintances, :info, nil},
|
{:acquaintances, :info, :known},
|
||||||
{:family, :primary, :relative},
|
{:family, :primary, :relative},
|
||||||
{:friends, :secondary, :friend},
|
{:friends, :secondary, :friend},
|
||||||
{:partners, :info, :partner},
|
{:partners, :info, :partner},
|
||||||
{:dating, :success, :date},
|
{:dating, :success, :dating},
|
||||||
{:engaged, :success, :fiancé},
|
{:engaged, :success, :fiancé},
|
||||||
{:married, :success, :spouse},
|
{:married, :success, :spouse},
|
||||||
{:divorced, :error, :ex},
|
{:divorced, :error, :ex},
|
||||||
@ -37,6 +37,16 @@ defmodule Friends.Relationship do
|
|||||||
}
|
}
|
||||||
end
|
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
|
def get_type(rel) do
|
||||||
rel.type |> types |> elem(0)
|
rel.type |> types |> elem(0)
|
||||||
end
|
end
|
||||||
@ -57,9 +67,27 @@ defmodule Friends.Relationship do
|
|||||||
)
|
)
|
||||||
end
|
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
|
def changeset(struct, params \\ %{}) do
|
||||||
struct
|
struct
|
||||||
|> Ecto.Changeset.cast(params, @attrs)
|
|> Ecto.Changeset.cast(params, @attrs)
|
||||||
|
|> validate_type
|
||||||
|> Ecto.Changeset.unique_constraint(
|
|> Ecto.Changeset.unique_constraint(
|
||||||
[:friend_id, :relation_id],
|
[:friend_id, :relation_id],
|
||||||
name: :relationships_friend_id_relation_id_index
|
name: :relationships_friend_id_relation_id_index
|
||||||
@ -122,8 +150,29 @@ defmodule Friends.Relationship do
|
|||||||
end
|
end
|
||||||
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
|
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
|
end
|
||||||
|
|
||||||
def get_by_slugs([slug1, slug2]) do
|
def get_by_slugs([slug1, slug2]) do
|
||||||
@ -141,7 +190,12 @@ defmodule Friends.Relationship do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def age(relationship) do
|
def age(relationship) do
|
||||||
relationship.events
|
case relationship.events do
|
||||||
|
[] ->
|
||||||
|
nil
|
||||||
|
|
||||||
|
e ->
|
||||||
|
e
|
||||||
|> Enum.map(fn event ->
|
|> Enum.map(fn event ->
|
||||||
Date.diff(Date.utc_today(), event.date)
|
Date.diff(Date.utc_today(), event.date)
|
||||||
end)
|
end)
|
||||||
@ -149,6 +203,7 @@ defmodule Friends.Relationship do
|
|||||||
|> List.last()
|
|> List.last()
|
||||||
|> div(365)
|
|> div(365)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def load_events(
|
def load_events(
|
||||||
%Relationship{
|
%Relationship{
|
||||||
|
|||||||
@ -24,6 +24,10 @@ defmodule FriendsWeb do
|
|||||||
import Plug.Conn
|
import Plug.Conn
|
||||||
import FriendsWeb.Gettext
|
import FriendsWeb.Gettext
|
||||||
alias FriendsWeb.Router.Helpers, as: Routes
|
alias FriendsWeb.Router.Helpers, as: Routes
|
||||||
|
|
||||||
|
alias Friends.{Friend, Relationship}
|
||||||
|
alias Friends.Accounts.User
|
||||||
|
import Helpers
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -48,6 +52,15 @@ defmodule FriendsWeb do
|
|||||||
use Phoenix.LiveView,
|
use Phoenix.LiveView,
|
||||||
layout: {FriendsWeb.LayoutView, "live.html"}
|
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())
|
unquote(view_helpers())
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@ -55,6 +68,14 @@ defmodule FriendsWeb do
|
|||||||
def live_component do
|
def live_component do
|
||||||
quote do
|
quote do
|
||||||
use Phoenix.LiveComponent
|
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())
|
unquote(view_helpers())
|
||||||
end
|
end
|
||||||
@ -63,117 +84,8 @@ defmodule FriendsWeb do
|
|||||||
def component do
|
def component do
|
||||||
quote do
|
quote do
|
||||||
use Phoenix.Component
|
use Phoenix.Component
|
||||||
|
import Helpers
|
||||||
unquote(view_helpers())
|
import FriendsWeb.LiveHelpers
|
||||||
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
|
|
||||||
|
|
||||||
unquote(view_helpers())
|
unquote(view_helpers())
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
defmodule FriendsWeb.FriendsController do
|
defmodule FriendsWeb.FriendsController do
|
||||||
use FriendsWeb, :controller
|
use FriendsWeb, :controller
|
||||||
alias Friends.{Friend, Relationship}
|
|
||||||
alias Friends.Accounts.User
|
|
||||||
import Helpers
|
|
||||||
|
|
||||||
def index(conn, _params) do
|
def index(conn, _params) do
|
||||||
conn
|
conn
|
||||||
|
|||||||
@ -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
|
||||||
@ -1,25 +1,43 @@
|
|||||||
defmodule FriendsWeb.Components.Cards do
|
defmodule FriendsWeb.Components.Cards do
|
||||||
use FriendsWeb, :live_component
|
use FriendsWeb, :live_component
|
||||||
|
|
||||||
|
import Helpers.Names
|
||||||
|
|
||||||
def relationship_card(assigns) do
|
def relationship_card(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div id={"relation-#{@relation.id}"} class="card card-compact w-96 bg-base-100 shadow-xl">
|
<div id={"relation-#{@relation.id}"} class="relative overflow-visible card card-compact w-96 bg-base-100 shadow-xl" phx-hook="RelationshipCard" relationship-id={@relationship.id}>
|
||||||
<figure><img src="https://placeimg.com/400/225/people" alt={@relation.id} /></figure>
|
|
||||||
<div class="card-body">
|
|
||||||
<h3 class="card-title">
|
|
||||||
<.link navigate={Routes.friends_show_path(FriendsWeb.Endpoint, :overview, @relation.slug)} class=""><%=@relation.name%></.link>
|
|
||||||
<%= if @relationship |> Friends.Relationship.get_relation do %>
|
|
||||||
<div class={"badge badge-#{@relationship |> Friends.Relationship.get_color}"}><%= @relationship |> Friends.Relationship.get_relation %></div>
|
|
||||||
<% end %>
|
|
||||||
</h3>
|
|
||||||
<p>If a dog chews shoes whose shoes does he choose?</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<%= if @editable do %>
|
<%= if @editable do %>
|
||||||
Delete
|
<!-- The button to open modal -->
|
||||||
|
<label for={"delete-relationship-#{@relation.id}"} class="btn btn-error absolute top-2 right-2">delete</label>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
|
<figure class="p-0 m-0"><img class="py-0 my-0" src="https://placeimg.com/400/225/people" alt={@relation.id} /></figure>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="flex flex-row justify-between items-center">
|
||||||
|
<h2 class="card-title py-0 my-0"><.link navigate={Routes.friends_show_path(FriendsWeb.Endpoint, :overview, @relation.slug)} class="no-underline font-bold hover:underline"><%=@relation.name%></.link></h2>
|
||||||
|
<.link patch={Routes.relationship_show_path(FriendsWeb.Endpoint, :overview, @friend.slug, @relation.slug)}>(details)</.link>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<Components.relationship_details editable={@editable} relationship={@relationship} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<input type="checkbox" id={"delete-relationship-#{@relation.id}"} class="modal-toggle" />
|
||||||
|
<div class="modal modal-bottom sm:modal-middle">
|
||||||
|
<div class="modal-box">
|
||||||
|
<h3 class="font-bold text-lg mt-0 pt-0">Are you sure you want to delete <%=@friend |> first_name%>'s relationship with <%=@relation |> first_name%>?</h3>
|
||||||
|
<p class="py-4">Unless these two people really don't know each other, you probably want to change the relationship type, e.g. from "dating" to "ex".</p>
|
||||||
|
<div class="modal-action">
|
||||||
|
<label for={"delete-relationship-#{@relation.id}"} class="btn btn-sm btn-ghost">Never mind</label>
|
||||||
|
<label for={"delete-relationship-#{@relation.id}"} class="btn btn-sm btn-primary" onClick={"javascript:deleteRelation(#{@relationship.id})"}>Accept</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def confirm_dialog(id) do
|
||||||
|
JS.show(to: "#warning-relation-#{id}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,11 +1,5 @@
|
|||||||
defmodule FriendsWeb.FriendsLive.Components do
|
defmodule FriendsWeb.Components do
|
||||||
use FriendsWeb, :live_component
|
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
|
def header(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
@ -44,233 +38,73 @@ defmodule FriendsWeb.FriendsLive.Components do
|
|||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
def show_page(:main, assigns), do: show_page(:overview, %{assigns | live_action: :overview})
|
def relationship_details(assigns) do
|
||||||
|
|
||||||
def show_page(:overview, assigns) do
|
|
||||||
~H"""
|
~H"""
|
||||||
<%= if @address_latlon != "null" do %>
|
<div class="flex flex-row items-center gap-3">
|
||||||
<Map.show address_latlon={@address_latlon} />
|
<div class="dropdown" id={"type-selector-#{@relationship.id}"}>
|
||||||
<% end %>
|
<label tabindex="0" class={"hover:badge-ghost badge badge-#{@relationship |> Friends.Relationship.get_color} m-1 text-white"}>
|
||||||
<ul class="py-4 pl-0 md:text-xl h-1/2">
|
<%= @relationship |> Friends.Relationship.get_relation %>
|
||||||
<li class="flex flex-row mb-8 gap-6">
|
|
||||||
<strong class="w-28 text-right">Nickname:</strong>
|
|
||||||
<div class="">
|
|
||||||
<%= if is_nil(@friend.nickname) do %>
|
|
||||||
<span class="italic">none</span>
|
|
||||||
<% else %>
|
|
||||||
<%= @friend.nickname %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class="flex flex-row mb-8 gap-6">
|
|
||||||
<strong class="w-28 text-right">Birthday:</strong>
|
|
||||||
<div class=""><%= @friend.born |> Calendar.strftime("%B %d, %Y") %>
|
|
||||||
<br class="md:hidden"/>
|
|
||||||
<span class="font-light">(<%= @friend |> Friend.age %> years old)</span>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class="flex flex-row mb-8 gap-6">
|
|
||||||
<strong class="w-28 text-right">Email:</strong>
|
|
||||||
<div class=""><%= @friend.email %></div>
|
|
||||||
</li>
|
|
||||||
<li class="flex flex-row mb-8 gap-6">
|
|
||||||
<strong class="w-28 text-right">Phone:</strong>
|
|
||||||
<div class=""><%= @friend.phone %></div>
|
|
||||||
</li>
|
|
||||||
<li class="flex flex-row mb-8 gap-6">
|
|
||||||
<strong class="w-28 text-right">Address:</strong>
|
|
||||||
<%= if @address_latlon == "null" do %>
|
|
||||||
<span class="italic">none</span>
|
|
||||||
<% else %>
|
|
||||||
<div class=""><%= @address %></div>
|
|
||||||
<% end %>
|
|
||||||
<input type="hidden" autocomplete="latlon" value={@address_latlon}/>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
|
|
||||||
def show_page(:timeline, assigns) do
|
<%= if @editable do %>
|
||||||
~H"""
|
|
||||||
<div id="timeline" class="flex md:flex-row flex-col gap-8 p-8">
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-chevron-down" viewBox="0 0 16 16"> <path fill-rule="evenodd" d="M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z"/> </svg>
|
||||||
<%= for event <- @friend |> Friends.Friend.get_events do %>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<b><%= event.name %></b> |
|
|
||||||
<span><%= event.date |> format_date %></span>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= if @friend |> Friends.Friend.get_events |> Enum.empty? do %>
|
</label>
|
||||||
<div class="italic">No events on record yet.</div>
|
<Components.relationship_type_selector relationship={@relationship} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span>since</span>
|
||||||
|
|
||||||
|
<%= if @relationship |> Relationship.age do %>
|
||||||
|
<%=
|
||||||
|
@relationship |> Relationship.age
|
||||||
|
%>
|
||||||
|
<% else %>
|
||||||
|
<div class={"tooltip tooltip-#{@relationship |> Relationship.get_color}"} data-tip="add milestone dates on the detail page.">
|
||||||
|
<a class="hover:cursor-help" style="text-decoration-style:dashed">(no date)</a>
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
def show_page(:relationships, assigns) do
|
def relationship_type_selector(assigns) do
|
||||||
~H"""
|
~H"""
|
||||||
<div id="relationships" class="flex md:flex-row flex-col gap-8 p-8">
|
<ul tabindex="0" class="absolute dropdown-content menu p-0 shadow bg-base-100 rounded-box w-52 justify-start">
|
||||||
<%= for relation <- @relationships do %>
|
<%= Relationship.types()
|
||||||
<% relationship = relation(@friend, relation) %>
|
|> Tuple.to_list()
|
||||||
<Cards.relationship_card relation={relation} relationship={relationship} editable={@editable}/>
|
|> Enum.map(fn(tuple) ->
|
||||||
<% end %>
|
type = tuple |> elem(0)
|
||||||
<%= if @relationships |> Enum.empty? do %>
|
class = tuple |> elem(1) |> to_string
|
||||||
<div class="italic">No relationships on record yet.</div>
|
|
||||||
<% end %>
|
selected = (@relationship |> Relationship.get_type) == type
|
||||||
</div>
|
|
||||||
|
if class != "hidden" do
|
||||||
|
if selected do
|
||||||
|
"<li class='p-2 m-0 text-sm bg-slate-200 select-none' style='font-weight:normal;'>#{type}</li>"
|
||||||
|
else
|
||||||
|
"<li
|
||||||
|
class='p-2 m-0 text-sm hover:bg-slate-400 hover:text-black hover:cursor-pointer'
|
||||||
|
style='font-weight:normal;'
|
||||||
|
onClick='#{relationship_type_function(@relationship.id, type)}'
|
||||||
|
>
|
||||||
|
#{type}
|
||||||
|
</li>"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end) |> Enum.join
|
||||||
|
|> raw
|
||||||
|
%>
|
||||||
|
</ul>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
def relationship_type_function(id, type) do
|
||||||
|
"""
|
||||||
|
relationType("#{id}", "#{type}")
|
||||||
"""
|
"""
|
||||||
end
|
end
|
||||||
|
|
||||||
###
|
###
|
||||||
def edit_page(:welcome, assigns) do
|
|
||||||
top = ~H"""
|
|
||||||
<h1>Welcome!</h1>
|
|
||||||
<p>Before we get started, we just need some basic info about you:</p>
|
|
||||||
<%= 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 %>
|
|
||||||
<div class="border-b-4 flex flex-row">
|
|
||||||
<%= 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 %>
|
|
||||||
<div class="min-w-fit flex place-items-center mx-4"><%= error_tag f, :name %></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Map.show address_latlon={@address_latlon} />
|
|
||||||
<ul class="py-4 pl-0 h-1/2">
|
|
||||||
<li class="flex flex-row gap-x-6">
|
|
||||||
<strong class="md:text-xl w-20 md:w-28 shrink-0 text-right">Email:</strong>
|
|
||||||
<div class="flex flex-col h-16">
|
|
||||||
<%= text_input f, :email, class: "input input-primary input-sm md:input-md input-disabled", phx_debounce: "blur", value: @friend.email %>
|
|
||||||
<div class="min-w-fit flex place-items-center mr-4"><%= error_tag f, :email %></div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<%= if @live_action != :welcome do %>
|
|
||||||
<li class="flex flex-row gap-x-6 h-16">
|
|
||||||
<strong class="md:text-xl w-20 md:w-28 shrink-0 text-right">Nickname:</strong>
|
|
||||||
<div class=""><%= text_input f, :nickname, class: "input input-primary input-sm md:input-md", phx_debounce: "blur", value: @friend.nickname %></div>
|
|
||||||
</li>
|
|
||||||
<% end %>
|
|
||||||
<li class="flex flex-row gap-x-6">
|
|
||||||
<strong class="md:text-xl w-20 md:w-28 shrink-0 text-right">Birthday:</strong>
|
|
||||||
<div class="flex flex-col h-16">
|
|
||||||
<%= date_input f, :born, class: "input input-primary input-sm md:input-md", phx_debounce: "blur", value: @friend.born %>
|
|
||||||
<div class="min-w-fit flex place-items-center mr-4"><%= error_tag f, :born %></div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class="flex flex-row gap-x-6">
|
|
||||||
<strong class="md:text-xl w-20 md:w-28 shrink-0 text-right">Phone:</strong>
|
|
||||||
<div class="flex flex-col h-16">
|
|
||||||
<%= text_input f, :phone, class: "input input-primary input-sm md:input-md", phx_debounce: "blur", value: @friend.phone |> FriendsWeb.LiveHelpers.display_phone(@changeset) %>
|
|
||||||
<div class="min-w-fit flex place-items-center mr-4"><%= error_tag f, :phone %></div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class="flex flex-row gap-x-6 relative">
|
|
||||||
<strong class="md:text-xl w-20 md:w-28 shrink-0 text-right">Address:</strong>
|
|
||||||
<div class="flex flex-col h-16">
|
|
||||||
<%= 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"
|
|
||||||
%>
|
|
||||||
</div>
|
|
||||||
<Autocomplete.search_results
|
|
||||||
search_results={@search_results}
|
|
||||||
search_query={@search_query}
|
|
||||||
select_fxn="selectMapResult"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div class="form-control flex flex-row gap-x-4 md:justify-end mb-4 md:w-1/2">
|
|
||||||
<%= if @live_action != :welcome do %>
|
|
||||||
<div class="flex-1">
|
|
||||||
<.link patch={Routes.friends_show_path(FriendsWeb.Endpoint, :overview, @friend.slug)} class="btn btn-block btn-outline">back</.link>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<div class="flex-1">
|
|
||||||
<%= if @changeset.valid? do %>
|
|
||||||
<%= submit "Save", phx_disable_with: "Saving...", class: "btn btn-block" %>
|
|
||||||
<% else %>
|
|
||||||
<%= submit "Save", class: "btn btn-block btn-disabled" %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
<%= if @live_action != :welcome and @current_user.profile.id == @friend.id do %>
|
|
||||||
<div class="flex-1">
|
|
||||||
<.link href={Routes.user_settings_path(FriendsWeb.Endpoint, :edit)} class="btn btn-block btn-error">Delete</.link>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</.form>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
|
|
||||||
def edit_page(:relationships, assigns) do
|
|
||||||
~H"""
|
|
||||||
<%= show_page(:relationships, assigns) %>
|
|
||||||
|
|
||||||
<.form for={@changeset} let={f}>
|
|
||||||
<ul class="py-4 pl-0 h-1/2">
|
|
||||||
<li class="flex flex-row gap-x-6 relative">
|
|
||||||
<strong class="md:text-xl basis-auto shrink-0 text-right">Type a name:</strong>
|
|
||||||
<div class="flex flex-col h-16 relative">
|
|
||||||
<%= 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"
|
|
||||||
%>
|
|
||||||
<Autocomplete.search_results
|
|
||||||
search_results={@search_results}
|
|
||||||
search_query={@search_query}
|
|
||||||
select_fxn="selectRelation"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<div class="form-control flex flex-row gap-x-4 md:justify-end mb-4 md:w-1/2">
|
|
||||||
<%= if @live_action != :welcome do %>
|
|
||||||
<div class="flex-1">
|
|
||||||
<.link patch={Routes.friends_show_path(FriendsWeb.Endpoint, :overview, @friend.slug)} class="btn btn-block btn-outline">back</.link>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</.form>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
|
|
||||||
def edit_page(:timeline, assigns) do
|
|
||||||
~H"""
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,11 +1,5 @@
|
|||||||
defmodule FriendsWeb.FriendsLive.Edit do
|
defmodule FriendsWeb.FriendsLive.Edit do
|
||||||
use FriendsWeb, :live_view
|
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
|
# No slug means it's a new profile form
|
||||||
def mount(%{}, token, socket) do
|
def mount(%{}, token, socket) do
|
||||||
@ -33,6 +27,7 @@ defmodule FriendsWeb.FriendsLive.Edit do
|
|||||||
if(live_action) do
|
if(live_action) do
|
||||||
{:ok,
|
{:ok,
|
||||||
socket
|
socket
|
||||||
|
|> assign(:mode, :edit)
|
||||||
|> assign(:live_action, live_action)
|
|> assign(:live_action, live_action)
|
||||||
|> assign_current_user(token |> Map.get("user_token"))
|
|> assign_current_user(token |> Map.get("user_token"))
|
||||||
|> assign(:friend, friend)
|
|> assign(:friend, friend)
|
||||||
@ -62,6 +57,7 @@ defmodule FriendsWeb.FriendsLive.Edit do
|
|||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|
|> assign(:mode, :edit)
|
||||||
|> assign_friend(friend)
|
|> assign_friend(friend)
|
||||||
|> assign(:action, Routes.friends_path(socket, :update))
|
|> assign(:action, Routes.friends_path(socket, :update))
|
||||||
|> assign(:live_action, live_action)
|
|> assign(:live_action, live_action)
|
||||||
@ -85,6 +81,7 @@ defmodule FriendsWeb.FriendsLive.Edit do
|
|||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|
|> assign(:mode, :edit)
|
||||||
|> assign_friend(friend)
|
|> assign_friend(friend)
|
||||||
|> assign(:relationships, friend |> relations)
|
|> assign(:relationships, friend |> relations)
|
||||||
|> assign(:live_action, live_action)
|
|> assign(:live_action, live_action)
|
||||||
@ -92,7 +89,10 @@ defmodule FriendsWeb.FriendsLive.Edit do
|
|||||||
|> assign(:relation_id, nil)
|
|> assign(:relation_id, nil)
|
||||||
|> assign(:search_results, nil)
|
|> assign(:search_results, nil)
|
||||||
|> assign(:editable, editable)
|
|> 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
|
end
|
||||||
|
|
||||||
# Timeline edit
|
# Timeline edit
|
||||||
@ -107,6 +107,7 @@ defmodule FriendsWeb.FriendsLive.Edit do
|
|||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|
|> assign(:mode, :edit)
|
||||||
|> assign_friend(friend)
|
|> assign_friend(friend)
|
||||||
|> assign(:live_action, live_action)
|
|> assign(:live_action, live_action)
|
||||||
|> assign(:search_query, nil)
|
|> assign(:search_query, nil)
|
||||||
@ -237,46 +238,6 @@ defmodule FriendsWeb.FriendsLive.Edit do
|
|||||||
end
|
end
|
||||||
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
|
def handle_event(_event, _unsigned_params, socket) do
|
||||||
{:noreply, socket}
|
{:noreply, socket}
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<section class="row">
|
<section class="row">
|
||||||
<article class="column prose">
|
<article class="column prose">
|
||||||
<%= edit_menu(assigns) %>
|
<%= edit_menu(assigns) %>
|
||||||
<%= edit_page(@live_action, assigns) %>
|
<%= apply(FriendsWeb.LiveViews.Edit, @live_action, [assigns]) %>
|
||||||
</article>
|
</article>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@ -1,11 +1,5 @@
|
|||||||
defmodule FriendsWeb.FriendsLive.Friend do
|
defmodule FriendsWeb.FriendsLive.Friend do
|
||||||
use FriendsWeb, :live_view
|
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
|
# Initialize variables on first load
|
||||||
def mount(%{}, token, socket) do
|
def mount(%{}, token, socket) do
|
||||||
|
|||||||
121
friends/lib/friends_web/live/live_views/edit.ex
Normal file
121
friends/lib/friends_web/live/live_views/edit.ex
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
defmodule FriendsWeb.LiveViews.Edit do
|
||||||
|
use FriendsWeb, :live_component
|
||||||
|
|
||||||
|
def welcome(assigns) do
|
||||||
|
top = ~H"""
|
||||||
|
<h1>Welcome!</h1>
|
||||||
|
<p>Before we get started, we just need some basic info about you:</p>
|
||||||
|
<%= 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 %>
|
||||||
|
<div class="border-b-4 flex flex-row">
|
||||||
|
<%= 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 %>
|
||||||
|
<div class="min-w-fit flex place-items-center mx-4"><%= error_tag f, :name %></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Map.show address_latlon={@address_latlon} />
|
||||||
|
<ul class="py-4 pl-0 h-1/2">
|
||||||
|
<li class="flex flex-row gap-x-6">
|
||||||
|
<strong class="md:text-xl w-20 md:w-28 shrink-0 text-right">Email:</strong>
|
||||||
|
<div class="flex flex-col h-16">
|
||||||
|
<%= text_input f, :email, class: "input input-primary input-sm md:input-md input-disabled", phx_debounce: "blur", value: @friend.email %>
|
||||||
|
<div class="min-w-fit flex place-items-center mr-4"><%= error_tag f, :email %></div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<%= if @live_action != :welcome do %>
|
||||||
|
<li class="flex flex-row gap-x-6 h-16">
|
||||||
|
<strong class="md:text-xl w-20 md:w-28 shrink-0 text-right">Nickname:</strong>
|
||||||
|
<div class=""><%= text_input f, :nickname, class: "input input-primary input-sm md:input-md", phx_debounce: "blur", value: @friend.nickname %></div>
|
||||||
|
</li>
|
||||||
|
<% end %>
|
||||||
|
<li class="flex flex-row gap-x-6">
|
||||||
|
<strong class="md:text-xl w-20 md:w-28 shrink-0 text-right">Birthday:</strong>
|
||||||
|
<div class="flex flex-col h-16">
|
||||||
|
<%= date_input f, :born, class: "input input-primary input-sm md:input-md", phx_debounce: "blur", value: @friend.born %>
|
||||||
|
<div class="min-w-fit flex place-items-center mr-4"><%= error_tag f, :born %></div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="flex flex-row gap-x-6">
|
||||||
|
<strong class="md:text-xl w-20 md:w-28 shrink-0 text-right">Phone:</strong>
|
||||||
|
<div class="flex flex-col h-16">
|
||||||
|
<%= text_input f, :phone, class: "input input-primary input-sm md:input-md", phx_debounce: "blur", value: @friend.phone |> FriendsWeb.LiveHelpers.display_phone(@changeset) %>
|
||||||
|
<div class="min-w-fit flex place-items-center mr-4"><%= error_tag f, :phone %></div>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="flex flex-row gap-x-6 relative">
|
||||||
|
<strong class="md:text-xl w-20 md:w-28 shrink-0 text-right">Address:</strong>
|
||||||
|
<div class="flex flex-col h-16">
|
||||||
|
<%= 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"
|
||||||
|
%>
|
||||||
|
</div>
|
||||||
|
<Autocomplete.search_results
|
||||||
|
search_results={@search_results}
|
||||||
|
search_query={@search_query}
|
||||||
|
select_fxn="selectMapResult"
|
||||||
|
/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="form-control flex flex-row gap-x-4 md:justify-end mb-4 md:w-1/2">
|
||||||
|
<%= if @live_action != :welcome do %>
|
||||||
|
<div class="flex-1">
|
||||||
|
<.link patch={Routes.friends_show_path(FriendsWeb.Endpoint, :overview, @friend.slug)} class="btn btn-block btn-outline">back</.link>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
<div class="flex-1">
|
||||||
|
<%= if @changeset.valid? do %>
|
||||||
|
<%= submit "Save", phx_disable_with: "Saving...", class: "btn btn-block" %>
|
||||||
|
<% else %>
|
||||||
|
<%= submit "Save", class: "btn btn-block btn-disabled" %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<%= if @live_action != :welcome and @current_user.profile.id == @friend.id do %>
|
||||||
|
<div class="flex-1">
|
||||||
|
<.link href={Routes.user_settings_path(FriendsWeb.Endpoint, :edit)} class="btn btn-block btn-error">Delete</.link>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</.form>
|
||||||
|
"""
|
||||||
|
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
|
||||||
115
friends/lib/friends_web/live/live_views/show.ex
Normal file
115
friends/lib/friends_web/live/live_views/show.ex
Normal file
@ -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 %>
|
||||||
|
<Map.show address_latlon={@address_latlon} />
|
||||||
|
<% end %>
|
||||||
|
<ul class="py-4 pl-0 md:text-xl h-1/2">
|
||||||
|
<li class="flex flex-row mb-8 gap-6">
|
||||||
|
<strong class="w-28 text-right">Nickname:</strong>
|
||||||
|
<div class="">
|
||||||
|
<%= if is_nil(@friend.nickname) do %>
|
||||||
|
<span class="italic">none</span>
|
||||||
|
<% else %>
|
||||||
|
<%= @friend.nickname %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="flex flex-row mb-8 gap-6">
|
||||||
|
<strong class="w-28 text-right">Birthday:</strong>
|
||||||
|
<div class=""><%= @friend.born |> Calendar.strftime("%B %d, %Y") %>
|
||||||
|
<br class="md:hidden"/>
|
||||||
|
<span class="font-light">(<%= @friend |> Friend.age %> years old)</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class="flex flex-row mb-8 gap-6">
|
||||||
|
<strong class="w-28 text-right">Email:</strong>
|
||||||
|
<div class=""><%= @friend.email %></div>
|
||||||
|
</li>
|
||||||
|
<li class="flex flex-row mb-8 gap-6">
|
||||||
|
<strong class="w-28 text-right">Phone:</strong>
|
||||||
|
<div class=""><%= @friend.phone %></div>
|
||||||
|
</li>
|
||||||
|
<li class="flex flex-row mb-8 gap-6">
|
||||||
|
<strong class="w-28 text-right">Address:</strong>
|
||||||
|
<%= if @address_latlon == "null" do %>
|
||||||
|
<span class="italic">none</span>
|
||||||
|
<% else %>
|
||||||
|
<div class=""><%= @address %></div>
|
||||||
|
<% end %>
|
||||||
|
<input type="hidden" autocomplete="latlon" value={@address_latlon}/>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
def relationships(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div id="relationships" class="flex md:flex-row flex-col gap-8 p-8">
|
||||||
|
<%= for relation <- @relationships do %>
|
||||||
|
<% relationship = relation(@friend, relation) %>
|
||||||
|
<Cards.relationship_card
|
||||||
|
friend={@friend}
|
||||||
|
relation={relation}
|
||||||
|
relationship={relationship}
|
||||||
|
editable={@editable}
|
||||||
|
mode={@mode}
|
||||||
|
/>
|
||||||
|
<% end %>
|
||||||
|
<%= if @relationships |> Enum.empty? do %>
|
||||||
|
<div class="italic">No relationships on record yet.</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<%= if @editable do %>
|
||||||
|
<.form for={@changeset} let={f} class="border-t-4">
|
||||||
|
<ul class="py-4 pl-0 h-1/2">
|
||||||
|
<li class="flex flex-row gap-x-6 relative items-center">
|
||||||
|
<strong class="md:text-xl basis-auto shrink-0 text-right">Type a name:</strong>
|
||||||
|
<div class="flex flex-col relative">
|
||||||
|
<%= 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"
|
||||||
|
%>
|
||||||
|
<Autocomplete.search_results
|
||||||
|
search_results={@search_results}
|
||||||
|
search_query={@search_query}
|
||||||
|
select_fxn="selectRelation"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</.form>
|
||||||
|
<% end %>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
def timeline(assigns) do
|
||||||
|
~H"""
|
||||||
|
<div id="timeline" class="flex md:flex-row flex-col gap-8 p-8">
|
||||||
|
<%= for event <- @friend |> Friends.Friend.get_events do %>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<b><%= event.name %></b> |
|
||||||
|
<span><%= event.date |> format_date %></span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<% end %>
|
||||||
|
<%= if @friend |> Friends.Friend.get_events |> Enum.empty? do %>
|
||||||
|
<div class="italic">No events on record yet.</div>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -1,10 +1,7 @@
|
|||||||
defmodule FriendsWeb.FriendsLive.Show do
|
defmodule FriendsWeb.FriendsLive.Show do
|
||||||
use FriendsWeb, :live_view
|
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
|
live_action = socket.assigns.live_action || false
|
||||||
|
|
||||||
friend = Friend.get_by_slug(slug)
|
friend = Friend.get_by_slug(slug)
|
||||||
@ -15,6 +12,7 @@ defmodule FriendsWeb.FriendsLive.Show do
|
|||||||
if(live_action) do
|
if(live_action) do
|
||||||
{:ok,
|
{:ok,
|
||||||
socket
|
socket
|
||||||
|
|> assign(:mode, :show)
|
||||||
|> assign(:live_action, live_action)
|
|> assign(:live_action, live_action)
|
||||||
|> assign_current_user(token |> Map.get("user_token"))
|
|> assign_current_user(token |> Map.get("user_token"))
|
||||||
|> assign(:friend, friend)
|
|> assign(:friend, friend)
|
||||||
@ -30,35 +28,119 @@ defmodule FriendsWeb.FriendsLive.Show do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def handle_params(
|
def handle_params(
|
||||||
%{"slug" => slug} = attrs,
|
%{"slug" => slug},
|
||||||
_url,
|
_url,
|
||||||
%{assigns: %{friend: friend, live_action: :overview}} = socket
|
%{assigns: %{live_action: :overview}} = socket
|
||||||
) do
|
) do
|
||||||
friend = Friend.get_by_slug(slug)
|
friend = Friend.get_by_slug(slug)
|
||||||
editable = friend |> Friend.can_be_edited_by(socket.assigns[:current_user])
|
|
||||||
live_action = socket.assigns.live_action
|
live_action = socket.assigns.live_action
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|> assign_friend(friend)
|
|> assign(:mode, :show)
|
||||||
|> title(friend.name <> " - " <> (live_action |> titlecase))
|
|> title(friend.name <> " - " <> (live_action |> titlecase))
|
||||||
|> assign(:editable, editable)}
|
|> assign_friend(friend)}
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_params(
|
def handle_params(
|
||||||
%{"slug" => slug} = attrs,
|
%{"slug" => slug} = params,
|
||||||
_url,
|
url,
|
||||||
%{assigns: %{friend: friend, live_action: :relationships}} = socket
|
%{assigns: %{live_action: :relationships}} = socket
|
||||||
) do
|
) do
|
||||||
|
live_action = socket.assigns.live_action
|
||||||
|
|
||||||
friend = Friend.get_by_slug(slug)
|
friend = Friend.get_by_slug(slug)
|
||||||
editable = friend |> Friend.can_be_edited_by(socket.assigns[:current_user])
|
editable = friend |> Friend.can_be_edited_by(socket.assigns[:current_user])
|
||||||
live_action = socket.assigns.live_action
|
|
||||||
|
|
||||||
{:noreply,
|
{:noreply,
|
||||||
socket
|
socket
|
||||||
|
|> assign(:mode, :edit)
|
||||||
|> assign_friend(friend)
|
|> assign_friend(friend)
|
||||||
|> assign(:relationships, friend |> Helpers.relations())
|
|> assign(:relationships, friend |> relations)
|
||||||
|> title(friend.name <> " - " <> (live_action |> titlecase))
|
|> assign(:live_action, live_action)
|
||||||
|> assign(:editable, editable)}
|
|> 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
|
||||||
end
|
end
|
||||||
|
|||||||
@ -2,9 +2,9 @@
|
|||||||
<article class="column prose">
|
<article class="column prose">
|
||||||
<%= menu(assigns) %>
|
<%= menu(assigns) %>
|
||||||
<%= header(assigns) %>
|
<%= header(assigns) %>
|
||||||
<%= show_page(@live_action, assigns) %>
|
<%= apply(FriendsWeb.LiveViews.Show, @live_action, [assigns]) %>
|
||||||
|
|
||||||
<%= if @editable do %>
|
<%= if @editable and @mode != :edit do %>
|
||||||
<div class="form-control flex flex-row mb-4">
|
<div class="form-control flex flex-row mb-4">
|
||||||
<.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%></.link>
|
<.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%></.link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -105,6 +105,11 @@ defmodule FriendsWeb.Router do
|
|||||||
live "/:slug/relationships", FriendsLive.Show, :relationships
|
live "/:slug/relationships", FriendsLive.Show, :relationships
|
||||||
end
|
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)
|
# Edit modes (require being logged in and having a profile)
|
||||||
scope "/edit/", FriendsWeb do
|
scope "/edit/", FriendsWeb do
|
||||||
pipe_through [:browser, :require_authenticated_user, :capture_profile]
|
pipe_through [:browser, :require_authenticated_user, :capture_profile]
|
||||||
@ -116,4 +121,11 @@ defmodule FriendsWeb.Router do
|
|||||||
live "/:slug/timeline", FriendsLive.Edit, :timeline
|
live "/:slug/timeline", FriendsLive.Edit, :timeline
|
||||||
live "/:slug/relationships", FriendsLive.Edit, :relationships
|
live "/:slug/relationships", FriendsLive.Edit, :relationships
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# API
|
||||||
|
scope "/api", FriendsWeb do
|
||||||
|
pipe_through :api
|
||||||
|
|
||||||
|
delete "/relationship/:id", RelationshipsController, :delete
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" data-theme="corporate"> <!-- pastel -->
|
<html lang="en" data-theme="cupcake"> <!-- pastel -->
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8"/>
|
<meta charset="utf-8"/>
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
defmodule FriendsWeb.LiveHelpers do
|
defmodule FriendsWeb.LiveHelpers do
|
||||||
use FriendsWeb, :live_component
|
|
||||||
alias Friends.Friend
|
alias Friends.Friend
|
||||||
|
use Phoenix.LiveComponent
|
||||||
|
|
||||||
def titlecase(atom) do
|
def titlecase(atom) do
|
||||||
atom |> to_string |> :string.titlecase()
|
atom |> to_string |> :string.titlecase()
|
||||||
@ -83,6 +83,7 @@ defmodule FriendsWeb.LiveHelpers do
|
|||||||
def assign_friend(socket, friend) do
|
def assign_friend(socket, friend) do
|
||||||
socket
|
socket
|
||||||
|> assign(:friend, friend)
|
|> assign(:friend, friend)
|
||||||
|
|> assign(:editable, friend |> Friend.can_be_edited_by(socket.assigns[:current_user]))
|
||||||
|> assign(:changeset, friend |> Friend.changeset())
|
|> assign(:changeset, friend |> Friend.changeset())
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -90,6 +91,7 @@ defmodule FriendsWeb.LiveHelpers do
|
|||||||
def assign_friend(socket, friend, changeset) do
|
def assign_friend(socket, friend, changeset) do
|
||||||
socket
|
socket
|
||||||
|> assign(:friend, friend)
|
|> assign(:friend, friend)
|
||||||
|
|> assign(:editable, friend |> Friend.can_be_edited_by(socket.assigns[:current_user]))
|
||||||
|> assign(:changeset, changeset)
|
|> assign(:changeset, changeset)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user