feat: copy caption in post actions menu

This commit is contained in:
gosangam
2026-06-08 23:45:01 +05:30
parent 421b41cade
commit cf733062f8
6 changed files with 43 additions and 6 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ export default function HomePage() {
const authed = !loading && !!user;
return (
<main className="flex-1 max-w-2xl w-full mx-auto px-4 sm:px-6 py-16 sm:py-24">
<main className="flex-1 max-w-2xl w-full mx-auto px-4 sm:px-6 py-16 sm:py-24 select-none">
<div className="flex flex-col items-center text-center gap-5">
<span
aria-hidden
+1 -1
View File
@@ -211,7 +211,7 @@ export default function ChantPage() {
const pct = target > 0 ? Math.min(100, (count / target) * 100) : 0;
return (
<main className="flex-1 w-full max-w-lg mx-auto px-4 sm:px-6 py-5 flex flex-col">
<main className="flex-1 w-full max-w-lg mx-auto px-4 sm:px-6 py-5 flex flex-col select-none">
<div className="flex items-center justify-between">
<Link
href="/sadhna-streak"
+1 -1
View File
@@ -114,7 +114,7 @@ export default function SadhnaStreakPage() {
}
return (
<main className="flex-1 w-full max-w-2xl mx-auto px-4 sm:px-6 py-5 flex flex-col gap-5">
<main className="flex-1 w-full max-w-2xl mx-auto px-4 sm:px-6 py-5 flex flex-col gap-5 select-none">
<header className="text-center space-y-0.5">
<h1 className="font-(family-name:--font-title) text-2xl sm:text-3xl font-semibold text-foreground">
Daily Sādhana
+1 -1
View File
@@ -55,7 +55,7 @@ export function RewardCelebration({
initial={{ scale: 0.85, opacity: 0, y: 16 }}
animate={{ scale: 1, opacity: 1, y: 0 }}
transition={{ type: "spring", stiffness: 220, damping: 20 }}
className="relative z-10 w-full max-w-md max-h-[90vh] overflow-y-auto rounded-3xl border border-border bg-card p-6 text-center shadow-2xl"
className="relative z-10 w-full max-w-md max-h-[90vh] overflow-y-auto rounded-3xl border border-border bg-card p-6 text-center shadow-2xl select-none"
>
<p className="text-xs font-semibold uppercase tracking-wider text-primary">
A gift for your sādhana
+1 -1
View File
@@ -124,7 +124,7 @@ function RewardDetail({
className="absolute inset-0 bg-black/60 backdrop-blur-sm"
onClick={onClose}
/>
<div className="relative z-10 w-full max-w-md max-h-[90vh] overflow-y-auto rounded-3xl border border-border bg-card p-6 text-center shadow-2xl">
<div className="relative z-10 w-full max-w-md max-h-[90vh] overflow-y-auto rounded-3xl border border-border bg-card p-6 text-center shadow-2xl select-none">
<div className="relative mx-auto mb-4 flex size-40 items-center justify-center">
<div className="absolute inset-0 rounded-full bg-primary/10 blur-2xl" />
<Image
+38 -1
View File
@@ -21,7 +21,7 @@
import { useCallback, useEffect, useRef, useState } from "react";
import { createPortal } from "react-dom";
import { Ban, Flag, MoreVertical, Trash2 } from "lucide-react";
import { Ban, Copy, Flag, MoreVertical, Trash2 } from "lucide-react";
import { toast } from "sonner";
import {
Drawer,
@@ -41,6 +41,8 @@ import { useAuthGate } from "@/components/auth/auth-gate";
import { useMediaQuery } from "@/components/selection-toolbar/shared";
import { ConfirmModal } from "@/components/ui/confirm-modal";
import { ReportDialog, type ReportOption } from "@/components/social/report-dialog";
import { createShortLink } from "@/lib/share";
import { structuredReferenceForPost } from "@/lib/source-reference";
// Post report categories — mirrors the BFF allow-list (postReportCategories).
const POST_REPORT_OPTIONS: ReportOption[] = [
@@ -147,10 +149,45 @@ export function PostCardMenu({
};
}, [open, isMobile]);
// Copy the post's caption + source citation + a (short) link to the post —
// the same payload the share sheet / footer produce. Available to every
// viewer.
async function copyCaption() {
setOpen(false);
try {
const origin = window.location.origin;
let url = "";
if (authorHandle) {
const longPath = `/u/${encodeURIComponent(authorHandle)}/posts#post-${post.id}`;
const code = await createShortLink(longPath);
url = code
? new URL(`/share/${code}`, origin).toString()
: new URL(longPath, origin).toString();
}
const ref = structuredReferenceForPost(post.source_url, post.source_title);
const lines: string[] = [];
if (post.caption) lines.push(`“${post.caption}”`, "");
lines.push("Source:", ref.title);
if (ref.detail) lines.push(ref.detail);
if (url) lines.push(url);
await navigator.clipboard.writeText(lines.join("\n"));
toast.success("Caption copied");
} catch {
toast.error("Could not copy");
}
}
// Build the items list AFTER all hooks have run so a conditional
// early-return doesn't break the rules-of-hooks contract on
// re-renders where viewer state changes.
const items: MenuItem[] = [];
// Copy caption — first item for everyone (owner and non-owner alike).
items.push({
key: "copy",
label: "Copy caption",
icon: <Copy className="size-4" />,
onClick: copyCaption,
});
if (isOwner) {
if (onDelete) {
items.push({