Compare commits

..

19 Commits

Author SHA1 Message Date
KKlochko 669a288ef1 Update the version.
continuous-integration/drone/push Build is passing Details
continuous-integration/drone/tag Build is passing Details
10 months ago
KKlochko b47d994989 Add the JSON API route to search by BookID.
10 months ago
KKlochko dd06829f31 Update the generator for BookIds.
continuous-integration/drone/push Build is passing Details
10 months ago
KKlochko 1842ebc32c Update the tests for Metadata.get_author_book action.
10 months ago
KKlochko 02a34694e4 Update the Author Show LiveView to show their books.
10 months ago
KKlochko e4f17524ea Update the card components to check a dbi_server existence by the id.
10 months ago
KKlochko 03b889b094 Fix to hide the label if no identifiers for Book Show LiveView.
10 months ago
KKlochko 30df53702b Update to allow a Book to have no Publisher.
10 months ago
KKlochko cedc4d78e8 Update DBIServer Show LiveView to show the edit button for admins.
10 months ago
KKlochko 11a7962167 Update the code format for components and LiveViews.
continuous-integration/drone/push Build is failing Details
10 months ago
KKlochko 589f13d90e Update the Book's nested form's layout if there're an error.
10 months ago
KKlochko e754ef0d8f Update the DarkModeSwitcher component to change the icon order.
10 months ago
KKlochko aded500cab Add the DarkModeSwitcher component.
10 months ago
KKlochko 3bc4616c5d Update to allow search book by an empty bid.
continuous-integration/drone/push Build is passing Details
10 months ago
KKlochko 5c31715211 Update the background colors for the light theme.
continuous-integration/drone/push Build is passing Details
10 months ago
KKlochko 2e023fa2f7 Fix the light theme for Author Show LiveView.
10 months ago
KKlochko e9e49572d1 Add the content's horizontal padding for card components.
continuous-integration/drone/push Build is passing Details
10 months ago
KKlochko bf333aad07 Update the select components to show the message about no records.
10 months ago
KKlochko e226e696da Update the select components to add hover style for records.
10 months ago

@ -26,11 +26,14 @@ import "flowbite/dist/flowbite.phoenix.js";
import bidSort from "./bidSort"; import bidSort from "./bidSort";
import authorRoleSort from "./authorRoleSort"; import authorRoleSort from "./authorRoleSort";
import darkModeHook from "../vendor/dark_mode"
let DarkThemeToggle = darkModeHook;
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, {
longPollFallbackMs: 2500, longPollFallbackMs: 2500,
params: {_csrf_token: csrfToken}, params: {_csrf_token: csrfToken},
hooks: { bidSort, authorRoleSort } hooks: { bidSort, authorRoleSort, DarkThemeToggle }
}) })
// Show progress bar on live navigation and form submits // Show progress bar on live navigation and form submits

@ -6,6 +6,7 @@ const fs = require("fs")
const path = require("path") const path = require("path")
module.exports = { module.exports = {
darkMode: 'class',
content: [ content: [
"../deps/ash_authentication_phoenix/**/*.*ex", "../deps/ash_authentication_phoenix/**/*.*ex",
"./js/**/*.js", "./js/**/*.js",

@ -0,0 +1,39 @@
const localStorageKey = 'theme';
const isDark = () => {
if (localStorage.getItem(localStorageKey) === 'dark') return true;
if (localStorage.getItem(localStorageKey) === 'light') return false;
return window.matchMedia('(prefers-color-scheme: dark)').matches;
}
const setupThemeToggle = () => {
toggleVisibility = (dark) => {
const themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
const themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
if (themeToggleDarkIcon == null || themeToggleLightIcon == null) return;
const show = dark ? themeToggleLightIcon : themeToggleDarkIcon;
const hide = dark ? themeToggleDarkIcon : themeToggleLightIcon;
show.classList.remove('hidden', 'text-transparent');
hide.classList.add('hidden', 'text-transparent');
if (dark) {
document.documentElement.classList.add('dark')
} else {
document.documentElement.classList.remove('dark')
}
try { localStorage.setItem(localStorageKey, dark ? 'dark' : 'light') } catch (_err) { }
}
toggleVisibility(isDark())
document.getElementById('theme-toggle').addEventListener('click', function () {
toggleVisibility(!isDark())
});
}
const darkModeHook = {
mounted() {
setupThemeToggle();
},
updated() {
},
}
export default darkModeHook;

@ -8,6 +8,7 @@ defmodule DecentralisedBookIndex.Metadata do
base_route "/books", Metadata.Book do base_route "/books", Metadata.Book do
get :by_id get :by_id
index :search index :search
index :search_by_bid, route: "/search-by-bid"
related :bids, :read, primary?: true related :bids, :read, primary?: true
related :author_roles, :read, primary?: true related :author_roles, :read, primary?: true

@ -179,11 +179,17 @@ defmodule DecentralisedBookIndex.Metadata.Book do
prepare fn query, context -> prepare fn query, context ->
author = query.arguments.author author = query.arguments.author
{:ok, author_ids} = Metadata.get_author_ids(author) author_ids =
case Metadata.get_author_ids(author) do
{:ok, ids} -> ids
{:error, _error} -> []
end
Metadata.Book query
|> Ash.Query.filter(expr(exists(author_roles, author_id in ^author_ids))) |> Ash.Query.filter(expr(exists(author_roles, author_id in ^author_ids)))
end end
pagination offset?: true, default_limit: 10
end end
read :search do read :search do
@ -204,7 +210,7 @@ defmodule DecentralisedBookIndex.Metadata.Book do
description "Return a list of Books, optionally filtering by BookId." description "Return a list of Books, optionally filtering by BookId."
argument :type, :string, allow_nil?: false argument :type, :string, allow_nil?: false
argument :bid, :string, allow_nil?: false argument :bid, :string, allow_nil?: true
filter expr(exists(bids, type == ^arg(:type) and contains(bid, ^arg(:bid)))) filter expr(exists(bids, type == ^arg(:type) and contains(bid, ^arg(:bid))))
@ -373,7 +379,6 @@ defmodule DecentralisedBookIndex.Metadata.Book do
end end
belongs_to :publisher, Metadata.Publisher do belongs_to :publisher, Metadata.Publisher do
allow_nil? false
public? true public? true
end end

@ -50,7 +50,11 @@ defmodule DecentralisedBookIndexWeb.CoreComponents do
data-cancel={JS.exec(@on_cancel, "phx-remove")} data-cancel={JS.exec(@on_cancel, "phx-remove")}
class="relative z-50 hidden" class="relative z-50 hidden"
> >
<div id={"#{@id}-bg"} class="bg-zinc-50/90 dark:bg-gray-900/90 fixed inset-0 transition-opacity" aria-hidden="true" /> <div
id={"#{@id}-bg"}
class="bg-zinc-50/90 dark:bg-gray-900/90 fixed inset-0 transition-opacity"
aria-hidden="true"
/>
<div <div
class="fixed inset-0 overflow-y-auto" class="fixed inset-0 overflow-y-auto"
aria-labelledby={"#{@id}-title"} aria-labelledby={"#{@id}-title"}
@ -425,7 +429,8 @@ defmodule DecentralisedBookIndexWeb.CoreComponents do
class={[ class={[
"mt-2 block w-full rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6 min-h-[6rem] dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500", "mt-2 block w-full rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6 min-h-[6rem] dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500",
@errors == [] && "border-zinc-300 focus:border-zinc-400", @errors == [] && "border-zinc-300 focus:border-zinc-400",
@errors != [] && "border-rose-400 focus:border-rose-400 dark:border-rose-400 dark:focus:border-rose-400" @errors != [] &&
"border-rose-400 focus:border-rose-400 dark:border-rose-400 dark:focus:border-rose-400"
]} ]}
{@rest} {@rest}
>{Phoenix.HTML.Form.normalize_value("textarea", @value)}</textarea> >{Phoenix.HTML.Form.normalize_value("textarea", @value)}</textarea>
@ -449,7 +454,8 @@ defmodule DecentralisedBookIndexWeb.CoreComponents do
class={[ class={[
"mt-2 block w-full rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500", "mt-2 block w-full rounded-lg text-zinc-900 focus:ring-0 sm:text-sm sm:leading-6 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500",
@errors == [] && "border-zinc-300 focus:border-zinc-400", @errors == [] && "border-zinc-300 focus:border-zinc-400",
@errors != [] && "border-rose-400 focus:border-rose-400 dark:border-rose-400 dark:focus:border-rose-400" @errors != [] &&
"border-rose-400 focus:border-rose-400 dark:border-rose-400 dark:focus:border-rose-400"
]} ]}
{@rest} {@rest}
/> />
@ -546,8 +552,8 @@ defmodule DecentralisedBookIndexWeb.CoreComponents do
~H""" ~H"""
<div class="overflow-y-auto px-4 sm:overflow-visible sm:px-0 "> <div class="overflow-y-auto px-4 sm:overflow-visible sm:px-0 ">
<table class="w-full text-sm text-left text-gray-500 dark:text-gray-400 bg-white dark:bg-gray-800 relative shadow-md sm:rounded-lg overflow-hidden"> <table class="w-full text-sm text-left text-gray-500 dark:text-gray-400 bg-slate-100 dark:bg-gray-800 relative shadow-md sm:rounded-lg overflow-hidden">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400"> <thead class="text-xs text-gray-700 uppercase bg-slate-200 dark:bg-gray-700 dark:text-gray-400">
<tr> <tr>
<th :for={col <- @col} class="p-4 font-normal">{col[:label]}</th> <th :for={col <- @col} class="p-4 font-normal">{col[:label]}</th>
<th :if={@action != []} class="relative p-0 pb-4"> <th :if={@action != []} class="relative p-0 pb-4">
@ -560,7 +566,11 @@ defmodule DecentralisedBookIndexWeb.CoreComponents do
phx-update={match?(%Phoenix.LiveView.LiveStream{}, @rows) && "stream"} phx-update={match?(%Phoenix.LiveView.LiveStream{}, @rows) && "stream"}
class="relative divide-zinc-100 text-sm leading-6 text-zinc-700" class="relative divide-zinc-100 text-sm leading-6 text-zinc-700"
> >
<tr :for={row <- @rows} id={@row_id && @row_id.(row)} class=" bg-white shadow-md dark:bg-gray-800 hover:bg-gray-100 dark:bg-gray-850 dark:hover:bg-gray-700"> <tr
:for={row <- @rows}
id={@row_id && @row_id.(row)}
class="bg-slate-100 shadow-md dark:bg-gray-800 hover:bg-slate-300 dark:bg-gray-850 dark:hover:bg-gray-700"
>
<td <td
:for={{col, i} <- Enum.with_index(@col)} :for={{col, i} <- Enum.with_index(@col)}
phx-click={@row_click && @row_click.(row)} phx-click={@row_click && @row_click.(row)}
@ -568,7 +578,10 @@ defmodule DecentralisedBookIndexWeb.CoreComponents do
> >
<div class="block py-4 pr-6"> <div class="block py-4 pr-6">
<span class="absolute -inset-y-px right-0 -left-4 group-hover:bg-zinc-50 sm:rounded-l-xl" /> <span class="absolute -inset-y-px right-0 -left-4 group-hover:bg-zinc-50 sm:rounded-l-xl" />
<span class={["p-4 relative font-sm text-gray-500 dark:text-gray-400", i == 0 && "p-4 medium text-gray-900 whitespace-nowrap dark:text-white"]}> <span class={[
"p-4 relative font-sm text-gray-500 dark:text-gray-400",
i == 0 && "p-4 medium text-gray-900 whitespace-nowrap dark:text-white"
]}>
{render_slot(col, @row_item.(row))} {render_slot(col, @row_item.(row))}
</span> </span>
</div> </div>

@ -10,6 +10,14 @@
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} /> <link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}> <script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
</script> </script>
<script>
// On page load or when changing themes, best to add inline in `head` to avoid FOUC
if (localStorage.getItem('color-theme') === 'dark' || (!('color-theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark')
}
</script>
</head> </head>
<body class="bg-white dark:bg-gray-900"> <body class="bg-white dark:bg-gray-900">
{@inner_content} {@inner_content}

@ -34,6 +34,8 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents do
alias MyComponents.SelectAuthorAlias alias MyComponents.SelectAuthorAlias
import MyComponents.SelectedAuthorAlias, only: [selected_author_alias: 1] import MyComponents.SelectedAuthorAlias, only: [selected_author_alias: 1]
import MyComponents.DarkModeSwitcher, only: [dark_mode_switcher: 1]
end end
end end
end end

@ -9,8 +9,8 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.AuthorCard do
def author_card(assigns) do def author_card(assigns) do
~H""" ~H"""
<div class="w-full max-w-sm bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700 mx-auto my-3"> <div class="w-full max-w-sm bg-slate-100 border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700 mx-auto my-3">
<%= if @current_user != nil and Role.can_moderate?(@current_user.role) and is_nil(@author.dbi_server) do %> <%= if @current_user != nil and Role.can_moderate?(@current_user.role) and is_nil(@author.dbi_server_id) do %>
<div class="flex justify-end px-4 pt-4"> <div class="flex justify-end px-4 pt-4">
<button <button
id={"dropdownButton#{@author.id}"} id={"dropdownButton#{@author.id}"}
@ -32,7 +32,7 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.AuthorCard do
<!-- Dropdown menu --> <!-- Dropdown menu -->
<div <div
id={"dropdown#{@author.id}"} id={"dropdown#{@author.id}"}
class="z-10 hidden text-base list-none bg-white divide-y divide-gray-100 rounded-lg shadow-sm w-44 dark:bg-gray-700" class="z-10 hidden text-base list-none bg-slate-200 divide-y divide-gray-100 rounded-lg shadow-sm w-44 dark:bg-gray-700"
> >
<ul class="py-2" aria-labelledby="dropdownButton"> <ul class="py-2" aria-labelledby="dropdownButton">
<li> <li>
@ -63,7 +63,7 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.AuthorCard do
</div> </div>
</div> </div>
<% end %> <% end %>
<div class="flex flex-col items-center py-5 text-center"> <div class="flex flex-col items-center py-5 text-center px-2">
<.link navigate={~p"/authors/#{@author.id}"}> <.link navigate={~p"/authors/#{@author.id}"}>
<%= if @author.avatar_url != nil do %> <%= if @author.avatar_url != nil do %>
<img <img

@ -9,8 +9,8 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.BookCard do
def book_card(assigns) do def book_card(assigns) do
~H""" ~H"""
<div class="w-full max-w-sm bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700 mx-auto my-3"> <div class="w-full max-w-sm bg-slate-100 border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700 mx-auto my-3">
<%= if @current_user != nil and Role.can_moderate?(@current_user.role) and is_nil(@book.dbi_server) do %> <%= if @current_user != nil and Role.can_moderate?(@current_user.role) and is_nil(@book.dbi_server_id) do %>
<div class="flex justify-end px-4 pt-4"> <div class="flex justify-end px-4 pt-4">
<button <button
id={"dropdownButton#{@book.id}"} id={"dropdownButton#{@book.id}"}
@ -32,7 +32,7 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.BookCard do
<!-- Dropdown menu --> <!-- Dropdown menu -->
<div <div
id={"dropdown#{@book.id}"} id={"dropdown#{@book.id}"}
class="z-10 hidden text-base list-none bg-white divide-y divide-gray-100 rounded-lg shadow-sm w-44 dark:bg-gray-700" class="z-10 hidden text-base list-none bg-slate-200 divide-y divide-gray-100 rounded-lg shadow-sm w-44 dark:bg-gray-700"
> >
<ul class="py-2" aria-labelledby="dropdownButton"> <ul class="py-2" aria-labelledby="dropdownButton">
<li> <li>
@ -63,7 +63,7 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.BookCard do
</div> </div>
</div> </div>
<% end %> <% end %>
<div class="flex flex-col items-center py-5 text-center"> <div class="flex flex-col items-center py-5 text-center px-2">
<.link navigate={~p"/books/#{@book.id}"}> <.link navigate={~p"/books/#{@book.id}"}>
<%= if @book.cover_image_url != nil do %> <%= if @book.cover_image_url != nil do %>
<img class="w-36 mb-3 shadow-lg" src={@book.cover_image_url} alt={"#{@book.title} image"} /> <img class="w-36 mb-3 shadow-lg" src={@book.cover_image_url} alt={"#{@book.title} image"} />

@ -14,26 +14,58 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.BookSearch do
def book_search(assigns) do def book_search(assigns) do
~H""" ~H"""
<div class="bg-white shadow-md dark:bg-gray-800 sm:rounded-lg"> <div class="bg-slate-100 shadow-md dark:bg-gray-800 sm:rounded-lg">
<div class="flex items-center justify-center p-4 flex-row space-y-0 space-x-4"> <div class="flex items-center justify-center p-4 flex-row space-y-0 space-x-4">
<div class="w-full grow"> <div class="w-full grow">
<form class="flex items-center" phx-change="search" phx-submit="search"> <form class="flex items-center" phx-change="search" phx-submit="search">
<label for="simple-search" class="sr-only">Search</label> <label for="simple-search" class="sr-only">Search</label>
<div class="relative w-full"> <div class="relative w-full">
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none"> <div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <svg
<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" /> aria-hidden="true"
class="w-5 h-5 text-gray-500 dark:text-gray-400"
fill="currentColor"
viewbox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<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"
/>
</svg> </svg>
</div> </div>
<input name="query" type="text" phx-debounce="300" autocomplete="off" id="simple-search" class="block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" placeholder="Search" value={@search_query}> <input
name="query"
type="text"
phx-debounce="300"
autocomplete="off"
id="simple-search"
class="block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
placeholder="Search"
value={@search_query}
/>
</div> </div>
</form> </form>
</div> </div>
<%= if @current_user != nil and Role.can_moderate?(@current_user.role) do %> <%= if @current_user != nil and Role.can_moderate?(@current_user.role) do %>
<.link patch={~p"/books/new"} class="flex-shrink-0 my-auto"> <.link patch={~p"/books/new"} class="flex-shrink-0 my-auto">
<button type="button" class="flex items-center justify-between px-4 py-2 text-sm font-medium text-black dark:text-white rounded-lg bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 dark:bg-primary-600 dark:hover:bg-primary-700 focus:outline-none dark:focus:ring-primary-800"> <button
<svg class="h-3.5 w-3.5 mr-2" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> type="button"
<path clip-rule="evenodd" fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" /> class="flex items-center justify-between px-4 py-2 text-sm font-medium text-black dark:text-white rounded-lg bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 dark:bg-primary-600 dark:hover:bg-primary-700 focus:outline-none dark:focus:ring-primary-800"
>
<svg
class="h-3.5 w-3.5 mr-2"
fill="currentColor"
viewbox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
clip-rule="evenodd"
fill-rule="evenodd"
d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z"
/>
</svg> </svg>
Add book Add book
</button> </button>
@ -58,17 +90,42 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.BookSearch do
def dropdown_settings(assigns) do def dropdown_settings(assigns) do
~H""" ~H"""
<div class="flex justify-end px-4"> <div class="flex justify-end px-4">
<button id="dropdownButtonSearchBook" data-dropdown-toggle="dropdownSearchBook" class="inline-block text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:ring-4 focus:outline-none focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-1.5" type="button"> <button
id="dropdownButtonSearchBook"
data-dropdown-toggle="dropdownSearchBook"
class="inline-block text-gray-500 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:ring-4 focus:outline-none focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-1.5"
type="button"
>
<span class="sr-only">Open dropdown</span> <span class="sr-only">Open dropdown</span>
<svg class="w-5 h-5 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"> <svg
<path stroke="currentColor" stroke-linecap="round" stroke-width="2" d="M20 6H10m0 0a2 2 0 1 0-4 0m4 0a2 2 0 1 1-4 0m0 0H4m16 6h-2m0 0a2 2 0 1 0-4 0m4 0a2 2 0 1 1-4 0m0 0H4m16 6H10m0 0a2 2 0 1 0-4 0m4 0a2 2 0 1 1-4 0m0 0H4"/> class="w-5 h-5 text-gray-800 dark:text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-width="2"
d="M20 6H10m0 0a2 2 0 1 0-4 0m4 0a2 2 0 1 1-4 0m0 0H4m16 6h-2m0 0a2 2 0 1 0-4 0m4 0a2 2 0 1 1-4 0m0 0H4m16 6H10m0 0a2 2 0 1 0-4 0m4 0a2 2 0 1 1-4 0m0 0H4"
/>
</svg> </svg>
</button> </button>
<!-- Dropdown menu --> <!-- Dropdown menu -->
<div id="dropdownSearchBook" class="z-10 hidden text-base list-none bg-white rounded-lg shadow-sm w-64 dark:bg-gray-700 p-2 flex flex-col gap-2"> <div
id="dropdownSearchBook"
class="z-10 hidden text-base list-none bg-slate-200 rounded-lg shadow-sm w-64 dark:bg-gray-700 p-2 flex flex-col gap-2"
>
<div class="flex items-center space-x-3 md:w-auto"> <div class="flex items-center space-x-3 md:w-auto">
<form phx-change="change-search-mode" class="w-full"> <form phx-change="change-search-mode" class="w-full">
<select name="search_mode" id="search_mode" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"> <select
name="search_mode"
id="search_mode"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
>
<%= for {name, value} <- @search_mode_options do %> <%= for {name, value} <- @search_mode_options do %>
<%= if value == @search_mode do %> <%= if value == @search_mode do %>
<option value={value} selected>{name}</option> <option value={value} selected>{name}</option>
@ -81,7 +138,11 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.BookSearch do
</div> </div>
<div class="flex items-center space-x-3 md:w-auto"> <div class="flex items-center space-x-3 md:w-auto">
<form phx-change="change-sort" class="w-full"> <form phx-change="change-sort" class="w-full">
<select name="sort_by" id="sort_by" class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"> <select
name="sort_by"
id="sort_by"
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
>
<%= for {name, value} <- @select_options do %> <%= for {name, value} <- @select_options do %>
<%= if value == @selected_option do %> <%= if value == @selected_option do %>
<option value={value} selected>{name}</option> <option value={value} selected>{name}</option>

@ -120,9 +120,21 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.Buttons do
]} ]}
{@rest} {@rest}
> >
<svg class="w-6 h-6 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24"> <svg
<path fill-rule="evenodd" d="M5 3a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V7.414A2 2 0 0 0 20.414 6L18 3.586A2 2 0 0 0 16.586 3H5Zm3 11a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6H8v-6Zm1-7V5h6v2a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1Z" clip-rule="evenodd"/> class="w-6 h-6 dark:text-white"
<path fill-rule="evenodd" d="M14 17h-4v-2h4v2Z" clip-rule="evenodd"/> aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
fill-rule="evenodd"
d="M5 3a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2V7.414A2 2 0 0 0 20.414 6L18 3.586A2 2 0 0 0 16.586 3H5Zm3 11a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6H8v-6Zm1-7V5h6v2a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1Z"
clip-rule="evenodd"
/>
<path fill-rule="evenodd" d="M14 17h-4v-2h4v2Z" clip-rule="evenodd" />
</svg> </svg>
{render_slot(@inner_block)} {render_slot(@inner_block)}
</.success_button> </.success_button>
@ -147,9 +159,21 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.Buttons do
]} ]}
{@rest} {@rest}
> >
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 mr-2 -ml-0.5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"> <svg
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"></path> xmlns="http://www.w3.org/2000/svg"
<path fill-rule="evenodd" d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z" clip-rule="evenodd"></path> class="h-4 w-4 mr-2 -ml-0.5"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<path d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z">
</path>
<path
fill-rule="evenodd"
d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"
clip-rule="evenodd"
>
</path>
</svg> </svg>
{render_slot(@inner_block)} {render_slot(@inner_block)}
</.primary_button> </.primary_button>
@ -174,8 +198,22 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.Buttons do
]} ]}
{@rest} {@rest}
> >
<svg class="w-6 h-6 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"> <svg
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18 17.94 6M18 18 6.06 6"/> class="w-6 h-6 dark:text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M6 18 17.94 6M18 18 6.06 6"
/>
</svg> </svg>
{render_slot(@inner_block)} {render_slot(@inner_block)}
</.primary_button> </.primary_button>

@ -0,0 +1,56 @@
defmodule DecentralisedBookIndexWeb.Components.MyComponents.DarkModeSwitcher do
use Phoenix.Component
@doc"""
This component is based on https://github.com/aiwaiwa/phoenix_dark_mode/blob/main/dark_mode.ex.
"""
def dark_mode_switcher(assigns) do
~H"""
<button
id="theme-toggle"
type="button"
phx-update="ignore"
phx-hook="DarkThemeToggle"
class="text-zinc-500 dark:text-zinc-400 hover:bg-slate-200 dark:hover:bg-gray-700 rounded-lg text-sm p-2"
>
<svg
id="theme-toggle-dark-icon"
class="w-5 h-5 text-transparent hidden"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path>
</svg>
<svg
id="theme-toggle-light-icon"
class="w-5 h-5 text-transparent"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10 2a1 1 0 011 1v1a1 1 0 11-2 0V3a1 1 0 011-1zm4 8a4 4 0 11-8 0 4 4 0 018 0zm-.464 4.95l.707.707a1 1 0 001.414-1.414l-.707-.707a1 1 0 00-1.414 1.414zm2.12-10.607a1 1 0 010 1.414l-.706.707a1 1 0 11-1.414-1.414l.707-.707a1 1 0 011.414 0zM17 11a1 1 0 100-2h-1a1 1 0 100 2h1zm-7 4a1 1 0 011 1v1a1 1 0 11-2 0v-1a1 1 0 011-1zM5.05 6.464A1 1 0 106.465 5.05l-.708-.707a1 1 0 00-1.414 1.414l.707.707zm1.414 8.486l-.707.707a1 1 0 01-1.414-1.414l.707-.707a1 1 0 011.414 1.414zM4 11a1 1 0 100-2H3a1 1 0 000 2h1z"
fill-rule="evenodd"
clip-rule="evenodd"
>
</path>
</svg>
</button>
<script>
// Toggle early based on <html class="dark">
const themeToggleDarkIcon = document.getElementById('theme-toggle-dark-icon');
const themeToggleLightIcon = document.getElementById('theme-toggle-light-icon');
if (themeToggleDarkIcon != null && themeToggleLightIcon != null) {
let dark = document.documentElement.classList.contains('dark');
const show = dark ? themeToggleLightIcon : themeToggleDarkIcon;
const hide = dark ? themeToggleDarkIcon : themeToggleLightIcon;
show.classList.remove('hidden', 'text-transparent');
hide.classList.add('hidden', 'text-transparent');
}
</script>
"""
end
end

@ -11,25 +11,48 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.JustSearchResources
def just_search_resources(assigns) do def just_search_resources(assigns) do
~H""" ~H"""
<div class="relative bg-white shadow-md dark:bg-gray-800 sm:rounded-lg"> <div class="relative bg-slate-100 shadow-md dark:bg-gray-800 sm:rounded-lg">
<div class="flex flex-col items-center justify-between p-4 space-y-3 md:flex-row md:space-y-0 md:space-x-4"> <div class="flex flex-col items-center justify-between p-4 space-y-3 md:flex-row md:space-y-0 md:space-x-4">
<div class="w-full grow"> <div class="w-full grow">
<form class="flex items-center" phx-change="search" phx-submit="search"> <form class="flex items-center" phx-change="search" phx-submit="search">
<label for="simple-search" class="sr-only">Search</label> <label for="simple-search" class="sr-only">Search</label>
<div class="relative w-full"> <div class="relative w-full">
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none"> <div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <svg
<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" /> aria-hidden="true"
class="w-5 h-5 text-gray-500 dark:text-gray-400"
fill="currentColor"
viewbox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<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"
/>
</svg> </svg>
</div> </div>
<input name="query" type="text" phx-debounce="300" autocomplete="off" id="simple-search" class="block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" placeholder={@search_placeholder} value={@search_query}> <input
name="query"
type="text"
phx-debounce="300"
autocomplete="off"
id="simple-search"
class="block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
placeholder={@search_placeholder}
value={@search_query}
/>
</div> </div>
</form> </form>
</div> </div>
<div class="flex flex-col items-stretch justify-end flex-shrink-0 w-full space-y-2 md:w-auto md:flex-row md:space-y-0 md:items-center md:space-x-3"> <div class="flex flex-col items-stretch justify-end flex-shrink-0 w-full space-y-2 md:w-auto md:flex-row md:space-y-0 md:items-center md:space-x-3">
<div class="flex items-center w-full space-x-3 md:w-auto"> <div class="flex items-center w-full space-x-3 md:w-auto">
<form phx-change="change-sort" class="w-full"> <form phx-change="change-sort" class="w-full">
<select name="sort_by" id="sort_by" class="grow bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"> <select
name="sort_by"
id="sort_by"
class="grow bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
>
<%= for {name, value} <- @select_options do %> <%= for {name, value} <- @select_options do %>
<%= if value == @selected_option do %> <%= if value == @selected_option do %>
<option value={value} selected>{name}</option> <option value={value} selected>{name}</option>

@ -20,7 +20,7 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.Pagination do
kind="primary" kind="primary"
inverse inverse
patch={"#{@endpoint}?#{page_params_which(@page, @params, "prev")}"} patch={"#{@endpoint}?#{page_params_which(@page, @params, "prev")}"}
class="flex items-center justify-center px-4 h-10 ms-0 leading-tight text-gray-500 bg-white border border-e-0 border-gray-300 rounded-s-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white" class="flex items-center justify-center px-4 h-10 ms-0 leading-tight text-gray-500 bg-slate-100 border border-e-0 border-gray-300 rounded-s-lg hover:bg-slate-300 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
disabled={!AshPhoenix.LiveView.prev_page?(@page)} disabled={!AshPhoenix.LiveView.prev_page?(@page)}
> >
Previous Previous
@ -32,7 +32,7 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.Pagination do
<a <a
href={"#{@endpoint}?#{page_params_number(@page_params, @params, number)}"} href={"#{@endpoint}?#{page_params_number(@page_params, @params, number)}"}
aria-current="page" aria-current="page"
class="flex items-center justify-center px-4 h-10 text-blue-600 border border-gray-300 bg-blue-50 hover:bg-blue-100 hover:text-blue-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white" class="flex items-center justify-center px-4 h-10 text-blue-600 border border-gray-300 bg-blue-100 hover:bg-blue-200 hover:text-blue-700 dark:border-gray-700 dark:bg-gray-700 dark:text-white"
> >
{number} {number}
</a> </a>
@ -41,7 +41,7 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.Pagination do
<li> <li>
<a <a
href={"#{@endpoint}?#{page_params_number(@page_params, @params, number)}"} href={"#{@endpoint}?#{page_params_number(@page_params, @params, number)}"}
class="flex items-center justify-center px-4 h-10 leading-tight text-gray-500 bg-white border border-gray-300 hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white" class="flex items-center justify-center px-4 h-10 leading-tight text-gray-500 bg-slate-100 border border-gray-300 hover:bg-slate-300 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
> >
{number} {number}
</a> </a>
@ -54,7 +54,7 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.Pagination do
kind="primary" kind="primary"
inverse inverse
patch={"#{@endpoint}?#{page_params_which(@page, @params, "next")}"} patch={"#{@endpoint}?#{page_params_which(@page, @params, "next")}"}
class="flex items-center justify-center px-4 h-10 leading-tight text-gray-500 bg-white border border-gray-300 rounded-e-lg hover:bg-gray-100 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white" class="flex items-center justify-center px-4 h-10 leading-tight text-gray-500 bg-slate-100 border border-gray-300 rounded-e-lg hover:bg-slate-300 hover:text-gray-700 dark:bg-gray-800 dark:border-gray-700 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
disabled={!AshPhoenix.LiveView.next_page?(@page)} disabled={!AshPhoenix.LiveView.next_page?(@page)}
> >
Next Next

@ -17,27 +17,59 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.SearchResources do
def search_resources(assigns) do def search_resources(assigns) do
~H""" ~H"""
<div class="relative bg-white shadow-md dark:bg-gray-800 sm:rounded-lg"> <div class="relative bg-slate-100 shadow-md dark:bg-gray-800 sm:rounded-lg">
<div class="flex flex-col items-center justify-between p-4 space-y-3 md:flex-row md:space-y-0 md:space-x-4"> <div class="flex flex-col items-center justify-between p-4 space-y-3 md:flex-row md:space-y-0 md:space-x-4">
<div class="w-full grow"> <div class="w-full grow">
<form class="flex items-center" phx-change="search" phx-submit="search"> <form class="flex items-center" phx-change="search" phx-submit="search">
<label for="simple-search" class="sr-only">Search</label> <label for="simple-search" class="sr-only">Search</label>
<div class="relative w-full"> <div class="relative w-full">
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none"> <div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <svg
<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" /> aria-hidden="true"
class="w-5 h-5 text-gray-500 dark:text-gray-400"
fill="currentColor"
viewbox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<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"
/>
</svg> </svg>
</div> </div>
<input name="query" type="text" phx-debounce="300" autocomplete="off" id="simple-search" class="block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" placeholder={@search_placeholder} value={@search_query}> <input
name="query"
type="text"
phx-debounce="300"
autocomplete="off"
id="simple-search"
class="block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
placeholder={@search_placeholder}
value={@search_query}
/>
</div> </div>
</form> </form>
</div> </div>
<div class="flex flex-col items-stretch justify-end flex-shrink-0 w-full space-y-2 md:w-auto md:flex-row md:space-y-0 md:items-center md:space-x-3"> <div class="flex flex-col items-stretch justify-end flex-shrink-0 w-full space-y-2 md:w-auto md:flex-row md:space-y-0 md:items-center md:space-x-3">
<%= if can_add?(@current_user, @moderator_role) do %> <%= if can_add?(@current_user, @moderator_role) do %>
<.link patch={@resource_new_url}> <.link patch={@resource_new_url}>
<button type="button" class="grow w-full flex items-center justify-left px-4 py-2 text-sm font-medium text-black dark:text-white rounded-lg bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 dark:bg-primary-600 dark:hover:bg-primary-700 focus:outline-none dark:focus:ring-primary-800"> <button
<svg class="h-3.5 w-3.5 mr-2" fill="currentColor" viewbox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> type="button"
<path clip-rule="evenodd" fill-rule="evenodd" d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z" /> class="grow w-full flex items-center justify-left px-4 py-2 text-sm font-medium text-black dark:text-white rounded-lg bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:ring-primary-300 dark:bg-primary-600 dark:hover:bg-primary-700 focus:outline-none dark:focus:ring-primary-800"
>
<svg
class="h-3.5 w-3.5 mr-2"
fill="currentColor"
viewbox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<path
clip-rule="evenodd"
fill-rule="evenodd"
d="M10 3a1 1 0 011 1v5h5a1 1 0 110 2h-5v5a1 1 0 11-2 0v-5H4a1 1 0 110-2h5V4a1 1 0 011-1z"
/>
</svg> </svg>
Add {@resource_type} Add {@resource_type}
</button> </button>
@ -45,7 +77,11 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.SearchResources do
<% end %> <% end %>
<div class="flex items-center w-full space-x-3 md:w-auto"> <div class="flex items-center w-full space-x-3 md:w-auto">
<form phx-change="change-sort" class="w-full"> <form phx-change="change-sort" class="w-full">
<select name="sort_by" id="sort_by" class="grow bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"> <select
name="sort_by"
id="sort_by"
class="grow bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
>
<%= for {name, value} <- @select_options do %> <%= for {name, value} <- @select_options do %>
<%= if value == @selected_option do %> <%= if value == @selected_option do %>
<option value={value} selected>{name}</option> <option value={value} selected>{name}</option>

@ -14,19 +14,55 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.SelectAuthor do
<label for="simple-search" class="sr-only">Search</label> <label for="simple-search" class="sr-only">Search</label>
<div class="relative w-full"> <div class="relative w-full">
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none"> <div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <svg
<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> aria-hidden="true"
class="w-5 h-5 text-gray-500 dark:text-gray-400"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<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>
</svg> </svg>
</div> </div>
<input name="query" type="text" phx-debounce="300" autocomplete="off" id="simple-search" class="block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" placeholder="Search" value=""> <input
name="query"
type="text"
phx-debounce="300"
autocomplete="off"
id="simple-search"
class="block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
placeholder="Search"
value=""
/>
</div> </div>
</form> </form>
<%= if Enum.empty?(@page.results) do %>
<div class="flex justify-center ">
<p class="text-lg font-semibold py-5 dark:text-white">
No Authors
</p>
</div>
<% else %>
<%= for author <- @page.results do %> <%= for author <- @page.results do %>
<div phx-click="select-author" phx-target={@notify_component} phx-value-author={author.id} phx-value-path={@form_path} class="w-full bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700 mx-auto my-3"> <div
<h5 class="mb-1 text-lg font-medium text-gray-900 dark:text-white px-2 pt-1">{author.name}</h5> phx-click="select-author"
phx-target={@notify_component}
phx-value-author={author.id}
phx-value-path={@form_path}
class="w-full bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700 mx-auto my-3 hover:bg-gray-100 dark:hover:bg-gray-700"
>
<h5 class="mb-1 text-lg font-medium text-gray-900 dark:text-white px-2 pt-1">
{author.name}
</h5>
</div> </div>
<% end %> <% end %>
<% end %>
</div> </div>
""" """
end end

@ -42,18 +42,26 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.SelectAuthorAlias do
</div> </div>
</form> </form>
<%= if Enum.empty?(@page.results) do %>
<div class="flex justify-center ">
<p class="text-lg font-semibold py-5 dark:text-white">
No Authors
</p>
</div>
<% else %>
<%= for author_alias <- @page.results do %> <%= for author_alias <- @page.results do %>
<div <div
phx-click="select-author-alias" phx-click="select-author-alias"
phx-target={@notify_component} phx-target={@notify_component}
phx-value-author-alias={author_alias.author_alias_registry_id} phx-value-author-alias={author_alias.author_alias_registry_id}
class="w-full bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700 mx-auto my-3" class="w-full bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700 mx-auto my-3 hover:bg-gray-100 dark:hover:bg-gray-700"
> >
<h5 class="mb-1 text-lg font-medium text-gray-900 dark:text-white px-2 pt-1"> <h5 class="mb-1 text-lg font-medium text-gray-900 dark:text-white px-2 pt-1">
{author_alias.name} {author_alias.name}
</h5> </h5>
</div> </div>
<% end %> <% end %>
<% end %>
</div> </div>
""" """
end end

@ -42,18 +42,26 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.SelectBookEdition do
</div> </div>
</form> </form>
<%= if Enum.empty?(@page.results) do %>
<div class="flex justify-center ">
<p class="text-lg font-semibold py-5 dark:text-white">
No Books
</p>
</div>
<% else %>
<%= for book_edition <- @page.results do %> <%= for book_edition <- @page.results do %>
<div <div
phx-click="select-book-edition" phx-click="select-book-edition"
phx-target={@notify_component} phx-target={@notify_component}
phx-value-book-edition={book_edition.book_editions_registry_id} phx-value-book-edition={book_edition.book_editions_registry_id}
class="w-full bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700 mx-auto my-3" class="w-full bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700 mx-auto my-3 hover:bg-gray-100 dark:hover:bg-gray-700"
> >
<h5 class="mb-1 text-lg font-medium text-gray-900 dark:text-white px-2 pt-1"> <h5 class="mb-1 text-lg font-medium text-gray-900 dark:text-white px-2 pt-1">
{book_edition.title} {book_edition.title}
</h5> </h5>
</div> </div>
<% end %> <% end %>
<% end %>
</div> </div>
""" """
end end

@ -14,19 +14,54 @@ defmodule DecentralisedBookIndexWeb.Components.MyComponents.SelectPublisher do
<label for="simple-search" class="sr-only">Search</label> <label for="simple-search" class="sr-only">Search</label>
<div class="relative w-full"> <div class="relative w-full">
<div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none"> <div class="absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none">
<svg aria-hidden="true" class="w-5 h-5 text-gray-500 dark:text-gray-400" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <svg
<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> aria-hidden="true"
class="w-5 h-5 text-gray-500 dark:text-gray-400"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
>
<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>
</svg> </svg>
</div> </div>
<input name="query" type="text" phx-debounce="300" autocomplete="off" id="simple-search" class="block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500" placeholder="Search" value=""> <input
name="query"
type="text"
phx-debounce="300"
autocomplete="off"
id="simple-search"
class="block w-full p-2 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-primary-500 focus:border-primary-500 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-primary-500 dark:focus:border-primary-500"
placeholder="Search"
value=""
/>
</div> </div>
</form> </form>
<%= if Enum.empty?(@page.results) do %>
<div class="flex justify-center ">
<p class="text-lg font-semibold py-5 dark:text-white">
No Publishers
</p>
</div>
<% else %>
<%= for publisher <- @page.results do %> <%= for publisher <- @page.results do %>
<div phx-click="select-publisher" phx-target={@notify_component} phx-value-publisher={publisher.id} class="w-full bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700 mx-auto my-3"> <div
<h5 class="mb-1 text-lg font-medium text-gray-900 dark:text-white px-2 pt-1">{publisher.name}</h5> phx-click="select-publisher"
phx-target={@notify_component}
phx-value-publisher={publisher.id}
class="w-full bg-white border border-gray-200 rounded-lg shadow-sm dark:bg-gray-800 dark:border-gray-700 mx-auto my-3 hover:bg-gray-100 dark:hover:bg-gray-700"
>
<h5 class="mb-1 text-lg font-medium text-gray-900 dark:text-white px-2 pt-1">
{publisher.name}
</h5>
</div> </div>
<% end %> <% end %>
<% end %>
</div> </div>
""" """
end end

@ -2,7 +2,8 @@ defmodule DecentralisedBookIndexWeb.Components.MyPartials.Navbar do
use Phoenix.Component use Phoenix.Component
use DecentralisedBookIndexWeb, :verified_routes use DecentralisedBookIndexWeb, :verified_routes
import DecentralisedBookIndexWeb.Components.MyComponents.Buttons import DecentralisedBookIndexWeb.Components.MyComponents.{Buttons, DarkModeSwitcher}
alias Phoenix.LiveView.JS alias Phoenix.LiveView.JS
alias DecentralisedBookIndex.Accounts.Role alias DecentralisedBookIndex.Accounts.Role
@ -11,7 +12,7 @@ defmodule DecentralisedBookIndexWeb.Components.MyPartials.Navbar do
def partial_navbar(assigns) do def partial_navbar(assigns) do
~H""" ~H"""
<nav class="bg-white border-gray-200 px-4 lg:px-6 py-2.5 dark:bg-gray-800"> <nav class="bg-slate-100 border-gray-200 px-4 lg:px-6 py-2.5 dark:bg-gray-800">
<div class="flex flex-wrap justify-between items-center mx-auto max-w-screen-xl"> <div class="flex flex-wrap justify-between items-center mx-auto max-w-screen-xl">
<.link patch={~p"/books"} class="flex items-center"> <.link patch={~p"/books"} class="flex items-center">
<svg <svg
@ -35,6 +36,7 @@ defmodule DecentralisedBookIndexWeb.Components.MyPartials.Navbar do
<span class="self-center text-xl font-semibold whitespace-nowrap dark:text-white">DBI</span> <span class="self-center text-xl font-semibold whitespace-nowrap dark:text-white">DBI</span>
</.link> </.link>
<div class="flex items-center lg:order-2"> <div class="flex items-center lg:order-2">
<.dark_mode_switcher />
<.user_info current_user={@current_user} /> <.user_info current_user={@current_user} />
<button <button
data-collapse-toggle="mobile-menu-2" data-collapse-toggle="mobile-menu-2"
@ -153,7 +155,7 @@ defmodule DecentralisedBookIndexWeb.Components.MyPartials.Navbar do
<!-- Dropdown menu --> <!-- Dropdown menu -->
<div <div
id="dropdown-user-info" id="dropdown-user-info"
class="z-10 hidden text-base list-none bg-white rounded-lg shadow-sm w-40 dark:bg-gray-700 flex flex-col items-center pt-2 border dark:border-gray-600" class="z-10 hidden text-base list-none bg-slate-100 rounded-lg shadow-sm w-40 dark:bg-gray-700 flex flex-col items-center pt-2 border dark:border-gray-600"
> >
<%= if @current_user.role != :user do %> <%= if @current_user.role != :user do %>
<p class="text-zinc-700 dark:text-white"> <p class="text-zinc-700 dark:text-white">

@ -30,19 +30,11 @@ defmodule DecentralisedBookIndexWeb.AuthorLive.Index do
<% else %> <% else %>
<div class="flex flex-wrap flex-[3_1_auto]"> <div class="flex flex-wrap flex-[3_1_auto]">
<%= for author <- @page.results do %> <%= for author <- @page.results do %>
<.author_card <.author_card author={author} current_user={@current_user} />
author={author}
current_user={@current_user}
/>
<% end %> <% end %>
</div> </div>
<.pagination <.pagination endpoint={~p"/authors"} page={@page} page_params={@page_params} params={@params} />
endpoint={~p"/authors"}
page={@page}
page_params={@page_params}
params={@params}
/>
<% end %> <% end %>
""" """
end end

@ -2,6 +2,7 @@ defmodule DecentralisedBookIndexWeb.AuthorLive.Show do
use DecentralisedBookIndexWeb, :live_view use DecentralisedBookIndexWeb, :live_view
alias DecentralisedBookIndex.Accounts.Role alias DecentralisedBookIndex.Accounts.Role
alias DecentralisedBookIndex.Metadata
@impl true @impl true
def render(assigns) do def render(assigns) do
@ -27,7 +28,7 @@ defmodule DecentralisedBookIndexWeb.AuthorLive.Show do
</:actions> </:actions>
</.header> </.header>
<div class="flex flex-col items-start gap-6 max-w-6xl mx-auto p-4 bg-gray-900 rounded-lg transition-all duration-300 md:prose md:block"> <div class="flex flex-col items-start gap-6 max-w-6xl mx-auto p-4 rounded-lg transition-all duration-300 md:prose md:block">
<%= if @author.avatar_url != nil do %> <%= if @author.avatar_url != nil do %>
<img <img
class="w-36 h-36 mb-3 rounded-full shadow-lg mx-auto md:float-left md:mr-4" class="w-36 h-36 mb-3 rounded-full shadow-lg mx-auto md:float-left md:mr-4"
@ -54,7 +55,7 @@ defmodule DecentralisedBookIndexWeb.AuthorLive.Show do
<div class="flex-1 pt-2 lg:pt-0 min-h-36"> <div class="flex-1 pt-2 lg:pt-0 min-h-36">
<h2 class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Description</h2> <h2 class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Description</h2>
<p class="text-gray-300 leading-relaxed"> <p class="text-zinc-800 dark:text-gray-400 leading-relaxed">
{@author.description} {@author.description}
</p> </p>
@ -96,6 +97,19 @@ defmodule DecentralisedBookIndexWeb.AuthorLive.Show do
</ul> </ul>
<% end %> <% end %>
<%= if not Enum.empty?(@page.results) do %>
<h2 class="mb-2 text-lg font-semibold text-gray-900 dark:text-white mt-10">
Books
</h2>
<div class="flex flex-wrap flex-[3_1_auto]">
<%= for book <- @page.results do %>
<.book_card book={book} current_user={@current_user} />
<% end %>
</div>
<.pagination endpoint={~p"/authors/#{@author}"} page={@page} page_params={@page_params} params={@params} />
<% end %>
<.back navigate={~p"/authors"}>Back to authors</.back> <.back navigate={~p"/authors"}>Back to authors</.back>
""" """
end end
@ -106,16 +120,30 @@ defmodule DecentralisedBookIndexWeb.AuthorLive.Show do
end end
@impl true @impl true
def handle_params(%{"id" => id}, _, socket) do def handle_params(%{"id" => id} = params, _, socket) do
author = author =
Ash.get!(DecentralisedBookIndex.Metadata.Author, id, load: [:dbi_server], actor: socket.assigns.current_user) Ash.get!(Metadata.Author, id,
load: [:dbi_server],
actor: socket.assigns.current_user
)
alternative_names = Metadata.get_author_alternative_names!(author)
alternative_names = DecentralisedBookIndex.Metadata.get_author_alternative_names!(author) page_params = AshPhoenix.LiveView.page_from_params(params, 10)
page =
Metadata.get_author_books!(author,
load: [:brief_description],
page: page_params ++ [count: true],
actor: socket.assigns.current_user
)
{:noreply, {:noreply,
socket socket
|> assign(:page_title, page_title(socket.assigns.live_action)) |> assign(:page_title, page_title(socket.assigns.live_action))
|> assign(:author, author) |> assign(:author, author)
|> assign(:page_params, page_params)
|> assign(:page, page)
|> assign(:params, params)
|> assign(:alternative_names, alternative_names)} |> assign(:alternative_names, alternative_names)}
end end

@ -21,19 +21,11 @@ defmodule DecentralisedBookIndexWeb.BookLive.FormComponent do
<%= if @form.source.type == :create do %> <%= if @form.source.type == :create do %>
<.input field={@form[:title]} type="text" label="Title" /> <.input field={@form[:title]} type="text" label="Title" />
<.input field={@form[:cover_image_url]} type="text" label="Cover image url" /> <.input field={@form[:cover_image_url]} type="text" label="Cover image url" />
<.input <.input field={@form[:description]} type="textarea" label="Description" />
field={@form[:description]}
type="textarea"
label="Description"
/>
<.bids_inputs form={@form} myself={@myself} /> <.bids_inputs form={@form} myself={@myself} />
<.author_roles_inputs form={@form} myself={@myself} notify_component={@myself} /> <.author_roles_inputs form={@form} myself={@myself} notify_component={@myself} />
<.input field={@form[:format]} type="text" label="Format" /> <.input field={@form[:format]} type="text" label="Format" />
<.input <.input field={@form[:language]} type="text" label="Language" />
field={@form[:language]}
type="text"
label="Language"
/>
<.input field={@form[:page_count]} type="number" label="Page count" /> <.input field={@form[:page_count]} type="number" label="Page count" />
<div> <div>
<.input field={@form[:publisher_id]} type="text" label="Publisher" type="hidden" show_errors?={false} /> <.input field={@form[:publisher_id]} type="text" label="Publisher" type="hidden" show_errors?={false} />
@ -48,19 +40,11 @@ defmodule DecentralisedBookIndexWeb.BookLive.FormComponent do
<%= if @form.source.type == :update do %> <%= if @form.source.type == :update do %>
<.input field={@form[:title]} type="text" label="Title" /> <.input field={@form[:title]} type="text" label="Title" />
<.input field={@form[:cover_image_url]} type="text" label="Cover image url" /> <.input field={@form[:cover_image_url]} type="text" label="Cover image url" />
<.input <.input field={@form[:description]} type="textarea" label="Description" />
field={@form[:description]}
type="textarea"
label="Description"
/>
<.bids_inputs form={@form} myself={@myself} /> <.bids_inputs form={@form} myself={@myself} />
<.author_roles_inputs form={@form} myself={@myself} notify_component={@myself} /> <.author_roles_inputs form={@form} myself={@myself} notify_component={@myself} />
<.input field={@form[:format]} type="text" label="Format" /> <.input field={@form[:format]} type="text" label="Format" />
<.input <.input field={@form[:language]} type="text" label="Language" />
field={@form[:language]}
type="text"
label="Language"
/>
<.input field={@form[:page_count]} type="number" label="Page count" /> <.input field={@form[:page_count]} type="number" label="Page count" />
<div> <div>
<.input field={@form[:publisher_id]} type="text" label="Publisher" type="hidden" show_errors?={false} /> <.input field={@form[:publisher_id]} type="text" label="Publisher" type="hidden" show_errors?={false} />
@ -145,16 +129,30 @@ defmodule DecentralisedBookIndexWeb.BookLive.FormComponent do
<tbody phx-hook="bidSort" id="bidSort" phx-target={@myself}> <tbody phx-hook="bidSort" id="bidSort" phx-target={@myself}>
<.inputs_for :let={bid_form} field={@form[:bids]}> <.inputs_for :let={bid_form} field={@form[:bids]}>
<tr data-id={bid_form.index}> <tr data-id={bid_form.index}>
<td class="px-3 w-10 pt-2"> <td class="px-3 w-10 h-10 pt-2 align-top group">
<svg class="w-6 h-6 text-gray-800 dark:text-white handle cursor-pointer" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"> <svg
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m8 15 4 4 4-4m0-6-4-4-4 4"/> class="w-6 h-6 text-gray-800 dark:text-white handle cursor-pointer mt-2"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m8 15 4 4 4-4m0-6-4-4-4 4"
/>
</svg> </svg>
</td> </td>
<td class="px-3 w-36"> <td class="px-3 w-36 align-top group">
<label for={bid_form[:type].id} class="hidden">Type</label> <label for={bid_form[:type].id} class="hidden">Type</label>
<.input field={bid_form[:type]} /> <.input field={bid_form[:type]} />
</td> </td>
<td class="px-3"> <td class="px-3 align-top group">
<label for={bid_form[:bid].id} class="hidden">Id</label> <label for={bid_form[:bid].id} class="hidden">Id</label>
<.input field={bid_form[:bid]} /> <.input field={bid_form[:bid]} />
</td> </td>
@ -167,8 +165,20 @@ defmodule DecentralisedBookIndexWeb.BookLive.FormComponent do
size="xs" size="xs"
inverse inverse
> >
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24"> <svg
<path fill-rule="evenodd" d="M8.586 2.586A2 2 0 0 1 10 2h4a2 2 0 0 1 2 2v2h3a1 1 0 1 1 0 2v12a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V8a1 1 0 0 1 0-2h3V4a2 2 0 0 1 .586-1.414ZM10 6h4V4h-4v2Zm1 4a1 1 0 1 0-2 0v8a1 1 0 1 0 2 0v-8Zm4 0a1 1 0 1 0-2 0v8a1 1 0 1 0 2 0v-8Z" clip-rule="evenodd"/> class="w-6 h-6 text-gray-800 dark:text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
fill-rule="evenodd"
d="M8.586 2.586A2 2 0 0 1 10 2h4a2 2 0 0 1 2 2v2h3a1 1 0 1 1 0 2v12a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V8a1 1 0 0 1 0-2h3V4a2 2 0 0 1 .586-1.414ZM10 6h4V4h-4v2Zm1 4a1 1 0 1 0-2 0v8a1 1 0 1 0 2 0v-8Zm4 0a1 1 0 1 0-2 0v8a1 1 0 1 0 2 0v-8Z"
clip-rule="evenodd"
/>
</svg> </svg>
</.button_link> </.button_link>
</td> </td>
@ -229,12 +239,26 @@ defmodule DecentralisedBookIndexWeb.BookLive.FormComponent do
<tbody phx-hook="authorRoleSort" id="authorRoleSort" phx-target={@myself}> <tbody phx-hook="authorRoleSort" id="authorRoleSort" phx-target={@myself}>
<.inputs_for :let={author_roles_form} field={@form[:author_roles]}> <.inputs_for :let={author_roles_form} field={@form[:author_roles]}>
<tr data-id={author_roles_form.index}> <tr data-id={author_roles_form.index}>
<td class="px-3 w-10 pt-2"> <td class="px-3 w-10 pt-2 align-top group">
<svg class="w-6 h-6 text-gray-800 dark:text-white handle cursor-pointer" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24"> <svg
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m8 15 4 4 4-4m0-6-4-4-4 4"/> class="w-6 h-6 text-gray-800 dark:text-white handle cursor-pointer mt-2"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="none"
viewBox="0 0 24 24"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m8 15 4 4 4-4m0-6-4-4-4 4"
/>
</svg> </svg>
</td> </td>
<td class="px-3 w-48"> <td class="px-3 w-48 align-top group">
<label for={author_roles_form[:author_id].id} class="hidden">Type</label> <label for={author_roles_form[:author_id].id} class="hidden">Type</label>
<.input field={author_roles_form[:author_id]} type="hidden" show_errors?={false} /> <.input field={author_roles_form[:author_id]} type="hidden" show_errors?={false} />
<.selected_author <.selected_author
@ -244,7 +268,7 @@ defmodule DecentralisedBookIndexWeb.BookLive.FormComponent do
class="mt-2 w-full" class="mt-2 w-full"
/> />
</td> </td>
<td class="px-3"> <td class="px-3 align-top group">
<label for={author_roles_form[:role].id} class="hidden">Id</label> <label for={author_roles_form[:role].id} class="hidden">Id</label>
<.input field={author_roles_form[:role]} /> <.input field={author_roles_form[:role]} />
</td> </td>
@ -257,8 +281,20 @@ defmodule DecentralisedBookIndexWeb.BookLive.FormComponent do
size="xs" size="xs"
inverse inverse
> >
<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24"> <svg
<path fill-rule="evenodd" d="M8.586 2.586A2 2 0 0 1 10 2h4a2 2 0 0 1 2 2v2h3a1 1 0 1 1 0 2v12a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V8a1 1 0 0 1 0-2h3V4a2 2 0 0 1 .586-1.414ZM10 6h4V4h-4v2Zm1 4a1 1 0 1 0-2 0v8a1 1 0 1 0 2 0v-8Zm4 0a1 1 0 1 0-2 0v8a1 1 0 1 0 2 0v-8Z" clip-rule="evenodd"/> class="w-6 h-6 text-gray-800 dark:text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
fill-rule="evenodd"
d="M8.586 2.586A2 2 0 0 1 10 2h4a2 2 0 0 1 2 2v2h3a1 1 0 1 1 0 2v12a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V8a1 1 0 0 1 0-2h3V4a2 2 0 0 1 .586-1.414ZM10 6h4V4h-4v2Zm1 4a1 1 0 1 0-2 0v8a1 1 0 1 0 2 0v-8Zm4 0a1 1 0 1 0-2 0v8a1 1 0 1 0 2 0v-8Z"
clip-rule="evenodd"
/>
</svg> </svg>
</.button_link> </.button_link>
</td> </td>

@ -30,19 +30,11 @@ defmodule DecentralisedBookIndexWeb.BookLive.Index do
<% else %> <% else %>
<div class="flex flex-wrap flex-[3_1_auto]"> <div class="flex flex-wrap flex-[3_1_auto]">
<%= for book <- @page.results do %> <%= for book <- @page.results do %>
<.book_card <.book_card book={book} current_user={@current_user} />
book={book}
current_user={@current_user}
/>
<% end %> <% end %>
</div> </div>
<.pagination <.pagination endpoint={~p"/books"} page={@page} page_params={@page_params} params={@params} />
endpoint={~p"/books"}
page={@page}
page_params={@page_params}
params={@params}
/>
<% end %> <% end %>
<.modal :if={@live_action in [:new, :edit]} id="book-modal" show on_cancel={JS.patch(~p"/books")}> <.modal :if={@live_action in [:new, :edit]} id="book-modal" show on_cancel={JS.patch(~p"/books")}>

@ -33,11 +33,27 @@ defmodule DecentralisedBookIndexWeb.BookLive.Show do
<div class="mb-4"> <div class="mb-4">
<%= if @book.cover_image_url != nil do %> <%= if @book.cover_image_url != nil do %>
<img class="w-36 shadow-lg mt-14 mx-auto object-cover" src={@book.cover_image_url} alt={"#{@book.title} image"}/> <img
class="w-36 shadow-lg mt-14 mx-auto object-cover"
src={@book.cover_image_url}
alt={"#{@book.title} image"}
/>
<% else %> <% else %>
<div class="relative w-36 h-36 overflow-hidden mt-14 mx-auto"> <div class="relative w-36 h-36 overflow-hidden mt-14 mx-auto">
<svg class="w-36 h-36 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24"> <svg
<path fill-rule="evenodd" d="M6 2a2 2 0 0 0-2 2v15a3 3 0 0 0 3 3h12a1 1 0 1 0 0-2h-2v-2h2a1 1 0 0 0 1-1V4a2 2 0 0 0-2-2h-8v16h5v2H7a1 1 0 1 1 0-2h1V2H6Z" clip-rule="evenodd"/> class="w-36 h-36 text-gray-800 dark:text-white"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
fill="currentColor"
viewBox="0 0 24 24"
>
<path
fill-rule="evenodd"
d="M6 2a2 2 0 0 0-2 2v15a3 3 0 0 0 3 3h12a1 1 0 1 0 0-2h-2v-2h2a1 1 0 0 0 1-1V4a2 2 0 0 0-2-2h-8v16h5v2H7a1 1 0 1 1 0-2h1V2H6Z"
clip-rule="evenodd"
/>
</svg> </svg>
</div> </div>
<% end %> <% end %>
@ -48,10 +64,14 @@ defmodule DecentralisedBookIndexWeb.BookLive.Show do
<dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Description</dt> <dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Description</dt>
<dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">{@book.description}</dd> <dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">{@book.description}</dd>
</dl> </dl>
<%= if not is_nil(@book.publisher) do %>
<dl> <dl>
<dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Publisher</dt> <dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Publisher</dt>
<dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">{@book.publisher.name}</dd> <dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">
{@book.publisher.name}
</dd>
</dl> </dl>
<% end %>
<dl> <dl>
<dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Published</dt> <dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Published</dt>
<dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">{@book.published}</dd> <dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">{@book.published}</dd>
@ -68,12 +88,16 @@ defmodule DecentralisedBookIndexWeb.BookLive.Show do
<dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Language</dt> <dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Language</dt>
<dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">{@book.language}</dd> <dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">{@book.language}</dd>
</dl> </dl>
<%= if not Enum.empty?(@book.bids) do %>
<dl> <dl>
<dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Identifiers</dt> <dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Identifiers</dt>
<%= for bid <- @book.bids do %> <%= for bid <- @book.bids do %>
<dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">{bid.type}: {bid.bid}</dd> <dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">
{bid.type}: {bid.bid}
</dd>
<% end %> <% end %>
</dl> </dl>
<% end %>
<%= if not is_nil(@book.dbi_server) do %> <%= if not is_nil(@book.dbi_server) do %>
<dl> <dl>
<dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">From Server</dt> <dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">From Server</dt>
@ -96,10 +120,7 @@ defmodule DecentralisedBookIndexWeb.BookLive.Show do
<h1 class="text-lg font-semibold leading-8 text-zinc-800 dark:text-white">Editions</h1> <h1 class="text-lg font-semibold leading-8 text-zinc-800 dark:text-white">Editions</h1>
<div class="flex flex-wrap flex-[3_1_auto]"> <div class="flex flex-wrap flex-[3_1_auto]">
<%= for book <- @alternative_editions do %> <%= for book <- @alternative_editions do %>
<.book_card <.book_card book={book} current_user={@current_user} />
book={book}
current_user={@current_user}
/>
<% end %> <% end %>
</div> </div>
<% end %> <% end %>

@ -73,12 +73,7 @@ defmodule DecentralisedBookIndexWeb.DbiServerLive.Index do
</:action> </:action>
</.table> </.table>
<.pagination <.pagination endpoint={~p"/servers"} page={@page} page_params={@page_params} params={@params} />
endpoint={~p"/servers"}
page={@page}
page_params={@page_params}
params={@params}
/>
</div> </div>
<% end %> <% end %>
""" """
@ -87,7 +82,10 @@ defmodule DecentralisedBookIndexWeb.DbiServerLive.Index do
@impl true @impl true
def mount(_params, _session, socket) do def mount(_params, _session, socket) do
if connected?(socket) do if connected?(socket) do
Phoenix.PubSub.subscribe(DecentralisedBookIndex.PubSub, DecentralisedBookIndex.SyncWorkerManual.topic()) Phoenix.PubSub.subscribe(
DecentralisedBookIndex.PubSub,
DecentralisedBookIndex.SyncWorkerManual.topic()
)
end end
{:ok, {:ok,

@ -10,7 +10,7 @@ defmodule DecentralisedBookIndexWeb.DbiServerLive.Show do
{@dbi_server.name} {@dbi_server.name}
<:actions> <:actions>
<%= if is_nil(@dbi_server.dbi_server) and @current_user != nil and Role.can_administrate?(@current_user.role) do %> <%= if @current_user != nil and Role.can_administrate?(@current_user.role) do %>
<.link patch={~p"/servers/#{@dbi_server}/edit"} phx-click={JS.push_focus()}> <.link patch={~p"/servers/#{@dbi_server}/edit"} phx-click={JS.push_focus()}>
<.edit_button> <.edit_button>
Edit Edit
@ -27,7 +27,9 @@ defmodule DecentralisedBookIndexWeb.DbiServerLive.Show do
</dl> </dl>
<dl> <dl>
<dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Sync on?</dt> <dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Sync on?</dt>
<dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">{@dbi_server.sync_on?}</dd> <dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">
{@dbi_server.sync_on?}
</dd>
</dl> </dl>
<%= if not is_nil(@dbi_server.dbi_server) do %> <%= if not is_nil(@dbi_server.dbi_server) do %>
<dl> <dl>
@ -57,7 +59,10 @@ defmodule DecentralisedBookIndexWeb.DbiServerLive.Show do
|> assign(:page_title, page_title(socket.assigns.live_action)) |> assign(:page_title, page_title(socket.assigns.live_action))
|> assign( |> assign(
:dbi_server, :dbi_server,
Ash.get!(DecentralisedBookIndex.Metadata.DBIServer, id, load: [:dbi_server], actor: socket.assigns.current_user) Ash.get!(DecentralisedBookIndex.Metadata.DBIServer, id,
load: [:dbi_server],
actor: socket.assigns.current_user
)
)} )}
end end

@ -57,7 +57,10 @@ defmodule DecentralisedBookIndexWeb.PublisherLive.Show do
|> assign(:page_title, page_title(socket.assigns.live_action)) |> assign(:page_title, page_title(socket.assigns.live_action))
|> assign( |> assign(
:publisher, :publisher,
Ash.get!(DecentralisedBookIndex.Metadata.Publisher, id, load: [:dbi_server], actor: socket.assigns.current_user) Ash.get!(DecentralisedBookIndex.Metadata.Publisher, id,
load: [:dbi_server],
actor: socket.assigns.current_user
)
)} )}
end end

@ -30,11 +30,7 @@ defmodule DecentralisedBookIndexWeb.UserLive.Index do
</div> </div>
<% else %> <% else %>
<div class="pt-2 flex flex-col gap-2"> <div class="pt-2 flex flex-col gap-2">
<.table <.table id="users" rows={@users} row_click={fn user -> JS.navigate(~p"/users/#{user}") end}>
id="users"
rows={@users}
row_click={fn user -> JS.navigate(~p"/users/#{user}") end}
>
<:col :let={user} label="Email">{user.email}</:col> <:col :let={user} label="Email">{user.email}</:col>
<:col :let={user} label="Role">{user.role}</:col> <:col :let={user} label="Role">{user.role}</:col>
@ -48,12 +44,7 @@ defmodule DecentralisedBookIndexWeb.UserLive.Index do
</:action> </:action>
</.table> </.table>
<.pagination <.pagination endpoint={~p"/servers"} page={@page} page_params={@page_params} params={@params} />
endpoint={~p"/servers"}
page={@page}
page_params={@page_params}
params={@params}
/>
</div> </div>
<% end %> <% end %>
""" """

@ -4,7 +4,7 @@ defmodule DecentralisedBookIndex.MixProject do
def project do def project do
[ [
app: :decentralised_book_index, app: :decentralised_book_index,
version: "1.0.0", version: "1.1.0",
elixir: "~> 1.14", elixir: "~> 1.14",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
start_permanent: Mix.env() == :prod, start_permanent: Mix.env() == :prod,

@ -0,0 +1,21 @@
defmodule DecentralisedBookIndex.Repo.Migrations.UpdateToAllowABookToHaveNoPublisher do
@moduledoc """
Updates resources based on their most recent snapshots.
This file was autogenerated with `mix ash_postgres.generate_migrations`
"""
use Ecto.Migration
def up do
alter table(:books) do
modify :publisher_id, :uuid, null: true
end
end
def down do
alter table(:books) do
modify :publisher_id, :uuid, null: false
end
end
end

@ -0,0 +1,227 @@
{
"attributes": [
{
"allow_nil?": false,
"default": "fragment(\"gen_random_uuid()\")",
"generated?": false,
"primary_key?": true,
"references": null,
"size": null,
"source": "id",
"type": "uuid"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "title",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "description",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "published",
"type": "date"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "language",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "format",
"type": "text"
},
{
"allow_nil?": false,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "page_count",
"type": "bigint"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "cover_image_url",
"type": "text"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "inserted_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": false,
"default": "fragment(\"(now() AT TIME ZONE 'utc')\")",
"generated?": false,
"primary_key?": false,
"references": null,
"size": null,
"source": "updated_at",
"type": "utc_datetime_usec"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "books_dbi_server_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "dbi_servers"
},
"size": null,
"source": "dbi_server_id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "books_book_editions_registry_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "book_editions_registries"
},
"size": null,
"source": "book_editions_registry_id",
"type": "uuid"
},
{
"allow_nil?": true,
"default": "nil",
"generated?": false,
"primary_key?": false,
"references": {
"deferrable": false,
"destination_attribute": "id",
"destination_attribute_default": null,
"destination_attribute_generated": null,
"index?": false,
"match_type": null,
"match_with": null,
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"name": "books_publisher_id_fkey",
"on_delete": null,
"on_update": null,
"primary_key?": true,
"schema": "public",
"table": "publishers"
},
"size": null,
"source": "publisher_id",
"type": "uuid"
}
],
"base_filter": null,
"check_constraints": [],
"custom_indexes": [
{
"all_tenants?": false,
"concurrently": false,
"error_fields": [],
"fields": [
{
"type": "string",
"value": "title gin_trgm_ops"
}
],
"include": null,
"message": null,
"name": "book_title_gin_index",
"nulls_distinct": true,
"prefix": null,
"table": null,
"unique": false,
"using": "GIN",
"where": null
}
],
"custom_statements": [],
"has_create_action": true,
"hash": "AA67DC03E5D78606517F268BD28B58521F2000DE3BA531FA48363CCD08CE4475",
"identities": [],
"multitenancy": {
"attribute": null,
"global": null,
"strategy": null
},
"repo": "Elixir.DecentralisedBookIndex.Repo",
"schema": null,
"table": "books"
}

@ -202,7 +202,7 @@ defmodule DecentralisedBookIndex.Metadata.BookTest do
%{order: 1, author_id: author.id, role: ""} %{order: 1, author_id: author.id, role: ""}
] ]
{:ok, _book} = {:ok, book} =
Metadata.create_book( Metadata.create_book(
"Book", "Book",
"An description", "An description",
@ -218,7 +218,10 @@ defmodule DecentralisedBookIndex.Metadata.BookTest do
actor: user actor: user
) )
assert {:ok, _books} = Metadata.get_author_books(author) assert {:ok, books} = Metadata.get_author_books(author)
book_ids = Enum.map(books.results, & &1.id)
assert book.id in book_ids
end end
test "get the list contains aliases' books", %{user: user} do test "get the list contains aliases' books", %{user: user} do
@ -248,7 +251,7 @@ defmodule DecentralisedBookIndex.Metadata.BookTest do
assert {:ok, books} = Metadata.get_author_books(author) assert {:ok, books} = Metadata.get_author_books(author)
book_ids = Enum.map(books, & &1.id) book_ids = Enum.map(books.results, & &1.id)
assert book.id in book_ids assert book.id in book_ids
assert book2.id in book_ids assert book2.id in book_ids

@ -0,0 +1,45 @@
defmodule DecentralisedBookIndexWeb.Live.BookLive.IndexTest do
use DecentralisedBookIndexWeb.LiveCase, async: true
describe "Search by bid" do
setup do
%{
modes: [
"isbn",
"isbn13",
"asin"
],
books:
for _ <- 1..5 do
generate(book())
end
}
end
test "can search by an empty bid", %{conn: conn, modes: modes} do
for mode <- modes do
{:ok, _view, html} =
conn
|> live("/books?search_mode=#{mode}")
assert html =~ "Listing Books"
assert html =~ "No Books"
end
end
test "can search by a bid", %{conn: conn, modes: modes, books: books} do
[book | others] = books
for %{type: type, bid: bid} <- book.bids do
{:ok, view, html} =
conn
|> live("/books?query=#{bid}&search_mode=#{type}")
html = render(view)
assert html =~ book.title
refute html =~ "No Books"
end
end
end
end

@ -119,7 +119,7 @@ defmodule DecentralisedBookIndex.Generators do
count = count =
opts[:count] || 2 opts[:count] || 2
types = ["isbn10", "isbn13", "asin"] types = ["isbn", "isbn13", "asin"]
if count > length(types), do: count = length(types) if count > length(types), do: count = length(types)

Loading…
Cancel
Save