aboutsummaryrefslogtreecommitdiff
path: root/web/src/app/quotes/page.js
diff options
context:
space:
mode:
authorAndrew Lee <andrew@alee14.me>2025-03-25 17:23:30 -0400
committerAndrew Lee <andrew@alee14.me>2025-03-25 17:23:30 -0400
commit2c783bdb703e4ad69c9f3f846c2c9e6a527ccc80 (patch)
treee02587b4dca4bd7027b4cc012d458b18392643ea /web/src/app/quotes/page.js
parent1c12d378d66b92b1674acd17640f2bac752da289 (diff)
downloadAleeBot-2c783bdb703e4ad69c9f3f846c2c9e6a527ccc80.tar.gz
AleeBot-2c783bdb703e4ad69c9f3f846c2c9e6a527ccc80.tar.bz2
AleeBot-2c783bdb703e4ad69c9f3f846c2c9e6a527ccc80.zip
Implemented admin dashboard
Diffstat (limited to 'web/src/app/quotes/page.js')
-rw-r--r--web/src/app/quotes/page.js247
1 files changed, 247 insertions, 0 deletions
diff --git a/web/src/app/quotes/page.js b/web/src/app/quotes/page.js
new file mode 100644
index 0000000..27ae439
--- /dev/null
+++ b/web/src/app/quotes/page.js
@@ -0,0 +1,247 @@
+'use client';
+import { useState, useEffect } from "react";
+import Card from "@/app/components/Card";
+import Navbar from "@/app/components/Navbar";
+import { fetchWithAuth } from "@/utils/api";
+
+export default function Quotes() {
+ const [pendingQuotes, setPendingQuotes] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [message, setMessage] = useState(null);
+ const [formData, setFormData] = useState({
+ author: '',
+ authorImage: '',
+ quote: '',
+ year: '',
+ submitterID: ''
+ });
+
+ useEffect(() => {
+ fetchPendingQuotes();
+ }, []);
+
+ const fetchPendingQuotes = async () => {
+ try {
+ setLoading(true);
+ const response = await fetchWithAuth('/api/quotes/pending');
+
+ if (!response.ok) {
+ throw new Error('Failed to fetch pending quotes');
+ }
+
+ const data = await response.json();
+ setPendingQuotes(data);
+ } catch (err) {
+ setError(err.message);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleInputChange = (e) => {
+ const { name, value } = e.target;
+ setFormData({
+ ...formData,
+ [name]: value
+ });
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setMessage(null);
+
+ try {
+ const response = await fetchWithAuth('/api/quotes/add', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify(formData)
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to submit quote');
+ }
+
+ setMessage({
+ type: 'success',
+ text: 'Quote submitted successfully'
+ });
+
+ // Reset form
+ setFormData({
+ author: '',
+ authorImage: '',
+ quote: '',
+ year: '',
+ submitterID: ''
+ });
+ } catch (err) {
+ setMessage({
+ type: 'error',
+ text: err.message
+ });
+ }
+ };
+
+ const handleApproveQuote = async (id) => {
+ if (window.confirm('Are you sure you want to approve this quote?')) {
+ try {
+ const response = await fetchWithAuth('/api/quotes/approve', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ id })
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to approve quote');
+ }
+
+ setMessage({
+ type: 'success',
+ text: 'Quote approved successfully'
+ });
+
+ // Refresh quotes
+ fetchPendingQuotes();
+ } catch (err) {
+ setMessage({
+ type: 'error',
+ text: err.message
+ });
+ }
+ }
+ };
+
+ const handleRejectQuote = async (id) => {
+ if (window.confirm('Are you sure you want to reject this quote?')) {
+ try {
+ const response = await fetchWithAuth('/api/quotes/reject', {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({ id })
+ });
+
+ if (!response.ok) {
+ throw new Error('Failed to reject quote');
+ }
+
+ setMessage({
+ type: 'success',
+ text: 'Quote rejected successfully'
+ });
+
+ // Refresh quotes
+ fetchPendingQuotes();
+ } catch (err) {
+ setMessage({
+ type: 'error',
+ text: err.message
+ });
+ }
+ }
+ };
+
+ return (
+ <>
+ <Navbar />
+ <div className="flex flex-col gap-4 p-12">
+ <h1 className="text-3xl">Submit New Quote</h1>
+ <form className="flex flex-col gap-4 px-20" onSubmit={handleSubmit}>
+ <input
+ name="author"
+ type="text"
+ placeholder="Author"
+ value={formData.author}
+ onChange={handleInputChange}
+ required
+ />
+ <input
+ name="authorImage"
+ type="url"
+ placeholder="Author URL"
+ value={formData.authorImage}
+ onChange={handleInputChange}
+ required
+ />
+ <textarea
+ name="quote"
+ placeholder="Quote"
+ value={formData.quote}
+ onChange={handleInputChange}
+ required
+ />
+ <input
+ name="year"
+ type="number"
+ placeholder="Year"
+ value={formData.year}
+ onChange={handleInputChange}
+ required
+ />
+ <input
+ name="submitterID"
+ type="text"
+ placeholder="Submitter ID"
+ value={formData.submitterID}
+ onChange={handleInputChange}
+ required
+ />
+ <button
+ type="submit"
+ className="bg-blue-500 hover:bg-blue-700 text-white py-2 px-4 rounded"
+ >
+ Submit
+ </button>
+ </form>
+
+ {message && (
+ <div className={`p-4 rounded ${message.type === 'success' ? 'bg-green-800 text-green-200' : 'bg-red-800 text-red-200'}`}>
+ {message.text}
+ </div>
+ )}
+
+
+ <h1 className="text-3xl">Pending Quotes</h1>
+
+ {loading && <p>Loading quotes...</p>}
+ {error && <p className="text-red-500">Error: {error}</p>}
+
+ {!loading && pendingQuotes.length === 0 && (
+ <p>No pending quotes found.</p>
+ )}
+
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4">
+ {pendingQuotes.map(quote => (
+ <Card key={quote.id}>
+ <h2 className="text-xl font-medium">{quote.author}</h2>
+ <p>Author URL: {quote.authorImage}</p>
+ <p>{quote.quote}</p>
+ <small className="block mb-3">- {quote.year}</small>
+ <p className="text-sm text-gray-400 mb-2">Submitted by: {quote.submitterAuthor || quote.submitterID}</p>
+ <div className="flex gap-3 mt-2">
+ <button
+ onClick={() => handleApproveQuote(quote.id)}
+ className="bg-green-600 hover:bg-green-500 text-white py-1 px-3 rounded"
+ >
+ Approve
+ </button>
+ <button
+ onClick={() => handleRejectQuote(quote.id)}
+ className="bg-red-600 hover:bg-red-500 text-white py-1 px-3 rounded"
+ >
+ Reject
+ </button>
+ </div>
+ </Card>
+ ))}
+ </div>
+ </div>
+ </>
+ );
+}