From 4bc2cdd65397b7f56c534251dae715f69baa535d Mon Sep 17 00:00:00 2001 From: Gopi Date: Sun, 20 Jul 2025 02:49:04 -0700 Subject: [PATCH] feat(ui): make ngrok URL clickable with copy button in Dashboard (#422) Co-authored-by: Gopi Kori --- .../Settings/DashboardSettingsView.swift | 55 +++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/mac/VibeTunnel/Presentation/Views/Settings/DashboardSettingsView.swift b/mac/VibeTunnel/Presentation/Views/Settings/DashboardSettingsView.swift index 27cf5cf2..bf57b632 100644 --- a/mac/VibeTunnel/Presentation/Views/Settings/DashboardSettingsView.swift +++ b/mac/VibeTunnel/Presentation/Views/Settings/DashboardSettingsView.swift @@ -441,10 +441,20 @@ private struct RemoteAccessStatusSection: View { .font(.system(size: 10)) Text("ngrok") .font(.callout) - Text("(\(status.publicUrl.replacingOccurrences(of: "https://", with: "")))") - .font(.caption) - .foregroundColor(.secondary) - .lineLimit(1) + + if let url = URL(string: status.publicUrl) { + Link(status.publicUrl, destination: url) + .font(.caption) + .foregroundStyle(.blue) + .lineLimit(1) + } else { + Text(status.publicUrl) + .font(.caption) + .foregroundColor(.secondary) + .lineLimit(1) + } + + NgrokURLCopyButton(url: status.publicUrl) } else { Image(systemName: "circle") .foregroundColor(.gray) @@ -495,6 +505,43 @@ private struct RemoteAccessStatusSection: View { } } +// MARK: - Ngrok URL Copy Button + +private struct NgrokURLCopyButton: View { + let url: String + @State private var showCopiedFeedback = false + @State private var feedbackTask: DispatchWorkItem? + + var body: some View { + Button(action: { + NSPasteboard.general.clearContents() + NSPasteboard.general.setString(url, forType: .string) + + // Cancel previous timer if exists + feedbackTask?.cancel() + + withAnimation { + showCopiedFeedback = true + } + + // Create new timer + let task = DispatchWorkItem { + withAnimation { + showCopiedFeedback = false + } + } + feedbackTask = task + DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: task) + }, label: { + Image(systemName: showCopiedFeedback ? "checkmark" : "doc.on.doc") + .foregroundColor(showCopiedFeedback ? .green : .accentColor) + }) + .buttonStyle(.borderless) + .help("Copy URL") + .font(.caption) + } +} + // MARK: - Previews #Preview("Dashboard Settings") {