はじめに
この記事はElixirアドベントカレンダー2024のシリーズ2、8日目の記事です
アプリを初回起動時に表示されるWelcomeページとオンボーディングを実装していきます
今回の作業ブランチを作成します
git checkout -b feature/onboarding
UIライブラリの追加
Welcomeページを作る前にUIを実装するにあたって素のTailwindだと辛いのでライブラリを入れていこうと思います
UIコンポーネントライブラリの選定
PhoenixではデフォルトでCSSフレームワークでTailwindCSSが採用されています
ですが、PhoenixのCoreComponentとTailwindでUIを実装するのは大変なので、
UIコンポーネントライブラリを使用して、そのコストを抑えることができます
UIコンポーネントの選定は気をつけることがあって、
ReactやVue,SveltなどJSのフレームワークの上で動かすものが多くヒットするのでTailwind単体で動いて、JavaScriptを使用していないもの、またはLiveViewに対応している物を選ぶ必要があります
JavaScriptを使用していないものを選ぶのは、LiveViewでUI制御を行うときに干渉してしまうため調整が面倒だからです
Tailwind単体で動いて、JSを使用していないもの
こちらは2種類あります
- tailwindのユーティリティクラスだけで作成した実装サンプル集
- applyでcssのクラス的にまとめたコンポーネント集
大半は1です。実装の時にこのUIどう実装するのかがわかるので、参考にするのにも大変良いです
ですがこれだと、記述がどうしても多くなってしまいますので
いい感じにまとめてくれている2を心情的には使いたいのですが、該当するものはほとんどなく
いい感じに揃っているのはこちらです
今回はこれを使用します
LiveViewに対応している物を選ぶ必要があります
こちらは今年いくつか出てきたので気になった方は試してみてください
daisyuiをインストールします
JSのライブラリをインストールするときは assetsに移動する必要があるので注意が必要です
cd assets
npm i daisyui
cd ..
インストールしたら、pluginsに requireを追加します
module.exports = {
content: ["./js/**/*.js", "../lib/*_web.ex", "../lib/*_web/**/*.*ex"],
theme: {...},
plugins: [
require("@tailwindcss/forms"),
+ require("daisyui"),
...
],
UIコンポーネントの導入が終わったのでコミットします
git add .
git commit -m 'add daisyui'
Welcomeページ
次にWelcomeページを作成します
こちらは未ログイン時にアプリを起動した際に表示される画面で
ログインと新規登録の導線を表示させます
Welcomeページに表示するいい感じの写真をpriv/static/images
に入れます
phx.newで作られた初期ページhome.html.heex
を以下のように変更します
画像のパスは~p""
シジルで/images/ファイル名
とすることで展開してくれます
このコンポーネントを使用して画面を構築します
<div class="hero h-screen " style={"background-image: url(#{~p"/images/welcome.png"});"}>
<div class="hero-overlay bg-opacity-0"></div>
<div class="hero-content text-center text-neutral-content">
<div class="w-full">
<div class="-mt-64">
<h1 class="text-5xl font-bold mb-24">Welcome to Trarecord</h1>
</div>
<div class="fixed bottom-12 right-8 flex flex-col gap-y-8">
<.link navigate={~p"/users/log_in"} class="btn font-bold normal-case w-32">
Sign In
</.link>
<.link navigate={~p"/users/register"} class="btn font-bold normal-case w-32">
Sign Up
</.link>
</div>
</div>
</div>
</div>
起動時に未ログインの場合はログインページにリダイレクトしていましたが、Welcomeページをそのまま表示するように変更します
defmodule TrarecordWeb.PageController do
use TrarecordWeb, :controller
alias Trarecord.Accounts.User
- def home(conn, _params) do
- # The home page is often custom made,
- # so skip the default app layout.
- render(conn, :home, layout: false)
- end
def index(conn, _params) do
redirect_to(conn, conn.assigns.current_user)
end
defp redirect_to(conn, %User{}) do
redirect(conn, to: ~p"/users/settings")
end
defp redirect_to(conn, nil) do
- redirect(conn, to: ~p"/users/log_in")
+ render(conn, :home, layout: false)
end
end
これで起動時にWelcome画面が表示されそこから、ログイン、新規登録の導線ができました
Welcomeページにlogin,registerリンクが出て邪魔なので削除します
<!DOCTYPE html>
<html lang="en" class="[scrollbar-gutter:stable]">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="csrf-token" content={get_csrf_token()} />
<.live_title suffix=" · Phoenix Framework">
<%= assigns[:page_title] || "Trarecord" %>
</.live_title>
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
</script>
</head>
<body class="bg-white">
- <ul class="relative z-10 flex items-center gap-4 px-4 sm:px-6 lg:px-8 justify-end">
- <%= if @current_user do %>
- <li class="text-[0.8125rem] leading-6 text-zinc-900">
- <%= @current_user.email %>
- </li>
- <li>
- <.link
- href={~p"/users/settings"}
- class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
- >
- Settings
- </.link>
- </li>
- <li>
- <.link
- href={~p"/users/log_out"}
- method="delete"
- class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
- >
- Log out
- </.link>
- </li>
- <% else %>
- <li>
- <.link
- href={~p"/users/register"}
- class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
- >
- Register
- </.link>
- </li>
- <li>
- <.link
- href={~p"/users/log_in"}
- class="text-[0.8125rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
- >
- Log in
- </.link>
- </li>
- <% end %>
- </ul>
<%= @inner_content %>
</body>
</html>
これでWelcomeページが出来たのでコミットします
git add .
git commit -m 'impl welcome page'
オンボーディング
オンボーディングとはアプリやWebサービスを登録した後にどのようなアプリで、どう使うのかをユーザーに伝える機能です
スライドショーだったり、実際に使用するボタンのみ有効にしてユーザーの操作をガイドするなどいくつか種類があります
今回はスライドショーで実装していきます
画像の準備
スライドショーで使用する画像ですが、以下からダウンロードします
この方の画像は無料ですが、クレジット表記をする必要があるので、ダウンロード時に表記するクレジットのhtmlをコピーして控えておきましょう
オンボーディングページの作成
素材が揃ったのでオンボーディングページを作成します
スライドショーなのでページ推移はせずに nextボタンを押して次の画像とテキストを表示し、最後の画像ならfinishを押してオンボーディングを完了させます
表示する画像とテキスト、リンクのリストを返すintro/0
関数を実装します
データ構造は次の様にしています
- page -> ページ番号
- title -> タイトル
- text -> 本文
- link -> クレジットのリンク
- image -> 画像パス
- bg -> 背景色
画像の切り替えイベントはページ番号が3だったらfinishで完了となりそれ以外は次の画像を表示するようにしています
defmodule TrarecordWeb.OnboardingLive.Index do
use TrarecordWeb, :live_view
def render(assigns) do
~H"""
<div
class="flex justify-center w-screen h-screen bg-local bg-contain bg-center bg-no-repeat"
style={"background-image: url(#{@page_data.image}); background-color: #{@page_data.bg}"}
>
<div class="grid grid-rows-6 mt-12 text-center">
<h1 class="text-2xl"><%= raw(@page_data.title) %></h1>
<p class="text-base -mt-12 row-span-4"><%= raw(@page_data.text) %></p>
<%= if @page == 3 do %>
<p class="text-xl cursor-default" phx-click="finish">finish</p>
<% else %>
<p class="text-xl cursor-default" phx-click="next">next</p>
<% end %>
</div>
</div>
<p id="freepik" class="absolute bottom-2 right-4 text-sm">
<a href={@page_data.link} class="underline text-blue-600">
Image by storyset on Freepik
</a>
</p>
"""
end
def mount(_params, _session, socket) do
socket
|> assign(:page, 1)
|> assign(:page_data, Enum.find(intro(), &(&1.page == 1)))
|> then(&{:ok, &1})
end
def handle_event("next", _params, socket) do
page = socket.assigns.page + 1
socket
|> assign(:page, page)
|> assign(:page_data, Enum.find(intro(), &(&1.page == page)))
|> then(&{:noreply, &1})
end
def handle_event("finish", _params, socket) do
{:noreply, push_navigate(socket, to: ~p"/")}
end
def intro() do
[
%{
page: 1,
title: "Let's gather where we want to go.",
text: "You can register <br />various spots using Google search.",
link:
"https://www.freepik.com/free-vector/flying-around-world-with-airplane-concept-illustration_8426450.htm#fromView=author&page=1&position=12&uuid=dbcac267-0605-4076-ae43-111dfb07a118",
image: "/images/flying-around-world.svg",
bg: "#59b2ab"
},
%{
page: 2,
title: "Let's organize them in folders.",
text: "If you have more spots, divide them into <br />folders for easier management.",
link:
"https://www.freepik.com/free-vector/resume-folder-concept-illustration_5358953.htm#fromView=author&page=1&position=6&uuid=31a01ff6-d2cb-4798-bc51-89a0517d7ad9",
image: "/images/folder.svg",
bg: "#febe29"
},
%{
page: 3,
title: "Record your travel.",
text:
"You can record your memories of actually visiting the spot<br />with GPS logging and Checkin.",
link:
"https://www.freepik.com/free-vector/post-concept-illustration_5928515.htm#fromView=author&page=1&position=2&uuid=1450568b-5aac-4d60-a3ad-bef059625d94",
image: "/images/post.svg",
bg: "#22bcb5"
}
]
end
end
ルーティングとリダイレクト処理
liveviewのページは作ったのでルーティングを追加します
登録後に表示するので、ログイン後のスコープに追加します
scope "/", TrarecordWeb do
pipe_through [:browser, :require_authenticated_user]
live_session :require_authenticated_user,
on_mount: [{TrarecordWeb.UserAuth, :ensure_authenticated}] do
live "/users/settings", UserSettingsLive, :edit
live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
+ live "/onboarding", OnboardingLive.Index, :index
end
end
user_registration_liveのフォームをsubmitするとここを通るので、put_session/3
関数でオンボーディングにリダイレクトするように変更します
def create(conn, %{"_action" => "registered"} = params) do
- create(conn, params, "Account created successfully!")
+ conn
+ |> put_session(:user_return_to, ~p"/onboarding")
+ |> create(params, "Account created successfully!")
end
オンボーディング時にヘッダーが邪魔なので以下のように変更します
<main>
<div class="flex flex-col">
<.flash_group flash={@flash} />
<%= @inner_content %>
</div>
</main>
オンボーディングの実装が完了したのでコミットします
git add .
git commit -m 'impl onboarding'
動作確認
実装が完了したので動作確認を行います
新規登録後オンボーディングが始まり、完了後はログイン後画面にリダイレクトされるのを確認できました
テストの追加・修正
オンボーディング周りで既存の改修を行ったので、こけたテストの修正とオンボーディングのテストを追加していきます
page_controller
未ログインで起動時はWelcomeページを開くようにしたので変更
defmodule TrarecordWeb.PageControllerTest do
use TrarecordWeb.ConnCase
test "GET /", %{conn: conn} do
conn = get(conn, ~p"/")
- assert html_response(conn, 302) =~ "/users/log_in"
+ assert html_response(conn, 200) =~ "Welcome"
end
end
user_session_controller_test
登録後は onboardingにリダイレクトするようにしたので変更
test "login following registration", %{conn: conn, user: user} do
conn =
conn
|> post(~p"/users/log_in", %{
"_action" => "registered",
"user" => %{
"email" => user.email,
"password" => valid_user_password()
}
})
- assert redirected_to(conn) == ~p"/"
+ assert redirected_to(conn) == ~p"/onboarding"
assert Phoenix.Flash.get(conn.assigns.flash, :info) =~ "Account created successfully"
end
user_login_live_test
ヘッダーを消したので新規登録のリンクは Sing upだけになったので変更
test "renders log in page", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/users/log_in")
assert html =~ "Log in"
- assert html =~ "Register"
+ assert html =~ "Sign up"
assert html =~ "Forgot your password?"
end
user_registration_live_test
新規登録後はonboardingにリダイレクトするようにしたので変更
test "creates account and logs the user in", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/users/register")
email = unique_user_email()
form = form(lv, "#registration_form", user: params_for(:user_form_data, email: email))
render_submit(form)
conn = follow_trigger_action(form, conn)
- assert redirected_to(conn) == ~p"/"
+ assert redirected_to(conn) == ~p"/onboarding"
# Now do a logged in request and assert on the menu
conn = get(conn, "/")
response = html_response(conn, 302)
assert response =~ "/users/settings"
end
onboarding_live_test
ページを開いた時のテストと、オンボーディング完了と完了後のリダイレクトのテストを作成
defmodule TrarecordWeb.OnboardingLiveTest do
use TrarecordWeb.ConnCase, async: true
import Phoenix.LiveViewTest
describe "start onbarding" do
setup %{conn: conn} do
%{conn: log_in_user(conn, insert(:user))}
end
test "render onboarding page", %{conn: conn} do
{:ok, _lv, html} = live(conn, ~p"/onboarding")
assert html =~ "Let's gather where we want to go."
assert html =~ "next"
end
test "finish onboarding and redirect /", %{conn: conn} do
{:ok, lv, _html} = live(conn, ~p"/onboarding")
assert lv
|> element("p", "next")
|> render_click() =~ "Let's organize them in folders."
assert lv
|> element("p", "next")
|> render_click() =~ "Record your travel."
assert lv
|> element("p", "finish")
|> render_click()
|> follow_redirect(conn, "/")
end
end
end
git add .
git commit -m 'fix test'
git push origin feature/onbarding
CI check and Merge
CIも無事通ったのでマージして完了です
最後に
UIコンポーネントライブラリの追加、Welcompeページとオンボーディングの実装が完了しました
これでアプリらしさが結構出てきたかと思います
次は指定したリンクをブラウザで開く、ネイティブ機能との連携を実装していきます
本記事は以上になりますありがとうございました
参考ページ
https://sailboatui.com/
https://www.hyperui.dev/
https://tailwindui.com/
https://tailblocks.cc/
https://flowrift.com
https://merakiui.com/
https://daisyui.com/
https://petal.build
https://mishka.tools/chelekom
https://github.com/bluzky/salad_ui
https://github.com/briankariuki/tremorx
https://www.freepik.com/author/stories
https://myajo.net/tips/9500/