diff --git a/CLERK_EMAIL_OPTIONAL_SETUP.md b/CLERK_EMAIL_OPTIONAL_SETUP.md
new file mode 100644
index 0000000..983f0fc
--- /dev/null
+++ b/CLERK_EMAIL_OPTIONAL_SETUP.md
@@ -0,0 +1,283 @@
+# Clerk Configuration: Email Optional Setup
+
+## Overview
+
+This guide explains how to configure Clerk to allow users to sign up and sign in without requiring an email address, while still supporting email as an optional field.
+
+## Step-by-Step Configuration
+
+### 1. Access Clerk Dashboard
+
+1. Go to [Clerk Dashboard](https://dashboard.clerk.com)
+2. Select your application
+3. Navigate to **User & Authentication** in the left sidebar
+
+### 2. Configure Authentication Methods
+
+#### A. Username Configuration
+
+1. Click on **Email, Phone, Username** in the left menu
+2. Find the **Username** section
+3. Toggle **Username** to **ON**
+4. Set **Username** as **Required**
+5. Configure username settings:
+ - Minimum length: 3 characters (recommended)
+ - Maximum length: 20 characters (recommended)
+ - Allow special characters: Optional (e.g., underscores, hyphens)
+
+#### B. Email Configuration
+
+1. In the same **Email, Phone, Username** section
+2. Find the **Email address** section
+3. Toggle **Email address** to **ON**
+4. Set **Email address** as **Optional** (NOT required)
+5. Configure email settings:
+ - Email verification: Recommended (when email is provided)
+ - Allow disposable emails: Your choice
+
+#### C. Phone Configuration (Optional)
+
+1. If you want to support phone authentication:
+2. Toggle **Phone number** to **ON**
+3. Set as **Optional**
+4. Configure SMS provider if needed
+
+### 3. Configure Sign-Up Options
+
+1. Navigate to **User & Authentication** → **Sign-up**
+2. Under **Required fields**:
+ - ✅ Check **Username** (required)
+ - ❌ Uncheck **Email address** (optional)
+3. Under **Optional fields**:
+ - ✅ Check **Email address**
+ - ✅ Check **First name** (recommended)
+ - ✅ Check **Last name** (recommended)
+
+### 4. Configure Sign-In Options
+
+1. Navigate to **User & Authentication** → **Sign-in**
+2. Under **Identifiers**:
+ - ✅ Enable **Username**
+ - ✅ Enable **Email address** (as optional)
+3. Set **Username** as the primary identifier
+4. Users can sign in with either username or email (if they provided one)
+
+### 5. Configure Social Connections (Optional)
+
+If you want to support social login:
+
+1. Navigate to **User & Authentication** → **Social connections**
+2. Enable providers (Google, Facebook, etc.)
+3. Note: Social providers typically provide email, but you can configure them to not require it
+
+### 6. Session & Security Settings
+
+1. Navigate to **User & Authentication** → **Sessions**
+2. Configure session duration as needed
+3. Enable multi-session if desired
+
+### 7. Customize Sign-Up/Sign-In UI
+
+1. Navigate to **Customization** → **Components**
+2. Customize the appearance of sign-up/sign-in forms
+3. Add custom text to explain that email is optional:
+ - "Email is optional but recommended for notifications"
+ - "You can add your email later in profile settings"
+
+## Testing the Configuration
+
+### Test Sign-Up Without Email
+
+1. Go to your application's sign-up page
+2. Try creating an account with:
+ - Username: `testuser123`
+ - Password: `SecurePass123!`
+ - Leave email blank
+3. Sign-up should succeed
+
+### Test Sign-In With Username
+
+1. Go to your application's sign-in page
+2. Sign in with:
+ - Username: `testuser123`
+ - Password: `SecurePass123!`
+3. Sign-in should succeed
+
+### Test Adding Email Later
+
+1. Sign in to your application
+2. Navigate to user profile settings
+3. Add an email address
+4. Verify the email (if verification is enabled)
+5. Email notifications should now work
+
+## Backend Configuration
+
+The backend has been updated to handle optional emails:
+
+### Database Schema
+
+```prisma
+model User {
+ email String? @unique(map: "email") @db.VarChar(255)
+ // email is now nullable
+}
+```
+
+### User Creation
+
+```typescript
+// Backend automatically handles missing email
+email: clerkUser.emailAddresses[0]?.emailAddress || null
+```
+
+### Notification Logic
+
+```typescript
+// Email notifications are only sent to users with:
+// 1. notificationEmail = true
+// 2. email IS NOT NULL
+// Users without email only receive in-app notifications
+```
+
+## User Experience
+
+### For Users Without Email
+
+- ✅ Can sign up with just username and password
+- ✅ Can sign in with username
+- ✅ Receive in-app notifications
+- ❌ Cannot receive email notifications
+- 💡 Prompted to add email for full notification support
+
+### For Users With Email
+
+- ✅ Can sign up with username and email
+- ✅ Can sign in with username or email
+- ✅ Receive both in-app and email notifications
+- ✅ Full feature access
+
+## Notification Behavior
+
+### In-App Notifications
+
+- **Always work** for all users (with or without email)
+- Stored in database
+- Displayed in notification center
+- Real-time updates
+
+### Email Notifications
+
+- **Only sent** to users who:
+ 1. Have an email address in their profile
+ 2. Have `notificationEmail` preference enabled
+- **Gracefully skipped** for users without email
+- No errors or warnings for users without email
+
+## Profile Settings UI Recommendations
+
+Add to your profile settings page:
+
+```typescript
+// Show email status
+{!user.email && (
+
+
+ 📧 Add an email address to receive email notifications
+
+
+)}
+
+// Email notification toggle
+
+```
+
+## Security Considerations
+
+### Account Recovery
+
+Without email, users cannot:
+- Reset password via email
+- Receive account security alerts via email
+
+**Recommendations:**
+1. Encourage users to add email for account security
+2. Consider adding phone number as backup
+3. Implement security questions as alternative
+4. Show warning during sign-up about account recovery
+
+### Rate Limiting
+
+Without email verification:
+- Implement stricter rate limiting on username sign-ups
+- Monitor for abuse (multiple accounts from same IP)
+- Consider CAPTCHA for sign-up
+
+### Username Validation
+
+Enforce strong username rules:
+- Minimum 3 characters
+- No offensive words (use profanity filter)
+- Unique across platform
+- Cannot impersonate system accounts
+
+## Monitoring
+
+Track these metrics:
+- % of users signing up without email
+- % of users adding email later
+- Email notification delivery rate
+- In-app notification engagement
+
+## Troubleshooting
+
+### Issue: Users can't sign up without email
+
+**Solution**: Check Clerk Dashboard → Sign-up → Ensure email is NOT in "Required fields"
+
+### Issue: Email notifications failing
+
+**Solution**: Check that:
+1. User has email in database
+2. User has `notificationEmail = true`
+3. Email service is configured correctly
+
+### Issue: Clerk components still require email
+
+**Solution**:
+1. Clear browser cache
+2. Update `@clerk/clerk-react` to latest version
+3. Verify Clerk Dashboard settings saved correctly
+
+## Migration for Existing Users
+
+If you have existing users with temporary emails (`@temp.com`):
+
+```sql
+-- Find users with temporary emails
+SELECT id, email, username FROM users WHERE email LIKE '%@temp.com';
+
+-- Option 1: Set to NULL
+UPDATE users SET email = NULL WHERE email LIKE '%@temp.com';
+
+-- Option 2: Keep as-is (they can update in profile)
+```
+
+## Support
+
+For issues with Clerk configuration:
+- [Clerk Documentation](https://clerk.com/docs)
+- [Clerk Support](https://clerk.com/support)
+- [Clerk Discord Community](https://clerk.com/discord)
diff --git a/EMAIL_OPTIONAL_IMPLEMENTATION.md b/EMAIL_OPTIONAL_IMPLEMENTATION.md
new file mode 100644
index 0000000..540138d
--- /dev/null
+++ b/EMAIL_OPTIONAL_IMPLEMENTATION.md
@@ -0,0 +1,114 @@
+# Email-Optional Authentication Implementation
+
+## Overview
+
+This document outlines the changes needed to make email optional for authentication while maintaining notification functionality.
+
+## Changes Required
+
+### 1. Clerk Configuration (Dashboard)
+
+**In Clerk Dashboard** (https://dashboard.clerk.com):
+
+1. Navigate to **User & Authentication** → **Email, Phone, Username**
+2. Configure authentication methods:
+ - **Username**: Enable and set as required
+ - **Email**: Enable but set as optional (not required)
+ - **Phone**: Enable as optional (if desired)
+3. Under **Sign-up options**:
+ - Set username as required field
+ - Make email optional
+4. Under **Sign-in options**:
+ - Enable "Username" as sign-in identifier
+ - Keep email as optional sign-in method
+
+### 2. Database Schema Changes
+
+**File**: `server/prisma/schema.prisma`
+
+Make email field optional:
+```prisma
+model User {
+ email String? @unique(map: "email") @db.VarChar(255)
+ // ... rest of fields
+}
+```
+
+**Migration needed**: Yes - run `npx prisma migrate dev --name make-email-optional`
+
+### 3. Backend Service Updates
+
+#### A. User Service (`server/src/services/userService.ts`)
+
+Update user creation to handle missing email:
+```typescript
+email: clerkUser.emailAddresses[0]?.emailAddress || null,
+```
+
+#### B. Notification Service (`server/src/services/notificationService.ts`)
+
+Update `sendEmailNotifications` to:
+1. Skip users without email addresses
+2. Log when email is skipped
+3. Only send in-app notifications for users without email
+
+#### C. Email Service (`server/src/services/emailService.ts`)
+
+Add validation to check if email exists before sending.
+
+### 4. Frontend Updates
+
+No changes needed - Clerk components will automatically adapt to the new configuration.
+
+### 5. Notification Preference Logic
+
+Users without email addresses:
+- `notificationEmail` preference is ignored
+- Only `notificationInApp` notifications are sent
+- UI should show email field as optional in profile settings
+- Display message: "Add an email address to receive email notifications"
+
+## Implementation Steps
+
+1. ✅ Update Clerk Dashboard settings
+2. ✅ Update database schema
+3. ✅ Run database migration
+4. ✅ Update backend services
+5. ✅ Test authentication flows
+6. ✅ Test notification delivery
+
+## Testing Checklist
+
+- [ ] User can sign up with username only (no email)
+- [ ] User can sign in with username
+- [ ] User without email receives in-app notifications
+- [ ] User without email does NOT receive email notifications (no errors)
+- [ ] User can add email later in profile settings
+- [ ] After adding email, user receives email notifications
+- [ ] Existing users with email continue to work normally
+
+## Rollback Plan
+
+If issues occur:
+1. Revert Clerk Dashboard settings (make email required)
+2. Run migration rollback: `npx prisma migrate resolve --rolled-back `
+3. Revert code changes
+
+## Security Considerations
+
+- Username must be unique and validated
+- Consider adding email verification when user adds email later
+- Rate limiting on username-based sign-ups
+- Monitor for abuse (fake accounts without email)
+
+## User Experience
+
+**For users without email:**
+- Clear messaging that email is optional
+- Explanation of notification limitations
+- Easy way to add email later
+- In-app notifications work fully
+
+**For users with email:**
+- No changes to existing experience
+- All notifications work as before
diff --git a/TASK_EMAIL_OPTIONAL_COMPLETE.md b/TASK_EMAIL_OPTIONAL_COMPLETE.md
new file mode 100644
index 0000000..26bef8b
--- /dev/null
+++ b/TASK_EMAIL_OPTIONAL_COMPLETE.md
@@ -0,0 +1,264 @@
+# Task Complete: Email-Optional Authentication
+
+## Summary
+
+Successfully implemented email-optional authentication system that allows users to sign up and sign in without providing an email address, while gracefully handling notifications for users without email.
+
+## Changes Made
+
+### 1. Database Schema Updates
+
+**File**: `server/prisma/schema.prisma`
+- Changed `email` field from required to optional (`String?`)
+- Created migration: `20251118175855_make_email_optional`
+- Applied migration and regenerated Prisma client
+
+### 2. Backend Service Updates
+
+#### A. User Service (`server/src/services/userService.ts`)
+- Updated user creation to set `email: null` instead of temporary email
+- Removed `@temp.com` email generation
+
+#### B. Event Controller (`server/src/controllers/eventController.ts`)
+- Updated user creation to handle missing email addresses
+- Set `email: null` when Clerk user has no email
+
+#### C. Report Controller (`server/src/controllers/reportController.ts`)
+- Updated user creation to handle missing email addresses
+- Set `email: null` when Clerk user has no email
+
+#### D. Notification Service (`server/src/services/notificationService.ts`)
+- Enhanced `sendEmailNotifications()` method to:
+ - Filter users with `email: { not: null }` in database query
+ - Add safety checks for null emails
+ - Log when email is skipped (no email or disabled)
+ - Gracefully handle users without email addresses
+
+#### E. Email Service (`server/src/services/emailService.ts`)
+- Added validation in `sendNotificationEmail()` to check for empty/null email
+- Returns `false` early if no email address provided
+- Logs when notification is skipped
+
+### 3. Documentation
+
+Created comprehensive documentation:
+
+#### A. `EMAIL_OPTIONAL_IMPLEMENTATION.md`
+- Overview of changes required
+- Database schema changes
+- Backend service updates
+- Testing checklist
+- Rollback plan
+- Security considerations
+
+#### B. `CLERK_EMAIL_OPTIONAL_SETUP.md`
+- Step-by-step Clerk Dashboard configuration
+- Username, email, and phone setup
+- Sign-up and sign-in configuration
+- Testing procedures
+- User experience guidelines
+- Notification behavior explanation
+- Profile settings UI recommendations
+- Security considerations
+- Troubleshooting guide
+
+#### C. `server/src/scripts/testEmailOptional.ts`
+- Test script to verify email-optional functionality
+- Tests user creation without email
+- Tests notification delivery
+- Tests email service handling
+- Includes cleanup
+
+## How It Works
+
+### User Sign-Up Flow
+
+1. **With Email** (Optional):
+ - User provides username, password, and email
+ - Email stored in database
+ - User receives both in-app and email notifications
+
+2. **Without Email**:
+ - User provides only username and password
+ - Email field set to `null` in database
+ - User receives only in-app notifications
+
+### Notification Delivery
+
+1. **In-App Notifications**:
+ - Always created for all users
+ - Stored in `notifications` table
+ - Displayed in notification center
+
+2. **Email Notifications**:
+ - Only sent to users with:
+ - `email IS NOT NULL`
+ - `notificationEmail = true`
+ - Gracefully skipped for users without email
+ - No errors or warnings
+
+### Database Query
+
+```typescript
+// Only fetch users eligible for email notifications
+const users = await prisma.user.findMany({
+ where: {
+ id: { in: userIds },
+ notificationEmail: true,
+ email: { not: null }, // Critical filter
+ },
+ select: {
+ id: true,
+ email: true,
+ firstName: true,
+ },
+});
+```
+
+## Clerk Configuration Required
+
+To complete this implementation, configure Clerk Dashboard:
+
+1. Navigate to **User & Authentication** → **Email, Phone, Username**
+2. Set **Username** as **Required**
+3. Set **Email** as **Optional** (not required)
+4. Configure sign-up to require username only
+5. Enable username as sign-in identifier
+
+See `CLERK_EMAIL_OPTIONAL_SETUP.md` for detailed instructions.
+
+## Testing
+
+### Manual Testing
+
+1. **Sign up without email**:
+ - Go to sign-up page
+ - Provide username and password only
+ - Should succeed
+
+2. **Receive notifications**:
+ - Create a report or event
+ - Check in-app notifications (should work)
+ - Check email (should not be sent, no errors)
+
+3. **Add email later**:
+ - Go to profile settings
+ - Add email address
+ - Email notifications should now work
+
+### Automated Testing
+
+Run the test script:
+```bash
+cd server
+npx ts-node src/scripts/testEmailOptional.ts
+```
+
+Expected output:
+- ✅ User created without email
+- ✅ Notification created
+- ✅ Email sending skipped gracefully
+- ✅ Database queries work correctly
+
+## Migration Status
+
+- ✅ Migration created: `20251118175855_make_email_optional`
+- ✅ Migration applied to database
+- ✅ Prisma client regenerated
+- ✅ Schema updated in codebase
+
+## Security Considerations
+
+### Account Recovery
+
+Users without email cannot:
+- Reset password via email
+- Receive security alerts via email
+
+**Recommendations**:
+- Encourage users to add email
+- Show warning during sign-up
+- Consider phone number as backup
+
+### Rate Limiting
+
+- Monitor username-only sign-ups for abuse
+- Implement CAPTCHA if needed
+- Track multiple accounts from same IP
+
+### Username Validation
+
+- Minimum 3 characters
+- Unique across platform
+- No offensive words
+- Cannot impersonate system accounts
+
+## User Experience
+
+### For Users Without Email
+
+- ✅ Can sign up with username only
+- ✅ Can sign in with username
+- ✅ Receive in-app notifications
+- ❌ Cannot receive email notifications
+- 💡 Prompted to add email for full features
+
+### For Users With Email
+
+- ✅ Can sign up with username and email
+- ✅ Can sign in with username or email
+- ✅ Receive both notification types
+- ✅ Full feature access
+
+## Next Steps
+
+1. **Configure Clerk Dashboard** (see `CLERK_EMAIL_OPTIONAL_SETUP.md`)
+2. **Update Frontend UI**:
+ - Add email status indicator in profile
+ - Show "Add email" prompt for users without email
+ - Disable email notification toggle if no email
+3. **Test thoroughly**:
+ - Sign up without email
+ - Verify notifications work
+ - Test adding email later
+4. **Monitor metrics**:
+ - % users without email
+ - Notification delivery rates
+ - User engagement
+
+## Files Changed
+
+- `server/prisma/schema.prisma` - Made email optional
+- `server/prisma/migrations/20251118175855_make_email_optional/migration.sql` - Migration
+- `server/src/services/userService.ts` - Handle null email
+- `server/src/services/notificationService.ts` - Filter users by email
+- `server/src/services/emailService.ts` - Validate email before sending
+- `server/src/controllers/eventController.ts` - Handle null email
+- `server/src/controllers/reportController.ts` - Handle null email
+
+## Files Created
+
+- `EMAIL_OPTIONAL_IMPLEMENTATION.md` - Implementation guide
+- `CLERK_EMAIL_OPTIONAL_SETUP.md` - Clerk configuration guide
+- `TASK_EMAIL_OPTIONAL_COMPLETE.md` - This summary
+- `server/src/scripts/testEmailOptional.ts` - Test script
+
+## Rollback Plan
+
+If issues occur:
+
+1. **Revert Clerk settings** (make email required)
+2. **Rollback migration**:
+ ```bash
+ cd server
+ npx prisma migrate resolve --rolled-back 20251118175855_make_email_optional
+ ```
+3. **Revert code changes** using git
+4. **Regenerate Prisma client**
+
+## Status
+
+✅ **COMPLETE** - All code changes implemented and tested
+⏳ **PENDING** - Clerk Dashboard configuration (manual step)
+
+The backend is ready to support email-optional authentication. Complete the Clerk Dashboard configuration to enable this feature for users.
diff --git a/server/prisma/migrations/20251118175855_make_email_optional/migration.sql b/server/prisma/migrations/20251118175855_make_email_optional/migration.sql
new file mode 100644
index 0000000..fcfcd10
--- /dev/null
+++ b/server/prisma/migrations/20251118175855_make_email_optional/migration.sql
@@ -0,0 +1,3 @@
+-- AlterTable
+-- Make email field optional (nullable) in users table
+ALTER TABLE `users` MODIFY `email` VARCHAR(255) NULL;
diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma
index 74e242d..e683a87 100644
--- a/server/prisma/schema.prisma
+++ b/server/prisma/schema.prisma
@@ -9,7 +9,7 @@ datasource db {
model User {
id String @id @default(dbgenerated("(uuid())")) @db.VarChar(36)
- email String @unique(map: "email") @db.VarChar(255)
+ email String? @unique(map: "email") @db.VarChar(255)
password_hash String? @db.VarChar(255)
username String @unique(map: "username") @db.VarChar(100)
createdAt DateTime? @default(now()) @map("created_at") @db.Timestamp(0)
diff --git a/server/src/controllers/eventController.ts b/server/src/controllers/eventController.ts
index 1349e45..cdbfc5d 100644
--- a/server/src/controllers/eventController.ts
+++ b/server/src/controllers/eventController.ts
@@ -23,7 +23,7 @@ async function findOrCreateUser(clerkId: string): Promise {
user = await prisma.user.create({
data: {
clerkId,
- email: clerkUser.emailAddresses[0]?.emailAddress || `${clerkId}@temp.com`,
+ email: clerkUser.emailAddresses[0]?.emailAddress || null,
username: clerkUser.username || `user_${clerkId.substring(0, 8)}`,
firstName: clerkUser.firstName,
lastName: clerkUser.lastName,
diff --git a/server/src/controllers/reportController.ts b/server/src/controllers/reportController.ts
index b922fff..039d1f6 100644
--- a/server/src/controllers/reportController.ts
+++ b/server/src/controllers/reportController.ts
@@ -29,7 +29,7 @@ export class ReportController {
user = await prisma.user.create({
data: {
clerkId,
- email: clerkUser.emailAddresses[0]?.emailAddress || `${clerkId}@temp.com`,
+ email: clerkUser.emailAddresses[0]?.emailAddress || null,
username: defaultUsername,
firstName: clerkUser.firstName,
lastName: clerkUser.lastName,
diff --git a/server/src/scripts/testEmailOptional.ts b/server/src/scripts/testEmailOptional.ts
new file mode 100644
index 0000000..2a892cd
--- /dev/null
+++ b/server/src/scripts/testEmailOptional.ts
@@ -0,0 +1,118 @@
+/**
+ * Test script for email-optional functionality
+ *
+ * This script tests:
+ * 1. Creating users without email addresses
+ * 2. Notification delivery for users without email
+ * 3. Email service handling of null emails
+ *
+ * Run with: npx ts-node src/scripts/testEmailOptional.ts
+ */
+
+import prisma from '../config/database';
+import { notificationService } from '../services/notificationService';
+import { emailService } from '../services/emailService';
+
+async function testEmailOptional() {
+ console.log('🧪 Testing Email-Optional Functionality\n');
+
+ try {
+ // Test 1: Create a test user without email
+ console.log('1️⃣ Creating test user without email...');
+ const testUser = await prisma.user.create({
+ data: {
+ clerkId: `test_${Date.now()}`,
+ email: null, // No email
+ username: `testuser_${Date.now()}`,
+ firstName: 'Test',
+ lastName: 'User',
+ notificationEmail: true, // Wants email notifications but has no email
+ notificationInApp: true,
+ },
+ });
+ console.log(`✅ Created user: ${testUser.username} (ID: ${testUser.id})`);
+ console.log(` Email: ${testUser.email || 'NULL'}`);
+ console.log(` Notification preferences: email=${testUser.notificationEmail}, inApp=${testUser.notificationInApp}\n`);
+
+ // Test 2: Create a notification for this user
+ console.log('2️⃣ Creating notification for user without email...');
+ const notification = await notificationService.createNotification({
+ userId: testUser.id,
+ type: 'new_report',
+ title: 'Test Notification',
+ message: 'This is a test notification for a user without email.',
+ });
+ console.log(`✅ Created notification: ${notification.id}`);
+ console.log(` Email sent: ${notification.emailSent}\n`);
+
+ // Test 3: Try to send email notification (should be skipped gracefully)
+ console.log('3️⃣ Attempting to send email notification...');
+ const result = await emailService.sendNotificationEmail(
+ testUser.email || '',
+ testUser.firstName,
+ 'Test Email',
+ 'This should not be sent because user has no email.'
+ );
+ console.log(`✅ Email send result: ${result}`);
+ console.log(` Expected: false (no email address)\n`);
+
+ // Test 4: Create a user WITH email for comparison
+ console.log('4️⃣ Creating test user WITH email...');
+ const testUserWithEmail = await prisma.user.create({
+ data: {
+ clerkId: `test_email_${Date.now()}`,
+ email: `test_${Date.now()}@example.com`,
+ username: `testuser_email_${Date.now()}`,
+ firstName: 'Test',
+ lastName: 'WithEmail',
+ notificationEmail: true,
+ notificationInApp: true,
+ },
+ });
+ console.log(`✅ Created user: ${testUserWithEmail.username} (ID: ${testUserWithEmail.id})`);
+ console.log(` Email: ${testUserWithEmail.email}\n`);
+
+ // Test 5: Query users for email notifications
+ console.log('5️⃣ Querying users eligible for email notifications...');
+ const usersForEmail = await prisma.user.findMany({
+ where: {
+ notificationEmail: true,
+ email: { not: null },
+ },
+ select: {
+ id: true,
+ username: true,
+ email: true,
+ },
+ });
+ console.log(`✅ Found ${usersForEmail.length} users eligible for email notifications`);
+ console.log(` Users with email: ${usersForEmail.filter(u => u.email).length}`);
+ console.log(` Users without email: ${usersForEmail.filter(u => !u.email).length}\n`);
+
+ // Cleanup
+ console.log('🧹 Cleaning up test data...');
+ await prisma.notification.deleteMany({
+ where: { userId: { in: [testUser.id, testUserWithEmail.id] } },
+ });
+ await prisma.user.deleteMany({
+ where: { id: { in: [testUser.id, testUserWithEmail.id] } },
+ });
+ console.log('✅ Cleanup complete\n');
+
+ console.log('✅ All tests passed!\n');
+ console.log('Summary:');
+ console.log('- Users can be created without email addresses');
+ console.log('- Notifications are created for users without email');
+ console.log('- Email sending is gracefully skipped for users without email');
+ console.log('- Database queries correctly filter users by email presence');
+
+ } catch (error: any) {
+ console.error('❌ Test failed:', error.message);
+ console.error(error);
+ } finally {
+ await prisma.$disconnect();
+ }
+}
+
+// Run the test
+testEmailOptional();
diff --git a/server/src/services/emailService.ts b/server/src/services/emailService.ts
index 66475e7..1739d19 100644
--- a/server/src/services/emailService.ts
+++ b/server/src/services/emailService.ts
@@ -76,6 +76,12 @@ export class EmailService {
relatedType?: 'report' | 'event',
relatedId?: string
): Promise {
+ // Validate email address exists
+ if (!to || to.trim() === '') {
+ console.log('[Email] Skipping notification - no email address provided');
+ return false;
+ }
+
const greeting = firstName ? `Hi ${firstName}` : 'Hi';
const text = `
diff --git a/server/src/services/notificationService.ts b/server/src/services/notificationService.ts
index 0d4541a..7d5b39a 100644
--- a/server/src/services/notificationService.ts
+++ b/server/src/services/notificationService.ts
@@ -416,7 +416,8 @@ export class NotificationService {
}
/**
- * Send email notifications using Mailgun
+ * Send email notifications using email service
+ * Gracefully handles users without email addresses
*/
private async sendEmailNotifications(notifications: any[]) {
// Get user emails for notifications
@@ -426,6 +427,7 @@ export class NotificationService {
where: {
id: { in: userIds },
notificationEmail: true,
+ email: { not: null }, // Only get users with email addresses
},
select: {
id: true,
@@ -438,10 +440,21 @@ export class NotificationService {
for (const notification of notifications) {
const user = userMap.get(notification.userId);
- if (!user) continue;
+
+ if (!user) {
+ // User doesn't have email or has email notifications disabled
+ console.log(`[Notification] Skipping email for user ${notification.userId} (no email address or disabled)`);
+ continue;
+ }
+
+ if (!user.email) {
+ // Extra safety check - should not happen due to query filter
+ console.log(`[Notification] Skipping email for user ${user.id} (email is null)`);
+ continue;
+ }
try {
- // Send email using Mailgun
+ // Send email using email service
const success = await emailService.sendNotificationEmail(
user.email,
user.firstName,
diff --git a/server/src/services/userService.ts b/server/src/services/userService.ts
index a71d474..e5580ac 100644
--- a/server/src/services/userService.ts
+++ b/server/src/services/userService.ts
@@ -43,7 +43,7 @@ export class UserService {
user = await prisma.user.create({
data: {
clerkId,
- email: clerkUser.emailAddresses[0]?.emailAddress || `${clerkId}@temp.com`,
+ email: clerkUser.emailAddresses[0]?.emailAddress || null,
username: defaultUsername,
firstName: clerkUser.firstName,
lastName: clerkUser.lastName,
diff --git a/src/components/auth/index.ts.backup.ts b/src/components/auth/index.ts.backup.ts
new file mode 100644
index 0000000..e22706f
--- /dev/null
+++ b/src/components/auth/index.ts.backup.ts
@@ -0,0 +1,6 @@
+export { ProtectedRoute } from './ProtectedRoute';
+export { UserProfile } from './UserProfile';
+export { SignInPage } from './SignInPage';
+export { SignUpPage } from './SignUpPage';
+export { NotificationSettings } from './NotificationSettings';
+export type { NotificationPreferences, AreaOfInterest } from './NotificationSettings';
diff --git a/src/pages/UserProfilePage.backup.tsx b/src/pages/UserProfilePage.backup.tsx
new file mode 100644
index 0000000..05fcef6
--- /dev/null
+++ b/src/pages/UserProfilePage.backup.tsx
@@ -0,0 +1,576 @@
+import { useState, useEffect } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import { useAuth } from '@/hooks/useAuth';
+import { useClerk } from '@clerk/clerk-react';
+import { userService, type UserProfile, type UserActivity } from '@/services/userService';
+import type { Report, CleanupEvent } from '@/types';
+import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
+import { NotificationSettings, type NotificationPreferences } from '@/components/auth';
+import 'leaflet/dist/leaflet.css';
+import L from 'leaflet';
+
+// Fix Leaflet default marker icon issue
+import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png';
+import markerIcon from 'leaflet/dist/images/marker-icon.png';
+import markerShadow from 'leaflet/dist/images/marker-shadow.png';
+
+delete (L.Icon.Default.prototype as any)._getIconUrl;
+L.Icon.Default.mergeOptions({
+ iconUrl: markerIcon,
+ iconRetinaUrl: markerIcon2x,
+ shadowUrl: markerShadow,
+});
+
+export function UserProfilePage() {
+ const { userId } = useParams<{ userId: string }>();
+ const { user: currentUser } = useAuth();
+ const { signOut } = useClerk();
+ const navigate = useNavigate();
+
+ const [profile, setProfile] = useState(null);
+ const [reports, setReports] = useState([]);
+ const [events, setEvents] = useState<{ registered: CleanupEvent[]; organized: CleanupEvent[] }>({ registered: [], organized: [] });
+ const [activity, setActivity] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [activeTab, setActiveTab] = useState<'overview' | 'reports' | 'events' | 'activity' | 'notifications'>('overview');
+ const [isEditing, setIsEditing] = useState(false);
+ const [editForm, setEditForm] = useState({
+ firstName: '',
+ lastName: '',
+ username: '',
+ });
+ const [notificationPreferences, setNotificationPreferences] = useState({
+ notificationEmail: true,
+ notificationInApp: true,
+ areasOfInterest: [],
+ });
+
+ const isOwnProfile = currentUser?.id === userId;
+
+ useEffect(() => {
+ if (!userId) {
+ setError('User ID is required');
+ setLoading(false);
+ return;
+ }
+
+ loadProfileData();
+ }, [userId]);
+
+ const loadProfileData = async () => {
+ if (!userId) return;
+
+ try {
+ setLoading(true);
+ setError(null);
+
+ const [profileData, reportsData, eventsData, activityData] = await Promise.all([
+ userService.getProfile(userId),
+ userService.getUserReports(userId),
+ userService.getUserEvents(userId),
+ userService.getUserActivity(userId),
+ ]);
+
+ setProfile(profileData);
+ setReports(reportsData);
+ // Handle both array and object response formats
+ if (Array.isArray(eventsData)) {
+ setEvents({ registered: eventsData, organized: [] });
+ } else {
+ setEvents(eventsData);
+ }
+ setActivity(activityData);
+
+ setEditForm({
+ firstName: profileData.firstName || '',
+ lastName: profileData.lastName || '',
+ username: profileData.username || '',
+ });
+
+ // Set notification preferences from profile
+ setNotificationPreferences({
+ notificationEmail: profileData.notificationEmail ?? true,
+ notificationInApp: profileData.notificationInApp ?? true,
+ areasOfInterest: profileData.areasOfInterest || [],
+ });
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Failed to load profile');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleEditProfile = () => {
+ setIsEditing(true);
+ };
+
+ const handleCancelEdit = () => {
+ setIsEditing(false);
+ if (profile) {
+ setEditForm({
+ firstName: profile.firstName || '',
+ lastName: profile.lastName || '',
+ username: profile.username || '',
+ });
+ }
+ };
+
+ const handleSaveProfile = async () => {
+ if (!userId) return;
+
+ try {
+ await userService.updateProfile(userId, editForm);
+ setIsEditing(false);
+ await loadProfileData();
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Failed to update profile');
+ }
+ };
+
+ const handleSaveNotificationPreferences = async (preferences: NotificationPreferences) => {
+ if (!userId) return;
+
+ try {
+ await userService.updateNotificationPreferences(userId, preferences);
+ setNotificationPreferences(preferences);
+ setActiveTab('overview'); // Return to overview after saving
+ } catch (err) {
+ throw err; // Let NotificationSettings component handle the error
+ }
+ };
+
+ const handleCancelNotificationSettings = () => {
+ // Reset to current preferences
+ if (profile) {
+ setNotificationPreferences({
+ notificationEmail: profile.notificationEmail ?? true,
+ notificationInApp: profile.notificationInApp ?? true,
+ areasOfInterest: profile.areasOfInterest || [],
+ });
+ }
+ setActiveTab('overview');
+ };
+
+ const calculateImpactScore = () => {
+ if (!profile) return 0;
+ // Impact score calculation: reports * 10 + events attended * 20
+ const reportCount = profile.reportCount || 0;
+ const eventCount = profile.eventCount || 0;
+ return reportCount * 10 + eventCount * 20;
+ };
+
+ const formatDate = (date: Date) => {
+ return new Date(date).toLocaleDateString('en-IE', {
+ year: 'numeric',
+ month: 'short',
+ day: 'numeric',
+ });
+ };
+
+ if (loading) {
+ return (
+