From edc4b03c4e7b3a0280e5f7a48816a189e6f57400 Mon Sep 17 00:00:00 2001 From: AdithaBuwaneka Date: Mon, 21 Jul 2025 22:56:11 +0530 Subject: [PATCH 01/13] feat: enhance success stories management with detailed logging, user consent checks, and improved error handling --- src/app/api/admin/success-stories/route.ts | 206 +++---- .../SuccessStoriesContent.tsx | 502 +++++++++--------- src/lib/models/feedbackSchema.ts | 2 +- 3 files changed, 369 insertions(+), 341 deletions(-) diff --git a/src/app/api/admin/success-stories/route.ts b/src/app/api/admin/success-stories/route.ts index d9874cc4..1717b150 100644 --- a/src/app/api/admin/success-stories/route.ts +++ b/src/app/api/admin/success-stories/route.ts @@ -26,13 +26,20 @@ async function verifyAdminToken(request: NextRequest) { // GET - Fetch all feedback entries with success stories (for admin) export async function GET(request: NextRequest) { try { + console.log("Starting GET request for success stories"); + + // Connect to database await connect(); + console.log("Database connected successfully"); // Ensure User and Admin models are registered for populate operations User; Admin; + console.log("Models registered"); const adminData = await verifyAdminToken(request); + console.log("Admin verification result:", adminData ? "Success" : "Failed"); + if (!adminData) { return NextResponse.json( { success: false, message: "Unauthorized" }, @@ -46,12 +53,23 @@ export async function GET(request: NextRequest) { const search = searchParams.get("search") || ""; const status = searchParams.get("status") || "all"; // all, published, unpublished + console.log("Query params:", { page, limit, search, status }); + const skip = (page - 1) * limit; + // First, let's check if we can query the Feedback collection at all + const totalFeedbackCount = await Feedback.countDocuments({}); + console.log(`Total feedback entries in database: ${totalFeedbackCount}`); + + // Check how many have success stories + const feedbackWithStoriesCount = await Feedback.countDocuments({ + successStory: { $exists: true, $ne: "" } + }); + console.log(`Feedback entries with success stories: ${feedbackWithStoriesCount}`); + // Build query for feedback entries with success stories + // Admin can see all success stories, not filtered by canSuccessStoryPost const query: any = { - userId: { $ne: null }, - canSuccessStoryPost: true, successStory: { $exists: true, $ne: "" } }; @@ -67,34 +85,51 @@ export async function GET(request: NextRequest) { query.isPublished = status === "published"; } + console.log("MongoDB query:", JSON.stringify(query, null, 2)); + // Get feedback entries with success stories const feedbackWithStories = await Feedback.find(query) .populate("userId", "firstName lastName email avatar") .sort({ date: -1 }) .skip(skip) - .limit(limit); + .limit(limit) + .lean(); // Add lean() for better performance + + console.log(`Found ${feedbackWithStories.length} feedback entries`); // Transform feedback data to match expected success story format const transformedStories = feedbackWithStories .filter(feedback => feedback.userId !== null) - .map(feedback => ({ - _id: feedback._id, - userId: feedback.userId, - title: feedback.adminTitle || `${feedback.rating}-Star Experience`, - description: feedback.successStory, - feedback: feedback.feedback, - rating: feedback.rating, - isPublished: feedback.isPublished, - publishedAt: feedback.isPublished ? feedback.date : null, - createdAt: feedback.date, - updatedAt: feedback.date, - displayName: feedback.displayName, - isAnonymous: feedback.isAnonymous - })); + .map(feedback => { + try { + return { + _id: feedback._id, + userId: feedback.userId, + title: feedback.adminTitle || `${feedback.rating || 5}-Star Experience`, + description: feedback.successStory, + feedback: feedback.feedback, + rating: feedback.rating || 5, + isPublished: feedback.isPublished || false, + publishedAt: feedback.isPublished ? feedback.date : null, + createdAt: feedback.date, + updatedAt: feedback.date, + displayName: feedback.isAnonymous ? 'Anonymous' : (feedback.displayName || 'Unknown User'), + isAnonymous: feedback.isAnonymous || false, + canSuccessStoryPost: feedback.canSuccessStoryPost || false + }; + } catch (mapError) { + console.error("Error transforming feedback:", mapError, feedback); + return null; + } + }) + .filter(story => story !== null); // Remove any null entries from transformation errors + + console.log(`Transformed ${transformedStories.length} success stories`); const total = await Feedback.countDocuments(query); + console.log(`Total documents matching query: ${total}`); - return NextResponse.json({ + const response = { success: true, data: { successStories: transformedStories, @@ -107,18 +142,34 @@ export async function GET(request: NextRequest) { hasPrevPage: page > 1, }, }, - }); + }; + + console.log("Returning response with", transformedStories.length, "stories"); + return NextResponse.json(response); } catch (error) { - console.error("Error fetching success stories:", error); + console.error("Detailed error in GET success stories:", { + message: error instanceof Error ? error.message : 'Unknown error', + stack: error instanceof Error ? error.stack : 'No stack trace', + error + }); return NextResponse.json( - { success: false, message: "Failed to fetch success stories" }, + { success: false, message: `Server error: ${error instanceof Error ? error.message : 'Unknown error'}` }, { status: 500 } ); } } -// POST - Create a new feedback entry with success story (admin-created) +// POST - Admin cannot create new success stories, only users can +// This endpoint is disabled as per requirements export async function POST(request: NextRequest) { + return NextResponse.json( + { success: false, message: "Admin cannot create new success stories. Only users can submit feedback with success stories." }, + { status: 403 } + ); +} + +// PUT - Update a feedback entry's success story details +export async function PUT(request: NextRequest) { try { await connect(); @@ -130,92 +181,38 @@ export async function POST(request: NextRequest) { ); } - const { userId, title, description, feedback, rating, isPublished } = await request.json(); + const { id, title, isPublished } = await request.json(); - // Validate required fields - if (!userId || !description || !feedback) { + if (!id) { return NextResponse.json( - { success: false, message: "User ID, success story, and feedback are required" }, + { success: false, message: "Feedback ID is required" }, { status: 400 } ); } - // Verify user exists - const user = await User.findById(userId); - if (!user) { + // Find the feedback entry first to check canSuccessStoryPost + const existingFeedback = await Feedback.findById(id); + if (!existingFeedback) { + console.error("Feedback entry not found with ID:", id); return NextResponse.json( - { success: false, message: "User not found" }, + { success: false, message: "Feedback entry not found" }, { status: 404 } ); } - // Create feedback entry with success story - const feedbackEntry = new Feedback({ - userId, - feedback: feedback, - successStory: description, - rating: rating || 5, - adminTitle: title, - canSuccessStoryPost: true, - isAnonymous: false, - isPublished, - displayName: `${user.firstName} ${user.lastName}`, + console.log("Existing feedback:", { + id: existingFeedback._id, + canSuccessStoryPost: existingFeedback.canSuccessStoryPost, + isPublished: existingFeedback.isPublished, + newPublishState: isPublished }); - await feedbackEntry.save(); - - // Populate user details for response - await feedbackEntry.populate("userId", "firstName lastName email avatar"); - - // Transform response to match expected format - const transformedStory = { - _id: feedbackEntry._id, - userId: feedbackEntry.userId, - title: feedbackEntry.adminTitle || `${feedbackEntry.rating}-Star Experience`, - description: feedbackEntry.successStory, - feedback: feedbackEntry.feedback, - rating: feedbackEntry.rating, - isPublished: feedbackEntry.isPublished, - publishedAt: feedbackEntry.isPublished ? feedbackEntry.date : null, - createdAt: feedbackEntry.date, - updatedAt: feedbackEntry.date, - displayName: feedbackEntry.displayName, - isAnonymous: feedbackEntry.isAnonymous - }; - - return NextResponse.json({ - success: true, - message: "Success story created successfully", - data: transformedStory, - }); - } catch (error) { - console.error("Error creating success story:", error); - return NextResponse.json( - { success: false, message: "Failed to create success story" }, - { status: 500 } - ); - } -} - -// PUT - Update a feedback entry's success story details -export async function PUT(request: NextRequest) { - try { - await connect(); - - const adminData = await verifyAdminToken(request); - if (!adminData) { - return NextResponse.json( - { success: false, message: "Unauthorized" }, - { status: 401 } - ); - } - - const { id, title, isPublished } = await request.json(); - - if (!id) { + // Only allow publishing if user gave consent (canSuccessStoryPost: true) + if (isPublished === true && !existingFeedback.canSuccessStoryPost) { + console.warn("Attempted to publish without consent"); return NextResponse.json( - { success: false, message: "Feedback ID is required" }, - { status: 400 } + { success: false, message: "Cannot publish success story without user consent" }, + { status: 403 } ); } @@ -253,8 +250,9 @@ export async function PUT(request: NextRequest) { publishedAt: feedback.isPublished ? feedback.date : null, createdAt: feedback.date, updatedAt: feedback.date, - displayName: feedback.displayName, - isAnonymous: feedback.isAnonymous + displayName: feedback.isAnonymous ? 'Anonymous' : feedback.displayName, + isAnonymous: feedback.isAnonymous, + canSuccessStoryPost: feedback.canSuccessStoryPost }; return NextResponse.json({ @@ -263,9 +261,13 @@ export async function PUT(request: NextRequest) { data: transformedStory, }); } catch (error) { - console.error("Error updating success story:", error); + console.error("Detailed error in PUT success stories:", { + message: error instanceof Error ? error.message : 'Unknown error', + stack: error instanceof Error ? error.stack : 'No stack trace', + error + }); return NextResponse.json( - { success: false, message: "Failed to update success story" }, + { success: false, message: `Failed to update success story: ${error instanceof Error ? error.message : 'Unknown error'}` }, { status: 500 } ); } @@ -308,9 +310,13 @@ export async function DELETE(request: NextRequest) { message: "Success story deleted successfully", }); } catch (error) { - console.error("Error deleting success story:", error); + console.error("Detailed error in DELETE success stories:", { + message: error instanceof Error ? error.message : 'Unknown error', + stack: error instanceof Error ? error.stack : 'No stack trace', + error + }); return NextResponse.json( - { success: false, message: "Failed to delete success story" }, + { success: false, message: `Failed to delete success story: ${error instanceof Error ? error.message : 'Unknown error'}` }, { status: 500 } ); } diff --git a/src/components/Admin/dashboardContent/SuccessStoriesContent.tsx b/src/components/Admin/dashboardContent/SuccessStoriesContent.tsx index b8c1deee..1dd4bbfb 100644 --- a/src/components/Admin/dashboardContent/SuccessStoriesContent.tsx +++ b/src/components/Admin/dashboardContent/SuccessStoriesContent.tsx @@ -29,14 +29,19 @@ interface SuccessStory { userId: User | null; title: string; description: string; + feedback: string; + rating: number; image?: string; isPublished: boolean; publishedAt?: string; - createdBy: { + createdBy?: { username: string; }; createdAt: string; updatedAt: string; + displayName: string; + isAnonymous: boolean; + canSuccessStoryPost: boolean; } interface CreateStoryFormData { @@ -81,8 +86,26 @@ export default function SuccessStoriesContent() { const response = await fetch(`/api/admin/success-stories?${params}`, { credentials: "include", // Important for cookies }); + + console.log("Response status:", response.status); + console.log("Response headers:", response.headers); + + // Check if response is HTML (likely an error page) + const contentType = response.headers.get('content-type'); + if (!contentType || !contentType.includes('application/json')) { + const text = await response.text(); + console.error("Received HTML instead of JSON:", text.substring(0, 200)); + + if (response.status === 401 || text.includes('login')) { + console.warn("Admin not authenticated, redirecting to login"); + window.location.href = '/admin/login'; + return; + } + + throw new Error(`Server returned ${response.status}: Expected JSON but got HTML`); + } + const data = await response.json(); - console.log("Success stories response:", { status: response.status, data }); if (response.ok && data.success) { @@ -97,9 +120,11 @@ export default function SuccessStoriesContent() { console.warn("Admin not authenticated, redirecting to login"); window.location.href = '/admin/login'; } + alert(`Failed to fetch success stories: ${data.message || 'Unknown error'}`); } } catch (error) { console.error("Error fetching success stories:", error); + alert(`Error loading success stories: ${error instanceof Error ? error.message : 'Unknown error'}`); } finally { setLoading(false); } @@ -130,29 +155,43 @@ export default function SuccessStoriesContent() { fetchUsers(); }, [currentPage, searchTerm, statusFilter]); - // Handle form submission + // Handle form submission - Only for editing const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + if (!editingStory) { + alert("Admin cannot create new success stories. Only users can submit feedback with success stories."); + return; + } + + // Check consent before allowing publish + if (formData.isPublished && !editingStory.canSuccessStoryPost) { + alert("Cannot publish this success story because the user did not give consent for publication."); + return; + } + try { - const url = editingStory - ? "/api/admin/success-stories" - : "/api/admin/success-stories"; - - const method = editingStory ? "PUT" : "POST"; - const body = editingStory - ? { ...formData, id: editingStory._id } - : formData; - - const response = await fetch(url, { - method, + const response = await fetch("/api/admin/success-stories", { + method: "PUT", headers: { "Content-Type": "application/json", }, - credentials: "include", // Important for cookies - body: JSON.stringify(body), + credentials: "include", + body: JSON.stringify({ + id: editingStory._id, + title: formData.title, + isPublished: formData.isPublished + }), }); + // Check content type before parsing JSON + const contentType = response.headers.get('content-type'); + if (!contentType || !contentType.includes('application/json')) { + const text = await response.text(); + console.error("Received HTML instead of JSON:", text.substring(0, 200)); + throw new Error(`Server returned ${response.status}: Expected JSON but got HTML`); + } + const data = await response.json(); if (data.success) { @@ -168,11 +207,11 @@ export default function SuccessStoriesContent() { }); fetchSuccessStories(); } else { - alert(data.message || "Failed to save success story"); + alert(data.message || "Failed to update success story"); } } catch (error) { - console.error("Error saving success story:", error); - alert("Failed to save success story"); + console.error("Error updating success story:", error); + alert(`Failed to update success story: ${error instanceof Error ? error.message : 'Unknown error'}`); } }; @@ -188,6 +227,14 @@ export default function SuccessStoriesContent() { credentials: "include", // Important for cookies }); + // Check content type before parsing JSON + const contentType = response.headers.get('content-type'); + if (!contentType || !contentType.includes('application/json')) { + const text = await response.text(); + console.error("Received HTML instead of JSON:", text.substring(0, 200)); + throw new Error(`Server returned ${response.status}: Expected JSON but got HTML`); + } + const data = await response.json(); if (data.success) { @@ -197,12 +244,18 @@ export default function SuccessStoriesContent() { } } catch (error) { console.error("Error deleting success story:", error); - alert("Failed to delete success story"); + alert(`Failed to delete success story: ${error instanceof Error ? error.message : 'Unknown error'}`); } }; // Handle toggle publish status const handleTogglePublish = async (story: SuccessStory) => { + // Check if user gave consent before allowing publish + if (!story.isPublished && !story.canSuccessStoryPost) { + alert("Cannot publish this success story because the user did not give consent for publication."); + return; + } + try { const response = await fetch("/api/admin/success-stories", { method: "PUT", @@ -216,6 +269,14 @@ export default function SuccessStoriesContent() { }), }); + // Check content type before parsing JSON + const contentType = response.headers.get('content-type'); + if (!contentType || !contentType.includes('application/json')) { + const text = await response.text(); + console.error("Received HTML instead of JSON:", text.substring(0, 200)); + throw new Error(`Server returned ${response.status}: Expected JSON but got HTML`); + } + const data = await response.json(); if (data.success) { @@ -225,19 +286,19 @@ export default function SuccessStoriesContent() { } } catch (error) { console.error("Error updating success story:", error); - alert("Failed to update success story"); + alert(`Failed to update success story: ${error instanceof Error ? error.message : 'Unknown error'}`); } }; - // Handle edit + // Handle edit - Only title and publish status const handleEdit = (story: SuccessStory) => { setEditingStory(story); setFormData({ userId: story.userId?._id || "", title: story.title, description: story.description, - feedback: (story as any).feedback || "General feedback", - rating: (story as any).rating || 5, + feedback: story.feedback || "General feedback", + rating: story.rating || 5, isPublished: story.isPublished, }); setShowCreateForm(true); @@ -258,34 +319,16 @@ export default function SuccessStoriesContent() { } return ( -
+

Success Stories Management

-

Create and manage user success stories

+

Review and manage user success stories from feedback

-
{/* Stats Cards */} -
+
@@ -316,9 +359,9 @@ export default function SuccessStoriesContent() {
{/* Filters */} -
-
-
+
+
+
-
+
setFormData({ ...formData, userId: e.target.value })} - required - className="w-full border border-gray-300 rounded-lg px-3 py-2 pr-10 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-900 bg-white appearance-none cursor-pointer hover:border-gray-400 transition-colors" - > - - {users.map((user) => ( - - ))} - -
- - - -
-
+ + {/* Read-only information */} +
+

Original Feedback Details (Read-only)

+
+

User: {editingStory.isAnonymous ? "Anonymous" : editingStory.displayName}

+

Rating: {editingStory.rating}/5 stars

+

User Consent: {editingStory.canSuccessStoryPost ? "Yes" : "No"}

+

Feedback: {editingStory.feedback}

+

Success Story: {editingStory.description}

+
+
setFormData({ ...formData, title: e.target.value })} - required maxLength={200} className="w-full border border-gray-300 rounded-lg px-3 py-2.5 focus:ring-2 focus:ring-blue-500 focus:border-blue-500 text-gray-900 bg-white placeholder-gray-400 hover:border-gray-400 transition-colors" - placeholder="Enter story title" + placeholder="Enter custom title for this story" />

{formData.title.length}/200 characters

-
- -