This commit is contained in:
Chris McCord 2021-10-27 16:02:56 -04:00
parent e28abc0a0a
commit 708bf715e1
16 changed files with 406 additions and 184 deletions

View file

@ -3,6 +3,49 @@
@import "tailwindcss/components"; @import "tailwindcss/components";
@import "tailwindcss/utilities"; @import "tailwindcss/utilities";
/* animations */
.fade-in-scale {
animation: 0.2s ease-in 0s normal forwards 1 fade-in-scale-keys;
}
.fade-out-scale {
animation: 0.2s ease-out 0s normal forwards 1 fade-out-scale-keys;
}
.slide-in-right {
animation: slide-in-right-keys 0.2s forwards;
}
.fade-in {
animation: 0.2s ease-out 0s normal forwards 1 fade-in-keys;
}
.fade-out {
animation: 0.2s ease-out 0s normal forwards 1 fade-out-keys;
}
@keyframes fade-in-scale-keys{
0% { scale: 0.95; opacity: 0; }
100% { scale: 1.0; opacity: 1; }
}
@keyframes fade-out-scale-keys{
0% { scale: 1.0; opacity: 1; }
100% { scale: 0.95; opacity: 0; }
}
@keyframes fade-in-keys{
0% { opacity: 0; }
100% { opacity: 1; }
}
@keyframes fade-out-keys{
0% { opacity: 1; }
100% { opacity: 0; }
}
@keyframes slide-in-right-keys{
100%{ transform: translateX(0%); }
}
/* Alerts and form errors used by phx.new */ /* Alerts and form errors used by phx.new */
.alert { .alert {
padding: 15px; padding: 15px;

View file

@ -1,40 +1,48 @@
// We import the CSS which is extracted to its own file by esbuild.
// Remove this line if you add a your own CSS build pipeline (e.g postcss).
import Alpine from "alpinejs"
// If you want to use Phoenix channels, run `mix help phx.gen.channel`
// to get started and then uncomment the line below.
// import "./user_socket.js"
// You can include dependencies in two ways.
//
// The simplest option is to put them in assets/vendor and
// import them using relative paths:
//
// import "./vendor/some-package.js"
//
// Alternatively, you can `npm install some-package` and import
// them using a path starting with the package name:
//
// import "some-package"
//
// Include phoenix_html to handle method=PUT/DELETE in forms and buttons.
import "phoenix_html" import "phoenix_html"
// Establish Phoenix Socket and LiveView configuration.
import {Socket} from "phoenix" import {Socket} from "phoenix"
import {LiveSocket} from "./phoenix_live_view" import {LiveSocket} from "./phoenix_live_view"
import topbar from "../vendor/topbar" import topbar from "../vendor/topbar"
let render = (webComponent, html) => {
let shadow = webComponent.attachShadow({mode: "open"})
document.querySelectorAll("link").forEach(link => shadow.appendChild(link.cloneNode()))
let div = document.createElement("div")
div.setAttribute("class", webComponent.getAttribute("class"))
div.innerHTML = html || webComponent.innerHTML
shadow.appendChild(div)
return div
}
let Hooks = {}
Hooks.Progress = {
setWidth(at){
this.el.style.width = `${Math.floor((at / (this.max - this.min)) * 100)}%`
},
mounted(){
this.min = parseInt(this.el.dataset.min)
this.max = parseInt(this.el.dataset.max)
this.val = parseInt(this.el.dataset.val)
setInterval(() => this.setWidth(this.val++), 1000)
}
}
let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content") let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("content")
let liveSocket = new LiveSocket("/live", Socket, { let liveSocket = new LiveSocket("/live", Socket, {
params: { _csrf_token: csrfToken }, hooks: Hooks,
params: {_csrf_token: csrfToken},
dom: { dom: {
onBeforeElUpdated(from, to) { onNodeAdded(node){
if (from._x_dataStack) { if(node.getAttribute && node.getAttribute("data-fade-in")){
window.Alpine.clone(from, to); from.classList.add("fade-in")
} }
}, },
onBeforeElUpdated(from, to) {
if(from.classList.contains("fade-in")){
from.classList.remove("fade-in")
from.classList.add("fade-in")
}
}
} }
}) })
@ -52,7 +60,4 @@ liveSocket.connect()
// >> liveSocket.disableLatencySim() // >> liveSocket.disableLatencySim()
window.liveSocket = liveSocket window.liveSocket = liveSocket
Alpine.start()
window.Alpine = Alpine

View file

@ -4,9 +4,6 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"dependencies": {
"alpinejs": "^3.2.2"
},
"devDependencies": { "devDependencies": {
"@tailwindcss/forms": "^0.3.3", "@tailwindcss/forms": "^0.3.3",
"autoprefixer": "^10.2.0", "autoprefixer": "^10.2.0",
@ -165,19 +162,6 @@
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
"dev": true "dev": true
}, },
"node_modules/@vue/reactivity": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.7.tgz",
"integrity": "sha512-VDeQiZs6s5m1W7hIX+vzmokDCHPEKNYrSxoHWXj4MiGamcT5XZxACj/VXOCK9c6qz36qK5EQOfDWtmVhxfI2hQ==",
"dependencies": {
"@vue/shared": "3.2.7"
}
},
"node_modules/@vue/shared": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.7.tgz",
"integrity": "sha512-YwGOcNZjOY/MmadpzFBXWyHEwZSf0lVU4XF5zpD7tXC9dmqjdo38Jkk06wATu4LYHDPW4emXKMB5YLFPWPkwFA=="
},
"node_modules/acorn": { "node_modules/acorn": {
"version": "7.4.1", "version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
@ -210,14 +194,6 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/alpinejs": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.2.4.tgz",
"integrity": "sha512-nUHI7QarOyqFX1vKTP5MC54yBFh9/J+YyxE50aP3lb6aZGxwX0HIPGVuAwy2SXQFP45P4/jKHv1LOuvRp/etiQ==",
"dependencies": {
"@vue/reactivity": "^3.0.2"
}
},
"node_modules/ansi-styles": { "node_modules/ansi-styles": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@ -1682,19 +1658,6 @@
"integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==",
"dev": true "dev": true
}, },
"@vue/reactivity": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.7.tgz",
"integrity": "sha512-VDeQiZs6s5m1W7hIX+vzmokDCHPEKNYrSxoHWXj4MiGamcT5XZxACj/VXOCK9c6qz36qK5EQOfDWtmVhxfI2hQ==",
"requires": {
"@vue/shared": "3.2.7"
}
},
"@vue/shared": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.7.tgz",
"integrity": "sha512-YwGOcNZjOY/MmadpzFBXWyHEwZSf0lVU4XF5zpD7tXC9dmqjdo38Jkk06wATu4LYHDPW4emXKMB5YLFPWPkwFA=="
},
"acorn": { "acorn": {
"version": "7.4.1", "version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
@ -1718,14 +1681,6 @@
"integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==",
"dev": true "dev": true
}, },
"alpinejs": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.2.4.tgz",
"integrity": "sha512-nUHI7QarOyqFX1vKTP5MC54yBFh9/J+YyxE50aP3lb6aZGxwX0HIPGVuAwy2SXQFP45P4/jKHv1LOuvRp/etiQ==",
"requires": {
"@vue/reactivity": "^3.0.2"
}
},
"ansi-styles": { "ansi-styles": {
"version": "4.3.0", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",

View file

@ -2,9 +2,7 @@
"scripts": { "scripts": {
"deploy": "NODE_ENV=production tailwindcss --postcss --minify -i css/app.css -o ../priv/static/assets/app.css" "deploy": "NODE_ENV=production tailwindcss --postcss --minify -i css/app.css -o ../priv/static/assets/app.css"
}, },
"dependencies": { "dependencies": {},
"alpinejs": "^3.2.2"
},
"devDependencies": { "devDependencies": {
"@tailwindcss/forms": "^0.3.3", "@tailwindcss/forms": "^0.3.3",
"autoprefixer": "^10.2.0", "autoprefixer": "^10.2.0",

View file

@ -88,6 +88,7 @@ defmodule LiveBeatsWeb do
import Phoenix.View import Phoenix.View
import LiveBeatsWeb.ErrorHelpers import LiveBeatsWeb.ErrorHelpers
import LiveBeatsWeb.LiveHelpers
import LiveBeatsWeb.Gettext import LiveBeatsWeb.Gettext
alias LiveBeatsWeb.Router.Helpers, as: Routes alias LiveBeatsWeb.Router.Helpers, as: Routes
end end

View file

@ -35,6 +35,10 @@ defmodule LiveBeatsWeb.OAuthCallbackController do
redirect(conn, to: "/") redirect(conn, to: "/")
end end
def sign_out(conn, _) do
LiveBeatsWeb.UserAuth.log_out_user(conn)
end
defp github_client(conn) do defp github_client(conn) do
conn.assigns[:github_client] || LiveBeats.Github conn.assigns[:github_client] || LiveBeats.Github
end end

View file

@ -6,7 +6,8 @@ defmodule LiveBeatsWeb.UserAuth do
alias LiveBeats.Accounts alias LiveBeats.Accounts
alias LiveBeatsWeb.Router.Helpers, as: Routes alias LiveBeatsWeb.Router.Helpers, as: Routes
def mount_defaults(_params, session, socket) do def on_mount(:default, _params, session, socket) do
socket = LiveView.assign(socket, :nonce, Map.fetch!(session, "nonce"))
case session do case session do
%{"user_id" => user_id} -> %{"user_id" => user_id} ->
{:cont, LiveView.assign_new(socket, :current_user, fn -> Accounts.get_user!(user_id) end)} {:cont, LiveView.assign_new(socket, :current_user, fn -> Accounts.get_user!(user_id) end)}

View file

@ -3,6 +3,18 @@ defmodule LiveBeatsWeb.HomeLive do
def render(assigns) do def render(assigns) do
~H""" ~H"""
<.title_bar>
LiveBeats - Chill
<:action>Share</:action>
<:action primary phx-click={show_modal("add-songs")}>Add Songs</:action>
</.title_bar>
<.modal id="add-songs">
<:title>Add Songs</:title>
a modal
<:cancel>Close</:cancel>
<:confirm>Add</:confirm>
</.modal>
<!-- users --> <!-- users -->
<div class="px-4 mt-6 sm:px-6 lg:px-8"> <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> <h2 class="text-gray-500 text-xs font-medium uppercase tracking-wide">Who's Here</h2>

View file

@ -0,0 +1,187 @@
defmodule LiveBeatsWeb.LiveHelpers do
import Phoenix.LiveView
import Phoenix.LiveView.Helpers
import Phoenix.HTML
alias Phoenix.LiveView.JS
def link_patch(assigns) do
~H"""
<%= live_patch @text, [
to: @to,
class: "phx-modal-close"
] ++ assigns_to_attributes(assigns, [:to, :text]) %>
"""
end
def show_mobile_sidebar(js \\ %JS{}) do
js
|> JS.show(to: "#mobile-sidebar-container", transition: "fade-in")
|> JS.show(
to: "#mobile-sidebar",
display: "flex",
time: 300,
transition:
{"transition ease-in-out duration-300 transform", "-translate-x-full", "translate-x-0"}
)
end
def hide_mobile_sidebar(js \\ %JS{}) do
js
|> JS.hide(to: "#mobile-sidebar-container", transition: "fade-out")
|> JS.hide(
to: "#mobile-sidebar",
time: 300,
transition:
{"transition ease-in-out duration-300 transform", "translate-x-0", "-translate-x-full"}
)
end
def show_dropdown(to) do
JS.show(
to: to,
transition:
{"transition ease-out duration-120", "transform opacity-0 scale-95",
"transform opacity-100 scale-100"}
)
end
def hide_dropdown(to) do
JS.hide(
to: to,
transition:
{"transition ease-in duration-120", "transform opacity-100 scale-100",
"transform opacity-0 scale-95"}
)
end
def show_modal(js \\ %JS{}, id) do
js
|> JS.show(
to: "##{id}",
display: "inline-block",
transition: {"ease-out duration-300", "opacity-0", "opacity-100"}
)
|> JS.show(
to: "##{id} .modal-content",
display: "inline-block",
transition:
{"ease-out duration-300", "opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95",
"opacity-100 translate-y-0 sm:scale-100"}
)
end
def hide_modal(js \\ %JS{}, id) do
js
|> JS.hide(
to: "##{id}",
transition: {"ease-in duration-200", "opacity-100", "opacity-0"}
)
|> JS.hide(
to: "##{id} .modal-content",
transition:
{"ease-in duration-200", "opacity-100 translate-y-0 sm:scale-100",
"opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"}
)
end
def modal(assigns) do
assigns =
assigns
|> assign_new(:title, fn -> "" end)
|> assign_new(:return_to, fn -> nil end)
~H"""
<div id={@id} class="hidden fixed z-10 inset-0 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-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">&#8203;</span>
<div
class="modal-content inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6"
phx-click-away={hide_modal(@id)}
>
<div class="sm:flex sm:items-start">
<div class="mx-auto flex-shrink-0 flex items-center justify-center h-12 w-12 rounded-full bg-green-100 sm:mx-0 sm:h-10 sm:w-10">
<!-- Heroicon name: outline/exclamation -->
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-green-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v3m0 0v3m0-3h3m-3 0H9m12 0a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
</div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900" id="modal-title">
<%= render_slot(@title) %>
</h3>
<div class="mt-2">
<p class="text-sm text-gray-500">
<%= render_slot(@inner_block) %>
</p>
</div>
</div>
</div>
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button type="button" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-red-600 text-base font-medium text-white hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-red-500 sm:ml-3 sm:w-auto sm:text-sm">
<%= render_slot(@confirm) %>
</button>
<button
type="button"
class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:w-auto sm:text-sm"
phx-window-keydown={hide_modal(@id)} phx-key="escape"
phx-click={hide_modal(@id)}
>
<%= render_slot(@cancel) %>
</button>
</div>
</div>
</div>
</div>
"""
end
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)
~H"""
<div class="bg-gray-200 flex-auto dark:bg-black rounded-full overflow-hidden" phx-update="ignore">
<div id="progress"
class="bg-lime-500 dark:bg-lime-400 h-1.5 w-0"
phx-hook="Progress"
data-min={@min}
data-max={@max}
data-val={@value}>
</div>
</div>
"""
end
def title_bar(assigns) do
assigns = assign_new(assigns, :action, fn -> [] end)
~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">
<div class="flex-1 min-w-0">
<h1 class="text-lg font-medium leading-6 text-gray-900 sm:truncate">
<%= render_slot(@inner_block) %>
</h1>
</div>
<div class="mt-4 flex sm:mt-0 sm:ml-4">
<%= for action <- @action, rest = assigns_to_attributes(action) do %>
<%= if action[: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}>
<%= render_slot(action) %>
</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}>
<%= render_slot(action) %>
</button>
<% end %>
<% end %>
</div>
</div>
"""
end
end

View file

@ -1,6 +1,8 @@
defmodule LiveBeatsWeb.PlayerLive do defmodule LiveBeatsWeb.PlayerLive do
use LiveBeatsWeb, :live_view use LiveBeatsWeb, :live_view
on_mount LiveBeatsWeb.UserAuth
def render(assigns) do def render(assigns) do
~H""" ~H"""
<!-- player --> <!-- player -->
@ -17,13 +19,9 @@ defmodule LiveBeatsWeb.PlayerLive do
</p> </p>
</div> </div>
</div> </div>
<div class="bg-gray-200 flex-auto dark:bg-black rounded-full overflow-hidden" phx-update="ignore">
<div class="bg-lime-500 dark:bg-lime-400 h-1.5" role="progressbar" <.progress_bar nonce={@nonce} />
style="width: 0%;"
x-data="{progress: 0}"
x-init="setInterval(() => $el.style.width = `${progress++}%`, 1000)">
</div>
</div>
<div class="text-gray-500 dark:text-gray-400 flex-row justify-between text-sm font-medium tabular-nums"> <div class="text-gray-500 dark:text-gray-400 flex-row justify-between text-sm font-medium tabular-nums">
<div><%= @time %></div> <div><%= @time %></div>
<div><%= @count %></div> <div><%= @count %></div>

View file

@ -10,6 +10,7 @@ defmodule LiveBeatsWeb.Router do
plug :put_root_layout, {LiveBeatsWeb.LayoutView, :root} plug :put_root_layout, {LiveBeatsWeb.LayoutView, :root}
plug :protect_from_forgery plug :protect_from_forgery
plug :put_secure_browser_headers plug :put_secure_browser_headers
plug :put_nonce
end end
pipeline :api do pipeline :api do
@ -19,10 +20,10 @@ defmodule LiveBeatsWeb.Router do
scope "/", LiveBeatsWeb do scope "/", LiveBeatsWeb do
pipe_through :browser pipe_through :browser
live_session :default, on_mount: {LiveBeatsWeb.UserAuth, :mount_defaults} do live_session :default, on_mount: LiveBeatsWeb.UserAuth do
live "/test", IndexLive
live "/", HomeLive, :index live "/", HomeLive, :index
live "/signin", SigninLive, :index live "/signin", SigninLive, :index
delete "/signout", OAuthCallbackController, :sign_out
end end
end end
@ -64,4 +65,20 @@ defmodule LiveBeatsWeb.Router do
forward "/mailbox", Plug.Swoosh.MailboxPreview forward "/mailbox", Plug.Swoosh.MailboxPreview
end end
end end
defp put_nonce(conn, _) do
nonce = Phoenix.HTML.Tag.csrf_token_value()
endpoint = Phoenix.Controller.endpoint_module(conn)
url = endpoint.url()
uri = endpoint.struct_url()
ws_url = %URI{uri | scheme: "ws"}
wss_url = %URI{uri | scheme: "wss"}
conn
|> put_session(:nonce, nonce)
|> put_resp_header(
"content-security-policy",
"script-src 'nonce-#{nonce}' #{url} #{ws_url}; connect-src 'self' #{ws_url} #{wss_url}"
)
end
end end

View file

@ -10,36 +10,20 @@
<script defer phx-track-static type="text/javascript" src={Routes.static_path(@conn, "/assets/app.js")}></script> <script defer phx-track-static type="text/javascript" src={Routes.static_path(@conn, "/assets/app.js")}></script>
</head> </head>
<body> <body>
<div x-data="{open: false}" @keydown.window.escape="open = false" class="relative h-screen flex overflow-hidden bg-white"> <div class="relative h-screen flex overflow-hidden bg-white">
<div x-show="open" class="fixed inset-0 flex z-40 lg:hidden" <div id="mobile-sidebar-container" class="fixed inset-0 flex z-40 lg:hidden" aria-modal="true" style="display: none;">
x-description="Off-canvas menu for mobile, show/hide based on off-canvas menu state." x-ref="dialog" <div
aria-modal="true" style="display: none;"> class="fixed inset-0 bg-gray-600 bg-opacity-75"
phx-click={hide_mobile_sidebar()}>
<div x-show="open" x-transition:enter="transition-opacity ease-linear duration-300"
x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
x-transition:leave="transition-opacity ease-linear duration-300" x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
x-description="Off-canvas menu overlay, show/hide based on off-canvas menu state."
class="fixed inset-0 bg-gray-600 bg-opacity-75" @click="open = false" aria-hidden="true" style="display: none;">
</div> </div>
<div x-show="open" x-transition:enter="transition ease-in-out duration-300 transform" <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">
x-transition:enter-start="-translate-x-full" x-transition:enter-end="translate-x-0" <div class="absolute top-0 right-0 -mr-12 pt-2">
x-transition:leave="transition ease-in-out duration-300 transform" x-transition:leave-start="translate-x-0"
x-transition:leave-end="-translate-x-full"
x-description="Off-canvas menu, show/hide based on off-canvas menu state."
class="relative flex-1 flex flex-col max-w-xs w-full pt-5 pb-4 bg-white" style="display: none;">
<div x-show="open" x-transition:enter="ease-in-out duration-300" x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100" x-transition:leave="ease-in-out duration-300"
x-transition:leave-start="opacity-100" x-transition:leave-end="opacity-0"
x-description="Close button, show/hide based on off-canvas menu state."
class="absolute top-0 right-0 -mr-12 pt-2" style="display: none;">
<button type="button" <button type="button"
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" 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"
@click="open = false"> phx-click={hide_mobile_sidebar()}>
<span class="sr-only">Close sidebar</span> <span class="sr-only">Close sidebar</span>
<svg class="h-6 w-6 text-white" x-description="Heroicon name: outline/x" xmlns="http://www.w3.org/2000/svg" <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"> 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> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg> </svg>
@ -56,9 +40,7 @@
<%= live_redirect to: Routes.home_path(@conn, :index), <%= live_redirect to: Routes.home_path(@conn, :index),
class: "bg-gray-100 text-gray-900 group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md" do %> class: "bg-gray-100 text-gray-900 group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md" do %>
<svg class="text-gray-500 mr-3 flex-shrink-0 h-6 w-6" x-state:on="Current" x-state:off="Default" <svg class="text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
x-state-description="Current: &quot;text-gray-500&quot;, Default: &quot;text-gray-400 group-hover:text-gray-500&quot;"
x-description="Heroicon name: outline/home" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"> viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"> d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6">
@ -68,11 +50,8 @@
<% end %> <% end %>
<a href="#" <a href="#"
class="text-gray-600 hover:text-gray-900 hover:bg-gray-50 group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md" class="text-gray-600 hover:text-gray-900 hover:bg-gray-50 group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md">
x-state-description="undefined: &quot;bg-gray-100 text-gray-900&quot;, undefined: &quot;text-gray-600 hover:text-gray-900 hover:bg-gray-50&quot;">
<svg class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6" <svg class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
x-state-description="undefined: &quot;text-gray-500&quot;, undefined: &quot;text-gray-400 group-hover:text-gray-500&quot;"
x-description="Heroicon name: outline/view-list" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"> viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 6h16M4 10h16M4 14h16M4 18h16"></path> d="M4 6h16M4 10h16M4 14h16M4 18h16"></path>
@ -81,14 +60,55 @@
</a> </a>
<%= if @current_user do %> <%= if @current_user do %>
<!-- User account dropdown -->
<div class="px-3 mt-6 relative inline-block text-left w-full">
<div>
<button type="button"
class="group w-full bg-gray-100 rounded-md px-3.5 py-2 text-sm text-left font-medium text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-purple-500"
id="options-menu-button"
phx-click={show_dropdown("#account-dropdown-mobile")}>
<span class="flex w-full justify-between items-center">
<span class="flex min-w-0 items-center justify-between space-x-3">
<img class="w-10 h-10 bg-gray-300 rounded-full flex-shrink-0"
src="https://images.unsplash.com/photo-1502685104226-ee32379fefbe?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=3&amp;w=256&amp;h=256&amp;q=80"
alt="">
<span class="flex-1 flex flex-col min-w-0">
<span class="text-gray-900 text-sm font-medium truncate"><%= @current_user.name %></span>
<span class="text-gray-500 text-sm truncate">@<%= @current_user.username %></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"
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>
</svg>
</span>
</button>
</div>
<div id="account-dropdown-mobile" phx-click-away={hide_dropdown("#account-dropdown-mobile")} class="hidden z-10 mx-3 origin-top absolute right-0 left-0 mt-1 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-200 focus:outline-none" role="menu">
<div class="py-1" role="none">
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-0">View profile</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-1">Settings</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-2">Notifications</a>
</div>
<div class="py-1" role="none">
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-3">Get desktop app</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-4">Support</a>
</div>
<div class="py-1" role="none">
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-5">Logout</a>
</div>
</div>
</div>
<% else %> <% else %>
<%= live_redirect to: Routes.signin_path(@conn, :index), <%= live_redirect to: Routes.signin_path(@conn, :index),
class: "text-gray-600 hover:text-gray-900 hover:bg-gray-50 group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md" do %> class: "text-gray-600 hover:text-gray-900 hover:bg-gray-50 group flex items-center px-2 py-2 text-base leading-5 font-medium rounded-md" do %>
<svg class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6" <svg class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
x-state-description="undefined: &quot;text-gray-500&quot;, undefined: &quot;text-gray-400 group-hover:text-gray-500&quot;" xmlns="http://www.w3.org/2000/svg" fill="none"
x-description="Heroicon name: outline/clock" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"> viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <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> d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
@ -150,17 +170,13 @@
<!-- Sidebar component, swap this element with another sidebar if you like --> <!-- Sidebar component, swap this element with another sidebar if you like -->
<div class="h-0 flex-1 flex flex-col overflow-y-auto"> <div class="h-0 flex-1 flex flex-col overflow-y-auto">
<%= if @current_user do %> <%= if @current_user do %>
<!-- User account dropdown --> <!-- User account dropdown -->
<div x-data="{open: false, activeIndex: 0}" <div class="px-3 mt-6 relative inline-block text-left">
@keydown.escape.stop="open = false"
@click.away="open = false"
class="px-3 mt-6 relative inline-block text-left">
<div> <div>
<button type="button" <button type="button"
class="group w-full bg-gray-100 rounded-md px-3.5 py-2 text-sm text-left font-medium text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-purple-500" class="group w-full bg-gray-100 rounded-md px-3.5 py-2 text-sm text-left font-medium text-gray-700 hover:bg-gray-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-offset-gray-100 focus:ring-purple-500"
id="options-menu-button" id="options-menu-button"
x-ref="button" phx-click={show_dropdown("#account-dropdown")}>
@click="open = true">
<span class="flex w-full justify-between items-center"> <span class="flex w-full justify-between items-center">
<span class="flex min-w-0 items-center justify-between space-x-3"> <span class="flex min-w-0 items-center justify-between space-x-3">
<img class="w-10 h-10 bg-gray-300 rounded-full flex-shrink-0" <img class="w-10 h-10 bg-gray-300 rounded-full flex-shrink-0"
@ -172,7 +188,7 @@
</span> </span>
</span> </span>
<svg class="flex-shrink-0 h-5 w-5 text-gray-400 group-hover:text-gray-500" <svg class="flex-shrink-0 h-5 w-5 text-gray-400 group-hover:text-gray-500"
x-description="Heroicon name: solid/selector" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"
fill="currentColor" aria-hidden="true"> fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" <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" 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"
@ -181,18 +197,18 @@
</span> </span>
</button> </button>
</div> </div>
<div x-show="open" x-transition:enter="transition ease-out duration-100" x-transition:enter-start="transform opacity-0 scale-95" x-transition:enter-end="transform opacity-100 scale-100" x-transition:leave="transition ease-in duration-75" x-transition:leave-start="transform opacity-100 scale-100" x-transition:leave-end="transform opacity-0 scale-95" class="z-10 mx-3 origin-top absolute right-0 left-0 mt-1 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-200 focus:outline-none" x-ref="menu-items" x-description="Dropdown menu, show/hide based on menu state." role="menu" aria-orientation="vertical" aria-labelledby="options-menu-button" tabindex="-1" @keydown.arrow-up.prevent="onArrowUp()" @keydown.arrow-down.prevent="onArrowDown()" @keydown.tab="open = false"> <div id="account-dropdown" phx-click-away={hide_dropdown("#account-dropdown")} class="hidden z-10 mx-3 origin-top absolute right-0 left-0 mt-1 rounded-md shadow-lg bg-white ring-1 ring-black ring-opacity-5 divide-y divide-gray-200" role="menu" aria-expanded="true">
<div class="py-1" role="none"> <div class="py-1" role="none">
<a href="#" class="block px-4 py-2 text-sm text-gray-700" x-state:on="Active" x-state:off="Not Active" :class="{ 'bg-gray-100 text-gray-900': activeIndex === 0, 'text-gray-700': !(activeIndex === 0) }" role="menuitem" tabindex="-1" id="options-menu-item-0" @mouseenter="activeIndex = 0" @mouseleave="activeIndex = -1" @click="open = false; focusButton()">View profile</a> <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-0">View profile</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700" :class="{ 'bg-gray-100 text-gray-900': activeIndex === 1, 'text-gray-700': !(activeIndex === 1) }" role="menuitem" tabindex="-1" id="options-menu-item-1" @mouseenter="activeIndex = 1" @mouseleave="activeIndex = -1" @click="open = false; focusButton()">Settings</a> <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-1">Settings</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700" :class="{ 'bg-gray-100 text-gray-900': activeIndex === 2, 'text-gray-700': !(activeIndex === 2) }" role="menuitem" tabindex="-1" id="options-menu-item-2" @mouseenter="activeIndex = 2" @mouseleave="activeIndex = -1" @click="open = false; focusButton()">Notifications</a> <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-2">Notifications</a>
</div> </div>
<div class="py-1" role="none"> <div class="py-1" role="none">
<a href="#" class="block px-4 py-2 text-sm text-gray-700" :class="{ 'bg-gray-100 text-gray-900': activeIndex === 3, 'text-gray-700': !(activeIndex === 3) }" role="menuitem" tabindex="-1" id="options-menu-item-3" @mouseenter="activeIndex = 3" @mouseleave="activeIndex = -1" @click="open = false; focusButton()">Get desktop app</a> <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-3">Get desktop app</a>
<a href="#" class="block px-4 py-2 text-sm text-gray-700" :class="{ 'bg-gray-100 text-gray-900': activeIndex === 4, 'text-gray-700': !(activeIndex === 4) }" role="menuitem" tabindex="-1" id="options-menu-item-4" @mouseenter="activeIndex = 4" @mouseleave="activeIndex = -1" @click="open = false; focusButton()">Support</a> <a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" role="menuitem" id="options-menu-item-4">Support</a>
</div> </div>
<div class="py-1" role="none"> <div class="py-1" role="none">
<a href="#" class="block px-4 py-2 text-sm text-gray-700" :class="{ 'bg-gray-100 text-gray-900': activeIndex === 5, 'text-gray-700': !(activeIndex === 5) }" role="menuitem" tabindex="-1" id="options-menu-item-5" @mouseenter="activeIndex = 5" @mouseleave="activeIndex = -1" @click="open = false; focusButton()">Logout</a> <%= link "Logout", to: Routes.o_auth_callback_path(@conn, :sign_out), method: :delete, role: "menuitem", id: "options-menu-item-5", class: "block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100" %>
</div> </div>
</div> </div>
</div> </div>
@ -220,9 +236,8 @@
<%= live_redirect to: Routes.home_path(@conn, :index), <%= live_redirect to: Routes.home_path(@conn, :index),
class: "bg-gray-200 text-gray-900 group flex items-center px-2 py-2 text-sm font-medium rounded-md" do %> class: "bg-gray-200 text-gray-900 group flex items-center px-2 py-2 text-sm font-medium rounded-md" do %>
<svg class="text-gray-500 mr-3 flex-shrink-0 h-6 w-6" x-state:on="Current" x-state:off="Default" <svg class="text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
x-state-description="Current: &quot;text-gray-500&quot;, Default: &quot;text-gray-400 group-hover:text-gray-500&quot;" xmlns="http://www.w3.org/2000/svg" fill="none"
x-description="Heroicon name: outline/home" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"> viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"> d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6">
@ -232,11 +247,9 @@
<% end %> <% end %>
<a href="#" <a href="#"
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" 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">
x-state-description="undefined: &quot;bg-gray-200 text-gray-900&quot;, undefined: &quot;text-gray-700 hover:text-gray-900 hover:bg-gray-50&quot;">
<svg class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6" <svg class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
x-state-description="undefined: &quot;text-gray-500&quot;, undefined: &quot;text-gray-400 group-hover:text-gray-500&quot;" xmlns="http://www.w3.org/2000/svg" fill="none"
x-description="Heroicon name: outline/view-list" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"> viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M4 6h16M4 10h16M4 14h16M4 18h16"></path> d="M4 6h16M4 10h16M4 14h16M4 18h16"></path>
@ -248,8 +261,7 @@
<%= live_redirect to: Routes.signin_path(@conn, :index), <%= live_redirect to: Routes.signin_path(@conn, :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" do %> 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" do %>
<svg class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6" <svg class="text-gray-400 group-hover:text-gray-500 mr-3 flex-shrink-0 h-6 w-6"
x-state-description="undefined: &quot;text-gray-500&quot;, undefined: &quot;text-gray-400 group-hover:text-gray-500&quot;" xmlns="http://www.w3.org/2000/svg" fill="none"
x-description="Heroicon name: outline/clock" xmlns="http://www.w3.org/2000/svg" fill="none"
viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true"> viewBox="0 0 24 24" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" <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> d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"></path>
@ -300,11 +312,11 @@
<div class="flex flex-col w-0 flex-1 overflow-hidden"> <div class="flex flex-col w-0 flex-1 overflow-hidden">
<!-- Search header --> <!-- Search header -->
<div class="relative z-10 flex-shrink-0 flex h-16 bg-white border-b border-gray-200 lg:hidden"> <div class="relative z-10 flex-shrink-0 flex h-16 bg-white border-b border-gray-200 lg:hidden">
<button type="button" x-description="Sidebar toggle, controls the 'sidebarOpen' sidebar state." <button type="button"
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" 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"
@click="open = true"> phx-click={show_mobile_sidebar()}>
<span class="sr-only">Open sidebar</span> <span class="sr-only">Open sidebar</span>
<svg class="h-6 w-6" x-description="Heroicon name: outline/menu-alt-1" xmlns="http://www.w3.org/2000/svg" <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"> 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> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h8m-8 6h16"></path>
</svg> </svg>
@ -315,7 +327,7 @@
<label for="search-field" class="sr-only">Search</label> <label for="search-field" class="sr-only">Search</label>
<div class="relative w-full text-gray-400 focus-within:text-gray-600"> <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"> <div class="absolute inset-y-0 left-0 flex items-center pointer-events-none">
<svg class="h-5 w-5" x-description="Heroicon name: solid/search" xmlns="http://www.w3.org/2000/svg" <svg class="h-5 w-5" xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path fill-rule="evenodd" <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" 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"
@ -329,16 +341,14 @@
</form> </form>
</div> </div>
<div class="flex items-center"> <div class="flex items-center">
<!-- Profile dropdown --> <!-- Profile dropdown TODO -->
<div x-data="{open: false}" <div @keydown.escape.stop="open = false" @click.away="open = false" class="ml-3 relative">
@keydown.escape.stop="open = false" @click.away="open = false" class="ml-3 relative">
<div> <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" 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" x-ref="button" @click="open = true" @keyup.space.prevent="onButtonEnter()" id="user-menu-button" @click="open = true"
@keydown.enter.prevent="onButtonEnter()" aria-expanded="false" aria-haspopup="true" aria-expanded="false" aria-haspopup="true"
x-bind:aria-expanded="open.toString()" @keydown.arrow-up.prevent="onArrowUp()" >
@keydown.arrow-down.prevent="onArrowDown()">
<span class="sr-only">Open user menu</span> <span class="sr-only">Open user menu</span>
<img class="h-8 w-8 rounded-full" <img class="h-8 w-8 rounded-full"
src="https://images.unsplash.com/photo-1502685104226-ee32379fefbe?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80" src="https://images.unsplash.com/photo-1502685104226-ee32379fefbe?ixlib=rb-1.2.1&amp;ixid=eyJhcHBfaWQiOjEyMDd9&amp;auto=format&amp;fit=facearea&amp;facepad=2&amp;w=256&amp;h=256&amp;q=80"
@ -352,27 +362,7 @@
<main class="flex-1 relative z-0 overflow-y-auto focus:outline-none"> <main class="flex-1 relative z-0 overflow-y-auto focus:outline-none">
<%= live_render(@conn, LiveBeatsWeb.PlayerLive, session: %{}) %> <%= live_render(@conn, LiveBeatsWeb.PlayerLive, session: %{}) %>
<!-- 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">
<div class="flex-1 min-w-0">
<h1 class="text-lg font-medium leading-6 text-gray-900 sm:truncate">
LiveBeats Chill
</h1>
</div>
<div class="mt-4 flex sm:mt-0 sm:ml-4">
<button type="button"
class="order-1 ml-3 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">
Share
</button>
<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">
Create
</button>
</div>
</div>
<%= @inner_content %> <%= @inner_content %>
</main> </main>
</div> </div>
</div> </div>

View file

@ -1,6 +1,8 @@
defmodule LiveBeatsWeb.LayoutView do defmodule LiveBeatsWeb.LayoutView do
use LiveBeatsWeb, :view use LiveBeatsWeb, :view
alias Phoenix.LiveView.JS
# Phoenix LiveDashboard is available only in development by default, # Phoenix LiveDashboard is available only in development by default,
# so we instruct Elixir to not warn if the dashboard route is missing. # so we instruct Elixir to not warn if the dashboard route is missing.
@compile {:no_warn_undefined, {Routes, :live_dashboard_path, 2}} @compile {:no_warn_undefined, {Routes, :live_dashboard_path, 2}}

View file

@ -33,13 +33,13 @@ defmodule LiveBeats.MixProject do
# Type `mix help deps` for examples and options. # Type `mix help deps` for examples and options.
defp deps do defp deps do
[ [
{:phoenix, "~> 1.6.0-rc.0", override: true}, {:phoenix, "~> 1.6.0"},
{:phoenix_ecto, "~> 4.4"}, {:phoenix_ecto, "~> 4.4"},
{:ecto_sql, "~> 3.6"}, {:ecto_sql, "~> 3.6"},
{:postgrex, ">= 0.0.0"}, {:postgrex, ">= 0.0.0"},
{:phoenix_html, "~> 3.0"}, {:phoenix_html, "~> 3.0"},
{:phoenix_live_reload, "~> 1.2", only: :dev}, {:phoenix_live_reload, "~> 1.2", only: :dev},
{:phoenix_live_view, "~> 0.16.0"}, {:phoenix_live_view, path: "~/oss/phoenix_live_view", override: true},
{:floki, ">= 0.30.0", only: :test}, {:floki, ">= 0.30.0", only: :test},
{:phoenix_live_dashboard, "~> 0.5"}, {:phoenix_live_dashboard, "~> 0.5"},
{:esbuild, "~> 0.2", runtime: Mix.env() == :dev}, {:esbuild, "~> 0.2", runtime: Mix.env() == :dev},

View file

@ -14,18 +14,18 @@
"gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"}, "gettext": {:hex, :gettext, "0.18.2", "7df3ea191bb56c0309c00a783334b288d08a879f53a7014341284635850a6e55", [:mix], [], "hexpm", "f9f537b13d4fdd30f3039d33cb80144c3aa1f8d9698e47d7bcbcc8df93b1f5c5"},
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
"jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"}, "jason": {:hex, :jason, "1.2.2", "ba43e3f2709fd1aa1dce90aaabfd039d000469c05c56f0b8e31978e03fa39052", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "18a228f5f0058ee183f29f9eae0805c6e59d61c3b006760668d8d18ff0d12179"},
"mime": {:hex, :mime, "2.0.1", "0de4c81303fe07806ebc2494d5321ce8fb4df106e34dd5f9d787b637ebadc256", [:mix], [], "hexpm", "7a86b920d2aedce5fb6280ac8261ac1a739ae6c1a1ad38f5eadf910063008942"}, "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"}, "mint": {:hex, :mint, "1.3.0", "396b3301102f7b775e103da5a20494b25753aed818d6d6f0ad222a3a018c3600", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "a9aac960562e43ca69a77e5176576abfa78b8398cec5543dd4fb4ab0131d5c1e"},
"phoenix": {:hex, :phoenix, "1.6.0-rc.0", "87dc1bb400588019a878ecf32c2d229c7d7f31a520c574860a059934663ffa70", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "2a0d344d2a2f654a9300b2b09dbf9c3821762e1364e26fce12d76fcd498b92c0"}, "phoenix": {:hex, :phoenix, "1.6.2", "6cbd5c8ed7a797f25a919a37fafbc2fb1634c9cdb12a4448d7a5d0b26926f005", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7bbee475acae0c3abc229b7f189e210ea788e63bd168e585f60c299a4b2f9133"},
"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_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.0.2", "0d71bd7dfa5fad2103142206e25e16accd64f41bcbd0002af3f0da17e530968d", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "d6c6e85d9bef8d52a5a66fcccd15529651f379eaccbf10500343a17f6f814f82"}, "phoenix_html": {:hex, :phoenix_html, "3.1.0", "0b499df05aad27160d697a9362f0e89fa0e24d3c7a9065c2bd9d38b4d1416c09", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0c0a98a2cefa63433657983a2a594c7dee5927e4391e0f1bfd3a151d1def33fc"},
"phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.5.0", "3282d8646e1bfc1ef1218f508d9fcefd48cf47f9081b7667bd9b281b688a49cf", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.6", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.16.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "609740be43de94ae0abd2c4300ff0356a6e8a9487bf340e69967643a59fa7ec8"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.5.0", "3282d8646e1bfc1ef1218f508d9fcefd48cf47f9081b7667bd9b281b688a49cf", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.6", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:phoenix_live_view, "~> 0.16.0", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "609740be43de94ae0abd2c4300ff0356a6e8a9487bf340e69967643a59fa7ec8"},
"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_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": {:hex, :phoenix_live_view, "0.16.1", "a17652e936718b6b6b52ef64d4b9860bc30c41b9a491e25f2b49a70604efa436", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.9 or ~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "94bbc572471ad151b756b38dd10acbf91e0bcc132ad8b78240baa0dcf77cea74"}, "phoenix_live_view": {:hex, :phoenix_live_view, "0.16.4", "5692edd0bac247a9a816eee7394e32e7a764959c7d0cf9190662fc8b0cd24c97", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.5.9 or ~> 1.6.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "754ba49aa2e8601afd4f151492c93eb72df69b0b9856bab17711b8397e43bba0"},
"phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.0.0", "a1ae76717bb168cdeb10ec9d92d1480fec99e3080f011402c0a2d68d47395ffb", [:mix], [], "hexpm", "c52d948c4f261577b9c6fa804be91884b381a7f8f18450c5045975435350f771"},
"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"}, "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.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [: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", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"}, "plug": {:hex, :plug, "1.12.1", "645678c800601d8d9f27ad1aebba1fdb9ce5b2623ddb961a074da0b96c35187d", [: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", "d57e799a777bc20494b784966dc5fbda91eb4a09f571f76545b72a634ce0d30b"},
"plug_cowboy": {:hex, :plug_cowboy, "2.5.1", "7cc96ff645158a94cf3ec9744464414f02287f832d6847079adfe0b58761cbd0", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "107d0a5865fa92bcb48e631cc0729ae9ccfa0a9f9a1bd8f01acb513abf1c2d64"}, "plug_cowboy": {:hex, :plug_cowboy, "2.5.2", "62894ccd601cf9597e2c23911ff12798a8a18d237e9739f58a6b04e4988899fe", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "ea6e87f774c8608d60c8d34022a7d073bd7680a0a013f049fc62bf35efea1044"},
"plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"}, "plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
"postgrex": {:hex, :postgrex, "0.15.10", "2809dee1b1d76f7cbabe570b2a9285c2e7b41be60cf792f5f2804a54b838a067", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "1560ca427542f6b213f8e281633ae1a3b31cdbcd84ebd7f50628765b8f6132be"}, "postgrex": {:hex, :postgrex, "0.15.10", "2809dee1b1d76f7cbabe570b2a9285c2e7b41be60cf792f5f2804a54b838a067", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "1560ca427542f6b213f8e281633ae1a3b31cdbcd84ebd7f50628765b8f6132be"},
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},

View file

@ -0,0 +1,9 @@
defmodule LiveBeats.Repo.Migrations.CreateGenres do
use Ecto.Migration
def change do
create table(:genres) do
add :title, :text, null: false
end
end
end