Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions assets/css/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,20 @@
@apply bg-gray-500;
border-radius: 4px;
}

/* ===== Light Theme ===== */
body.light-theme {
background-color: rgb(255 255 255);
color: rgb(17 24 39);
}

/* Theme toggle icon visibility */
.theme-icon-light {
display: none;
}
body.light-theme .theme-icon-dark {
display: none;
}
body.light-theme .theme-icon-light {
display: block;
}
34 changes: 34 additions & 0 deletions assets/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,27 @@ let csrfToken = document.querySelector("meta[name='csrf-token']").getAttribute("
let livePath = document.querySelector("meta[name='live-path']").getAttribute("content");
let liveTransport = document .querySelector("meta[name='live-transport']") .getAttribute("content");

// Theme management
const Theme = {
STORAGE_KEY: "error-tracker-theme",

init() {
const saved = localStorage.getItem(this.STORAGE_KEY);
if (saved === "light") {
document.body.classList.add("light-theme");
}
},

toggle() {
const isLight = document.body.classList.toggle("light-theme");
localStorage.setItem(this.STORAGE_KEY, isLight ? "light" : "dark");
},

isLight() {
return document.body.classList.contains("light-theme");
}
};

const Hooks = {
JsonPrettyPrint: {
mounted() {
Expand All @@ -26,6 +47,11 @@ const Hooks = {
// Keep the original content if there's an error
}
}
},
ThemeInit: {
mounted() {
Theme.init();
}
}
};

Expand All @@ -41,6 +67,14 @@ topbar.config({ barColors: { 0: "#29d" }, shadowColor: "rgba(0, 0, 0, .3)" });
window.addEventListener("phx:page-loading-start", (_info) => topbar.show(300));
window.addEventListener("phx:page-loading-stop", (_info) => topbar.hide());

// Set up theme toggle via event delegation (CSP-compliant, avoids inline onclick)
document.addEventListener("click", function(e) {
var toggle = e.target.closest("[data-theme-toggle]");
if (toggle) {
Theme.toggle();
}
});

// connect if there are any LiveViews on the page
liveSocket.connect();
window.liveSocket = liveSocket;
3 changes: 2 additions & 1 deletion assets/tailwind.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module.exports = {
plugin(({addVariant}) => addVariant('phx-no-feedback', ['&.phx-no-feedback', '.phx-no-feedback &'])),
plugin(({addVariant}) => addVariant('phx-click-loading', ['&.phx-click-loading', '.phx-click-loading &'])),
plugin(({addVariant}) => addVariant('phx-submit-loading', ['&.phx-submit-loading', '.phx-submit-loading &'])),
plugin(({addVariant}) => addVariant('phx-change-loading', ['&.phx-change-loading', '.phx-change-loading &']))
plugin(({addVariant}) => addVariant('phx-change-loading', ['&.phx-change-loading', '.phx-change-loading &'])),
plugin(({addVariant}) => addVariant('light', 'body.light-theme &'))
]
}
42 changes: 30 additions & 12 deletions lib/error_tracker/web/components/core_components.ex
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ defmodule ErrorTracker.Web.CoreComponents do
<.link
class={[
"phx-submit-loading:opacity-75 py-[11.5px]",
"text-sm font-semibold text-sky-500 hover:text-white/80",
"text-sm font-semibold text-sky-500 light:text-sky-600 hover:text-white/80 light:hover:text-gray-900/80",
@class
]}
{@rest}
Expand Down Expand Up @@ -63,14 +63,29 @@ defmodule ErrorTracker.Web.CoreComponents do
def badge(assigns) do
color_class =
case assigns.color do
:blue -> "bg-blue-900 text-blue-300"
:gray -> "bg-gray-700 text-gray-300"
:red -> "bg-red-400/10 text-red-300 ring-red-400/20"
:green -> "bg-emerald-400/10 text-emerald-300 ring-emerald-400/20"
:yellow -> "bg-yellow-900 text-yellow-300"
:indigo -> "bg-indigo-900 text-indigo-300"
:purple -> "bg-purple-900 text-purple-300"
:pink -> "bg-pink-900 text-pink-300"
:blue ->
"bg-blue-900 light:bg-blue-100 text-blue-300 light:text-blue-800"

:gray ->
"bg-gray-700 light:bg-gray-200 text-gray-300 light:text-gray-700"

:red ->
"bg-red-400/10 light:bg-red-100 text-red-300 light:text-red-800 ring-red-400/20 light:ring-red-400/30"

:green ->
"bg-emerald-400/10 light:bg-emerald-100 text-emerald-300 light:text-emerald-800 ring-emerald-400/20 light:ring-emerald-400/30"

:yellow ->
"bg-yellow-900 light:bg-yellow-100 text-yellow-300 light:text-yellow-800"

:indigo ->
"bg-indigo-900 light:bg-indigo-100 text-indigo-300 light:text-indigo-800"

:purple ->
"bg-purple-900 light:bg-purple-100 text-purple-300 light:text-purple-800"

:pink ->
"bg-pink-900 light:bg-pink-100 text-pink-300 light:text-pink-800"
end

assigns = Map.put(assigns, :color_class, color_class)
Expand All @@ -95,14 +110,14 @@ defmodule ErrorTracker.Web.CoreComponents do
<div class="mt-10 w-full flex">
<button
:if={@page > 1}
class="flex items-center justify-center px-4 h-10 text-base font-medium text-gray-400 bg-gray-900 border border-gray-400 rounded-lg hover:bg-gray-800 hover:text-white"
class="flex items-center justify-center px-4 h-10 text-base font-medium text-gray-400 light:text-gray-500 bg-gray-900 light:bg-gray-100 border border-gray-400 light:border-gray-300 rounded-lg hover:bg-gray-800 light:hover:bg-gray-200 hover:text-white light:hover:text-gray-900"
phx-click={@event_previous}
>
Previous page
</button>
<button
:if={@page < @total_pages}
class="flex items-center justify-center px-4 h-10 text-base font-medium text-gray-400 bg-gray-900 border border-gray-400 rounded-lg hover:bg-gray-800 hover:text-white"
class="flex items-center justify-center px-4 h-10 text-base font-medium text-gray-400 light:text-gray-500 bg-gray-900 light:bg-gray-100 border border-gray-400 light:border-gray-300 rounded-lg hover:bg-gray-800 light:hover:bg-gray-200 hover:text-white light:hover:text-gray-900"
phx-click={@event_next}
>
Next page
Expand All @@ -122,7 +137,10 @@ defmodule ErrorTracker.Web.CoreComponents do
<div>
<h2
:if={assigns[:title]}
class={["text-sm font-semibold mb-2 uppercase text-gray-400", @title_class]}
class={[
"text-sm font-semibold mb-2 uppercase text-gray-400 light:text-gray-500",
@title_class
]}
>
{@title}
</h2>
Expand Down
41 changes: 36 additions & 5 deletions lib/error_tracker/web/components/layouts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,17 @@ defmodule ErrorTracker.Web.Layouts do

def navbar(assigns) do
~H"""
<nav class="border-gray-400 bg-gray-900">
<nav class="border-gray-400 light:border-gray-300 bg-gray-900 light:bg-gray-100">
<div class="container flex flex-wrap items-center justify-between mx-auto p-4">
<.link
href={dashboard_path(@socket)}
class="self-center text-2xl font-semibold whitespace-nowrap text-white"
class="self-center text-2xl font-semibold whitespace-nowrap text-white light:text-gray-900"
>
<span class="mr-2">🐛</span>ErrorTracker
</.link>
<button
type="button"
class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm rounded -lg md:hidden focus:outline-none focus:ring-2 text-gray-400 hover:bg-gray-700 focus:ring-gray-500"
class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm rounded -lg md:hidden focus:outline-none focus:ring-2 text-gray-400 light:text-gray-500 hover:bg-gray-700 light:hover:bg-gray-200 focus:ring-gray-500"
aria-controls="navbar-main"
aria-expanded="false"
phx-click={JS.toggle(to: "#navbar-main")}
Expand All @@ -68,7 +68,7 @@ defmodule ErrorTracker.Web.Layouts do
</svg>
</button>
<div class="hidden w-full md:block md:w-auto" id="navbar-main">
<ul class="font-medium flex flex-col p-4 md:p-0 mt-4 border border-gray-400 bg-gray-900 rounded-lg md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0 md:bg-gray-800">
<ul class="font-medium flex flex-col p-4 md:p-0 mt-4 border border-gray-400 light:border-gray-300 bg-gray-900 light:bg-gray-100 rounded-lg md:flex-row md:space-x-8 rtl:space-x-reverse md:mt-0 md:border-0 md:bg-gray-800 light:md:bg-gray-50">
<.navbar_item to="https://github.com/elixir-error-tracker/error-tracker" target="_blank">
<svg
width="18"
Expand All @@ -86,6 +86,37 @@ defmodule ErrorTracker.Web.Layouts do
</svg>
GitHub
</.navbar_item>
<li>
<button
data-theme-toggle
class="block py-2 px-3 rounded-lg text-white light:text-gray-900 hover:text-white light:hover:text-gray-900 hover:bg-gray-700 light:hover:bg-gray-200 md:hover:bg-transparent md:border-0 md:hover:text-sky-500"
aria-label="Toggle theme"
title="Toggle theme"
>
<!-- Moon icon (shown in dark mode) -->
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5 theme-icon-dark"
>
<path
fill-rule="evenodd"
d="M7.455 2.004a.75.75 0 0 1 .26.77 7 7 0 0 0 9.958 7.967.75.75 0 0 1 1.067.853A8.5 8.5 0 1 1 6.647 1.921a.75.75 0 0 1 .808.083Z"
clip-rule="evenodd"
/>
</svg>
<!-- Sun icon (shown in light mode) -->
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 20 20"
fill="currentColor"
class="w-5 h-5 theme-icon-light"
>
<path d="M10 2a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 10 2ZM10 15a.75.75 0 0 1 .75.75v1.5a.75.75 0 0 1-1.5 0v-1.5A.75.75 0 0 1 10 15ZM10 7a3 3 0 1 0 0 6 3 3 0 0 0 0-6ZM15.657 5.404a.75.75 0 1 0-1.06-1.06l-1.061 1.06a.75.75 0 0 0 1.06 1.06l1.06-1.06ZM6.464 14.596a.75.75 0 1 0-1.06-1.06l-1.06 1.06a.75.75 0 0 0 1.06 1.06l1.06-1.06ZM18 10a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1 0-1.5h1.5A.75.75 0 0 1 18 10ZM5 10a.75.75 0 0 1-.75.75h-1.5a.75.75 0 0 1 0-1.5h1.5A.75.75 0 0 1 5 10ZM14.596 15.657a.75.75 0 0 0 1.06-1.06l-1.06-1.061a.75.75 0 1 0-1.06 1.06l1.06 1.06ZM5.404 6.464a.75.75 0 0 0 1.06-1.06l-1.06-1.06a.75.75 0 1 0-1.061 1.06l1.06 1.06Z" />
</svg>
</button>
</li>
</ul>
</div>
</div>
Expand All @@ -103,7 +134,7 @@ defmodule ErrorTracker.Web.Layouts do
<li>
<a
href={@to}
class="whitespace-nowrap flex-0 block py-2 px-3 rounded-lg text-white hover:text-white hover:bg-gray-700 md:hover:bg-transparent md:border-0 md:hover:text-sky-500"
class="whitespace-nowrap flex-0 block py-2 px-3 rounded-lg text-white light:text-gray-900 hover:text-white light:hover:text-gray-900 hover:bg-gray-700 light:hover:bg-gray-200 md:hover:bg-transparent md:border-0 md:hover:text-sky-500"
{@rest}
>
{render_slot(@inner_block)}
Expand Down
2 changes: 1 addition & 1 deletion lib/error_tracker/web/components/layouts/root.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
</script>
</head>

<body class="bg-gray-800 text-white">
<body id="body" class="bg-gray-800 text-white" phx-hook="ThemeInit">
{@inner_content}
</body>
</html>
20 changes: 10 additions & 10 deletions lib/error_tracker/web/live/dashboard.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,28 @@
value={@search_form[:reason].value}
type="text"
placeholder="Error"
class="border text-sm rounded-lg block p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
class="border text-sm rounded-lg block p-2.5 bg-gray-700 light:bg-white border-gray-600 light:border-gray-300 placeholder-gray-400 text-white light:text-gray-900 focus:ring-blue-500 focus:border-blue-500"
phx-debounce
/>
<input
name={@search_form[:source_line].name}
value={@search_form[:source_line].value}
type="text"
placeholder="Source line"
class="border text-sm rounded-lg block p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
class="border text-sm rounded-lg block p-2.5 bg-gray-700 light:bg-white border-gray-600 light:border-gray-300 placeholder-gray-400 text-white light:text-gray-900 focus:ring-blue-500 focus:border-blue-500"
phx-debounce
/>
<input
name={@search_form[:source_function].name}
value={@search_form[:source_function].value}
type="text"
placeholder="Source function"
class="border text-sm rounded-lg block p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
class="border text-sm rounded-lg block p-2.5 bg-gray-700 light:bg-white border-gray-600 light:border-gray-300 placeholder-gray-400 text-white light:text-gray-900 focus:ring-blue-500 focus:border-blue-500"
phx-debounce
/>
<select
name={@search_form[:status].name}
class="border text-sm rounded-lg block p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500"
class="border text-sm rounded-lg block p-2.5 bg-gray-700 light:bg-white border-gray-600 light:border-gray-300 placeholder-gray-400 text-white light:text-gray-900 focus:ring-blue-500 focus:border-blue-500"
>
<option value="" selected={@search_form[:status].value == ""}>All</option>
<option value="unresolved" selected={@search_form[:status].value == "unresolved"}>
Expand All @@ -42,9 +42,9 @@
</select>
</.form>

<div class="relative overflow-x-auto shadow-md sm:rounded-lg ring-1 ring-gray-900">
<table class="w-full text-sm text-left rtl:text-right text-gray-400 table-fixed">
<thead class="text-xs uppercase bg-gray-900">
<div class="relative overflow-x-auto shadow-md sm:rounded-lg ring-1 ring-gray-900 light:ring-gray-200">
<table class="w-full text-sm text-left rtl:text-right text-gray-400 light:text-gray-500 table-fixed">
<thead class="text-xs uppercase bg-gray-900 light:bg-gray-100">
<tr>
<th scope="col" class="px-4 pr-2 w-72">Error</th>
<th scope="col" class="px-4 py-3 w-72">Occurrences</th>
Expand All @@ -60,9 +60,9 @@
</tr>
<tr
:for={error <- @errors}
class="border-b bg-gray-400/10 border-y border-gray-900 hover:bg-gray-800/60 last-of-type:border-b-0"
class="border-b bg-gray-400/10 light:bg-gray-50 border-y border-gray-900 light:border-gray-200 hover:bg-gray-800/60 light:hover:bg-gray-100 last-of-type:border-b-0"
>
<td scope="row" class="px-4 py-4 font-medium text-white relative">
<td scope="row" class="px-4 py-4 font-medium text-white light:text-gray-900 relative">
<.link navigate={error_path(@socket, error, @search)} class="absolute inset-1">
<span class="sr-only">({sanitize_module(error.kind)}) {error.reason}</span>
</.link>
Expand All @@ -71,7 +71,7 @@
</p>
<p
:if={ErrorTracker.Error.has_source_info?(error)}
class="whitespace-nowrap text-ellipsis overflow-hidden font-normal text-gray-400"
class="whitespace-nowrap text-ellipsis overflow-hidden font-normal text-gray-400 light:text-gray-500"
>
{sanitize_module(error.source_function)}
<br />
Expand Down
Loading
Loading