build share count

This commit is contained in:
2026-03-28 22:02:37 -05:00
parent d1bfba89a8
commit d3fb42e8ed
6 changed files with 36 additions and 4 deletions

View File

@@ -11,6 +11,7 @@ export interface Meme {
parent_id: string | null;
collection_id: number | null;
ocr_text: string | null;
share_count: number;
created_at: string;
tags: string[];
children?: Meme[];
@@ -96,6 +97,10 @@ export const api = {
});
},
share(id: string): Promise<{ share_count: number }> {
return apiFetch(`/api/memes/${id}/share`, { method: 'POST' });
},
move(id: string, collection_id: number): Promise<Meme> {
return apiFetch(`/api/memes/${id}/collection`, {
method: 'PUT',

View File

@@ -1,5 +1,5 @@
import { useState } from 'react';
import { X, Minimize2, Trash2, Edit2, Check, Layers, FolderOpen, Inbox, ScanText, ChevronDown, ChevronUp, ExternalLink, Image, Info } from 'lucide-react';
import { X, Minimize2, Trash2, Edit2, Check, Layers, FolderOpen, Inbox, ScanText, ChevronDown, ChevronUp, ExternalLink, Image, Info, Share2 } from 'lucide-react';
import { useMeme, useDeleteMeme, useUpdateMeme, useMoveMeme, useCollections } from '../hooks/useMemes';
import { useAuth } from '../hooks/useAuth';
import { SharePanel } from './SharePanel';
@@ -221,7 +221,7 @@ export function MemeDetail({ memeId, onClose }: Props) {
{displayMeme && (
<section>
<h3 className="text-xs font-semibold text-zinc-500 uppercase tracking-wider mb-2">Share</h3>
<SharePanel meme={displayMeme} />
<SharePanel meme={displayMeme} onShared={refetch} />
</section>
)}
@@ -333,6 +333,13 @@ export function MemeDetail({ memeId, onClose }: Props) {
<dt className="text-zinc-500">Type</dt>
<dd className="text-zinc-300">{meme.mime_type.replace('image/', '').replace('video/', '')}</dd>
</div>
<div className="flex justify-between">
<dt className="text-zinc-500">Shared</dt>
<dd className="text-zinc-300 flex items-center gap-1">
<Share2 size={11} className="text-zinc-500" />
{meme.share_count ?? 0} time{(meme.share_count ?? 0) !== 1 ? 's' : ''}
</dd>
</div>
<div className="flex justify-between">
<dt className="text-zinc-500">Uploaded</dt>
<dd className="text-zinc-300 text-right text-xs">{formatDate(meme.created_at)}</dd>

View File

@@ -5,19 +5,24 @@ import { api } from '../api/client';
interface Props {
meme: Meme;
onShared?: () => void; // called after a share is recorded so parent can refetch
}
export function SharePanel({ meme }: Props) {
export function SharePanel({ meme, onShared }: Props) {
const [copied, setCopied] = useState(false);
// /m/:id gives crawlers OG meta tags so SMS/Telegram show a rich preview card
const shareUrl = `${window.location.origin}/m/${meme.id}`;
const imageUrl = `${window.location.origin}${api.imageUrl(meme.file_path)}`;
function recordShare() {
api.memes.share(meme.id).then(() => onShared?.()).catch(() => {});
}
async function copyLink() {
await navigator.clipboard.writeText(shareUrl);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
recordShare();
}
const telegramUrl = `https://t.me/share/url?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(meme.title)}`;
@@ -42,6 +47,7 @@ export function SharePanel({ meme }: Props) {
href={telegramUrl}
target="_blank"
rel="noopener noreferrer"
onClick={recordShare}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-[#229ED9]/20 hover:bg-[#229ED9]/30 text-[#229ED9] text-sm font-medium transition-colors"
title="Share on Telegram"
>
@@ -51,6 +57,7 @@ export function SharePanel({ meme }: Props) {
<a
href={smsUrl}
onClick={recordShare}
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-green-900/30 hover:bg-green-900/50 text-green-400 text-sm font-medium transition-colors"
title="Share via SMS"
>