From 9fd292fe05aa159237e265a06a8ba6642de0d0c1 Mon Sep 17 00:00:00 2001 From: martinvibes Date: Mon, 22 Sep 2025 22:51:45 +0100 Subject: [PATCH] feat: enhance user management dashboard with new components and notifications --- public/strk.png.svg | 9 + .../user-management/readers/[id]/page.tsx | 53 ++- .../blockchain/wallet-connect-context.tsx | 20 +- .../dashboard/DeleteReviewModal.tsx | 75 ++++ .../dashboard/DirectNotificationContent.tsx | 229 ++++++++++++ .../dashboard/NotificationDetailModal.tsx | 124 +++++++ .../dashboard/ReviewsAndFeedbackContent.tsx | 183 ++++++++++ src/components/dashboard/SendMessageModal.tsx | 134 +++++++ .../dashboard/TransactionContent.tsx | 329 ++++++++++++++++++ 9 files changed, 1135 insertions(+), 21 deletions(-) create mode 100644 public/strk.png.svg create mode 100644 src/components/dashboard/DeleteReviewModal.tsx create mode 100644 src/components/dashboard/DirectNotificationContent.tsx create mode 100644 src/components/dashboard/NotificationDetailModal.tsx create mode 100644 src/components/dashboard/ReviewsAndFeedbackContent.tsx create mode 100644 src/components/dashboard/SendMessageModal.tsx create mode 100644 src/components/dashboard/TransactionContent.tsx diff --git a/public/strk.png.svg b/public/strk.png.svg new file mode 100644 index 0000000..be0b898 --- /dev/null +++ b/public/strk.png.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/src/app/dashboard/admin/user-management/readers/[id]/page.tsx b/src/app/dashboard/admin/user-management/readers/[id]/page.tsx index 1fe69e7..a6fa14b 100644 --- a/src/app/dashboard/admin/user-management/readers/[id]/page.tsx +++ b/src/app/dashboard/admin/user-management/readers/[id]/page.tsx @@ -7,6 +7,9 @@ import Image from "next/image"; import { Header } from "@/components/dashboard/header"; import reader1_img from "@/assets/images/reader1.png"; +import DirectNotificationContent from "@/components/dashboard/DirectNotificationContent"; +import ReviewsAndFeedbackContent from "@/components/dashboard/ReviewsAndFeedbackContent"; +import TransactionContent from "@/components/dashboard/TransactionContent"; import { Search } from "lucide-react"; @@ -67,9 +70,9 @@ const UserProfilePage = () => { const [activeTab, setActiveTab] = useState("Library"); const [activeFilter, setActiveFilter] = useState("All"); - const setShowModal = (show: boolean) => { + const setShowModal = (show: boolean) => { setSuspendModalOpen(show); - } + }; const stats = [ { label: "Following", value: "4", color: "text-blue-600" }, @@ -229,9 +232,11 @@ const UserProfilePage = () => { return ( <>
- {suspendModalOpen &&
- -
} + {suspendModalOpen && ( +
+ +
+ )}
{/* Header Section */} @@ -290,7 +295,10 @@ const UserProfilePage = () => { {/* Stats Grid */}
{stats.map((stat, index) => ( -
+
{stat.label}
@@ -340,7 +348,10 @@ const UserProfilePage = () => { {/* Library Stats */}
{libraryStats.map((stat, index) => ( -
+
{stat.label}
@@ -388,14 +399,30 @@ const UserProfilePage = () => {
)} + {/* Direct Notification Content */} + {activeTab === "Direct Notification" && ( + + )} + + {/* Transaction Content */} + {activeTab === "Transactions" && } + + {/* Reviews & Feedback Content */} + {activeTab === "Reviews & Feedback" && ( + + )} + {/* Other Tab Contents */} - {activeTab !== "Library" && ( -
-
- {activeTab} content would go here + {activeTab !== "Library" && + activeTab !== "Direct Notification" && + activeTab !== "Transactions" && + activeTab !== "Reviews & Feedback" && ( +
+
+ {activeTab} content would go here +
-
- )} + )}
diff --git a/src/components/blockchain/wallet-connect-context.tsx b/src/components/blockchain/wallet-connect-context.tsx index 93643c1..b1f88e3 100644 --- a/src/components/blockchain/wallet-connect-context.tsx +++ b/src/components/blockchain/wallet-connect-context.tsx @@ -1,5 +1,11 @@ -"use client" -import React, { createContext, useContext, ReactNode, useState, useEffect } from "react"; +"use client"; +import React, { + createContext, + useContext, + ReactNode, + useState, + useEffect, +} from "react"; import { AccountStatus, useAccount, useConnect } from "@starknet-react/core"; // Define the context type @@ -27,13 +33,13 @@ export function WalletProvider({ children }: { children: ReactNode }) { // Check if any wallet is available in the browser useEffect(() => { - const hasWallet = connectors.some(connector => connector.available()); + const hasWallet = connectors.some((connector) => connector.available()); setIsWalletDetected(hasWallet); }, [connectors]); // Handle connection errors useEffect(() => { - if (status === "error" as AccountStatus) { + if (status === ("error" as AccountStatus)) { setError(new Error("Failed to connect wallet")); } else { setError(null); @@ -59,13 +65,11 @@ export function WalletProvider({ children }: { children: ReactNode }) { error, isModalOpen, openConnectModal, - closeConnectModal + closeConnectModal, }; return ( - - {children} - + {children} ); } diff --git a/src/components/dashboard/DeleteReviewModal.tsx b/src/components/dashboard/DeleteReviewModal.tsx new file mode 100644 index 0000000..84030b5 --- /dev/null +++ b/src/components/dashboard/DeleteReviewModal.tsx @@ -0,0 +1,75 @@ +"use client"; + +import React from "react"; + +interface DeleteReviewModalProps { + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + review?: { + id: string; + bookTitle: string; + author: string; + reviewerName: string; + rating: number; + reviewText: string; + } | null; +} + +const DeleteReviewModal: React.FC = ({ + isOpen, + onClose, + onConfirm, + review, +}) => { + if (!isOpen || !review) return null; + + return ( +
+
+ {/* Warning Icon */} +
+
+
+
+ ! +
+
+
+
+ + {/* Warning Message */} +
+

+ You are about to delete{" "} + + @{review.reviewerName}'s + {" "} + Review on the book{" "} + + {review.bookTitle} by {review.author} + +

+
+ + {/* Action Buttons */} +
+ + +
+
+
+ ); +}; + +export default DeleteReviewModal; diff --git a/src/components/dashboard/DirectNotificationContent.tsx b/src/components/dashboard/DirectNotificationContent.tsx new file mode 100644 index 0000000..bc945c8 --- /dev/null +++ b/src/components/dashboard/DirectNotificationContent.tsx @@ -0,0 +1,229 @@ +"use client"; + +import React, { useState } from "react"; +import { Search } from "lucide-react"; +import NotificationDetailModal from "./NotificationDetailModal"; +import SendMessageModal from "./SendMessageModal"; + +interface Notification { + id: string; + title: string; + sender: string; + message: string; + date: string; + avatar?: string; + receiver?: string; + time?: string; + status?: string; +} + +const mockNotifications: Notification[] = [ + { + id: "1", + title: "Policy Violation", + sender: "Ola**p@gmail.com", + message: + "Your recent account activity has been flagged for violating our Terms of Service concerning unauthorized access.", + date: "13 June, 2025", + }, + { + id: "2", + title: "Policy Violation", + sender: "Ola**p@gmail.com", + message: + "Your recent account activity has been flagged for violating our Terms of Service concerning unauthorized access.", + date: "13 June, 2025", + }, + { + id: "3", + title: "Policy Violation", + sender: "Ola**p@gmail.com", + message: + "Your recent account activity has been flagged for violating our Terms of Service concerning unauthorized access.", + date: "13 June, 2025", + }, + { + id: "4", + title: "Policy Violation", + sender: "Ola**p@gmail.com", + message: + "Your recent account activity has been flagged for violating our Terms of Service concerning unauthorized access.", + date: "13 June, 2025", + }, +]; + +const NotificationCard: React.FC<{ + notification: Notification; + onViewProfile: (notification: Notification) => void; +}> = ({ notification, onViewProfile }) => { + return ( +
+
+ {/* Avatar placeholder */} +
+ +
+
+
+
+

+ {notification.title} +

+

+ Sent by: {notification.sender} +

+
+ +

+ {notification.message} +

+

{notification.date}

+
+ +
+ +
+
+
+
+
+ ); +}; + +const DirectNotificationContent: React.FC = () => { + const [activeFilter, setActiveFilter] = useState<"All" | "Sent by Me">("All"); + const [searchQuery, setSearchQuery] = useState(""); + const [selectedNotification, setSelectedNotification] = + useState(null); + const [isDetailModalOpen, setIsDetailModalOpen] = useState(false); + const [isSendModalOpen, setIsSendModalOpen] = useState(false); + + const filteredNotifications = mockNotifications.filter((notification) => { + const matchesFilter = + activeFilter === "All" || activeFilter === "Sent by Me"; + const matchesSearch = + notification.title.toLowerCase().includes(searchQuery.toLowerCase()) || + notification.message.toLowerCase().includes(searchQuery.toLowerCase()) || + notification.sender.toLowerCase().includes(searchQuery.toLowerCase()); + + return matchesFilter && matchesSearch; + }); + + const handleViewProfile = (notification: Notification) => { + setSelectedNotification(notification); + setIsDetailModalOpen(true); + }; + + const handleSendMessage = (title: string, body: string) => { + console.log("Sending message:", { title, body }); + // Here you would typically send the message to your backend + }; + + const closeDetailModal = () => { + setIsDetailModalOpen(false); + setSelectedNotification(null); + }; + + const closeSendModal = () => { + setIsSendModalOpen(false); + }; + + return ( +
+ {/* Navigation and Search Bar */} +
+ {/* Filter Tabs */} +
+ + +
+ + setSearchQuery(e.target.value)} + className="pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm" + /> +
+
+ + {/* Search and Send Message */} +
+ {/* Search Bar */} + + {/* Send Message Button */} + +
+
+ + {/* Notifications List */} +
+ {filteredNotifications.map((notification) => ( + + ))} +
+ + {/* Empty State */} + {filteredNotifications.length === 0 && ( +
+
+ No notifications found +
+
+ {searchQuery + ? "Try adjusting your search terms" + : "No notifications to display"} +
+
+ )} + + {/* Modals */} + + + +
+ ); +}; + +export default DirectNotificationContent; diff --git a/src/components/dashboard/NotificationDetailModal.tsx b/src/components/dashboard/NotificationDetailModal.tsx new file mode 100644 index 0000000..82eaf17 --- /dev/null +++ b/src/components/dashboard/NotificationDetailModal.tsx @@ -0,0 +1,124 @@ +"use client"; + +import React from "react"; +import { ArrowLeft } from "lucide-react"; + +interface NotificationDetailModalProps { + isOpen: boolean; + onClose: () => void; + notification: { + id: string; + title: string; + sender: string; + message: string; + date: string; + receiver?: string; + time?: string; + status?: string; + } | null; +} + +const NotificationDetailModal: React.FC = ({ + isOpen, + onClose, + notification, +}) => { + if (!isOpen || !notification) return null; + + return ( +
+
+ {/* Header with Back Button */} +
+ +
+ + {/* Content */} +
+ {/* Sender Info */} +
+
+ + Sent by: {notification.sender} + +
+
+ + {/* Title */} +

+ {notification.title} +

+ + {/* Banner Image */} +
+
+
+
+
+ COMING +
+
+ SOON +
+
+
+ {/* Subtle pattern overlay */} +
+
+
+
+
+
+
+
+ + {/* Message Content */} +
+

+ We are excited to announce a series of improvements and new + features in our subscription plans. Starting this month, + subscribers will enjoy enhanced benefits tailored to provide + greater value and a more seamless experience. +

+
+ + {/* Details Table */} +
+
+

+ Notification Details +

+
+
+
+ Receiver: + Reader +
+
+ Date: + 12 March, 2024 +
+
+ Time: + 12:14 PM +
+
+ Status: + + Sent + +
+
+
+
+
+
+ ); +}; + +export default NotificationDetailModal; diff --git a/src/components/dashboard/ReviewsAndFeedbackContent.tsx b/src/components/dashboard/ReviewsAndFeedbackContent.tsx new file mode 100644 index 0000000..6d27123 --- /dev/null +++ b/src/components/dashboard/ReviewsAndFeedbackContent.tsx @@ -0,0 +1,183 @@ +"use client"; + +import React, { useState } from "react"; +import { Star, Trash2 } from "lucide-react"; +import DeleteReviewModal from "./DeleteReviewModal"; + +interface Review { + id: string; + bookTitle: string; + author: string; + reviewerName: string; + reviewerAvatar: string; + timestamp: string; + rating: number; + reviewText: string; +} + +const mockReviews: Review[] = [ + { + id: "1", + bookTitle: "Native Invisible", + author: "Darrin Collins", + reviewerName: "Adeja Samad", + reviewerAvatar: "/api/placeholder/40/40", + timestamp: "Yesterday", + rating: 4, + reviewText: + "This was a great read, and I was hooked. However, the death of my favorite character impacted my overall enjoyment, which is why I'm rating it 4 stars instead of 5.", + }, + { + id: "2", + bookTitle: "Love at Night", + author: "Joe Graphix", + reviewerName: "Adeja Samad", + reviewerAvatar: "/api/placeholder/40/40", + timestamp: "Yesterday", + rating: 4, + reviewText: + "This was a great read, and I was hooked. However, the death of my favorite character impacted my overall enjoyment, which is why I'm rating it 4 stars instead of 5.", + }, + { + id: "3", + bookTitle: "Designing For Humans", + author: "Joe Terkuma", + reviewerName: "Adeja Samad", + reviewerAvatar: "/api/placeholder/40/40", + timestamp: "Yesterday", + rating: 4, + reviewText: + "This was a great read, and I was hooked. However, the death of my favorite character impacted my overall enjoyment, which is why I'm rating it 4 stars instead of 5.", + }, +]; + +const ReviewCard: React.FC<{ + review: Review; + onDelete: (review: Review) => void; +}> = ({ review, onDelete }) => { + const renderStars = (rating: number) => { + const stars = []; + for (let i = 1; i <= 5; i++) { + stars.push( + + ); + } + return stars; + }; + + return ( +
+ {/* Book Title Tag */} +
+ + {review.bookTitle} by {review.author} + +
+ + {/* Review Content */} +
+ {/* Reviewer Avatar */} +
+
+ {review.reviewerName.charAt(0)} +
+
+ + {/* Review Details */} +
+ {/* Reviewer Info and Rating */} +
+
+

+ {review.reviewerName} +

+

{review.timestamp}

+
+ + {/* Delete Button and Rating */} +
+ +
+ {renderStars(review.rating)} +
+
+
+ + {/* Review Text */} +

+ {review.reviewText} +

+
+
+
+ ); +}; + +const ReviewsAndFeedbackContent: React.FC = () => { + const [reviews, setReviews] = useState(mockReviews); + const [reviewToDelete, setReviewToDelete] = useState(null); + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + + const handleDeleteReview = (review: Review) => { + setReviewToDelete(review); + setIsDeleteModalOpen(true); + }; + + const confirmDelete = () => { + if (reviewToDelete) { + setReviews(reviews.filter((review) => review.id !== reviewToDelete.id)); + setReviewToDelete(null); + setIsDeleteModalOpen(false); + } + }; + + const cancelDelete = () => { + setReviewToDelete(null); + setIsDeleteModalOpen(false); + }; + + return ( +
+ {/* Reviews List */} +
+ {reviews.map((review) => ( + + ))} +
+ + {/* Empty State */} + {reviews.length === 0 && ( +
+
No reviews found
+
+ This user hasn't received any reviews yet +
+
+ )} + + {/* Delete Confirmation Modal */} + +
+ ); +}; + +export default ReviewsAndFeedbackContent; diff --git a/src/components/dashboard/SendMessageModal.tsx b/src/components/dashboard/SendMessageModal.tsx new file mode 100644 index 0000000..ea88f5c --- /dev/null +++ b/src/components/dashboard/SendMessageModal.tsx @@ -0,0 +1,134 @@ +"use client"; + +import React, { useState } from "react"; +import { ArrowLeft, Bold, Italic, Underline, Image, Link } from "lucide-react"; + +interface SendMessageModalProps { + isOpen: boolean; + onClose: () => void; + onSend: (title: string, body: string) => void; +} + +const SendMessageModal: React.FC = ({ + isOpen, + onClose, + onSend, +}) => { + const [title, setTitle] = useState(""); + const [body, setBody] = useState(""); + + const handleSend = () => { + if (title.trim() && body.trim()) { + onSend(title, body); + setTitle(""); + setBody(""); + onClose(); + } + }; + + const handleCancel = () => { + setTitle(""); + setBody(""); + onClose(); + }; + + if (!isOpen) return null; + + return ( +
+
+ {/* Header with Back Button */} +
+ +
+ + {/* Content */} +
+ {/* Title Input */} +
+ setTitle(e.target.value)} + className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent text-lg" + /> + + {/* Title Formatting Options */} +
+ + + +
+
+ + {/* Body Input */} +
+