mirror of
https://github.com/fly-apps/live_beats.git
synced 2024-05-13 14:52:42 +00:00
Compare commits
3 commits
bb8f14cfe1
...
6b02cfc614
Author | SHA1 | Date | |
---|---|---|---|
6b02cfc614 | |||
51971a28a8 | |||
626a7bc305 |
|
@ -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
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
|
|
@ -1,5 +1,6 @@
|
|||
// See the Tailwind configuration guide for advanced usage
|
||||
// https://tailwindcss.com/docs/configuration
|
||||
|
||||
module.exports = {
|
||||
content: [
|
||||
'./js/**/*.js',
|
||||
|
|
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} ->
|
||||
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
defmodule LiveBeatsWeb.LiveHelpers do
|
||||
import Phoenix.LiveView
|
||||
import Phoenix.LiveView.Helpers
|
||||
use Phoenix.Component
|
||||
|
||||
alias LiveBeatsWeb.Router.Helpers, as: Routes
|
||||
alias Phoenix.LiveView.JS
|
||||
|
@ -35,9 +34,21 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
>
|
||||
<div class="flex">
|
||||
<div class="flex-shrink-0">
|
||||
<svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-red-800" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
<svg
|
||||
class="animate-spin -ml-1 mr-3 h-5 w-5 text-red-800"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
|
||||
</circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
|
@ -50,21 +61,31 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
"""
|
||||
end
|
||||
|
||||
attr :flash, :map
|
||||
attr :kiny, :atom
|
||||
|
||||
def flash(%{kind: :error} = assigns) do
|
||||
~H"""
|
||||
<%= if live_flash(@flash, @kind) do %>
|
||||
<div
|
||||
id="flash"
|
||||
class="rounded-md bg-red-50 p-4 fixed top-1 right-1 w-96 fade-in-scale z-50"
|
||||
phx-click={JS.push("lv:clear-flash") |> JS.remove_class("fade-in-scale", to: "#flash") |> hide("#flash")}
|
||||
phx-click={
|
||||
JS.push("lv:clear-flash")
|
||||
|> JS.remove_class("fade-in-scale", to: "#flash")
|
||||
|> hide("#flash")
|
||||
}
|
||||
phx-hook="Flash"
|
||||
>
|
||||
<div class="flex justify-between items-center space-x-3 text-red-700">
|
||||
<.icon name={:exclamation_circle} class="w-5 w-5"/>
|
||||
<.icon name={:exclamation_circle} class="w-5 w-5" />
|
||||
<p class="flex-1 text-sm font-medium" role="alert">
|
||||
<%= live_flash(@flash, @kind) %>
|
||||
</p>
|
||||
<button type="button" class="inline-flex bg-red-50 rounded-md p-1.5 text-red-500 hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-red-50 focus:ring-red-600">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex bg-red-50 rounded-md p-1.5 text-red-500 hover:bg-red-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-red-50 focus:ring-red-600"
|
||||
>
|
||||
<.icon name={:x} class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
@ -84,11 +105,14 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
phx-hook="Flash"
|
||||
>
|
||||
<div class="flex justify-between items-center space-x-3 text-green-700">
|
||||
<.icon name={:check_circle} class="w-5 h-5"/>
|
||||
<.icon name={:check_circle} class="w-5 h-5" />
|
||||
<p class="flex-1 text-sm font-medium" role="alert">
|
||||
<%= live_flash(@flash, @kind) %>
|
||||
</p>
|
||||
<button type="button" class="inline-flex bg-green-50 rounded-md p-1.5 text-green-500 hover:bg-green-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-green-50 focus:ring-green-600">
|
||||
<button
|
||||
type="button"
|
||||
class="inline-flex bg-green-50 rounded-md p-1.5 text-green-500 hover:bg-green-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-green-50 focus:ring-green-600"
|
||||
>
|
||||
<.icon name={:x} class="w-4 h-4" />
|
||||
</button>
|
||||
</div>
|
||||
|
@ -99,57 +123,41 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
|
||||
def spinner(assigns) do
|
||||
~H"""
|
||||
<svg class="inline-block animate-spin h-2.5 w-2.5 text-gray-400" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" aria-hidden="true">
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
||||
<svg
|
||||
class="inline-block animate-spin h-2.5 w-2.5 text-gray-400"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4">
|
||||
</circle>
|
||||
<path
|
||||
class="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
"""
|
||||
end
|
||||
|
||||
attr :name, :atom, required: true
|
||||
attr :outlined, :boolean, default: false
|
||||
attr :rest, :global, default: %{class: "w-4 h-4 inline-block"}
|
||||
|
||||
def icon(assigns) do
|
||||
assigns =
|
||||
assigns
|
||||
|> assign_new(:outlined, fn -> false end)
|
||||
|> assign_new(:class, fn -> "w-4 h-4 inline-block" end)
|
||||
|> assign_new(:"aria-hidden", fn -> !Map.has_key?(assigns, :"aria-label") end)
|
||||
assigns = assign_new(assigns, :"aria-hidden", fn -> !Map.has_key?(assigns, :"aria-label") end)
|
||||
|
||||
~H"""
|
||||
<%= if @outlined do %>
|
||||
<%= apply(Heroicons.Outline, @name, [assigns_to_attributes(assigns, [:outlined, :name])]) %>
|
||||
<%= apply(Heroicons.Outline, @name, [Map.to_list(@rest)]) %>
|
||||
<% else %>
|
||||
<%= apply(Heroicons.Solid, @name, [assigns_to_attributes(assigns, [:outlined, :name])]) %>
|
||||
<%= apply(Heroicons.Solid, @name, [Map.to_list(@rest)]) %>
|
||||
<% end %>
|
||||
"""
|
||||
end
|
||||
|
||||
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" class={@class}>
|
||||
<%= render_slot(@inner_block) %>
|
||||
</a>
|
||||
"""
|
||||
end
|
||||
|
||||
def link(%{patch: to} = assigns) do
|
||||
opts = assigns |> assigns_to_attributes() |> Keyword.put(:to, to)
|
||||
assigns = assign(assigns, :opts, opts)
|
||||
|
||||
~H"""
|
||||
<%= live_patch @opts do %><%= render_slot(@inner_block) %><% end %>
|
||||
"""
|
||||
end
|
||||
|
||||
def link(%{} = assigns) do
|
||||
opts = assigns |> assigns_to_attributes() |> Keyword.put(:to, assigns[:href] || "#")
|
||||
assigns = assign(assigns, :opts, opts)
|
||||
|
||||
~H"""
|
||||
<%= Phoenix.HTML.Link.link @opts do %><%= render_slot(@inner_block) %><% end %>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a button triggered dropdown with aria keyboard and focus supporrt.
|
||||
|
||||
|
@ -171,13 +179,14 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
<:link navigate={Routes.settings_path(LiveBeatsWeb.Endpoint, :edit)}Settings</:link>
|
||||
</.dropdown>
|
||||
"""
|
||||
def dropdown(assigns) do
|
||||
assigns =
|
||||
assigns
|
||||
|> assign_new(:img, fn -> nil end)
|
||||
|> assign_new(:title, fn -> nil end)
|
||||
|> assign_new(:subtitle, fn -> nil end)
|
||||
attr :id, :string, required: true
|
||||
attr :ok, :string, required: true
|
||||
attr :img, :list, default: []
|
||||
attr :title, :list, default: []
|
||||
attr :subtitle, :list, default: []
|
||||
attr :link, :list, default: []
|
||||
|
||||
def dropdown(assigns) do
|
||||
~H"""
|
||||
<!-- User account dropdown -->
|
||||
<div class="px-3 mt-6 relative inline-block text-left">
|
||||
|
@ -194,19 +203,32 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
<span class="flex w-full justify-between items-center">
|
||||
<span class="flex min-w-0 items-center justify-between space-x-3">
|
||||
<%= for img <- @img do %>
|
||||
<img class="w-10 h-10 bg-gray-300 rounded-full flex-shrink-0" alt="" {assigns_to_attributes(img)}/>
|
||||
<img
|
||||
class="w-10 h-10 bg-gray-300 rounded-full flex-shrink-0"
|
||||
alt=""
|
||||
{assigns_to_attributes(img)}
|
||||
/>
|
||||
<% end %>
|
||||
<span class="flex-1 flex flex-col min-w-0">
|
||||
<span class="text-gray-900 text-sm font-medium truncate"><%= render_slot(@title) %></span>
|
||||
<span class="text-gray-900 text-sm font-medium truncate">
|
||||
<%= render_slot(@title) %>
|
||||
</span>
|
||||
<span class="text-gray-500 text-sm truncate"><%= render_slot(@subtitle) %></span>
|
||||
</span>
|
||||
</span>
|
||||
<svg class="flex-shrink-0 h-5 w-5 text-gray-400 group-hover:text-gray-500"
|
||||
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
|
||||
fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
<svg
|
||||
class="flex-shrink-0 h-5 w-5 text-gray-400 group-hover:text-gray-500"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 20 20"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<path
|
||||
fill-rule="evenodd"
|
||||
d="M10 3a1 1 0 01.707.293l3 3a1 1 0 01-1.414 1.414L10 5.414 7.707 7.707a1 1 0 01-1.414-1.414l3-3A1 1 0 0110 3zm-3.707 9.293a1 1 0 011.414 0L10 14.586l2.293-2.293a1 1 0 011.414 1.414l-3 3a1 1 0 01-1.414 0l-3-3a1 1 0 010-1.414z"
|
||||
clip-rule="evenodd"></path>
|
||||
clip-rule="evenodd"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
|
@ -223,8 +245,11 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
<.link
|
||||
tabindex="-1"
|
||||
role="menuitem"
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-purple-500" {link}
|
||||
><%= render_slot(link) %></.link>
|
||||
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-purple-500"
|
||||
{link}
|
||||
>
|
||||
<%= render_slot(link) %>
|
||||
</.link>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -333,30 +358,46 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
|> JS.dispatch("click", to: "##{id} [data-modal-return]")
|
||||
end
|
||||
|
||||
def modal(assigns) do
|
||||
assigns =
|
||||
assigns
|
||||
|> assign_new(:show, fn -> false end)
|
||||
|> assign_new(:patch, fn -> nil end)
|
||||
|> assign_new(:navigate, fn -> nil end)
|
||||
|> assign_new(:on_cancel, fn -> %JS{} end)
|
||||
|> assign_new(:on_confirm, fn -> %JS{} end)
|
||||
# slots
|
||||
|> assign_new(:title, fn -> [] end)
|
||||
|> assign_new(:confirm, fn -> [] end)
|
||||
|> assign_new(:cancel, fn -> [] end)
|
||||
|> assign_rest(~w(id show patch navigate on_cancel on_confirm title confirm cancel)a)
|
||||
attr :id, :string, required: true
|
||||
attr :show, :boolean, default: false
|
||||
attr :patch, :string, default: nil
|
||||
attr :navigate, :string, default: nil
|
||||
attr :on_cancel, JS, default: %JS{}
|
||||
attr :on_confirm, JS, default: %JS{}
|
||||
# slots
|
||||
attr :title, :list, default: []
|
||||
attr :confirm, :list, default: []
|
||||
attr :cancel, :list, default: []
|
||||
attr :rest, :global
|
||||
|
||||
def modal(assigns) do
|
||||
~H"""
|
||||
<div id={@id} class={"fixed z-10 inset-0 overflow-y-auto #{if @show, do: "fade-in", else: "hidden"}"} {@rest}>
|
||||
<div
|
||||
id={@id}
|
||||
class={"fixed z-10 inset-0 overflow-y-auto #{if @show, do: "fade-in", else: "hidden"}"}
|
||||
{@rest}
|
||||
>
|
||||
<.focus_wrap id={"#{@id}-focus-wrap"} content={"##{@id}-container"}>
|
||||
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0" aria-labelledby={"#{@id}-title"} aria-describedby={"#{@id}-description"} role="dialog" aria-modal="true" tabindex="0">
|
||||
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
|
||||
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">​</span>
|
||||
<div
|
||||
class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"
|
||||
aria-labelledby={"#{@id}-title"}
|
||||
aria-describedby={"#{@id}-description"}
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
tabindex="0"
|
||||
>
|
||||
<div class="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true">
|
||||
</div>
|
||||
<span class="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
|
||||
​
|
||||
</span>
|
||||
<div
|
||||
id={"#{@id}-container"}
|
||||
class={"#{if @show, do: "fade-in-scale", else: "hidden"} sticky inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform sm:my-8 sm:align-middle sm:max-w-xl sm:w-full sm:p-6"}
|
||||
phx-window-keydown={hide_modal(@on_cancel, @id)} phx-key="escape"
|
||||
class={
|
||||
"#{if @show, do: "fade-in-scale", else: "hidden"} sticky inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform sm:my-8 sm:align-middle sm:max-w-xl sm:w-full sm:p-6"
|
||||
}
|
||||
phx-window-keydown={hide_modal(@on_cancel, @id)}
|
||||
phx-key="escape"
|
||||
phx-click-away={hide_modal(@on_cancel, @id)}
|
||||
>
|
||||
<%= if @patch do %>
|
||||
|
@ -366,16 +407,16 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
<.link navigate={@navigate} data-modal-return class="hidden"></.link>
|
||||
<% end %>
|
||||
<div class="sm:flex sm:items-start">
|
||||
<div class={"mx-auto flex-shrink-0 flex items-center justify-center h-8 w-8 rounded-full bg-purple-100 sm:mx-0"}>
|
||||
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-8 w-8 rounded-full bg-purple-100 sm:mx-0">
|
||||
<!-- Heroicon name: outline/plus -->
|
||||
<.icon name={:information_circle} outlined class="h-6 w-6 text-purple-600"/>
|
||||
<.icon name={:information_circle} outlined class="h-6 w-6 text-purple-600" />
|
||||
</div>
|
||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full mr-12">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900" id={"#{@id}-title"}>
|
||||
<%= render_slot(@title) %>
|
||||
</h3>
|
||||
<div class="mt-2">
|
||||
<p id={"#{@id}-content"} class={"text-sm text-gray-500"}>
|
||||
<p id={"#{@id}-content"} class="text-sm text-gray-500">
|
||||
<%= render_slot(@inner_block) %>
|
||||
</p>
|
||||
</div>
|
||||
|
@ -410,39 +451,35 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
"""
|
||||
end
|
||||
|
||||
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
|
||||
attr :value, :integer
|
||||
|
||||
def progress_bar(assigns) do
|
||||
assigns =
|
||||
assigns
|
||||
|> assign_new(:min, fn -> 0 end)
|
||||
|> assign_new(:max, fn -> 100 end)
|
||||
|> assign_new(:value, fn -> assigns[:min] || 0 end)
|
||||
assigns = assign_new(assigns, :value, fn -> assigns[:min] || 0 end)
|
||||
|
||||
~H"""
|
||||
<div id={"#{@id}-container"} class="bg-gray-200 flex-auto dark:bg-black rounded-full overflow-hidden" phx-update="ignore">
|
||||
<div
|
||||
id={"#{@id}-container"}
|
||||
class="bg-gray-200 flex-auto dark:bg-black rounded-full overflow-hidden"
|
||||
phx-update="ignore"
|
||||
>
|
||||
<div
|
||||
id={@id}
|
||||
class="bg-lime-500 dark:bg-lime-400 h-1.5 w-0"
|
||||
data-min={@min}
|
||||
data-max={@max}
|
||||
data-val={@value}>
|
||||
data-val={@value}
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
def title_bar(assigns) do
|
||||
assigns = assign_new(assigns, :actions, fn -> [] end)
|
||||
attr :actions, :list, default: []
|
||||
|
||||
def title_bar(assigns) do
|
||||
~H"""
|
||||
<!-- Page title & actions -->
|
||||
<div class="border-b border-gray-200 px-4 py-4 sm:flex sm:items-center sm:justify-between sm:px-6 lg:px-8 sm:h-16">
|
||||
|
@ -460,13 +497,15 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
"""
|
||||
end
|
||||
|
||||
def button(%{patch: _} = assigns) do
|
||||
assigns = assign_new(assigns, :primary, fn -> false end)
|
||||
attr :patch, :string
|
||||
attr :primary, :boolean, default: false
|
||||
attr :rest, :global
|
||||
|
||||
def button(%{patch: _} = assigns) do
|
||||
~H"""
|
||||
<%= if @primary do %>
|
||||
<%= live_patch [to: @patch, class: "order-0 inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 sm:order-1 sm:ml-3"] ++
|
||||
assigns_to_attributes(assigns, [:primary, :patch]) do %>
|
||||
Map.to_list(@rest) do %>
|
||||
<%= render_slot(@inner_block) %>
|
||||
<% end %>
|
||||
<% else %>
|
||||
|
@ -479,24 +518,32 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
end
|
||||
|
||||
def button(%{} = assigns) do
|
||||
assigns =
|
||||
assigns
|
||||
|> assign_new(:primary, fn -> false end)
|
||||
|> assign(:rest, assigns_to_attributes(assigns))
|
||||
|
||||
~H"""
|
||||
<%= if @primary do %>
|
||||
<button type="button" class="order-0 inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 sm:order-1 sm:ml-3" {@rest}>
|
||||
<button
|
||||
type="button"
|
||||
class="order-0 inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-purple-600 hover:bg-purple-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 sm:order-1 sm:ml-3"
|
||||
{@rest}
|
||||
>
|
||||
<%= render_slot(@inner_block) %>
|
||||
</button>
|
||||
<% else %>
|
||||
<button type="button" class="order-1 inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 sm:order-0 sm:ml-0 lg:ml-3" {@rest}>
|
||||
<button
|
||||
type="button"
|
||||
class="order-1 inline-flex items-center px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-purple-500 sm:order-0 sm:ml-0 lg:ml-3"
|
||||
{@rest}
|
||||
>
|
||||
<%= render_slot(@inner_block) %>
|
||||
</button>
|
||||
<% end %>
|
||||
"""
|
||||
end
|
||||
|
||||
attr :row_id, :any, default: false
|
||||
attr :rows, :list, required: true
|
||||
# slots
|
||||
attr :col, :list, required: true
|
||||
|
||||
def table(assigns) do
|
||||
assigns =
|
||||
assigns
|
||||
|
@ -510,8 +557,7 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
<thead>
|
||||
<tr class="border-t border-gray-200">
|
||||
<%= for col <- @col do %>
|
||||
<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"><%= col.label %></span>
|
||||
</th>
|
||||
<% end %>
|
||||
|
@ -521,7 +567,9 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
<%= for {row, i} <- Enum.with_index(@rows) do %>
|
||||
<tr id={@row_id && @row_id.(row)} class="hover:bg-gray-50">
|
||||
<%= for col <- @col do %>
|
||||
<td class={"px-6 py-3 whitespace-nowrap text-sm font-medium text-gray-900 #{if i == 0, do: "max-w-0 w-full"} #{col[:class]}"}>
|
||||
<td class={
|
||||
"px-6 py-3 whitespace-nowrap text-sm font-medium text-gray-900 #{if i == 0, do: "max-w-0 w-full"} #{col[:class]}"
|
||||
}>
|
||||
<div class="flex items-center space-x-3 lg:pl-2">
|
||||
<%= render_slot(col, row) %>
|
||||
</div>
|
||||
|
@ -536,13 +584,17 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
"""
|
||||
end
|
||||
|
||||
attr :id, :any, required: true
|
||||
attr :module, :atom, required: true
|
||||
attr :row_id, :any, default: false
|
||||
attr :rows, :list, required: true
|
||||
attr :owns_profile?, :boolean, default: false
|
||||
attr :active_id, :any, default: nil
|
||||
# slots
|
||||
attr :col, :list
|
||||
|
||||
def live_table(assigns) do
|
||||
assigns =
|
||||
assigns
|
||||
|> assign_new(:row_id, fn -> false end)
|
||||
|> assign_new(:active_id, fn -> nil end)
|
||||
|> assign_new(:owns_profile?, fn -> assigns.owns_profile? end)
|
||||
|> assign(:col, for(col <- assigns.col, col[:if] != false, do: col))
|
||||
assigns = assign(assigns, :col, for(col <- assigns.col, col[:if] != false, do: col))
|
||||
|
||||
~H"""
|
||||
<div class="hidden mt-8 sm:block">
|
||||
|
@ -551,8 +603,7 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
<thead>
|
||||
<tr class="border-t border-gray-200">
|
||||
<%= for col <- @col do %>
|
||||
<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"><%= col.label %></span>
|
||||
</th>
|
||||
<% end %>
|
||||
|
@ -563,10 +614,12 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
<.live_component
|
||||
module={@module}
|
||||
id={@row_id.(row)}
|
||||
row={row} col={@col}
|
||||
row={row}
|
||||
col={@col}
|
||||
index={i}
|
||||
active_id={@active_id}
|
||||
class="hover:bg-gray-50",
|
||||
class="hover:bg-gray-50"
|
||||
,
|
||||
owns_profile?={@owns_profile?}
|
||||
/>
|
||||
<% end %>
|
||||
|
@ -595,8 +648,4 @@ defmodule LiveBeatsWeb.LiveHelpers do
|
|||
|> JS.dispatch("js:focus-closest", to: to)
|
||||
|> hide(to)
|
||||
end
|
||||
|
||||
defp assign_rest(assigns, exclude) do
|
||||
assign(assigns, :rest, assigns_to_attributes(assigns, exclude))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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,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
|
||||
|
||||
|
|
|
@ -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) %>|
|
||||
|
|
|
@ -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,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&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&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&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&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&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&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&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&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&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&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&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&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&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&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&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=facearea&facepad=2&w=256&h=256&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&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>
|
||||
|
@ -457,4 +612,4 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
defmodule LiveBeatsWeb.LayoutView do
|
||||
use LiveBeatsWeb, :view
|
||||
use Phoenix.Component
|
||||
|
||||
# Phoenix LiveDashboard is available only in development by default,
|
||||
# so we instruct Elixir to not warn if the dashboard route is missing.
|
||||
|
@ -15,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>
|
||||
|
@ -35,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>
|
||||
|
@ -69,11 +92,10 @@ defmodule LiveBeatsWeb.LayoutView do
|
|||
|
||||
def sidebar_account_dropdown(assigns) do
|
||||
~H"""
|
||||
<.dropdown id={@id}>
|
||||
<:img src={@current_user.avatar_url}/>
|
||||
<.dropdown id={@id} foo="bar">
|
||||
<: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>
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -7,7 +7,7 @@ defmodule LiveBeats.MixProject do
|
|||
version: "0.1.0",
|
||||
elixir: "~> 1.12",
|
||||
elixirc_paths: elixirc_paths(Mix.env()),
|
||||
compilers: [:gettext] ++ Mix.compilers(),
|
||||
compilers: [:gettext, :phoenix_live_view] ++ Mix.compilers(),
|
||||
start_permanent: Mix.env() == :prod,
|
||||
aliases: aliases(),
|
||||
deps: deps()
|
||||
|
|
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", "ae2fb6bddd64fc7e3908ee6dd632f608254f532e", []},
|
||||
"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", "1ae64fb69b0951f2f67f01d369ec9a4006060001", []},
|
||||
"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"},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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