Compare commits
No commits in common. "406963f6bf25eedfad324a5d39e8f68c3ce40c8d" and "838e9730a4f4b4525bfef03576a7982b9e086be8" have entirely different histories.
406963f6bf
...
838e9730a4
@ -1,39 +0,0 @@
|
|||||||
|
|
||||||
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) {
|
|
||||||
console.error('Error handling request:', error);
|
|
||||||
NextResponse.json({message: 'Going to server error'}, {status: 500});
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error handling request:', error);
|
|
||||||
return NextResponse.json({ message: 'Internal Server Error'}, { status: 500 });
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,6 +1,5 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import NavigationButtons from "@/components/NavigationButtons";
|
import NavigationButtons from "@/components/NavigationButtons";
|
||||||
import ReportButton from "@/components/ReportButton";
|
|
||||||
import ChapterRenderer from "@/components/ChapterContentRenderer";
|
import ChapterRenderer from "@/components/ChapterContentRenderer";
|
||||||
import { Chapter } from "@/lib/types";
|
import { Chapter } from "@/lib/types";
|
||||||
import { fetchChapterByBookId, fetchGlossaryByBookId } from "@/lib/api";
|
import { fetchChapterByBookId, fetchGlossaryByBookId } from "@/lib/api";
|
||||||
@ -51,7 +50,6 @@ export default async function ChapterPage(props: { params: paramsType}) {
|
|||||||
<div className="pt-4"></div>
|
<div className="pt-4"></div>
|
||||||
<ChapterRenderer content={chapter_content_html} glossary={english_glossary} />
|
<ChapterRenderer content={chapter_content_html} glossary={english_glossary} />
|
||||||
<NavigationButtons bookId={bookId} documentId={chapterId} prevChapter={prev_chapter} nextChapter={next_chapter}/>
|
<NavigationButtons bookId={bookId} documentId={chapterId} prevChapter={prev_chapter} nextChapter={next_chapter}/>
|
||||||
<ReportButton bookId={bookId} chapterId={chapterId} />
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
@ -1,107 +0,0 @@
|
|||||||
"use client"
|
|
||||||
import React, { useState, useRef } from "react";
|
|
||||||
import { createReport
|
|
||||||
|
|
||||||
} from "@/lib/api";
|
|
||||||
interface ReportButtonProps {
|
|
||||||
bookId: string;
|
|
||||||
chapterId: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = async (event: React.FormEvent<HTMLFormElement>) => {
|
|
||||||
// Implement report submission here
|
|
||||||
event.preventDefault()
|
|
||||||
const response = await createReport(errorType,details,bookId,chapterId)
|
|
||||||
//Linting be linting
|
|
||||||
if (response.status === 201){
|
|
||||||
alert('Report submitted successfully')
|
|
||||||
}
|
|
||||||
else{
|
|
||||||
alert('Failed to submit report')
|
|
||||||
}
|
|
||||||
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;
|
|
@ -4,33 +4,6 @@ import { Book, Chapter, Editor, Announcement, Glossary } from "./types";
|
|||||||
const API_URL = process.env.NEXT_PUBLIC_API_URL as string;
|
const API_URL = process.env.NEXT_PUBLIC_API_URL as string;
|
||||||
const API_TOKEN = process.env.STRAPI_API_TOKEN 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>(
|
export async function fetchFromAPI<T>(
|
||||||
endpoint: string,
|
endpoint: string,
|
||||||
options: RequestInit = {}
|
options: RequestInit = {}
|
||||||
@ -40,9 +13,8 @@ export async function fetchFromAPI<T>(
|
|||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const config: RequestInit = {
|
const config: RequestInit = {
|
||||||
method: "GET",
|
method: "GET", // Default method is GET
|
||||||
headers,
|
headers,
|
||||||
...options,
|
...options,
|
||||||
};
|
};
|
||||||
@ -161,36 +133,4 @@ export async function fetchAnnouncementById(announcementId: string): Promise<Ann
|
|||||||
export async function fetchGlossaryByBookId(bookId: string): Promise<Glossary> {
|
export async function fetchGlossaryByBookId(bookId: string): Promise<Glossary> {
|
||||||
const data = await fetchFromAPI<Glossary>(`/api/glossaries?filters[book][documentId]=${bookId}`);
|
const data = await fetchFromAPI<Glossary>(`/api/glossaries?filters[book][documentId]=${bookId}`);
|
||||||
return data[0];
|
return data[0];
|
||||||
}
|
|
||||||
|
|
||||||
export 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
|
|
||||||
}
|
}
|
@ -1,31 +0,0 @@
|
|||||||
import { NextResponse } from 'next/server';
|
|
||||||
|
|
||||||
const rateLimitCache = new Map<string, { timestamp: number; count: number }>();
|
|
||||||
const RATE_LIMIT_WINDOW_MS = 60000;
|
|
||||||
const RATE_LIMIT_MAX_REQUESTS = 5;
|
|
||||||
|
|
||||||
export function middleware(request: Request) {
|
|
||||||
const ip = request.headers.get('x-forwarded-for') || '127.0.0.1';
|
|
||||||
const now = Date.now();
|
|
||||||
|
|
||||||
const userRateLimit = rateLimitCache.get(ip) || { count: 0, timestamp: now };
|
|
||||||
|
|
||||||
if (now - userRateLimit.timestamp > RATE_LIMIT_WINDOW_MS) {
|
|
||||||
rateLimitCache.set(ip, { count: 1, timestamp: now });
|
|
||||||
} else if (userRateLimit.count >= RATE_LIMIT_MAX_REQUESTS) {
|
|
||||||
return NextResponse.json(
|
|
||||||
{ message: 'Rate limit exceeded. Please wait a moment.' },
|
|
||||||
{ status: 429 }
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
userRateLimit.count += 1;
|
|
||||||
rateLimitCache.set(ip, userRateLimit);
|
|
||||||
}
|
|
||||||
|
|
||||||
return NextResponse.next();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply middleware only to API routes
|
|
||||||
export const config = {
|
|
||||||
matcher: '/api/:path*',
|
|
||||||
};
|
|
Loading…
Reference in New Issue
Block a user