Final parity push (web-tier only): - REST API write + token auth: POST /api/tokens (issue a personal_access_token for the session user), Bearer auth via src/lib/api-auth.ts, POST /api/articles/[slug]/comment, GET/DELETE /api/me/tokens, full tickets API (/api/tickets +[id] +[id]/reply), radio current-dj/points/points-leaderboard/ embed-config + POST shouts, and a real-time /api/radio/stream (SSE). 31 public API routes total. - Pages: /draw-badge (buy a custom profile badge → credits + RCON), /me dashboard (stats + online friends + referral claim). Wired into the nav. - HTML sanitisation (sanitize-html) — the HTMLPurifier equivalent — applied to writeable boxes + article bodies before dangerouslySetInnerHTML. - "Dusk" dark theme preset + a default-dark site option honoured by the no-flash boot script. Verified live (prod, amx_test): token issue → Bearer endpoint 200, no-token 401; /api/me/tokens lists it; current-dj/leaderboard JSON; /me + /draw-badge 200; reverted the test user + tokens. tsc 0, vitest 49/49, next build 0.
45 lines
1.3 KiB
TypeScript
45 lines
1.3 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
|
|
/**
|
|
* Copies the user's referral link to the clipboard. Mirrors AtomCMS's
|
|
* copyCode() helper on the /me page, but resolves the link to an absolute URL
|
|
* (the server only knows a relative path, so we prepend the current origin).
|
|
*/
|
|
export default function CopyReferralButton({ value }: { value: string }) {
|
|
const [copied, setCopied] = useState(false);
|
|
|
|
async function copy() {
|
|
const absolute =
|
|
typeof window !== "undefined" && value.startsWith("/")
|
|
? `${window.location.origin}${value}`
|
|
: value;
|
|
try {
|
|
await navigator.clipboard.writeText(absolute);
|
|
} catch {
|
|
// Older browsers / insecure contexts: fall back to a hidden textarea.
|
|
const ta = document.createElement("textarea");
|
|
ta.value = absolute;
|
|
ta.style.position = "fixed";
|
|
ta.style.opacity = "0";
|
|
document.body.appendChild(ta);
|
|
ta.select();
|
|
try {
|
|
document.execCommand("copy");
|
|
} catch {
|
|
/* give up silently */
|
|
}
|
|
document.body.removeChild(ta);
|
|
}
|
|
setCopied(true);
|
|
setTimeout(() => setCopied(false), 2000);
|
|
}
|
|
|
|
return (
|
|
<button type="button" className="btn btn-secondary" onClick={copy}>
|
|
{copied ? "Copied!" : "Copy link"}
|
|
</button>
|
|
);
|
|
}
|