@@ -26,6 +26,25 @@ templ OwnerKeysPageTempl(campaignID string, keys []APIKey, stats *APIStats, csrf
2626 </a >
2727 </div >
2828
29+ // Campaign ID for Foundry VTT module configuration.
30+ <div class =" card p-4 flex items-center justify-between gap-4" >
31+ <div class =" min-w-0" >
32+ <p class =" text-sm font-medium text-fg" >Campaign ID</p >
33+ <p class =" text-xs text-fg-muted" >Use this when configuring the Foundry VTT module.</p >
34+ </div >
35+ <div class =" flex items-center gap-2 shrink-0" >
36+ <code class =" px-3 py-1.5 bg-surface-alt rounded text-sm font-mono text-fg select-all" id =" campaign-id-value" >{ campaignID }</code >
37+ <button
38+ type =" button"
39+ onclick =" navigator.clipboard.writeText(document.getElementById('campaign-id-value').textContent.trim()).then(() => { this.innerHTML = '<i class=\'fa-solid fa-check text-emerald-500\'></i>'; setTimeout(() => { this.innerHTML = '<i class=\'fa-solid fa-copy\'></i>'; }, 2000); })"
40+ class =" btn-secondary text-sm px-3 py-2 shrink-0"
41+ title =" Copy to clipboard"
42+ >
43+ <i class =" fa-solid fa-copy" ></i >
44+ </button >
45+ </div >
46+ </div >
47+
2948 // Quick stats.
3049 if stats != nil {
3150 <div class =" grid grid-cols-2 md:grid-cols-4 gap-4" >
@@ -69,6 +88,8 @@ templ OwnerKeysPageTempl(campaignID string, keys []APIKey, stats *APIStats, csrf
6988 method =" POST"
7089 action ={ templ.SafeURL (fmt.Sprintf (" /campaigns/%s /api-keys" , campaignID)) }
7190 hx-post ={ fmt.Sprintf (" /campaigns/%s /api-keys" , campaignID) }
91+ hx-target =" #main-content"
92+ hx-swap =" innerHTML show:window:top"
7293 class =" space-y-4"
7394 >
7495 <input type =" hidden" name =" csrf_token" value ={ csrfToken }/>
@@ -193,56 +214,67 @@ templ ownerKeyRow(campaignID string, k APIKey, csrfToken string) {
193214 </div >
194215}
195216
196- // KeyCreatedTempl renders the one-time key display after creation.
217+ // KeyCreatedTempl renders the one-time key display after creation (full page) .
197218templ KeyCreatedTempl (campaignID string , result *CreateAPIKeyResult, csrfToken string ) {
198219 @ layouts.App (" Key Created" ) {
199- <div class =" max-w-2xl mx-auto space-y-6" >
200- <div class =" card p-6" >
201- <div class =" flex items-center gap-3 mb-4" >
202- <span class =" w-10 h-10 rounded-lg bg-emerald-100 dark:bg-emerald-900/30 flex items-center justify-center" >
203- <i class =" fa-solid fa-check text-emerald-600 dark:text-emerald-400" ></i >
204- </span >
205- <div >
206- <h1 class =" text-xl font-bold text-fg" >API Key Created</h1 >
207- <p class =" text-sm text-fg-secondary" >{ result.Key .Name }</p >
208- </div >
209- </div >
220+ @ keyCreatedContent (campaignID, result)
221+ }
222+ }
210223
211- <div class =" bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg p-4 mb-4" >
212- <p class =" text-sm font-semibold text-amber-700 dark:text-amber-400 mb-2" >
213- <i class =" fa-solid fa-triangle-exclamation mr-1" ></i >
214- Copy this key now — it will not be shown again!
215- </p >
216- <div class =" flex items-center gap-2" >
217- <code class =" flex-1 p-3 bg-white dark:bg-gray-900 rounded text-sm font-mono text-fg break-all select-all" id =" api-key-value" >
218- { result.RawKey }
219- </code >
220- <button
221- onclick =" navigator.clipboard.writeText(document.getElementById('api-key-value').textContent.trim())"
222- class =" btn-secondary text-sm shrink-0"
223- >
224- <i class =" fa-solid fa-copy mr-1" ></i > Copy
225- </button >
226- </div >
227- </div >
224+ // KeyCreatedFragmentTempl renders the one-time key display as an HTMX fragment
225+ // (no layout wrapper) for inline replacement after form submission.
226+ templ KeyCreatedFragmentTempl (campaignID string , result *CreateAPIKeyResult) {
227+ @ keyCreatedContent (campaignID, result)
228+ }
228229
229- <div class =" text-sm text-fg-secondary space-y-1" >
230- <p ><strong >Prefix:</strong > <code class =" text-xs font-mono" >{ result.Key .KeyPrefix }</code ></p >
231- <p ><strong >Permissions:</strong > { formatPermissions (result.Key .Permissions ) }</p >
232- <p ><strong >Rate Limit:</strong > { fmt.Sprintf (" %d " , result.Key .RateLimit ) } req/min</p >
233- if result.Key .ExpiresAt != nil {
234- <p ><strong >Expires:</strong > { result.Key .ExpiresAt .Format (" Jan 2, 2006" ) }</p >
235- }
230+ // keyCreatedContent is the inner content shared by full-page and fragment variants.
231+ templ keyCreatedContent (campaignID string , result *CreateAPIKeyResult) {
232+ <div class =" max-w-2xl mx-auto space-y-6" >
233+ <div class =" card p-6" >
234+ <div class =" flex items-center gap-3 mb-4" >
235+ <span class =" w-10 h-10 rounded-lg bg-emerald-100 dark:bg-emerald-900/30 flex items-center justify-center" >
236+ <i class =" fa-solid fa-check text-emerald-600 dark:text-emerald-400" ></i >
237+ </span >
238+ <div >
239+ <h1 class =" text-xl font-bold text-fg" >API Key Created</h1 >
240+ <p class =" text-sm text-fg-secondary" >{ result.Key .Name }</p >
236241 </div >
242+ </div >
237243
238- <div class =" mt-6" >
239- <a href ={ templ.SafeURL (fmt.Sprintf (" /campaigns/%s /api-keys" , campaignID)) } class =" btn-primary text-sm" >
240- Back to API Keys
241- </a >
244+ <div class =" bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg p-4 mb-4" >
245+ <p class =" text-sm font-semibold text-amber-700 dark:text-amber-400 mb-2" >
246+ <i class =" fa-solid fa-triangle-exclamation mr-1" ></i >
247+ Copy this key now — it will not be shown again!
248+ </p >
249+ <div class =" flex items-center gap-2" >
250+ <code class =" flex-1 p-3 bg-white dark:bg-gray-900 rounded text-sm font-mono text-fg break-all select-all" id =" api-key-value" >
251+ { result.RawKey }
252+ </code >
253+ <button
254+ onclick =" navigator.clipboard.writeText(document.getElementById('api-key-value').textContent.trim())"
255+ class =" btn-secondary text-sm shrink-0"
256+ >
257+ <i class =" fa-solid fa-copy mr-1" ></i > Copy
258+ </button >
242259 </div >
243260 </div >
261+
262+ <div class =" text-sm text-fg-secondary space-y-1" >
263+ <p ><strong >Prefix:</strong > <code class =" text-xs font-mono" >{ result.Key .KeyPrefix }</code ></p >
264+ <p ><strong >Permissions:</strong > { formatPermissions (result.Key .Permissions ) }</p >
265+ <p ><strong >Rate Limit:</strong > { fmt.Sprintf (" %d " , result.Key .RateLimit ) } req/min</p >
266+ if result.Key .ExpiresAt != nil {
267+ <p ><strong >Expires:</strong > { result.Key .ExpiresAt .Format (" Jan 2, 2006" ) }</p >
268+ }
269+ </div >
270+
271+ <div class =" mt-6" >
272+ <a href ={ templ.SafeURL (fmt.Sprintf (" /campaigns/%s /api-keys" , campaignID)) } class =" btn-primary text-sm" >
273+ Back to API Keys
274+ </a >
275+ </div >
244276 </div >
245- }
277+ </ div >
246278}
247279
248280// formatPermissions formats permission list for display.
0 commit comments