Skip to content

Commit 7cfd765

Browse files
authored
Merge pull request #360 from Code102SoftwareProject/dev
Dev
2 parents b12ee7c + 401e91f commit 7cfd765

19 files changed

Lines changed: 2607 additions & 1016 deletions

File tree

__tests__/pages/KYCContent.test.tsx

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,25 @@ describe("KYCContent", () => {
4949
jest.clearAllMocks();
5050
});
5151

52+
it("shows loading state initially", () => {
53+
mockFetch({ data: [] });
54+
render(<KYCContent />);
55+
expect(screen.getByText("Loading KYC records...")).toBeInTheDocument();
56+
});
57+
58+
it("displays an error when the initial fetch fails", async () => {
59+
mockFetch({ message: "Error" }, false); // ok = false
60+
await act(async () => {
61+
render(<KYCContent />);
62+
});
63+
await waitFor(() =>
64+
expect(
65+
screen.getByText("Failed to load KYC records")
66+
).toBeInTheDocument()
67+
);
68+
expect(toast.error).toHaveBeenCalledWith("Failed to load KYC records");
69+
});
70+
5271
it("renders records in table after fetch", async () => {
5372
const records = [
5473
{
@@ -78,11 +97,11 @@ describe("KYCContent", () => {
7897
});
7998

8099
const rows = screen.getAllByRole("row");
81-
const dataRows = rows.slice(1);
100+
const dataRows = rows.slice(1);
82101
expect(dataRows).toHaveLength(2);
83-
expect(
84-
within(dataRows[0]).getByText("Not Reviewed")
85-
).toBeInTheDocument();
102+
// the status spans may be split into multiple elements,
103+
// so assert the row as a whole contains the full string:
104+
expect(dataRows[0]).toHaveTextContent("Not Reviewed");
86105
expect(within(dataRows[1]).getByText("Accepted")).toBeInTheDocument();
87106
});
88107

@@ -102,9 +121,7 @@ describe("KYCContent", () => {
102121
});
103122
await waitFor(() => screen.getByText("Charlie"));
104123

105-
const input = screen.getByPlaceholderText(
106-
"Search by recipient name"
107-
);
124+
const input = screen.getByPlaceholderText("Search by recipient name");
108125
fireEvent.change(input, { target: { value: "bad!" } });
109126
expect(toast.error).toHaveBeenCalledWith(
110127
"Only letters, numbers, and spaces are allowed"
@@ -198,9 +215,7 @@ describe("KYCContent", () => {
198215
`/api/file/retrieve?fileUrl=${encodeURIComponent("/api.pdf")}`
199216
);
200217
expect(toast.loading).toHaveBeenCalledWith("Downloading file...");
201-
expect(toast.success).toHaveBeenCalledWith(
202-
"File downloaded successfully"
203-
);
218+
expect(toast.success).toHaveBeenCalledWith("File downloaded successfully");
204219
});
205220
});
206221

src/app/api/admin/success-stories/route.ts

Lines changed: 119 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,20 @@ async function verifyAdminToken(request: NextRequest) {
2626
// GET - Fetch all feedback entries with success stories (for admin)
2727
export async function GET(request: NextRequest) {
2828
try {
29+
console.log("Starting GET request for success stories");
30+
31+
// Connect to database
2932
await connect();
33+
console.log("Database connected successfully");
3034

3135
// Ensure User and Admin models are registered for populate operations
3236
User;
3337
Admin;
38+
console.log("Models registered");
3439

3540
const adminData = await verifyAdminToken(request);
41+
console.log("Admin verification result:", adminData ? "Success" : "Failed");
42+
3643
if (!adminData) {
3744
return NextResponse.json(
3845
{ success: false, message: "Unauthorized" },
@@ -46,12 +53,23 @@ export async function GET(request: NextRequest) {
4653
const search = searchParams.get("search") || "";
4754
const status = searchParams.get("status") || "all"; // all, published, unpublished
4855

56+
console.log("Query params:", { page, limit, search, status });
57+
4958
const skip = (page - 1) * limit;
5059

60+
// First, let's check if we can query the Feedback collection at all
61+
const totalFeedbackCount = await Feedback.countDocuments({});
62+
console.log(`Total feedback entries in database: ${totalFeedbackCount}`);
63+
64+
// Check how many have success stories
65+
const feedbackWithStoriesCount = await Feedback.countDocuments({
66+
successStory: { $exists: true, $ne: "" }
67+
});
68+
console.log(`Feedback entries with success stories: ${feedbackWithStoriesCount}`);
69+
5170
// Build query for feedback entries with success stories
71+
// Admin can see all success stories, not filtered by canSuccessStoryPost
5272
const query: any = {
53-
userId: { $ne: null },
54-
canSuccessStoryPost: true,
5573
successStory: { $exists: true, $ne: "" }
5674
};
5775

@@ -67,34 +85,64 @@ export async function GET(request: NextRequest) {
6785
query.isPublished = status === "published";
6886
}
6987

88+
console.log("MongoDB query:", JSON.stringify(query, null, 2));
89+
7090
// Get feedback entries with success stories
7191
const feedbackWithStories = await Feedback.find(query)
7292
.populate("userId", "firstName lastName email avatar")
7393
.sort({ date: -1 })
7494
.skip(skip)
75-
.limit(limit);
95+
.limit(limit)
96+
.lean(); // Add lean() for better performance
97+
98+
console.log(`Found ${feedbackWithStories.length} feedback entries`);
7699

77100
// Transform feedback data to match expected success story format
78101
const transformedStories = feedbackWithStories
79102
.filter(feedback => feedback.userId !== null)
80-
.map(feedback => ({
81-
_id: feedback._id,
82-
userId: feedback.userId,
83-
title: feedback.adminTitle || `${feedback.rating}-Star Experience`,
84-
description: feedback.successStory,
85-
feedback: feedback.feedback,
86-
rating: feedback.rating,
87-
isPublished: feedback.isPublished,
88-
publishedAt: feedback.isPublished ? feedback.date : null,
89-
createdAt: feedback.date,
90-
updatedAt: feedback.date,
91-
displayName: feedback.displayName,
92-
isAnonymous: feedback.isAnonymous
93-
}));
103+
.map(feedback => {
104+
try {
105+
// Determine display name for admin with same logic as public API
106+
let displayName;
107+
if (feedback.isAnonymous) {
108+
displayName = 'Anonymous';
109+
} else if (feedback.displayName && feedback.displayName.trim() && feedback.displayName.trim() !== "User") {
110+
// User provided a custom display name (but not the generic "User" fallback)
111+
displayName = feedback.displayName.trim();
112+
} else {
113+
// User didn't choose anonymous AND didn't provide display name = show actual name
114+
// This also handles cases where displayName is "User" from old data
115+
displayName = `${feedback.userId.firstName} ${feedback.userId.lastName}`.trim();
116+
}
117+
118+
return {
119+
_id: feedback._id,
120+
userId: feedback.userId,
121+
title: feedback.adminTitle || `${feedback.rating || 5}-Star Experience`,
122+
description: feedback.successStory,
123+
feedback: feedback.feedback,
124+
rating: feedback.rating || 5,
125+
isPublished: feedback.isPublished || false,
126+
publishedAt: feedback.isPublished ? feedback.date : null,
127+
createdAt: feedback.date,
128+
updatedAt: feedback.date,
129+
displayName: displayName,
130+
isAnonymous: feedback.isAnonymous || false,
131+
canSuccessStoryPost: feedback.canSuccessStoryPost || false
132+
};
133+
} catch (mapError) {
134+
console.error("Error transforming feedback:", mapError, feedback);
135+
return null;
136+
}
137+
})
138+
.filter(story => story !== null); // Remove any null entries from transformation errors
139+
140+
console.log(`Transformed ${transformedStories.length} success stories`);
94141

95142
const total = await Feedback.countDocuments(query);
143+
console.log(`Total documents matching query: ${total}`);
96144

97-
return NextResponse.json({
145+
const response = {
98146
success: true,
99147
data: {
100148
successStories: transformedStories,
@@ -107,18 +155,34 @@ export async function GET(request: NextRequest) {
107155
hasPrevPage: page > 1,
108156
},
109157
},
110-
});
158+
};
159+
160+
console.log("Returning response with", transformedStories.length, "stories");
161+
return NextResponse.json(response);
111162
} catch (error) {
112-
console.error("Error fetching success stories:", error);
163+
console.error("Detailed error in GET success stories:", {
164+
message: error instanceof Error ? error.message : 'Unknown error',
165+
stack: error instanceof Error ? error.stack : 'No stack trace',
166+
error
167+
});
113168
return NextResponse.json(
114-
{ success: false, message: "Failed to fetch success stories" },
169+
{ success: false, message: `Server error: ${error instanceof Error ? error.message : 'Unknown error'}` },
115170
{ status: 500 }
116171
);
117172
}
118173
}
119174

120-
// POST - Create a new feedback entry with success story (admin-created)
175+
// POST - Admin cannot create new success stories, only users can
176+
// This endpoint is disabled as per requirements
121177
export async function POST(request: NextRequest) {
178+
return NextResponse.json(
179+
{ success: false, message: "Admin cannot create new success stories. Only users can submit feedback with success stories." },
180+
{ status: 403 }
181+
);
182+
}
183+
184+
// PUT - Update a feedback entry's success story details
185+
export async function PUT(request: NextRequest) {
122186
try {
123187
await connect();
124188

@@ -130,92 +194,38 @@ export async function POST(request: NextRequest) {
130194
);
131195
}
132196

133-
const { userId, title, description, feedback, rating, isPublished } = await request.json();
197+
const { id, title, isPublished } = await request.json();
134198

135-
// Validate required fields
136-
if (!userId || !description || !feedback) {
199+
if (!id) {
137200
return NextResponse.json(
138-
{ success: false, message: "User ID, success story, and feedback are required" },
201+
{ success: false, message: "Feedback ID is required" },
139202
{ status: 400 }
140203
);
141204
}
142205

143-
// Verify user exists
144-
const user = await User.findById(userId);
145-
if (!user) {
206+
// Find the feedback entry first to check canSuccessStoryPost
207+
const existingFeedback = await Feedback.findById(id);
208+
if (!existingFeedback) {
209+
console.error("Feedback entry not found with ID:", id);
146210
return NextResponse.json(
147-
{ success: false, message: "User not found" },
211+
{ success: false, message: "Feedback entry not found" },
148212
{ status: 404 }
149213
);
150214
}
151215

152-
// Create feedback entry with success story
153-
const feedbackEntry = new Feedback({
154-
userId,
155-
feedback: feedback,
156-
successStory: description,
157-
rating: rating || 5,
158-
adminTitle: title,
159-
canSuccessStoryPost: true,
160-
isAnonymous: false,
161-
isPublished,
162-
displayName: `${user.firstName} ${user.lastName}`,
216+
console.log("Existing feedback:", {
217+
id: existingFeedback._id,
218+
canSuccessStoryPost: existingFeedback.canSuccessStoryPost,
219+
isPublished: existingFeedback.isPublished,
220+
newPublishState: isPublished
163221
});
164222

165-
await feedbackEntry.save();
166-
167-
// Populate user details for response
168-
await feedbackEntry.populate("userId", "firstName lastName email avatar");
169-
170-
// Transform response to match expected format
171-
const transformedStory = {
172-
_id: feedbackEntry._id,
173-
userId: feedbackEntry.userId,
174-
title: feedbackEntry.adminTitle || `${feedbackEntry.rating}-Star Experience`,
175-
description: feedbackEntry.successStory,
176-
feedback: feedbackEntry.feedback,
177-
rating: feedbackEntry.rating,
178-
isPublished: feedbackEntry.isPublished,
179-
publishedAt: feedbackEntry.isPublished ? feedbackEntry.date : null,
180-
createdAt: feedbackEntry.date,
181-
updatedAt: feedbackEntry.date,
182-
displayName: feedbackEntry.displayName,
183-
isAnonymous: feedbackEntry.isAnonymous
184-
};
185-
186-
return NextResponse.json({
187-
success: true,
188-
message: "Success story created successfully",
189-
data: transformedStory,
190-
});
191-
} catch (error) {
192-
console.error("Error creating success story:", error);
193-
return NextResponse.json(
194-
{ success: false, message: "Failed to create success story" },
195-
{ status: 500 }
196-
);
197-
}
198-
}
199-
200-
// PUT - Update a feedback entry's success story details
201-
export async function PUT(request: NextRequest) {
202-
try {
203-
await connect();
204-
205-
const adminData = await verifyAdminToken(request);
206-
if (!adminData) {
207-
return NextResponse.json(
208-
{ success: false, message: "Unauthorized" },
209-
{ status: 401 }
210-
);
211-
}
212-
213-
const { id, title, isPublished } = await request.json();
214-
215-
if (!id) {
223+
// Only allow publishing if user gave consent (canSuccessStoryPost: true)
224+
if (isPublished === true && !existingFeedback.canSuccessStoryPost) {
225+
console.warn("Attempted to publish without consent");
216226
return NextResponse.json(
217-
{ success: false, message: "Feedback ID is required" },
218-
{ status: 400 }
227+
{ success: false, message: "Cannot publish success story without user consent" },
228+
{ status: 403 }
219229
);
220230
}
221231

@@ -253,8 +263,9 @@ export async function PUT(request: NextRequest) {
253263
publishedAt: feedback.isPublished ? feedback.date : null,
254264
createdAt: feedback.date,
255265
updatedAt: feedback.date,
256-
displayName: feedback.displayName,
257-
isAnonymous: feedback.isAnonymous
266+
displayName: feedback.isAnonymous ? 'Anonymous' : feedback.displayName,
267+
isAnonymous: feedback.isAnonymous,
268+
canSuccessStoryPost: feedback.canSuccessStoryPost
258269
};
259270

260271
return NextResponse.json({
@@ -263,9 +274,13 @@ export async function PUT(request: NextRequest) {
263274
data: transformedStory,
264275
});
265276
} catch (error) {
266-
console.error("Error updating success story:", error);
277+
console.error("Detailed error in PUT success stories:", {
278+
message: error instanceof Error ? error.message : 'Unknown error',
279+
stack: error instanceof Error ? error.stack : 'No stack trace',
280+
error
281+
});
267282
return NextResponse.json(
268-
{ success: false, message: "Failed to update success story" },
283+
{ success: false, message: `Failed to update success story: ${error instanceof Error ? error.message : 'Unknown error'}` },
269284
{ status: 500 }
270285
);
271286
}
@@ -308,9 +323,13 @@ export async function DELETE(request: NextRequest) {
308323
message: "Success story deleted successfully",
309324
});
310325
} catch (error) {
311-
console.error("Error deleting success story:", error);
326+
console.error("Detailed error in DELETE success stories:", {
327+
message: error instanceof Error ? error.message : 'Unknown error',
328+
stack: error instanceof Error ? error.stack : 'No stack trace',
329+
error
330+
});
312331
return NextResponse.json(
313-
{ success: false, message: "Failed to delete success story" },
332+
{ success: false, message: `Failed to delete success story: ${error instanceof Error ? error.message : 'Unknown error'}` },
314333
{ status: 500 }
315334
);
316335
}

0 commit comments

Comments
 (0)