Report button support, all that jazz.

This commit is contained in:
Hieuhuy Pham 2025-01-24 12:54:05 -05:00
parent 9c78ed022d
commit ad66be5039
4 changed files with 215 additions and 2 deletions

View File

@ -0,0 +1,38 @@
import { NextResponse } from 'next/server';
export async function POST(request: Request) {
try {
const API_URL = process.env.NEXT_PUBLIC_API_URL;
const API_TOKEN = process.env.STRAPI_API_TOKEN;
const body = await request.json();
if (!API_URL || !API_TOKEN) {
return NextResponse.json({ message: 'Server configuration error' }, {status: 500});
}
try {
const response = await fetch(`${API_URL}/api/reports`, {
method: 'POST',
headers: {
Authorization: `Bearer ${API_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(body),
});
const data = await response.json();
if (!response.ok) {
return NextResponse.json({ error: data }, { status: response.status });
}
return NextResponse.json({data: data}, {status: response.status});
} catch (error: any) {
NextResponse.json({message: 'Going to server error', error: error.message})
}
} catch (error: any) {
console.error('Error handling request:', error);
return NextResponse.json({ message: 'Internal Server Error', error: error.message }, { status: 500 });
}
}

View File

@ -1,5 +1,6 @@
import React from "react";
import NavigationButtons from "@/components/NavigationButtons";
import ReportButton from "@/components/ReportButton";
import ChapterRenderer from "@/components/ChapterContentRenderer";
import { Chapter } from "@/lib/types";
import { fetchChapterByBookId, fetchGlossaryByBookId } from "@/lib/api";
@ -50,6 +51,7 @@ export default async function ChapterPage(props: { params: paramsType}) {
<div className="pt-4"></div>
<ChapterRenderer content={chapter_content_html} glossary={english_glossary} />
<NavigationButtons bookId={bookId} documentId={chapterId} prevChapter={prev_chapter} nextChapter={next_chapter}/>
<ReportButton bookId={bookId} chapterId={chapterId} />
</div>
);
}

View File

@ -0,0 +1,128 @@
"use client"
import React, { useState, useRef } from "react";
interface ReportButtonProps {
bookId: string;
chapterId: string;
}
async function createReport(
error_type: string,
details: string,
book_id: string,
chapter_id: string,
) {
const payload = {
data:{
error_type: error_type,
details: details,
book: book_id,
chapter: chapter_id
}
}
const response = await fetch(
'/api/reports',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload)
}
)
if(!response.ok) {
throw new Error(`API request failed with status ${response.status}`)
}
return response.json()
}
const ReportButton: React.FC<ReportButtonProps> = ({ bookId, chapterId }) => {
const modalRef = useRef<HTMLDivElement>(null)
const [isOpen, setIsOpen] = useState(false)
const [errorType, setErrorType] = useState('')
const [details, setDetails] = useState('')
const handleSubmitReport = (event: React.FormEvent<HTMLFormElement>) => {
// Implement report submission here
event.preventDefault()
createReport(errorType,details,bookId,chapterId)
setErrorType('')
setDetails('')
setIsOpen(false)
}
const handleExit = (event: React.MouseEvent) => {
setErrorType('')
setDetails('')
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
setIsOpen(false)
}
event.stopPropagation()
}
return (
<div className="flex justify-center pt-4"
onClick={handleExit}>
<button onClick={() => setIsOpen(true)} className="px-4 py-2 bg-red-600 text-white font-semibold rounded-lg shadow-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-400 focus:ring-offset-2"> Report </button>
{isOpen && (<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
<div
className="dark:bg-gray-800 bg-white w-11/12 md:w-1/2 lg:w-1/3 p-6 rounded-lg shadow-lg relative"
ref={modalRef}
onClick={(e) => e.stopPropagation()}>
<div className="flex justify-between items-center border-b pb-3">
<h3 className="text-lg font-semibold ">Report Chapter</h3>
</div>
<div className="mt-4">
<form className="p-6 rounded-lg max-w-md mx-auto space-y-4"
onSubmit={handleSubmitReport}>
<div>
<label className="block text-md font-semibold mb-1 dark:text-gray-300" htmlFor="error-type">
Select Error Type :
</label>
<select
id="error-type"
className="block w-full border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 rounded-lg p-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
value={errorType}
onChange={(e)=> setErrorType(e.target.value)}
>
<option value="">Select your Type</option>
<option value="Spelling Error">Spelling Error</option>
<option value="Pronoun Error">Pronoun Error</option>
<option value="Formatting Error">Formatting Error</option>
<option value="Missing Content">Missing Content</option>
</select>
</div>
<div>
<label className="block text-md font-semibold mb-1" htmlFor="details">
Additional Details :
</label>
<textarea
id="details"
placeholder="Provide additional details"
value={details}
onChange={(e) => setDetails(e.target.value)}
className="block w-full h-24 border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-gray-300 rounded-lg p-2 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none overflow-y-auto"/>
</div>
<div className="flex justify-between">
<button
type="submit"
className="px-4 py-2 bg-green-500 text-white font-medium rounded-lg hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-offset-2"
>
Submit
</button>
<button
onClick={handleExit}
className="px-4 py-2 dark:bg-gray-500 bg-gray-200 rounded-lg hover:bg-gray-300"
>
Close
</button>
</div>
</form>
</div>
</div>
</div>
)}
</div>
);
}
export default ReportButton;

View File

@ -1,9 +1,36 @@
import { addDays, subDays } from "date-fns";
import { Book, Chapter, Editor, Announcement, Glossary } from "./types";
import { Book, Chapter, Editor, Announcement, Glossary, Report } from "./types";
const API_URL = process.env.NEXT_PUBLIC_API_URL as string;
const API_TOKEN = process.env.STRAPI_API_TOKEN as string;
export async function createFromAPI<T>(
endpoint: string,
payload: string,
options: RequestInit = {},
): Promise<T> {
const headers: HeadersInit = {
Authorization: `Bearer ${API_TOKEN}`,
"Content-Type": "application/json",
};
const config: RequestInit = {
method: "POST",
headers,
body: payload,
...options,
}
const response = await fetch(`${API_URL}${endpoint}`, config)
if(!response.ok) {
throw new Error(`API request failed with status ${response.status}`)
}
return response.json()
}
export async function fetchFromAPI<T>(
endpoint: string,
options: RequestInit = {}
@ -13,8 +40,9 @@ export async function fetchFromAPI<T>(
"Content-Type": "application/json",
};
const config: RequestInit = {
method: "GET", // Default method is GET
method: "GET",
headers,
...options,
};
@ -134,3 +162,20 @@ export async function fetchGlossaryByBookId(bookId: string): Promise<Glossary> {
const data = await fetchFromAPI<Glossary>(`/api/glossaries?filters[book][documentId]=${bookId}`);
return data[0];
}
export async function createReport(
error_type: string,
details: string,
book_id: string,
chapter_id: string
): Promise<Report> {
const payload = {
data: {
error_type,
details,
book: book_id,
chapter: chapter_id
}
}
const data = await createFromAPI<Report>(`/api/reports`, JSON.stringify(payload))
return data
}