Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 138 additions & 9 deletions app/a/[slug]/BlogPostClient.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"use client"

import { useState, useEffect, useRef } from "react"
import Link from "next/link"
import { ArrowLeft, Calendar, User } from "lucide-react"
import Image from "next/image"
Expand All @@ -8,6 +9,15 @@ import Footer from "@/components/footer"
import { BlogPost } from "@/lib/blog"

export default function BlogPostPage({ post }: { post: BlogPost }) {
const [copied, setCopied] = useState(false)
const copiedTimeoutRef = useRef<number | null>(null)

useEffect(() => {
return () => {
if (copiedTimeoutRef.current != null) window.clearTimeout(copiedTimeoutRef.current)
}
}, [])

return (
<div className="min-h-screen bg-gradient-to-br from-green-50 via-yellow-50 to-[#FFC517]/10">
{/* Header */}
Expand All @@ -29,15 +39,11 @@ export default function BlogPostPage({ post }: { post: BlogPost }) {
{/* Hero Image */}
{post.image && (
<div className="relative h-64 md:h-96">
<Image
src={
post.image.startsWith("/")
? `${post.image}`
: post.image
}
alt={post.title}
fill
className="object-cover"
<Image
src={post.image}
alt={post.title}
fill
className="object-cover"
priority
/>
<div className="absolute inset-0 bg-gradient-to-t from-black/20 to-transparent" />
Expand Down Expand Up @@ -67,6 +73,129 @@ export default function BlogPostPage({ post }: { post: BlogPost }) {
</div>
</div>

{/* Social Share Buttons */}
<div className="mb-8 pb-8 border-b border-gradient-to-r from-[#228B22]/20 to-[#FFBF00]/20">
<p className="text-sm font-medium text-gray-600 mb-4">Share this article:</p>
<div className="flex flex-wrap items-center gap-3">
{/* Twitter/X Share */}
<button
type="button"
onClick={() => {
const text = encodeURIComponent(post.title)
const url = encodeURIComponent(window.location.href)
const shareWindow = window.open(
`https://twitter.com/intent/tweet?text=${text}&url=${url}`,
'_blank',
'noopener,noreferrer,width=550,height=420'
)
if (!shareWindow) {
alert('Please allow popups to share this article.')
}
}}
className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-gradient-to-r from-[#228B22] to-[#5A981A] hover:from-[#3E921E] hover:to-[#91A511] text-white text-sm font-medium transition-all duration-300 shadow-md hover:shadow-lg transform hover:-translate-y-0.5"
aria-label="Share on Twitter"
>
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" />
</svg>
Twitter
</button>

{/* LinkedIn Share */}
<button
type="button"
onClick={() => {
const url = encodeURIComponent(window.location.href)
const title = encodeURIComponent(post.title)
const summary = encodeURIComponent(post.excerpt || '')
const shareWindow = window.open(
`https://www.linkedin.com/sharing/share-offsite/?url=${url}&title=${title}&summary=${summary}`,
'_blank',
'noopener,noreferrer,width=550,height=420'
)
if (!shareWindow) {
alert('Please allow popups to share this article.')
}
}}
className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-gradient-to-r from-[#91A511] to-[#ADAC0D] hover:from-[#ADAC0D] hover:to-[#C8B209] text-white text-sm font-medium transition-all duration-300 shadow-md hover:shadow-lg transform hover:-translate-y-0.5"
aria-label="Share on LinkedIn"
>
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
</svg>
LinkedIn
</button>

{/* Facebook Share */}
<button
type="button"
onClick={() => {
const url = encodeURIComponent(window.location.href)
const quote = encodeURIComponent(post.title)
const shareWindow = window.open(
`https://www.facebook.com/sharer/sharer.php?u=${url}&quote=${quote}`,
'_blank',
'noopener,noreferrer,width=550,height=420'
)
if (!shareWindow) {
alert('Please allow popups to share this article.')
}
}}
className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-gradient-to-r from-[#C8B209] to-[#E4B905] hover:from-[#E4B905] hover:to-[#FFBF00] text-white text-sm font-medium transition-all duration-300 shadow-md hover:shadow-lg transform hover:-translate-y-0.5"
aria-label="Share on Facebook"
>
<svg className="w-4 h-4" fill="currentColor" viewBox="0 0 24 24">
<path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" />
</svg>
Facebook
</button>

{/* Copy Link */}
<button
type="button"
onClick={() => {
if (!window.isSecureContext || !navigator.clipboard?.writeText) {
alert('Copy is only available in a secure context (HTTPS or localhost). Please copy the URL from the address bar.')
return
}
navigator.clipboard.writeText(window.location.href)
.then(() => {
if (copiedTimeoutRef.current != null) window.clearTimeout(copiedTimeoutRef.current)
setCopied(true)
copiedTimeoutRef.current = window.setTimeout(() => setCopied(false), 2000)
})
.catch((err) => {
console.error('Failed to copy:', err)
alert('Failed to copy link. Please copy manually.')
})
}}
className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-gradient-to-r from-[#E4B905] to-[#FFBF00] hover:from-[#FFBF00] hover:to-[#FFC517] text-white text-sm font-medium transition-all duration-300 shadow-md hover:shadow-lg transform hover:-translate-y-0.5"
aria-label="Copy link to clipboard"
>
{copied ? (
<>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
Copied!
</>
) : (
<>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
</svg>
Copy Link
</>
)}
</button>
</div>
</div>

{/* Screen reader announcement for copy feedback */}
<div role="status" aria-live="polite" className="sr-only">
{copied && "Link copied to clipboard"}
</div>

<div className="prose prose-lg max-w-none">
<MarkdownRenderer content={post.content} />
</div>
Expand Down
13 changes: 13 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,19 @@ body {
.text-balance {
text-wrap: balance;
}

/* Screen reader only - hides content visually but keeps it accessible to screen readers */
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
}

@layer base {
Expand Down