diff --git a/assets/css/app.css b/assets/css/app.css index 2c921a9..b64e229 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -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; +} diff --git a/assets/js/app.js b/assets/js/app.js index 6e06925..8897c8e 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -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() { @@ -26,6 +47,11 @@ const Hooks = { // Keep the original content if there's an error } } + }, + ThemeInit: { + mounted() { + Theme.init(); + } } }; @@ -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; diff --git a/assets/tailwind.config.js b/assets/tailwind.config.js index 1447c42..bf9177f 100644 --- a/assets/tailwind.config.js +++ b/assets/tailwind.config.js @@ -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 &')) ] } diff --git a/lib/error_tracker/web/components/core_components.ex b/lib/error_tracker/web/components/core_components.ex index 20e27ef..ddbc26d 100644 --- a/lib/error_tracker/web/components/core_components.ex +++ b/lib/error_tracker/web/components/core_components.ex @@ -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} @@ -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) @@ -95,14 +110,14 @@ defmodule ErrorTracker.Web.CoreComponents do
@@ -103,7 +134,7 @@ defmodule ErrorTracker.Web.Layouts do
  • {render_slot(@inner_block)} diff --git a/lib/error_tracker/web/components/layouts/root.html.heex b/lib/error_tracker/web/components/layouts/root.html.heex index 71464d1..6dd30a1 100644 --- a/lib/error_tracker/web/components/layouts/root.html.heex +++ b/lib/error_tracker/web/components/layouts/root.html.heex @@ -25,7 +25,7 @@ - + {@inner_content} diff --git a/lib/error_tracker/web/live/dashboard.html.heex b/lib/error_tracker/web/live/dashboard.html.heex index a40f9a7..5d27078 100644 --- a/lib/error_tracker/web/live/dashboard.html.heex +++ b/lib/error_tracker/web/live/dashboard.html.heex @@ -9,7 +9,7 @@ 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 /> -
    - - +
    +
    + @@ -60,9 +60,9 @@ -
    Error Occurrences
    + <.link navigate={error_path(@socket, error, @search)} class="absolute inset-1"> ({sanitize_module(error.kind)}) {error.reason} @@ -71,7 +71,7 @@

    {sanitize_module(error.source_function)}
    diff --git a/lib/error_tracker/web/live/show.html.heex b/lib/error_tracker/web/live/show.html.heex index 0e02552..b24b22c 100644 --- a/lib/error_tracker/web/live/show.html.heex +++ b/lib/error_tracker/web/live/show.html.heex @@ -5,7 +5,7 @@

    -
    +
    <.section title="Full message"> -
    <%= @occurrence.reason %>
    +
    <%= @occurrence.reason %>
    <.section :if={ErrorTracker.Error.has_source_info?(@error)} title="Source"> -
    +      
             <%= sanitize_module(@error.source_function) %>
             <%= @error.source_line %>
    <.section :if={@occurrence.breadcrumbs != []} title="Bread crumbs"> -
    - +
    +
    Enum.reverse() |> Enum.with_index() } - class="border-b bg-gray-400/10 border-gray-900 last:border-b-0" + class="border-b bg-gray-400/10 light:bg-gray-50 border-gray-900 light:border-gray-200 last:border-b-0" > - - +
    + {length(@occurrence.breadcrumbs) - index}. {breadcrumb} + {breadcrumb} +
    <.section :if={@occurrence.stacktrace.lines != []} title="Stacktrace"> -
    +
    -
    +
    <.section title={"Occurrence (#{@total_occurrences} total)"}>