This commit is contained in:
Chris McCord 2022-08-03 09:40:11 -04:00
parent 51971a28a8
commit 6b02cfc614
35 changed files with 801 additions and 421 deletions

View file

@ -263,21 +263,6 @@ let Focus = {
},
}
// Accessible focus wrapping
Hooks.FocusWrap = {
mounted(){
this.content = document.querySelector(this.el.getAttribute("data-content"))
this.focusStart = this.el.querySelector(`#${this.el.id}-start`)
this.focusEnd = this.el.querySelector(`#${this.el.id}-end`)
this.focusStart.addEventListener("focus", () => Focus.focusLastDescendant(this.content))
this.focusEnd.addEventListener("focus", () => Focus.focusFirstDescendant(this.content))
this.content.addEventListener("phx:show-end", () => this.content.focus())
if(window.getComputedStyle(this.content).display !== "none"){
Focus.focusFirstDescendant(this.content)
}
},
}
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, {
hooks: Hooks,
@ -297,7 +282,7 @@ let routeUpdated = () => {
// Show progress bar on live navigation and form submits
topbar.config({barColors: {0: "rgba(147, 51, 234, 1)"}, shadowColor: "rgba(0, 0, 0, .3)"})
window.addEventListener("phx:page-loading-start", info => topbar.show())
window.addEventListener("phx:page-loading-start", info => topbar.delayedShow(200))
window.addEventListener("phx:page-loading-stop", info => topbar.hide())
// Accessible routing
@ -333,5 +318,4 @@ liveSocket.connect()
// >> liveSocket.enableDebug()
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
// >> liveSocket.disableLatencySim()
window.liveSocket = liveSocket
window.liveSocket = liveSocket

1
assets/js/phoenix Symbolic link
View file

@ -0,0 +1 @@
/Users/chris/oss/phoenix/assets/js/phoenix

1
assets/js/phoenix_live_view Symbolic link
View file

@ -0,0 +1 @@
/Users/chris/oss/phoenix_live_view/assets/js/phoenix_live_view

View file

@ -4,7 +4,7 @@
* http://buunguyen.github.io/topbar
* Copyright (c) 2021 Buu Nguyen
*/
(function (window, document) {
(function (window, document) {
"use strict";
// https://gist.github.com/paulirish/1579671
@ -35,10 +35,11 @@
})();
var canvas,
progressTimerId,
fadeTimerId,
currentProgress,
showing,
progressTimerId = null,
fadeTimerId = null,
delayTimerId = null,
addEvent = function (elem, type, handler) {
if (elem.addEventListener) elem.addEventListener(type, handler, false);
else if (elem.attachEvent) elem.attachEvent("on" + type, handler);
@ -95,6 +96,11 @@
for (var key in opts)
if (options.hasOwnProperty(key)) options[key] = opts[key];
},
delayedShow: function(time) {
if (showing) return;
if (delayTimerId) return;
delayTimerId = setTimeout(() => topbar.show(), time);
},
show: function () {
if (showing) return;
showing = true;
@ -125,6 +131,8 @@
return currentProgress;
},
hide: function () {
clearTimeout(delayTimerId);
delayTimerId = null;
if (!showing) return;
showing = false;
if (progressTimerId != null) {

View file

@ -11,9 +11,7 @@ config :live_beats,
replica: LiveBeats.ReplicaRepo,
ecto_repos: [LiveBeats.Repo]
config :live_beats, :files,
admin_usernames: []
config :live_beats, :files, admin_usernames: []
# Configures the endpoint
config :live_beats, LiveBeatsWeb.Endpoint,

View file

@ -19,8 +19,7 @@ if config_env() == :prod do
For example: ecto://USER:PASS@HOST/DATABASE
"""
replica_database_url =
System.get_env("REPLICA_DATABASE_URL") || database_url
replica_database_url = System.get_env("REPLICA_DATABASE_URL") || database_url
host = System.get_env("PHX_HOST") || "example.com"
ecto_ipv6? = System.get_env("ECTO_IPV6") == "true"
@ -69,7 +68,6 @@ if config_env() == :prod do
hostname: "livebeats.local",
transport_opts: [inet6: true]
config :live_beats, :github,
client_id: System.fetch_env!("LIVE_BEATS_GITHUB_CLIENT_ID"),
client_secret: System.fetch_env!("LIVE_BEATS_GITHUB_CLIENT_SECRET")

View file

@ -30,7 +30,6 @@ config :live_beats, LiveBeats.ReplicaRepo,
pool_size: 10,
priv: "priv/repo"
# We don't run a server during test. If one is required,
# you can enable the server option below.
config :live_beats, LiveBeatsWeb.Endpoint,

View file

@ -19,6 +19,7 @@ defmodule LiveBeats do
"""
def config([main_key | rest] = keyspace) when is_list(keyspace) do
main = Application.fetch_env!(:live_beats, main_key)
Enum.reduce(rest, main, fn next_key, current ->
case Keyword.fetch(current, next_key) do
{:ok, val} -> val
@ -85,11 +86,11 @@ defmodule LiveBeats do
target.handle_execute({src_mod, event_struct})
catch
kind, err ->
Logger.error """
Logger.error("""
executing {#{inspect(src_mod)}, #{inspect(event_mod)}} failed with #{inspect(kind)}
#{inspect(err)}
"""
""")
end
end
end

View file

@ -31,11 +31,17 @@ defmodule LiveBeats.Accounts.Identity do
"provider_id" => to_string(info["id"]),
"provider_login" => info["login"],
"provider_name" => info["name"] || info["login"],
"provider_email" => primary_email,
"provider_email" => primary_email
}
%Identity{provider: @github, provider_meta: %{"user" => info, "emails" => emails}}
|> cast(params, [:provider_token, :provider_email, :provider_login, :provider_name, :provider_id])
|> cast(params, [
:provider_token,
:provider_email,
:provider_login,
:provider_name,
:provider_id
])
|> validate_required([:provider_token, :provider_email, :provider_name, :provider_id])
|> validate_length(:provider_meta, max: 10_000)
end

View file

@ -4,7 +4,6 @@ defmodule LiveBeats.Accounts.User do
alias LiveBeats.Accounts.{User, Identity}
schema "users" do
field :email, :string
field :name, :string
@ -61,7 +60,6 @@ defmodule LiveBeats.Accounts.User do
|> validate_username()
end
defp validate_email(changeset) do
changeset
|> validate_required([:email])

View file

@ -1,6 +1,7 @@
defmodule LiveBeats.Github do
def authorize_url() do
state = random_string()
"https://github.com/login/oauth/authorize?client_id=#{client_id()}&state=#{state}&scope=user:email"
end
@ -34,6 +35,7 @@ defmodule LiveBeats.Github do
end
defp fetch_user_info({:error, _reason} = error), do: error
defp fetch_user_info({:ok, token}) do
resp =
http(
@ -43,6 +45,7 @@ defmodule LiveBeats.Github do
[],
[{"accept", "application/vnd.github.v3+json"}, {"Authorization", "token #{token}"}]
)
case resp do
{:ok, info} -> {:ok, %{info: Jason.decode!(info), token: token}}
{:error, _reason} = err -> err
@ -50,6 +53,7 @@ defmodule LiveBeats.Github do
end
defp fetch_emails({:error, _} = err), do: err
defp fetch_emails({:ok, user}) do
resp =
http(
@ -59,6 +63,7 @@ defmodule LiveBeats.Github do
[],
[{"accept", "application/vnd.github.v3+json"}, {"Authorization", "token #{user.token}"}]
)
case resp do
{:ok, info} ->
emails = Jason.decode!(info)

View file

@ -16,6 +16,7 @@ defmodule LiveBeats.MediaLibrary.Genre do
end
defp put_slug(%Ecto.Changeset{valid?: false} = changeset), do: changeset
defp put_slug(%Ecto.Changeset{valid?: true} = changeset) do
if title = get_change(changeset, :title) do
put_change(changeset, :slug, Phoenix.Naming.underscore(title))

View file

@ -90,8 +90,11 @@ defmodule LiveBeatsWeb.Presence do
|> assign_new(:total_count, fn -> count end)
~H"""
<div class="px-4 mt-6 sm:px-6 lg:px-8"> <!-- users -->
<h2 class="text-gray-500 text-xs font-medium uppercase tracking-wide">Listening now (<%= @count %>)</h2>
<div class="px-4 mt-6 sm:px-6 lg:px-8">
<!-- users -->
<h2 class="text-gray-500 text-xs font-medium uppercase tracking-wide">
Listening now (<%= @count %>)
</h2>
<ul
id="listening-now"
role="list"
@ -99,11 +102,7 @@ defmodule LiveBeatsWeb.Presence do
class="grid grid-cols-1 gap-4 sm:gap-4 sm:grid-cols-2 xl:grid-cols-5 mt-3"
>
<%= for {id, _time} <- Enum.sort(@presence_ids, fn {_, t1}, {_, t2} -> t1 < t2 end) do %>
<.live_component
id={id}
module={BadgeComponent}
presence={@presences[id]}
/>
<.live_component id={id} module={BadgeComponent} presence={@presences[id]} />
<% end %>
</ul>
<%= if @total_count > @count do %>
@ -125,7 +124,7 @@ end
defmodule LiveBeatsWeb.Presence.BadgeComponent do
use LiveBeatsWeb, :live_component
# https://fly.io/docs/reference/regions/
#  https://fly.io/docs/reference/regions/
@region_names %{
"ams" => "Amsterdam, Netherlands",
"atl" => "Atlanta, Georgia (US)",
@ -152,14 +151,27 @@ defmodule LiveBeatsWeb.Presence.BadgeComponent do
def render(assigns) do
~H"""
<li id={"presence-#{@id}"} class="relative col-span-1 flex shadow-sm rounded-md overflow-hidden">
<.link navigate={profile_path(@presence)} class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md truncate">
<img class="w-12 h-12 flex-shrink-0 flex items-center justify-center rounded-l-md bg-purple-600" src={@presence.avatar_url} alt="">
<.link
navigate={profile_path(@presence)}
class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md truncate"
>
<img
class="w-12 h-12 flex-shrink-0 flex items-center justify-center rounded-l-md bg-purple-600"
src={@presence.avatar_url}
alt=""
/>
<div class="flex-1 flex items-center justify-between text-gray-900 text-sm font-medium hover:text-gray-600 pl-3">
<div class="flex-1 py-1 text-sm truncate">
<%= @presence.username %>
<%= if @ping do %>
<p class="text-gray-400 text-xs">ping: <%= @ping %>ms</p>
<%= if @region do %><img class="inline w-7 h-7 absolute right-3 top-3" src={"https://fly.io/ui/images/#{@region}.svg"} title={region_name(@region)} /><% end %>
<%= if @region do %>
<img
class="inline w-7 h-7 absolute right-3 top-3"
src={"https://fly.io/ui/images/#{@region}.svg"}
title={region_name(@region)}
/>
<% end %>
<% end %>
</div>
</div>

View file

@ -10,7 +10,6 @@ defmodule LiveBeatsWeb.OAuthCallbackController do
with {:ok, info} <- client.exchange_access_token(code: code, state: state),
%{info: info, primary_email: primary, emails: emails, token: token} = info,
{:ok, user} <- Accounts.register_github_user(primary, info, emails, token) do
conn
|> put_flash(:info, "Welcome #{user.email}")
|> LiveBeatsWeb.UserAuth.log_in_user(user)
@ -19,7 +18,10 @@ defmodule LiveBeatsWeb.OAuthCallbackController do
Logger.debug("failed GitHub insert #{inspect(changeset.errors)}")
conn
|> put_flash(:error, "We were unable to fetch the necessary information from your GithHub account")
|> put_flash(
:error,
"We were unable to fetch the necessary information from your GithHub account"
)
|> redirect(to: "/")
{:error, reason} ->
@ -40,6 +42,6 @@ defmodule LiveBeatsWeb.OAuthCallbackController do
end
defp github_client(conn) do
conn.assigns[:github_client] || LiveBeats.Github
conn.assigns[:github_client] || LiveBeats.Github
end
end

View file

@ -19,7 +19,9 @@ defmodule LiveBeatsWeb.UserAuth do
def on_mount(:ensure_authenticated, _params, session, socket) do
case session do
%{"user_id" => user_id} ->
new_socket = LiveView.assign_new(socket, :current_user, fn -> Accounts.get_user!(user_id) end)
new_socket =
LiveView.assign_new(socket, :current_user, fn -> Accounts.get_user!(user_id) end)
%Accounts.User{} = new_socket.assigns.current_user
{:cont, new_socket}

View file

@ -158,42 +158,6 @@ defmodule LiveBeatsWeb.LiveHelpers do
"""
end
attr :navigate, :string
attr :patch, :string
attr :href, :string, default: nil
attr :replace, :string, default: false
attr :rest, :global
def link(%{navigate: _to} = assigns) do
assigns = assign_new(assigns, :class, fn -> nil end)
~H"""
<a href={@navigate} data-phx-link="redirect" data-phx-link-state="push" {@rest}>
<%= render_slot(@inner_block) %>
</a>
"""
end
def link(%{patch: _to} = assigns) do
~H"""
<a
href={@patch}
data-phx-link="patch"
data-phx-link-state={if @replace, do: "replace", else: "push"}
{@rest}
>
<%= render_slot(@inner_block) %>
</a>
"""
end
def link(%{} = assigns) do
~H"""
<a href={@href || "#"} {@rest}>
<%= render_slot(@inner_block) %>
</a>
"""
end
@doc """
Returns a button triggered dropdown with aria keyboard and focus supporrt.
@ -487,19 +451,6 @@ defmodule LiveBeatsWeb.LiveHelpers do
"""
end
attr :id, :string, required: true
attr :content, :string
def focus_wrap(assigns) do
~H"""
<div id={@id} phx-hook="FocusWrap" data-content={@content}>
<span id={"#{@id}-start"} tabindex="0" aria-hidden="true"></span>
<%= render_slot(@inner_block) %>
<span id={"#{@id}-end"} tabindex="0" aria-hidden="true"></span>
</div>
"""
end
attr :id, :string, required: true
attr :min, :integer, default: 0
attr :max, :integer, default: 100

View file

@ -29,9 +29,11 @@ defmodule LiveBeatsWeb.PlayerLive do
<.progress_bar id="player-progress" />
<div id="player-info"
<div
id="player-info"
class="text-gray-500 dark:text-gray-400 flex-row justify-between text-sm font-medium tabular-nums"
phx-update="ignore">
phx-update="ignore"
>
<div id="player-time"></div>
<div id="player-duration"></div>
</div>
@ -43,7 +45,7 @@ defmodule LiveBeatsWeb.PlayerLive do
navigate={profile_path(@profile)}
class="mx-auto flex border-2 border-white border-opacity-20 rounded-md p-1 pr-2"
>
<span class="mt-1"><.icon name={:user_circle} class="w-4 h-4 block"/></span>
<span class="mt-1"><.icon name={:user_circle} class="w-4 h-4 block" /></span>
<p class="ml-2"><%= @profile.username %></p>
</.link>
<% else %>
@ -52,7 +54,12 @@ defmodule LiveBeatsWeb.PlayerLive do
<%= if is_nil(@profile) or @own_profile? do %>
<!-- prev -->
<button type="button" class="sm:block xl:block mx-auto scale-75" phx-click={js_prev(@own_profile?)} aria-label="Previous">
<button
type="button"
class="sm:block xl:block mx-auto scale-75"
phx-click={js_prev(@own_profile?)}
aria-label="Previous"
>
<svg width="17" height="18">
<path d="M0 0h2v18H0V0zM4 9l13-9v18L4 9z" fill="currentColor" />
</svg>
@ -60,23 +67,70 @@ defmodule LiveBeatsWeb.PlayerLive do
<!-- /prev -->
<!-- play/pause -->
<button type="button" class="mx-auto scale-75" phx-click={js_play_pause(@own_profile?)} aria-label={if @playing do "Pause" else "Play" end}>
<button
type="button"
class="mx-auto scale-75"
phx-click={js_play_pause(@own_profile?)}
aria-label={
if @playing do
"Pause"
else
"Play"
end
}
>
<%= if @playing do %>
<svg id="player-pause" width="50" height="50" fill="none">
<circle class="text-gray-300 dark:text-gray-500" cx="25" cy="25" r="24" stroke="currentColor" stroke-width="1.5" />
<circle
class="text-gray-300 dark:text-gray-500"
cx="25"
cy="25"
r="24"
stroke="currentColor"
stroke-width="1.5"
/>
<path d="M18 16h4v18h-4V16zM28 16h4v18h-4z" fill="currentColor" />
</svg>
<% else %>
<svg id="player-play" width="50" height="50" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<circle id="svg_1" stroke-width="0.8" stroke="currentColor" r="11.4" cy="12" cx="12" class="text-gray-300 dark:text-gray-500"/>
<path stroke="null" fill="currentColor" transform="rotate(90 12.8947 12.3097)" id="svg_6" d="m9.40275,15.10014l3.49194,-5.58088l3.49197,5.58088l-6.98391,0z" stroke-width="1.5" fill="none"/>
<svg
id="player-play"
width="50"
height="50"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<circle
id="svg_1"
stroke-width="0.8"
stroke="currentColor"
r="11.4"
cy="12"
cx="12"
class="text-gray-300 dark:text-gray-500"
/>
<path
stroke="null"
fill="currentColor"
transform="rotate(90 12.8947 12.3097)"
id="svg_6"
d="m9.40275,15.10014l3.49194,-5.58088l3.49197,5.58088l-6.98391,0z"
stroke-width="1.5"
fill="none"
/>
</svg>
<% end %>
</button>
<!-- /play/pause -->
<!-- next -->
<button type="button" class="mx-auto scale-75" phx-click={js_next(@own_profile?)} aria-label="Next">
<button
type="button"
class="mx-auto scale-75"
phx-click={js_next(@own_profile?)}
aria-label="Next"
>
<svg width="17" height="18" viewBox="0 0 17 18" fill="none">
<path d="M17 0H15V18H17V0Z" fill="currentColor" />
<path d="M13 9L0 0V18L13 9Z" fill="currentColor" />
@ -86,8 +140,14 @@ defmodule LiveBeatsWeb.PlayerLive do
<% else %>
<button type="button" class="mx-auto scale-75"></button>
<!-- stop button -->
<button type="button" class="mx-auto scale-75" phx-click={JS.push("switch_profile", value: %{user_id: nil}, target: "#player", loading: "#player")}>
<.icon name={:stop} class="h-12 w-12"/>
<button
type="button"
class="mx-auto scale-75"
phx-click={
JS.push("switch_profile", value: %{user_id: nil}, target: "#player", loading: "#player")
}
>
<.icon name={:stop} class="h-12 w-12" />
</button>
<!-- stop button -->
<% end %>
@ -106,9 +166,7 @@ defmodule LiveBeatsWeb.PlayerLive do
<%= if @profile do %>
<.modal id="not-authorized" on_confirm={hide_modal("not-authorized")}>
<:title>You can't do that</:title>
Only <%= @profile.username %> can control playback
<:confirm>Ok</:confirm>
</.modal>
<% end %>
@ -156,10 +214,11 @@ defmodule LiveBeatsWeb.PlayerLive do
if profile && connected?(socket) do
current_user = Accounts.update_active_profile(current_user, profile.user_id)
#untrack last profile the user was listening
# untrack last profile the user was listening
if socket.assigns.profile do
Presence.untrack_profile_user(socket.assigns.profile, current_user.id)
end
Presence.track_profile_user(profile, current_user.id)
send(self(), :play_current)
@ -311,7 +370,7 @@ defmodule LiveBeatsWeb.PlayerLive do
vsn: 1,
ip: to_string(song.server_ip),
size: song.mp3_filesize,
uuid: song.mp3_filename,
uuid: song.mp3_filename
})
push_event(socket, "play", %{

View file

@ -12,30 +12,42 @@ defmodule LiveBeatsWeb.ProfileLive do
<.title_bar>
<div>
<div class="block">
<%= @profile.tagline %> <%= if @owns_profile? do %>(you)<% end %>
<%= @profile.tagline %>
<%= if @owns_profile? do %>
(you)
<% end %>
</div>
<.link href={@profile.external_homepage_url} target="_blank" class="text-sm text-gray-600">
<.icon name={:code}/> <span class=""><%= url_text(@profile.external_homepage_url) %></span>
<.icon name={:code} /> <span class=""><%= url_text(@profile.external_homepage_url) %></span>
</.link>
</div>
<:actions>
<%= if @active_profile_id == @profile.user_id do %>
<.button primary
phx-click={JS.push("switch_profile", value: %{user_id: nil}, target: "#player", loading: "#player")}
<.button
primary
phx-click={
JS.push("switch_profile", value: %{user_id: nil}, target: "#player", loading: "#player")
}
>
<.icon name={:stop}/><span class="ml-2">Stop Listening</span>
<.icon name={:stop} /><span class="ml-2">Stop Listening</span>
</.button>
<% else %>
<.button primary
phx-click={JS.push("switch_profile", value: %{user_id: @profile.user_id}, target: "#player", loading: "#player")}
<.button
primary
phx-click={
JS.push("switch_profile",
value: %{user_id: @profile.user_id},
target: "#player",
loading: "#player"
)
}
>
<.icon name={:play}/><span class="ml-2">Listen</span>
<.icon name={:play} /><span class="ml-2">Listen</span>
</.button>
<% end %>
<%= if @owns_profile? do %>
<.button id="upload-btn" primary patch={profile_path(@current_user, :new)}>
<.icon name={:upload}/><span class="ml-2">Upload Songs</span>
<.icon name={:upload} /><span class="ml-2">Upload Songs</span>
</.button>
<% end %>
</:actions>
@ -73,18 +85,23 @@ defmodule LiveBeatsWeb.ProfileLive do
row_id={fn song -> "song-#{song.id}" end}
owns_profile?={@owns_profile?}
>
<:col let={%{song: song}} label="Title"><%= song.title %></:col>
<:col let={%{song: song}} label="Artist"><%= song.artist %></:col>
<:col let={%{song: song}} label="Attribution" class="max-w-5xl break-words text-gray-600 font-light"><%= song.attribution %></:col>
<:col let={%{song: song}} label="Duration"><%= MP3Stat.to_mmss(song.duration) %></:col>
<:col let={%{song: song}} label="" if={@owns_profile?}>
<:col :let={%{song: song}} label="Title"><%= song.title %></:col>
<:col :let={%{song: song}} label="Artist"><%= song.artist %></:col>
<:col
:let={%{song: song}}
label="Attribution"
class="max-w-5xl break-words text-gray-600 font-light"
>
<%= song.attribution %>
</:col>
<:col :let={%{song: song}} label="Duration"><%= MP3Stat.to_mmss(song.duration) %></:col>
<:col :let={%{song: song}} label="" if={@owns_profile?}>
<.link
id={"delete-song-#{song.id}"}
phx-click={show_modal("delete-modal-#{song.id}")}
class="inline-flex items-center px-3 py-2 text-sm leading-4 font-medium"
>
<.icon name={:trash} class="-ml-0.5 mr-2 h-4 w-4"/>
Delete
<.icon name={:trash} class="-ml-0.5 mr-2 h-4 w-4" /> Delete
</.link>
</:col>
</.live_table>

View file

@ -17,21 +17,31 @@ defmodule LiveBeatsWeb.ProfileLive.SongEntryComponent do
<% else %>
Title
<span class="text-gray-400">
(calculating duration <.spinner class="inline-block animate-spin h-2.5 w-2.5 text-gray-400"/>)
(calculating duration
<.spinner class="inline-block animate-spin h-2.5 w-2.5 text-gray-400" />)
</span>
<% end %>
</label>
<input type="text" name={"songs[#{@ref}][title]"} value={@title}
class="block w-full border-0 p-0 text-gray-900 placeholder-gray-500 focus:ring-0 sm:text-sm" {%{autofocus: @index == 0}}/>
<input
type="text"
name={"songs[#{@ref}][title]"}
value={@title}
class="block w-full border-0 p-0 text-gray-900 placeholder-gray-500 focus:ring-0 sm:text-sm"
{%{autofocus: @index == 0}}
/>
</div>
<div class="border border-gray-300 rounded-md px-3 py-2 mt-2 shadow-sm focus-within:ring-1 focus-within:ring-indigo-600 focus-within:border-indigo-600">
<label for="name" class="block text-xs font-medium text-gray-900">Artist</label>
<input type="text" name={"songs[#{@ref}][artist]"} value={@artist}
class="block w-full border-0 p-0 text-gray-900 placeholder-gray-500 focus:ring-0 sm:text-sm"/>
<input
type="text"
name={"songs[#{@ref}][artist]"}
value={@artist}
class="block w-full border-0 p-0 text-gray-900 placeholder-gray-500 focus:ring-0 sm:text-sm"
/>
</div>
<div class="col-span-full sm:grid sm:grid-cols-2 sm:gap-2 sm:items-start">
<.error input_name={"songs[#{@ref}][title]"} field={:title} errors={@errors} class="-mt-1"/>
<.error input_name={"songs[#{@ref}][artist]"} field={:artist} errors={@errors} class="-mt-1"/>
<.error input_name={"songs[#{@ref}][title]"} field={:title} errors={@errors} class="-mt-1" />
<.error input_name={"songs[#{@ref}][artist]"} field={:artist} errors={@errors} class="-mt-1" />
</div>
<div class="border col-span-full border-gray-300 rounded-md px-3 py-2 mt-2 shadow-sm focus-within:ring-1 focus-within:ring-indigo-600 focus-within:border-indigo-600">
<label for="name" class="block text-xs font-medium text-gray-900">
@ -43,7 +53,12 @@ defmodule LiveBeatsWeb.ProfileLive.SongEntryComponent do
><%= @attribution %></textarea>
</div>
<div class="col-span-full sm:grid sm:grid-cols-2 sm:gap-2 sm:items-start">
<.error input_name={"songs[#{@ref}][attribution]"} field={:attribution} errors={@errors} class="-mt-1"/>
<.error
input_name={"songs[#{@ref}][attribution]"}
field={:attribution}
errors={@errors}
class="-mt-1"
/>
</div>
<div
role="progressbar"
@ -52,7 +67,8 @@ defmodule LiveBeatsWeb.ProfileLive.SongEntryComponent do
aria-valuenow={@progress}
style={"transition: width 0.5s ease-in-out; width: #{@progress}%; min-width: 1px;"}
class="col-span-full bg-purple-500 dark:bg-purple-400 h-1.5 w-0 p-0"
></div>
>
</div>
</div>
"""
end

View file

@ -10,27 +10,39 @@ defmodule LiveBeatsWeb.ProfileLive.SongRowComponent do
<tr id={@id} class={@class} tabindex="0">
<%= for {col, i} <- Enum.with_index(@col) do %>
<td
class={"px-6 py-3 text-sm font-medium text-gray-900 #{if i == 0, do: "w-80 cursor-pointer"} #{col[:class]}"}
class={
"px-6 py-3 text-sm font-medium text-gray-900 #{if i == 0, do: "w-80 cursor-pointer"} #{col[:class]}"
}
phx-click={JS.push("play_or_pause", value: %{id: @song.id})}
>
>
<div class="flex items-center space-x-3 lg:pl-2">
<%= if i == 0 do %>
<%= if @status == :playing do %>
<span class="flex pt-1 relative mr-2 w-4">
<span class="w-3 h-3 animate-ping bg-purple-400 rounded-full absolute"></span>
<.icon name={:volume_up} class="h-5 w-5 -mt-1 -ml-1" aria-label="Playing" role="button"/>
<.icon
name={:volume_up}
class="h-5 w-5 -mt-1 -ml-1"
aria-label="Playing"
role="button"
/>
</span>
<% end %>
<%= if @status == :paused do %>
<span class="flex pt-1 relative mr-2 w-4">
<.icon name={:volume_up} class="h-5 w-5 -mt-1 -ml-1 text-gray-400" aria-label="Paused" role="button"/>
<.icon
name={:volume_up}
class="h-5 w-5 -mt-1 -ml-1 text-gray-400"
aria-label="Paused"
role="button"
/>
</span>
<% end %>
<%= if @status == :stopped do %>
<span class="flex relative w-6 -translate-x-1">
<%= if @owns_profile? do %>
<.icon name={:play} class="h-5 w-5 text-gray-400" aria-label="Play" role="button"/>
<% end %>
<%= if @owns_profile? do %>
<.icon name={:play} class="h-5 w-5 text-gray-400" aria-label="Play" role="button" />
<% end %>
</span>
<% end %>
<% end %>
@ -42,7 +54,8 @@ defmodule LiveBeatsWeb.ProfileLive.SongRowComponent do
"""
end
def update(%{action: :send, status: status}, socket) when status in [:playing, :paused, :stopped] do
def update(%{action: :send, status: status}, socket)
when status in [:playing, :paused, :stopped] do
{:ok, assign(socket, status: status)}
end

View file

@ -169,7 +169,7 @@ defmodule LiveBeatsWeb.ProfileLive.UploadFormComponent do
do: ~H|Something went wrong|
defp file_error(%{kind: %Ecto.Changeset{}} = assigns),
do: ~H|<%= @label %>: <%= LiveBeatsWeb.ErrorHelpers.translate_changeset_errors(@kind) %>|
do: ~H|<%= @label %>: <%= LiveBeatsWeb.ErrorHelpers.translate_changeset_errors(@kind) %>|
defp file_error(%{kind: {msg, opts}} = assigns) when is_binary(msg) and is_list(opts),
do: ~H|<%= @label %>: <%= LiveBeatsWeb.ErrorHelpers.translate_error(@kind) %>|

View file

@ -7,14 +7,13 @@
class="space-y-8"
phx-target={@myself}
phx-change="validate"
phx-submit="save">
phx-submit="save"
>
<div class="space-y-8 divide-y divide-gray-200 sm:space-y-5">
<div class="space-y-2 sm:space-y-2">
<%= for {{ref, changeset}, i} <- Enum.with_index(@changesets) do %>
<.live_component id={ref} module={SongEntryComponent} changeset={changeset} index={i} />
<% end %>
<!-- upload -->
<div class="sm:grid sm:border-t sm:border-gray-200 sm:pt-5">
<div class="mt-1 sm:mt-0" phx-drop-target={@uploads.mp3.ref}>
@ -22,7 +21,7 @@
<div class="rounded-md bg-red-50 p-4 mb-2">
<div class="flex">
<div class="flex-shrink-0">
<.icon name={:x_circle} class="h-5 w-5 text-red-400"/>
<.icon name={:x_circle} class="h-5 w-5 text-red-400" />
</div>
<div class="ml-3">
<h3 class="text-sm font-medium text-red-800">
@ -42,13 +41,30 @@
<div class="max-w-lg flex justify-center px-6 pt-5 pb-6 border-2 border-gray-300 border-dashed rounded-md">
<div class="space-y-1 text-center">
<svg class="mx-auto h-12 w-12 text-gray-400" stroke="currentColor" fill="none" viewBox="0 0 48 48" aria-hidden="true">
<path d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"></path>
<svg
class="mx-auto h-12 w-12 text-gray-400"
stroke="currentColor"
fill="none"
viewBox="0 0 48 48"
aria-hidden="true"
>
<path
d="M28 8H12a4 4 0 00-4 4v20m32-12v8m0 0v8a4 4 0 01-4 4H12a4 4 0 01-4-4v-4m32-4l-3.172-3.172a4 4 0 00-5.656 0L28 28M8 32l9.172-9.172a4 4 0 015.656 0L28 28m0 0l4 4m4-24h8m-4-4v8m-12 4h.02"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
</path>
</svg>
<div class="flex text-sm text-gray-600">
<label for="file-upload" class="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500">
<span phx-click={js_exec("##{@uploads.mp3.ref}", "click", [])}>Upload files</span>
<%= live_file_input @uploads.mp3, class: "sr-only", tabindex: "0" %>
<label
for="file-upload"
class="relative cursor-pointer bg-white rounded-md font-medium text-indigo-600 hover:text-indigo-500 focus-within:outline-none focus-within:ring-2 focus-within:ring-offset-2 focus-within:ring-indigo-500"
>
<span phx-click={js_exec("##{@uploads.mp3.ref}", "click", [])}>
Upload files
</span>
<%= live_file_input(@uploads.mp3, class: "sr-only", tabindex: "0") %>
</label>
<p class="pl-1">or drag and drop</p>
</div>

View file

@ -10,7 +10,13 @@ defmodule LiveBeatsWeb.SettingsLive do
</.title_bar>
<div class="max-w-3xl px-4 mx-auto mt-6">
<.form let={f} for={@changeset} phx-change="validate" phx-submit="save" class="space-y-8 divide-y divide-gray-200">
<.form
:let={f}
for={@changeset}
phx-change="validate"
phx-submit="save"
class="space-y-8 divide-y divide-gray-200"
>
<div class="space-y-8 divide-y divide-gray-200">
<div>
<div>
@ -28,8 +34,16 @@ defmodule LiveBeatsWeb.SettingsLive do
<span class="inline-flex items-center px-3 rounded-l-md border border-r-0 border-gray-300 bg-gray-50 text-gray-500 sm:text-sm">
<%= URI.parse(LiveBeatsWeb.Endpoint.url()).host %>/
</span>
<%= text_input f, :username, class: "flex-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300" %>
<.error field={:username} input_name="user[username]" errors={@changeset.errors} class="pt-2 pl-4 pr-4 ml-2 text-center" />
<%= text_input(f, :username,
class:
"flex-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full min-w-0 rounded-none rounded-r-md sm:text-sm border-gray-300"
) %>
<.error
field={:username}
input_name="user[username]"
errors={@changeset.errors}
class="pt-2 pl-4 pr-4 ml-2 text-center"
/>
</div>
</div>
@ -38,7 +52,11 @@ defmodule LiveBeatsWeb.SettingsLive do
Email (from GitHub)
</label>
<div class="mt-1 flex rounded-md shadow-sm">
<%= text_input f, :email, disabled: true, class: "flex-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full min-w-0 rounded-md sm:text-sm border-gray-300 bg-gray-50" %>
<%= text_input(f, :email,
disabled: true,
class:
"flex-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full min-w-0 rounded-md sm:text-sm border-gray-300 bg-gray-50"
) %>
</div>
</div>
@ -47,8 +65,16 @@ defmodule LiveBeatsWeb.SettingsLive do
Profile Tagline
</label>
<div class="mt-1">
<%= text_input f, :profile_tagline, class: "flex-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full min-w-0 rounded-md sm:text-sm border-gray-300" %>
<.error field={:profile_tagline} input_name="user[profile_tagline]" errors={@changeset.errors} class="pt-2 pl-4 pr-4 ml-2 text-center" />
<%= text_input(f, :profile_tagline,
class:
"flex-1 focus:ring-indigo-500 focus:border-indigo-500 block w-full min-w-0 rounded-md sm:text-sm border-gray-300"
) %>
<.error
field={:profile_tagline}
input_name="user[profile_tagline]"
errors={@changeset.errors}
class="pt-2 pl-4 pr-4 ml-2 text-center"
/>
</div>
<p class="text-sm text-gray-500">Write a short tagline for your beats page.</p>
</div>
@ -58,7 +84,10 @@ defmodule LiveBeatsWeb.SettingsLive do
<div class="pt-5">
<div class="flex justify-end">
<button type="submit" class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<button
type="submit"
class="ml-3 inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Save
</button>
</div>

View file

@ -5,7 +5,11 @@ defmodule LiveBeatsWeb.SignInLive do
~H"""
<div class="min-h-screen bg-gray-50 flex flex-col justify-center py-12 sm:px-6 lg:px-8">
<div class="sm:mx-auto sm:w-full sm:max-w-md">
<img class="mx-auto h-12 w-auto" src="https://tailwindui.com/img/logos/workflow-mark-indigo-600.svg" alt="Workflow">
<img
class="mx-auto h-12 w-auto"
src="https://tailwindui.com/img/logos/workflow-mark-indigo-600.svg"
alt="Workflow"
/>
<h2 class="mt-6 text-center text-3xl font-extrabold text-gray-900">
Sign in to your account
</h2>
@ -20,7 +24,10 @@ defmodule LiveBeatsWeb.SignInLive do
<div class="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div class="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10">
<div class="space-y-6">
<a href={LiveBeats.Github.authorize_url()} class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
<a
href={LiveBeats.Github.authorize_url()}
class="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Sign in with GitHub
</a>
</div>

View file

@ -1,28 +1,48 @@
<div id="mobile-sidebar-container" class="fixed inset-0 flex z-40 lg:hidden" aria-modal="true" style="display: none;" role="region">
<div
class="fixed inset-0 bg-gray-600 bg-opacity-75"
phx-click={hide_mobile_sidebar()}>
</div>
<div
id="mobile-sidebar-container"
class="fixed inset-0 flex z-40 lg:hidden"
aria-modal="true"
style="display: none;"
role="region"
>
<div class="fixed inset-0 bg-gray-600 bg-opacity-75" phx-click={hide_mobile_sidebar()}></div>
<div id="mobile-sidebar" class="relative flex-1 flex flex-col max-w-xs w-full pt-5 pb-4 bg-white hidden min-h-screen">
<div
id="mobile-sidebar"
class="relative flex-1 flex flex-col max-w-xs w-full pt-5 pb-4 bg-white hidden min-h-screen"
>
<div class="absolute top-0 right-0 -mr-12 pt-2">
<button type="button"
<button
type="button"
id="hide-mobile-sidebar"
aria-expanded="true"
aria-controls="mobile-sidebar"
class="ml-1 flex items-center justify-center h-10 w-10 rounded-full focus:outline-none focus:ring-2 focus:ring-inset focus:ring-white"
phx-click={hide_mobile_sidebar()}>
phx-click={hide_mobile_sidebar()}
>
<span class="sr-only">Close sidebar</span>
<svg class="h-6 w-6 text-white" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
<svg
class="h-6 w-6 text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18L18 6M6 6l12 12"
>
</path>
</svg>
</button>
</div>
<div class="flex-shrink-0 flex items-center px-4">
<.link navigate={home_path(@current_user)}>
<.icon name={:status_online} class="w-8 h-8 text-purple-600 -mt-2 inline-block" outlined/>
<.icon name={:status_online} class="w-8 h-8 text-purple-600 -mt-2 inline-block" outlined />
<span class="h-8 w-auto text-2xl ml-1 font-bold">
LiveBeats
</span>
@ -30,14 +50,14 @@
</div>
<div class="mt-5 flex-1 h-0 overflow-y-auto">
<%= if @current_user do %>
<.sidebar_account_dropdown id="mobile-account-dropdown" current_user={@current_user}/>
<.sidebar_account_dropdown id="mobile-account-dropdown" current_user={@current_user} />
<% end %>
<nav class="px-2">
<%= if @current_user do %>
<.sidebar_nav_links current_user={@current_user} active_tab={@active_tab}/>
<.sidebar_nav_links current_user={@current_user} active_tab={@active_tab} />
<% end %>
<.sidebar_active_users id="desktop-active-users" users={@active_users}/>
<.sidebar_active_users id="desktop-active-users" users={@active_users} />
</nav>
</div>
</div>
@ -46,14 +66,12 @@
<!-- Dummy element to force sidebar to shrink to fit close icon -->
</div>
</div>
<!-- Static sidebar for desktop -->
<div class="hidden lg:flex lg:flex-shrink-0" role="region">
<div class="flex flex-col w-64 border-r border-gray-200 pt-5 pb-4 bg-gray-100">
<div class="flex items-center flex-shrink-0 px-6">
<.link navigate={home_path(@current_user)}>
<.icon name={:status_online} class="w-8 h-8 text-purple-600 -mt-2 inline-block" outlined/>
<.icon name={:status_online} class="w-8 h-8 text-purple-600 -mt-2 inline-block" outlined />
<span class="h-8 w-auto text-2xl ml-1 font-bold">
LiveBeats
</span>
@ -62,32 +80,48 @@
<!-- Sidebar component, swap this element with another sidebar if you like -->
<div class="h-0 flex-1 flex flex-col overflow-y-auto">
<%= if @current_user do %>
<.sidebar_account_dropdown id="account-dropdown" current_user={@current_user}/>
<.sidebar_account_dropdown id="account-dropdown" current_user={@current_user} />
<% end %>
<!-- Sidebar Search -->
<div class="px-3 mt-5">
<label for="search" class="sr-only">Search</label>
<div class="mt-1 relative rounded-md shadow-sm">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none" aria-hidden="true">
<svg class="mr-3 h-4 w-4 text-gray-400" x-description="Heroicon name: solid/search"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
<div
class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none"
aria-hidden="true"
>
<svg
class="mr-3 h-4 w-4 text-gray-400"
x-description="Heroicon name: solid/search"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
clip-rule="evenodd"></path>
clip-rule="evenodd"
>
</path>
</svg>
</div>
<input type="text" name="search" id="search"
<input
type="text"
name="search"
id="search"
class="focus:ring-indigo-500 focus:border-indigo-500 block w-full pl-9 sm:text-sm border-gray-300 rounded-md"
placeholder="Search">
placeholder="Search"
/>
</div>
</div>
<!-- Navigation -->
<nav class="px-3 mt-6">
<%= if @current_user do %>
<.sidebar_nav_links current_user={@current_user} active_tab={@active_tab}/>
<.sidebar_nav_links current_user={@current_user} active_tab={@active_tab} />
<% end %>
<!-- Secondary navigation -->
<.sidebar_active_users id="mobile-active-users" users={@active_users}/>
<.sidebar_active_users id="mobile-active-users" users={@active_users} />
</nav>
</div>
</div>
@ -95,17 +129,34 @@
<!-- Main column -->
<div class="flex flex-col w-0 flex-1 overflow-hidden">
<!-- Search header -->
<div class="relative z-10 flex-shrink-0 flex h-16 bg-white border-b border-gray-200 lg:hidden" role="navigation">
<button type="button"
<div
class="relative z-10 flex-shrink-0 flex h-16 bg-white border-b border-gray-200 lg:hidden"
role="navigation"
>
<button
type="button"
id="show-mobile-sidebar"
aria-expanded="false"
aria-controls="mobile-sidebar"
class="px-4 border-r border-gray-200 text-gray-500 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-purple-500 lg:hidden"
phx-click={show_mobile_sidebar()}>
phx-click={show_mobile_sidebar()}
>
<span class="sr-only">Open sidebar</span>
<svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16"></path>
<svg
class="h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h8m-8 6h16"
>
</path>
</svg>
</button>
<div class="flex-1 flex justify-between px-4 sm:px-6 lg:px-8">
@ -114,16 +165,28 @@
<label for="search-field" class="sr-only">Search</label>
<div class="relative w-full text-gray-400 focus-within:text-gray-600">
<div class="absolute inset-y-0 left-0 flex items-center pointer-events-none">
<svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
<svg
class="h-5 w-5"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M8 4a4 4 0 100 8 4 4 0 000-8zM2 8a6 6 0 1110.89 3.476l4.817 4.817a1 1 0 01-1.414 1.414l-4.816-4.816A6 6 0 012 8z"
clip-rule="evenodd"></path>
clip-rule="evenodd"
>
</path>
</svg>
</div>
<input id="search-field" name="search-field"
<input
id="search-field"
name="search-field"
class="block w-full h-full pl-8 pr-3 py-2 border-transparent text-gray-900 placeholder-gray-500 focus:outline-none focus:ring-0 focus:border-transparent focus:placeholder-gray-400 sm:text-sm"
placeholder="Search" type="search">
placeholder="Search"
type="search"
/>
</div>
</form>
</div>
@ -132,15 +195,16 @@
<!-- Profile dropdown TODO -->
<div class="ml-3 relative">
<div>
<button type="button"
<button
type="button"
class="max-w-xs bg-white flex items-center text-sm rounded-full focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500"
id="user-menu-button" @click="open = true"
aria-expanded="false" aria-haspopup="true"
id="user-menu-button"
@click="open = true"
aria-expanded="false"
aria-haspopup="true"
>
<span class="sr-only">Open user menu</span>
<img class="h-8 w-8 rounded-full"
src={@current_user.avatar_url}
alt="">
<img class="h-8 w-8 rounded-full" src={@current_user.avatar_url} alt="" />
</button>
</div>
</div>
@ -149,8 +213,8 @@
</div>
</div>
<.flash flash={@flash} kind={:info}/>
<.flash flash={@flash} kind={:error}/>
<.flash flash={@flash} kind={:info} />
<.flash flash={@flash} kind={:error} />
<.connection_status>
Re-establishing connection...
</.connection_status>
@ -165,9 +229,18 @@
<%= @inner_content %>
</main>
<div class="relative">
<div id="ping-container" class="fixed bottom-0 right-0 bg-gray-900 text-gray-200 px-2 rounded-tl-md text-sm w-[114px] min-w-max" phx-update="ignore">
<div
id="ping-container"
class="fixed bottom-0 right-0 bg-gray-900 text-gray-200 px-2 rounded-tl-md text-sm w-[114px] min-w-max"
phx-update="ignore"
>
<span id="ping" phx-hook="Ping"></span>
<%= if @region do %><img class="inline w-5 h-5 absolute right-0" src={"https://fly.io/ui/images/#{@region}.svg"} /><% end %>
<%= if @region do %>
<img
class="inline w-5 h-5 absolute right-0"
src={"https://fly.io/ui/images/#{@region}.svg"}
/>
<% end %>
</div>
</div>
</div>

View file

@ -1,13 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<%= csrf_meta_tag() %>
<%= live_title_tag assigns[:page_title] || "LiveBeats", suffix: " · Phoenix Framework" %>
<link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/assets/app.css")}/>
<script defer phx-track-static type="text/javascript" src={Routes.static_path(@conn, "/assets/app.js")}></script>
<%= live_title_tag(assigns[:page_title] || "LiveBeats", suffix: " · Phoenix Framework") %>
<link phx-track-static rel="stylesheet" href={Routes.static_path(@conn, "/assets/app.css")} />
<script
defer
phx-track-static
type="text/javascript"
src={Routes.static_path(@conn, "/assets/app.js")}
>
</script>
</head>
<body>
<%= @inner_content %>

View file

@ -1,15 +1,16 @@
<!-- Pinned projects -->
<div class="px-4 mt-6 sm:px-6 lg:px-8">
<h2 class="text-gray-500 text-xs font-medium uppercase tracking-wide">Who's Here</h2>
<ul role="list" class="grid grid-cols-1 gap-4 sm:gap-6 sm:grid-cols-2 xl:grid-cols-4 mt-3" x-max="1">
<ul
role="list"
class="grid grid-cols-1 gap-4 sm:gap-6 sm:grid-cols-2 xl:grid-cols-4 mt-3"
x-max="1"
>
<li class="relative col-span-1 flex shadow-sm rounded-md">
<div
class="flex-shrink-0 flex items-center justify-center w-16 bg-pink-600 text-white text-sm font-medium rounded-l-md">
<div class="flex-shrink-0 flex items-center justify-center w-16 bg-pink-600 text-white text-sm font-medium rounded-l-md">
CM
</div>
<div
class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md truncate">
<div class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md truncate">
<div class="flex-1 px-4 py-2 text-sm truncate">
<a href="#" class="text-gray-900 font-medium hover:text-gray-600">
Chris
@ -20,12 +21,10 @@
</li>
<li class="relative col-span-1 flex shadow-sm rounded-md">
<div
class="flex-shrink-0 flex items-center justify-center w-16 bg-purple-600 text-white text-sm font-medium rounded-l-md">
<div class="flex-shrink-0 flex items-center justify-center w-16 bg-purple-600 text-white text-sm font-medium rounded-l-md">
KM
</div>
<div
class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md truncate">
<div class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md truncate">
<div class="flex-1 px-4 py-2 text-sm truncate">
<a href="#" class="text-gray-900 font-medium hover:text-gray-600">
Kurt
@ -36,12 +35,10 @@
</li>
<li class="relative col-span-1 flex shadow-sm rounded-md">
<div
class="flex-shrink-0 flex items-center justify-center w-16 bg-green-600 text-white text-sm font-medium rounded-l-md">
<div class="flex-shrink-0 flex items-center justify-center w-16 bg-green-600 text-white text-sm font-medium rounded-l-md">
JV
</div>
<div
class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md truncate">
<div class="flex-1 flex items-center justify-between border-t border-r border-b border-gray-200 bg-white rounded-r-md truncate">
<div class="flex-1 px-4 py-2 text-sm truncate">
<a href="#" class="text-gray-900 font-medium hover:text-gray-600">
José
@ -52,327 +49,472 @@
</li>
</ul>
</div>
<!-- Projects list (only on smallest breakpoint) -->
<div class="mt-10 sm:hidden">
<div class="px-4 sm:px-6">
<h2 class="text-gray-500 text-xs font-medium uppercase tracking-wide">Projects</h2>
</div>
<ul role="list" class="mt-3 border-t border-gray-200 divide-y divide-gray-100">
<li>
<a href="#" class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6">
<a
href="#"
class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6"
>
<span class="flex items-center truncate space-x-3">
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-pink-600" aria-hidden="true"></span>
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-pink-600" aria-hidden="true">
</span>
<span class="font-medium truncate text-sm leading-6">
GraphQL API
<!-- space -->
<span class="truncate font-normal text-gray-500">in Engineering</span>
</span>
</span>
<svg class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
<svg
class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
clip-rule="evenodd"
>
</path>
</svg>
</a>
</li>
<li>
<a href="#" class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6">
<a
href="#"
class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6"
>
<span class="flex items-center truncate space-x-3">
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-purple-600" aria-hidden="true"></span>
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-purple-600" aria-hidden="true">
</span>
<span class="font-medium truncate text-sm leading-6">
New Benefits Plan
<!-- space -->
<span class="truncate font-normal text-gray-500">in Human Resources</span>
</span>
</span>
<svg class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
<svg
class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
clip-rule="evenodd"
>
</path>
</svg>
</a>
</li>
<li>
<a href="#" class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6">
<a
href="#"
class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6"
>
<span class="flex items-center truncate space-x-3">
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-yellow-500" aria-hidden="true"></span>
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-yellow-500" aria-hidden="true">
</span>
<span class="font-medium truncate text-sm leading-6">
Onboarding Emails
<!-- space -->
<span class="truncate font-normal text-gray-500">in Customer Success</span>
</span>
</span>
<svg class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
<svg
class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
clip-rule="evenodd"
>
</path>
</svg>
</a>
</li>
<li>
<a href="#" class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6">
<a
href="#"
class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6"
>
<span class="flex items-center truncate space-x-3">
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-green-500" aria-hidden="true"></span>
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-green-500" aria-hidden="true">
</span>
<span class="font-medium truncate text-sm leading-6">
iOS App
<!-- space -->
<span class="truncate font-normal text-gray-500">in Engineering</span>
</span>
</span>
<svg class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
<svg
class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
clip-rule="evenodd"
>
</path>
</svg>
</a>
</li>
<li>
<a href="#" class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6">
<a
href="#"
class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6"
>
<span class="flex items-center truncate space-x-3">
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-pink-600" aria-hidden="true"></span>
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-pink-600" aria-hidden="true">
</span>
<span class="font-medium truncate text-sm leading-6">
Marketing Site Redesign
<!-- space -->
<span class="truncate font-normal text-gray-500">in Engineering</span>
</span>
</span>
<svg class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
<svg
class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
clip-rule="evenodd"
>
</path>
</svg>
</a>
</li>
<li>
<a href="#" class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6">
<a
href="#"
class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6"
>
<span class="flex items-center truncate space-x-3">
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-purple-600" aria-hidden="true"></span>
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-purple-600" aria-hidden="true">
</span>
<span class="font-medium truncate text-sm leading-6">
Hire CFO
<!-- space -->
<span class="truncate font-normal text-gray-500">in Human Resources</span>
</span>
</span>
<svg class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
<svg
class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
clip-rule="evenodd"
>
</path>
</svg>
</a>
</li>
<li>
<a href="#" class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6">
<a
href="#"
class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6"
>
<span class="flex items-center truncate space-x-3">
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-yellow-500" aria-hidden="true"></span>
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-yellow-500" aria-hidden="true">
</span>
<span class="font-medium truncate text-sm leading-6">
Android App
<!-- space -->
<span class="truncate font-normal text-gray-500">in Engineering</span>
</span>
</span>
<svg class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
<svg
class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
clip-rule="evenodd"
>
</path>
</svg>
</a>
</li>
<li>
<a href="#" class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6">
<a
href="#"
class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6"
>
<span class="flex items-center truncate space-x-3">
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-green-500" aria-hidden="true"></span>
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-green-500" aria-hidden="true">
</span>
<span class="font-medium truncate text-sm leading-6">
New Customer Portal
<!-- space -->
<span class="truncate font-normal text-gray-500">in Engineering</span>
</span>
</span>
<svg class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
<svg
class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
clip-rule="evenodd"
>
</path>
</svg>
</a>
</li>
<li>
<a href="#" class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6">
<a
href="#"
class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6"
>
<span class="flex items-center truncate space-x-3">
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-pink-600" aria-hidden="true"></span>
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-pink-600" aria-hidden="true">
</span>
<span class="font-medium truncate text-sm leading-6">
Co-op Program
<!-- space -->
<span class="truncate font-normal text-gray-500">in Human Resources</span>
</span>
</span>
<svg class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
<svg
class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
clip-rule="evenodd"
>
</path>
</svg>
</a>
</li>
<li>
<a href="#" class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6">
<a
href="#"
class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6"
>
<span class="flex items-center truncate space-x-3">
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-purple-600" aria-hidden="true"></span>
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-purple-600" aria-hidden="true">
</span>
<span class="font-medium truncate text-sm leading-6">
Implement NPS
<!-- space -->
<span class="truncate font-normal text-gray-500">in Customer Success</span>
</span>
</span>
<svg class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
<svg
class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
clip-rule="evenodd"
>
</path>
</svg>
</a>
</li>
<li>
<a href="#" class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6">
<a
href="#"
class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6"
>
<span class="flex items-center truncate space-x-3">
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-yellow-500" aria-hidden="true"></span>
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-yellow-500" aria-hidden="true">
</span>
<span class="font-medium truncate text-sm leading-6">
Employee Recognition Program
<!-- space -->
<span class="truncate font-normal text-gray-500">in Human Resources</span>
</span>
</span>
<svg class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
<svg
class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
clip-rule="evenodd"
>
</path>
</svg>
</a>
</li>
<li>
<a href="#" class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6">
<a
href="#"
class="group flex items-center justify-between px-4 py-4 hover:bg-gray-50 sm:px-6"
>
<span class="flex items-center truncate space-x-3">
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-green-500" aria-hidden="true"></span>
<span class="w-2.5 h-2.5 flex-shrink-0 rounded-full bg-green-500" aria-hidden="true">
</span>
<span class="font-medium truncate text-sm leading-6">
Open Source Web Client
<!-- space -->
<span class="truncate font-normal text-gray-500">in Engineering</span>
</span>
</span>
<svg class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd"
<svg
class="ml-4 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/chevron-right"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path
fill-rule="evenodd"
d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z"
clip-rule="evenodd"></path>
clip-rule="evenodd"
>
</path>
</svg>
</a>
</li>
</ul>
</div>
<!-- Songs table (small breakpoint and up) -->
<div class="hidden mt-8 sm:block">
<div class="align-middle inline-block min-w-full border-b border-gray-200">
<table class="min-w-full">
<thead>
<tr class="border-t border-gray-200">
<th
class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<span class="lg:pl-2">Nextup</span>
</th>
<th
class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
<th class="px-6 py-3 border-b border-gray-200 bg-gray-50 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
likes
</th>
<th
class="hidden md:table-cell px-6 py-3 border-b border-gray-200 bg-gray-50 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
<th class="hidden md:table-cell px-6 py-3 border-b border-gray-200 bg-gray-50 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
user
</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-100">
<%= for _ <- 1..20 do %>
<tr>
<td class="px-6 py-3 max-w-0 w-full whitespace-nowrap text-sm font-medium text-gray-900">
<div class="flex items-center space-x-3 lg:pl-2">
<div class="flex-shrink-0 w-2.5 h-2.5 rounded-full bg-pink-600" aria-hidden="true"></div>
<a href="#" class="truncate hover:text-gray-600">
<span>
GraphQL API
<tr>
<td class="px-6 py-3 max-w-0 w-full whitespace-nowrap text-sm font-medium text-gray-900">
<div class="flex items-center space-x-3 lg:pl-2">
<div class="flex-shrink-0 w-2.5 h-2.5 rounded-full bg-pink-600" aria-hidden="true">
</div>
<a href="#" class="truncate hover:text-gray-600">
<span>
GraphQL API
<!-- space -->
<span class="text-gray-500 font-normal">in Engineering</span>
</span>
</a>
</div>
</td>
<td class="px-6 py-3 text-sm text-gray-500 font-medium">
<div class="flex items-center space-x-2">
<div class="flex flex-shrink-0 -space-x-1">
<img class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
src="https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80"
alt="Dries Vincent">
<img class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
src="https://images.unsplash.com/photo-1517841905240-472988babdf9?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80"
alt="Lindsay Walton">
<img class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80"
alt="Courtney Henry">
<img class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80"
alt="Tom Cook">
<span class="text-gray-500 font-normal">in Engineering</span>
</span>
</a>
</div>
</td>
<td class="px-6 py-3 text-sm text-gray-500 font-medium">
<div class="flex items-center space-x-2">
<div class="flex flex-shrink-0 -space-x-1">
<img
class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
src="https://images.unsplash.com/photo-1506794778202-cad84cf45f1d?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80"
alt="Dries Vincent"
/>
<span class="flex-shrink-0 text-xs leading-5 font-medium">+8</span>
</div>
</td>
<td class="hidden md:table-cell px-6 py-3 whitespace-nowrap text-sm text-gray-500 text-right">
March 17, 2020
</td>
</tr>
<img
class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
src="https://images.unsplash.com/photo-1517841905240-472988babdf9?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80"
alt="Lindsay Walton"
/>
<img
class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
src="https://images.unsplash.com/photo-1438761681033-6461ffad8d80?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80"
alt="Courtney Henry"
/>
<img
class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
src="https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80"
alt="Tom Cook"
/>
</div>
<span class="flex-shrink-0 text-xs leading-5 font-medium">+8</span>
</div>
</td>
<td class="hidden md:table-cell px-6 py-3 whitespace-nowrap text-sm text-gray-500 text-right">
March 17, 2020
</td>
</tr>
<% end %>
<tr>
<td class="px-6 py-3 max-w-0 w-full whitespace-nowrap text-sm font-medium text-gray-900">
<div class="flex items-center space-x-3 lg:pl-2">
<div class="flex-shrink-0 w-2.5 h-2.5 rounded-full bg-purple-600" aria-hidden="true"></div>
<div class="flex-shrink-0 w-2.5 h-2.5 rounded-full bg-purple-600" aria-hidden="true">
</div>
<a href="#" class="truncate hover:text-gray-600">
<span>
New Benefits Plan
@ -385,23 +527,29 @@
<td class="px-6 py-3 text-sm text-gray-500 font-medium">
<div class="flex items-center space-x-2">
<div class="flex flex-shrink-0 -space-x-1">
<img class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
<img
class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
src="https://images.unsplash.com/photo-1519345182560-3f2917c472ef?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80"
alt="Leonard Krasner">
alt="Leonard Krasner"
/>
<img class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
<img
class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
src="https://images.unsplash.com/photo-1463453091185-61582044d556?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80"
alt="Floyd Miles">
alt="Floyd Miles"
/>
<img class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
<img
class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
src="https://images.unsplash.com/photo-1502685104226-ee32379fefbe?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80"
alt="Emily Selman">
alt="Emily Selman"
/>
<img class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
<img
class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
src="https://images.unsplash.com/photo-1500917293891-ef795e70e1f6?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80"
alt="Kristin Watson">
alt="Kristin Watson"
/>
</div>
<span class="flex-shrink-0 text-xs leading-5 font-medium">+4</span>
@ -415,7 +563,8 @@
<tr>
<td class="px-6 py-3 max-w-0 w-full whitespace-nowrap text-sm font-medium text-gray-900">
<div class="flex items-center space-x-3 lg:pl-2">
<div class="flex-shrink-0 w-2.5 h-2.5 rounded-full bg-yellow-500" aria-hidden="true"></div>
<div class="flex-shrink-0 w-2.5 h-2.5 rounded-full bg-yellow-500" aria-hidden="true">
</div>
<a href="#" class="truncate hover:text-gray-600">
<span>
Onboarding Emails
@ -428,23 +577,29 @@
<td class="px-6 py-3 text-sm text-gray-500 font-medium">
<div class="flex items-center space-x-2">
<div class="flex flex-shrink-0 -space-x-1">
<img class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
<img
class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
src="https://images.unsplash.com/photo-1502685104226-ee32379fefbe?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80"
alt="Emily Selman">
alt="Emily Selman"
/>
<img class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
<img
class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
src="https://images.unsplash.com/photo-1500917293891-ef795e70e1f6?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80"
alt="Kristin Watson">
alt="Kristin Watson"
/>
<img class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
<img
class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
src="https://images.unsplash.com/photo-1505840717430-882ce147ef2d?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80"
alt="Emma Dorsey">
alt="Emma Dorsey"
/>
<img class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
<img
class="max-w-none h-6 w-6 rounded-full ring-2 ring-white"
src="https://images.unsplash.com/photo-1509783236416-c9ad59bae472?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80"
alt="Alicia Bell">
alt="Alicia Bell"
/>
</div>
<span class="flex-shrink-0 text-xs leading-5 font-medium">+10</span>
@ -457,4 +612,4 @@
</tbody>
</table>
</div>
</div>
</div>

View file

@ -24,7 +24,9 @@ defmodule LiveBeatsWeb.ErrorHelpers do
<%= for error <- @error_values do %>
<span
phx-feedback-for={@input_name}
class={"invalid-feedback inline-block pl-2 pr-2 text-sm text-white bg-red-600 rounded-md #{@class}"}
class={
"invalid-feedback inline-block pl-2 pr-2 text-sm text-white bg-red-600 rounded-md #{@class}"
}
>
<%= translate_error(error) %>
</span>

View file

@ -16,7 +16,8 @@ defmodule LiveBeatsWeb.LayoutView do
</h3>
<div class="mt-1 space-y-1" role="group" aria-labelledby={@id}>
<%= for user <- @users do %>
<.link navigate={profile_path(user)}
<.link
navigate={profile_path(user)}
class="group flex items-center px-3 py-2 text-base leading-5 font-medium text-gray-600 rounded-md hover:text-gray-900 hover:bg-gray-50"
>
<span class="w-2.5 h-2.5 mr-4 bg-indigo-500 rounded-full" aria-hidden="true"></span>
@ -36,30 +37,51 @@ defmodule LiveBeatsWeb.LayoutView do
<%= if @current_user do %>
<.link
navigate={profile_path(@current_user)}
class={"text-gray-700 hover:text-gray-900 group flex items-center px-2 py-2 text-sm font-medium rounded-md #{if @active_tab == :profile, do: "bg-gray-200", else: "hover:bg-gray-50"}"}
class={
"text-gray-700 hover:text-gray-900 group flex items-center px-2 py-2 text-sm font-medium rounded-md #{if @active_tab == :profile, do: "bg-gray-200", else: "hover:bg-gray-50"}"
}
aria-current={if @active_tab == :profile, do: "true", else: "false"}
>
<.icon name={:music_note} outlined class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"/>
My Songs
<.icon
name={:music_note}
outlined
class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
/> My Songs
</.link>
<.link
navigate={Routes.settings_path(Endpoint, :edit)}
class={"text-gray-700 hover:text-gray-900 group flex items-center px-2 py-2 text-sm font-medium rounded-md #{if @active_tab == :settings, do: "bg-gray-200", else: "hover:bg-gray-50"}"}
class={
"text-gray-700 hover:text-gray-900 group flex items-center px-2 py-2 text-sm font-medium rounded-md #{if @active_tab == :settings, do: "bg-gray-200", else: "hover:bg-gray-50"}"
}
aria-current={if @active_tab == :settings, do: "true", else: "false"}
>
<.icon name={:adjustments} outlined class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"/>
Settings
<.icon
name={:adjustments}
outlined
class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
/> Settings
</.link>
<% else %>
<.link navigate={Routes.sign_in_path(Endpoint, :index)}
<.link
navigate={Routes.sign_in_path(Endpoint, :index)}
class="text-gray-700 hover:text-gray-900 hover:bg-gray-50 group flex items-center px-2 py-2 text-sm font-medium rounded-md"
>
<svg class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
<svg
class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
aria-hidden="true"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"
>
</path>
</svg>
Sign in
</.link>
@ -71,10 +93,9 @@ defmodule LiveBeatsWeb.LayoutView do
def sidebar_account_dropdown(assigns) do
~H"""
<.dropdown id={@id} foo="bar">
<:img src={@current_user.avatar_url}/>
<:img src={@current_user.avatar_url} />
<:title><%= @current_user.name %></:title>
<:subtitle>@<%= @current_user.username %></:subtitle>
<:link navigate={profile_path(@current_user)}>View Profile</:link>
<:link navigate={Routes.settings_path(Endpoint, :edit)}>Settings</:link>
<:link href={Routes.o_auth_callback_path(Endpoint, :sign_out)} method={:delete}>Sign out</:link>

View file

@ -22,12 +22,12 @@
"libcluster": {:hex, :libcluster, "3.3.1", "e7a4875cd1290cee7a693d6bd46076863e9e433708b01339783de6eff5b7f0aa", [:mix], [{:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "b575ca63c1cd84e01f3fa0fc45e6eb945c1ee7ae8d441d33def999075e9e5398"},
"mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"},
"mint": {:hex, :mint, "1.3.0", "396b3301102f7b775e103da5a20494b25753aed818d6d6f0ad222a3a018c3600", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "a9aac960562e43ca69a77e5176576abfa78b8398cec5543dd4fb4ab0131d5c1e"},
"phoenix": {:git, "https://github.com/phoenixframework/phoenix.git", "c5bb03eee8ad802c125c92202ebec4e15c35c3e8", []},
"phoenix": {:git, "https://github.com/phoenixframework/phoenix.git", "063bb8c37b05a61b6f411ef4b678efe0cd4e3841", []},
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
"phoenix_html": {:hex, :phoenix_html, "3.2.0", "1c1219d4b6cb22ac72f12f73dc5fad6c7563104d083f711c3fcd8551a1f4ae11", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "36ec97ba56d25c0136ef1992c37957e4246b649d620958a1f9fa86165f8bc54f"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.6.2", "0769470265eb13af01b5001b29cb935f4710d6adaa1ffc18417a570a337a2f0f", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.3", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.17.1", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "5bc6c6b38a2ca8b5020b442322fcee6afd5e641637a0b1fb059d4bd89bc58e7b"},
"phoenix_live_reload": {:hex, :phoenix_live_reload, "1.3.3", "3a53772a6118d5679bf50fc1670505a290e32a1d195df9e069d8c53ab040c054", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "766796676e5f558dbae5d1bdb066849673e956005e3730dfd5affd7a6da4abac"},
"phoenix_live_view": {:git, "https://github.com/phoenixframework/phoenix_live_view.git", "8bae1b3944c2cd9c3078839a980eca56cc59d449", []},
"phoenix_live_view": {:git, "https://github.com/phoenixframework/phoenix_live_view.git", "aab28ef96bf79a4218c1402a4aef6c3613380e1a", []},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"},
"phoenix_view": {:hex, :phoenix_view, "1.0.0", "fea71ecaaed71178b26dd65c401607de5ec22e2e9ef141389c721b3f3d4d8011", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "82be3e2516f5633220246e2e58181282c71640dab7afc04f70ad94253025db0c"},
"plug": {:hex, :plug, "1.13.4", "addb6e125347226e3b11489e23d22a60f7ab74786befb86c14f94fb5f23ca9a4", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "06114c1f2a334212fe3ae567dbb3b1d29fd492c1a09783d52f3d489c1a6f4cf2"},

View file

@ -4,17 +4,17 @@ defmodule LiveBeats.MP3StatTest do
alias LiveBeats.MP3Stat
test "parse/1 with valid mp3" do
{:ok, %MP3Stat{} = stat} = MP3Stat.parse("test/support/fixtures/silence1s.mp3")
{:ok, %MP3Stat{} = stat} = MP3Stat.parse("test/support/fixtures/silence1s.mp3")
assert stat.duration == 1
assert stat.title == "Silence"
assert stat.artist == "Anon"
end
test "parse/1 with invalid mp3" do
assert {:error, :bad_file} = MP3Stat.parse("mix.exs")
assert {:error, :bad_file} = MP3Stat.parse("mix.exs")
end
test "parse/1 with missing file" do
assert {:error, :bad_file} = MP3Stat.parse("lsfjslkfjslkfjs")
assert {:error, :bad_file} = MP3Stat.parse("lsfjslkfjslkfjs")
end
end

View file

@ -1,6 +1,7 @@
defmodule Phoenix.Presence.ClientTest.Presence do
use Phoenix.Presence, otp_app: :live_beats,
pubsub_server: LiveBeats.PubSub
use Phoenix.Presence,
otp_app: :live_beats,
pubsub_server: LiveBeats.PubSub
end
defmodule Phoenix.Presence.ClientTest do

View file

@ -9,6 +9,7 @@ defmodule LiveBeats.AccountsFixtures do
def user_fixture(attrs \\ %{}) do
primary_email = attrs[:email] || unique_user_email()
info = %{
"avatar_url" => "https://avatars3.githubusercontent.com/u/576796?v=4",
"bio" => nil,
@ -43,11 +44,11 @@ defmodule LiveBeats.AccountsFixtures do
"updated_at" => "2020-09-18T19:34:45Z",
"url" => "https://api.github.com/users/chrismccord"
}
emails = []
token = "token"
{:ok, user} =
LiveBeats.Accounts.register_github_user(primary_email, info, emails, token)
{:ok, user} = LiveBeats.Accounts.register_github_user(primary_email, info, emails, token)
user
end

View file

@ -1,5 +1,4 @@
defmodule Phoenix.Presence.Client.Mock do
def init(_opts) do
{:ok, %{}}
end
@ -11,5 +10,4 @@ defmodule Phoenix.Presence.Client.Mock do
def handle_leave(_topic, _key, _meta, state) do
{:ok, state}
end
end

View file

@ -2,7 +2,6 @@ defmodule Phoenix.Presence.Client.PresenceMock do
use GenServer
alias Phoenix.Presence.Client
def start_link(opts \\ []) do
GenServer.start_link(__MODULE__, opts[:id], opts)
end