Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 7, 2025

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

  • Sidebar navigation with 4 sections: Overview, Profile, Donations, Settings
  • Mobile-responsive with collapsible sidebar menu and overlay
  • Topbar with user info, GitHub avatar, and logout

Sections

  • Overview: Stats cards (total donations, earnings, supporters, monthly growth) with API integration
  • Profile: View/edit name and bio, display GitHub avatar and username
  • Donations: Paginated history showing supporter name, amount, message, timestamp
  • Settings: Mobile Money configuration (provider, phone number) and public profile toggle

Components

  • StatsCard: Configurable color themes for different metrics
  • All sections include loading states, error handling, and empty states
  • Full i18n support (English/French)

Backend

API Endpoints

GET  /api/dashboard/stats     // Aggregate donation statistics
GET  /api/donations           // Creator's donation history with supporter info
PATCH /api/user/profile       // Update name, bio, publicProfile
PATCH /api/user/payment       // Update mobileMoneyNumber, mobileMoneyProvider

All routes protected with JWT authentication middleware.

Database

Schema Changes

model User {
  bio                  String?
  publicProfile        Boolean @default(true)
  mobileMoneyNumber    String?
  mobileMoneyProvider  String?
  receivedDonations    Donation[] @relation("ReceivedDonations")
  sentDonations        Donation[] @relation("SentDonations")
}

model Donation {
  message     String?
  creator     User  @relation("ReceivedDonations", fields: [creatorId], references: [id])
  creatorId   Int
  supporter   User? @relation("SentDonations", fields: [supporterId], references: [id])
  supporterId Int?
}

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_fields

Notes

  • CodeQL flagged missing rate limiting on new routes (non-critical, recommended for production)
  • Database migration must be run before deployment
  • Dashboard requires authenticated session (GitHub OAuth)
Original prompt

This section details on the original issue you should resolve

<issue_title>Dashboard Creator</issue_title>
<issue_description>## Description
Dashboard to manage profile, support history, and settings.

Tasks

  • Layout: Sidebar, Topbar, Content
  • Sections:
  • Profile
  • Donation history
  • Settings (edit bio, payment method)
  • Connect to API to retrieve data
  • Responsive design

Acceptance criteria

  • Functional dashboard
  • Data displayed correctly
  • Connection to the database (supabase)</issue_description>

<agent_instructions>First, you'll respect the repository structure, and then you'll create the dashboard section for this application, taking the server side into account. If adjustments are needed, make them, and create something that works correctly. So, when a user logs in or signs up with GitHub, as configured, we should register them, and you know what to do next.</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@2MJ-DEV @kxrthikeya Please read the contribution guidelines and work, especially update your main branch regularly and please test that what you did before the PR works correctly. Until then, happy coding!

Custom agent used: CodeBuddy
Specialized assistant in front-end web development (HTML/CSS/JS/React), project productivity, and automatic creation of GitHub issues in Markdown.


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI self-assigned this Nov 7, 2025
@vercel
Copy link

vercel bot commented Nov 7, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
buymeacoffeeafrica Ready Ready Preview Comment Nov 7, 2025 8:56pm

@coderabbitai
Copy link

coderabbitai bot commented Nov 7, 2025

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch copilot/create-dashboard-section

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI changed the title [WIP] Add dashboard creator for profile management and settings Implement creator dashboard with profile management, donation history, and payment settings Nov 7, 2025
Copilot AI requested a review from 2MJ-DEV November 7, 2025 20:59
@2MJ-DEV
Copy link
Owner

2MJ-DEV commented Nov 7, 2025

provides screenshots for a brief overview of the dashboard @copilot

Copy link
Contributor Author

Copilot AI commented Nov 7, 2025

provides screenshots for a brief overview of the dashboard @copilot

Dashboard Overview

Here's a comprehensive visual guide to the implemented creator dashboard:

📊 Dashboard Layout

Home Page

The dashboard features a responsive sidebar with 4 main sections and a top navigation bar displaying user info with GitHub avatar.

🎯 Key Sections Implemented

1. Overview Section

  • 4 statistics cards with color-coded icons:
    • ☕ Total Coffees (yellow) - donation count
    • 💵 Total Earnings (green) - dollar amount
    • 👥 Supporters (blue) - unique supporter count
    • 📈 Monthly Growth (purple) - percentage change

2. Profile Section

  • GitHub avatar display
  • Editable name and bio fields
  • Connected GitHub username and email
  • Edit/Save functionality with validation

3. Donation History

  • Chronological list of donations
  • Shows: supporter name, amount, message, date
  • Empty state for no donations
  • Loading skeleton screens

4. Settings Section

  • Mobile Money configuration:
    • Provider selection (Airtel/Orange/M-Pesa/MTN)
    • Phone number input
  • Public profile toggle switch
  • Form validation and feedback

📱 Responsive Design

  • Collapsible sidebar for mobile
  • Mobile overlay menu
  • Optimized layouts for all screen sizes

🌍 Internationalization

  • Full English/French translation support
  • Dark mode compatible

All components integrate with the backend API and include proper loading states, error handling, and empty states.

@2MJ-DEV 2MJ-DEV marked this pull request as ready for review November 7, 2025 22:08
Copilot AI review requested due to automatic review settings November 7, 2025 22:08
@2MJ-DEV 2MJ-DEV merged commit e19ec63 into main Nov 7, 2025
7 of 8 checks passed
@2MJ-DEV 2MJ-DEV deleted the copilot/create-dashboard-section branch November 7, 2025 22:08
Copy link
Contributor

Copilot AI left a 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

Comment on lines +25 to +26
if (name !== undefined) updateData.name = name.trim();
if (bio !== undefined) updateData.bio = bio.trim();
Copy link

Copilot AI Nov 7, 2025

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.

Copilot uses AI. Check for mistakes.
const { mobileMoneyNumber, mobileMoneyProvider } = req.body;

const updateData = {};
if (mobileMoneyNumber !== undefined) updateData.mobileMoneyNumber = mobileMoneyNumber.trim();
Copy link

Copilot AI Nov 7, 2025

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.

Copilot uses AI. Check for mistakes.
Comment on lines +46 to +48
const updateData = {};
if (mobileMoneyNumber !== undefined) updateData.mobileMoneyNumber = mobileMoneyNumber.trim();
if (mobileMoneyProvider !== undefined) updateData.mobileMoneyProvider = mobileMoneyProvider;
Copy link

Copilot AI Nov 7, 2025

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.

Suggested change
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;
}

Copilot uses AI. Check for mistakes.
Comment on lines +9 to +38
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,
Copy link

Copilot AI Nov 7, 2025

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.

Suggested change
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,

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +15
const [formData, setFormData] = useState({
name: user?.name || "",
bio: user?.bio || "",
});
Copy link

Copilot AI Nov 7, 2025

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.

Copilot uses AI. Check for mistakes.
/>
<StatsCard
title={t("dashboard.overview.stats.totalAmount")}
value={`$${stats.totalAmount.toFixed(2)}`}
Copy link

Copilot AI Nov 7, 2025

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.).

Copilot uses AI. Check for mistakes.
title="Logout"
>
<LogOut className="w-4 h-4" />
<span className="hidden sm:inline">Logout</span>
Copy link

Copilot AI Nov 7, 2025

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.

Suggested change
<span className="hidden sm:inline">Logout</span>
<span className="hidden sm:inline">{t("dashboard.logout")}</span>

Copilot uses AI. Check for mistakes.
Comment on lines +32 to +33
const monthlyGrowth = lastMonthDonations.length > 0
? ((thisMonthDonations.length - lastMonthDonations.length) / lastMonthDonations.length) * 100
Copy link

Copilot AI Nov 7, 2025

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +34
const formatDate = (dateString) => {
return new Date(dateString).toLocaleDateString(undefined, {
year: "numeric",
month: "long",
day: "numeric",
});
};
Copy link

Copilot AI Nov 7, 2025

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.

Copilot uses AI. Check for mistakes.
createdAt: donation.createdAt,
message: donation.message || null,
supporter: {
name: donation.supporter?.name || "Anonymous",
Copy link

Copilot AI Nov 7, 2025

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').

Suggested change
name: donation.supporter?.name || "Anonymous",
name: donation.supporter?.name || null,

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dashboard Creator

2 participants