Merge pull request #4 from auggie2lbcf/comment-section

add comment section
This commit is contained in:
2025-04-08 15:11:17 -05:00
committed by GitHub
7 changed files with 192 additions and 84 deletions
-1
View File
@@ -5119,7 +5119,6 @@
"version": "6.15.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.15.0.tgz",
"integrity": "sha512-ifBhQ0rRzHDzqp9jAQP6OwHSH7dbYIQjD3SbJs9YYk9AikKEettW/9s/tbSFDTpXcRbF+u1aLrhHxDFaYtZpFQ==",
"license": "Apache-2.0",
"dependencies": {
"@mongodb-js/saslprep": "^1.1.9",
"bson": "^6.10.3",
Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

+106 -54
View File
@@ -1,9 +1,15 @@
import React, { useState } from 'react';
import React, { useState, useEffect } from 'react';
interface CommentFormProps {
postId: string;
}
interface Comment {
name: string;
comment: string;
createdAt: string;
}
const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
@@ -11,35 +17,63 @@ const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);
const [error, setError] = useState<string | null>(null);
const [comments, setComments] = useState<Comment[]>([]);
useEffect(() => {
const fetchComments = async () => {
try {
const response = await fetch(`/api/comments?postId=${postId}`);
if (response.ok) {
const data = await response.json();
setComments(data);
} else {
console.error('Failed to fetch comments');
}
} catch (err) {
console.error('Error fetching comments:', err);
}
};
fetchComments();
}, [postId]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
// Basic validation
if (!name.trim() || !email.trim() || !comment.trim()) {
setError('All fields are required');
return;
}
setIsSubmitting(true);
setError(null);
try {
// In a real implementation, you would send this data to your API
// For now, we'll just simulate a successful submission
await new Promise(resolve => setTimeout(resolve, 1000));
// Clear form and show success message
setName('');
setEmail('');
setComment('');
setIsSubmitted(true);
setIsSubmitting(false);
// Reset success message after 5 seconds
setTimeout(() => {
setIsSubmitted(false);
}, 5000);
const response = await fetch('/api/comments', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, email, comment, postId }),
});
if (response.ok) {
setName('');
setEmail('');
setComment('');
setIsSubmitted(true);
setIsSubmitting(false);
// Fetch updated comments
const updatedComments = await fetch(`/api/comments?postId=${postId}`).then((res) =>
res.json()
);
setComments(updatedComments);
setTimeout(() => setIsSubmitted(false), 5000);
} else {
const data = await response.json();
setError(data.message || 'Something went wrong. Please try again later.');
setIsSubmitting(false);
}
} catch (err) {
setError('Something went wrong. Please try again later.');
setIsSubmitting(false);
@@ -49,19 +83,19 @@ const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
return (
<div className="mt-12 pt-6 border-t border-primary-200">
<h3 className="text-2xl font-bold mb-6">Leave a Comment</h3>
{isSubmitted && (
<div className="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-md mb-6">
Your comment has been submitted and is awaiting moderation. Thank you!
Your comment has been submitted. Thank you!
</div>
)}
{error && (
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md mb-6">
{error}
</div>
)}
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
@@ -75,6 +109,12 @@ const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
onChange={(e) => setName(e.target.value)}
className="w-full px-4 py-2 border border-primary-300 rounded-md focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
required
onFocus={() => {
if (typeof window !== 'undefined') {
const savedName = localStorage.getItem('name');
if (savedName) setName(savedName);
}
}}
/>
</div>
<div>
@@ -88,10 +128,16 @@ const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
onChange={(e) => setEmail(e.target.value)}
className="w-full px-4 py-2 border border-primary-300 rounded-md focus:outline-none focus:ring-2 focus:ring-accent focus:border-transparent"
required
onFocus={() => {
if (typeof window !== 'undefined') {
const savedEmail = localStorage.getItem('email');
if (savedEmail) setEmail(savedEmail);
}
}}
/>
</div>
</div>
<div>
<label htmlFor="comment" className="block text-primary-700 mb-1">
Comment <span className="text-red-500">*</span>
@@ -105,18 +151,32 @@ const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
required
></textarea>
</div>
<div className="flex items-center">
{/* <div className="flex items-center">
<input
type="checkbox"
id="save-info"
className="h-4 w-4 text-accent border-primary-300 rounded focus:ring-accent"
checked={typeof window !== 'undefined' && localStorage.getItem('saveInfo') === 'true'}
onChange={(e) => {
if (typeof window !== 'undefined') {
const saveInfo = e.target.checked;
localStorage.setItem('saveInfo', saveInfo.toString());
if (saveInfo) {
localStorage.setItem('name', name);
localStorage.setItem('email', email);
} else {
localStorage.removeItem('name');
localStorage.removeItem('email');
}
}
}}
/>
<label htmlFor="save-info" className="ml-2 block text-sm text-primary-600">
Save my name and email for the next time I comment
</label>
</div>
</div> */}
<button
type="submit"
className="button"
@@ -125,35 +185,27 @@ const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
{isSubmitting ? 'Submitting...' : 'Post Comment'}
</button>
</form>
{/* Sample comments - in a real implementation, these would be fetched from an API */}
<div className="mt-12">
<h3 className="text-xl font-bold mb-6">Comments (2)</h3>
<h3 className="text-xl font-bold mb-6">Comments ({comments.length})</h3>
<div className="space-y-6">
<div className="bg-white p-6 rounded-md shadow-sm border border-primary-200">
<div className="flex justify-between items-start mb-4">
<div>
<h4 className="font-bold">John Calvin</h4>
<p className="text-sm text-primary-500">March 10, 2025</p>
{comments.map((comment) => (
<div
key={comment.createdAt}
className="bg-white p-6 rounded-md shadow-sm border border-primary-200"
>
<div className="flex justify-between items-start mb-4">
<div>
<h4 className="font-bold">{comment.name}</h4>
<p className="text-sm text-primary-500">
{new Date(comment.createdAt).toLocaleDateString()}
</p>
</div>
</div>
<p className="text-primary-700">{comment.comment}</p>
</div>
<p className="text-primary-700">
A profound meditation on the doctrine of grace. I particularly appreciate your emphasis on how this truth transforms our daily lives, not just our theological understanding.
</p>
</div>
<div className="bg-white p-6 rounded-md shadow-sm border border-primary-200">
<div className="flex justify-between items-start mb-4">
<div>
<h4 className="font-bold">Martin Luther</h4>
<p className="text-sm text-primary-500">March 5, 2025</p>
</div>
</div>
<p className="text-primary-700">
This post eloquently articulates what I've been trying to explain to my congregation. The way you connected Scripture with practical application was masterful. I'll be sharing this widely.
</p>
</div>
))}
</div>
</div>
</div>
+54
View File
@@ -0,0 +1,54 @@
import { NextApiRequest, NextApiResponse } from 'next';
import { MongoClient } from 'mongodb';
const uri = process.env.MONGODB_URI || 'your-mongodb-uri';
const dbName = 'confessions-of-grace';
const client = new MongoClient(uri);
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method === 'POST') {
const { name, email, comment, postId } = req.body;
if (!name || !email || !comment || !postId) {
return res.status(400).json({ message: 'All fields are required' });
}
try {
if (!client.db(dbName)) await client.connect();
const db = client.db(dbName);
const collection = db.collection('comments');
await collection.insertOne({ name, email, comment, postId, createdAt: new Date() });
res.status(201).json({ message: 'Comment submitted successfully' });
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Internal server error' });
}
} else if (req.method === 'GET') {
const { postId } = req.query;
if (!postId) {
return res.status(400).json({ message: 'Post ID is required' });
}
try {
if (!client.db(dbName)) await client.connect();
const db = client.db(dbName);
const collection = db.collection('comments');
const comments = await collection
.find({ postId })
.sort({ createdAt: -1 })
.toArray();
res.status(200).json(comments);
} catch (error) {
console.error(error);
res.status(500).json({ message: 'Internal server error' });
}
} else {
res.status(405).json({ message: 'Method not allowed' });
}
}
@@ -1,27 +0,0 @@
import { MongoClient } from 'mongodb';
const uri = process.env.MONGODB_URI || ''; // Load URI from environment variables
const options = {};
let client: MongoClient;
let clientPromise: Promise<MongoClient>;
if (!process.env.MONGODB_URI) {
throw new Error('Please add your MongoDB URI to .env.local');
}
// Reuse the client instance for serverless deployments
if (process.env.NODE_ENV === 'development') {
// Create a global instance to preserve the same client during HMR in development
if (!(global as typeof globalThis & { _mongoClientPromise?: Promise<MongoClient> })._mongoClientPromise) {
client = new MongoClient(uri, options);
(global as typeof globalThis & { _mongoClientPromise?: Promise<MongoClient> })._mongoClientPromise = client.connect();
}
clientPromise = (global as typeof globalThis & { _mongoClientPromise?: Promise<MongoClient> })._mongoClientPromise!;
} else {
// In production, create a new client connection
client = new MongoClient(uri, options);
clientPromise = client.connect();
}
export default clientPromise;
+3 -2
View File
@@ -8,6 +8,7 @@ import { GetStaticPaths, GetStaticProps } from 'next';
import Image from 'next/image';
import Link from 'next/link';
import React from 'react';
import CommentSection from "@/components/CommentSection";
interface PostProps {
post: PostData;
@@ -96,8 +97,8 @@ const Post: React.FC<PostProps> = ({ post, morePosts }) => {
))}
</div>
</div>
{/* <CommentSection postId={post.id} /> */}
<CommentSection postId={post.id} />
<div className="mt-12 pt-6 border-t border-primary-200">
<Link href="/" className="text-accent-dark hover:text-accent inline-flex items-center">
+29
View File
@@ -0,0 +1,29 @@
---
title: "Against Ethnocentric Christian Nationalism"
date: "2025-04-05"
author: "@Auggie2LBCF"
excerpt: "TBD"
tags: ["christianity", "nationalism", "theology", "ethics", "identity", "political philosophy", "church and state"]
coverImage: "/images/templar.jpeg"
---
## Definitions
I will start by defining what *exactly* I mean by each of the words in the title. This is to prevent any confusion with my position and to help see what I am seeing.
### Christian
With where society is at with Christianity. It will be helpful to fence what I mean by Christian. Here are 5 points that I would use when describing a Christian:
1. **Those who have been renerated with an effectual call.** They have been given a new heart and spiritual life (John 3:3), drawing them irresistibly to Christ (2 Timothy 1:9).
2. **Those who have a saving faith.** A wholehearted trust and reliance *solely* upon Jesus Christ (Ephesians 2:8-9), this faith is understood as a gift from God (WSC Q. 86).
3. **Those who have repenantce unto life.** Saving faith is always accompanied by repentance -a heartfelt sorrow for sin, a turning away from it, and a sincere turning towards God and His commands (Acts 20:21), again, as a grace from God.
4. **Those who have been justified.** The believer has been declared righteous by God, not on their merits but solely on Christ's righteousness being imputed (credited) to them (Romans 3:24-25a, Heidelberg Q. 60).
5. **Those who have union with Christ.** The believer is united with Christ (2 Corinthians 5:17) and adopted into God's family as His child.
When I refer to Christian, I do not refer to those who fall into the reformed baptist category, but those who are heirs with Christ with me. This includes people such as Sam Waldron to Mike Winger.
### Christian Nationalism
Christian nationalism is a totality of national action, consisting of civil laws and social customs, conducted by a Christian nation as a Christian nation, in order to procure for itself both earthly and heavenly good in Christ. (*The Case for Christian Nationalism*, Stephen Wolfe)
### Ethnocentric
Ethnocentric is the
## Systematic Theology
Then God said, "Let us make man in our image, after our likeness. And let them have dominion over the fish of the sea and over the birds of the heavens and over the livestock and over all the earth and over every creeping thing that creeps on the earth."
## Historical Theology