social share up and running
This commit is contained in:
1 parent
000ebd388a
commit
b8779e5a35
22 files changed
+1935
-41
No files matched your search
@@ -324,6 +324,11 @@ export function AdminDashboardComponent() {
|
||||
</span>
|
||||
<span>•</span>
|
||||
<span>Прегледи: {article.views}</span>
|
||||
<span>•</span>
|
||||
<span>
|
||||
Shares: {(article.facebookShares || 0) + (article.twitterShares || 0) +
|
||||
(article.whatsappShares || 0) + (article.telegramShares || 0)}
|
||||
</span>
|
||||
</div>
|
||||
{article.excerpt && (
|
||||
<p className="mt-2 text-sm text-muted-foreground line-clamp-2">
|
||||
@@ -414,9 +419,142 @@ export function AdminDashboardComponent() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
|
||||
{/* Confirmation Dialog */}
|
||||
{/* Social Media Analytics - Only show when not viewing archived items */}
|
||||
{!showArchived && (
|
||||
<div className="mt-8">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Social Media Analytics</CardTitle>
|
||||
<CardDescription>Share statistics for articles</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="space-y-6">
|
||||
{/* Total Shares Summary */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-5 gap-4">
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-2xl font-bold">
|
||||
{articles.reduce((sum, a) => sum + (a.facebookShares || 0), 0) || 0}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">Facebook Shares</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-2xl font-bold">
|
||||
{articles.reduce((sum, a) => sum + (a.twitterShares || 0), 0) || 0}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">Twitter Shares</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-2xl font-bold">
|
||||
{articles.reduce((sum, a) => sum + (a.whatsappShares || 0), 0) || 0}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">WhatsApp Shares</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-2xl font-bold">
|
||||
{articles.reduce((sum, a) => sum + (a.telegramShares || 0), 0) || 0}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">Telegram Shares</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardContent className="pt-6">
|
||||
<div className="text-2xl font-bold">
|
||||
{articles.reduce((sum, a) =>
|
||||
sum + (a.facebookShares || 0) + (a.twitterShares || 0) +
|
||||
(a.whatsappShares || 0) + (a.telegramShares || 0), 0) || 0}
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">Total Shares</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Top Shared Articles */}
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold mb-4">Top Shared Articles</h3>
|
||||
<div className="space-y-3">
|
||||
{articles
|
||||
.filter(a => a.status === 'published')
|
||||
.sort((a, b) => {
|
||||
const aShares = (a.facebookShares || 0) + (a.twitterShares || 0) +
|
||||
(a.whatsappShares || 0) + (a.telegramShares || 0);
|
||||
const bShares = (b.facebookShares || 0) + (b.twitterShares || 0) +
|
||||
(b.whatsappShares || 0) + (b.telegramShares || 0);
|
||||
return bShares - aShares;
|
||||
})
|
||||
.slice(0, 5)
|
||||
.map((article) => {
|
||||
const totalShares = (article.facebookShares || 0) +
|
||||
(article.twitterShares || 0) +
|
||||
(article.whatsappShares || 0) +
|
||||
(article.telegramShares || 0);
|
||||
const shareRate = article.views > 0
|
||||
? ((totalShares / article.views) * 100).toFixed(2)
|
||||
: '0.00';
|
||||
|
||||
return (
|
||||
<div
|
||||
key={article.id}
|
||||
className="p-4 border rounded-lg hover:bg-accent/50 transition-colors"
|
||||
>
|
||||
<div className="flex items-start justify-between">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Link
|
||||
to="/articles/$id"
|
||||
params={{ id: article.id }}
|
||||
className="font-medium hover:text-primary hover:underline"
|
||||
>
|
||||
{article.title}
|
||||
</Link>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 text-sm text-muted-foreground">
|
||||
<span>Views: {article.views}</span>
|
||||
<span>•</span>
|
||||
<span>Shares: {totalShares}</span>
|
||||
<span>•</span>
|
||||
<span>Share Rate: {shareRate}%</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-4 mt-2 text-xs">
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="w-2 h-2 bg-blue-500 rounded-full"></span>
|
||||
Facebook: {article.facebookShares || 0}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="w-2 h-2 bg-sky-500 rounded-full"></span>
|
||||
Twitter: {article.twitterShares || 0}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="w-2 h-2 bg-green-500 rounded-full"></span>
|
||||
WhatsApp: {article.whatsappShares || 0}
|
||||
</span>
|
||||
<span className="flex items-center gap-1">
|
||||
<span className="w-2 h-2 bg-cyan-500 rounded-full"></span>
|
||||
Telegram: {article.telegramShares || 0}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Confirmation Dialog */}
|
||||
{showConfirmDialog && itemToDelete && (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<div className="bg-white p-6 rounded-lg max-w-md w-full mx-4">
|
||||
|
||||
@@ -7,6 +7,7 @@ import { YouTubeEmbed } from '@/components/ui/youtube-embed'
|
||||
import { extractYouTubeVideoId, getVideoPositionClasses } from '@/lib/video-utils'
|
||||
import { CommentSection } from '@/components/features/comments/CommentSection'
|
||||
import { ReactionButtons } from '@/components/features/comments/ReactionButtons'
|
||||
import { SocialShareButtons } from '@/components/features/social-share'
|
||||
|
||||
export function ArticleDetailComponent({ id }: { id: string }) {
|
||||
const { data, isLoading, error } = useQuery({
|
||||
@@ -69,9 +70,21 @@ export function ArticleDetailComponent({ id }: { id: string }) {
|
||||
<span>By {data.author.name}</span>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{data.featuredImage && data.imagePosition !== 'none' && (
|
||||
{/* Social Sharing */}
|
||||
<div className="mb-8">
|
||||
<SocialShareButtons
|
||||
articleId={data.id}
|
||||
title={data.title}
|
||||
url={typeof window !== 'undefined' ? window.location.href : ''}
|
||||
excerpt={data.excerpt}
|
||||
image={data.featuredImage}
|
||||
tags={data.tags}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{data.featuredImage && data.imagePosition !== 'none' && (
|
||||
<div className={`relative mb-4 ${
|
||||
data.imagePosition === 'top'
|
||||
? 'w-full mb-8'
|
||||
@@ -165,10 +178,26 @@ export function ArticleDetailComponent({ id }: { id: string }) {
|
||||
>
|
||||
{data.content}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{data.tags && Array.isArray(data.tags) && data.tags.length > 0 && (
|
||||
{/* Social Sharing Footer */}
|
||||
<div className="mt-8 pt-8 border-t">
|
||||
<div className="flex flex-col items-center">
|
||||
<p className="text-sm text-muted-foreground mb-4">Share this article:</p>
|
||||
<SocialShareButtons
|
||||
articleId={data.id}
|
||||
title={data.title}
|
||||
url={typeof window !== 'undefined' ? window.location.href : ''}
|
||||
excerpt={data.excerpt}
|
||||
image={data.featuredImage}
|
||||
tags={data.tags}
|
||||
variant="footer"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{data.tags && Array.isArray(data.tags) && data.tags.length > 0 && (
|
||||
<div className="mt-8 pt-8 border-t">
|
||||
<h3 className="text-sm font-semibold mb-4 text-muted-foreground">Tags</h3>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import * as api from '@/lib/api'
|
||||
import { SocialShareButtons } from '@/components/features/social-share'
|
||||
|
||||
export function ArticlesComponent() {
|
||||
const { data, isLoading, error } = useQuery({
|
||||
@@ -31,34 +32,52 @@ export function ArticlesComponent() {
|
||||
<p className="text-muted-foreground">Latest news and articles</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{data?.data.map((article) => (
|
||||
<Link
|
||||
key={article.id}
|
||||
to={`/articles/${article.id}`}
|
||||
className="p-6 rounded-xl border bg-card hover:shadow-lg transition-shadow cursor-pointer block"
|
||||
>
|
||||
<h2 className="text-xl font-semibold mb-2 line-clamp-2">
|
||||
{article.title}
|
||||
</h2>
|
||||
{article.excerpt && (
|
||||
<p className="text-muted-foreground text-sm mb-4 line-clamp-3">
|
||||
{article.excerpt}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex items-center justify-between text-sm text-muted-foreground">
|
||||
<span>
|
||||
{new Date(article.createdAt).toLocaleDateString('mk-MK', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</span>
|
||||
<span>{article.views} views</span>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{data?.data.map((article) => (
|
||||
<div
|
||||
key={article.id}
|
||||
className="p-6 rounded-xl border bg-card hover:shadow-lg transition-shadow"
|
||||
>
|
||||
<Link
|
||||
to={`/articles/${article.id}`}
|
||||
className="block mb-4"
|
||||
>
|
||||
<h2 className="text-xl font-semibold mb-2 line-clamp-2">
|
||||
{article.title}
|
||||
</h2>
|
||||
{article.excerpt && (
|
||||
<p className="text-muted-foreground text-sm mb-4 line-clamp-3">
|
||||
{article.excerpt}
|
||||
</p>
|
||||
)}
|
||||
</Link>
|
||||
|
||||
<div className="flex items-center justify-between text-sm text-muted-foreground">
|
||||
<div className="flex items-center space-x-4">
|
||||
<span>
|
||||
{new Date(article.createdAt).toLocaleDateString('mk-MK', {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric',
|
||||
})}
|
||||
</span>
|
||||
<span>•</span>
|
||||
<span>{article.views} views</span>
|
||||
</div>
|
||||
|
||||
<SocialShareButtons
|
||||
articleId={article.id}
|
||||
title={article.title}
|
||||
url={`${window.location.origin}/articles/${article.id}`}
|
||||
excerpt={article.excerpt}
|
||||
image={article.featuredImage}
|
||||
tags={article.tags}
|
||||
variant="compact"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{data?.data.length === 0 && (
|
||||
<div className="text-center py-12">
|
||||
|
||||
Reference in new issue
Block a user