From 80887240070c2baca866cf6a74d051eefa14eab4 Mon Sep 17 00:00:00 2001 From: Amos Kibet Date: Fri, 16 Aug 2024 14:10:44 +0300 Subject: [PATCH] Enable SEO (#18) --- lib/phx_tools_web.ex | 1 + .../components/layouts/root.html.heex | 1 + .../components/seo_meta_tag_component.ex | 80 ++++++++++++++++++ .../live/phx_tools_live/index.ex | 20 ++++- priv/static/images/phx_tools.png | Bin 0 -> 21944 bytes .../seo_meta_tag_component_test.exs | 60 +++++++++++++ 6 files changed, 160 insertions(+), 2 deletions(-) create mode 100644 lib/phx_tools_web/components/seo_meta_tag_component.ex create mode 100644 priv/static/images/phx_tools.png create mode 100644 test/phx_tools_web/components/seo_meta_tag_component_test.exs diff --git a/lib/phx_tools_web.ex b/lib/phx_tools_web.ex index 5c1c845a4..529e54cf3 100644 --- a/lib/phx_tools_web.ex +++ b/lib/phx_tools_web.ex @@ -73,6 +73,7 @@ defmodule PhxToolsWeb do use Phoenix.Component alias PhxToolsWeb.Components.SharedComponents + alias PhxToolsWeb.SeoMetaTagComponent # Import convenience functions from controllers import Phoenix.Controller, diff --git a/lib/phx_tools_web/components/layouts/root.html.heex b/lib/phx_tools_web/components/layouts/root.html.heex index b7e0eef62..3f4ec7c8e 100644 --- a/lib/phx_tools_web/components/layouts/root.html.heex +++ b/lib/phx_tools_web/components/layouts/root.html.heex @@ -4,6 +4,7 @@ + <.live_title> <%= assigns[:page_title] || "PhxTools" %> diff --git a/lib/phx_tools_web/components/seo_meta_tag_component.ex b/lib/phx_tools_web/components/seo_meta_tag_component.ex new file mode 100644 index 000000000..efa2a543e --- /dev/null +++ b/lib/phx_tools_web/components/seo_meta_tag_component.ex @@ -0,0 +1,80 @@ +defmodule PhxToolsWeb.SeoMetaTagComponent do + @moduledoc false + + use PhxToolsWeb, :html + + alias PhxToolsWeb.Endpoint + + @type assigns :: map() + @type rendered :: rendered() + + @default_description """ + Phx.tools is a shell script for Linux and macOS that configures + the development environment for you in a few easy steps. + Once you finish running the script, you'll be able to start the database server, + create a new Phoenix application, and launch the server... + """ + + attr :attributes, :map + + @spec seo_meta_tags(assigns()) :: rendered() + def seo_meta_tags(assigns) do + assigns = + assigns + |> assign_new(:description, fn -> String.trim(@default_description) end) + |> assign_new(:image_url, fn -> static_url(Endpoint, ~p"/images/phx_tools.png") end) + |> assign_new(:url, fn + %{attributes: %{url: url}} -> url + _assigns -> url(~p"/") + end) + + ~H""" + <.open_graph_meta_tags description={@description} image_url={@image_url} url={@url} /> + <.other_meta_tags + description={@description} + keywords="elixir, erlang, homebrew, mise, phoenix, postgres, postgresql" + /> + <.twitter_meta_tags description={@description} image_url={@image_url} url={@url} /> + """ + end + + attr :description, :string, required: true + attr :image_url, :string, required: true + attr :url, :string, required: true + + defp open_graph_meta_tags(assigns) do + ~H""" + + + + + + """ + end + + attr :description, :string, required: true + attr :keywords, :string, required: true + + defp other_meta_tags(assigns) do + ~H""" + + + """ + end + + attr :description, :string, required: true + attr :image_url, :string, required: true + attr :url, :string, required: true + + defp twitter_meta_tags(assigns) do + ~H""" + + + + + + + + """ + end +end diff --git a/lib/phx_tools_web/live/phx_tools_live/index.ex b/lib/phx_tools_web/live/phx_tools_live/index.ex index 751618626..fc8753bf1 100644 --- a/lib/phx_tools_web/live/phx_tools_live/index.ex +++ b/lib/phx_tools_web/live/phx_tools_live/index.ex @@ -6,11 +6,27 @@ defmodule PhxToolsWeb.PhxToolsLive.Index do alias PhxToolsWeb.PhxToolsLive.LandingComponent @impl Phoenix.LiveView - def mount(_params, session, socket), do: {:ok, assign_os(socket, session)} + def mount(_params, session, socket) do + {:ok, + socket + |> assign(:seo_attributes, %{}) + |> assign_os(session)} + end defp assign_os(socket, %{"operating_system" => operating_system}), do: assign(socket, :operating_system, operating_system) @impl Phoenix.LiveView - def handle_params(_params, _uri, socket), do: {:noreply, socket} + def handle_params(_params, _uri, socket), + do: {:noreply, apply_action(socket, socket.assigns.live_action)} + + defp apply_action(socket, :linux) do + assign(socket, seo_attributes: %{url: url(~p"/linux")}) + end + + defp apply_action(socket, :macOS) do + assign(socket, seo_attributes: %{url: url(~p"/macOS")}) + end + + defp apply_action(socket, _index), do: socket end diff --git a/priv/static/images/phx_tools.png b/priv/static/images/phx_tools.png new file mode 100644 index 0000000000000000000000000000000000000000..c12d222cf2cdd2376c24ac50ce8bb6c3f8391153 GIT binary patch literal 21944 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 G68{S+k3p>f literal 0 HcmV?d00001 diff --git a/test/phx_tools_web/components/seo_meta_tag_component_test.exs b/test/phx_tools_web/components/seo_meta_tag_component_test.exs new file mode 100644 index 000000000..67106c3b2 --- /dev/null +++ b/test/phx_tools_web/components/seo_meta_tag_component_test.exs @@ -0,0 +1,60 @@ +defmodule PhxToolsWeb.SeoMetaTagComponentTest do + use PhxToolsWeb.ConnCase, async: true + + import Phoenix.LiveViewTest + + alias PhxToolsWeb.SeoMetaTagComponent + + describe "seo_meta_tags/1" do + test "renders meta tags with default values" do + assert render_component(&SeoMetaTagComponent.seo_meta_tags/1, attributes: nil) =~ + "" + + assert render_component(&SeoMetaTagComponent.seo_meta_tags/1, attributes: nil) =~ + "" + + assert render_component(&SeoMetaTagComponent.seo_meta_tags/1, attributes: nil) =~ + "" + + assert render_component(&SeoMetaTagComponent.seo_meta_tags/1, attributes: nil) =~ + "" + + assert render_component(&SeoMetaTagComponent.seo_meta_tags/1, attributes: nil) =~ + "" + + assert render_component(&SeoMetaTagComponent.seo_meta_tags/1, attributes: nil) =~ + "" + + assert render_component(&SeoMetaTagComponent.seo_meta_tags/1, attributes: nil) =~ + "" + + assert render_component(&SeoMetaTagComponent.seo_meta_tags/1, attributes: nil) =~ + "" + + assert render_component(&SeoMetaTagComponent.seo_meta_tags/1, attributes: nil) =~ + "" + + assert render_component(&SeoMetaTagComponent.seo_meta_tags/1, attributes: nil) =~ + "" + + assert render_component(&SeoMetaTagComponent.seo_meta_tags/1, attributes: nil) =~ + "" + + assert render_component(&SeoMetaTagComponent.seo_meta_tags/1, attributes: nil) =~ + "" + + assert render_component(&SeoMetaTagComponent.seo_meta_tags/1, attributes: nil) =~ + "" + end + + test "renders meta tags with the values passed" do + assigns = %{url: "http://sample.link"} + + assert render_component(&SeoMetaTagComponent.seo_meta_tags/1, attributes: assigns) =~ + "" + + assert render_component(&SeoMetaTagComponent.seo_meta_tags/1, attributes: assigns) =~ + "" + end + end +end