mirror of
https://github.com/fly-apps/live_beats.git
synced 2024-11-21 23:50:59 +00:00
Touchup
This commit is contained in:
parent
51971a28a8
commit
6b02cfc614
35 changed files with 801 additions and 421 deletions
|
@ -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
|
||||
|
@ -334,4 +319,3 @@ liveSocket.connect()
|
|||
// >> liveSocket.enableLatencySim(1000) // enabled for duration of browser session
|
||||
// >> liveSocket.disableLatencySim()
|
||||
window.liveSocket = liveSocket
|
||||
|
||||
|
|
1
assets/js/phoenix
Symbolic link
1
assets/js/phoenix
Symbolic link
|
@ -0,0 +1 @@
|
|||
/Users/chris/oss/phoenix/assets/js/phoenix
|
1
assets/js/phoenix_live_view
Symbolic link
1
assets/js/phoenix_live_view
Symbolic link
|
@ -0,0 +1 @@
|
|||
/Users/chris/oss/phoenix_live_view/assets/js/phoenix_live_view
|
14
assets/vendor/topbar.js
vendored
14
assets/vendor/topbar.js
vendored
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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} ->
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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", %{
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -10,7 +10,9 @@ 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">
|
||||
|
@ -18,18 +20,28 @@ defmodule LiveBeatsWeb.ProfileLive.SongRowComponent 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"/>
|
||||
<.icon name={:play} class="h-5 w-5 text-gray-400" aria-label="Play" role="button" />
|
||||
<% end %>
|
||||
</span>
|
||||
<% 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
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 %>
|
||||
|
|
|
@ -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,273 +49,410 @@
|
|||
</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>
|
||||
|
@ -328,7 +462,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-pink-600" aria-hidden="true"></div>
|
||||
<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
|
||||
|
@ -341,23 +476,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-1506794778202-cad84cf45f1d?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
||||
alt="Dries Vincent">
|
||||
alt="Dries Vincent"
|
||||
/>
|
||||
|
||||
<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-1517841905240-472988babdf9?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
||||
alt="Lindsay Walton">
|
||||
alt="Lindsay Walton"
|
||||
/>
|
||||
|
||||
<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-1438761681033-6461ffad8d80?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
||||
alt="Courtney Henry">
|
||||
alt="Courtney Henry"
|
||||
/>
|
||||
|
||||
<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-1472099645785-5658abf4ff4e?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
||||
alt="Tom Cook">
|
||||
|
||||
alt="Tom Cook"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span class="flex-shrink-0 text-xs leading-5 font-medium">+8</span>
|
||||
|
@ -372,7 +513,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-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&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&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&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&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&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&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&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&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&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&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&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&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&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&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&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&q=80"
|
||||
alt="Alicia Bell">
|
||||
|
||||
alt="Alicia Bell"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span class="flex-shrink-0 text-xs leading-5 font-medium">+10</span>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
4
mix.lock
4
mix.lock
|
@ -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"},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
defmodule Phoenix.Presence.ClientTest.Presence do
|
||||
use Phoenix.Presence, otp_app: :live_beats,
|
||||
use Phoenix.Presence,
|
||||
otp_app: :live_beats,
|
||||
pubsub_server: LiveBeats.PubSub
|
||||
end
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue