package webui import ( "fmt" "github.com/pogo-vcs/pogo/db" "github.com/pogo-vcs/pogo/server/env" "github.com/pogo-vcs/pogo/server/webui/components" "time" ) script inviteScript() { // Handle create invite form submission document.getElementById("createInviteForm").addEventListener("submit", async (e) => { e.preventDefault(); const hours = document.getElementById("hours").value; const errorDiv = document.getElementById("errorMessage"); const successDiv = document.getElementById("successMessage"); try { const formData = new FormData(); formData.append("hours", hours); const response = await fetch("/api/invites/create", { method: "POST", body: formData, credentials: "same-origin" }); if (response.ok) { const data = await response.json(); successDiv.innerHTML = `

Invite Created!

Share this invite URL with the person you want to invite:

${data.invite_url}

This invite expires in ${hours} hours.

`; successDiv.classList.remove("hidden"); errorDiv.classList.add("hidden"); // Refresh the page after a short delay to show the new invite setTimeout(() => window.location.reload(), 1000); } else { const text = await response.text(); errorDiv.textContent = text || "Failed to create invite"; errorDiv.classList.remove("hidden"); successDiv.classList.add("hidden"); } } catch (error) { errorDiv.textContent = error.message || "Network error. Please try again."; errorDiv.classList.remove("hidden"); successDiv.classList.add("hidden"); } }); // Handle copy buttons document.querySelectorAll(".copy-btn").forEach(btn => { btn.addEventListener("click", async () => { const inviteUrl = btn.dataset.inviteUrl; // Helper function to show feedback const showFeedback = (message, isSuccess = true) => { const originalText = btn.textContent; btn.textContent = message; btn.classList.remove("bg-ctp-blue", "hover:bg-ctp-sapphire"); if (isSuccess) { btn.classList.add("bg-ctp-green"); } else { btn.classList.add("bg-ctp-red"); } setTimeout(() => { btn.textContent = originalText; if (isSuccess) { btn.classList.remove("bg-ctp-green"); } else { btn.classList.remove("bg-ctp-red"); } btn.classList.add("bg-ctp-blue", "hover:bg-ctp-sapphire"); }, 2000); }; // Try modern clipboard API first if (navigator.clipboard && navigator.clipboard.writeText) { try { await navigator.clipboard.writeText(inviteUrl); showFeedback("Copied!"); return; } catch (err) { console.warn('Clipboard API failed, trying fallback:', err); } } // Fallback for older browsers or when clipboard API is not available try { const textArea = document.createElement("textarea"); textArea.value = inviteUrl; textArea.style.position = "fixed"; textArea.style.left = "-999999px"; textArea.style.top = "-999999px"; document.body.appendChild(textArea); textArea.focus(); textArea.select(); const successful = document.execCommand("copy"); document.body.removeChild(textArea); if (successful) { showFeedback("Copied!"); } else { showFeedback("Copy failed", false); } } catch (fallbackErr) { console.error('All copy methods failed:', fallbackErr); showFeedback("Copy failed", false); } }); }); // Handle discard buttons document.querySelectorAll(".discard-btn").forEach(btn => { btn.addEventListener("click", async () => { if (!confirm("Are you sure you want to revoke this invitation? This action cannot be undone.")) { return; } const token = btn.dataset.token; try { const formData = new FormData(); formData.append("token", token); const response = await fetch("/api/invites/revoke", { method: "POST", body: formData, credentials: "same-origin" }); if (response.ok) { // Remove the row or reload the page window.location.reload(); } else { const text = await response.text(); alert("Failed to revoke invite: " + (text || "Unknown error")); } } catch (error) { alert(error.message || "Network error. Please try again."); } }); }); } templ Invites() { if !IsLoggedIn(ctx) { @layout("Unauthorized") { @components.Header(nil) @components.Main() {

Unauthorized

You must be logged in to manage invitations.

} } } else { {{ user := GetUser(ctx) }} @layout("User Invitations - Pogo") { @components.Header(user) @components.Main() {

Manage Invitations

Create New Invitation

Your Invitations

if invites, err := db.Q.GetAllInvitesByUser(ctx, user.ID); err == nil { if len(invites) > 0 {
for _, invite := range invites { {{ tokenDisplay := db.EncodeToken(invite.Token) fullToken := db.EncodeToken(invite.Token) if len(tokenDisplay) > 16 { tokenDisplay = tokenDisplay[:16] + "..." } status := "Active" statusClass := "text-ctp-green" usedBy := "-" isActive := true if invite.UsedAt.Valid { status = "Used" statusClass = "text-ctp-blue" isActive = false if invite.UsedByUsername != nil { usedBy = *invite.UsedByUsername } } else if time.Now().After(invite.ExpiresAt.Time) { status = "Expired" statusClass = "text-ctp-red" isActive = false } inviteURL := fmt.Sprintf("%s/register?invite=%s", env.PublicAddress, fullToken) }} }
Token Created Expires Status Used By Actions
{ tokenDisplay } { invite.CreatedAt.Time.Format("Jan 2, 2006 15:04") } { invite.ExpiresAt.Time.Format("Jan 2, 2006 15:04") } { status } { usedBy } if isActive {
} else { - }
} else {

You have not created any invitations yet.

} } else {

Failed to load invitations.

}
} @inviteScript() } } }