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", "version": "6.15.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.15.0.tgz", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.15.0.tgz",
"integrity": "sha512-ifBhQ0rRzHDzqp9jAQP6OwHSH7dbYIQjD3SbJs9YYk9AikKEettW/9s/tbSFDTpXcRbF+u1aLrhHxDFaYtZpFQ==", "integrity": "sha512-ifBhQ0rRzHDzqp9jAQP6OwHSH7dbYIQjD3SbJs9YYk9AikKEettW/9s/tbSFDTpXcRbF+u1aLrhHxDFaYtZpFQ==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"@mongodb-js/saslprep": "^1.1.9", "@mongodb-js/saslprep": "^1.1.9",
"bson": "^6.10.3", "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 { interface CommentFormProps {
postId: string; postId: string;
} }
interface Comment {
name: string;
comment: string;
createdAt: string;
}
const CommentSection: React.FC<CommentFormProps> = ({ postId }) => { const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
const [name, setName] = useState(''); const [name, setName] = useState('');
const [email, setEmail] = useState(''); const [email, setEmail] = useState('');
@@ -11,35 +17,63 @@ const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false); const [isSubmitted, setIsSubmitted] = useState(false);
const [error, setError] = useState<string | null>(null); 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) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
// Basic validation
if (!name.trim() || !email.trim() || !comment.trim()) { if (!name.trim() || !email.trim() || !comment.trim()) {
setError('All fields are required'); setError('All fields are required');
return; return;
} }
setIsSubmitting(true); setIsSubmitting(true);
setError(null); setError(null);
try { try {
// In a real implementation, you would send this data to your API const response = await fetch('/api/comments', {
// For now, we'll just simulate a successful submission method: 'POST',
await new Promise(resolve => setTimeout(resolve, 1000)); headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name, email, comment, postId }),
// Clear form and show success message });
setName('');
setEmail(''); if (response.ok) {
setComment(''); setName('');
setIsSubmitted(true); setEmail('');
setIsSubmitting(false); setComment('');
setIsSubmitted(true);
// Reset success message after 5 seconds setIsSubmitting(false);
setTimeout(() => {
setIsSubmitted(false); // Fetch updated comments
}, 5000); 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) { } catch (err) {
setError('Something went wrong. Please try again later.'); setError('Something went wrong. Please try again later.');
setIsSubmitting(false); setIsSubmitting(false);
@@ -49,19 +83,19 @@ const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
return ( return (
<div className="mt-12 pt-6 border-t border-primary-200"> <div className="mt-12 pt-6 border-t border-primary-200">
<h3 className="text-2xl font-bold mb-6">Leave a Comment</h3> <h3 className="text-2xl font-bold mb-6">Leave a Comment</h3>
{isSubmitted && ( {isSubmitted && (
<div className="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-md mb-6"> <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> </div>
)} )}
{error && ( {error && (
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md mb-6"> <div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-md mb-6">
{error} {error}
</div> </div>
)} )}
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div> <div>
@@ -75,6 +109,12 @@ const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
onChange={(e) => setName(e.target.value)} 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" 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 required
onFocus={() => {
if (typeof window !== 'undefined') {
const savedName = localStorage.getItem('name');
if (savedName) setName(savedName);
}
}}
/> />
</div> </div>
<div> <div>
@@ -88,10 +128,16 @@ const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
onChange={(e) => setEmail(e.target.value)} 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" 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 required
onFocus={() => {
if (typeof window !== 'undefined') {
const savedEmail = localStorage.getItem('email');
if (savedEmail) setEmail(savedEmail);
}
}}
/> />
</div> </div>
</div> </div>
<div> <div>
<label htmlFor="comment" className="block text-primary-700 mb-1"> <label htmlFor="comment" className="block text-primary-700 mb-1">
Comment <span className="text-red-500">*</span> Comment <span className="text-red-500">*</span>
@@ -105,18 +151,32 @@ const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
required required
></textarea> ></textarea>
</div> </div>
<div className="flex items-center"> {/* <div className="flex items-center">
<input <input
type="checkbox" type="checkbox"
id="save-info" id="save-info"
className="h-4 w-4 text-accent border-primary-300 rounded focus:ring-accent" 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"> <label htmlFor="save-info" className="ml-2 block text-sm text-primary-600">
Save my name and email for the next time I comment Save my name and email for the next time I comment
</label> </label>
</div> </div> */}
<button <button
type="submit" type="submit"
className="button" className="button"
@@ -125,35 +185,27 @@ const CommentSection: React.FC<CommentFormProps> = ({ postId }) => {
{isSubmitting ? 'Submitting...' : 'Post Comment'} {isSubmitting ? 'Submitting...' : 'Post Comment'}
</button> </button>
</form> </form>
{/* Sample comments - in a real implementation, these would be fetched from an API */}
<div className="mt-12"> <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="space-y-6">
<div className="bg-white p-6 rounded-md shadow-sm border border-primary-200"> {comments.map((comment) => (
<div className="flex justify-between items-start mb-4"> <div
<div> key={comment.createdAt}
<h4 className="font-bold">John Calvin</h4> className="bg-white p-6 rounded-md shadow-sm border border-primary-200"
<p className="text-sm text-primary-500">March 10, 2025</p> >
<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> </div>
<p className="text-primary-700">{comment.comment}</p>
</div> </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> </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 Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
import React from 'react'; import React from 'react';
import CommentSection from "@/components/CommentSection";
interface PostProps { interface PostProps {
post: PostData; post: PostData;
@@ -96,8 +97,8 @@ const Post: React.FC<PostProps> = ({ post, morePosts }) => {
))} ))}
</div> </div>
</div> </div>
{/* <CommentSection postId={post.id} /> */} <CommentSection postId={post.id} />
<div className="mt-12 pt-6 border-t border-primary-200"> <div className="mt-12 pt-6 border-t border-primary-200">
<Link href="/" className="text-accent-dark hover:text-accent inline-flex items-center"> <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