Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ Thumbs.db
memory-bank/
.taskmasterconfig
tasks/
docs/
.github/docs/
reports/
reports-temp/

Expand Down
15 changes: 12 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,18 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
- `npx playwright test tests/specific-test.spec.ts` - Run specific E2E test

### Testing Credentials
- **Email/Password**: `jackson_rhoden@outlook.com` / `numfIt-8rorpo-fumwym`
- **Google OAuth**: `jacksonrhoden64@googlemail.com`
- **Test Events**: `/events/75c8904e-671f-426c-916d-4e275806e277`
**Standard Login Accounts:**
- **User**: `test1@localloopevents.xyz` / `zunTom-9wizri-refdes`
- **Staff**: `teststaff1@localloopevents.xyz` / `bobvip-koDvud-wupva0` (currently user level, needs upgrade)
- **Admin**: `testadmin1@localloopevents.xyz` / `nonhyx-1nopta-mYhnum` (currently user level, needs upgrade)

**Google OAuth Account:**
- **Email**: `TestLocalLoop@Gmail.com` / `zowvok-8zurBu-xovgaj`

**Test Events:**
- **Free Event**: `/events/75c8904e-671f-426c-916d-4e275806e277`

**Note**: All accounts currently have regular user privileges. Staff and admin accounts need role upgrades in the database.

## Architecture Overview

Expand Down
64 changes: 60 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
> **Connect Communities Through Events** 🌟
> Complete event management solution with Google Calendar integration, Stripe payments, and real-time analytics.

🚀 **[Live Application](https://local-loop-qa.vercel.app)** | 📱 **Mobile-First Design** | 🔒 **Production Ready**
🚀 **[Live Application](https://localloopevents.xyz)** | 📱 **Mobile-First Design** | 🔒 **Production Ready**

---

Expand Down Expand Up @@ -79,7 +79,7 @@
## 🚀 **Getting Started**

### **For Event Organizers**
1. **Visit**: [https://local-loop-qa.vercel.app](https://local-loop-qa.vercel.app)
1. **Visit**: [https://localloopevents.xyz](https://localloopevents.xyz)
2. **Sign in** with your Google account
3. **Create your first event** using the intuitive event creation flow
4. **Manage attendees** through the comprehensive staff dashboard
Expand All @@ -93,6 +93,62 @@

---

## 🧪 **Testing & Demo Environment**

### **Live Testing Environment**
Experience the full functionality of LocalLoop with our comprehensive testing setup:

**🌐 Live Site**: [https://localloopevents.xyz](https://localloopevents.xyz)
**📧 Custom Domain**: Full email functionality with `@localloopevents.xyz` domain

### **Quick Test Access**

#### **Standard User Account**
- **Email**: `test1@localloopevents.xyz`
- **Password**: `zunTom-9wizri-refdes`
- **Features**: Complete user experience including RSVP, calendar integration, and event browsing

#### **Staff Account**
- **Email**: `teststaff1@localloopevents.xyz`
- **Password**: `bobvip-koDvud-wupva0`
- **Features**: Event management and staff dashboard access

### **📋 Complete Testing Documentation**
For comprehensive testing including Google OAuth accounts, Stripe payment testing, admin credentials, and detailed testing checklists:

**📄 [Client Testing Guide](docs/CLIENT_TESTING_GUIDE.md)** - Complete testing documentation with all credentials and testing procedures

*This comprehensive guide includes:*
- 🔑 **All Test Account Credentials** - Including Google OAuth demo account
- 💳 **Stripe Payment Testing** - Test card numbers and payment flow validation
- 🎭 **Demo Events** - Specific events configured for testing
- ✅ **Complete Testing Checklist** - Every feature and functionality to validate
- 🔒 **Security & Technical Validation** - Performance, accessibility, and security testing

### **Key Testing Capabilities**
1. **✅ Complete User Workflows** - Registration, RSVP, and event management
2. **✅ Google Calendar Integration** - Two-way sync with live Google Calendar testing
3. **✅ Payment Processing** - Stripe integration with comprehensive test scenarios
4. **✅ Email Notifications** - Live email delivery and verification
5. **✅ Mobile & Cross-Browser** - Responsive design across all devices
6. **✅ Accessibility Compliance** - WCAG standards with improved aria labels
7. **✅ Production-Grade Performance** - Optimized loading and error handling

### **Recent Improvements Addressing Feedback**
- **🎯 Streamlined Event Listings** - Fixed "Upcoming Events" to show only future events
- **♿ Enhanced Accessibility** - Corrected aria labels and improved keyboard navigation
- **🎨 Reduced UI Clutter** - Cleaner information hierarchy and visual polish
- **📱 Mobile Optimization** - Touch-friendly interface with improved navigation

### **MVP Foundation & Development Roadmap**
This release represents a **stable and comprehensive MVP** designed to:
- ✅ **Demonstrate All Required Functionality** - Every client requirement fully implemented
- 🔧 **Provide Production-Ready Foundation** - Stable base for CI integration and enhancements
- 🚀 **Enable Creative Development** - Architecture prepared for advanced features
- 📅 **Support Timeline** - Ready for enhanced UI/UX and creative features starting June 28, 2025

---

## 🛠️ **Core Features**

### **🎪 Event Management**
Expand Down Expand Up @@ -413,7 +469,7 @@ This comprehensive guide covers:
### **Production Deployment**
The application is automatically deployed to Vercel on every push to the `main` branch.

**Live URL**: [https://local-loop-qa.vercel.app](https://local-loop-qa.vercel.app)
**Live URL**: [https://localloopevents.xyz](https://localloopevents.xyz)

### **Manual Deployment**
```bash
Expand Down Expand Up @@ -468,7 +524,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file

---

**🎪 Ready to transform your local community events? [Get started now!](https://local-loop-qa.vercel.app)**
**🎪 Ready to transform your local community events? [Get started now!](https://localloopevents.xyz)**



6 changes: 6 additions & 0 deletions app/api/analytics/performance/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@ type MetricData = PerformanceMetric | APIPerformanceMetric | {
// POST endpoint for collecting metrics
export async function POST(request: NextRequest) {
try {
// Check if request has content before parsing
const contentLength = request.headers.get('content-length')
if (!contentLength || contentLength === '0') {
return NextResponse.json({ error: 'Empty request body' }, { status: 400 })
}

const data: MetricData = await request.json()
const supabase = await createServerSupabaseClient()

Expand Down
4 changes: 0 additions & 4 deletions app/api/auth/google/callback/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ import { EMAIL_ADDRESSES } from '@/lib/config/email-addresses'
* - Encrypts tokens before storage
*/
export async function GET(request: NextRequest) {
console.log('[DEBUG] OAuth callback route started')

try {
// Rate limiting for OAuth callback
const { oauthRateLimiter } = await import('@/lib/validation')
Expand All @@ -43,8 +41,6 @@ export async function GET(request: NextRequest) {
const state = searchParams.get('state')
const error = searchParams.get('error')

console.log('[DEBUG] OAuth parameters:', { code: !!code, state: !!state, error })

// Handle OAuth authorization denied
if (error) {
console.log(`[ERROR] OAuth authorization denied: ${error}`)
Expand Down
2 changes: 1 addition & 1 deletion app/api/refunds/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ export async function POST(request: NextRequest) {
}

return NextResponse.json(
{ error: 'Refund processing failed', details: error.message },
{ error: 'Refund processing failed', details: error instanceof Error ? error.message : String(error) },
{ status: 500 }
)
}
Expand Down
3 changes: 3 additions & 0 deletions app/auth/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export default function LoginPage() {
onChange={(e) => setEmail(e.target.value)}
className="block w-full px-4 py-3 border border-border placeholder-muted-foreground text-foreground bg-background rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary text-base"
placeholder="Email address"
data-testid="email-input"
/>
</div>
<div>
Expand All @@ -151,6 +152,7 @@ export default function LoginPage() {
onChange={(e) => setPassword(e.target.value)}
className="block w-full px-4 py-3 border border-border placeholder-muted-foreground text-foreground bg-background rounded-md focus:outline-none focus:ring-2 focus:ring-primary focus:border-primary text-base"
placeholder="Password"
data-testid="password-input"
/>
</div>
</div>
Expand All @@ -166,6 +168,7 @@ export default function LoginPage() {
type="submit"
disabled={loading}
className="group relative w-full flex justify-center py-3 px-4 border border-transparent text-base font-medium rounded-md text-white bg-primary hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary disabled:opacity-50 min-h-[44px]"
data-testid="login-submit-button"
>
{loading ? 'Signing in...' : 'Sign in'}
</button>
Expand Down
82 changes: 55 additions & 27 deletions components/auth/ProfileDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,20 @@
import { useAuth as useAuthHook } from '@/lib/hooks/useAuth'
import Link from 'next/link'

export function ProfileDropdown() {
interface ProfileDropdownProps {
testIdPrefix?: string;
mobileIconOnly?: boolean;
onOpenChange?: (isOpen: boolean) => void;
}

export function ProfileDropdown({ testIdPrefix = "", mobileIconOnly = false, onOpenChange }: ProfileDropdownProps) {
const [isOpen, setIsOpen] = useState(false)

// Helper function to update open state and notify parent
const updateOpenState = (newIsOpen: boolean) => {

Check warning on line 19 in components/auth/ProfileDropdown.tsx

View workflow job for this annotation

GitHub Actions / ⚡ Quick Quality Check

The 'updateOpenState' function makes the dependencies of useEffect Hook (at line 149) change on every render. To fix this, wrap the definition of 'updateOpenState' in its own useCallback() Hook
setIsOpen(newIsOpen)
onOpenChange?.(newIsOpen)
}
const [calendarConnected, setCalendarConnected] = useState(false)
const [calendarLoading, setCalendarLoading] = useState(false)
const [calendarCheckLoading, setCalendarCheckLoading] = useState(true)
Expand Down Expand Up @@ -105,19 +117,13 @@
// Handle sign out
const handleSignOut = async () => {
try {
console.log('🚪 ProfileDropdown: Starting sign out...')
setIsOpen(false)

updateOpenState(false)
// Use the main auth context signOut method
await signOut()

console.log('✅ ProfileDropdown: Sign out completed')
} catch (error) {
console.error('❌ ProfileDropdown: Error signing out:', error)

console.error('Error signing out:', error)
// Even if signOut fails, force a page reload to clear state
if (typeof window !== 'undefined') {
console.log('🔄 ProfileDropdown: Forcing page reload to clear auth state')
window.location.href = '/'
}
}
Expand All @@ -134,36 +140,49 @@
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
setIsOpen(false)
updateOpenState(false)
}
}

document.addEventListener('mousedown', handleClickOutside)
return () => document.removeEventListener('mousedown', handleClickOutside)
}, [])
}, [updateOpenState])

if (!user) return null

return (
<div className="relative" ref={dropdownRef}>
{/* Profile Button */}
<button
onClick={() => setIsOpen(!isOpen)}
className="flex items-center gap-2 bg-muted hover:bg-accent px-3 py-2 rounded-lg transition-colors"
onClick={() => updateOpenState(!isOpen)}
className={`flex items-center ${mobileIconOnly ? 'gap-0 p-2' : 'gap-2 px-3 py-2'} bg-muted hover:bg-accent rounded-lg transition-colors`}
data-testid={`${testIdPrefix}profile-dropdown-button`}
aria-label={`Profile menu for ${getUserDisplayName()}`}
aria-expanded={isOpen}
aria-haspopup="true"
>
<User className="w-4 h-4 text-muted-foreground" />
<span className="text-sm font-medium text-foreground">{getUserDisplayName()}</span>
<ChevronDown className={`w-4 h-4 text-muted-foreground transition-transform ${isOpen ? 'rotate-180' : ''}`} />
<User className={`${mobileIconOnly ? 'w-5 h-5' : 'w-4 h-4'} text-muted-foreground`} />
{!mobileIconOnly && (
<span className="text-sm font-medium text-foreground" data-testid="profile-display-name">{getUserDisplayName()}</span>
)}
{!mobileIconOnly && (
<ChevronDown className={`w-4 h-4 text-muted-foreground transition-transform ${isOpen ? 'rotate-180' : ''}`} />
)}
</button>

{/* Dropdown Menu */}
{isOpen && (
<div className="absolute right-0 mt-2 w-56 bg-card rounded-lg shadow-lg border border-border py-2 z-50">
<div className="px-4 py-2 border-b border-border">
<p className="text-sm font-medium text-foreground truncate">{getUserDisplayName()}</p>
<p className="text-xs text-muted-foreground truncate" title={user.email}>{user.email}</p>
<div
className="absolute right-0 mt-2 w-56 bg-card rounded-lg shadow-lg border border-border py-2 z-50"
data-testid="profile-dropdown-menu"
role="menu"
aria-label="Profile menu"
>
<div className="px-4 py-2 border-b border-border" data-testid="profile-info-section">
<p className="text-sm font-medium text-foreground truncate" data-testid="profile-name">{getUserDisplayName()}</p>
<p className="text-xs text-muted-foreground truncate" title={user.email} data-testid="profile-email">{user.email}</p>
{userProfile?.role && (
<p className="text-xs text-primary capitalize font-medium mt-1">
<p className="text-xs text-primary capitalize font-medium mt-1" data-testid="profile-role">
{userProfile.role}
</p>
)}
Expand All @@ -172,8 +191,10 @@
{/* My Events Link */}
<Link
href="/my-events"
onClick={() => setIsOpen(false)}
onClick={() => updateOpenState(false)}
className="w-full flex items-center gap-2 px-4 py-2 text-sm text-foreground hover:bg-accent transition-colors"
data-testid="profile-my-events-link"
role="menuitem"
>
<Calendar className="w-4 h-4" />
My Events
Expand All @@ -183,8 +204,10 @@
{isStaff && (
<Link
href="/staff"
onClick={() => setIsOpen(false)}
onClick={() => updateOpenState(false)}
className="w-full flex items-center gap-2 px-4 py-2 text-sm text-foreground hover:bg-accent transition-colors"
data-testid="profile-staff-dashboard-link"
role="menuitem"
>
{isAdmin ? <Settings className="w-4 h-4" /> : <BarChart3 className="w-4 h-4" />}
{isAdmin ? 'Admin Dashboard' : 'Staff Dashboard'}
Expand All @@ -194,35 +217,37 @@
<div className="border-t border-border my-1" />

{/* Google Calendar Connection */}
<div className="px-4 py-2">
<div className="px-4 py-2" data-testid="google-calendar-section">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Calendar className="w-4 h-4 text-muted-foreground" />
<span className="text-sm text-foreground">Google Calendar</span>
</div>

{calendarCheckLoading ? (
<Loader2 className="w-3 h-3 animate-spin text-muted-foreground" />
<Loader2 className="w-3 h-3 animate-spin text-muted-foreground" data-testid="calendar-status-loading" />
) : (
<div className="flex items-center gap-2">
{calendarConnected ? (
<>
<CheckCircle className="w-3 h-3 text-green-600" />
<CheckCircle className="w-3 h-3 text-green-600" data-testid="calendar-connected-icon" />
<button
onClick={handleCalendarDisconnect}
disabled={calendarLoading}
className="text-xs text-red-600 hover:text-red-700 disabled:opacity-50"
data-testid="calendar-disconnect-button"
>
{calendarLoading ? 'Disconnecting...' : 'Disconnect'}
</button>
</>
) : (
<>
<AlertCircle className="w-3 h-3 text-amber-500" />
<AlertCircle className="w-3 h-3 text-amber-500" data-testid="calendar-disconnected-icon" />
<button
onClick={handleCalendarConnect}
disabled={calendarLoading}
className="text-xs text-blue-600 hover:text-blue-700 disabled:opacity-50"
data-testid="calendar-connect-button"
>
{calendarLoading ? 'Connecting...' : 'Connect'}
</button>
Expand All @@ -238,6 +263,9 @@
<button
onClick={handleSignOut}
className="w-full flex items-center gap-2 px-4 py-2 text-sm text-foreground hover:bg-accent transition-colors"
data-testid="profile-sign-out-button"
role="menuitem"
aria-label="Sign out of your account"
>
<LogOut className="w-4 h-4" />
Sign Out
Expand Down
Loading
Loading