From ed7fcc8680ff3ccf09543d382bc7ee11b0bc447b Mon Sep 17 00:00:00 2001 From: Echo Date: Mon, 17 Nov 2025 17:37:38 -0500 Subject: [PATCH] modal refresh (#636) * modal refresh * fix up yields and custom injections --- app/assets/stylesheets/application.css | 4 - app/helpers/application_helper.rb | 8 ++ app/javascript/application.js | 50 +------ .../api_key_rotation_controller.js | 133 ++++++++---------- .../controllers/modal_controller.js | 34 +++++ app/views/layouts/application.html.erb | 46 +++--- app/views/shared/_modal.html.erb | 63 +++++++++ app/views/shared/_nav.html.erb | 1 + app/views/static_pages/index.html.erb | 3 - app/views/users/edit.html.erb | 44 +++++- public/images/down-arrow.svg | 6 - 11 files changed, 234 insertions(+), 158 deletions(-) create mode 100644 app/javascript/controllers/modal_controller.js create mode 100644 app/views/shared/_modal.html.erb delete mode 100644 public/images/down-arrow.svg diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index f2ff367..601d202 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -227,10 +227,6 @@ select { animation: bounce 1s infinite; } -#scroll-arrow { - transition: transform 1s ease-in-out; -} - @keyframes bounce { 0%, 100% { diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 8920979..1b68a93 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -181,4 +181,12 @@ module ApplicationHelper else language.capitalize end end + + def modal_open_button(modal_id, text, **options) + button_tag text, { + type: "button", + data: { action: "click->modal#open" }, + onclick: "document.getElementById('#{modal_id}').querySelector('[data-controller=\"modal\"]').dispatchEvent(new CustomEvent('modal:open', { bubbles: true }))" + }.merge(options) + end end diff --git a/app/javascript/application.js b/app/javascript/application.js index f871ba0..9bad551 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -17,42 +17,11 @@ function setupCurrentlyHacking() { function outta() { // we should figure out a better way of doing this rather than this shit ass way, but it works for now const modal = document.getElementById('logout-modal'); - const can = document.getElementById('cancel-logout'); + if (!modal) return; - if (!modal || !can) return; - modal.classList.remove('hidden'); - - function logshow() { - modal.classList.remove('pointer-events-none'); - modal.classList.remove('opacity-0'); - modal.querySelector('.bg-dark').classList.remove('scale-95'); - modal.querySelector('.bg-dark').classList.add('scale-100'); - } - - function logquit() { - modal.classList.add('opacity-0'); - modal.querySelector('.bg-dark').classList.remove('scale-100'); - modal.querySelector('.bg-dark').classList.add('scale-95'); - setTimeout(() => { - modal.classList.add('pointer-events-none'); - }, 300); - } - - window.showLogout = logshow; - - can.addEventListener('click', logquit); - - modal.addEventListener('click', function(e) { - if (e.target === modal) { - logquit(); - } - }); - - document.addEventListener('keydown', function(e) { - if (e.key === 'Escape' && !modal.classList.contains('pointer-events-none')) { - logquit(); - } - }); + window.showLogout = function() { + modal.dispatchEvent(new CustomEvent('modal:open', { bubbles: true })); + }; } function weirdclockthing() { @@ -108,16 +77,6 @@ function weirdclockthing() { } } -function scrollarrow() { - window.addEventListener('scroll', function () { - const arrow = document.getElementById('scroll-arrow'); - arrow.classList.remove('bounce'); - this.setInterval(() => { - arrow.style.transform = 'translateY(200px)'; - }, 100); - }); -} - // Handle both initial page load and subsequent Turbo navigations document.addEventListener('turbo:load', function() { setupCurrentlyHacking(); @@ -133,5 +92,4 @@ document.addEventListener('DOMContentLoaded', function() { setupCurrentlyHacking(); outta(); weirdclockthing(); - scrollarrow(); }); diff --git a/app/javascript/controllers/api_key_rotation_controller.js b/app/javascript/controllers/api_key_rotation_controller.js index 30c69ce..4f571a1 100644 --- a/app/javascript/controllers/api_key_rotation_controller.js +++ b/app/javascript/controllers/api_key_rotation_controller.js @@ -1,98 +1,87 @@ -import { Controller } from "@hotwired/stimulus" +import { Controller } from "@hotwired/stimulus"; export default class extends Controller { - async rotateKey(event) { - event.preventDefault() - - if (!confirm("Are you sure you want to rotate your API key? Your old key will be immediately invalidated and you'll need to update it in all your applications.")) { - return - } + confirm(event) { + event.preventDefault(); + document + .getElementById("api-key-confirm-modal") + .dispatchEvent(new CustomEvent("modal:open", { bubbles: true })); + } - const button = event.currentTarget - const og = button.textContent - button.textContent = "Rotating..." - button.disabled = true + async rotate(event) { + event.preventDefault(); + + const b = event.currentTarget; + const og = b.textContent; + b.textContent = "Rotating..."; + b.disabled = true; try { const r = await fetch("/my/settings/rotate_api_key", { method: "POST", headers: { "Content-Type": "application/json", - "X-CSRF-Token": document.querySelector("[name='csrf-token']").content - } - }) + "X-CSRF-Token": document.querySelector("[name='csrf-token']").content, + }, + }); - const d = await r.json() + const data = await r.json(); - if (r.ok && d.token) { - this.m(d.token) + if (r.ok && data.token) { + this.done(data.token); } else { - alert("Failed to rotate API key: " + (d.error || "Unknown error")) + alert("Failed to rotate API key: " + (data.error || "Unknown error")); } } catch (error) { - console.error("Error rotating API key:", error) - alert("Failed to rotate API key. Please try again.") + console.error("Error rotating API key:", error); + alert("Failed to rotate API key. Please try again."); } finally { - button.textContent = og - button.disabled = false + b.textContent = og; + b.disabled = false; } } - m(token) { - const c = ` -
-
-

🔑 New API Key Generated

-
-
-

- We have gone ahead and invalidated your old API key, here is your new API key. Update your editor configuration with this new key. -

-
- ${token} -
-
-
- - -
-
-
-
- ` + done(token) { + const d = document.getElementById("new-api-key-display"); - document.body.insertAdjacentHTML("beforeend", c) + if (!d) { + console.error("Could not find new-api-key-display element"); + alert("Error displaying new API key. Please refresh and try again."); + return; + } - document.getElementById("copy-api-key").addEventListener("click", () => { - navigator.clipboard.writeText(token).then(() => { - const button = document.getElementById("copy-api-key") - const og = button.textContent - button.textContent = "Copied!" - setTimeout(() => { - button.textContent = og - }, 2000) - }) - }) + d.textContent = token; + d.dataset.token = token; + + document + .getElementById("api-key-confirm-modal") + .dispatchEvent(new CustomEvent("modal:close", { bubbles: true })); - document.getElementById("close-modal").addEventListener("click", this.closeModal) - - document.getElementById("api-key-modal").addEventListener("click", (event) => { - if (event.target.id === "api-key-modal") { - this.closeModal() - } - }) + setTimeout(() => { + document + .getElementById("api-key-success-modal") + .dispatchEvent(new CustomEvent("modal:open", { bubbles: true })); + }, 150); } - closeModal() { - const modal = document.getElementById("api-key-modal") - if (modal) { - modal.remove() + copyKey(event) { + event.preventDefault(); + const d = document.getElementById("new-api-key-display"); + + if (!d) { + console.error("modal issues???"); + return; } + + const t = d.dataset.token; + const b = event.currentTarget; + + navigator.clipboard.writeText(t).then(() => { + const og = b.textContent; + b.textContent = "Copied!"; + setTimeout(() => { + b.textContent = og; + }, 2000); + }); } } diff --git a/app/javascript/controllers/modal_controller.js b/app/javascript/controllers/modal_controller.js new file mode 100644 index 0000000..43eee8e --- /dev/null +++ b/app/javascript/controllers/modal_controller.js @@ -0,0 +1,34 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["content"] + + connect() { + this.element.addEventListener("click", (e) => { + if (e.target === this.element) { + this.close() + } + }) + + this.element.addEventListener("modal:open", () => this.open()) + this.element.addEventListener("modal:close", () => this.close()) + } + + open() { + this.element.classList.remove("hidden") + setTimeout(() => { + this.element.classList.remove("opacity-0", "pointer-events-none") + this.contentTarget.classList.remove("scale-95") + this.contentTarget.classList.add("scale-100") + }, 10) + } + + close() { + this.element.classList.add("opacity-0", "pointer-events-none") + this.contentTarget.classList.remove("scale-100") + this.contentTarget.classList.add("scale-95") + setTimeout(() => { + this.element.classList.add("hidden") + }, 300) + } +} diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index bfaeb3c..304268b 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -231,31 +231,25 @@
- - + <%= render "shared/modal", + modal_id: "logout-modal", + title: "Woah hold on a sec", + description: "You sure you want to sign out? You can sign back in later but that is a bit of a hassle...", + icon_svg: '', + icon_color: "text-primary", + buttons: [ + { + text: "Go back", + class: "border border-gray-600 text-gray-300 hover:bg-darkless", + action: "click->modal#close" + }, + { + text: "Log out now", + class: "bg-primary text-white hover:bg-red-600 font-medium", + form: true, + url: signout_path, + method: "delete" + } + ] %> diff --git a/app/views/shared/_modal.html.erb b/app/views/shared/_modal.html.erb new file mode 100644 index 0000000..ef664d4 --- /dev/null +++ b/app/views/shared/_modal.html.erb @@ -0,0 +1,63 @@ +<% + modal_id ||= "modal-#{SecureRandom.hex(4)}" + icon_svg ||= nil + icon_color ||= "text-primary" + title ||= "Confirm" + description ||= nil + buttons ||= [] + max_width ||= "max-w-md" + custom ||= nil +%> + + diff --git a/app/views/shared/_nav.html.erb b/app/views/shared/_nav.html.erb index 57ecd71..eee3276 100644 --- a/app/views/shared/_nav.html.erb +++ b/app/views/shared/_nav.html.erb @@ -76,6 +76,7 @@
<% if current_user&.trust_level == "red" %>
diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index c457ad5..8ae8a03 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -183,7 +183,7 @@ @@ -550,3 +550,45 @@ <% end %>
+ +
+ <%= render "shared/modal", + modal_id: "api-key-confirm-modal", + title: "Rotate API Key?", + description: "Your old key will be immediately invalidated and you'll need to update it in all your applications.", + icon_svg: '', + icon_color: "text-primary", + buttons: [ + { + text: "Cancel", + class: "border border-gray-600 text-gray-300 hover:bg-darkless", + action: "click->modal#close" + }, + { + text: "Rotate Now", + class: "bg-primary text-white hover:bg-red-600 font-medium", + action: "click->api-key-rotation#rotate" + } + ] %> + + <%= render "shared/modal", + modal_id: "api-key-success-modal", + title: "New API Key Generated", + description: "Your old API key has been invalidated. Update your editor configuration with this new key:", + icon_svg: '', + icon_color: "text-green-500", + max_width: "max-w-lg", + buttons: [ + { + text: "Close", + class: "border border-gray-600 text-gray-300 hover:bg-darkless", + action: "click->modal#close" + }, + { + text: "Copy Key", + class: "bg-primary text-white hover:bg-red-600 font-medium", + action: "click->api-key-rotation#copyKey" + } + ], + custom: '
' %> +
diff --git a/public/images/down-arrow.svg b/public/images/down-arrow.svg deleted file mode 100644 index b7ca0be..0000000 --- a/public/images/down-arrow.svg +++ /dev/null @@ -1,6 +0,0 @@ - - - - - -