Added addresses. Cleaning routes. Flow.

This commit is contained in:
Ryan Pandya 2022-11-05 18:12:02 -07:00
parent bb6d7e1e2d
commit fe0d748a53
21 changed files with 318 additions and 286 deletions

View File

@ -12,6 +12,7 @@ defmodule Friends.Friend do
field(:nickname, :string) field(:nickname, :string)
field(:born, :date) field(:born, :date)
field(:phone, :string) field(:phone, :string)
field(:address, :string)
field(:email, :string) field(:email, :string)
field(:slug, :string) field(:slug, :string)
field(:memories, {:array, :string}) field(:memories, {:array, :string})
@ -89,15 +90,15 @@ defmodule Friends.Friend do
def create(params) do def create(params) do
%Friend{} %Friend{}
|> Friend.changeset(%{params | id: nil}) |> Friend.changeset(%{params | id: nil})
|> Map.put(:action, :insert) |> Map.put(:action, :insert)
end end
def update(params) do def update(params) do
{:ok, Friend.get_by_id(params.id |> String.to_integer()) {:ok,
|> Friend.changeset(params) Friend.get_by_id(params.id |> String.to_integer())
|> Map.put(:action, :update) |> Friend.changeset(params)
} |> Map.put(:action, :update)}
end end
def commit(changeset) do def commit(changeset) do
@ -107,18 +108,19 @@ defmodule Friends.Friend do
|> load_preloads |> load_preloads
end end
def generate_slug(%Ecto.Changeset{} = changeset) do def generate_slug(%Ecto.Changeset{} = changeset) do
slug = changeset.changes.name |> to_slug slug = changeset.changes.name |> to_slug
changeset |> changeset(%{ changeset
|> changeset(%{
slug: slug slug: slug
}) })
end end
def generate_slug(%Friend{} = friend) do def generate_slug(%Friend{} = friend) do
%{ %{
friend | slug: friend.name |> to_slug friend
| slug: friend.name |> to_slug
} }
end end
@ -128,11 +130,12 @@ defmodule Friends.Friend do
params params
|> create() |> create()
|> commit() |> commit()
_number -> _number ->
params params
|> update() |> update()
|> commit() |> commit()
end end
end end
def get_relationships(friend) do def get_relationships(friend) do
@ -154,15 +157,16 @@ defmodule Friends.Friend do
end end
def can_be_edited_by(friend, user) do def can_be_edited_by(friend, user) do
friend.id == user.profile.id if user |> is_nil(), do: false, else: friend.id == user.profile.id
end end
def assign_user(%Friend{} = friend) do def assign_user(%Friend{} = friend) do
case friend.email |> Friends.Accounts.get_user_by_email do case friend.email |> Friends.Accounts.get_user_by_email() do
nil -> nil ->
nil nil
user -> user ->
user |> Friends.Accounts.assign_profile user |> Friends.Accounts.assign_profile()
end end
end end
@ -170,24 +174,31 @@ defmodule Friends.Friend do
model model
|> @repo.preload(:user) |> @repo.preload(:user)
end end
def load_user(%Friends.Friend{} = friend), do: friend def load_user(%Friends.Friend{} = friend), do: friend
def load_relationships(%Friends.Friend{ def load_relationships(
relationships: %Ecto.Association.NotLoaded{}, %Friends.Friend{
reverse_relationships: %Ecto.Association.NotLoaded{} relationships: %Ecto.Association.NotLoaded{},
} = model) do reverse_relationships: %Ecto.Association.NotLoaded{}
} = model
) do
model model
|> @repo.preload([:relationships, :reverse_relationships]) |> @repo.preload([:relationships, :reverse_relationships])
end end
def load_relationships(%Friends.Friend{} = friend), do: friend def load_relationships(%Friends.Friend{} = friend), do: friend
def load_preloads(%Friends.Friend{ def load_preloads(
relationships: %Ecto.Association.NotLoaded{}, %Friends.Friend{
reverse_relationships: %Ecto.Association.NotLoaded{}, relationships: %Ecto.Association.NotLoaded{},
user: %Ecto.Association.NotLoaded{}} = model) do reverse_relationships: %Ecto.Association.NotLoaded{},
user: %Ecto.Association.NotLoaded{}
} = model
) do
model model
|> @repo.preload([:user,:relationships, :reverse_relationships]) |> @repo.preload([:user, :relationships, :reverse_relationships])
end end
def load_preloads(%Friends.Friend{} = friend), do: friend
def load_preloads(%Friends.Friend{} = friend), do: friend
end end

View File

@ -1,13 +1,12 @@
defmodule FriendsWeb.FriendController do defmodule FriendsWeb.FriendsController do
use FriendsWeb, :controller use FriendsWeb, :controller
alias Friends.{Friend, Relationship} alias Friends.{Friend, Relationship}
alias Friends.Accounts.User alias Friends.Accounts.User
import Helpers import Helpers
def index(conn, _params) do def index(conn, _params) do
conn conn
|> assign(:all_friends, Friend.all) |> assign(:all_friends, Friend.all())
|> render("index.html") |> render("index.html")
end end
end end

View File

@ -7,15 +7,14 @@ defmodule FriendsWeb.PageController do
def index(conn, _params) do def index(conn, _params) do
new_friend = Friend.new() |> Friend.changeset() new_friend = Friend.new() |> Friend.changeset()
if(conn|> get_session(:linked)) do if(conn |> get_session(:linked)) do
# If logged in and linked, redirect to friend_path :index # If logged in and linked, redirect to friend_path :index
conn |> redirect(to: FriendsWeb.Router.Helpers.friend_path(conn, :index)) conn |> redirect(to: FriendsWeb.Router.Helpers.friends_path(conn, :index))
else else
# Otherwise, show the landing page # Otherwise, show the landing page
conn conn
|> assign(:new_friend, new_friend) |> assign(:new_friend, new_friend)
|> assign(:all_friends, Friend.all()) |> assign(:friends, Friend.all())
|> assign(:users, User |> Repo.all())
|> render("index.html") |> render("index.html")
end end
end end

View File

@ -128,18 +128,21 @@ defmodule FriendsWeb.UserAuth do
end end
@doc """ @doc """
Used when a user is signed in and should therefore have a profile linked. Used to detect or link a profile to a user.
""" """
def capture_profile(conn, _opts) do def capture_profile(conn, _opts) do
current_user = conn.assigns[:current_user] current_user = conn.assigns[:current_user]
if current_user do if current_user do
# There's a user, but is there a profile? # There's a user, but is there a profile?
profile = current_user |> Map.get(:profile) profile = current_user |> Map.get(:profile)
case profile do case profile do
nil -> nil ->
# No profile linked # No profile linked
conn conn
|> create_or_link_profile(current_user) |> create_or_link_profile(current_user)
_profile -> _profile ->
# Profile already linked # Profile already linked
conn |> put_session(:linked, true) conn |> put_session(:linked, true)
@ -151,31 +154,31 @@ defmodule FriendsWeb.UserAuth do
end end
defp create_or_link_profile(conn, user) do defp create_or_link_profile(conn, user) do
case user.email |> Friends.Friend.get_by_email do case user.email |> Friends.Friend.get_by_email() do
# Find a profile and link it # Find a profile and link it
nil -> nil ->
conn conn
|> redirect(to: Routes.profile_form_path(conn, :new)) |> put_flash(:info, "You're logged in but we still need to make you a profile!")
|> redirect(to: Routes.friends_edit_path(conn, :overview, :new))
|> halt() |> halt()
# Or make a new one # Or make a new one
profile -> _profile ->
user user
|> Friends.Accounts.assign_profile |> Friends.Accounts.assign_profile()
conn conn
|> put_session(:linked, true) |> put_session(:linked, true)
end end
end end
@doc """ @doc """
Used for routes that require the user to be authenticated. Used for routes that require the user to be authenticated.
If you want to enforce the user email is confirmed before If you want to enforce the user email is confirmed before
they use the application at all, here would be a good place. they use the application at all, here would be a good place.
""" """
def require_authenticated_user(conn, _opts) do def require_authenticated_user(conn, _opts \\ nil) do
if conn.assigns[:current_user] do if conn.assigns[:current_user] do
conn conn
else else

View File

@ -4,7 +4,30 @@ defmodule FriendsWeb.FriendsLive.Components do
import Helpers import Helpers
alias Friends.Friend alias Friends.Friend
def show_page("overview", assigns) do def header(assigns) do
~H"""
<div class="border-b-4 flex flex-row justify-between">
<h1 class="mb-0 pl-2" style="height:48px; text-indent:5px"><%= @friend.name %></h1>
</div>
"""
end
def menu(assigns) do
~H"""
<div class="hidden sm:tabs sm:mb-8">
<%= for page <- [:overview, :timeline, :relationships] do %>
<% is_active = if(page == @live_action) do "tab-active" end %>
<.link patch={Routes.friends_show_path(FriendsWeb.Endpoint, page, @friend.slug)} class={"font-bold sm:tab-lg flex-grow no-underline tab tab-lifted #{is_active}"}>
<%= page |> to_string |> :string.titlecase() %>
</.link>
<% end %>
</div>
"""
end
def show_page(:main, assigns), do: show_page(:overview, %{assigns | live_action: :overview})
def show_page(:overview, assigns) do
~H""" ~H"""
<ul class="py-4 pl-0 md:text-xl h-1/2"> <ul class="py-4 pl-0 md:text-xl h-1/2">
<li class="flex flex-row mb-8 gap-6"> <li class="flex flex-row mb-8 gap-6">
@ -32,18 +55,17 @@ defmodule FriendsWeb.FriendsLive.Components do
<strong class="w-28 text-right">Phone:</strong> <strong class="w-28 text-right">Phone:</strong>
<div class=""><%= @friend.phone %></div> <div class=""><%= @friend.phone %></div>
</li> </li>
<li class="flex flex-row mb-8 gap-6">
<strong class="w-28 text-right">Address:</strong>
<div class=""><%= @friend.address %></div>
</li>
</ul> </ul>
<%= if @friend |> Friend.can_be_edited_by(@current_user) do %>
<div class="form-control flex flex-row mb-4">
<.link patch={"/friend/#{@friend.slug}/#{@page_view}/edit"} class="btn btn-block md:btn-wide text-white">edit</.link>
</div>
<% end %>
""" """
end end
def show_page("calendar", assigns) do def show_page(:timeline, assigns) do
~H""" ~H"""
<div id="calendar" class="flex md:flex-row flex-col gap-8 p-8"> <div id="timeline" class="flex md:flex-row flex-col gap-8 p-8">
<%= for event <- @friend |> Friends.Friend.get_events do %> <%= for event <- @friend |> Friends.Friend.get_events do %>
<ul> <ul>
<li> <li>
@ -58,7 +80,7 @@ defmodule FriendsWeb.FriendsLive.Components do
""" """
end end
def show_page("relationships", assigns) do def show_page(:relationships, assigns) do
~H""" ~H"""
<div id="relationships" class="flex md:flex-row flex-col gap-8"> <div id="relationships" class="flex md:flex-row flex-col gap-8">
<%= for relation <- @friend |> relations do %> <%= for relation <- @friend |> relations do %>
@ -80,15 +102,12 @@ defmodule FriendsWeb.FriendsLive.Components do
<div class="italic p-4">No relationships on record yet.</div> <div class="italic p-4">No relationships on record yet.</div>
<% end %> <% end %>
</div> </div>
<div class="form-control flex flex-row my-8">
<.link patch={"/friend/#{@friend.slug}/update?page=relationships"} class="btn btn-block md:btn-wide text-white">edit</.link>
</div>
""" """
end end
### ###
def form_page("overview", assigns) do def edit_page(:overview, assigns) do
~H""" ~H"""
<.form <.form
for={@changeset} for={@changeset}
@ -140,7 +159,7 @@ defmodule FriendsWeb.FriendsLive.Components do
<div class="form-control flex flex-row gap-x-4 md:justify-end mb-4 md:w-1/2"> <div class="form-control flex flex-row gap-x-4 md:justify-end mb-4 md:w-1/2">
<div class="flex-1"> <div class="flex-1">
<.link patch={"/friend/#{@friend.slug}"} class="btn btn-block btn-outline">back</.link> <.link patch={Routes.friends_show_path(FriendsWeb.Endpoint, :overview, @friend.slug)} class="btn btn-block btn-outline">back</.link>
</div> </div>
<div class="flex-1"> <div class="flex-1">
<%= if @changeset.valid? do %> <%= if @changeset.valid? do %>
@ -159,9 +178,9 @@ defmodule FriendsWeb.FriendsLive.Components do
""" """
end end
def form_page("relationships", assigns) do def edit_page(:relationships, assigns) do
~H""" ~H"""
<%= FriendsWeb.FriendLive.Friend.header(assigns) %> <%= Components.header(assigns) %>
""" """
end end
end end

View File

@ -1,7 +1,7 @@
defmodule FriendsWeb.FriendsLive.Friend do defmodule FriendsWeb.FriendsLive.Friend do
use FriendsWeb, :live_view use FriendsWeb, :live_view
alias FriendsWeb.FriendLive.Components alias FriendsWeb.FriendsLive.Components
alias FriendsWeb.Router.Helpers, as: Routes alias FriendsWeb.Router.Helpers, as: Routes
alias Friends.Friend alias Friends.Friend
@ -18,32 +18,6 @@ defmodule FriendsWeb.FriendsLive.Friend do
|> assign(:changeset, %Friend{} |> Friend.changeset())} |> assign(:changeset, %Friend{} |> Friend.changeset())}
end end
# Show Friend
def handle_params(%{"slug" => slug} = attrs, _url, socket) do
friend = Friend.get_by_slug(slug)
page =
if (attrs |> Map.get("page")) in ["overview", "calendar", "relationships"] do
attrs["page"]
else
"redir"
end
if page == "redir" do
{:noreply,
socket
|> push_patch(to: "/friend/#{slug}/overview")}
else
{:noreply,
socket
|> title(friend.name <> " - " <> (page |> :string.titlecase()))
|> assign_friend(friend)
|> page_view(page)
|> assign(:current_user, nil)
|> assign(:action, Routes.friend_path(socket, :update, friend.id))}
end
end
# New Friend # New Friend
def handle_params(_attrs, _token, socket) do def handle_params(_attrs, _token, socket) do
friend = Friend.new() friend = Friend.new()
@ -52,8 +26,7 @@ defmodule FriendsWeb.FriendsLive.Friend do
socket socket
|> title("New Friend") |> title("New Friend")
|> assign_friend(friend) |> assign_friend(friend)
|> page_view("overview") |> assign(:action, Routes.friends_path(socket, :create))}
|> assign(:action, Routes.friend_path(socket, :create))}
end end
# Handle form validation # Handle form validation
@ -134,57 +107,5 @@ defmodule FriendsWeb.FriendsLive.Friend do
|> push_navigate(to: "/")} |> push_navigate(to: "/")}
end end
# Set variables on page: friend, changeset, relationships
def assign_friend(socket, friend) do
socket
|> assign(:friend, friend)
|> assign(:changeset, friend |> Friend.changeset())
|> assign(:relationships, friend.relationships)
end
# Same thing, but this time we have a changeset we want to keep
def assign_friend(socket, friend, changeset) do
socket
|> assign(:friend, friend)
|> assign(:changeset, changeset)
|> assign(:relationships, friend.relationships)
end
# Route to the right sub-template in Components/Components.ex # Route to the right sub-template in Components/Components.ex
def content(assigns) do
~H"""
<%= if @live_action != :new do %>
<%= FriendsWeb.FriendsLive.Friend.menu(assigns) %>
<% end %>
<%= cond do %>
<% @live_action == :show -> %>
<%= header(assigns) %>
<%= @page_view |> Components.show_page(assigns) %>
<% @live_action in [:edit, :new] -> %>
<%= @page_view |> Components.form_page(assigns) %>
<% end %>
"""
end
def header(assigns) do
~H"""
<div class="border-b-4 flex flex-row justify-between">
<h1 class="mb-0 pl-2" style="height:48px; text-indent:5px"><%= @friend.name %></h1>
</div>
"""
end
def menu(assigns) do
~H"""
<div class="hidden sm:tabs sm:mb-8">
<%= for page <- ["overview", "calendar", "relationships"] do %>
<% is_active = if(page == @page_view) do "tab-active" end %>
<.link patch={"/friend/#{@friend.slug}/#{page}"} class={"font-bold sm:tab-lg flex-grow no-underline tab tab-lifted #{is_active}"}>
<%= page |> :string.titlecase() %>
</.link>
<% end %>
</div>
"""
end
end end

View File

@ -1,3 +0,0 @@
defmodule FriendsWeb.FriendLive do
use FriendsWeb, :live_view
end

View File

@ -0,0 +1,3 @@
defmodule FriendsWeb.FriendsLive do
use FriendsWeb, :live_view
end

View File

@ -1,31 +1,40 @@
""" defmodule FriendsWeb.FriendsLive.Show do
defmodule FriendsWeb.FriendLive.Show do
use FriendsWeb, :live_view use FriendsWeb, :live_view
import FriendsWeb.LiveHelpers
alias Friends.Friend alias Friends.Friend
alias FriendsWeb.FriendsLive.Components
def handle_params(attrs, token, socket) do def mount(%{"slug" => slug} = _attrs, token, socket) do
FriendsWeb.FriendLive.Friend.handle_params(attrs, token, socket) live_action = socket.assigns.live_action || false
friend = Friend.get_by_slug(slug)
editable = friend |> Friend.can_be_edited_by(socket.assigns[:current_user])
if(live_action) do
{:ok,
socket
|> assign(:live_action, live_action)
|> assign_current_user(token |> Map.get("user_token"))
|> assign(:friend, friend)
|> title(friend.name <> " - " <> (live_action |> titlecase))
|> assign(:changeset, %Friend{} |> Friend.changeset())
|> assign(:action, editable)}
else
{:ok, socket |> redirect(to: Routes.friends_show_path(socket, :overview, friend.slug))}
end
end end
def mount(%{"slug" => slug} = attrs, token, socket) do def handle_params(%{"slug" => slug} = attrs, _url, socket) do
friend = slug |> Friends.Friend.get_by_slug() live_action = socket.assigns.live_action || false
page = friend = Friend.get_by_slug(slug)
if (attrs |> Map.get("page")) in ["overview", "calendar", "relationships"] do editable = friend |> Friend.can_be_edited_by(socket.assigns[:current_user])
attrs["page"]
else
"overview"
end
{:ok, {:noreply,
socket socket
|> title(friend.name <> " - " <> (page |> :string.titlecase())) |> assign_friend(friend)
|> page_view(page) |> assign(:live_action, live_action)
|> assign_current_user(token |> Map.get("user_token")) |> title(friend.name <> " - " <> (live_action |> titlecase))
|> assign(:friend, friend) |> assign(:editable, editable)}
|> assign(:changeset, %Friend{} |> Friend.changeset())
|> assign(:action, Routes.friend_path(socket, :update, friend.id))}
end end
end end
"""

View File

@ -1,5 +1,13 @@
<section class="row"> <section class="row">
<article class="column prose"> <article class="column prose">
<FriendsWeb.FriendLive.Friend.content friend={@friend} page_view={@page_view} changeset={@changeset} action={@action} live_action={@live_action}/> <%= Components.menu(assigns) %>
<%= Components.header(assigns) %>
<%= Components.show_page(@live_action, assigns) %>
<%= if @editable do %>
<div class="form-control flex flex-row mb-4">
<.link patch={Routes.friends_edit_path(FriendsWeb.Endpoint, :overview, @friend.slug)} class="btn btn-block md:btn-wide text-white">edit</.link>
</div>
<% end %>
</article> </article>
</section> </section>

View File

@ -52,54 +52,63 @@ defmodule FriendsWeb.Router do
end end
## Authentication routes ## Authentication routes
# Routes that only work if user not authenticated
scope "/", FriendsWeb do scope "/users", FriendsWeb do
pipe_through [:browser, :redirect_if_user_is_authenticated] pipe_through [:browser, :redirect_if_user_is_authenticated]
# Requires the user NOT be authenticated: get "/register", UserRegistrationController, :new
post "/register", UserRegistrationController, :create
get "/users/register", UserRegistrationController, :new get "/log_in", UserSessionController, :new
post "/users/register", UserRegistrationController, :create post "/log_in", UserSessionController, :create
get "/users/log_in", UserSessionController, :new get "/reset_password", UserResetPasswordController, :new
post "/users/log_in", UserSessionController, :create post "/reset_password", UserResetPasswordController, :create
get "/users/reset_password", UserResetPasswordController, :new get "/reset_password/:token", UserResetPasswordController, :edit
post "/users/reset_password", UserResetPasswordController, :create put "/reset_password/:token", UserResetPasswordController, :update
get "/users/reset_password/:token", UserResetPasswordController, :edit
put "/users/reset_password/:token", UserResetPasswordController, :update
end end
scope "/", FriendsWeb do # Confirmation and logout
pipe_through [:browser, :require_authenticated_user] scope "/users", FriendsWeb do
# Requires the user DO be authenticated:
live "/profile/new", ProfileLive.Form, :new
get "/users/settings", UserSettingsController, :edit
put "/users/settings", UserSettingsController, :update
get "/users/settings/confirm_email/:token", UserSettingsController, :confirm_email
end
scope "/", FriendsWeb do
pipe_through [:browser] pipe_through [:browser]
delete "/log_out", UserSessionController, :delete
delete "/users/log_out", UserSessionController, :delete get "/confirm", UserConfirmationController, :new
get "/users/confirm", UserConfirmationController, :new post "/confirm", UserConfirmationController, :create
post "/users/confirm", UserConfirmationController, :create get "/confirm/:token", UserConfirmationController, :edit
get "/users/confirm/:token", UserConfirmationController, :edit post "/confirm/:token", UserConfirmationController, :update
post "/users/confirm/:token", UserConfirmationController, :update
end end
# Routes that require the user be authenticated:
scope "/users/settings", FriendsWeb do
pipe_through [:browser, :require_authenticated_user]
get "/", UserSettingsController, :edit
put "/", UserSettingsController, :update
get "/confirm_email/:token", UserSettingsController, :confirm_email
end
# THE ACTUAL GUTS OF THE APP # THE ACTUAL GUTS OF THE APP
scope "/", FriendsWeb do scope "/", FriendsWeb do
pipe_through [:browser, :capture_profile] pipe_through [:browser, :capture_profile]
get "/", PageController, :index get "/", PageController, :index
get "/friends", FriendController, :index
live "/friend/:slug", FriendLive.Show
live "/friend/:slug/edit", FriendLive.Edit
end end
# View-only modes (don't require being logged in and having a profile)
scope "/friends", FriendsWeb do
pipe_through [:browser]
get "/", FriendsController, :index
live "/:slug", FriendsLive.Show
live "/:slug/overview", FriendsLive.Show, :overview
live "/:slug/timeline", FriendsLive.Show, :timeline
live "/:slug/relationships", FriendsLive.Show, :relationships
end
# Edit modes (require being logged in and having a profile)
scope "/update/", FriendsWeb do
pipe_through [:browser, :require_authenticated_user, :capture_profile]
live "/:slug/overview", FriendsLive.Edit, :overview
live "/:slug/timeline", FriendsLive.Edit, :timeline
live "/:slug/relationships", FriendsLive.Edit, :relationships
post "/:slug/update", FriendsController, :update
end
end end

View File

@ -3,7 +3,7 @@
<ul class="text-xl"> <ul class="text-xl">
<%= for f <- @all_friends do %> <%= for f <- @all_friends do %>
<li> <li>
<.link href={"/friend/#{f.slug}"}><%= f.name %></.link> <.link href={Routes.friends_show_path(@conn, :overview, f.slug)}><%= f.name %></.link>
<%= if @current_user do %> <%= if @current_user do %>
<%= if f.id == @current_user.profile.id do %> <%= if f.id == @current_user.profile.id do %>
(you) (you)
@ -14,6 +14,6 @@
<%= if @current_user do %> <%= if @current_user do %>
<div class="m-4 mt-16"> <div class="m-4 mt-16">
<.link href="/friends/new" class="btn btn-primary">Add Friend</.link> <.link href={Routes.friends_edit_path(@conn, :overview, :new)}>Add Friend</.link>
</div> </div>
<% end %> <% end %>

View File

@ -9,7 +9,7 @@
and basically go back to the 2011 Facebook we all miss. and basically go back to the 2011 Facebook we all miss.
</p> </p>
<div class="hidden md:block"> <div class="hidden md:block">
<.db_stats all_friends={@all_friends} users={@users} /> <.friends_list friends={@friends} />
</div> </div>
</div> </div>
<div id="sign-up" class="prose card md:w-fit bg-neutral-content text-neutral shadow-xl items-center"> <div id="sign-up" class="prose card md:w-fit bg-neutral-content text-neutral shadow-xl items-center">
@ -20,7 +20,7 @@
<.sign_up new_friend={@new_friend} /> <.sign_up new_friend={@new_friend} />
</div> </div>
</div> </div>
<div id="stats" class="prose w-full mb-8 md:hidden"> <div id="friends" class="prose w-full mb-8 md:hidden">
<.db_stats all_friends={@all_friends} users={@users} /> <.friends_list friends={@friends} />
</div> </div>
</div> </div>

View File

@ -1,6 +1,5 @@
defmodule FriendsWeb.FriendView do defmodule FriendsWeb.FriendsView do
use FriendsWeb, :view use FriendsWeb, :view
import Phoenix.Component import Phoenix.Component
import Helpers import Helpers
end end

View File

@ -1,5 +1,10 @@
defmodule FriendsWeb.LiveHelpers do defmodule FriendsWeb.LiveHelpers do
use FriendsWeb, :live_component use FriendsWeb, :live_component
alias Friends.Friend
def titlecase(atom) do
atom |> to_string |> :string.titlecase()
end
def assign_current_user(socket, user_token) do def assign_current_user(socket, user_token) do
user = user =
@ -26,8 +31,19 @@ defmodule FriendsWeb.LiveHelpers do
|> assign(:page_title, title) |> assign(:page_title, title)
end end
# Set page_view variable # Set variables on page: friend, changeset, relationships
def page_view(socket, page) do def assign_friend(socket, friend) do
socket |> assign(:page_view, page) socket
|> assign(:friend, friend)
|> assign(:changeset, friend |> Friend.changeset())
|> assign(:relationships, friend.relationships)
end
# Same thing, but this time we have a changeset we want to keep
def assign_friend(socket, friend, changeset) do
socket
|> assign(:friend, friend)
|> assign(:changeset, changeset)
|> assign(:relationships, friend.relationships)
end end
end end

View File

@ -6,24 +6,25 @@ defmodule FriendsWeb.PageView do
alias FriendsWeb.Components.SignUp, as: SignUp alias FriendsWeb.Components.SignUp, as: SignUp
def sign_up(assigns), do: SignUp.sign_up_form(assigns) def sign_up(assigns), do: SignUp.sign_up_form(assigns)
def db_stats(assigns) do
def friends_list(assigns) do
~H""" ~H"""
<h3 class="mt-12 border-b-2"> <h3 class="mt-12 border-b-2">
Database stats All Friends
</h3> </h3>
<ul> <ul>
<li> <%= for friend <- @friends do %>
<.link patch="/friends" class=""> <li>
<%= pluralize(@all_friends |> length, "friend") %> <div id={"friend-#{friend.id}"} class="">
</.link> <div class="">
</li> <h3 class="">
<li> <.link href={Routes.live_path(FriendsWeb.Endpoint, FriendsWeb.FriendsLive.Show, friend.slug)}><%= friend.name %></.link>
<.link patch="/users" class=""> </h3>
<%= pluralize(@users |> length, "user") %> </div>
</.link> </div>
</li> </li>
</ul> <% end %>
""" </ul>
"""
end end
end end

View File

@ -0,0 +1,9 @@
defmodule Friends.Repo.Migrations.AddAddressesToFriends do
use Ecto.Migration
def change do
alter table(:friends) do
add :address, :string
end
end
end

View File

@ -3,20 +3,22 @@ defmodule Friends.FriendsTest do
alias Friends.Friend alias Friends.Friend
import Friends.{AccountsFixtures,FriendsFixtures} import Friends.{AccountsFixtures, FriendsFixtures}
# alias Friends.Accounts.{User, UserToken} # alias Friends.Accounts.{User, UserToken}
describe "new/1" do describe "new/1" do
test "no params" do test "no params" do
f = Friend.new() f = Friend.new()
assert f.id == :new assert f.id == :new
end end
test "with params" do test "with params" do
attrs = valid_friend_attributes() attrs = valid_friend_attributes()
f = attrs |> Friend.new() f = attrs |> Friend.new()
assert f.id == :new assert f.id == :new
assert f.name == attrs |> Map.get(:name) assert f.name == attrs |> Map.get(:name)
end end
test "has no loaded preloads" do test "has no loaded preloads" do
f = Friend.new() f = Friend.new()
assert %Ecto.Association.NotLoaded{} = f.user assert %Ecto.Association.NotLoaded{} = f.user
@ -27,19 +29,22 @@ defmodule Friends.FriendsTest do
describe "commit/1" do describe "commit/1" do
test "changes id" do test "changes id" do
f = valid_friend_attributes() f =
|> Friend.create() valid_friend_attributes()
|> Friend.commit() |> Friend.create()
|> Friend.commit()
assert f.id != :new assert f.id != :new
end end
test "generates slug" do
f = valid_friend_attributes()
|> Friend.create()
|> Friend.commit()
assert f.slug != :nil test "generates slug" do
assert f.slug == f.name |> Helpers.to_slug f =
valid_friend_attributes()
|> Friend.create()
|> Friend.commit()
assert f.slug != nil
assert f.slug == f.name |> Helpers.to_slug()
end end
end end
@ -48,38 +53,41 @@ defmodule Friends.FriendsTest do
f = valid_friend_attributes() |> Friend.new() f = valid_friend_attributes() |> Friend.new()
assert f.slug == nil assert f.slug == nil
end end
test "generate_slug generates a slug, returns a friend" do test "generate_slug generates a slug, returns a friend" do
f = valid_friend_attributes() |> Friend.new() |> Friend.generate_slug f = valid_friend_attributes() |> Friend.new() |> Friend.generate_slug()
assert f.slug == f.name |> Helpers.to_slug assert f.slug == f.name |> Helpers.to_slug()
end end
test "generate_slug generates a slug, returns a changeset" do test "generate_slug generates a slug, returns a changeset" do
c = valid_friend_attributes() |> Friend.create() |> Friend.generate_slug c = valid_friend_attributes() |> Friend.create() |> Friend.generate_slug()
f = c.data f = c.data
assert f.slug == f.name |> Helpers.to_slug assert f.slug == f.name |> Helpers.to_slug()
end end
end end
describe "assign_user/1" do describe "assign_user/1" do
setup do setup do
friend = friend_fixture() friend = friend_fixture()
%{
friend: friend,
user: user_fixture(%{email: friend.email})
}
end
test "links a user to a friend", %{user: user, friend: friend} do
assert user.profile == nil
assert friend.user == nil
%{ %{
friend: new_friend, friend: friend,
user: new_user user: user_fixture(%{email: friend.email})
} = friend |> Friends.Friend.assign_user }
end
assert new_user.profile.id == friend.id test "links a user to a friend", %{user: user, friend: friend} do
assert new_friend.user.id == user.id assert user.profile == nil
assert friend.user == nil
end %{
friend: new_friend,
user: new_user
} = friend |> Friends.Friend.assign_user()
assert new_user.profile.id == friend.id
assert new_friend.user.id == user.id
end
end end
describe "preloads" do describe "preloads" do
@ -89,32 +97,30 @@ defmodule Friends.FriendsTest do
test "default nothing loaded", %{friend: friend} do test "default nothing loaded", %{friend: friend} do
f = friend f = friend
refute f.user |> Ecto.assoc_loaded? refute f.user |> Ecto.assoc_loaded?()
refute f.relationships |> Ecto.assoc_loaded? refute f.relationships |> Ecto.assoc_loaded?()
refute f.reverse_relationships |> Ecto.assoc_loaded? refute f.reverse_relationships |> Ecto.assoc_loaded?()
end end
test "load user", %{friend: friend} do test "load user", %{friend: friend} do
f = friend |> Friend.load_user f = friend |> Friend.load_user()
assert f.user |> Ecto.assoc_loaded? assert f.user |> Ecto.assoc_loaded?()
refute f.relationships |> Ecto.assoc_loaded? refute f.relationships |> Ecto.assoc_loaded?()
refute f.reverse_relationships |> Ecto.assoc_loaded? refute f.reverse_relationships |> Ecto.assoc_loaded?()
end end
test "load relationships", %{friend: friend} do test "load relationships", %{friend: friend} do
f = friend |> Friend.load_relationships f = friend |> Friend.load_relationships()
refute f.user |> Ecto.assoc_loaded? refute f.user |> Ecto.assoc_loaded?()
assert f.relationships |> Ecto.assoc_loaded? assert f.relationships |> Ecto.assoc_loaded?()
assert f.reverse_relationships |> Ecto.assoc_loaded? assert f.reverse_relationships |> Ecto.assoc_loaded?()
end end
test "load all", %{friend: friend} do test "load all", %{friend: friend} do
f = friend |> Friend.load_preloads f = friend |> Friend.load_preloads()
assert f.user |> Ecto.assoc_loaded? assert f.user |> Ecto.assoc_loaded?()
assert f.relationships |> Ecto.assoc_loaded? assert f.relationships |> Ecto.assoc_loaded?()
assert f.reverse_relationships |> Ecto.assoc_loaded? assert f.reverse_relationships |> Ecto.assoc_loaded?()
end end
end end
end end

View File

@ -1,19 +1,15 @@
defmodule FriendsWeb.FriendControllerTest do defmodule FriendsWeb.FriendsControllerTest do
use FriendsWeb.ConnCase, async: true use FriendsWeb.ConnCase, async: true
import Friends.{AccountsFixtures,FriendsFixtures} import Friends.{AccountsFixtures, FriendsFixtures}
setup do setup do
%{ %{
user: _user, user: _user,
friend: _friend friend: _friend
} = } = friend_fixture(%{email: user_fixture().email}) |> Friends.Friend.assign_user()
friend_fixture(
%{email: user_fixture().email}
) |> Friends.Friend.assign_user
end end
describe "GET '/friends'" do describe "GET '/friends'" do
test "shows the friends dashboard", %{conn: conn, friend: friend} do test "shows the friends dashboard", %{conn: conn, friend: friend} do
conn = get(conn, "/friends") conn = get(conn, "/friends")
assert html_response(conn, 200) =~ friend.name assert html_response(conn, 200) =~ friend.name
@ -27,6 +23,9 @@ defmodule FriendsWeb.FriendControllerTest do
assert html_response(conn, 200) =~ "Log out" assert html_response(conn, 200) =~ "Log out"
end end
test "shows option to add friend if logged in", %{conn: conn, friend: friend, user: user} do
conn = conn |> log_in_user(user) |> get("/friends")
assert html_response(conn, 200) =~ Routes.friends_edit_path(conn, :overview, :new)
end
end end
end end

View File

@ -0,0 +1,18 @@
defmodule FriendsWeb.FriendsLiveTest do
use FriendsWeb.ConnCase, async: true
import Friends.{AccountsFixtures, FriendsFixtures}
setup do
%{
user: _user,
friend: _friend
} = friend_fixture(%{email: user_fixture().email}) |> Friends.Friend.assign_user()
end
describe "GET '/friends/:slug'" do
test "shows the friend overview", %{conn: conn, friend: friend} do
conn = conn |> get("/friends/#{friend.slug}")
assert html_response(conn, 200) =~ friend.name
end
end
end

View File

@ -1,6 +1,6 @@
defmodule FriendsWeb.PageControllerTest do defmodule FriendsWeb.PageControllerTest do
use FriendsWeb.ConnCase, async: true use FriendsWeb.ConnCase, async: true
import Friends.{AccountsFixtures,FriendsFixtures} import Friends.{AccountsFixtures, FriendsFixtures}
alias FriendsWeb.Router.Helpers, as: Routes alias FriendsWeb.Router.Helpers, as: Routes
@ -16,17 +16,23 @@ defmodule FriendsWeb.PageControllerTest do
assert html_response(conn, 200) =~ "Register" assert html_response(conn, 200) =~ "Register"
end end
test "redirects to the new profile flow if logged in but has no profile", %{conn: conn, user: user} do test "redirects to the new profile flow if logged in but has no profile", %{
conn: conn,
user: user
} do
_friend = friend_fixture(%{email: "random_email@invalid.biz"}) _friend = friend_fixture(%{email: "random_email@invalid.biz"})
conn = conn |> log_in_user(user) |> get("/") conn = conn |> log_in_user(user) |> get("/")
assert redirected_to(conn) == Routes.profile_form_path(conn, :new) assert redirected_to(conn) == Routes.friends_edit_path(conn, :overview, :new)
assert get_flash(conn, :info) =~ "profile!"
end end
test "redirects to the friends dashboard if logged in & has a profile", %{conn: conn, user: user} do test "redirects to the friends dashboard if logged in & has a profile", %{
conn: conn,
user: user
} do
_friend = friend_fixture(%{email: user.email}) _friend = friend_fixture(%{email: user.email})
conn = conn |> log_in_user(user) |> get("/") conn = conn |> log_in_user(user) |> get("/")
assert redirected_to(conn) == Routes.friend_path(conn, :index) assert redirected_to(conn) == Routes.friends_path(conn, :index)
end end
end end
end end