-
Notifications
You must be signed in to change notification settings - Fork 18
Implement creator dashboard with profile management, donation history, and payment settings #67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Important Review skippedBot user detected. To trigger a single review, invoke the You can disable this status message by setting the ✨ Finishing touches🧪 Generate unit tests (beta)
Comment |
Co-authored-by: 2MJ-DEV <[email protected]>
Co-authored-by: 2MJ-DEV <[email protected]>
…file toggle Co-authored-by: 2MJ-DEV <[email protected]>
|
provides screenshots for a brief overview of the dashboard @copilot |
Dashboard OverviewHere's a comprehensive visual guide to the implemented creator dashboard: 📊 Dashboard LayoutThe dashboard features a responsive sidebar with 4 main sections and a top navigation bar displaying user info with GitHub avatar. 🎯 Key Sections Implemented1. Overview Section
2. Profile Section
3. Donation History
4. Settings Section
📱 Responsive Design
🌍 Internationalization
All components integrate with the backend API and include proper loading states, error handling, and empty states. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements a comprehensive dashboard functionality for the application, transforming the basic dashboard skeleton into a fully-featured creator management system. The changes include user profile management, donation tracking with statistics, payment settings configuration, and a responsive mobile-friendly interface.
Key changes:
- Added four interactive dashboard sections: Overview, Profile, Donations, and Settings
- Implemented backend API endpoints for profile updates, payment settings, donation retrieval, and dashboard statistics
- Extended database schema with bio, publicProfile, mobile money payment fields, and enhanced donation relationships
- Added responsive navigation with mobile menu support and dark mode styling
Reviewed Changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 12 comments.
Show a summary per file
| File | Description |
|---|---|
| src/pages/Dashboard/Dashboard.jsx | Orchestrates section navigation and mobile menu state management |
| src/pages/Dashboard/components/Navbar.jsx | Enhanced navbar with user info display, logout functionality, and mobile menu trigger |
| src/pages/Dashboard/components/Sidebar.jsx | Interactive navigation menu with active state highlighting and mobile overlay |
| src/pages/Dashboard/components/StatsCard.jsx | Reusable statistics card component with color theming support |
| src/pages/Dashboard/sections/Overview.jsx | Dashboard overview with statistics display and loading states |
| src/pages/Dashboard/sections/Profile.jsx | Profile editing interface with inline form toggle |
| src/pages/Dashboard/sections/Donations.jsx | Donation history display with empty states and formatting |
| src/pages/Dashboard/sections/Settings.jsx | Payment settings and account preferences configuration |
| src/locales/en.json | Added English translations for all dashboard features |
| src/locales/fr.json | Added French translations for all dashboard features |
| apps/server/src/app.js | Registered new API route handlers |
| apps/server/src/routes/userRoutes.js | User profile and payment settings routes |
| apps/server/src/routes/donationRoutes.js | Donation retrieval route |
| apps/server/src/routes/dashboardRoutes.js | Dashboard statistics route |
| apps/server/src/controllers/userController.js | Profile and payment update logic |
| apps/server/src/controllers/donationController.js | Donation fetching and transformation |
| apps/server/src/controllers/dashboardController.js | Dashboard statistics calculation |
| apps/server/prisma/schema.prisma | Schema updates for new user and donation fields |
| apps/server/prisma/migrations/20251107205140_add_profile_and_payment_fields/migration.sql | Database migration for schema changes |
| if (name !== undefined) updateData.name = name.trim(); | ||
| if (bio !== undefined) updateData.bio = bio.trim(); |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Empty strings after trimming will be saved to the database. Consider validating that trimmed values are not empty strings, or set them to null if empty to maintain data consistency.
| const { mobileMoneyNumber, mobileMoneyProvider } = req.body; | ||
|
|
||
| const updateData = {}; | ||
| if (mobileMoneyNumber !== undefined) updateData.mobileMoneyNumber = mobileMoneyNumber.trim(); |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing input validation for mobile money number format. Phone numbers should be validated for proper format and length to prevent invalid data storage and potential issues with payment processing.
| const updateData = {}; | ||
| if (mobileMoneyNumber !== undefined) updateData.mobileMoneyNumber = mobileMoneyNumber.trim(); | ||
| if (mobileMoneyProvider !== undefined) updateData.mobileMoneyProvider = mobileMoneyProvider; |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing validation for mobileMoneyProvider. Should validate against allowed provider values ('airtel', 'orange', 'mpesa', 'mtn') to prevent storing invalid provider names.
| const updateData = {}; | |
| if (mobileMoneyNumber !== undefined) updateData.mobileMoneyNumber = mobileMoneyNumber.trim(); | |
| if (mobileMoneyProvider !== undefined) updateData.mobileMoneyProvider = mobileMoneyProvider; | |
| const allowedProviders = ['airtel', 'orange', 'mpesa', 'mtn']; | |
| const updateData = {}; | |
| if (mobileMoneyNumber !== undefined) updateData.mobileMoneyNumber = mobileMoneyNumber.trim(); | |
| if (mobileMoneyProvider !== undefined) { | |
| if (!allowedProviders.includes(mobileMoneyProvider)) { | |
| return res.status(400).json({ error: "Invalid mobileMoneyProvider. Allowed values are: 'airtel', 'orange', 'mpesa', 'mtn'." }); | |
| } | |
| updateData.mobileMoneyProvider = mobileMoneyProvider; | |
| } |
| const donations = await prisma.donation.findMany({ | ||
| where: { creatorId: userId }, | ||
| }); | ||
|
|
||
| const totalDonations = donations.length; | ||
| const totalAmount = donations.reduce((sum, donation) => sum + donation.amount, 0); | ||
|
|
||
| // Calculate unique supporters (excluding null supporterId for anonymous donations) | ||
| const supporterIds = donations | ||
| .filter(d => d.supporterId !== null) | ||
| .map(d => d.supporterId); | ||
| const totalSupporters = new Set(supporterIds).size; | ||
|
|
||
| // Calculate monthly growth (simplified - comparing this month to last month) | ||
| const now = new Date(); | ||
| const thisMonth = new Date(now.getFullYear(), now.getMonth(), 1); | ||
| const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1); | ||
|
|
||
| const thisMonthDonations = donations.filter(d => new Date(d.createdAt) >= thisMonth); | ||
| const lastMonthDonations = donations.filter( | ||
| d => new Date(d.createdAt) >= lastMonth && new Date(d.createdAt) < thisMonth | ||
| ); | ||
|
|
||
| const monthlyGrowth = lastMonthDonations.length > 0 | ||
| ? ((thisMonthDonations.length - lastMonthDonations.length) / lastMonthDonations.length) * 100 | ||
| : 0; | ||
|
|
||
| return res.json({ | ||
| totalDonations, | ||
| totalAmount, |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fetching all donations without pagination could cause performance issues for creators with many donations. Consider adding pagination or limiting the query to recent donations only.
| const donations = await prisma.donation.findMany({ | |
| where: { creatorId: userId }, | |
| }); | |
| const totalDonations = donations.length; | |
| const totalAmount = donations.reduce((sum, donation) => sum + donation.amount, 0); | |
| // Calculate unique supporters (excluding null supporterId for anonymous donations) | |
| const supporterIds = donations | |
| .filter(d => d.supporterId !== null) | |
| .map(d => d.supporterId); | |
| const totalSupporters = new Set(supporterIds).size; | |
| // Calculate monthly growth (simplified - comparing this month to last month) | |
| const now = new Date(); | |
| const thisMonth = new Date(now.getFullYear(), now.getMonth(), 1); | |
| const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1); | |
| const thisMonthDonations = donations.filter(d => new Date(d.createdAt) >= thisMonth); | |
| const lastMonthDonations = donations.filter( | |
| d => new Date(d.createdAt) >= lastMonth && new Date(d.createdAt) < thisMonth | |
| ); | |
| const monthlyGrowth = lastMonthDonations.length > 0 | |
| ? ((thisMonthDonations.length - lastMonthDonations.length) / lastMonthDonations.length) * 100 | |
| : 0; | |
| return res.json({ | |
| totalDonations, | |
| totalAmount, | |
| // Aggregate total donations and total amount | |
| const totalStats = await prisma.donation.aggregate({ | |
| _count: { id: true }, | |
| _sum: { amount: true }, | |
| where: { creatorId: userId }, | |
| }); | |
| // Count unique supporters (excluding null supporterId) | |
| const totalSupporters = await prisma.donation.count({ | |
| where: { | |
| creatorId: userId, | |
| supporterId: { not: null }, | |
| }, | |
| distinct: ['supporterId'], | |
| }); | |
| // Calculate monthly growth (comparing this month to last month) | |
| const now = new Date(); | |
| const thisMonth = new Date(now.getFullYear(), now.getMonth(), 1); | |
| const lastMonth = new Date(now.getFullYear(), now.getMonth() - 1, 1); | |
| // Count donations for this month | |
| const thisMonthDonations = await prisma.donation.count({ | |
| where: { | |
| creatorId: userId, | |
| createdAt: { gte: thisMonth }, | |
| }, | |
| }); | |
| // Count donations for last month | |
| const lastMonthDonations = await prisma.donation.count({ | |
| where: { | |
| creatorId: userId, | |
| createdAt: { gte: lastMonth, lt: thisMonth }, | |
| }, | |
| }); | |
| const monthlyGrowth = lastMonthDonations > 0 | |
| ? ((thisMonthDonations - lastMonthDonations) / lastMonthDonations) * 100 | |
| : 0; | |
| return res.json({ | |
| totalDonations: totalStats._count.id, | |
| totalAmount: totalStats._sum.amount || 0, |
| const [formData, setFormData] = useState({ | ||
| name: user?.name || "", | ||
| bio: user?.bio || "", | ||
| }); |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FormData is initialized before user data is loaded, resulting in empty initial values. This should be updated when user data becomes available, or initialized within a useEffect that depends on user.
| /> | ||
| <StatsCard | ||
| title={t("dashboard.overview.stats.totalAmount")} | ||
| value={`$${stats.totalAmount.toFixed(2)}`} |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Currency is hardcoded to USD ($) symbol. Consider making currency configurable or using the user's locale/region to display appropriate currency based on mobile money provider (CDF for Congo, etc.).
| title="Logout" | ||
| > | ||
| <LogOut className="w-4 h-4" /> | ||
| <span className="hidden sm:inline">Logout</span> |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logout button text is hardcoded in English instead of using the translation function t(). Should use t('dashboard.logout') or similar for proper internationalization.
| <span className="hidden sm:inline">Logout</span> | |
| <span className="hidden sm:inline">{t("dashboard.logout")}</span> |
| const monthlyGrowth = lastMonthDonations.length > 0 | ||
| ? ((thisMonthDonations.length - lastMonthDonations.length) / lastMonthDonations.length) * 100 |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Monthly growth calculation uses donation count rather than donation amount. Consider clarifying if growth should be based on number of donations or total monetary value, as the current metric may be misleading.
| const monthlyGrowth = lastMonthDonations.length > 0 | |
| ? ((thisMonthDonations.length - lastMonthDonations.length) / lastMonthDonations.length) * 100 | |
| const thisMonthAmount = thisMonthDonations.reduce((sum, donation) => sum + donation.amount, 0); | |
| const lastMonthAmount = lastMonthDonations.reduce((sum, donation) => sum + donation.amount, 0); | |
| const monthlyGrowth = lastMonthAmount > 0 | |
| ? ((thisMonthAmount - lastMonthAmount) / lastMonthAmount) * 100 |
| const formatDate = (dateString) => { | ||
| return new Date(dateString).toLocaleDateString(undefined, { | ||
| year: "numeric", | ||
| month: "long", | ||
| day: "numeric", | ||
| }); | ||
| }; |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The formatDate function uses 'undefined' for locale, which defaults to system locale. Consider using the user's selected language from useI18n() context to ensure consistent date formatting with the rest of the application.
| createdAt: donation.createdAt, | ||
| message: donation.message || null, | ||
| supporter: { | ||
| name: donation.supporter?.name || "Anonymous", |
Copilot
AI
Nov 7, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded 'Anonymous' string should be handled on the frontend with proper internationalization. Consider returning null or empty string and let the frontend display the translated 'Anonymous' text using t('dashboard.donations.anonymous').
| name: donation.supporter?.name || "Anonymous", | |
| name: donation.supporter?.name || null, |

Implements a functional dashboard for creators to manage their profile, view support history, and configure payment settings with full responsive design and GitHub OAuth integration.
Frontend
Dashboard Structure
Sections
Components
StatsCard: Configurable color themes for different metricsBackend
API Endpoints
All routes protected with JWT authentication middleware.
Database
Schema Changes
Fixed donation model to properly distinguish between donation recipient (creator) and sender (supporter), enabling accurate supporter counting and attribution.
Migration:
20251107205140_add_profile_and_payment_fieldsNotes
Original prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.