mirror of
https://github.com/harivansh-afk/Austens-Wedding-Guide.git
synced 2026-04-16 00:03:00 +00:00
Imrpoved blogs page
This commit is contained in:
parent
8fd1ca8260
commit
108b4533e9
17 changed files with 689 additions and 112 deletions
111
src/components/CommentSection.tsx
Normal file
111
src/components/CommentSection.tsx
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
|
||||
interface Comment {
|
||||
id: number;
|
||||
author: string;
|
||||
content: string;
|
||||
timestamp: string;
|
||||
}
|
||||
|
||||
interface CommentSectionProps {
|
||||
postId: number;
|
||||
}
|
||||
|
||||
// Store comments in memory (in a real app, this would be in a database)
|
||||
const commentsStore: Record<number, Comment[]> = {};
|
||||
|
||||
export const CommentSection: React.FC<CommentSectionProps> = ({ postId }) => {
|
||||
const [comments, setComments] = useState<Comment[]>([]);
|
||||
const [newComment, setNewComment] = useState('');
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
// Load comments for this post
|
||||
useEffect(() => {
|
||||
setComments(commentsStore[postId] || []);
|
||||
}, [postId]);
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
if (!newComment.trim()) return;
|
||||
|
||||
setIsSubmitting(true);
|
||||
|
||||
// Simulate API delay
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
const comment: Comment = {
|
||||
id: Date.now(),
|
||||
author: 'Anonymous Farmer', // Replace with actual user system
|
||||
content: newComment.trim(),
|
||||
timestamp: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const updatedComments = [comment, ...(commentsStore[postId] || [])];
|
||||
commentsStore[postId] = updatedComments;
|
||||
setComments(updatedComments);
|
||||
setNewComment('');
|
||||
setIsSubmitting(false);
|
||||
};
|
||||
|
||||
const formatDate = (dateString: string) => {
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mt-12">
|
||||
<h3 className="text-2xl font-bold text-gray-800 mb-6">Comments</h3>
|
||||
|
||||
<form onSubmit={handleSubmit} className="mb-8">
|
||||
<textarea
|
||||
value={newComment}
|
||||
onChange={(e) => setNewComment(e.target.value)}
|
||||
placeholder="Share your thoughts..."
|
||||
className="w-full p-4 border border-gray-200 rounded-lg focus:ring-2 focus:ring-sage-500 focus:border-transparent resize-none"
|
||||
rows={3}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isSubmitting || !newComment.trim()}
|
||||
className={`mt-2 px-6 py-2 rounded-lg text-white transition-colors ${
|
||||
isSubmitting || !newComment.trim()
|
||||
? 'bg-gray-400 cursor-not-allowed'
|
||||
: 'bg-sage-500 hover:bg-sage-600'
|
||||
}`}
|
||||
>
|
||||
{isSubmitting ? 'Posting...' : 'Post Comment'}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<div className="space-y-6">
|
||||
{comments.length === 0 ? (
|
||||
<p className="text-gray-500 text-center py-8">
|
||||
No comments yet. Be the first to share your thoughts!
|
||||
</p>
|
||||
) : (
|
||||
comments.map((comment) => (
|
||||
<div
|
||||
key={comment.id}
|
||||
className="bg-white p-6 rounded-lg shadow-sm border border-gray-100"
|
||||
>
|
||||
<div className="flex justify-between items-start mb-2">
|
||||
<span className="font-medium text-gray-800">{comment.author}</span>
|
||||
<span className="text-sm text-gray-500">
|
||||
{formatDate(comment.timestamp)}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-700 whitespace-pre-wrap">{comment.content}</p>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
43
src/components/ErrorBoundary.tsx
Normal file
43
src/components/ErrorBoundary.tsx
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import React from 'react';
|
||||
|
||||
interface ErrorBoundaryState {
|
||||
hasError: boolean;
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
interface ErrorBoundaryProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||
constructor(props: ErrorBoundaryProps) {
|
||||
super(props);
|
||||
this.state = { hasError: false, error: null };
|
||||
}
|
||||
|
||||
static getDerivedStateFromError(error: Error) {
|
||||
return { hasError: true, error };
|
||||
}
|
||||
|
||||
render() {
|
||||
if (this.state.hasError) {
|
||||
return (
|
||||
<div className="min-h-[400px] flex items-center justify-center">
|
||||
<div className="text-center">
|
||||
<h2 className="text-2xl font-bold text-gray-800 mb-4">
|
||||
Oops! Something went wrong
|
||||
</h2>
|
||||
<button
|
||||
onClick={() => window.location.reload()}
|
||||
className="bg-sage-500 text-white px-4 py-2 rounded-lg hover:bg-sage-600 transition-colors"
|
||||
>
|
||||
Try Again
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
7
src/components/LoadingSpinner.tsx
Normal file
7
src/components/LoadingSpinner.tsx
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import React from 'react';
|
||||
|
||||
export const LoadingSpinner: React.FC = () => (
|
||||
<div className="flex justify-center items-center min-h-[400px]">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-4 border-sage-500 border-t-transparent" />
|
||||
</div>
|
||||
);
|
||||
59
src/components/Pagination.tsx
Normal file
59
src/components/Pagination.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import React from 'react';
|
||||
|
||||
interface PaginationProps {
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
onPageChange: (page: number) => void;
|
||||
}
|
||||
|
||||
export const Pagination: React.FC<PaginationProps> = ({
|
||||
currentPage,
|
||||
totalPages,
|
||||
onPageChange,
|
||||
}) => {
|
||||
const pages = Array.from({ length: totalPages }, (_, i) => i + 1);
|
||||
|
||||
if (totalPages <= 1) return null;
|
||||
|
||||
return (
|
||||
<div className="flex justify-center space-x-2 mt-8">
|
||||
<button
|
||||
onClick={() => onPageChange(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
currentPage === 1
|
||||
? 'bg-gray-200 text-gray-500 cursor-not-allowed'
|
||||
: 'bg-white text-gray-700 hover:bg-sage-100'
|
||||
}`}
|
||||
>
|
||||
Previous
|
||||
</button>
|
||||
|
||||
{pages.map((page) => (
|
||||
<button
|
||||
key={page}
|
||||
onClick={() => onPageChange(page)}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
currentPage === page
|
||||
? 'bg-sage-500 text-white'
|
||||
: 'bg-white text-gray-700 hover:bg-sage-100'
|
||||
}`}
|
||||
>
|
||||
{page}
|
||||
</button>
|
||||
))}
|
||||
|
||||
<button
|
||||
onClick={() => onPageChange(currentPage + 1)}
|
||||
disabled={currentPage === totalPages}
|
||||
className={`px-4 py-2 rounded-lg ${
|
||||
currentPage === totalPages
|
||||
? 'bg-gray-200 text-gray-500 cursor-not-allowed'
|
||||
: 'bg-white text-gray-700 hover:bg-sage-100'
|
||||
}`}
|
||||
>
|
||||
Next
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
60
src/components/ShareButtons.tsx
Normal file
60
src/components/ShareButtons.tsx
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
import React from 'react';
|
||||
import { BlogPost } from '../data/blogPosts';
|
||||
|
||||
interface ShareButtonsProps {
|
||||
post: BlogPost;
|
||||
}
|
||||
|
||||
export const ShareButtons: React.FC<ShareButtonsProps> = ({ post }) => {
|
||||
const url = window.location.href;
|
||||
const title = `Check out ${post.character}'s blog post: ${post.title}`;
|
||||
|
||||
const shareLinks = [
|
||||
{
|
||||
name: 'Twitter',
|
||||
url: `https://twitter.com/intent/tweet?text=${encodeURIComponent(title)}&url=${encodeURIComponent(url)}`,
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/>
|
||||
</svg>
|
||||
),
|
||||
color: 'text-blue-400 hover:text-blue-600',
|
||||
},
|
||||
{
|
||||
name: 'Facebook',
|
||||
url: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(url)}`,
|
||||
icon: (
|
||||
<svg className="w-5 h-5" 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>
|
||||
),
|
||||
color: 'text-blue-600 hover:text-blue-800',
|
||||
},
|
||||
{
|
||||
name: 'Reddit',
|
||||
url: `https://reddit.com/submit?url=${encodeURIComponent(url)}&title=${encodeURIComponent(title)}`,
|
||||
icon: (
|
||||
<svg className="w-5 h-5" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0zm5.01 4.744c.688 0 1.25.561 1.25 1.249a1.25 1.25 0 0 1-2.498.056l-2.597-.547-.8 3.747c1.824.07 3.48.632 4.674 1.488.308-.309.73-.491 1.207-.491.968 0 1.754.786 1.754 1.754 0 .716-.435 1.333-1.01 1.614a3.111 3.111 0 0 1 .042.52c0 2.694-3.13 4.87-7.004 4.87-3.874 0-7.004-2.176-7.004-4.87 0-.183.015-.366.043-.534A1.748 1.748 0 0 1 4.028 12c0-.968.786-1.754 1.754-1.754.463 0 .898.196 1.207.49 1.207-.883 2.878-1.43 4.744-1.487l.885-4.182a.342.342 0 0 1 .14-.197.35.35 0 0 1 .238-.042l2.906.617a1.214 1.214 0 0 1 1.108-.701zM9.25 12C8.561 12 8 12.562 8 13.25c0 .687.561 1.248 1.25 1.248.687 0 1.248-.561 1.248-1.249 0-.688-.561-1.249-1.249-1.249zm5.5 0c-.687 0-1.248.561-1.248 1.25 0 .687.561 1.248 1.249 1.248.688 0 1.249-.561 1.249-1.249 0-.687-.562-1.249-1.25-1.249zm-5.466 3.99a.327.327 0 0 0-.231.094.33.33 0 0 0 0 .463c.842.842 2.484.913 2.961.913.477 0 2.105-.056 2.961-.913a.361.361 0 0 0 .029-.463.33.33 0 0 0-.464 0c-.547.533-1.684.73-2.512.73-.828 0-1.979-.196-2.512-.73a.326.326 0 0 0-.232-.095z"/>
|
||||
</svg>
|
||||
),
|
||||
color: 'text-orange-500 hover:text-orange-700',
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="flex items-center space-x-4 mt-6">
|
||||
<span className="text-gray-600 font-medium">Share:</span>
|
||||
{shareLinks.map((link) => (
|
||||
<button
|
||||
key={link.name}
|
||||
onClick={() => window.open(link.url, '_blank')}
|
||||
className={`${link.color} transition-colors`}
|
||||
aria-label={`Share on ${link.name}`}
|
||||
>
|
||||
{link.icon}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue