- Add 11 missing server action files: badges, bulk-users, catalog, catalog-bc, catalog-items, import-badges, import-furni, multi-account-detect, permissions, rooms, soundtracks - Add missing admin navigation links: tickets, sounds, translations, import, radio sub-pages - Add translation keys for all new navigation items
60 lines
1.7 KiB
TypeScript
60 lines
1.7 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
|
|
/**
|
|
* Read-only copy box for an embed snippet. Renders the code inside a
|
|
* <textarea readOnly> so staff can select/copy it, plus a one-click copy
|
|
* button (with clipboard fallback to selecting the text).
|
|
*/
|
|
export function EmbedSnippet({
|
|
title,
|
|
description,
|
|
snippet,
|
|
}: {
|
|
title: string;
|
|
description?: string;
|
|
snippet: string;
|
|
}) {
|
|
const [copied, setCopied] = useState(false);
|
|
|
|
async function copy() {
|
|
try {
|
|
await navigator.clipboard.writeText(snippet);
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 1500);
|
|
} catch {
|
|
// Clipboard API unavailable (e.g. insecure context) — fall back to
|
|
// selecting the textarea so the user can copy manually.
|
|
const el = document.getElementById(
|
|
`snippet-${title.replace(/\s+/g, "-").toLowerCase()}`,
|
|
) as HTMLTextAreaElement | null;
|
|
el?.select();
|
|
}
|
|
}
|
|
|
|
const id = `snippet-${title.replace(/\s+/g, "-").toLowerCase()}`;
|
|
const lines = snippet.split("\n").length;
|
|
|
|
return (
|
|
<section className="mt-6">
|
|
<div className="flex items-center justify-between gap-4 mb-1">
|
|
<h2 className="m-0 text-base font-bold text-[var(--color-text)]">{title}</h2>
|
|
<button type="button" className="btn btn-primary" onClick={copy}>
|
|
{copied ? "Copied!" : "Copy"}
|
|
</button>
|
|
</div>
|
|
{description ? <p className="text-xs text-gray-500 dark:text-gray-400 mb-3">{description}</p> : null}
|
|
<textarea
|
|
id={id}
|
|
readOnly
|
|
value={snippet}
|
|
rows={Math.min(Math.max(lines + 1, 3), 14)}
|
|
onFocus={(e) => e.currentTarget.select()}
|
|
spellCheck={false}
|
|
className="w-full font-mono text-sm resize-vertical"
|
|
/>
|
|
</section>
|
|
);
|
|
}
|